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:
Evandro Fonseca Junior committed 2025-10-09 00:14:30 -03:00
1 parent 8d6cbeb0bb
commit 9a4e4c762e
51 files changed
+7389 -218

No files matched your search

+60
View File
@@ -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>
);
}
+283
View File
@@ -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>
);
}
+92
View File
@@ -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>
);
}
+20
View File
@@ -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>
);
}