Sync: IrisTEA - Plataforma de Chás Premium

This commit is contained in:
bigtux
2026-02-10 15:46:23 -03:00
parent 0b5b2c7ae6
commit a1ce0a2cad
14 changed files with 1506 additions and 127 deletions

View File

@@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
import { auth } from '@/lib/auth';
import { stripe, PLANS, PlanId } from '@/lib/stripe';
import { stripe, PLANS, PlanType } from '@/lib/stripe';
import { prisma } from '@/lib/prisma';
export async function POST(request: NextRequest) {
@@ -9,22 +9,24 @@ export async function POST(request: NextRequest) {
if (!session?.user?.id) {
return NextResponse.json(
{ error: 'Não autenticado' },
{ error: 'Não autorizado' },
{ status: 401 }
);
}
const body = await request.json();
const { planId } = body as { planId: PlanId };
const { plan } = body as { plan: PlanType };
if (!planId || !PLANS[planId]) {
if (!plan || !PLANS[plan]) {
return NextResponse.json(
{ error: 'Plano inválido' },
{ status: 400 }
);
}
const plan = PLANS[planId];
const selectedPlan = PLANS[plan];
// Buscar ou criar customer no Stripe
const user = await prisma.user.findUnique({
where: { id: session.user.id },
include: { subscription: true },
@@ -37,7 +39,6 @@ export async function POST(request: NextRequest) {
);
}
// Criar ou recuperar customer no Stripe
let customerId = user.subscription?.stripeCustomerId;
if (!customerId) {
@@ -51,31 +52,26 @@ export async function POST(request: NextRequest) {
customerId = customer.id;
// Atualizar subscription com customerId
await prisma.subscription.upsert({
await prisma.subscription.update({
where: { userId: user.id },
update: { stripeCustomerId: customerId },
create: {
userId: user.id,
plan: planId,
status: 'pending',
stripeCustomerId: customerId,
},
data: { stripeCustomerId: customerId },
});
}
// Criar sessão de checkout
const checkoutSession = await stripe.checkout.sessions.create({
customer: customerId,
mode: 'subscription',
payment_method_types: ['card'],
line_items: [
{
price_data: {
currency: 'brl',
product_data: {
name: `Íris TEA - Plano ${plan.name}`,
description: plan.description,
name: `IrisTEA - Plano ${selectedPlan.name}`,
description: selectedPlan.features.join(' • '),
},
unit_amount: plan.price,
unit_amount: selectedPlan.price,
recurring: {
interval: 'month',
},
@@ -83,12 +79,11 @@ export async function POST(request: NextRequest) {
quantity: 1,
},
],
mode: 'subscription',
success_url: `${process.env.AUTH_URL}/checkout/sucesso?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.AUTH_URL}/checkout/cancelado`,
success_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard?success=true`,
cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/planos?canceled=true`,
metadata: {
userId: user.id,
planId,
plan: plan,
},
});
@@ -96,7 +91,7 @@ export async function POST(request: NextRequest) {
} catch (error) {
console.error('Erro no checkout:', error);
return NextResponse.json(
{ error: 'Erro ao criar sessão de checkout' },
{ error: 'Erro ao criar sessão de pagamento' },
{ status: 500 }
);
}

View File

@@ -0,0 +1,129 @@
import { NextRequest, NextResponse } from 'next/server';
import { auth } from '@/lib/auth';
import { prisma } from '@/lib/prisma';
// Buscar progresso das crianças
export async function GET(request: NextRequest) {
try {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ error: 'Não autenticado' }, { status: 401 });
}
const searchParams = request.nextUrl.searchParams;
const childId = searchParams.get('childId');
const days = parseInt(searchParams.get('days') || '30');
const user = await prisma.user.findUnique({
where: { id: session.user.id },
include: { children: true },
});
if (!user) {
return NextResponse.json({ error: 'Usuário não encontrado' }, { status: 404 });
}
// Verificar permissão
let targetChildId = childId;
if (!targetChildId && user.children.length > 0) {
targetChildId = user.children[0].id;
}
if (user.role === 'parent' && targetChildId) {
const isParent = user.children.some(c => c.id === targetChildId);
if (!isParent) {
return NextResponse.json({ error: 'Sem permissão' }, { status: 403 });
}
}
const startDate = new Date();
startDate.setDate(startDate.getDate() - days);
const progress = await prisma.progress.findMany({
where: {
childId: targetChildId || undefined,
date: { gte: startDate },
},
orderBy: { date: 'asc' },
});
// Agrupar por categoria e calcular evolução
const categories = ['comunicacao', 'habilidades_sociais', 'autonomia', 'regulacao_emocional'];
const progressByCategory: Record<string, { dates: string[]; values: number[] }> = {};
categories.forEach(cat => {
const catProgress = progress.filter(p => p.category === cat);
progressByCategory[cat] = {
dates: catProgress.map(p => p.date.toISOString().split('T')[0]),
values: catProgress.map(p => p.value),
};
});
// Calcular último valor de cada categoria
const currentProgress = categories.map(cat => {
const catProgress = progress.filter(p => p.category === cat);
const lastValue = catProgress.length > 0 ? catProgress[catProgress.length - 1].value : 0;
return { category: cat, value: lastValue };
});
return NextResponse.json({
progressByCategory,
currentProgress,
child: user.children.find(c => c.id === targetChildId),
});
} catch (error) {
console.error('Erro ao buscar progresso:', error);
return NextResponse.json(
{ error: 'Erro ao buscar progresso' },
{ status: 500 }
);
}
}
// Registrar progresso (terapeuta)
export async function POST(request: NextRequest) {
try {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ error: 'Não autenticado' }, { status: 401 });
}
const user = await prisma.user.findUnique({
where: { id: session.user.id },
});
if (!user || (user.role !== 'therapist' && user.role !== 'admin')) {
return NextResponse.json({ error: 'Sem permissão' }, { status: 403 });
}
const body = await request.json();
const { childId, category, value, notes } = body;
if (!childId || !category || value === undefined) {
return NextResponse.json(
{ error: 'childId, category e value são obrigatórios' },
{ status: 400 }
);
}
const progress = await prisma.progress.create({
data: {
childId,
category,
value: Math.min(100, Math.max(0, value)), // Clamp 0-100
notes,
date: new Date(),
},
});
return NextResponse.json(progress);
} catch (error) {
console.error('Erro ao registrar progresso:', error);
return NextResponse.json(
{ error: 'Erro ao registrar progresso' },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,321 @@
'use client';
import { useState, useEffect } from 'react';
import Link from 'next/link';
import { useSession } from 'next-auth/react';
import {
ArrowLeft, FileText, Download, Calendar, TrendingUp,
BarChart3, PieChart, Loader2
} from 'lucide-react';
import {
LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend,
ResponsiveContainer, RadarChart, PolarGrid, PolarAngleAxis,
PolarRadiusAxis, Radar, AreaChart, Area
} from 'recharts';
interface ProgressData {
progressByCategory: Record<string, { dates: string[]; values: number[] }>;
currentProgress: { category: string; value: number }[];
child?: { id: string; name: string };
}
const categoryLabels: Record<string, string> = {
comunicacao: 'Comunicação',
habilidades_sociais: 'Habilidades Sociais',
autonomia: 'Autonomia',
regulacao_emocional: 'Regulação Emocional',
};
const categoryColors: Record<string, string> = {
comunicacao: '#3b82f6',
habilidades_sociais: '#22c55e',
autonomia: '#eab308',
regulacao_emocional: '#a855f7',
};
export default function RelatoriosPage() {
const { data: session } = useSession();
const [loading, setLoading] = useState(true);
const [data, setData] = useState<ProgressData | null>(null);
const [period, setPeriod] = useState<'7' | '30' | '90'>('30');
useEffect(() => {
fetchProgress();
}, [period]);
const fetchProgress = async () => {
try {
setLoading(true);
const response = await fetch(`/api/progress?days=${period}`);
if (response.ok) {
const progressData = await response.json();
setData(progressData);
}
} catch (error) {
console.error('Erro ao buscar progresso:', error);
} finally {
setLoading(false);
}
};
const generatePDF = async () => {
// Importar dinamicamente para evitar SSR issues
const html2canvas = (await import('html2canvas')).default;
const { jsPDF } = await import('jspdf');
const content = document.getElementById('report-content');
if (!content) return;
const canvas = await html2canvas(content, { scale: 2 });
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF('p', 'mm', 'a4');
const imgWidth = 210;
const pageHeight = 295;
const imgHeight = (canvas.height * imgWidth) / canvas.width;
let heightLeft = imgHeight;
let position = 0;
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
while (heightLeft >= 0) {
position = heightLeft - imgHeight;
pdf.addPage();
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
}
pdf.save(`relatorio-iris-${new Date().toISOString().split('T')[0]}.pdf`);
};
// Preparar dados para gráficos
const prepareLineChartData = () => {
if (!data) return [];
const allDates = new Set<string>();
Object.values(data.progressByCategory).forEach(cat => {
cat.dates.forEach(d => allDates.add(d));
});
const sortedDates = Array.from(allDates).sort();
return sortedDates.map(date => {
const point: Record<string, any> = { date: date.slice(5) }; // MM-DD format
Object.entries(data.progressByCategory).forEach(([cat, catData]) => {
const idx = catData.dates.indexOf(date);
point[cat] = idx !== -1 ? catData.values[idx] : null;
});
return point;
});
};
const prepareRadarData = () => {
if (!data) return [];
return data.currentProgress.map(p => ({
category: categoryLabels[p.category] || p.category,
value: p.value,
fullMark: 100,
}));
};
const lineChartData = prepareLineChartData();
const radarData = prepareRadarData();
return (
<div className="min-h-screen bg-gray-50">
{/* Header */}
<header className="bg-white border-b border-gray-200 px-4 lg:px-8 py-4">
<div className="max-w-7xl mx-auto flex items-center justify-between">
<div className="flex items-center gap-4">
<Link href="/dashboard" className="text-gray-400 hover:text-gray-600">
<ArrowLeft className="w-6 h-6" />
</Link>
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-indigo-100 text-indigo-600 flex items-center justify-center">
<FileText className="w-5 h-5" />
</div>
<div>
<h1 className="text-xl font-bold text-gray-900">Relatórios</h1>
<p className="text-sm text-gray-500">
{data?.child ? `Progresso de ${data.child.name}` : 'Acompanhamento de progresso'}
</p>
</div>
</div>
</div>
<div className="flex items-center gap-4">
{/* Period selector */}
<div className="flex bg-gray-100 rounded-lg p-1">
{[
{ value: '7', label: '7 dias' },
{ value: '30', label: '30 dias' },
{ value: '90', label: '90 dias' },
].map(p => (
<button
key={p.value}
onClick={() => setPeriod(p.value as any)}
className={`px-3 py-1.5 text-sm rounded-md transition ${
period === p.value
? 'bg-white shadow text-indigo-600'
: 'text-gray-600 hover:text-gray-900'
}`}
>
{p.label}
</button>
))}
</div>
<button
onClick={generatePDF}
className="flex items-center gap-2 bg-indigo-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-indigo-700 transition"
>
<Download className="w-4 h-4" />
Exportar PDF
</button>
</div>
</div>
</header>
<div className="max-w-7xl mx-auto p-4 lg:p-8" id="report-content">
{loading ? (
<div className="flex justify-center py-20">
<Loader2 className="w-12 h-12 text-indigo-500 animate-spin" />
</div>
) : (
<div className="space-y-8">
{/* Current Progress Cards */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
{data?.currentProgress.map(p => (
<div key={p.category} className="bg-white rounded-xl shadow-sm p-6">
<div className="flex items-center justify-between mb-4">
<span className="text-sm font-medium text-gray-500">
{categoryLabels[p.category]}
</span>
<TrendingUp
className="w-5 h-5"
style={{ color: categoryColors[p.category] }}
/>
</div>
<div className="text-3xl font-bold text-gray-900 mb-2">
{p.value}%
</div>
<div className="h-2 bg-gray-200 rounded-full">
<div
className="h-2 rounded-full transition-all"
style={{
width: `${p.value}%`,
backgroundColor: categoryColors[p.category],
}}
/>
</div>
</div>
))}
</div>
{/* Charts */}
<div className="grid lg:grid-cols-2 gap-8">
{/* Line Chart */}
<div className="bg-white rounded-xl shadow-sm p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-6 flex items-center gap-2">
<BarChart3 className="w-5 h-5 text-indigo-600" />
Evolução ao Longo do Tempo
</h3>
{lineChartData.length > 0 ? (
<ResponsiveContainer width="100%" height={300}>
<AreaChart data={lineChartData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="date" />
<YAxis domain={[0, 100]} />
<Tooltip />
<Legend />
{Object.keys(categoryLabels).map(cat => (
<Area
key={cat}
type="monotone"
dataKey={cat}
name={categoryLabels[cat]}
stroke={categoryColors[cat]}
fill={categoryColors[cat]}
fillOpacity={0.1}
connectNulls
/>
))}
</AreaChart>
</ResponsiveContainer>
) : (
<div className="h-[300px] flex items-center justify-center text-gray-500">
Sem dados para o período selecionado
</div>
)}
</div>
{/* Radar Chart */}
<div className="bg-white rounded-xl shadow-sm p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-6 flex items-center gap-2">
<PieChart className="w-5 h-5 text-indigo-600" />
Visão Geral das Áreas
</h3>
{radarData.length > 0 ? (
<ResponsiveContainer width="100%" height={300}>
<RadarChart data={radarData}>
<PolarGrid />
<PolarAngleAxis dataKey="category" />
<PolarRadiusAxis domain={[0, 100]} />
<Radar
name="Progresso"
dataKey="value"
stroke="#6366f1"
fill="#6366f1"
fillOpacity={0.5}
/>
</RadarChart>
</ResponsiveContainer>
) : (
<div className="h-[300px] flex items-center justify-center text-gray-500">
Sem dados para exibir
</div>
)}
</div>
</div>
{/* Summary */}
<div className="bg-white rounded-xl shadow-sm p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">
Resumo do Período
</h3>
<div className="prose prose-sm max-w-none text-gray-600">
<p>
Nos últimos <strong>{period} dias</strong>, observamos progresso em todas as áreas de desenvolvimento.
</p>
{data?.currentProgress && data.currentProgress.length > 0 && (
<>
<p>
A área com melhor desempenho é <strong>
{categoryLabels[data.currentProgress.reduce((a, b) =>
a.value > b.value ? a : b
).category]}
</strong> com {Math.max(...data.currentProgress.map(p => p.value))}% de progresso.
</p>
<p>
Recomendamos focar em <strong>
{categoryLabels[data.currentProgress.reduce((a, b) =>
a.value < b.value ? a : b
).category]}
</strong> nas próximas sessões para equilibrar o desenvolvimento.
</p>
</>
)}
</div>
</div>
</div>
)}
</div>
</div>
);
}

View File

@@ -1,21 +1,22 @@
@import "tailwindcss";
:root {
--iris-purple: #6366f1;
--iris-purple-dark: #4f46e5;
--iris-blue: #3b82f6;
--iris-green: #10b981;
--iris-yellow: #f59e0b;
--iris-red: #ef4444;
--iris-rainbow: linear-gradient(90deg, #ef4444, #f59e0b, #eab308, #22c55e, #3b82f6, #8b5cf6);
@theme {
--color-iris-purple: #6366f1;
--color-iris-purple-dark: #4f46e5;
--color-iris-blue: #3b82f6;
--color-iris-green: #10b981;
--color-iris-yellow: #f59e0b;
--color-iris-red: #ef4444;
}
@source "../**/*.{js,ts,jsx,tsx,mdx}";
body {
font-family: 'Inter', system-ui, sans-serif;
}
.gradient-rainbow {
background: var(--iris-rainbow);
background: linear-gradient(90deg, #ef4444, #f59e0b, #eab308, #22c55e, #3b82f6, #8b5cf6);
}
.text-gradient {
@@ -33,25 +34,3 @@ body {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.chat-bubble {
position: relative;
}
.chat-bubble::after {
content: '';
position: absolute;
bottom: -8px;
left: 20px;
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid #f3f4f6;
}
.chat-bubble-right::after {
left: auto;
right: 20px;
border-top-color: #6366f1;
}

132
src/app/lancamento/page.tsx Normal file
View File

@@ -0,0 +1,132 @@
'use client';
import { useState } from 'react';
import { Sparkles, Mail, ArrowRight, Check, Heart, Brain, Users, Calendar } from 'lucide-react';
export default function LancamentoPage() {
const [email, setEmail] = useState('');
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setStatus('loading');
await new Promise(resolve => setTimeout(resolve, 1000));
setStatus('success');
setEmail('');
};
return (
<div className="min-h-screen bg-gradient-to-br from-indigo-900 via-purple-900 to-indigo-800 text-white">
<div className="max-w-4xl mx-auto px-4 py-16 min-h-screen flex flex-col justify-center">
{/* Logo */}
<div className="text-center mb-12">
<div className="inline-flex items-center gap-3 mb-6">
<div className="w-16 h-16 rounded-2xl bg-gradient-to-br from-pink-500 via-purple-500 to-indigo-500 flex items-center justify-center shadow-2xl">
<Sparkles className="w-8 h-8 text-white" />
</div>
<span className="text-5xl font-bold">IrisTEA</span>
</div>
<div className="inline-block px-4 py-2 bg-white/10 rounded-full text-sm font-medium mb-8">
🚀 Em breve
</div>
<h1 className="text-4xl md:text-6xl font-bold mb-6 leading-tight">
Terapia ABA acessível<br />
<span className="text-transparent bg-clip-text bg-gradient-to-r from-pink-400 to-yellow-400">
para seu filho com autismo
</span>
</h1>
<p className="text-xl text-white/80 max-w-2xl mx-auto mb-12">
Combinamos inteligência artificial com terapeutas BCBA certificados
para oferecer tratamento de qualidade por uma fração do preço.
</p>
</div>
{/* Features */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-12">
<div className="text-center p-4 bg-white/5 rounded-xl backdrop-blur">
<Brain className="w-8 h-8 mx-auto mb-2 text-pink-400" />
<div className="font-semibold">IA 24/7</div>
<div className="text-sm text-white/60">Suporte contínuo</div>
</div>
<div className="text-center p-4 bg-white/5 rounded-xl backdrop-blur">
<Users className="w-8 h-8 mx-auto mb-2 text-pink-400" />
<div className="font-semibold">BCBAs</div>
<div className="text-sm text-white/60">Certificados</div>
</div>
<div className="text-center p-4 bg-white/5 rounded-xl backdrop-blur">
<Calendar className="w-8 h-8 mx-auto mb-2 text-pink-400" />
<div className="font-semibold">Flexível</div>
<div className="text-sm text-white/60">Sua agenda</div>
</div>
<div className="text-center p-4 bg-white/5 rounded-xl backdrop-blur">
<Heart className="w-8 h-8 mx-auto mb-2 text-pink-400" />
<div className="font-semibold">R$297</div>
<div className="text-sm text-white/60">A partir de</div>
</div>
</div>
{/* Waitlist Form */}
<div className="max-w-md mx-auto w-full">
<div className="bg-white/10 backdrop-blur-lg rounded-2xl p-8 shadow-2xl">
<h2 className="text-2xl font-bold text-center mb-2">
Entre na lista de espera
</h2>
<p className="text-white/70 text-center mb-6">
Seja o primeiro a saber quando lançarmos
</p>
{status === 'success' ? (
<div className="text-center py-4">
<div className="w-16 h-16 bg-green-500 rounded-full flex items-center justify-center mx-auto mb-4">
<Check className="w-8 h-8" />
</div>
<p className="text-xl font-semibold">Você está na lista! 🎉</p>
<p className="text-white/70 mt-2">Avisaremos quando lançarmos.</p>
</div>
) : (
<form onSubmit={handleSubmit} className="space-y-4">
<div className="relative">
<Mail className="w-5 h-5 text-white/50 absolute left-4 top-1/2 -translate-y-1/2" />
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Seu melhor e-mail"
required
className="w-full pl-12 pr-4 py-4 bg-white/10 border border-white/20 rounded-xl focus:ring-2 focus:ring-pink-500 focus:border-transparent outline-none text-white placeholder-white/50"
/>
</div>
<button
type="submit"
disabled={status === 'loading'}
className="w-full bg-gradient-to-r from-pink-500 to-purple-600 py-4 rounded-xl font-semibold flex items-center justify-center gap-2 hover:from-pink-600 hover:to-purple-700 transition disabled:opacity-50"
>
{status === 'loading' ? (
<div className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
) : (
<>
Quero ser avisado
<ArrowRight className="w-5 h-5" />
</>
)}
</button>
</form>
)}
</div>
<p className="text-center text-white/50 text-sm mt-6">
+500 famílias na lista de espera
</p>
</div>
{/* Footer */}
<div className="text-center mt-16 text-white/50 text-sm">
© 2026 IrisTEA · Terapia ABA Inteligente
</div>
</div>
</div>
);
}

208
src/app/planos/page.tsx Normal file
View File

@@ -0,0 +1,208 @@
'use client';
import Link from 'next/link';
import { useState, Suspense } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { Sparkles, Check, ArrowRight, AlertCircle } from 'lucide-react';
const plans = [
{
id: 'essencial',
name: 'Essencial',
price: 297,
sessions: 1,
features: [
'IA ilimitada 24/7',
'1 sessão BCBA/mês',
'Dashboard de progresso',
'Relatórios automáticos',
],
},
{
id: 'completo',
name: 'Completo',
price: 497,
sessions: 2,
popular: true,
features: [
'Tudo do Essencial',
'2 sessões BCBA/mês',
'Grupo de pais',
'Prioridade no suporte',
],
},
{
id: 'intensivo',
name: 'Intensivo',
price: 897,
sessions: 4,
features: [
'Tudo do Completo',
'4 sessões BCBA/mês',
'Equipe multidisciplinar',
'Plano personalizado',
],
},
];
function PlanosContent() {
const router = useRouter();
const searchParams = useSearchParams();
const canceled = searchParams.get('canceled');
const [loading, setLoading] = useState<string | null>(null);
const [error, setError] = useState('');
const handleSelectPlan = async (planId: string) => {
setLoading(planId);
setError('');
try {
const response = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ plan: planId }),
});
const data = await response.json();
if (!response.ok) {
if (response.status === 401) {
router.push('/login?redirect=/planos');
return;
}
throw new Error(data.error || 'Erro ao processar');
}
if (data.url) {
window.location.href = data.url;
}
} catch (err) {
setError('Erro ao iniciar pagamento. Tente novamente.');
setLoading(null);
}
};
return (
<>
{canceled && (
<div className="max-w-md mx-auto mb-8 p-4 bg-yellow-50 border border-yellow-200 rounded-xl text-yellow-800 flex items-center gap-3">
<AlertCircle className="w-5 h-5 flex-shrink-0" />
<p>Pagamento cancelado. Escolha um plano para continuar.</p>
</div>
)}
{error && (
<div className="max-w-md mx-auto mb-8 p-4 bg-red-50 border border-red-200 rounded-xl text-red-700 text-center">
{error}
</div>
)}
{/* Plans Grid */}
<div className="grid md:grid-cols-3 gap-8">
{plans.map((plan) => (
<div
key={plan.id}
className={`relative bg-white rounded-2xl shadow-xl p-8 ${
plan.popular ? 'ring-2 ring-indigo-600' : ''
}`}
>
{plan.popular && (
<div className="absolute -top-4 left-1/2 -translate-x-1/2 bg-indigo-600 text-white px-4 py-1 rounded-full text-sm font-medium">
Mais Popular
</div>
)}
<div className="text-center mb-6">
<h3 className="text-2xl font-bold text-gray-900 mb-2">
{plan.name}
</h3>
<div className="flex items-baseline justify-center gap-1">
<span className="text-4xl font-bold text-indigo-600">
R$ {plan.price}
</span>
<span className="text-gray-500">/mês</span>
</div>
<p className="text-sm text-gray-500 mt-2">
{plan.sessions} {plan.sessions === 1 ? 'sessão' : 'sessões'} BCBA/mês
</p>
</div>
<ul className="space-y-4 mb-8">
{plan.features.map((feature, idx) => (
<li key={idx} className="flex items-center gap-3">
<div className="w-5 h-5 rounded-full bg-green-100 flex items-center justify-center flex-shrink-0">
<Check className="w-3 h-3 text-green-600" />
</div>
<span className="text-gray-700">{feature}</span>
</li>
))}
</ul>
<button
onClick={() => handleSelectPlan(plan.id)}
disabled={loading !== null}
className={`w-full py-3 rounded-xl font-medium flex items-center justify-center gap-2 transition ${
plan.popular
? 'bg-indigo-600 text-white hover:bg-indigo-700'
: 'bg-gray-100 text-gray-900 hover:bg-gray-200'
} disabled:opacity-50`}
>
{loading === plan.id ? (
<div className="w-5 h-5 border-2 border-current/30 border-t-current rounded-full animate-spin" />
) : (
<>
Assinar agora
<ArrowRight className="w-4 h-4" />
</>
)}
</button>
</div>
))}
</div>
</>
);
}
export default function PlanosPage() {
return (
<div className="min-h-screen bg-gradient-to-b from-indigo-50 to-white">
{/* Header */}
<header className="bg-white border-b border-gray-100">
<div className="max-w-6xl mx-auto px-4 py-4">
<Link href="/" className="flex items-center gap-2">
<div className="w-10 h-10 rounded-xl gradient-rainbow flex items-center justify-center">
<Sparkles className="w-6 h-6 text-white" />
</div>
<span className="text-2xl font-bold text-gradient">Íris</span>
</Link>
</div>
</header>
{/* Content */}
<div className="max-w-6xl mx-auto px-4 py-12">
<div className="text-center mb-12">
<h1 className="text-4xl font-bold text-gray-900 mb-4">
Escolha seu plano
</h1>
<p className="text-xl text-gray-600">
Comece a transformar o desenvolvimento do seu filho hoje
</p>
</div>
<Suspense fallback={<div className="text-center">Carregando...</div>}>
<PlanosContent />
</Suspense>
{/* FAQ */}
<div className="mt-16 text-center">
<p className="text-gray-500">
Dúvidas? Entre em contato pelo WhatsApp{' '}
<a href="https://wa.me/5511999999999" className="text-indigo-600 hover:underline">
(11) 99999-9999
</a>
</p>
</div>
</div>
</div>
);
}