feat: Fase 1 - Autenticação completa
- Prisma com SQLite configurado - Tabelas: users, sessions, subscriptions, children, etc - Auth.js com credentials provider - API de registro com criação de usuário + criança - Middleware para proteger rotas - Login/Cadastro funcionais - Dashboard com sessão real
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -39,3 +39,5 @@ yarn-error.log*
|
|||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
|
||||||
|
/src/generated/prisma
|
||||||
|
|||||||
336
docs/MANUAL_MODELOS.md
Normal file
336
docs/MANUAL_MODELOS.md
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
# IrisTEA - Manual de Modelos de Negócio
|
||||||
|
|
||||||
|
## Visão Geral
|
||||||
|
|
||||||
|
A IrisTEA pode operar em **3 modelos diferentes**, cada um com suas vantagens:
|
||||||
|
|
||||||
|
| Modelo | Descrição | Custo Operacional | Escalabilidade |
|
||||||
|
|--------|-----------|-------------------|----------------|
|
||||||
|
| **100% IA** | Terapeuta virtual IA | Muito baixo | Infinita |
|
||||||
|
| **Híbrido** | IA + Supervisão humana | Médio | Alta |
|
||||||
|
| **100% Humano** | Terapeutas BCBA via teleterapia | Alto | Limitada |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## MODELO 1: 100% IA (Atual)
|
||||||
|
|
||||||
|
### Como Funciona
|
||||||
|
```
|
||||||
|
Criança ←→ Plataforma IA ←→ Análise Automática
|
||||||
|
↓
|
||||||
|
Relatórios para Pais
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tecnologias
|
||||||
|
- **GPT-4 / Claude** para conversação e orientação
|
||||||
|
- **Visão Computacional** para análise de comportamentos
|
||||||
|
- **Speech-to-Text** para análise de fala
|
||||||
|
- **Machine Learning** para personalização
|
||||||
|
|
||||||
|
### Preços Sugeridos
|
||||||
|
| Plano | Preço | Margem |
|
||||||
|
|-------|-------|--------|
|
||||||
|
| Básico | R$ 97/mês | ~90% |
|
||||||
|
| Completo | R$ 197/mês | ~92% |
|
||||||
|
| Premium | R$ 397/mês | ~85% (inclui BCBA) |
|
||||||
|
|
||||||
|
### Vantagens
|
||||||
|
- ✅ Disponível 24/7
|
||||||
|
- ✅ Sem lista de espera
|
||||||
|
- ✅ Custo muito baixo para o cliente
|
||||||
|
- ✅ Escalabilidade infinita
|
||||||
|
- ✅ Consistência total
|
||||||
|
- ✅ Análise de dados em tempo real
|
||||||
|
|
||||||
|
### Desvantagens
|
||||||
|
- ❌ Casos complexos precisam de humano
|
||||||
|
- ❌ Resistência de alguns pais
|
||||||
|
- ❌ Regulamentação incerta
|
||||||
|
- ❌ Não substitui diagnóstico
|
||||||
|
|
||||||
|
### Custos Operacionais (por usuário/mês)
|
||||||
|
- API OpenAI/Anthropic: ~R$ 15-30
|
||||||
|
- Infraestrutura: ~R$ 5
|
||||||
|
- **Total: ~R$ 20-35/usuário**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## MODELO 2: HÍBRIDO (Recomendado para começar)
|
||||||
|
|
||||||
|
### Como Funciona
|
||||||
|
```
|
||||||
|
Criança ←→ Plataforma IA (80% do tempo)
|
||||||
|
↓
|
||||||
|
BCBA Supervisor (20% do tempo)
|
||||||
|
↓
|
||||||
|
Plano Terapêutico Validado
|
||||||
|
```
|
||||||
|
|
||||||
|
### Estrutura
|
||||||
|
1. **IA faz o dia a dia:**
|
||||||
|
- Sessões interativas
|
||||||
|
- Atividades gamificadas
|
||||||
|
- Coleta de dados
|
||||||
|
- Relatórios automáticos
|
||||||
|
|
||||||
|
2. **BCBA humano faz:**
|
||||||
|
- Avaliação inicial (1 sessão)
|
||||||
|
- Supervisão mensal (1-2 sessões)
|
||||||
|
- Ajustes no plano terapêutico
|
||||||
|
- Casos de crise
|
||||||
|
|
||||||
|
### Preços Sugeridos
|
||||||
|
| Plano | Preço | Sessões BCBA |
|
||||||
|
|-------|-------|--------------|
|
||||||
|
| Essencial | R$ 297/mês | 1/mês |
|
||||||
|
| Completo | R$ 497/mês | 2/mês |
|
||||||
|
| Intensivo | R$ 897/mês | 4/mês |
|
||||||
|
|
||||||
|
### Vantagens
|
||||||
|
- ✅ Credibilidade (tem humano)
|
||||||
|
- ✅ Atende casos complexos
|
||||||
|
- ✅ Mais fácil de regulamentar
|
||||||
|
- ✅ IA reduz custo do BCBA
|
||||||
|
- ✅ Escalável (menos humanos necessários)
|
||||||
|
|
||||||
|
### Desvantagens
|
||||||
|
- ❌ Precisa contratar BCBAs
|
||||||
|
- ❌ Gestão de agenda
|
||||||
|
- ❌ Custo maior que 100% IA
|
||||||
|
|
||||||
|
### Custos Operacionais (por usuário/mês)
|
||||||
|
- API IA: ~R$ 20
|
||||||
|
- BCBA (1h/mês): ~R$ 100-150
|
||||||
|
- Infraestrutura: ~R$ 5
|
||||||
|
- **Total: ~R$ 125-175/usuário**
|
||||||
|
|
||||||
|
### Como Contratar BCBAs
|
||||||
|
1. **CLT**: Salário R$ 8.000-15.000 + benefícios
|
||||||
|
- 1 BCBA atende ~40 famílias/mês
|
||||||
|
- Custo/família: ~R$ 200-375
|
||||||
|
|
||||||
|
2. **PJ/Freelancer**: R$ 150-300/hora
|
||||||
|
- Mais flexível
|
||||||
|
- Sem vínculo
|
||||||
|
- Custo/família (1h/mês): R$ 150-300
|
||||||
|
|
||||||
|
3. **Parceria com Clínicas**: Comissão 30-40%
|
||||||
|
- Eles fornecem os BCBAs
|
||||||
|
- Você fornece a plataforma e clientes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## MODELO 3: 100% HUMANO (Estilo AnswersNow)
|
||||||
|
|
||||||
|
### Como Funciona
|
||||||
|
```
|
||||||
|
Criança ←→ BCBA via Vídeo ←→ Plataforma
|
||||||
|
↓
|
||||||
|
Sessões Semanais (2-8x)
|
||||||
|
↓
|
||||||
|
Relatórios Manuais
|
||||||
|
```
|
||||||
|
|
||||||
|
### Estrutura da Equipe
|
||||||
|
```
|
||||||
|
CEO
|
||||||
|
├── Diretor Clínico (BCBA-D)
|
||||||
|
│ ├── BCBA Supervisores (5-10)
|
||||||
|
│ │ └── Cada BCBA atende 15-20 famílias
|
||||||
|
│ └── Coordenador de Casos
|
||||||
|
├── Diretor Comercial
|
||||||
|
│ ├── Vendas/Intake
|
||||||
|
│ └── Marketing
|
||||||
|
└── Diretor de Tecnologia
|
||||||
|
├── Desenvolvedores
|
||||||
|
└── Suporte
|
||||||
|
```
|
||||||
|
|
||||||
|
### Preços Sugeridos (Mercado Brasil)
|
||||||
|
| Plano | Sessões/mês | Preço |
|
||||||
|
|-------|-------------|-------|
|
||||||
|
| Básico | 4 (1x/semana) | R$ 800/mês |
|
||||||
|
| Padrão | 8 (2x/semana) | R$ 1.400/mês |
|
||||||
|
| Intensivo | 16 (4x/semana) | R$ 2.400/mês |
|
||||||
|
|
||||||
|
### Preços AnswersNow (EUA - referência)
|
||||||
|
- ~$200-400/semana = $800-1.600/mês
|
||||||
|
- Convertido: R$ 4.000-8.000/mês
|
||||||
|
|
||||||
|
### Vantagens
|
||||||
|
- ✅ Modelo comprovado
|
||||||
|
- ✅ Regulamentação clara
|
||||||
|
- ✅ Confiança dos pais
|
||||||
|
- ✅ Atende qualquer complexidade
|
||||||
|
|
||||||
|
### Desvantagens
|
||||||
|
- ❌ Alto custo operacional
|
||||||
|
- ❌ Escala limitada (precisa contratar)
|
||||||
|
- ❌ Lista de espera inevitável
|
||||||
|
- ❌ Dependência de profissionais
|
||||||
|
|
||||||
|
### Custos Operacionais
|
||||||
|
- BCBA (4h/mês por família): R$ 400-600
|
||||||
|
- Plataforma: R$ 20
|
||||||
|
- Overhead (admin, comercial): R$ 100
|
||||||
|
- **Total: ~R$ 520-720/usuário**
|
||||||
|
- **Margem**: 30-50% nos planos
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## COMPARATIVO FINANCEIRO
|
||||||
|
|
||||||
|
### Cenário: 500 famílias ativas
|
||||||
|
|
||||||
|
| Métrica | 100% IA | Híbrido | 100% Humano |
|
||||||
|
|---------|---------|---------|-------------|
|
||||||
|
| **Receita/mês** | R$ 98.500 | R$ 248.500 | R$ 700.000 |
|
||||||
|
| **Custo/mês** | R$ 17.500 | R$ 87.500 | R$ 350.000 |
|
||||||
|
| **Lucro/mês** | R$ 81.000 | R$ 161.000 | R$ 350.000 |
|
||||||
|
| **Margem** | 82% | 65% | 50% |
|
||||||
|
| **Equipe necessária** | 3-5 | 10-15 | 30-50 |
|
||||||
|
| **Tempo p/ escalar** | Dias | Meses | Anos |
|
||||||
|
|
||||||
|
### Cenário: 5.000 famílias
|
||||||
|
|
||||||
|
| Métrica | 100% IA | Híbrido | 100% Humano |
|
||||||
|
|---------|---------|---------|-------------|
|
||||||
|
| **Receita/mês** | R$ 985.000 | R$ 2.485.000 | R$ 7.000.000 |
|
||||||
|
| **Custo/mês** | R$ 175.000 | R$ 875.000 | R$ 3.500.000 |
|
||||||
|
| **Lucro/mês** | R$ 810.000 | R$ 1.610.000 | R$ 3.500.000 |
|
||||||
|
| **Equipe** | 10-15 | 50-80 | 200-300 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## RECOMENDAÇÃO
|
||||||
|
|
||||||
|
### Fase 1 (0-6 meses): Modelo Híbrido Light
|
||||||
|
- Lançar com IA + 1 sessão BCBA/mês
|
||||||
|
- Validar produto e mercado
|
||||||
|
- Contratar 2-3 BCBAs PJ
|
||||||
|
- Meta: 100 famílias
|
||||||
|
|
||||||
|
### Fase 2 (6-18 meses): Escalar Híbrido
|
||||||
|
- Aumentar base de BCBAs
|
||||||
|
- Melhorar IA com dados reais
|
||||||
|
- Parcerias com clínicas
|
||||||
|
- Meta: 1.000 famílias
|
||||||
|
|
||||||
|
### Fase 3 (18+ meses): Migrar para IA
|
||||||
|
- IA já treinada com dados suficientes
|
||||||
|
- Oferecer planos 100% IA (mais baratos)
|
||||||
|
- Manter híbrido como premium
|
||||||
|
- Meta: 10.000+ famílias
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## REQUISITOS LEGAIS
|
||||||
|
|
||||||
|
### Para operar no Brasil:
|
||||||
|
|
||||||
|
1. **Empresa**
|
||||||
|
- CNPJ (LTDA ou SA)
|
||||||
|
- CNAE: 8650-0/03 (atividades de psicologia)
|
||||||
|
|
||||||
|
2. **Responsável Técnico**
|
||||||
|
- BCBA ou Psicólogo com CRP
|
||||||
|
- Assina como responsável clínico
|
||||||
|
|
||||||
|
3. **Regulamentação**
|
||||||
|
- CFP (se psicólogo) / CRP estadual
|
||||||
|
- BACB (para BCBAs internacionais)
|
||||||
|
- LGPD compliance
|
||||||
|
|
||||||
|
4. **Seguro**
|
||||||
|
- Responsabilidade Civil Profissional
|
||||||
|
- ~R$ 3.000-5.000/ano
|
||||||
|
|
||||||
|
### Considerações sobre IA
|
||||||
|
- Não existe regulamentação específica para "terapeuta IA"
|
||||||
|
- Recomendação: posicionar como "ferramenta de apoio"
|
||||||
|
- Ter sempre supervisão humana disponível
|
||||||
|
- Deixar claro que não substitui diagnóstico médico
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STACK TECNOLÓGICA
|
||||||
|
|
||||||
|
### MVP (o que já temos)
|
||||||
|
- Next.js + TypeScript
|
||||||
|
- Tailwind CSS
|
||||||
|
- Autenticação JWT
|
||||||
|
|
||||||
|
### Próximos Passos
|
||||||
|
```
|
||||||
|
1. Backend
|
||||||
|
- Node.js/Go API
|
||||||
|
- PostgreSQL
|
||||||
|
- Redis (cache/sessões)
|
||||||
|
|
||||||
|
2. IA/ML
|
||||||
|
- OpenAI GPT-4 API
|
||||||
|
- Azure Cognitive Services (visão)
|
||||||
|
- Whisper (transcrição)
|
||||||
|
|
||||||
|
3. Vídeo
|
||||||
|
- Azure Communication Services
|
||||||
|
- ou Daily.co / Twilio
|
||||||
|
|
||||||
|
4. Pagamentos
|
||||||
|
- Stripe ou Pagar.me
|
||||||
|
- Assinaturas recorrentes
|
||||||
|
|
||||||
|
5. Infraestrutura
|
||||||
|
- DigitalOcean / AWS
|
||||||
|
- CDN para assets
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ROADMAP SUGERIDO
|
||||||
|
|
||||||
|
### Mês 1-2
|
||||||
|
- [ ] Finalizar landing page
|
||||||
|
- [ ] Implementar autenticação
|
||||||
|
- [ ] Integrar pagamentos
|
||||||
|
- [ ] Chat com IA básico
|
||||||
|
|
||||||
|
### Mês 3-4
|
||||||
|
- [ ] Dashboard para pais
|
||||||
|
- [ ] Sistema de sessões
|
||||||
|
- [ ] Relatórios automáticos
|
||||||
|
- [ ] Contratar 2 BCBAs PJ
|
||||||
|
|
||||||
|
### Mês 5-6
|
||||||
|
- [ ] Lançamento beta (50 famílias)
|
||||||
|
- [ ] Coletar feedback
|
||||||
|
- [ ] Iterar produto
|
||||||
|
- [ ] Preparar lançamento oficial
|
||||||
|
|
||||||
|
### Mês 7-12
|
||||||
|
- [ ] Lançamento oficial
|
||||||
|
- [ ] Marketing digital
|
||||||
|
- [ ] Parcerias com clínicas
|
||||||
|
- [ ] Meta: 500 famílias
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CONTATOS ÚTEIS
|
||||||
|
|
||||||
|
### Associações
|
||||||
|
- ABPMC (Análise do Comportamento): abpmc.org.br
|
||||||
|
- SBNp (Neuropsicologia): sbnp.org.br
|
||||||
|
|
||||||
|
### Certificação BCBA
|
||||||
|
- BACB: bacb.com
|
||||||
|
- Cursos no Brasil: várias universidades
|
||||||
|
|
||||||
|
### Referências
|
||||||
|
- AnswersNow: getanswersnow.com
|
||||||
|
- Rethink: rethinkfirst.com
|
||||||
|
- Catalyst: catalyst.care
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Documento criado em Fevereiro/2026*
|
||||||
|
*IrisTEA - Terapia ABA Inteligente*
|
||||||
266
docs/MODELO_NEGOCIO.md
Normal file
266
docs/MODELO_NEGOCIO.md
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
# IrisTEA - Modelo de Negócio
|
||||||
|
|
||||||
|
## 🎯 O que é
|
||||||
|
|
||||||
|
**Plataforma de apoio ao desenvolvimento infantil para crianças com TEA**, combinando:
|
||||||
|
- Atividades interativas guiadas por IA
|
||||||
|
- Supervisão de profissionais certificados (BCBAs)
|
||||||
|
- Treinamento de pais/cuidadores
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 Como Funciona
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ FAMÍLIA │
|
||||||
|
│ │ │
|
||||||
|
│ ▼ │
|
||||||
|
│ ┌─────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ PLATAFORMA IRISTEA │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
|
||||||
|
│ │ │ IA │ │ BCBA │ │ DADOS │ │ │
|
||||||
|
│ │ │ 80% │ + │ 20% │ = │ANALYTICS │ │ │
|
||||||
|
│ │ │ do tempo │ │ do tempo │ │ │ │ │
|
||||||
|
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
|
||||||
|
│ │ │ │ │ │ │
|
||||||
|
│ │ ▼ ▼ ▼ │ │
|
||||||
|
│ │ • Atividades • Avaliação • Relatórios │ │
|
||||||
|
│ │ • Chat 24/7 • Supervisão • Progresso │ │
|
||||||
|
│ │ • Exercícios • Plano • Métricas │ │
|
||||||
|
│ │ • Gamificação • Ajustes • Insights │ │
|
||||||
|
│ └─────────────────────────────────────────────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ▼ │
|
||||||
|
│ CRIANÇA EVOLUI │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💰 Modelo de Receita
|
||||||
|
|
||||||
|
### Planos de Assinatura (B2C)
|
||||||
|
|
||||||
|
| Plano | Preço | O que inclui |
|
||||||
|
|-------|-------|--------------|
|
||||||
|
| **Essencial** | R$ 297/mês | IA ilimitada + 1 sessão BCBA/mês |
|
||||||
|
| **Completo** | R$ 497/mês | IA ilimitada + 2 sessões BCBA/mês + grupo pais |
|
||||||
|
| **Intensivo** | R$ 897/mês | IA ilimitada + 4 sessões BCBA/mês + multidisciplinar |
|
||||||
|
|
||||||
|
### Receita Adicional (Futuro)
|
||||||
|
|
||||||
|
| Fonte | Modelo |
|
||||||
|
|-------|--------|
|
||||||
|
| **B2B Clínicas** | Licenciamento da plataforma (R$ 2.000-5.000/mês) |
|
||||||
|
| **B2B Escolas** | Pacotes institucionais |
|
||||||
|
| **B2B Planos de Saúde** | Convênio/parceria |
|
||||||
|
| **Cursos para Pais** | Venda avulsa (R$ 197-497) |
|
||||||
|
| **Certificação ABA** | Parceria com instituições |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Unit Economics
|
||||||
|
|
||||||
|
### Por Família (Plano Completo - R$ 497/mês)
|
||||||
|
|
||||||
|
**Receita:** R$ 497
|
||||||
|
|
||||||
|
**Custos Variáveis:**
|
||||||
|
| Item | Custo |
|
||||||
|
|------|-------|
|
||||||
|
| API IA (OpenAI/Azure) | R$ 25 |
|
||||||
|
| BCBA (2h × R$ 80/h) | R$ 160 |
|
||||||
|
| Infraestrutura | R$ 10 |
|
||||||
|
| Pagamento (3.5%) | R$ 17 |
|
||||||
|
| **Total Variável** | **R$ 212** |
|
||||||
|
|
||||||
|
**Margem Bruta:** R$ 285 (57%)
|
||||||
|
|
||||||
|
### Projeção Escala
|
||||||
|
|
||||||
|
| Famílias | Receita/mês | Custo Var. | Margem Bruta |
|
||||||
|
|----------|-------------|------------|--------------|
|
||||||
|
| 100 | R$ 49.700 | R$ 21.200 | R$ 28.500 |
|
||||||
|
| 500 | R$ 248.500 | R$ 106.000 | R$ 142.500 |
|
||||||
|
| 1.000 | R$ 497.000 | R$ 212.000 | R$ 285.000 |
|
||||||
|
| 5.000 | R$ 2.485.000 | R$ 1.060.000 | R$ 1.425.000 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 👥 Estrutura Operacional
|
||||||
|
|
||||||
|
### Equipe Mínima (Fase 1: 0-200 famílias)
|
||||||
|
|
||||||
|
| Cargo | Qtd | Salário | Total/mês |
|
||||||
|
|-------|-----|---------|-----------|
|
||||||
|
| CEO/Fundador | 1 | Pró-labore | R$ 5.000 |
|
||||||
|
| Desenvolvedor Full Stack | 1 | R$ 12.000 | R$ 12.000 |
|
||||||
|
| BCBA Responsável Técnico | 1 | R$ 10.000 | R$ 10.000 |
|
||||||
|
| BCBAs PJ (atendimento) | 3 | R$ 80/h | ~R$ 15.000 |
|
||||||
|
| Marketing/Comercial | 1 | R$ 6.000 | R$ 6.000 |
|
||||||
|
| **Total Folha** | | | **R$ 48.000** |
|
||||||
|
|
||||||
|
### Equipe Escala (Fase 2: 500-1000 famílias)
|
||||||
|
|
||||||
|
| Cargo | Qtd | Total/mês |
|
||||||
|
|-------|-----|-----------|
|
||||||
|
| Diretoria | 2 | R$ 25.000 |
|
||||||
|
| Desenvolvimento | 3 | R$ 40.000 |
|
||||||
|
| Clínico (BCBAs) | 10 | R$ 80.000 |
|
||||||
|
| Comercial/Marketing | 3 | R$ 25.000 |
|
||||||
|
| Suporte/CS | 2 | R$ 10.000 |
|
||||||
|
| Administrativo | 1 | R$ 5.000 |
|
||||||
|
| **Total** | **21** | **R$ 185.000** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Funil de Vendas
|
||||||
|
|
||||||
|
```
|
||||||
|
TOPO (Awareness)
|
||||||
|
│ • Anúncios Facebook/Instagram
|
||||||
|
│ • Google Ads (palavras: autismo, TEA, ABA)
|
||||||
|
│ • Conteúdo blog/YouTube
|
||||||
|
│ • Parcerias com influenciadores
|
||||||
|
│
|
||||||
|
▼ 10.000 visitantes/mês
|
||||||
|
|
||||||
|
MEIO (Consideração)
|
||||||
|
│ • Landing page
|
||||||
|
│ • Lead magnet (e-book gratuito)
|
||||||
|
│ • Webinars gratuitos
|
||||||
|
│ • Avaliação gratuita
|
||||||
|
│
|
||||||
|
▼ 500 leads/mês (5% conversão)
|
||||||
|
|
||||||
|
FUNDO (Decisão)
|
||||||
|
│ • Trial 7 dias grátis
|
||||||
|
│ • Ligação de vendas
|
||||||
|
│ • Demonstração personalizada
|
||||||
|
│
|
||||||
|
▼ 50 clientes/mês (10% conversão)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Métricas Alvo
|
||||||
|
|
||||||
|
| Métrica | Meta |
|
||||||
|
|---------|------|
|
||||||
|
| CAC (Custo Aquisição Cliente) | < R$ 300 |
|
||||||
|
| LTV (Lifetime Value) | > R$ 3.000 |
|
||||||
|
| LTV/CAC | > 10x |
|
||||||
|
| Churn mensal | < 5% |
|
||||||
|
| NPS | > 60 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏆 Diferenciais Competitivos
|
||||||
|
|
||||||
|
### vs Clínicas Tradicionais
|
||||||
|
| Tradicional | IrisTEA |
|
||||||
|
|-------------|---------|
|
||||||
|
| R$ 2.000-5.000/mês | R$ 297-897/mês |
|
||||||
|
| 3-12 meses de espera | Início em 48h |
|
||||||
|
| Horário comercial | 24/7 com IA |
|
||||||
|
| 1 cidade | Brasil todo |
|
||||||
|
| Relatórios manuais | Automáticos em tempo real |
|
||||||
|
|
||||||
|
### vs Apps Educativos
|
||||||
|
| Apps genéricos | IrisTEA |
|
||||||
|
|----------------|---------|
|
||||||
|
| Atividades genéricas | Baseado em ABA |
|
||||||
|
| Sem profissional | BCBA supervisionando |
|
||||||
|
| Sem personalização | IA adaptativa |
|
||||||
|
| Sem relatórios | Dashboard completo |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📅 Roadmap
|
||||||
|
|
||||||
|
### Q1 2026 (Jan-Mar)
|
||||||
|
- [x] MVP landing page
|
||||||
|
- [x] Logo e identidade
|
||||||
|
- [ ] Sistema de autenticação
|
||||||
|
- [ ] Integração pagamentos
|
||||||
|
- [ ] Chat com IA básico
|
||||||
|
- [ ] Contratar 2 BCBAs
|
||||||
|
|
||||||
|
### Q2 2026 (Abr-Jun)
|
||||||
|
- [ ] Dashboard completo
|
||||||
|
- [ ] Sistema de sessões vídeo
|
||||||
|
- [ ] Relatórios automáticos
|
||||||
|
- [ ] Beta fechado (50 famílias)
|
||||||
|
- [ ] Validar product-market fit
|
||||||
|
|
||||||
|
### Q3 2026 (Jul-Set)
|
||||||
|
- [ ] Lançamento público
|
||||||
|
- [ ] Marketing digital
|
||||||
|
- [ ] Meta: 200 famílias
|
||||||
|
- [ ] App mobile
|
||||||
|
|
||||||
|
### Q4 2026 (Out-Dez)
|
||||||
|
- [ ] Parcerias clínicas
|
||||||
|
- [ ] B2B para escolas
|
||||||
|
- [ ] Meta: 500 famílias
|
||||||
|
- [ ] Preparar para investimento
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💵 Projeção Financeira (Ano 1)
|
||||||
|
|
||||||
|
| Mês | Famílias | Receita | Custos | Lucro |
|
||||||
|
|-----|----------|---------|--------|-------|
|
||||||
|
| 1 | 10 | R$ 4.970 | R$ 50.000 | -R$ 45.030 |
|
||||||
|
| 2 | 25 | R$ 12.425 | R$ 52.000 | -R$ 39.575 |
|
||||||
|
| 3 | 50 | R$ 24.850 | R$ 55.000 | -R$ 30.150 |
|
||||||
|
| 4 | 80 | R$ 39.760 | R$ 58.000 | -R$ 18.240 |
|
||||||
|
| 5 | 120 | R$ 59.640 | R$ 65.000 | -R$ 5.360 |
|
||||||
|
| 6 | 170 | R$ 84.490 | R$ 75.000 | **R$ 9.490** |
|
||||||
|
| 7 | 220 | R$ 109.340 | R$ 85.000 | R$ 24.340 |
|
||||||
|
| 8 | 280 | R$ 139.160 | R$ 95.000 | R$ 44.160 |
|
||||||
|
| 9 | 350 | R$ 173.950 | R$ 110.000 | R$ 63.950 |
|
||||||
|
| 10 | 420 | R$ 208.740 | R$ 125.000 | R$ 83.740 |
|
||||||
|
| 11 | 480 | R$ 238.560 | R$ 140.000 | R$ 98.560 |
|
||||||
|
| 12 | 550 | R$ 273.350 | R$ 155.000 | R$ 118.350 |
|
||||||
|
| **ANO** | | **R$ 1.369.235** | **R$ 1.065.000** | **R$ 304.235** |
|
||||||
|
|
||||||
|
**Break-even:** Mês 6 (~150 famílias)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Necessidade de Investimento
|
||||||
|
|
||||||
|
### Para chegar ao break-even (6 meses)
|
||||||
|
|
||||||
|
| Item | Valor |
|
||||||
|
|------|-------|
|
||||||
|
| Desenvolvimento plataforma | R$ 100.000 |
|
||||||
|
| Marketing inicial | R$ 80.000 |
|
||||||
|
| Equipe (6 meses) | R$ 288.000 |
|
||||||
|
| Infraestrutura | R$ 20.000 |
|
||||||
|
| Jurídico/Contábil | R$ 15.000 |
|
||||||
|
| Reserva | R$ 47.000 |
|
||||||
|
| **Total** | **R$ 550.000** |
|
||||||
|
|
||||||
|
### Opções de Funding
|
||||||
|
|
||||||
|
1. **Bootstrapping:** Começar menor, crescer orgânico
|
||||||
|
2. **Investidor Anjo:** R$ 300-500k por 10-15%
|
||||||
|
3. **Aceleradoras:** Inovativa, Startup Chile, Y Combinator
|
||||||
|
4. **Venture Capital:** Série Seed após tração
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 Próximos Passos
|
||||||
|
|
||||||
|
1. **Validar demanda:** Criar lista de espera, medir interesse
|
||||||
|
2. **Prototipar IA:** Testar chat com 10 famílias
|
||||||
|
3. **Contratar BCBA:** Responsável técnico
|
||||||
|
4. **Registrar domínio:** iristea.com.br
|
||||||
|
5. **MVP funcional:** 60 dias
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Documento: Modelo de Negócio IrisTEA*
|
||||||
|
*Versão: 1.0 - Fevereiro 2026*
|
||||||
629
package-lock.json
generated
629
package-lock.json
generated
@@ -8,12 +8,20 @@
|
|||||||
"name": "iris",
|
"name": "iris",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@auth/prisma-adapter": "^2.11.1",
|
||||||
|
"@prisma/client": "^6.19.2",
|
||||||
|
"bcrypt": "^6.0.0",
|
||||||
|
"framer-motion": "^12.33.0",
|
||||||
|
"lucide-react": "^0.563.0",
|
||||||
"next": "16.1.6",
|
"next": "16.1.6",
|
||||||
|
"next-auth": "^5.0.0-beta.30",
|
||||||
|
"prisma": "^6.19.2",
|
||||||
"react": "19.2.3",
|
"react": "19.2.3",
|
||||||
"react-dom": "19.2.3"
|
"react-dom": "19.2.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@types/bcrypt": "^6.0.0",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
@@ -36,6 +44,47 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@auth/core": {
|
||||||
|
"version": "0.41.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@auth/core/-/core-0.41.1.tgz",
|
||||||
|
"integrity": "sha512-t9cJ2zNYAdWMacGRMT6+r4xr1uybIdmYa49calBPeTqwgAFPV/88ac9TEvCR85pvATiSPt8VaNf+Gt24JIT/uw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@panva/hkdf": "^1.2.1",
|
||||||
|
"jose": "^6.0.6",
|
||||||
|
"oauth4webapi": "^3.3.0",
|
||||||
|
"preact": "10.24.3",
|
||||||
|
"preact-render-to-string": "6.5.11"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@simplewebauthn/browser": "^9.0.1",
|
||||||
|
"@simplewebauthn/server": "^9.0.2",
|
||||||
|
"nodemailer": "^7.0.7"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@simplewebauthn/browser": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@simplewebauthn/server": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"nodemailer": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@auth/prisma-adapter": {
|
||||||
|
"version": "2.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@auth/prisma-adapter/-/prisma-adapter-2.11.1.tgz",
|
||||||
|
"integrity": "sha512-Ke7DXP0Fy0Mlmjz/ZJLXwQash2UkA4621xCM0rMtEczr1kppLc/njCbUkHkIQ/PnmILjqSPEKeTjDPsYruvkug==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@auth/core": "0.41.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@prisma/client": ">=2.26.0 || >=3 || >=4 || >=5 || >=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.29.0",
|
"version": "7.29.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
||||||
@@ -1226,6 +1275,94 @@
|
|||||||
"node": ">=12.4.0"
|
"node": ">=12.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@panva/hkdf": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/panva"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/client": {
|
||||||
|
"version": "6.19.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.19.2.tgz",
|
||||||
|
"integrity": "sha512-gR2EMvfK/aTxsuooaDA32D8v+us/8AAet+C3J1cc04SW35FPdZYgLF+iN4NDLUgAaUGTKdAB0CYenu1TAgGdMg==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"prisma": "*",
|
||||||
|
"typescript": ">=5.1.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"prisma": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/config": {
|
||||||
|
"version": "6.19.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.19.2.tgz",
|
||||||
|
"integrity": "sha512-kadBGDl+aUswv/zZMk9Mx0C8UZs1kjao8H9/JpI4Wh4SHZaM7zkTwiKn/iFLfRg+XtOAo/Z/c6pAYhijKl0nzQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"c12": "3.1.0",
|
||||||
|
"deepmerge-ts": "7.1.5",
|
||||||
|
"effect": "3.18.4",
|
||||||
|
"empathic": "2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/debug": {
|
||||||
|
"version": "6.19.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.2.tgz",
|
||||||
|
"integrity": "sha512-lFnEZsLdFLmEVCVNdskLDCL8Uup41GDfU0LUfquw+ercJC8ODTuL0WNKgOKmYxCJVvFwf0OuZBzW99DuWmoH2A==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/engines": {
|
||||||
|
"version": "6.19.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.19.2.tgz",
|
||||||
|
"integrity": "sha512-TTkJ8r+uk/uqczX40wb+ODG0E0icVsMgwCTyTHXehaEfb0uo80M9g1aW1tEJrxmFHeOZFXdI2sTA1j1AgcHi4A==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/debug": "6.19.2",
|
||||||
|
"@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7",
|
||||||
|
"@prisma/fetch-engine": "6.19.2",
|
||||||
|
"@prisma/get-platform": "6.19.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/engines-version": {
|
||||||
|
"version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7.tgz",
|
||||||
|
"integrity": "sha512-03bgb1VD5gvuumNf+7fVGBzfpJPjmqV423l/WxsWk2cNQ42JD0/SsFBPhN6z8iAvdHs07/7ei77SKu7aZfq8bA==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/fetch-engine": {
|
||||||
|
"version": "6.19.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.19.2.tgz",
|
||||||
|
"integrity": "sha512-h4Ff4Pho+SR1S8XerMCC12X//oY2bG3Iug/fUnudfcXEUnIeRiBdXHFdGlGOgQ3HqKgosTEhkZMvGM9tWtYC+Q==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/debug": "6.19.2",
|
||||||
|
"@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7",
|
||||||
|
"@prisma/get-platform": "6.19.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/get-platform": {
|
||||||
|
"version": "6.19.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.19.2.tgz",
|
||||||
|
"integrity": "sha512-PGLr06JUSTqIvztJtAzIxOwtWKtJm5WwOG6xpsgD37Rc84FpfUBGLKz65YpJBGtkRQGXTYEFie7pYALocC3MtA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/debug": "6.19.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rtsao/scc": {
|
"node_modules/@rtsao/scc": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
||||||
@@ -1233,6 +1370,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@standard-schema/spec": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@swc/helpers": {
|
"node_modules/@swc/helpers": {
|
||||||
"version": "0.5.15",
|
"version": "0.5.15",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
||||||
@@ -1524,6 +1667,16 @@
|
|||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/bcrypt": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/estree": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||||
@@ -2415,6 +2568,20 @@
|
|||||||
"baseline-browser-mapping": "dist/cli.js"
|
"baseline-browser-mapping": "dist/cli.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bcrypt": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"node-addon-api": "^8.3.0",
|
||||||
|
"node-gyp-build": "^4.8.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "1.1.12",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||||
@@ -2473,6 +2640,34 @@
|
|||||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/c12": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"chokidar": "^4.0.3",
|
||||||
|
"confbox": "^0.2.2",
|
||||||
|
"defu": "^6.1.4",
|
||||||
|
"dotenv": "^16.6.1",
|
||||||
|
"exsolve": "^1.0.7",
|
||||||
|
"giget": "^2.0.0",
|
||||||
|
"jiti": "^2.4.2",
|
||||||
|
"ohash": "^2.0.11",
|
||||||
|
"pathe": "^2.0.3",
|
||||||
|
"perfect-debounce": "^1.0.0",
|
||||||
|
"pkg-types": "^2.2.0",
|
||||||
|
"rc9": "^2.1.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"magicast": "^0.3.5"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"magicast": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/call-bind": {
|
"node_modules/call-bind": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
|
||||||
@@ -2570,6 +2765,30 @@
|
|||||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/chokidar": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"readdirp": "^4.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14.16.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/citty": {
|
||||||
|
"version": "0.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz",
|
||||||
|
"integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"consola": "^3.2.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/client-only": {
|
"node_modules/client-only": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||||
@@ -2603,6 +2822,21 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/confbox": {
|
||||||
|
"version": "0.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz",
|
||||||
|
"integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/consola": {
|
||||||
|
"version": "3.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
|
||||||
|
"integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.18.0 || >=16.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/convert-source-map": {
|
"node_modules/convert-source-map": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||||
@@ -2718,6 +2952,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/deepmerge-ts": {
|
||||||
|
"version": "7.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz",
|
||||||
|
"integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/define-data-property": {
|
"node_modules/define-data-property": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||||
@@ -2754,6 +2997,18 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/defu": {
|
||||||
|
"version": "6.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
|
||||||
|
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/destr": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/detect-libc": {
|
"node_modules/detect-libc": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||||
@@ -2777,6 +3032,18 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dotenv": {
|
||||||
|
"version": "16.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
|
||||||
|
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://dotenvx.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dunder-proto": {
|
"node_modules/dunder-proto": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
@@ -2792,6 +3059,16 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/effect": {
|
||||||
|
"version": "3.18.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz",
|
||||||
|
"integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@standard-schema/spec": "^1.0.0",
|
||||||
|
"fast-check": "^3.23.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.286",
|
"version": "1.5.286",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz",
|
||||||
@@ -2806,6 +3083,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/empathic": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/enhanced-resolve": {
|
"node_modules/enhanced-resolve": {
|
||||||
"version": "5.19.0",
|
"version": "5.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz",
|
||||||
@@ -3444,6 +3730,34 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/exsolve": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/fast-check": {
|
||||||
|
"version": "3.23.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz",
|
||||||
|
"integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/dubzzz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/fast-check"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"pure-rand": "^6.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fast-deep-equal": {
|
"node_modules/fast-deep-equal": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
@@ -3585,6 +3899,33 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/framer-motion": {
|
||||||
|
"version": "12.33.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.33.0.tgz",
|
||||||
|
"integrity": "sha512-ca8d+rRPcDP5iIF+MoT3WNc0KHJMjIyFAbtVLvM9eA7joGSpeqDfiNH/kCs1t4CHi04njYvWyj0jS4QlEK/rJQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-dom": "^12.33.0",
|
||||||
|
"motion-utils": "^12.29.2",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/is-prop-valid": "*",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/is-prop-valid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/function-bind": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
@@ -3716,6 +4057,23 @@
|
|||||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/giget": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"citty": "^0.1.6",
|
||||||
|
"consola": "^3.4.0",
|
||||||
|
"defu": "^6.1.4",
|
||||||
|
"node-fetch-native": "^1.6.6",
|
||||||
|
"nypm": "^0.6.0",
|
||||||
|
"pathe": "^2.0.3"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"giget": "dist/cli.mjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/glob-parent": {
|
"node_modules/glob-parent": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||||
@@ -4393,12 +4751,20 @@
|
|||||||
"version": "2.6.1",
|
"version": "2.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
|
||||||
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
|
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"jiti": "lib/jiti-cli.mjs"
|
"jiti": "lib/jiti-cli.mjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jose": {
|
||||||
|
"version": "6.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz",
|
||||||
|
"integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/panva"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
@@ -4833,6 +5199,15 @@
|
|||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lucide-react": {
|
||||||
|
"version": "0.563.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.563.0.tgz",
|
||||||
|
"integrity": "sha512-8dXPB2GI4dI8jV4MgUDGBeLdGk8ekfqVZ0BdLcrRzocGgG75ltNEmWS+gE7uokKF/0oSUuczNDT+g9hFJ23FkA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.30.21",
|
"version": "0.30.21",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||||
@@ -4900,6 +5275,21 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/motion-dom": {
|
||||||
|
"version": "12.33.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.33.0.tgz",
|
||||||
|
"integrity": "sha512-XRPebVypsl0UM+7v0Hr8o9UAj0S2djsQWRdHBd5iVouVpMrQqAI0C/rDAT3QaYnXnHuC5hMcwDHCboNeyYjPoQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-utils": "^12.29.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/motion-utils": {
|
||||||
|
"version": "12.29.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.29.2.tgz",
|
||||||
|
"integrity": "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
@@ -5001,6 +5391,62 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/next-auth": {
|
||||||
|
"version": "5.0.0-beta.30",
|
||||||
|
"resolved": "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.30.tgz",
|
||||||
|
"integrity": "sha512-+c51gquM3F6nMVmoAusRJ7RIoY0K4Ts9HCCwyy/BRoe4mp3msZpOzYMyb5LAYc1wSo74PMQkGDcaghIO7W6Xjg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@auth/core": "0.41.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@simplewebauthn/browser": "^9.0.1",
|
||||||
|
"@simplewebauthn/server": "^9.0.2",
|
||||||
|
"next": "^14.0.0-0 || ^15.0.0 || ^16.0.0",
|
||||||
|
"nodemailer": "^7.0.7",
|
||||||
|
"react": "^18.2.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@simplewebauthn/browser": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@simplewebauthn/server": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"nodemailer": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/next-auth/node_modules/@auth/core": {
|
||||||
|
"version": "0.41.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@auth/core/-/core-0.41.0.tgz",
|
||||||
|
"integrity": "sha512-Wd7mHPQ/8zy6Qj7f4T46vg3aoor8fskJm6g2Zyj064oQ3+p0xNZXAV60ww0hY+MbTesfu29kK14Zk5d5JTazXQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@panva/hkdf": "^1.2.1",
|
||||||
|
"jose": "^6.0.6",
|
||||||
|
"oauth4webapi": "^3.3.0",
|
||||||
|
"preact": "10.24.3",
|
||||||
|
"preact-render-to-string": "6.5.11"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@simplewebauthn/browser": "^9.0.1",
|
||||||
|
"@simplewebauthn/server": "^9.0.2",
|
||||||
|
"nodemailer": "^6.8.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@simplewebauthn/browser": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@simplewebauthn/server": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"nodemailer": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/next/node_modules/postcss": {
|
"node_modules/next/node_modules/postcss": {
|
||||||
"version": "8.4.31",
|
"version": "8.4.31",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||||
@@ -5029,6 +5475,32 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/node-addon-api": {
|
||||||
|
"version": "8.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz",
|
||||||
|
"integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^18 || ^20 || >= 21"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/node-fetch-native": {
|
||||||
|
"version": "1.6.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz",
|
||||||
|
"integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/node-gyp-build": {
|
||||||
|
"version": "4.8.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
|
||||||
|
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"node-gyp-build": "bin.js",
|
||||||
|
"node-gyp-build-optional": "optional.js",
|
||||||
|
"node-gyp-build-test": "build-test.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-releases": {
|
"node_modules/node-releases": {
|
||||||
"version": "2.0.27",
|
"version": "2.0.27",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
|
||||||
@@ -5036,6 +5508,38 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/nypm": {
|
||||||
|
"version": "0.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.5.tgz",
|
||||||
|
"integrity": "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"citty": "^0.2.0",
|
||||||
|
"pathe": "^2.0.3",
|
||||||
|
"tinyexec": "^1.0.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"nypm": "dist/cli.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/nypm/node_modules/citty": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/citty/-/citty-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-8csy5IBFI2ex2hTVpaHN2j+LNE199AgiI7y4dMintrr8i0lQiFn+0AWMZrWdHKIgMOer65f8IThysYhoReqjWA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/oauth4webapi": {
|
||||||
|
"version": "3.8.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.3.tgz",
|
||||||
|
"integrity": "sha512-pQ5BsX3QRTgnt5HxgHwgunIRaDXBdkT23tf8dfzmtTIL2LTpdmxgbpbBm0VgFWAIDlezQvQCTgnVIUmHupXHxw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/panva"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/object-assign": {
|
"node_modules/object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
@@ -5159,6 +5663,12 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ohash": {
|
||||||
|
"version": "2.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
|
||||||
|
"integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/optionator": {
|
"node_modules/optionator": {
|
||||||
"version": "0.9.4",
|
"version": "0.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||||
@@ -5267,6 +5777,18 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/pathe": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/perfect-debounce": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
@@ -5286,6 +5808,17 @@
|
|||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pkg-types": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"confbox": "^0.2.2",
|
||||||
|
"exsolve": "^1.0.7",
|
||||||
|
"pathe": "^2.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/possible-typed-array-names": {
|
"node_modules/possible-typed-array-names": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
||||||
@@ -5325,6 +5858,25 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/preact": {
|
||||||
|
"version": "10.24.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz",
|
||||||
|
"integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/preact"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/preact-render-to-string": {
|
||||||
|
"version": "6.5.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.11.tgz",
|
||||||
|
"integrity": "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"preact": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prelude-ls": {
|
"node_modules/prelude-ls": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||||
@@ -5335,6 +5887,31 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prisma": {
|
||||||
|
"version": "6.19.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.19.2.tgz",
|
||||||
|
"integrity": "sha512-XTKeKxtQElcq3U9/jHyxSPgiRgeYDKxWTPOf6NkXA0dNj5j40MfEsZkMbyNpwDWCUv7YBFUl7I2VK/6ALbmhEg==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/config": "6.19.2",
|
||||||
|
"@prisma/engines": "6.19.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"prisma": "build/index.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": ">=5.1.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prop-types": {
|
"node_modules/prop-types": {
|
||||||
"version": "15.8.1",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
@@ -5357,6 +5934,22 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pure-rand": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/dubzzz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/fast-check"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/queue-microtask": {
|
"node_modules/queue-microtask": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||||
@@ -5378,6 +5971,16 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/rc9": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"defu": "^6.1.4",
|
||||||
|
"destr": "^2.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react": {
|
"node_modules/react": {
|
||||||
"version": "19.2.3",
|
"version": "19.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
||||||
@@ -5406,6 +6009,19 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/readdirp": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14.18.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/reflect.getprototypeof": {
|
"node_modules/reflect.getprototypeof": {
|
||||||
"version": "1.0.10",
|
"version": "1.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
||||||
@@ -6039,6 +6655,15 @@
|
|||||||
"url": "https://opencollective.com/webpack"
|
"url": "https://opencollective.com/webpack"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tinyexec": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tinyglobby": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.15",
|
"version": "0.2.15",
|
||||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||||
@@ -6240,7 +6865,7 @@
|
|||||||
"version": "5.9.3",
|
"version": "5.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
|
|||||||
@@ -9,12 +9,20 @@
|
|||||||
"lint": "eslint"
|
"lint": "eslint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@auth/prisma-adapter": "^2.11.1",
|
||||||
|
"@prisma/client": "^6.19.2",
|
||||||
|
"bcrypt": "^6.0.0",
|
||||||
|
"framer-motion": "^12.33.0",
|
||||||
|
"lucide-react": "^0.563.0",
|
||||||
"next": "16.1.6",
|
"next": "16.1.6",
|
||||||
|
"next-auth": "^5.0.0-beta.30",
|
||||||
|
"prisma": "^6.19.2",
|
||||||
"react": "19.2.3",
|
"react": "19.2.3",
|
||||||
"react-dom": "19.2.3"
|
"react-dom": "19.2.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@types/bcrypt": "^6.0.0",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
|
|||||||
BIN
prisma/dev.db
Normal file
BIN
prisma/dev.db
Normal file
Binary file not shown.
158
prisma/migrations/20260207031740_init/migration.sql
Normal file
158
prisma/migrations/20260207031740_init/migration.sql
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "User" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"email" TEXT NOT NULL,
|
||||||
|
"password" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"phone" TEXT,
|
||||||
|
"city" TEXT,
|
||||||
|
"state" TEXT,
|
||||||
|
"role" TEXT NOT NULL DEFAULT 'parent',
|
||||||
|
"emailVerified" DATETIME,
|
||||||
|
"image" TEXT,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Account" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
"type" TEXT NOT NULL,
|
||||||
|
"provider" TEXT NOT NULL,
|
||||||
|
"providerAccountId" TEXT NOT NULL,
|
||||||
|
"refresh_token" TEXT,
|
||||||
|
"access_token" TEXT,
|
||||||
|
"expires_at" INTEGER,
|
||||||
|
"token_type" TEXT,
|
||||||
|
"scope" TEXT,
|
||||||
|
"id_token" TEXT,
|
||||||
|
"session_state" TEXT,
|
||||||
|
CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Session" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"sessionToken" TEXT NOT NULL,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
"expires" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "VerificationToken" (
|
||||||
|
"identifier" TEXT NOT NULL,
|
||||||
|
"token" TEXT NOT NULL,
|
||||||
|
"expires" DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Subscription" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
"plan" TEXT NOT NULL,
|
||||||
|
"status" TEXT NOT NULL DEFAULT 'pending',
|
||||||
|
"stripeCustomerId" TEXT,
|
||||||
|
"stripeSubId" TEXT,
|
||||||
|
"currentPeriodEnd" DATETIME,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "Subscription_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Child" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"parentId" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"birthDate" DATETIME NOT NULL,
|
||||||
|
"diagnosis" TEXT NOT NULL,
|
||||||
|
"diagnosisDate" TEXT,
|
||||||
|
"currentTherapies" TEXT,
|
||||||
|
"priorities" TEXT,
|
||||||
|
"challenges" TEXT,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "Child_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "TherapistPatient" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"therapistId" TEXT NOT NULL,
|
||||||
|
"patientId" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT "TherapistPatient_therapistId_fkey" FOREIGN KEY ("therapistId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT "TherapistPatient_patientId_fkey" FOREIGN KEY ("patientId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Appointment" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"childId" TEXT NOT NULL,
|
||||||
|
"therapistId" TEXT NOT NULL,
|
||||||
|
"scheduledAt" DATETIME NOT NULL,
|
||||||
|
"duration" INTEGER NOT NULL DEFAULT 50,
|
||||||
|
"status" TEXT NOT NULL DEFAULT 'scheduled',
|
||||||
|
"roomUrl" TEXT,
|
||||||
|
"roomName" TEXT,
|
||||||
|
"notes" TEXT,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "Appointment_childId_fkey" FOREIGN KEY ("childId") REFERENCES "Child" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT "Appointment_therapistId_fkey" FOREIGN KEY ("therapistId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "SessionNote" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"appointmentId" TEXT NOT NULL,
|
||||||
|
"childId" TEXT NOT NULL,
|
||||||
|
"therapistId" TEXT NOT NULL,
|
||||||
|
"content" TEXT NOT NULL,
|
||||||
|
"objectives" TEXT,
|
||||||
|
"progress" TEXT,
|
||||||
|
"nextSteps" TEXT,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "SessionNote_appointmentId_fkey" FOREIGN KEY ("appointmentId") REFERENCES "Appointment" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT "SessionNote_childId_fkey" FOREIGN KEY ("childId") REFERENCES "Child" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT "SessionNote_therapistId_fkey" FOREIGN KEY ("therapistId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Progress" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"childId" TEXT NOT NULL,
|
||||||
|
"category" TEXT NOT NULL,
|
||||||
|
"value" INTEGER NOT NULL,
|
||||||
|
"notes" TEXT,
|
||||||
|
"date" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT "Progress_childId_fkey" FOREIGN KEY ("childId") REFERENCES "Child" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Subscription_userId_key" ON "Subscription"("userId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "TherapistPatient_therapistId_patientId_key" ON "TherapistPatient"("therapistId", "patientId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "SessionNote_appointmentId_key" ON "SessionNote"("appointmentId");
|
||||||
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Please do not edit this file manually
|
||||||
|
# It should be added in your version-control system (e.g., Git)
|
||||||
|
provider = "sqlite"
|
||||||
179
prisma/schema.prisma
Normal file
179
prisma/schema.prisma
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
// This is your Prisma schema file,
|
||||||
|
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "sqlite"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ AUTENTICAÇÃO ============
|
||||||
|
|
||||||
|
model User {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
email String @unique
|
||||||
|
password String
|
||||||
|
name String
|
||||||
|
phone String?
|
||||||
|
city String?
|
||||||
|
state String?
|
||||||
|
role String @default("parent") // parent, therapist, admin
|
||||||
|
emailVerified DateTime?
|
||||||
|
image String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
// Relações
|
||||||
|
accounts Account[]
|
||||||
|
sessions Session[]
|
||||||
|
subscription Subscription?
|
||||||
|
children Child[]
|
||||||
|
|
||||||
|
// Para terapeutas
|
||||||
|
patients TherapistPatient[] @relation("TherapistPatients")
|
||||||
|
assignedTo TherapistPatient[] @relation("PatientAssignments")
|
||||||
|
appointments Appointment[] @relation("TherapistAppointments")
|
||||||
|
sessionsNotes SessionNote[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model Account {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
userId String
|
||||||
|
type String
|
||||||
|
provider String
|
||||||
|
providerAccountId String
|
||||||
|
refresh_token String?
|
||||||
|
access_token String?
|
||||||
|
expires_at Int?
|
||||||
|
token_type String?
|
||||||
|
scope String?
|
||||||
|
id_token String?
|
||||||
|
session_state String?
|
||||||
|
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@unique([provider, providerAccountId])
|
||||||
|
}
|
||||||
|
|
||||||
|
model Session {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
sessionToken String @unique
|
||||||
|
userId String
|
||||||
|
expires DateTime
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
}
|
||||||
|
|
||||||
|
model VerificationToken {
|
||||||
|
identifier String
|
||||||
|
token String @unique
|
||||||
|
expires DateTime
|
||||||
|
|
||||||
|
@@unique([identifier, token])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ PAGAMENTOS ============
|
||||||
|
|
||||||
|
model Subscription {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
userId String @unique
|
||||||
|
plan String // essencial, completo, intensivo
|
||||||
|
status String @default("pending") // pending, active, cancelled, expired
|
||||||
|
stripeCustomerId String?
|
||||||
|
stripeSubId String?
|
||||||
|
currentPeriodEnd DateTime?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ CRIANÇAS ============
|
||||||
|
|
||||||
|
model Child {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
parentId String
|
||||||
|
name String
|
||||||
|
birthDate DateTime
|
||||||
|
diagnosis String
|
||||||
|
diagnosisDate String?
|
||||||
|
currentTherapies String? // JSON array
|
||||||
|
priorities String? // JSON array
|
||||||
|
challenges String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
parent User @relation(fields: [parentId], references: [id], onDelete: Cascade)
|
||||||
|
progress Progress[]
|
||||||
|
appointments Appointment[]
|
||||||
|
sessionNotes SessionNote[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ TERAPEUTAS ============
|
||||||
|
|
||||||
|
model TherapistPatient {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
therapistId String
|
||||||
|
patientId String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
therapist User @relation("TherapistPatients", fields: [therapistId], references: [id])
|
||||||
|
patient User @relation("PatientAssignments", fields: [patientId], references: [id])
|
||||||
|
|
||||||
|
@@unique([therapistId, patientId])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ AGENDAMENTOS ============
|
||||||
|
|
||||||
|
model Appointment {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
childId String
|
||||||
|
therapistId String
|
||||||
|
scheduledAt DateTime
|
||||||
|
duration Int @default(50) // minutos
|
||||||
|
status String @default("scheduled") // scheduled, completed, cancelled, no-show
|
||||||
|
roomUrl String? // URL Daily.co
|
||||||
|
roomName String?
|
||||||
|
notes String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
child Child @relation(fields: [childId], references: [id])
|
||||||
|
therapist User @relation("TherapistAppointments", fields: [therapistId], references: [id])
|
||||||
|
sessionNote SessionNote?
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ NOTAS DE SESSÃO ============
|
||||||
|
|
||||||
|
model SessionNote {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
appointmentId String @unique
|
||||||
|
childId String
|
||||||
|
therapistId String
|
||||||
|
content String // Markdown ou texto
|
||||||
|
objectives String? // JSON array
|
||||||
|
progress String? // JSON object com métricas
|
||||||
|
nextSteps String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
appointment Appointment @relation(fields: [appointmentId], references: [id])
|
||||||
|
child Child @relation(fields: [childId], references: [id])
|
||||||
|
therapist User @relation(fields: [therapistId], references: [id])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ PROGRESSO ============
|
||||||
|
|
||||||
|
model Progress {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
childId String
|
||||||
|
category String // comunicacao, habilidades_sociais, autonomia, regulacao_emocional
|
||||||
|
value Int // 0-100
|
||||||
|
notes String?
|
||||||
|
date DateTime @default(now())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
child Child @relation(fields: [childId], references: [id])
|
||||||
|
}
|
||||||
BIN
public/icon-iristea.png
Normal file
BIN
public/icon-iristea.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
39
public/icon-iristea.svg
Normal file
39
public/icon-iristea.svg
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||||
|
<defs>
|
||||||
|
<!-- Gradiente arco-íris -->
|
||||||
|
<linearGradient id="rainbow" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
|
<stop offset="0%" style="stop-color:#ef4444"/>
|
||||||
|
<stop offset="20%" style="stop-color:#f59e0b"/>
|
||||||
|
<stop offset="40%" style="stop-color:#22c55e"/>
|
||||||
|
<stop offset="60%" style="stop-color:#3b82f6"/>
|
||||||
|
<stop offset="80%" style="stop-color:#8b5cf6"/>
|
||||||
|
<stop offset="100%" style="stop-color:#ec4899"/>
|
||||||
|
</linearGradient>
|
||||||
|
|
||||||
|
<!-- Gradiente para o olho/íris -->
|
||||||
|
<radialGradient id="irisGrad" cx="50%" cy="50%" r="50%">
|
||||||
|
<stop offset="0%" style="stop-color:#818cf8"/>
|
||||||
|
<stop offset="50%" style="stop-color:#6366f1"/>
|
||||||
|
<stop offset="100%" style="stop-color:#4f46e5"/>
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- Background círculo -->
|
||||||
|
<circle cx="50" cy="50" r="48" fill="white"/>
|
||||||
|
|
||||||
|
<!-- Arco-íris acima do olho -->
|
||||||
|
<path d="M 12 52 Q 50 10, 88 52" stroke="url(#rainbow)" stroke-width="7" fill="none" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- Forma do olho -->
|
||||||
|
<ellipse cx="50" cy="58" rx="35" ry="26" fill="white" stroke="#e5e7eb" stroke-width="2"/>
|
||||||
|
|
||||||
|
<!-- Íris (parte colorida) -->
|
||||||
|
<circle cx="50" cy="58" r="20" fill="url(#irisGrad)"/>
|
||||||
|
|
||||||
|
<!-- Pupila -->
|
||||||
|
<circle cx="50" cy="58" r="9" fill="#1e1b4b"/>
|
||||||
|
|
||||||
|
<!-- Brilho -->
|
||||||
|
<circle cx="44" cy="52" r="4" fill="white" opacity="0.8"/>
|
||||||
|
<circle cx="54" cy="55" r="2" fill="white" opacity="0.6"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/logo-iristea.png
Normal file
BIN
public/logo-iristea.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
49
public/logo-iristea.svg
Normal file
49
public/logo-iristea.svg
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 120">
|
||||||
|
<defs>
|
||||||
|
<!-- Gradiente arco-íris -->
|
||||||
|
<linearGradient id="rainbow" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
|
<stop offset="0%" style="stop-color:#ef4444"/>
|
||||||
|
<stop offset="20%" style="stop-color:#f59e0b"/>
|
||||||
|
<stop offset="40%" style="stop-color:#22c55e"/>
|
||||||
|
<stop offset="60%" style="stop-color:#3b82f6"/>
|
||||||
|
<stop offset="80%" style="stop-color:#8b5cf6"/>
|
||||||
|
<stop offset="100%" style="stop-color:#ec4899"/>
|
||||||
|
</linearGradient>
|
||||||
|
|
||||||
|
<!-- Gradiente para o olho/íris -->
|
||||||
|
<radialGradient id="irisGrad" cx="50%" cy="50%" r="50%">
|
||||||
|
<stop offset="0%" style="stop-color:#818cf8"/>
|
||||||
|
<stop offset="50%" style="stop-color:#6366f1"/>
|
||||||
|
<stop offset="100%" style="stop-color:#4f46e5"/>
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- Ícone: Olho estilizado com arco-íris -->
|
||||||
|
<g transform="translate(10, 15)">
|
||||||
|
<!-- Arco-íris acima do olho -->
|
||||||
|
<path d="M 5 50 Q 45 5, 85 50" stroke="url(#rainbow)" stroke-width="6" fill="none" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- Forma do olho -->
|
||||||
|
<ellipse cx="45" cy="55" rx="38" ry="28" fill="white" stroke="#e5e7eb" stroke-width="2"/>
|
||||||
|
|
||||||
|
<!-- Íris (parte colorida) -->
|
||||||
|
<circle cx="45" cy="55" r="22" fill="url(#irisGrad)"/>
|
||||||
|
|
||||||
|
<!-- Pupila -->
|
||||||
|
<circle cx="45" cy="55" r="10" fill="#1e1b4b"/>
|
||||||
|
|
||||||
|
<!-- Brilho -->
|
||||||
|
<circle cx="38" cy="48" r="5" fill="white" opacity="0.8"/>
|
||||||
|
<circle cx="50" cy="52" r="2" fill="white" opacity="0.6"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Texto: IrisTEA -->
|
||||||
|
<text x="115" y="75" font-family="system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif" font-size="52" font-weight="700">
|
||||||
|
<tspan fill="#1f2937">Iris</tspan><tspan fill="url(#rainbow)">TEA</tspan>
|
||||||
|
</text>
|
||||||
|
|
||||||
|
<!-- Tagline -->
|
||||||
|
<text x="117" y="100" font-family="system-ui, -apple-system, sans-serif" font-size="14" fill="#6b7280" letter-spacing="0.5">
|
||||||
|
Teleterapia especializada em autismo
|
||||||
|
</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
3
src/app/api/auth/[...nextauth]/route.ts
Normal file
3
src/app/api/auth/[...nextauth]/route.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { handlers } from '@/lib/auth';
|
||||||
|
|
||||||
|
export const { GET, POST } = handlers;
|
||||||
112
src/app/api/register/route.ts
Normal file
112
src/app/api/register/route.ts
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import bcrypt from 'bcrypt';
|
||||||
|
import { prisma } from '@/lib/prisma';
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const body = await request.json();
|
||||||
|
|
||||||
|
const {
|
||||||
|
// Step 1 - Responsável
|
||||||
|
nomeResponsavel,
|
||||||
|
email,
|
||||||
|
telefone,
|
||||||
|
cidade,
|
||||||
|
estado,
|
||||||
|
|
||||||
|
// Step 2 - Criança
|
||||||
|
nomeCrianca,
|
||||||
|
dataNascimento,
|
||||||
|
diagnostico,
|
||||||
|
quandoDiagnostico,
|
||||||
|
quaisTerapias,
|
||||||
|
|
||||||
|
// Step 3 - Prioridades
|
||||||
|
prioridades,
|
||||||
|
desafios,
|
||||||
|
disponibilidade,
|
||||||
|
planoInteresse,
|
||||||
|
|
||||||
|
// Senha
|
||||||
|
password,
|
||||||
|
} = body;
|
||||||
|
|
||||||
|
// Validações básicas
|
||||||
|
if (!email || !password || !nomeResponsavel) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Campos obrigatórios faltando' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar se usuário já existe
|
||||||
|
const existingUser = await prisma.user.findUnique({
|
||||||
|
where: { email },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingUser) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Este e-mail já está cadastrado' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash da senha
|
||||||
|
const hashedPassword = await bcrypt.hash(password, 10);
|
||||||
|
|
||||||
|
// Criar usuário e criança em uma transação
|
||||||
|
const result = await prisma.$transaction(async (tx) => {
|
||||||
|
// Criar usuário
|
||||||
|
const user = await tx.user.create({
|
||||||
|
data: {
|
||||||
|
email,
|
||||||
|
password: hashedPassword,
|
||||||
|
name: nomeResponsavel,
|
||||||
|
phone: telefone,
|
||||||
|
city: cidade,
|
||||||
|
state: estado,
|
||||||
|
role: 'parent',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Criar assinatura pendente
|
||||||
|
await tx.subscription.create({
|
||||||
|
data: {
|
||||||
|
userId: user.id,
|
||||||
|
plan: planoInteresse || 'completo',
|
||||||
|
status: 'pending',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Criar perfil da criança
|
||||||
|
if (nomeCrianca && dataNascimento) {
|
||||||
|
await tx.child.create({
|
||||||
|
data: {
|
||||||
|
parentId: user.id,
|
||||||
|
name: nomeCrianca,
|
||||||
|
birthDate: new Date(dataNascimento),
|
||||||
|
diagnosis: diagnostico || '',
|
||||||
|
diagnosisDate: quandoDiagnostico,
|
||||||
|
currentTherapies: JSON.stringify(quaisTerapias || []),
|
||||||
|
priorities: JSON.stringify(prioridades || []),
|
||||||
|
challenges: desafios,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Cadastro realizado com sucesso!',
|
||||||
|
userId: result.id,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erro no cadastro:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Erro interno do servidor' },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
618
src/app/cadastro/page.tsx
Normal file
618
src/app/cadastro/page.tsx
Normal file
@@ -0,0 +1,618 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Sparkles, ArrowLeft, ArrowRight, Check, User, Baby, Target,
|
||||||
|
Lock, Eye, EyeOff
|
||||||
|
} from 'lucide-react';
|
||||||
|
|
||||||
|
type Step = 1 | 2 | 3;
|
||||||
|
|
||||||
|
interface FormData {
|
||||||
|
// Step 1 - Responsável
|
||||||
|
nomeResponsavel: string;
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
confirmPassword: string;
|
||||||
|
telefone: string;
|
||||||
|
cidade: string;
|
||||||
|
estado: string;
|
||||||
|
comoConheceu: string;
|
||||||
|
|
||||||
|
// Step 2 - Criança
|
||||||
|
nomeCrianca: string;
|
||||||
|
dataNascimento: string;
|
||||||
|
diagnostico: string;
|
||||||
|
quandoDiagnostico: string;
|
||||||
|
fazTerapia: string;
|
||||||
|
quaisTerapias: string[];
|
||||||
|
|
||||||
|
// Step 3 - Prioridades
|
||||||
|
prioridades: string[];
|
||||||
|
desafios: string;
|
||||||
|
expectativas: string;
|
||||||
|
disponibilidade: string[];
|
||||||
|
planoInteresse: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CadastroPage() {
|
||||||
|
const router = useRouter();
|
||||||
|
const [step, setStep] = useState<Step>(1);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
const [formData, setFormData] = useState<FormData>({
|
||||||
|
nomeResponsavel: '',
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
confirmPassword: '',
|
||||||
|
telefone: '',
|
||||||
|
cidade: '',
|
||||||
|
estado: '',
|
||||||
|
comoConheceu: '',
|
||||||
|
nomeCrianca: '',
|
||||||
|
dataNascimento: '',
|
||||||
|
diagnostico: '',
|
||||||
|
quandoDiagnostico: '',
|
||||||
|
fazTerapia: '',
|
||||||
|
quaisTerapias: [],
|
||||||
|
prioridades: [],
|
||||||
|
desafios: '',
|
||||||
|
expectativas: '',
|
||||||
|
disponibilidade: [],
|
||||||
|
planoInteresse: 'completo',
|
||||||
|
});
|
||||||
|
|
||||||
|
const estados = ['AC','AL','AP','AM','BA','CE','DF','ES','GO','MA','MT','MS','MG','PA','PB','PR','PE','PI','RJ','RN','RS','RO','RR','SC','SP','SE','TO'];
|
||||||
|
|
||||||
|
const terapias = ['ABA', 'Fonoaudiologia', 'Terapia Ocupacional', 'Psicologia', 'Psicopedagogia', 'Musicoterapia', 'Equoterapia', 'Nenhuma'];
|
||||||
|
|
||||||
|
const prioridadesOpcoes = [
|
||||||
|
'Comunicação / Fala',
|
||||||
|
'Comportamentos desafiadores',
|
||||||
|
'Habilidades sociais',
|
||||||
|
'Autonomia / Vida diária',
|
||||||
|
'Sono',
|
||||||
|
'Alimentação seletiva',
|
||||||
|
'Treino de desfralde',
|
||||||
|
'Rotina escolar',
|
||||||
|
'Regulação emocional',
|
||||||
|
'Estereotipias',
|
||||||
|
];
|
||||||
|
|
||||||
|
const disponibilidadeOpcoes = [
|
||||||
|
'Manhã (8h-12h)',
|
||||||
|
'Tarde (13h-18h)',
|
||||||
|
'Noite (19h-21h)',
|
||||||
|
'Sábados',
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
|
||||||
|
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||||
|
setError('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCheckbox = (field: 'quaisTerapias' | 'prioridades' | 'disponibilidade', value: string) => {
|
||||||
|
const current = formData[field];
|
||||||
|
if (current.includes(value)) {
|
||||||
|
setFormData({ ...formData, [field]: current.filter(v => v !== value) });
|
||||||
|
} else {
|
||||||
|
setFormData({ ...formData, [field]: [...current, value] });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const nextStep = () => {
|
||||||
|
// Validações por step
|
||||||
|
if (step === 1) {
|
||||||
|
if (!formData.nomeResponsavel || !formData.email || !formData.password) {
|
||||||
|
setError('Preencha todos os campos obrigatórios');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (formData.password !== formData.confirmPassword) {
|
||||||
|
setError('As senhas não conferem');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (formData.password.length < 6) {
|
||||||
|
setError('A senha deve ter no mínimo 6 caracteres');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step < 3) setStep((step + 1) as Step);
|
||||||
|
};
|
||||||
|
|
||||||
|
const prevStep = () => {
|
||||||
|
if (step > 1) setStep((step - 1) as Step);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setLoading(true);
|
||||||
|
setError('');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/register', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(formData),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(data.error || 'Erro ao cadastrar');
|
||||||
|
}
|
||||||
|
|
||||||
|
router.push('/cadastro/sucesso');
|
||||||
|
} catch (err: any) {
|
||||||
|
setError(err.message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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-4xl 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>
|
||||||
|
|
||||||
|
{/* Progress */}
|
||||||
|
<div className="max-w-4xl mx-auto px-4 py-8">
|
||||||
|
<div className="flex items-center justify-between mb-8">
|
||||||
|
{[1, 2, 3].map((s) => (
|
||||||
|
<div key={s} className="flex items-center">
|
||||||
|
<div className={`w-10 h-10 rounded-full flex items-center justify-center font-semibold transition ${
|
||||||
|
s < step ? 'bg-green-500 text-white' :
|
||||||
|
s === step ? 'bg-indigo-600 text-white' :
|
||||||
|
'bg-gray-200 text-gray-500'
|
||||||
|
}`}>
|
||||||
|
{s < step ? <Check className="w-5 h-5" /> : s}
|
||||||
|
</div>
|
||||||
|
<span className={`ml-3 hidden sm:block ${s === step ? 'text-indigo-600 font-medium' : 'text-gray-500'}`}>
|
||||||
|
{s === 1 ? 'Seus Dados' : s === 2 ? 'Sobre a Criança' : 'Prioridades'}
|
||||||
|
</span>
|
||||||
|
{s < 3 && <div className={`w-12 sm:w-24 h-1 mx-4 ${s < step ? 'bg-green-500' : 'bg-gray-200'}`} />}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Form Card */}
|
||||||
|
<div className="bg-white rounded-2xl shadow-xl p-8">
|
||||||
|
{error && (
|
||||||
|
<div className="mb-6 p-4 bg-red-50 border border-red-200 rounded-xl text-red-700">
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
|
||||||
|
{/* Step 1 - Responsável */}
|
||||||
|
{step === 1 && (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center gap-3 mb-6">
|
||||||
|
<div className="w-12 h-12 rounded-xl bg-indigo-100 text-indigo-600 flex items-center justify-center">
|
||||||
|
<User className="w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-bold text-gray-900">Seus Dados</h2>
|
||||||
|
<p className="text-gray-500">Informações do responsável</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-2 gap-6">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">Nome Completo *</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="nomeResponsavel"
|
||||||
|
value={formData.nomeResponsavel}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition"
|
||||||
|
placeholder="Seu nome completo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">E-mail *</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
name="email"
|
||||||
|
value={formData.email}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition"
|
||||||
|
placeholder="seu@email.com"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">Senha *</label>
|
||||||
|
<div className="relative">
|
||||||
|
<Lock className="w-5 h-5 text-gray-400 absolute left-4 top-1/2 -translate-y-1/2" />
|
||||||
|
<input
|
||||||
|
type={showPassword ? 'text' : 'password'}
|
||||||
|
name="password"
|
||||||
|
value={formData.password}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
minLength={6}
|
||||||
|
className="w-full pl-12 pr-12 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition"
|
||||||
|
placeholder="Mínimo 6 caracteres"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
|
className="absolute right-4 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||||
|
>
|
||||||
|
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">Confirmar Senha *</label>
|
||||||
|
<div className="relative">
|
||||||
|
<Lock className="w-5 h-5 text-gray-400 absolute left-4 top-1/2 -translate-y-1/2" />
|
||||||
|
<input
|
||||||
|
type={showPassword ? 'text' : 'password'}
|
||||||
|
name="confirmPassword"
|
||||||
|
value={formData.confirmPassword}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
className="w-full pl-12 pr-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition"
|
||||||
|
placeholder="Repita a senha"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">Telefone/WhatsApp *</label>
|
||||||
|
<input
|
||||||
|
type="tel"
|
||||||
|
name="telefone"
|
||||||
|
value={formData.telefone}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition"
|
||||||
|
placeholder="(11) 99999-9999"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">Estado *</label>
|
||||||
|
<select
|
||||||
|
name="estado"
|
||||||
|
value={formData.estado}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition"
|
||||||
|
>
|
||||||
|
<option value="">Selecione</option>
|
||||||
|
{estados.map(uf => <option key={uf} value={uf}>{uf}</option>)}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">Cidade *</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="cidade"
|
||||||
|
value={formData.cidade}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition"
|
||||||
|
placeholder="Sua cidade"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">Como conheceu a Íris?</label>
|
||||||
|
<select
|
||||||
|
name="comoConheceu"
|
||||||
|
value={formData.comoConheceu}
|
||||||
|
onChange={handleChange}
|
||||||
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition"
|
||||||
|
>
|
||||||
|
<option value="">Selecione</option>
|
||||||
|
<option value="google">Google</option>
|
||||||
|
<option value="instagram">Instagram</option>
|
||||||
|
<option value="facebook">Facebook</option>
|
||||||
|
<option value="indicacao">Indicação</option>
|
||||||
|
<option value="terapeuta">Terapeuta</option>
|
||||||
|
<option value="outro">Outro</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Step 2 - Criança */}
|
||||||
|
{step === 2 && (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center gap-3 mb-6">
|
||||||
|
<div className="w-12 h-12 rounded-xl bg-pink-100 text-pink-600 flex items-center justify-center">
|
||||||
|
<Baby className="w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-bold text-gray-900">Sobre a Criança</h2>
|
||||||
|
<p className="text-gray-500">Informações do seu filho(a)</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-2 gap-6">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">Nome da Criança *</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="nomeCrianca"
|
||||||
|
value={formData.nomeCrianca}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition"
|
||||||
|
placeholder="Nome do seu filho(a)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">Data de Nascimento *</label>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
name="dataNascimento"
|
||||||
|
value={formData.dataNascimento}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">Diagnóstico *</label>
|
||||||
|
<select
|
||||||
|
name="diagnostico"
|
||||||
|
value={formData.diagnostico}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition"
|
||||||
|
>
|
||||||
|
<option value="">Selecione</option>
|
||||||
|
<option value="tea_leve">TEA - Nível 1 (Leve)</option>
|
||||||
|
<option value="tea_moderado">TEA - Nível 2 (Moderado)</option>
|
||||||
|
<option value="tea_severo">TEA - Nível 3 (Severo)</option>
|
||||||
|
<option value="suspeita">Suspeita (sem diagnóstico fechado)</option>
|
||||||
|
<option value="outro">Outro</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">Quando foi o diagnóstico?</label>
|
||||||
|
<select
|
||||||
|
name="quandoDiagnostico"
|
||||||
|
value={formData.quandoDiagnostico}
|
||||||
|
onChange={handleChange}
|
||||||
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition"
|
||||||
|
>
|
||||||
|
<option value="">Selecione</option>
|
||||||
|
<option value="menos_6m">Menos de 6 meses</option>
|
||||||
|
<option value="6m_1a">6 meses a 1 ano</option>
|
||||||
|
<option value="1a_2a">1 a 2 anos</option>
|
||||||
|
<option value="mais_2a">Mais de 2 anos</option>
|
||||||
|
<option value="sem_diag">Ainda sem diagnóstico</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">Faz alguma terapia atualmente? *</label>
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<label className="flex items-center gap-2 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="fazTerapia"
|
||||||
|
value="sim"
|
||||||
|
checked={formData.fazTerapia === 'sim'}
|
||||||
|
onChange={handleChange}
|
||||||
|
className="w-4 h-4 text-indigo-600"
|
||||||
|
/>
|
||||||
|
<span>Sim</span>
|
||||||
|
</label>
|
||||||
|
<label className="flex items-center gap-2 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="fazTerapia"
|
||||||
|
value="nao"
|
||||||
|
checked={formData.fazTerapia === 'nao'}
|
||||||
|
onChange={handleChange}
|
||||||
|
className="w-4 h-4 text-indigo-600"
|
||||||
|
/>
|
||||||
|
<span>Não</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{formData.fazTerapia === 'sim' && (
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-3">Quais terapias?</label>
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||||
|
{terapias.map(t => (
|
||||||
|
<label key={t} className={`flex items-center gap-2 p-3 border rounded-xl cursor-pointer transition ${
|
||||||
|
formData.quaisTerapias.includes(t) ? 'border-indigo-500 bg-indigo-50' : 'border-gray-200 hover:border-indigo-300'
|
||||||
|
}`}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={formData.quaisTerapias.includes(t)}
|
||||||
|
onChange={() => handleCheckbox('quaisTerapias', t)}
|
||||||
|
className="w-4 h-4 text-indigo-600 rounded"
|
||||||
|
/>
|
||||||
|
<span className="text-sm">{t}</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Step 3 - Prioridades */}
|
||||||
|
{step === 3 && (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center gap-3 mb-6">
|
||||||
|
<div className="w-12 h-12 rounded-xl bg-green-100 text-green-600 flex items-center justify-center">
|
||||||
|
<Target className="w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-bold text-gray-900">Prioridades Terapêuticas</h2>
|
||||||
|
<p className="text-gray-500">O que você gostaria de trabalhar?</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-3">
|
||||||
|
Selecione até 3 prioridades *
|
||||||
|
</label>
|
||||||
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
{prioridadesOpcoes.map(p => (
|
||||||
|
<label key={p} className={`flex items-center gap-2 p-3 border rounded-xl cursor-pointer transition ${
|
||||||
|
formData.prioridades.includes(p) ? 'border-indigo-500 bg-indigo-50' : 'border-gray-200 hover:border-indigo-300'
|
||||||
|
} ${formData.prioridades.length >= 3 && !formData.prioridades.includes(p) ? 'opacity-50 cursor-not-allowed' : ''}`}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={formData.prioridades.includes(p)}
|
||||||
|
onChange={() => {
|
||||||
|
if (formData.prioridades.length < 3 || formData.prioridades.includes(p)) {
|
||||||
|
handleCheckbox('prioridades', p);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={formData.prioridades.length >= 3 && !formData.prioridades.includes(p)}
|
||||||
|
className="w-4 h-4 text-indigo-600 rounded"
|
||||||
|
/>
|
||||||
|
<span className="text-sm">{p}</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Descreva os principais desafios do dia a dia
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
name="desafios"
|
||||||
|
value={formData.desafios}
|
||||||
|
onChange={handleChange}
|
||||||
|
rows={3}
|
||||||
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition resize-none"
|
||||||
|
placeholder="Conte um pouco sobre as dificuldades que você enfrenta..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-3">
|
||||||
|
Disponibilidade para sessões *
|
||||||
|
</label>
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||||
|
{disponibilidadeOpcoes.map(d => (
|
||||||
|
<label key={d} className={`flex items-center gap-2 p-3 border rounded-xl cursor-pointer transition ${
|
||||||
|
formData.disponibilidade.includes(d) ? 'border-indigo-500 bg-indigo-50' : 'border-gray-200 hover:border-indigo-300'
|
||||||
|
}`}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={formData.disponibilidade.includes(d)}
|
||||||
|
onChange={() => handleCheckbox('disponibilidade', d)}
|
||||||
|
className="w-4 h-4 text-indigo-600 rounded"
|
||||||
|
/>
|
||||||
|
<span className="text-sm">{d}</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-3">
|
||||||
|
Plano de interesse *
|
||||||
|
</label>
|
||||||
|
<div className="grid md:grid-cols-3 gap-4">
|
||||||
|
{[
|
||||||
|
{ id: 'essencial', name: 'Essencial', price: 'R$ 297/mês', sessions: '2 sessões' },
|
||||||
|
{ id: 'completo', name: 'Completo', price: 'R$ 497/mês', sessions: '4 sessões', popular: true },
|
||||||
|
{ id: 'intensivo', name: 'Intensivo', price: 'R$ 897/mês', sessions: '8 sessões' },
|
||||||
|
].map(plan => (
|
||||||
|
<label key={plan.id} className={`relative p-4 border-2 rounded-xl cursor-pointer transition ${
|
||||||
|
formData.planoInteresse === plan.id ? 'border-indigo-500 bg-indigo-50' : 'border-gray-200 hover:border-indigo-300'
|
||||||
|
}`}>
|
||||||
|
{plan.popular && (
|
||||||
|
<span className="absolute -top-2 left-4 bg-indigo-600 text-white text-xs px-2 py-0.5 rounded-full">
|
||||||
|
Popular
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="planoInteresse"
|
||||||
|
value={plan.id}
|
||||||
|
checked={formData.planoInteresse === plan.id}
|
||||||
|
onChange={handleChange}
|
||||||
|
className="sr-only"
|
||||||
|
/>
|
||||||
|
<div className="font-semibold text-gray-900">{plan.name}</div>
|
||||||
|
<div className="text-indigo-600 font-bold">{plan.price}</div>
|
||||||
|
<div className="text-sm text-gray-500">{plan.sessions}</div>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Navigation */}
|
||||||
|
<div className="flex justify-between mt-8 pt-6 border-t border-gray-200">
|
||||||
|
{step > 1 ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={prevStep}
|
||||||
|
className="flex items-center gap-2 px-6 py-3 text-gray-600 hover:text-gray-900 transition"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="w-5 h-5" />
|
||||||
|
Voltar
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<Link href="/" className="flex items-center gap-2 px-6 py-3 text-gray-600 hover:text-gray-900 transition">
|
||||||
|
<ArrowLeft className="w-5 h-5" />
|
||||||
|
Início
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{step < 3 ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={nextStep}
|
||||||
|
className="flex items-center gap-2 bg-indigo-600 text-white px-8 py-3 rounded-full hover:bg-indigo-700 transition font-medium"
|
||||||
|
>
|
||||||
|
Continuar
|
||||||
|
<ArrowRight className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={loading}
|
||||||
|
className="flex items-center gap-2 bg-green-600 text-white px-8 py-3 rounded-full hover:bg-green-700 transition font-medium disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<div className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Check className="w-5 h-5" />
|
||||||
|
Finalizar Cadastro
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
103
src/app/cadastro/sucesso/page.tsx
Normal file
103
src/app/cadastro/sucesso/page.tsx
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { CheckCircle, Calendar, MessageCircle, FileText, ArrowRight, Sparkles } from 'lucide-react';
|
||||||
|
|
||||||
|
export default function SucessoPage() {
|
||||||
|
const proximosPassos = [
|
||||||
|
{
|
||||||
|
icon: Calendar,
|
||||||
|
title: 'Agendamento da Avaliação',
|
||||||
|
desc: 'Em até 24h, nossa equipe entrará em contato para agendar sua avaliação gratuita com uma terapeuta.',
|
||||||
|
time: '24 horas'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: MessageCircle,
|
||||||
|
title: 'Acesso ao Chat IA',
|
||||||
|
desc: 'Você já pode usar nosso assistente inteligente para tirar dúvidas sobre autismo e desenvolvimento.',
|
||||||
|
time: 'Imediato'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: FileText,
|
||||||
|
title: 'Materiais de Boas-Vindas',
|
||||||
|
desc: 'Enviamos para seu e-mail um guia completo para pais de crianças com TEA.',
|
||||||
|
time: 'Enviado'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-b from-green-50 to-white">
|
||||||
|
{/* Header */}
|
||||||
|
<header className="bg-white border-b border-gray-100">
|
||||||
|
<div className="max-w-4xl 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>
|
||||||
|
|
||||||
|
<div className="max-w-2xl mx-auto px-4 py-16">
|
||||||
|
{/* Success Icon */}
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<div className="w-24 h-24 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6 animate-float">
|
||||||
|
<CheckCircle className="w-14 h-14 text-green-600" />
|
||||||
|
</div>
|
||||||
|
<h1 className="text-3xl md:text-4xl font-bold text-gray-900 mb-4">
|
||||||
|
Cadastro Realizado com Sucesso! 🎉
|
||||||
|
</h1>
|
||||||
|
<p className="text-xl text-gray-600">
|
||||||
|
Bem-vindo(a) à família Íris! Estamos muito felizes em ter você conosco.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Próximos Passos */}
|
||||||
|
<div className="bg-white rounded-2xl shadow-xl p-8 mb-8">
|
||||||
|
<h2 className="text-xl font-bold text-gray-900 mb-6">Próximos Passos</h2>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
{proximosPassos.map((passo, i) => (
|
||||||
|
<div key={i} className="flex gap-4">
|
||||||
|
<div className="w-12 h-12 bg-indigo-100 rounded-xl flex items-center justify-center flex-shrink-0">
|
||||||
|
<passo.icon className="w-6 h-6 text-indigo-600" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h3 className="font-semibold text-gray-900">{passo.title}</h3>
|
||||||
|
<span className="text-sm text-indigo-600 bg-indigo-50 px-2 py-1 rounded-full">
|
||||||
|
{passo.time}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-gray-600 mt-1">{passo.desc}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* CTA */}
|
||||||
|
<div className="bg-indigo-600 rounded-2xl p-8 text-center text-white">
|
||||||
|
<h3 className="text-xl font-bold mb-2">Enquanto isso, que tal conhecer nosso assistente?</h3>
|
||||||
|
<p className="text-indigo-200 mb-6">
|
||||||
|
Tire suas primeiras dúvidas com nossa IA especializada em autismo
|
||||||
|
</p>
|
||||||
|
<Link
|
||||||
|
href="/dashboard"
|
||||||
|
className="inline-flex items-center gap-2 bg-white text-indigo-600 px-6 py-3 rounded-full font-medium hover:bg-indigo-50 transition"
|
||||||
|
>
|
||||||
|
Acessar Meu Painel
|
||||||
|
<ArrowRight className="w-5 h-5" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Contato */}
|
||||||
|
<div className="text-center mt-8 text-gray-500">
|
||||||
|
<p>Dúvidas? Entre em contato:</p>
|
||||||
|
<p className="font-medium text-gray-700">contato@iris.com.br | (11) 99999-9999</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
342
src/app/dashboard/page.tsx
Normal file
342
src/app/dashboard/page.tsx
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { useState, useRef, useEffect } from 'react';
|
||||||
|
import { useSession, signOut } from 'next-auth/react';
|
||||||
|
import {
|
||||||
|
Sparkles, Home, MessageCircle, Calendar, FileText, Settings, LogOut,
|
||||||
|
Video, Bell, ChevronRight, Star, Send, Brain, TrendingUp,
|
||||||
|
Clock, Award, Menu, X, Phone
|
||||||
|
} from 'lucide-react';
|
||||||
|
|
||||||
|
interface Message {
|
||||||
|
role: 'user' | 'ai';
|
||||||
|
text: string;
|
||||||
|
time: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DashboardPage() {
|
||||||
|
const { data: session } = useSession();
|
||||||
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||||
|
const [messages, setMessages] = useState<Message[]>([
|
||||||
|
{ role: 'ai', text: 'Olá! Sou a assistente da Íris 🌈 Como posso ajudar você hoje?', time: '10:00' },
|
||||||
|
]);
|
||||||
|
const [inputMessage, setInputMessage] = useState('');
|
||||||
|
const [isTyping, setIsTyping] = useState(false);
|
||||||
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const scrollToBottom = () => {
|
||||||
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
scrollToBottom();
|
||||||
|
}, [messages]);
|
||||||
|
|
||||||
|
const aiResponses = [
|
||||||
|
'Entendo sua preocupação! Isso é muito comum em crianças com TEA. Vamos trabalhar juntos algumas estratégias que podem ajudar.',
|
||||||
|
'Ótima pergunta! Baseado na análise comportamental aplicada (ABA), recomendo que você tente dividir a tarefa em pequenos passos e use reforço positivo após cada conquista.',
|
||||||
|
'Isso pode ser desafiador, mas existem técnicas que funcionam muito bem. Já tentou usar histórias sociais ou antecipação com apoio visual?',
|
||||||
|
'Que bom que você trouxe isso! Esse é um dos pontos que podemos aprofundar na próxima sessão com a Dra. Marina. Mas enquanto isso, você pode tentar...',
|
||||||
|
'Compreendo como isso pode ser difícil no dia a dia. Uma estratégia que muitos pais relatam sucesso é criar uma rotina visual com imagens.',
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleSendMessage = async () => {
|
||||||
|
if (!inputMessage.trim()) return;
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const time = now.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' });
|
||||||
|
|
||||||
|
setMessages(prev => [...prev, { role: 'user', text: inputMessage, time }]);
|
||||||
|
setInputMessage('');
|
||||||
|
setIsTyping(true);
|
||||||
|
|
||||||
|
// Simula resposta da IA
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||||
|
|
||||||
|
const aiResponse = aiResponses[Math.floor(Math.random() * aiResponses.length)];
|
||||||
|
const responseTime = new Date().toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' });
|
||||||
|
|
||||||
|
setIsTyping(false);
|
||||||
|
setMessages(prev => [...prev, { role: 'ai', text: aiResponse, time: responseTime }]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyPress = (e: React.KeyboardEvent) => {
|
||||||
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
handleSendMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const stats = [
|
||||||
|
{ label: 'Sessões Realizadas', value: '12', icon: Video, color: 'bg-blue-100 text-blue-600' },
|
||||||
|
{ label: 'Próxima Sessão', value: 'Amanhã', icon: Calendar, color: 'bg-green-100 text-green-600' },
|
||||||
|
{ label: 'Metas Atingidas', value: '8/10', icon: Award, color: 'bg-yellow-100 text-yellow-600' },
|
||||||
|
{ label: 'Dias de Streak', value: '15', icon: TrendingUp, color: 'bg-purple-100 text-purple-600' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const progressItems = [
|
||||||
|
{ name: 'Comunicação', progress: 75, color: 'bg-blue-500' },
|
||||||
|
{ name: 'Habilidades Sociais', progress: 60, color: 'bg-green-500' },
|
||||||
|
{ name: 'Autonomia', progress: 45, color: 'bg-yellow-500' },
|
||||||
|
{ name: 'Regulação Emocional', progress: 80, color: 'bg-purple-500' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const proximasAtividades = [
|
||||||
|
{ tipo: 'Sessão', titulo: 'Teleterapia com Dra. Marina', data: 'Amanhã, 15:00', icon: Video },
|
||||||
|
{ tipo: 'Tarefa', titulo: 'Praticar rotina visual de manhã', data: 'Diário', icon: FileText },
|
||||||
|
{ tipo: 'Lembrete', titulo: 'Reunião de grupo de pais', data: 'Sábado, 10:00', icon: Bell },
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleLogout = async () => {
|
||||||
|
await signOut({ callbackUrl: '/' });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Pegar primeiro nome
|
||||||
|
const firstName = session?.user?.name?.split(' ')[0] || 'Usuário';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50 flex">
|
||||||
|
{/* Sidebar */}
|
||||||
|
<aside className={`fixed inset-y-0 left-0 z-50 w-64 bg-white border-r border-gray-200 transform transition-transform duration-300 lg:translate-x-0 ${sidebarOpen ? 'translate-x-0' : '-translate-x-full'}`}>
|
||||||
|
<div className="flex items-center gap-2 p-6 border-b border-gray-100">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav className="p-4 space-y-2">
|
||||||
|
<a href="#" className="flex items-center gap-3 px-4 py-3 bg-indigo-50 text-indigo-600 rounded-xl font-medium">
|
||||||
|
<Home className="w-5 h-5" />
|
||||||
|
Dashboard
|
||||||
|
</a>
|
||||||
|
<a href="#chat" className="flex items-center gap-3 px-4 py-3 text-gray-600 hover:bg-gray-50 rounded-xl transition">
|
||||||
|
<MessageCircle className="w-5 h-5" />
|
||||||
|
Chat IA
|
||||||
|
</a>
|
||||||
|
<Link href="/dashboard/agenda" className="flex items-center gap-3 px-4 py-3 text-gray-600 hover:bg-gray-50 rounded-xl transition">
|
||||||
|
<Calendar className="w-5 h-5" />
|
||||||
|
Agenda
|
||||||
|
</Link>
|
||||||
|
<Link href="/dashboard/relatorios" className="flex items-center gap-3 px-4 py-3 text-gray-600 hover:bg-gray-50 rounded-xl transition">
|
||||||
|
<FileText className="w-5 h-5" />
|
||||||
|
Relatórios
|
||||||
|
</Link>
|
||||||
|
<Link href="/dashboard/configuracoes" className="flex items-center gap-3 px-4 py-3 text-gray-600 hover:bg-gray-50 rounded-xl transition">
|
||||||
|
<Settings className="w-5 h-5" />
|
||||||
|
Configurações
|
||||||
|
</Link>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div className="absolute bottom-0 left-0 right-0 p-4 border-t border-gray-100">
|
||||||
|
<button
|
||||||
|
onClick={handleLogout}
|
||||||
|
className="flex items-center gap-3 px-4 py-3 text-gray-600 hover:bg-gray-50 rounded-xl transition w-full"
|
||||||
|
>
|
||||||
|
<LogOut className="w-5 h-5" />
|
||||||
|
Sair
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
{/* Overlay */}
|
||||||
|
{sidebarOpen && (
|
||||||
|
<div className="fixed inset-0 bg-black/50 z-40 lg:hidden" onClick={() => setSidebarOpen(false)} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<main className="flex-1 lg:ml-64">
|
||||||
|
{/* Header */}
|
||||||
|
<header className="bg-white border-b border-gray-200 px-4 lg:px-8 py-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<button className="lg:hidden" onClick={() => setSidebarOpen(true)}>
|
||||||
|
<Menu className="w-6 h-6" />
|
||||||
|
</button>
|
||||||
|
<div>
|
||||||
|
<h1 className="text-xl font-bold text-gray-900">Olá, {firstName}! 👋</h1>
|
||||||
|
<p className="text-sm text-gray-500">Bem-vindo ao seu dashboard</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<button className="relative p-2 text-gray-400 hover:text-gray-600">
|
||||||
|
<Bell className="w-6 h-6" />
|
||||||
|
<span className="absolute top-1 right-1 w-2 h-2 bg-red-500 rounded-full" />
|
||||||
|
</button>
|
||||||
|
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-indigo-400 to-purple-500 flex items-center justify-center text-white font-semibold">
|
||||||
|
{firstName.charAt(0).toUpperCase()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div className="p-4 lg:p-8">
|
||||||
|
{/* Stats */}
|
||||||
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
|
||||||
|
{stats.map((stat, i) => (
|
||||||
|
<div key={i} className="bg-white rounded-xl p-4 shadow-sm">
|
||||||
|
<div className={`w-10 h-10 rounded-lg ${stat.color} flex items-center justify-center mb-3`}>
|
||||||
|
<stat.icon className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
<p className="text-2xl font-bold text-gray-900">{stat.value}</p>
|
||||||
|
<p className="text-sm text-gray-500">{stat.label}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid lg:grid-cols-3 gap-8">
|
||||||
|
{/* Left Column */}
|
||||||
|
<div className="lg:col-span-2 space-y-8">
|
||||||
|
{/* Terapeuta Card */}
|
||||||
|
<div className="bg-white rounded-xl shadow-sm p-6">
|
||||||
|
<h2 className="text-lg font-bold text-gray-900 mb-4">Sua Terapeuta</h2>
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="w-16 h-16 rounded-full bg-gradient-to-br from-pink-400 to-rose-500 flex items-center justify-center text-white text-xl font-bold">
|
||||||
|
MR
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="font-semibold text-gray-900">Dra. Marina Rodrigues</h3>
|
||||||
|
<p className="text-sm text-gray-500">BCBA • Especialista em TEA</p>
|
||||||
|
<div className="flex items-center gap-1 mt-1">
|
||||||
|
{[1,2,3,4,5].map(i => <Star key={i} className="w-4 h-4 fill-yellow-400 text-yellow-400" />)}
|
||||||
|
<span className="text-sm text-gray-500 ml-1">(127 avaliações)</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2 mt-4">
|
||||||
|
<button 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">
|
||||||
|
<Video className="w-4 h-4" />
|
||||||
|
Iniciar Sessão
|
||||||
|
</button>
|
||||||
|
<button className="flex items-center gap-2 border border-gray-300 text-gray-700 px-4 py-2 rounded-lg text-sm font-medium hover:bg-gray-50 transition">
|
||||||
|
<Phone className="w-4 h-4" />
|
||||||
|
Ligar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Progresso */}
|
||||||
|
<div className="bg-white rounded-xl shadow-sm p-6">
|
||||||
|
<div className="flex items-center justify-between mb-6">
|
||||||
|
<h2 className="text-lg font-bold text-gray-900">Progresso</h2>
|
||||||
|
<Link href="/dashboard/relatorios" className="text-sm text-indigo-600 hover:underline flex items-center gap-1">
|
||||||
|
Ver relatório completo
|
||||||
|
<ChevronRight className="w-4 h-4" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{progressItems.map((item, i) => (
|
||||||
|
<div key={i}>
|
||||||
|
<div className="flex justify-between mb-1">
|
||||||
|
<span className="text-sm font-medium text-gray-700">{item.name}</span>
|
||||||
|
<span className="text-sm text-gray-500">{item.progress}%</span>
|
||||||
|
</div>
|
||||||
|
<div className="h-2 bg-gray-200 rounded-full">
|
||||||
|
<div
|
||||||
|
className={`h-2 ${item.color} rounded-full transition-all`}
|
||||||
|
style={{ width: `${item.progress}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Próximas Atividades */}
|
||||||
|
<div className="bg-white rounded-xl shadow-sm p-6">
|
||||||
|
<h2 className="text-lg font-bold text-gray-900 mb-4">Próximas Atividades</h2>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{proximasAtividades.map((ativ, i) => (
|
||||||
|
<div key={i} className="flex items-center gap-4 p-3 bg-gray-50 rounded-lg">
|
||||||
|
<div className="w-10 h-10 bg-indigo-100 rounded-lg flex items-center justify-center text-indigo-600">
|
||||||
|
<ativ.icon className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<p className="font-medium text-gray-900">{ativ.titulo}</p>
|
||||||
|
<p className="text-sm text-gray-500">{ativ.data}</p>
|
||||||
|
</div>
|
||||||
|
<ChevronRight className="w-5 h-5 text-gray-400" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Column - Chat */}
|
||||||
|
<div className="lg:col-span-1">
|
||||||
|
<div id="chat" className="bg-white rounded-xl shadow-sm h-[600px] flex flex-col">
|
||||||
|
<div className="p-4 border-b border-gray-100">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-10 h-10 rounded-full gradient-rainbow flex items-center justify-center">
|
||||||
|
<Brain className="w-5 h-5 text-white" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-gray-900">Assistente Íris</h3>
|
||||||
|
<p className="text-xs text-green-500 flex items-center gap-1">
|
||||||
|
<span className="w-2 h-2 bg-green-500 rounded-full" />
|
||||||
|
Online
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
||||||
|
{messages.map((msg, i) => (
|
||||||
|
<div key={i} className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}>
|
||||||
|
<div className={`max-w-[85%] ${msg.role === 'user' ? 'order-2' : ''}`}>
|
||||||
|
<div className={`p-3 rounded-2xl ${
|
||||||
|
msg.role === 'user'
|
||||||
|
? 'bg-indigo-600 text-white rounded-br-md'
|
||||||
|
: 'bg-gray-100 text-gray-800 rounded-bl-md'
|
||||||
|
}`}>
|
||||||
|
{msg.text}
|
||||||
|
</div>
|
||||||
|
<p className={`text-xs text-gray-400 mt-1 ${msg.role === 'user' ? 'text-right' : ''}`}>
|
||||||
|
{msg.time}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{isTyping && (
|
||||||
|
<div className="flex justify-start">
|
||||||
|
<div className="bg-gray-100 p-3 rounded-2xl rounded-bl-md">
|
||||||
|
<div className="flex gap-1">
|
||||||
|
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0ms' }} />
|
||||||
|
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
|
||||||
|
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div ref={messagesEndRef} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-4 border-t border-gray-100">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={inputMessage}
|
||||||
|
onChange={(e) => setInputMessage(e.target.value)}
|
||||||
|
onKeyPress={handleKeyPress}
|
||||||
|
placeholder="Digite sua dúvida..."
|
||||||
|
className="flex-1 px-4 py-2 bg-gray-100 rounded-full outline-none focus:ring-2 focus:ring-indigo-500"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleSendMessage}
|
||||||
|
className="w-10 h-10 bg-indigo-600 text-white rounded-full flex items-center justify-center hover:bg-indigo-700 transition"
|
||||||
|
>
|
||||||
|
<Send className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,26 +1,57 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background: #ffffff;
|
--iris-purple: #6366f1;
|
||||||
--foreground: #171717;
|
--iris-purple-dark: #4f46e5;
|
||||||
}
|
--iris-blue: #3b82f6;
|
||||||
|
--iris-green: #10b981;
|
||||||
@theme inline {
|
--iris-yellow: #f59e0b;
|
||||||
--color-background: var(--background);
|
--iris-red: #ef4444;
|
||||||
--color-foreground: var(--foreground);
|
--iris-rainbow: linear-gradient(90deg, #ef4444, #f59e0b, #eab308, #22c55e, #3b82f6, #8b5cf6);
|
||||||
--font-sans: var(--font-geist-sans);
|
|
||||||
--font-mono: var(--font-geist-mono);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--background: #0a0a0a;
|
|
||||||
--foreground: #ededed;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: var(--background);
|
font-family: 'Inter', system-ui, sans-serif;
|
||||||
color: var(--foreground);
|
}
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
|
||||||
|
.gradient-rainbow {
|
||||||
|
background: var(--iris-rainbow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-gradient {
|
||||||
|
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #3b82f6 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-float {
|
||||||
|
animation: float 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float {
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,14 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Geist, Geist_Mono } from "next/font/google";
|
import { Inter } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
import { Providers } from "@/components/providers";
|
||||||
|
|
||||||
const geistSans = Geist({
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
variable: "--font-geist-sans",
|
|
||||||
subsets: ["latin"],
|
|
||||||
});
|
|
||||||
|
|
||||||
const geistMono = Geist_Mono({
|
|
||||||
variable: "--font-geist-mono",
|
|
||||||
subsets: ["latin"],
|
|
||||||
});
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Create Next App",
|
title: "Íris | Teleterapia ABA para Autismo",
|
||||||
description: "Generated by create next app",
|
description: "Conectamos famílias de crianças com TEA a terapeutas certificados BCBA. Teleterapia de qualidade + assistente IA 24/7.",
|
||||||
|
keywords: ["autismo", "TEA", "terapia ABA", "BCBA", "teleterapia", "crianças especiais"],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
@@ -23,11 +17,9 @@ export default function RootLayout({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="pt-BR">
|
||||||
<body
|
<body className={inter.className}>
|
||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
<Providers>{children}</Providers>
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
151
src/app/login/page.tsx
Normal file
151
src/app/login/page.tsx
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { signIn } from 'next-auth/react';
|
||||||
|
import { Sparkles, Mail, Lock, ArrowRight, Eye, EyeOff } from 'lucide-react';
|
||||||
|
|
||||||
|
export default function LoginPage() {
|
||||||
|
const router = useRouter();
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
});
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setLoading(true);
|
||||||
|
setError('');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await signIn('credentials', {
|
||||||
|
email: formData.email,
|
||||||
|
password: formData.password,
|
||||||
|
redirect: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result?.error) {
|
||||||
|
setError('E-mail ou senha inválidos');
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
router.push('/dashboard');
|
||||||
|
router.refresh();
|
||||||
|
} catch (err) {
|
||||||
|
setError('Erro ao fazer login. Tente novamente.');
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-b from-indigo-50 to-white flex flex-col">
|
||||||
|
{/* Header */}
|
||||||
|
<header className="p-4">
|
||||||
|
<div className="max-w-md mx-auto">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
{/* Login Form */}
|
||||||
|
<div className="flex-1 flex items-center justify-center px-4 py-12">
|
||||||
|
<div className="w-full max-w-md">
|
||||||
|
<div className="bg-white rounded-2xl shadow-xl p-8">
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<h1 className="text-2xl font-bold text-gray-900 mb-2">Bem-vindo de volta!</h1>
|
||||||
|
<p className="text-gray-500">Entre na sua conta para continuar</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="mb-6 p-4 bg-red-50 border border-red-200 rounded-xl text-red-700 text-center">
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">E-mail</label>
|
||||||
|
<div className="relative">
|
||||||
|
<Mail className="w-5 h-5 text-gray-400 absolute left-4 top-1/2 -translate-y-1/2" />
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
value={formData.email}
|
||||||
|
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
|
||||||
|
required
|
||||||
|
className="w-full pl-12 pr-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition"
|
||||||
|
placeholder="seu@email.com"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">Senha</label>
|
||||||
|
<div className="relative">
|
||||||
|
<Lock className="w-5 h-5 text-gray-400 absolute left-4 top-1/2 -translate-y-1/2" />
|
||||||
|
<input
|
||||||
|
type={showPassword ? 'text' : 'password'}
|
||||||
|
value={formData.password}
|
||||||
|
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
|
||||||
|
required
|
||||||
|
className="w-full pl-12 pr-12 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition"
|
||||||
|
placeholder="••••••••"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
|
className="absolute right-4 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||||
|
>
|
||||||
|
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<label className="flex items-center gap-2 cursor-pointer">
|
||||||
|
<input type="checkbox" className="w-4 h-4 text-indigo-600 rounded" />
|
||||||
|
<span className="text-sm text-gray-600">Lembrar de mim</span>
|
||||||
|
</label>
|
||||||
|
<a href="#" className="text-sm text-indigo-600 hover:underline">
|
||||||
|
Esqueceu a senha?
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={loading}
|
||||||
|
className="w-full bg-indigo-600 text-white py-3 rounded-xl hover:bg-indigo-700 transition font-medium flex items-center justify-center gap-2 disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<div className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
Entrar
|
||||||
|
<ArrowRight className="w-5 h-5" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div className="mt-6 text-center">
|
||||||
|
<p className="text-gray-500">
|
||||||
|
Não tem uma conta?{' '}
|
||||||
|
<Link href="/cadastro" className="text-indigo-600 font-medium hover:underline">
|
||||||
|
Cadastre-se
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
629
src/app/page.tsx
629
src/app/page.tsx
@@ -1,65 +1,582 @@
|
|||||||
import Image from "next/image";
|
'use client';
|
||||||
|
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import {
|
||||||
|
MessageCircle, Video, Brain, Heart, Shield, Clock,
|
||||||
|
ChevronRight, ChevronDown, Star, Check, Menu, X, Sparkles,
|
||||||
|
Users, Calendar, Award, ArrowRight, Zap, Phone, Mail,
|
||||||
|
Bot, Cpu, BarChart3, Mic, Camera, BookOpen, Target,
|
||||||
|
Lightbulb, TrendingUp, Play, Globe, DollarSign, Headphones,
|
||||||
|
Gamepad2, ClipboardCheck, UserCheck, MessageSquare, LineChart,
|
||||||
|
GraduationCap, Home as HomeIcon
|
||||||
|
} from 'lucide-react';
|
||||||
|
|
||||||
|
export default function LandingPage() {
|
||||||
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
|
const [openFaq, setOpenFaq] = useState<number | null>(null);
|
||||||
|
|
||||||
|
const howItWorks = [
|
||||||
|
{
|
||||||
|
num: '01',
|
||||||
|
title: 'Cadastro Simples',
|
||||||
|
desc: 'Preencha os dados da família e conte sobre seu filho em 5 minutos.',
|
||||||
|
icon: ClipboardCheck,
|
||||||
|
detail: 'Idade, diagnóstico, principais desafios e prioridades'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: '02',
|
||||||
|
title: 'Avaliação Inteligente',
|
||||||
|
desc: 'Nossa IA faz perguntas adaptativas para entender as necessidades específicas.',
|
||||||
|
icon: Brain,
|
||||||
|
detail: '15 minutos de questionário personalizado'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: '03',
|
||||||
|
title: 'Sessão com BCBA',
|
||||||
|
desc: 'Videochamada com terapeuta certificado para validar e criar o plano.',
|
||||||
|
icon: UserCheck,
|
||||||
|
detail: 'Profissional com mestrado em Análise do Comportamento'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: '04',
|
||||||
|
title: 'Atividades Diárias',
|
||||||
|
desc: 'Criança acessa jogos e exercícios terapêuticos guiados pela IA.',
|
||||||
|
icon: Gamepad2,
|
||||||
|
detail: '15-30 min/dia de atividades gamificadas'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: '05',
|
||||||
|
title: 'Suporte 24/7',
|
||||||
|
desc: 'Pais tiram dúvidas a qualquer hora com nossa IA especializada em ABA.',
|
||||||
|
icon: MessageSquare,
|
||||||
|
detail: '"Meu filho está em crise, o que faço?"'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: '06',
|
||||||
|
title: 'Evolução Contínua',
|
||||||
|
desc: 'Relatórios semanais + supervisão mensal do BCBA para ajustar o plano.',
|
||||||
|
icon: LineChart,
|
||||||
|
detail: 'Dashboard com métricas e progresso em tempo real'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const features = [
|
||||||
|
{
|
||||||
|
icon: Bot,
|
||||||
|
title: 'IA Terapeuta 24/7',
|
||||||
|
desc: 'Assistente treinado em ABA disponível a qualquer hora para orientar os pais e interagir com a criança.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: GraduationCap,
|
||||||
|
title: 'BCBAs Certificados',
|
||||||
|
desc: 'Profissionais com mestrado supervisionam o tratamento e garantem qualidade clínica.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Gamepad2,
|
||||||
|
title: 'Atividades Gamificadas',
|
||||||
|
desc: 'Jogos e exercícios baseados em ABA que a criança realmente quer fazer.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: BarChart3,
|
||||||
|
title: 'Relatórios Automáticos',
|
||||||
|
desc: 'Dashboard em tempo real com progresso, métricas e insights gerados por IA.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Users,
|
||||||
|
title: 'Treinamento de Pais',
|
||||||
|
desc: 'Aprenda técnicas ABA para aplicar no dia a dia e potencializar resultados.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Shield,
|
||||||
|
title: 'Seguro e Privado',
|
||||||
|
desc: 'Dados criptografados, conformidade LGPD e privacidade garantida.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const whatWeHelp = [
|
||||||
|
'Desenvolver comunicação e fala',
|
||||||
|
'Reduzir crises e comportamentos desafiadores',
|
||||||
|
'Melhorar habilidades sociais',
|
||||||
|
'Aumentar autonomia nas atividades diárias',
|
||||||
|
'Estabelecer rotinas e transições',
|
||||||
|
'Trabalhar seletividade alimentar',
|
||||||
|
'Treinar habilidades de brincar',
|
||||||
|
'Preparar para ambiente escolar',
|
||||||
|
];
|
||||||
|
|
||||||
|
const stats = [
|
||||||
|
{ value: '94%', label: 'das famílias relatam melhoria em 60 dias' },
|
||||||
|
{ value: '24/7', label: 'suporte disponível via IA' },
|
||||||
|
{ value: '85%', label: 'mais acessível que clínicas tradicionais' },
|
||||||
|
{ value: '48h', label: 'para começar (sem lista de espera)' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const testimonials = [
|
||||||
|
{
|
||||||
|
text: 'A combinação de IA com terapeuta humano é perfeita. A IA está sempre disponível quando preciso de ajuda rápida, e a BCBA garante que estamos no caminho certo. Meu filho evoluiu muito em 3 meses!',
|
||||||
|
name: 'Fernanda',
|
||||||
|
location: 'São Paulo, SP',
|
||||||
|
child: 'mãe do Davi, 4 anos'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Moramos no interior onde não tem BCBA. A IrisTEA trouxe terapia de qualidade para nossa casa. As atividades são tão divertidas que meu filho pede para fazer! Os relatórios me ajudam a ver a evolução.',
|
||||||
|
name: 'Carlos',
|
||||||
|
location: 'Chapecó, SC',
|
||||||
|
child: 'pai do Miguel, 6 anos'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Pagávamos R$3.000 em clínica e esperamos 8 meses na fila. Com a IrisTEA começamos em 2 dias, pagando 1/6 do valor. A IA responde minhas dúvidas às 3 da manhã quando meu filho não dorme.',
|
||||||
|
name: 'Juliana',
|
||||||
|
location: 'Recife, PE',
|
||||||
|
child: 'mãe do Théo, 5 anos'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const plans = [
|
||||||
|
{
|
||||||
|
name: 'Essencial',
|
||||||
|
price: '297',
|
||||||
|
features: [
|
||||||
|
'Acesso à IA 24/7',
|
||||||
|
'Atividades ilimitadas',
|
||||||
|
'1 sessão BCBA/mês (50 min)',
|
||||||
|
'Plano terapêutico personalizado',
|
||||||
|
'Relatórios semanais',
|
||||||
|
'Chat de suporte'
|
||||||
|
],
|
||||||
|
bcba: '1 sessão/mês',
|
||||||
|
popular: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Completo',
|
||||||
|
price: '497',
|
||||||
|
features: [
|
||||||
|
'Tudo do Essencial +',
|
||||||
|
'2 sessões BCBA/mês',
|
||||||
|
'Treinamento de pais',
|
||||||
|
'Grupo de apoio semanal',
|
||||||
|
'Materiais exclusivos',
|
||||||
|
'Relatórios para escola/médico'
|
||||||
|
],
|
||||||
|
bcba: '2 sessões/mês',
|
||||||
|
popular: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Intensivo',
|
||||||
|
price: '897',
|
||||||
|
features: [
|
||||||
|
'Tudo do Completo +',
|
||||||
|
'4 sessões BCBA/mês',
|
||||||
|
'Equipe multidisciplinar (fono, TO)',
|
||||||
|
'Suporte prioritário',
|
||||||
|
'Sessões extras sob demanda',
|
||||||
|
'Acompanhamento escolar'
|
||||||
|
],
|
||||||
|
bcba: '4 sessões/mês',
|
||||||
|
popular: false
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const faqs = [
|
||||||
|
{
|
||||||
|
q: 'Como funciona a combinação de IA com terapeuta humano?',
|
||||||
|
a: 'A IA cuida do dia a dia: atividades com a criança, suporte 24/7 aos pais, coleta de dados e relatórios. O BCBA (terapeuta humano certificado) faz a avaliação inicial, cria o plano terapêutico, supervisiona o progresso mensalmente e faz ajustes. É o melhor dos dois mundos: disponibilidade da IA + expertise humana.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'Quem são os BCBAs da IrisTEA?',
|
||||||
|
a: 'Nossos BCBAs são analistas do comportamento com mestrado ou doutorado, certificados pelo BACB (Behavior Analyst Certification Board). Todos têm mais de 1.000 horas de experiência supervisionada em TEA. Você conhece seu BCBA na primeira sessão e ele acompanha sua família durante todo o tratamento.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'Para qual idade funciona?',
|
||||||
|
a: 'Atendemos crianças de 2 a 12 anos. As atividades da IA são adaptadas automaticamente para cada faixa etária e nível de desenvolvimento. A intervenção precoce (2-6 anos) costuma ter os melhores resultados, mas crianças mais velhas também evoluem muito.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'Precisa de diagnóstico fechado?',
|
||||||
|
a: 'Recomendamos ter diagnóstico, mas aceitamos famílias com suspeita de TEA em processo de avaliação. Nossa IA e BCBAs podem ajudar a identificar áreas de atenção para compartilhar com o médico.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'Quanto tempo por dia meu filho precisa usar?',
|
||||||
|
a: 'Recomendamos 15-30 minutos de atividades na plataforma, mas isso é flexível. As atividades são gamificadas e a maioria das crianças pede para fazer mais! O importante é a consistência: um pouco todo dia é melhor que muito de vez em quando.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'Como é diferente de clínicas tradicionais?',
|
||||||
|
a: 'Preço: R$ 297-897 vs R$ 2.000-5.000. Espera: 48h vs 3-12 meses. Acesso: 24/7 vs horário comercial. Localização: qualquer lugar do Brasil vs sua cidade. Relatórios: automáticos em tempo real vs manuais mensais. A qualidade clínica é a mesma porque temos BCBAs certificados.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'Vocês têm convênio com plano de saúde?',
|
||||||
|
a: 'Ainda não, mas estamos em processo de credenciamento com os principais planos. Por enquanto, oferecemos recibo para reembolso. Muitos planos reembolsam 50-80% do valor de terapia ABA.'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export default function Home() {
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
<div className="min-h-screen bg-white">
|
||||||
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
{/* Header */}
|
||||||
<Image
|
<header className="fixed top-0 w-full bg-white/95 backdrop-blur-md z-50 border-b border-gray-100">
|
||||||
className="dark:invert"
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
src="/next.svg"
|
<div className="flex justify-between items-center h-16">
|
||||||
alt="Next.js logo"
|
<div className="flex items-center gap-2">
|
||||||
width={100}
|
<Image src="/icon-iristea.svg" alt="IrisTEA" width={40} height={40} className="rounded-xl" />
|
||||||
height={20}
|
<span className="text-2xl font-bold">
|
||||||
priority
|
<span className="text-gray-900">Iris</span>
|
||||||
/>
|
<span className="text-gradient">TEA</span>
|
||||||
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
</span>
|
||||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
</div>
|
||||||
To get started, edit the page.tsx file.
|
|
||||||
|
<nav className="hidden md:flex items-center gap-8">
|
||||||
|
<a href="#como-funciona" className="text-gray-600 hover:text-indigo-600 transition">Como Funciona</a>
|
||||||
|
<a href="#planos" className="text-gray-600 hover:text-indigo-600 transition">Planos</a>
|
||||||
|
<a href="#depoimentos" className="text-gray-600 hover:text-indigo-600 transition">Depoimentos</a>
|
||||||
|
<a href="#faq" className="text-gray-600 hover:text-indigo-600 transition">FAQ</a>
|
||||||
|
<Link href="/login" className="text-gray-600 hover:text-indigo-600 transition">Entrar</Link>
|
||||||
|
<Link href="/cadastro" className="bg-indigo-600 text-white px-5 py-2 rounded-full hover:bg-indigo-700 transition font-medium">
|
||||||
|
Começar Agora
|
||||||
|
</Link>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<button className="md:hidden" onClick={() => setIsMenuOpen(!isMenuOpen)}>
|
||||||
|
{isMenuOpen ? <X /> : <Menu />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isMenuOpen && (
|
||||||
|
<div className="md:hidden bg-white border-t border-gray-100 p-4">
|
||||||
|
<nav className="flex flex-col gap-4">
|
||||||
|
<a href="#como-funciona" className="text-gray-600">Como Funciona</a>
|
||||||
|
<a href="#planos" className="text-gray-600">Planos</a>
|
||||||
|
<a href="#faq" className="text-gray-600">FAQ</a>
|
||||||
|
<Link href="/login" className="text-gray-600">Entrar</Link>
|
||||||
|
<Link href="/cadastro" className="bg-indigo-600 text-white px-5 py-2 rounded-full text-center font-medium">
|
||||||
|
Começar Agora
|
||||||
|
</Link>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* Hero */}
|
||||||
|
<section className="pt-24 pb-16 md:pt-32 md:pb-24 bg-gradient-to-b from-indigo-50 via-white to-white">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="text-center max-w-4xl mx-auto">
|
||||||
|
<div className="inline-flex items-center gap-2 bg-gradient-to-r from-indigo-100 to-purple-100 text-indigo-700 px-4 py-2 rounded-full text-sm font-medium mb-6">
|
||||||
|
<Sparkles className="w-4 h-4" />
|
||||||
|
IA + Terapeutas Certificados = Resultado
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold text-gray-900 leading-tight mb-6">
|
||||||
|
Terapia ABA acessível para seu filho com{' '}
|
||||||
|
<span className="text-gradient">autismo</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
|
||||||
Looking for a starting point or more instructions? Head over to{" "}
|
<p className="text-xl text-gray-600 mb-8 max-w-2xl mx-auto">
|
||||||
<a
|
Combinamos <strong>inteligência artificial 24/7</strong> com <strong>terapeutas BCBA certificados</strong> para oferecer tratamento de qualidade por uma fração do preço das clínicas tradicionais.
|
||||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
</p>
|
||||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
|
||||||
>
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
Templates
|
<Link href="/cadastro" className="bg-indigo-600 text-white px-8 py-4 rounded-full hover:bg-indigo-700 transition font-medium text-lg flex items-center justify-center gap-2 shadow-lg shadow-indigo-200">
|
||||||
</a>{" "}
|
Começar Avaliação Gratuita
|
||||||
or the{" "}
|
<ArrowRight className="w-5 h-5" />
|
||||||
<a
|
</Link>
|
||||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
<a href="#como-funciona" className="border-2 border-gray-300 text-gray-700 px-8 py-4 rounded-full hover:border-indigo-600 hover:text-indigo-600 transition font-medium text-lg flex items-center justify-center gap-2">
|
||||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
<Play className="w-5 h-5" />
|
||||||
>
|
Ver Como Funciona
|
||||||
Learning
|
</a>
|
||||||
</a>{" "}
|
</div>
|
||||||
center.
|
|
||||||
|
{/* Stats */}
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-6 mt-12 pt-8 border-t border-gray-200">
|
||||||
|
{stats.map((stat, i) => (
|
||||||
|
<div key={i} className="text-center">
|
||||||
|
<div className="text-2xl md:text-3xl font-bold text-indigo-600">{stat.value}</div>
|
||||||
|
<div className="text-sm text-gray-500">{stat.label}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* How it Works - Step by Step */}
|
||||||
|
<section id="como-funciona" className="py-20 bg-white">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="text-center mb-16">
|
||||||
|
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-4">
|
||||||
|
Como a IrisTEA funciona
|
||||||
|
</h2>
|
||||||
|
<p className="text-xl text-gray-600 max-w-2xl mx-auto">
|
||||||
|
Um processo simples em 6 passos para transformar o desenvolvimento do seu filho
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
|
||||||
<a
|
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
{howItWorks.map((step, i) => (
|
||||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
<div key={i} className="relative bg-gradient-to-br from-gray-50 to-indigo-50 rounded-2xl p-8 hover:shadow-lg transition">
|
||||||
target="_blank"
|
<div className="text-5xl font-bold text-indigo-100 absolute top-4 right-4">{step.num}</div>
|
||||||
rel="noopener noreferrer"
|
<div className="w-14 h-14 rounded-xl bg-indigo-600 text-white flex items-center justify-center mb-6">
|
||||||
>
|
<step.icon className="w-7 h-7" />
|
||||||
<Image
|
|
||||||
className="dark:invert"
|
|
||||||
src="/vercel.svg"
|
|
||||||
alt="Vercel logomark"
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
/>
|
|
||||||
Deploy Now
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
|
||||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Documentation
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
<h3 className="text-xl font-semibold text-gray-900 mb-2">{step.title}</h3>
|
||||||
|
<p className="text-gray-600 mb-3">{step.desc}</p>
|
||||||
|
<p className="text-sm text-indigo-600 font-medium">{step.detail}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Features */}
|
||||||
|
<section className="py-20 bg-indigo-600 text-white">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<h2 className="text-3xl md:text-4xl font-bold mb-4">
|
||||||
|
O que você recebe
|
||||||
|
</h2>
|
||||||
|
<p className="text-xl text-indigo-200">
|
||||||
|
Tudo que sua família precisa em uma única plataforma
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
|
{features.map((feature, i) => (
|
||||||
|
<div key={i} className="bg-white/10 backdrop-blur rounded-2xl p-6 hover:bg-white/20 transition">
|
||||||
|
<div className="w-12 h-12 rounded-xl bg-white/20 flex items-center justify-center mb-4">
|
||||||
|
<feature.icon className="w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-lg font-semibold mb-2">{feature.title}</h3>
|
||||||
|
<p className="text-indigo-200">{feature.desc}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* What we help with */}
|
||||||
|
<section className="py-20 bg-gray-50">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="grid md:grid-cols-2 gap-12 items-center">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-6">
|
||||||
|
Trabalhamos os principais desafios do TEA
|
||||||
|
</h2>
|
||||||
|
<p className="text-gray-600 mb-8">
|
||||||
|
Nossa IA e BCBAs são especializados nos desafios mais comuns que famílias enfrentam no dia a dia. Criamos estratégias personalizadas baseadas em ABA para cada situação.
|
||||||
|
</p>
|
||||||
|
<Link href="/cadastro" className="inline-flex items-center gap-2 bg-indigo-600 text-white px-6 py-3 rounded-full hover:bg-indigo-700 transition font-medium">
|
||||||
|
Começar Avaliação Gratuita
|
||||||
|
<ArrowRight className="w-5 h-5" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 gap-3">
|
||||||
|
{whatWeHelp.map((item, i) => (
|
||||||
|
<div key={i} className="flex items-center gap-3 bg-white p-4 rounded-xl shadow-sm">
|
||||||
|
<div className="w-8 h-8 bg-green-100 rounded-full flex items-center justify-center flex-shrink-0">
|
||||||
|
<Check className="w-4 h-4 text-green-600" />
|
||||||
|
</div>
|
||||||
|
<span className="text-gray-700">{item}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Testimonials */}
|
||||||
|
<section id="depoimentos" className="py-20 bg-white">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="text-center mb-16">
|
||||||
|
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-4">
|
||||||
|
Famílias reais, resultados reais
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-3 gap-8">
|
||||||
|
{testimonials.map((t, i) => (
|
||||||
|
<div key={i} className="bg-gradient-to-br from-indigo-50 to-purple-50 rounded-2xl p-8">
|
||||||
|
<div className="flex items-center gap-1 mb-4">
|
||||||
|
{[1,2,3,4,5].map(j => (
|
||||||
|
<Star key={j} className="w-5 h-5 fill-yellow-400 text-yellow-400" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<p className="text-gray-700 mb-6">“{t.text}”</p>
|
||||||
|
<div>
|
||||||
|
<p className="font-semibold text-gray-900">{t.name}</p>
|
||||||
|
<p className="text-sm text-gray-500">{t.child}</p>
|
||||||
|
<p className="text-sm text-gray-400">{t.location}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Pricing */}
|
||||||
|
<section id="planos" className="py-20 bg-gradient-to-b from-white to-indigo-50">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="text-center mb-16">
|
||||||
|
<div className="inline-flex items-center gap-2 bg-green-100 text-green-700 px-4 py-2 rounded-full text-sm font-medium mb-4">
|
||||||
|
<DollarSign className="w-4 h-4" />
|
||||||
|
Até 85% mais acessível que clínicas tradicionais
|
||||||
|
</div>
|
||||||
|
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-4">
|
||||||
|
Planos para todas as famílias
|
||||||
|
</h2>
|
||||||
|
<p className="text-xl text-gray-600">
|
||||||
|
Sem taxa de adesão. Cancele quando quiser.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-3 gap-8 max-w-5xl mx-auto">
|
||||||
|
{plans.map((plan, i) => (
|
||||||
|
<div key={i} className={`rounded-2xl p-8 ${plan.popular ? 'bg-indigo-600 text-white ring-4 ring-indigo-600 ring-offset-4 scale-105' : 'bg-white border-2 border-gray-200'}`}>
|
||||||
|
{plan.popular && (
|
||||||
|
<span className="bg-yellow-400 text-yellow-900 text-sm font-medium px-3 py-1 rounded-full">
|
||||||
|
Mais Popular
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<h3 className={`text-2xl font-bold mt-4 ${plan.popular ? 'text-white' : 'text-gray-900'}`}>
|
||||||
|
{plan.name}
|
||||||
|
</h3>
|
||||||
|
<p className={`text-sm ${plan.popular ? 'text-indigo-200' : 'text-gray-500'}`}>
|
||||||
|
{plan.bcba}
|
||||||
|
</p>
|
||||||
|
<div className="my-6">
|
||||||
|
<span className={`text-5xl font-bold ${plan.popular ? 'text-white' : 'text-gray-900'}`}>
|
||||||
|
R${plan.price}
|
||||||
|
</span>
|
||||||
|
<span className={plan.popular ? 'text-indigo-200' : 'text-gray-500'}>/mês</span>
|
||||||
|
</div>
|
||||||
|
<ul className="space-y-3 mb-8">
|
||||||
|
{plan.features.map((f, j) => (
|
||||||
|
<li key={j} className="flex items-start gap-2">
|
||||||
|
<Check className={`w-5 h-5 mt-0.5 ${plan.popular ? 'text-indigo-200' : 'text-indigo-600'}`} />
|
||||||
|
<span className={plan.popular ? 'text-indigo-100' : 'text-gray-600'}>{f}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<Link
|
||||||
|
href="/cadastro"
|
||||||
|
className={`block text-center py-3 rounded-full font-medium transition ${
|
||||||
|
plan.popular
|
||||||
|
? 'bg-white text-indigo-600 hover:bg-indigo-50'
|
||||||
|
: 'bg-indigo-600 text-white hover:bg-indigo-700'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Começar Agora
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-center mt-12 p-6 bg-white rounded-2xl shadow-sm max-w-2xl mx-auto">
|
||||||
|
<p className="text-gray-600">
|
||||||
|
<strong>Comparativo:</strong> Clínicas tradicionais cobram R$ 2.000-5.000/mês com lista de espera de 3-12 meses.
|
||||||
|
Com a IrisTEA você começa em 48h pagando até 85% menos.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* FAQ */}
|
||||||
|
<section id="faq" className="py-20 bg-white">
|
||||||
|
<div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="text-center mb-16">
|
||||||
|
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-4">
|
||||||
|
Perguntas Frequentes
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{faqs.map((faq, i) => (
|
||||||
|
<div key={i} className="border border-gray-200 rounded-xl overflow-hidden">
|
||||||
|
<button
|
||||||
|
onClick={() => setOpenFaq(openFaq === i ? null : i)}
|
||||||
|
className="w-full flex items-center justify-between p-6 text-left bg-white hover:bg-gray-50 transition"
|
||||||
|
>
|
||||||
|
<span className="font-semibold text-gray-900 pr-4">{faq.q}</span>
|
||||||
|
<ChevronDown className={`w-5 h-5 text-gray-500 transition-transform flex-shrink-0 ${openFaq === i ? 'rotate-180' : ''}`} />
|
||||||
|
</button>
|
||||||
|
{openFaq === i && (
|
||||||
|
<div className="px-6 pb-6 text-gray-600">
|
||||||
|
{faq.a}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* CTA */}
|
||||||
|
<section className="py-20 bg-gradient-to-r from-indigo-600 to-purple-600">
|
||||||
|
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||||
|
<h2 className="text-3xl md:text-4xl font-bold text-white mb-6">
|
||||||
|
Seu filho merece o melhor tratamento
|
||||||
|
</h2>
|
||||||
|
<p className="text-xl text-indigo-200 mb-8">
|
||||||
|
Avaliação gratuita com nossa IA + primeira sessão com BCBA.
|
||||||
|
Sem compromisso. Comece em 48 horas.
|
||||||
|
</p>
|
||||||
|
<Link href="/cadastro" className="inline-flex items-center gap-2 bg-white text-indigo-600 px-8 py-4 rounded-full hover:bg-indigo-50 transition font-medium text-lg shadow-xl">
|
||||||
|
<Sparkles className="w-5 h-5" />
|
||||||
|
Começar Avaliação Gratuita
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<footer className="bg-gray-900 text-gray-400 py-12">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="grid md:grid-cols-4 gap-8 mb-8">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2 mb-4">
|
||||||
|
<Image src="/icon-iristea.svg" alt="IrisTEA" width={40} height={40} className="rounded-xl" />
|
||||||
|
<span className="text-2xl font-bold text-white">IrisTEA</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm">
|
||||||
|
Terapia ABA acessível combinando inteligência artificial com terapeutas certificados.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-white font-semibold mb-4">Plataforma</h4>
|
||||||
|
<ul className="space-y-2 text-sm">
|
||||||
|
<li><a href="#como-funciona" className="hover:text-white transition">Como Funciona</a></li>
|
||||||
|
<li><a href="#planos" className="hover:text-white transition">Planos</a></li>
|
||||||
|
<li><a href="#" className="hover:text-white transition">Para Profissionais</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-white font-semibold mb-4">Recursos</h4>
|
||||||
|
<ul className="space-y-2 text-sm">
|
||||||
|
<li><a href="#" className="hover:text-white transition">Blog</a></li>
|
||||||
|
<li><a href="#" className="hover:text-white transition">Guias para Pais</a></li>
|
||||||
|
<li><a href="#faq" className="hover:text-white transition">FAQ</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="text-white font-semibold mb-4">Contato</h4>
|
||||||
|
<ul className="space-y-2 text-sm">
|
||||||
|
<li className="flex items-center gap-2">
|
||||||
|
<Mail className="w-4 h-4" />
|
||||||
|
contato@iristea.com.br
|
||||||
|
</li>
|
||||||
|
<li className="flex items-center gap-2">
|
||||||
|
<MessageSquare className="w-4 h-4" />
|
||||||
|
Chat 24/7 no app
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="border-t border-gray-800 pt-8 flex flex-col md:flex-row justify-between items-center text-sm">
|
||||||
|
<p>© 2026 IrisTEA. Todos os direitos reservados.</p>
|
||||||
|
<div className="flex gap-6 mt-4 md:mt-0">
|
||||||
|
<a href="#" className="hover:text-white transition">Privacidade</a>
|
||||||
|
<a href="#" className="hover:text-white transition">Termos</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
8
src/components/providers.tsx
Normal file
8
src/components/providers.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { SessionProvider } from 'next-auth/react';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export function Providers({ children }: { children: ReactNode }) {
|
||||||
|
return <SessionProvider>{children}</SessionProvider>;
|
||||||
|
}
|
||||||
75
src/lib/auth.ts
Normal file
75
src/lib/auth.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import NextAuth from 'next-auth';
|
||||||
|
import Credentials from 'next-auth/providers/credentials';
|
||||||
|
import bcrypt from 'bcrypt';
|
||||||
|
import { prisma } from './prisma';
|
||||||
|
|
||||||
|
export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||||
|
session: {
|
||||||
|
strategy: 'jwt',
|
||||||
|
},
|
||||||
|
pages: {
|
||||||
|
signIn: '/login',
|
||||||
|
signOut: '/',
|
||||||
|
error: '/login',
|
||||||
|
},
|
||||||
|
providers: [
|
||||||
|
Credentials({
|
||||||
|
name: 'credentials',
|
||||||
|
credentials: {
|
||||||
|
email: { label: 'Email', type: 'email' },
|
||||||
|
password: { label: 'Password', type: 'password' },
|
||||||
|
},
|
||||||
|
async authorize(credentials) {
|
||||||
|
if (!credentials?.email || !credentials?.password) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: {
|
||||||
|
email: credentials.email as string,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
subscription: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const passwordMatch = await bcrypt.compare(
|
||||||
|
credentials.password as string,
|
||||||
|
user.password
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!passwordMatch) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: user.id,
|
||||||
|
email: user.email,
|
||||||
|
name: user.name,
|
||||||
|
role: user.role,
|
||||||
|
image: user.image,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
callbacks: {
|
||||||
|
async jwt({ token, user }) {
|
||||||
|
if (user) {
|
||||||
|
token.id = user.id;
|
||||||
|
token.role = (user as any).role;
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
},
|
||||||
|
async session({ session, token }) {
|
||||||
|
if (session.user) {
|
||||||
|
session.user.id = token.id as string;
|
||||||
|
(session.user as any).role = token.role;
|
||||||
|
}
|
||||||
|
return session;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
13
src/lib/prisma.ts
Normal file
13
src/lib/prisma.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
|
const globalForPrisma = globalThis as unknown as {
|
||||||
|
prisma: PrismaClient | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const prisma =
|
||||||
|
globalForPrisma.prisma ??
|
||||||
|
new PrismaClient({
|
||||||
|
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
|
||||||
33
src/middleware.ts
Normal file
33
src/middleware.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { auth } from '@/lib/auth';
|
||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
|
||||||
|
export default auth((req) => {
|
||||||
|
const isLoggedIn = !!req.auth;
|
||||||
|
const isOnDashboard = req.nextUrl.pathname.startsWith('/dashboard');
|
||||||
|
const isOnTherapist = req.nextUrl.pathname.startsWith('/terapeuta');
|
||||||
|
const isOnLogin = req.nextUrl.pathname === '/login';
|
||||||
|
const isOnCadastro = req.nextUrl.pathname.startsWith('/cadastro');
|
||||||
|
|
||||||
|
// Proteger rotas do dashboard
|
||||||
|
if (isOnDashboard || isOnTherapist) {
|
||||||
|
if (!isLoggedIn) {
|
||||||
|
return NextResponse.redirect(new URL('/login', req.nextUrl));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar role para área do terapeuta
|
||||||
|
if (isOnTherapist && req.auth?.user?.role !== 'therapist' && req.auth?.user?.role !== 'admin') {
|
||||||
|
return NextResponse.redirect(new URL('/dashboard', req.nextUrl));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirecionar se já estiver logado e tentar acessar login/cadastro
|
||||||
|
if (isLoggedIn && (isOnLogin || isOnCadastro)) {
|
||||||
|
return NextResponse.redirect(new URL('/dashboard', req.nextUrl));
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.next();
|
||||||
|
});
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
|
||||||
|
};
|
||||||
28
src/types/next-auth.d.ts
vendored
Normal file
28
src/types/next-auth.d.ts
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import 'next-auth';
|
||||||
|
|
||||||
|
declare module 'next-auth' {
|
||||||
|
interface Session {
|
||||||
|
user: {
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
name: string;
|
||||||
|
role: string;
|
||||||
|
image?: string | null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface User {
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
name: string;
|
||||||
|
role: string;
|
||||||
|
image?: string | null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'next-auth/jwt' {
|
||||||
|
interface JWT {
|
||||||
|
id: string;
|
||||||
|
role: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user