feat(ui): enhance footer with NutriScan branding and fix database path
- Redesigned Footer.tsx with comprehensive layout including brand section, navigation links, social media icons, and contact info to improve user engagement and brand visibility - Updated DATABASE_URL in .env to correct Prisma database path for proper configuration and connectivity - Updated dependencies to support new UI components and ensure compatibility with enhanced features
This commit is contained in:
1 parent
8d6cbeb0bb
commit
9a4e4c762e
51 files changed
+7389
-218
No files matched your search
@@ -0,0 +1,60 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
interface BillingToggleProps {
|
||||
isAnnual: boolean;
|
||||
onToggle: (isAnnual: boolean) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function BillingToggle({
|
||||
isAnnual,
|
||||
onToggle,
|
||||
className = "",
|
||||
}: BillingToggleProps) {
|
||||
return (
|
||||
<div className={`flex items-center justify-center ${className}`}>
|
||||
<div className="relative inline-flex items-center bg-gray-100 dark:bg-gray-800 rounded-lg p-1">
|
||||
{/* Toggle Background */}
|
||||
<div
|
||||
className={`absolute top-1 bottom-1 w-1/2 bg-white dark:bg-gray-700 rounded-md shadow-sm transition-transform duration-200 ${
|
||||
isAnnual ? "translate-x-full" : "translate-x-0"
|
||||
}`}
|
||||
/>
|
||||
|
||||
{/* Monthly Option */}
|
||||
<button
|
||||
onClick={() => onToggle(false)}
|
||||
className={`relative z-10 px-6 py-2 text-sm font-medium rounded-md transition-colors duration-200 ${
|
||||
!isAnnual
|
||||
? "text-gray-900 dark:text-white"
|
||||
: "text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300"
|
||||
}`}
|
||||
>
|
||||
Mensal
|
||||
</button>
|
||||
|
||||
{/* Annual Option */}
|
||||
<button
|
||||
onClick={() => onToggle(true)}
|
||||
className={`relative z-10 px-6 py-2 text-sm font-medium rounded-md transition-colors duration-200 ${
|
||||
isAnnual
|
||||
? "text-gray-900 dark:text-white"
|
||||
: "text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300"
|
||||
}`}
|
||||
>
|
||||
Anual
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Save Badge */}
|
||||
{isAnnual && (
|
||||
<Badge className="ml-3 bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300">
|
||||
Economize 20%
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
CheckCircle2,
|
||||
XCircle,
|
||||
Zap,
|
||||
BarChart3,
|
||||
GitCompare,
|
||||
Download,
|
||||
Code,
|
||||
MessageSquare,
|
||||
Shield,
|
||||
} from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||
|
||||
interface Feature {
|
||||
text: string;
|
||||
included: boolean;
|
||||
highlight?: boolean;
|
||||
note?: string;
|
||||
}
|
||||
|
||||
interface PricingCardProps {
|
||||
plan: "basic" | "premium" | "free";
|
||||
isAnnual?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const planData = {
|
||||
basic: {
|
||||
name: "Básico",
|
||||
monthlyPrice: 5,
|
||||
annualPrice: 4,
|
||||
description: "Perfeito para uso pessoal e pequenas consultas",
|
||||
badge: null,
|
||||
features: [
|
||||
{ text: "Até 100 consultas/mês", included: true },
|
||||
{ text: "Busca por código de barras", included: true },
|
||||
{ text: "Busca por nome", included: true },
|
||||
{ text: "Informações nutricionais completas", included: true },
|
||||
{ text: "Histórico (últimas 10)", included: true },
|
||||
{ text: "Filtros avançados", included: false },
|
||||
{ text: "Listas de compras", included: false },
|
||||
{ text: "Comparação de produtos", included: false },
|
||||
{ text: "Exportação de dados", included: false },
|
||||
{ text: "API de acesso", included: false },
|
||||
{ text: "Suporte prioritário", included: false },
|
||||
],
|
||||
},
|
||||
premium: {
|
||||
name: "Premium",
|
||||
monthlyPrice: 10,
|
||||
annualPrice: 8,
|
||||
description: "Ideal para profissionais e uso intensivo",
|
||||
badge: "Popular",
|
||||
features: [
|
||||
{ text: "Até 500 consultas/mês", included: true },
|
||||
{ text: "Busca por código de barras", included: true },
|
||||
{ text: "Busca por nome", included: true },
|
||||
{ text: "Informações nutricionais completas", included: true },
|
||||
{ text: "Histórico ilimitado", included: true },
|
||||
{ text: "Filtros avançados", included: true },
|
||||
{ text: "Listas de compras ilimitadas", included: true },
|
||||
{ text: "Comparação de produtos (até 5)", included: true },
|
||||
{ text: "Exportação de dados (CSV/JSON)", included: true },
|
||||
{ text: "API de acesso (1000 req/mês)", included: true },
|
||||
{ text: "Suporte prioritário", included: true },
|
||||
],
|
||||
},
|
||||
free: {
|
||||
name: "Free Beta",
|
||||
monthlyPrice: 0,
|
||||
annualPrice: 0,
|
||||
description: "Acesso completo durante fase beta",
|
||||
badge: "Beta",
|
||||
features: [
|
||||
{ text: "Consultas ilimitadas", included: true, highlight: true },
|
||||
{ text: "Busca por código de barras", included: true },
|
||||
{ text: "Busca por nome", included: true },
|
||||
{ text: "Informações nutricionais completas", included: true },
|
||||
{ text: "Histórico ilimitado", included: true },
|
||||
{ text: "Filtros avançados", included: true },
|
||||
{ text: "Listas de compras ilimitadas", included: true },
|
||||
{ text: "Comparação de produtos", included: true },
|
||||
{ text: "Exportação de dados", included: true },
|
||||
{ text: "Badge Beta Tester", included: true, highlight: true },
|
||||
{ text: "Acesso temporário", included: true, note: "Durante fase beta" },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const featureIcons: Record<string, any> = {
|
||||
consultas: Zap,
|
||||
filtros: BarChart3,
|
||||
comparação: GitCompare,
|
||||
exportação: Download,
|
||||
api: Code,
|
||||
suporte: MessageSquare,
|
||||
beta: Shield,
|
||||
};
|
||||
|
||||
export function PricingCard({
|
||||
plan,
|
||||
isAnnual = false,
|
||||
className = "",
|
||||
}: PricingCardProps) {
|
||||
const data = planData[plan];
|
||||
const price = isAnnual ? data.annualPrice : data.monthlyPrice;
|
||||
const isPopular = plan === "premium";
|
||||
const isFree = plan === "free";
|
||||
|
||||
const getFeatureIcon = (featureText: string) => {
|
||||
if (featureText.includes("consultas") || featureText.includes("API"))
|
||||
return Zap;
|
||||
if (featureText.includes("filtros")) return BarChart3;
|
||||
if (featureText.includes("comparação")) return GitCompare;
|
||||
if (featureText.includes("exportação")) return Download;
|
||||
if (featureText.includes("suporte")) return MessageSquare;
|
||||
if (featureText.includes("Beta")) return Shield;
|
||||
return CheckCircle2;
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
className={`relative overflow-hidden transition-all duration-300 hover:shadow-xl ${
|
||||
isPopular
|
||||
? "bg-gradient-to-br from-blue-600 to-blue-700 text-white scale-105 border-2 border-blue-500 shadow-2xl"
|
||||
: isFree
|
||||
? "border-blue-200 dark:border-blue-800 bg-white dark:bg-gray-900"
|
||||
: "bg-white dark:bg-gray-900 border-gray-100 dark:border-gray-800"
|
||||
} ${className}`}
|
||||
>
|
||||
{/* Badge */}
|
||||
{data.badge && (
|
||||
<div className="absolute top-0 right-0">
|
||||
<Badge
|
||||
className={`${
|
||||
isPopular
|
||||
? "bg-[#19A7A7] text-white"
|
||||
: isFree
|
||||
? "bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300"
|
||||
: "bg-gray-100 text-gray-700"
|
||||
} rounded-l-none rounded-br-lg`}
|
||||
>
|
||||
{data.badge}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<CardHeader className="pb-4">
|
||||
<div className="text-center">
|
||||
<h3
|
||||
className={`text-2xl font-bold ${
|
||||
isPopular ? "text-white" : "text-gray-900 dark:text-white"
|
||||
}`}
|
||||
>
|
||||
{data.name}
|
||||
</h3>
|
||||
<p
|
||||
className={`text-sm mt-2 ${
|
||||
isPopular ? "text-blue-100" : "text-gray-600 dark:text-gray-400"
|
||||
}`}
|
||||
>
|
||||
{data.description}
|
||||
</p>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="space-y-6">
|
||||
{/* Preço */}
|
||||
<div className="text-center">
|
||||
<div className="flex items-baseline justify-center">
|
||||
<span
|
||||
className={`text-5xl font-bold ${
|
||||
isPopular ? "text-white" : "text-gray-900 dark:text-white"
|
||||
}`}
|
||||
>
|
||||
${price}
|
||||
</span>
|
||||
{!isFree && (
|
||||
<span
|
||||
className={`text-sm ml-1 ${
|
||||
isPopular ? "text-blue-100" : "text-gray-500"
|
||||
}`}
|
||||
>
|
||||
/mês
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{isAnnual && !isFree && (
|
||||
<p
|
||||
className={`text-xs mt-1 ${
|
||||
isPopular ? "text-blue-100" : "text-green-600"
|
||||
}`}
|
||||
>
|
||||
Economize ${(data.monthlyPrice - data.annualPrice) * 12}/ano
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Botões */}
|
||||
<div className="space-y-3">
|
||||
<Button
|
||||
className={`w-full ${
|
||||
isPopular
|
||||
? "bg-[#19A7A7] hover:bg-teal-600 text-white"
|
||||
: isFree
|
||||
? "bg-[#0055FF] hover:bg-blue-600 text-white"
|
||||
: "bg-[#0055FF] hover:bg-blue-600 text-white"
|
||||
}`}
|
||||
size="lg"
|
||||
>
|
||||
{isFree ? "Começar Grátis" : "Começar Agora"}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className={`w-full ${
|
||||
isPopular
|
||||
? "border-blue-400 text-blue-100 hover:bg-blue-500/20"
|
||||
: "border-gray-300 text-gray-700 hover:bg-gray-50"
|
||||
}`}
|
||||
>
|
||||
Falar com Vendas
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Features */}
|
||||
<div className="space-y-3">
|
||||
{data.features.map((feature, index) => {
|
||||
const IconComponent = getFeatureIcon(feature.text);
|
||||
return (
|
||||
<div key={index} className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 mt-0.5">
|
||||
{feature.included ? (
|
||||
<CheckCircle2
|
||||
className={`w-4 h-4 ${
|
||||
feature.highlight
|
||||
? "text-green-500"
|
||||
: isPopular
|
||||
? "text-green-400"
|
||||
: "text-green-500"
|
||||
}`}
|
||||
/>
|
||||
) : (
|
||||
<XCircle
|
||||
className={`w-4 h-4 ${
|
||||
isPopular ? "text-red-400" : "text-gray-400"
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<span
|
||||
className={`text-sm ${
|
||||
feature.highlight
|
||||
? "font-semibold text-green-600 dark:text-green-400"
|
||||
: isPopular
|
||||
? "text-white"
|
||||
: "text-gray-700 dark:text-gray-300"
|
||||
}`}
|
||||
>
|
||||
{feature.text}
|
||||
</span>
|
||||
{feature.note && (
|
||||
<p
|
||||
className={`text-xs mt-1 ${
|
||||
isPopular ? "text-blue-100" : "text-gray-500"
|
||||
}`}
|
||||
>
|
||||
{feature.note}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
"use client";
|
||||
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion";
|
||||
|
||||
interface PricingFAQProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const faqData = [
|
||||
{
|
||||
question: "Como funciona o período de teste?",
|
||||
answer:
|
||||
"Oferecemos acesso gratuito durante nossa fase beta, sem limitações de tempo ou funcionalidades. Você pode usar todas as funcionalidades do Premium sem custo algum.",
|
||||
},
|
||||
{
|
||||
question: "Posso mudar de plano a qualquer momento?",
|
||||
answer:
|
||||
"Sim! Você pode fazer upgrade ou downgrade do seu plano a qualquer momento. As mudanças são aplicadas imediatamente e os valores são calculados proporcionalmente.",
|
||||
},
|
||||
{
|
||||
question: "O que acontece quando eu atingo o limite de consultas?",
|
||||
answer:
|
||||
"Quando você atinge o limite mensal de consultas, você pode fazer upgrade para um plano superior ou aguardar o próximo ciclo de cobrança. Também oferecemos pacotes adicionais de consultas.",
|
||||
},
|
||||
{
|
||||
question: "Como faço para cancelar minha assinatura?",
|
||||
answer:
|
||||
"Você pode cancelar sua assinatura a qualquer momento através da sua conta. O cancelamento é efetivado no final do período de cobrança atual, e você mantém acesso até lá.",
|
||||
},
|
||||
{
|
||||
question: "Vocês oferecem reembolso?",
|
||||
answer:
|
||||
"Sim! Oferecemos reembolso completo nos primeiros 30 dias após a assinatura. Entre em contato conosco se não estiver satisfeito com o serviço.",
|
||||
},
|
||||
{
|
||||
question: "Os dados são seguros e privados?",
|
||||
answer:
|
||||
"Absolutamente! Todos os dados são criptografados e armazenados de forma segura. Não compartilhamos suas informações pessoais ou histórico de consultas com terceiros.",
|
||||
},
|
||||
{
|
||||
question: "A API está disponível em todos os planos?",
|
||||
answer:
|
||||
"A API está disponível apenas nos planos Premium e Free Beta. O plano Básico não inclui acesso à API, mas você pode fazer upgrade a qualquer momento.",
|
||||
},
|
||||
{
|
||||
question: "Há suporte técnico disponível?",
|
||||
answer:
|
||||
"Sim! Oferecemos suporte por email para todos os usuários e suporte prioritário (email + chat) para usuários Premium. Nossa equipe responde em até 24 horas.",
|
||||
},
|
||||
];
|
||||
|
||||
export function PricingFAQ({ className = "" }: PricingFAQProps) {
|
||||
return (
|
||||
<div className={`max-w-4xl mx-auto ${className}`}>
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
Perguntas Frequentes
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 dark:text-gray-400">
|
||||
Tire suas dúvidas sobre nossos planos e funcionalidades
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Accordion type="single" collapsible className="space-y-4">
|
||||
{faqData.map((item, index) => (
|
||||
<AccordionItem
|
||||
key={index}
|
||||
value={`item-${index}`}
|
||||
className="border border-gray-100 dark:border-gray-800 rounded-lg px-6"
|
||||
>
|
||||
<AccordionTrigger className="text-left hover:no-underline py-6">
|
||||
<span className="text-lg font-medium text-gray-900 dark:text-white">
|
||||
{item.question}
|
||||
</span>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="pb-6">
|
||||
<p className="text-gray-600 dark:text-gray-400 leading-relaxed">
|
||||
{item.answer}
|
||||
</p>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
"use client";
|
||||
|
||||
interface PricingHeroProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function PricingHero({ className = "" }: PricingHeroProps) {
|
||||
return (
|
||||
<div className={`text-center ${className}`}>
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-gray-900 dark:text-white mb-6">
|
||||
Escolha o plano perfeito para você
|
||||
</h1>
|
||||
<p className="text-lg md:text-xl text-gray-600 dark:text-gray-400 max-w-3xl mx-auto">
|
||||
Acesse informações nutricionais completas de milhares de produtos. Desde
|
||||
uso pessoal até profissional, temos a solução ideal para suas
|
||||
necessidades.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user