mirror of
https://github.com/lucasrcsantana/story-generator.git
synced 2025-12-16 21:37:51 +00:00
feat: adiciona configuração docker e ci/cd
- Implementa Dockerfile com multi-stage build - Configura pipeline no Gitea Actions - Adiciona integração com Redis - Implementa healthchecks - Configura registry no Gitea minor: novas funcionalidades de infraestrutura
This commit is contained in:
parent
563a62a517
commit
521a99a5c2
9
.dockerignore
Normal file
9
.dockerignore
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
node_modules
|
||||||
|
.git
|
||||||
|
.env*
|
||||||
|
.dockerignore
|
||||||
|
Dockerfile
|
||||||
|
README.md
|
||||||
|
.next
|
||||||
|
build
|
||||||
|
dist
|
||||||
8
.gitea/config/registry.yml
Normal file
8
.gitea/config/registry.yml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
version: "1.0"
|
||||||
|
registries:
|
||||||
|
- name: seu-registry
|
||||||
|
host: seu-registry.com
|
||||||
|
type: container
|
||||||
|
credentials:
|
||||||
|
username: ${REGISTRY_USERNAME}
|
||||||
|
password: ${REGISTRY_PASSWORD}
|
||||||
58
.gitea/workflows/docker-build.yml
Normal file
58
.gitea/workflows/docker-build.yml
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
name: Docker Build and Push
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
tags: [ 'v*' ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v4
|
||||||
|
with:
|
||||||
|
images: seu-registry.com/historias-magicas
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=ref,event=pr
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
type=sha
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
|
- name: Login to Container Registry
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: seu-registry.com
|
||||||
|
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||||
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=registry,ref=seu-registry.com/historias-magicas:buildcache
|
||||||
|
cache-to: type=registry,ref=seu-registry.com/historias-magicas:buildcache,mode=max
|
||||||
|
|
||||||
|
- name: Update Portainer stack
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
uses: appleboy/ssh-action@master
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.DEPLOY_HOST }}
|
||||||
|
username: ${{ secrets.DEPLOY_USER }}
|
||||||
|
key: ${{ secrets.DEPLOY_KEY }}
|
||||||
|
script: |
|
||||||
|
cd /opt/portainer
|
||||||
|
docker stack deploy -c portainer-stack.yml historias-magicas
|
||||||
18
.github/workflows/deploy.yml
vendored
Normal file
18
.github/workflows/deploy.yml
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
name: Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Deploy to production
|
||||||
|
env:
|
||||||
|
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
|
||||||
|
run: |
|
||||||
|
echo "$DEPLOY_KEY" > deploy_key
|
||||||
|
chmod 600 deploy_key
|
||||||
|
ssh -i deploy_key user@seu-servidor.com 'cd /app && ./scripts/deploy.sh'
|
||||||
164
CHANGELOG.md
164
CHANGELOG.md
@ -5,141 +5,43 @@ Todas as mudanças notáveis neste projeto serão documentadas neste arquivo.
|
|||||||
O formato é baseado em [Keep a Changelog](https://keepachangelog.com/pt-BR/1.0.0/),
|
O formato é baseado em [Keep a Changelog](https://keepachangelog.com/pt-BR/1.0.0/),
|
||||||
e este projeto adere ao [Semantic Versioning](https://semver.org/lang/pt-BR/).
|
e este projeto adere ao [Semantic Versioning](https://semver.org/lang/pt-BR/).
|
||||||
|
|
||||||
## [0.1.0] - 2024-03-23
|
## [0.2.0] - 2024-03-21
|
||||||
|
|
||||||
### Adicionado
|
### Adicionado
|
||||||
- Landing page para pais
|
- Configuração Docker para ambiente de produção
|
||||||
- Design moderno e atraente
|
- Pipeline de CI/CD no Gitea Actions
|
||||||
- Seções de benefícios e funcionalidades
|
- Integração com Redis para cache
|
||||||
- Demonstração de métricas e análises
|
- Healthcheck da aplicação
|
||||||
- Gráficos interativos com recharts
|
- Adiciona seções:
|
||||||
- Gráfico de evolução do aluno
|
- Hero com CTA e social proof
|
||||||
- Comparativo antes/depois
|
- Problemas e Soluções
|
||||||
- Depoimentos de usuários
|
- Como a Magia Acontece
|
||||||
- CTAs estratégicos
|
- Comparação antes/depois
|
||||||
- Demonstração interativa
|
- Benefícios Mágicos em layout horizontal
|
||||||
- Copywriting focado em resultados
|
- Testimoniais
|
||||||
- Imagens otimizadas e responsivas
|
- Planos e preços
|
||||||
- Rota dedicada em /para-pais
|
- FAQ
|
||||||
|
- CTA final
|
||||||
- Edge function `generate-story` para geração de histórias com IA
|
- Footer
|
||||||
- Integração com OpenAI GPT para criação de texto
|
|
||||||
- Integração com DALL-E para geração de imagens
|
|
||||||
- Sistema de logs estruturados para monitoramento
|
|
||||||
- Tratamento robusto de erros e validações
|
|
||||||
|
|
||||||
- Componente `StoryGenerator` para interface de criação
|
|
||||||
- Fluxo de seleção de categorias (tema, disciplina, personagem, cenário)
|
|
||||||
- Feedback visual do processo de geração
|
|
||||||
- Validações de campos obrigatórios
|
|
||||||
- Navegação automática entre etapas
|
|
||||||
- Tratamento de erros com feedback visual
|
|
||||||
|
|
||||||
- Nova estrutura de dados para páginas de histórias
|
|
||||||
- Tabela `story_pages` para melhor organização
|
|
||||||
- Relacionamentos explícitos entre histórias e páginas
|
|
||||||
- Suporte a ordenação por número da página
|
|
||||||
|
|
||||||
- Otimização de carregamento de imagens
|
|
||||||
- Lazy loading com placeholders
|
|
||||||
- Pré-carregamento da próxima imagem
|
|
||||||
- Cache de imagens no frontend
|
|
||||||
- Transformações de imagem no Supabase Storage
|
|
||||||
- Múltiplas resoluções de imagem
|
|
||||||
|
|
||||||
- Sistema de cache de imagens no frontend
|
|
||||||
- Implementação de imageCache.ts
|
|
||||||
- Prevenção de recarregamento desnecessário
|
|
||||||
- Melhor performance em navegação
|
|
||||||
|
|
||||||
### Modificado
|
|
||||||
- Reorganização da landing page para pais
|
|
||||||
- Reordenação das seções para melhor fluxo
|
|
||||||
- Hero → Por que escolher → Como Funciona → Análise → Diferença → Depoimentos → CTA
|
|
||||||
- Otimização da jornada do usuário
|
|
||||||
- Melhor hierarquia de informações
|
|
||||||
- Fluxo narrativo mais coerente
|
|
||||||
- Progressão lógica de informações
|
|
||||||
- Posicionamento estratégico do CTA
|
|
||||||
|
|
||||||
- Reorganização da estrutura de arquivos
|
|
||||||
- Remoção da pasta /pages/story
|
|
||||||
- Consolidação dos componentes de história em /pages/student-dashboard
|
|
||||||
- Melhor organização hierárquica das rotas
|
|
||||||
|
|
||||||
- Otimização global de imagens
|
|
||||||
- Conversão automática para WebP
|
|
||||||
- Redimensionamento otimizado por contexto
|
|
||||||
- Parâmetros de qualidade personalizados
|
|
||||||
- Função utilitária centralizada
|
|
||||||
- Implementação em todas as rotas
|
|
||||||
- Otimização contextual por uso
|
|
||||||
- Pré-carregamento otimizado
|
|
||||||
|
|
||||||
- Otimização de imagens de capa
|
|
||||||
- Uso da primeira página como capa
|
|
||||||
- Tamanho reduzido para thumbnails
|
|
||||||
- Carregamento lazy para melhor performance
|
|
||||||
- Refatoração da interface de capa
|
|
||||||
- Tipagem específica para cover na interface Story
|
|
||||||
- Padronização do uso da primeira página como capa
|
|
||||||
- Otimização de queries para busca de capas
|
|
||||||
- Parâmetros de transformação para thumbnails
|
|
||||||
|
|
||||||
- Padronização da interface de histórias
|
|
||||||
- Consistência visual entre dashboard e lista
|
|
||||||
- Cards de história com mesmo estilo e comportamento
|
|
||||||
- Melhor experiência do usuário na navegação
|
|
||||||
|
|
||||||
- Atualização do schema do banco para suportar novas categorias
|
|
||||||
- Adição de tabelas para temas, disciplinas, personagens e cenários
|
|
||||||
- Relacionamentos entre histórias e categorias
|
|
||||||
- Índices para otimização de consultas
|
|
||||||
|
|
||||||
### Técnico
|
### Técnico
|
||||||
- Melhorias no tratamento de imagens
|
- Dockerfile com multi-stage build para otimização
|
||||||
- Tratamento de URLs indefinidas
|
- Configuração de registry no Gitea
|
||||||
- Imagem padrão para fallback
|
- Cache de histórias com Redis
|
||||||
- Otimização consistente em edge functions
|
- Scripts de deploy e monitoramento
|
||||||
- Melhor tipagem para URLs
|
|
||||||
- Prevenção de erros em runtime
|
|
||||||
|
|
||||||
- Implementação de logs estruturados com prefixos por contexto
|
### Modificado
|
||||||
- Validações de dados em múltiplas camadas
|
- Atualização do next.config.js para suporte standalone
|
||||||
- Tratamento de respostas da IA com fallbacks
|
- Adaptação da API para usar Redis cache
|
||||||
- Otimização de queries no banco de dados
|
- Configuração de redes Docker
|
||||||
- Feedback em tempo real do processo de geração
|
- Otimiza UX/UI com:
|
||||||
- Queries otimizadas para nova estrutura de dados
|
- Animações suaves
|
||||||
- Melhor tratamento de estados de loading e erro
|
- Gradientes modernos
|
||||||
- Implementação de componente ImageWithLoading
|
- Layout responsivo
|
||||||
- Sistema de cache de imagens
|
- Elementos interativos
|
||||||
- Otimização de URLs de imagem
|
- Social proof estratégico
|
||||||
|
|
||||||
- Refatoração de componentes para melhor reuso
|
|
||||||
- Separação de lógica de carregamento de imagens
|
|
||||||
- Componentes mais modulares e reutilizáveis
|
|
||||||
- Melhor organização do código
|
|
||||||
|
|
||||||
### Segurança
|
### Segurança
|
||||||
- Validação de dados de entrada na edge function
|
- Implementação de healthchecks
|
||||||
- Verificação de permissões do usuário
|
- Configuração de redes isoladas
|
||||||
- Sanitização de prompts para a IA
|
- Proteção de variáveis de ambiente
|
||||||
- Proteção contra dados sensíveis nos logs
|
|
||||||
|
|
||||||
### Próximos Passos
|
|
||||||
- [ ] Implementar cache de respostas da IA
|
|
||||||
- [ ] Adicionar retry policy para falhas de geração
|
|
||||||
- [ ] Melhorar prompts para histórias mais educativas
|
|
||||||
- [ ] Adicionar métricas de uso e performance
|
|
||||||
|
|
||||||
- Melhorias na página de demonstração
|
|
||||||
- Reorganização do layout e componentes
|
|
||||||
- Priorização do conteúdo da história
|
|
||||||
- Dashboard de métricas movido para baixo
|
|
||||||
- Fluxo interativo de demonstração
|
|
||||||
- Simulação de gravação e análise
|
|
||||||
- CTAs personalizados para escolas e pais
|
|
||||||
- Separação de dados mock
|
|
||||||
- Feedback visual aprimorado
|
|
||||||
- Estilos mais modernos e consistentes
|
|
||||||
- Melhor experiência de demonstração
|
|
||||||
40
Dockerfile
Normal file
40
Dockerfile
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# Build stage
|
||||||
|
FROM node:18-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Adicionar dependência do Redis
|
||||||
|
RUN apk add --no-cache redis
|
||||||
|
|
||||||
|
# Copiar arquivos de dependências
|
||||||
|
COPY package*.json ./
|
||||||
|
COPY yarn.lock ./
|
||||||
|
|
||||||
|
# Instalar dependências
|
||||||
|
RUN yarn install --frozen-lockfile
|
||||||
|
|
||||||
|
# Copiar código fonte
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build da aplicação
|
||||||
|
RUN yarn build
|
||||||
|
|
||||||
|
# Production stage
|
||||||
|
FROM node:18-alpine AS runner
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Adicionar dependência do Redis
|
||||||
|
RUN apk add --no-cache redis
|
||||||
|
|
||||||
|
# Copiar arquivos necessários do builder
|
||||||
|
COPY --from=builder /app/next.config.js ./
|
||||||
|
COPY --from=builder /app/public ./public
|
||||||
|
COPY --from=builder /app/.next/standalone ./
|
||||||
|
COPY --from=builder /app/.next/static ./.next/static
|
||||||
|
|
||||||
|
# Expor porta
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Comando para rodar a aplicação
|
||||||
|
CMD ["node", "server.js"]
|
||||||
33
docker-compose.yml
Normal file
33
docker-compose.yml
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
historias-magicas:
|
||||||
|
image: ${REGISTRY}/historias-magicas:${TAG}
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
- NEXT_PUBLIC_SUPABASE_URL=${NEXT_PUBLIC_SUPABASE_URL}
|
||||||
|
- NEXT_PUBLIC_SUPABASE_ANON_KEY=${NEXT_PUBLIC_SUPABASE_ANON_KEY}
|
||||||
|
- OPENAI_API_KEY=${OPENAI_API_KEY}
|
||||||
|
- REDIS_URL=redis://redis:6379
|
||||||
|
networks:
|
||||||
|
- network_public
|
||||||
|
deploy:
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.historias-magicas.rule=Host(`${DOMAIN}`)"
|
||||||
|
- "traefik.http.routers.historias-magicas.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.historias-magicas.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.historias-magicas.loadbalancer.server.port=3000"
|
||||||
|
- "traefik.docker.network=traefik-public"
|
||||||
|
replicas: 1
|
||||||
|
update_config:
|
||||||
|
parallelism: 1
|
||||||
|
delay: 10s
|
||||||
|
restart_policy:
|
||||||
|
condition: on-failure
|
||||||
|
|
||||||
|
networks:
|
||||||
|
traefik-public:
|
||||||
|
external: true
|
||||||
|
redis-network:
|
||||||
|
external: true
|
||||||
12
next.config.js
Normal file
12
next.config.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
output: 'standalone',
|
||||||
|
images: {
|
||||||
|
domains: [
|
||||||
|
'oaidalleapiprodscus.blob.core.windows.net',
|
||||||
|
// outros domínios necessários
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = nextConfig
|
||||||
@ -9,7 +9,10 @@
|
|||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
"format": "prettier --write \"src/**/*.{ts,tsx}\""
|
"format": "prettier --write \"src/**/*.{ts,tsx}\"",
|
||||||
|
"docker:build": "docker build -t historias-magicas .",
|
||||||
|
"docker:run": "docker run -p 3000:3000 historias-magicas",
|
||||||
|
"deploy:prod": "docker-compose up -d --build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-accordion": "^1.2.2",
|
"@radix-ui/react-accordion": "^1.2.2",
|
||||||
|
|||||||
34
portainer-stack.yml
Normal file
34
portainer-stack.yml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
historias-magicas:
|
||||||
|
image: ${REGISTRY}/historias-magicas:${TAG}
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
- NEXT_PUBLIC_SUPABASE_URL=${NEXT_PUBLIC_SUPABASE_URL}
|
||||||
|
- NEXT_PUBLIC_SUPABASE_ANON_KEY=${NEXT_PUBLIC_SUPABASE_ANON_KEY}
|
||||||
|
- OPENAI_API_KEY=${OPENAI_API_KEY}
|
||||||
|
- REDIS_URL=redis://redis:6379
|
||||||
|
networks:
|
||||||
|
- traefik-public
|
||||||
|
- redis-network
|
||||||
|
deploy:
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.historias-magicas.rule=Host(`${DOMAIN}`)"
|
||||||
|
- "traefik.http.routers.historias-magicas.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.historias-magicas.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.historias-magicas.loadbalancer.server.port=3000"
|
||||||
|
- "traefik.docker.network=traefik-public"
|
||||||
|
replicas: 2
|
||||||
|
update_config:
|
||||||
|
parallelism: 1
|
||||||
|
delay: 10s
|
||||||
|
restart_policy:
|
||||||
|
condition: on-failure
|
||||||
|
|
||||||
|
networks:
|
||||||
|
traefik-public:
|
||||||
|
external: true
|
||||||
|
redis-network:
|
||||||
|
external: true
|
||||||
5
src/lib/redis.ts
Normal file
5
src/lib/redis.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { Redis } from 'ioredis';
|
||||||
|
|
||||||
|
const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379');
|
||||||
|
|
||||||
|
export default redis;
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { StoryPrompt } from '@/types/story-generator'
|
||||||
import { createClient } from '@supabase/supabase-js'
|
import { createClient } from '@supabase/supabase-js'
|
||||||
|
|
||||||
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
|
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
|
||||||
@ -13,4 +14,26 @@ export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
|
|||||||
persistSession: true,
|
persistSession: true,
|
||||||
detectSessionInUrl: true
|
detectSessionInUrl: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const generateStoryFunction = async (prompt: StoryPrompt) => {
|
||||||
|
const { data: { session } } = await supabase.auth.getSession()
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
'https://seu-project-ref.supabase.co/functions/v1/generate-story',
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${session?.access_token}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify(prompt),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Falha ao gerar história')
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
}
|
||||||
17
src/pages/api/health.ts
Normal file
17
src/pages/api/health.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import redis from '@/lib/redis';
|
||||||
|
|
||||||
|
export default async function handler(req, res) {
|
||||||
|
try {
|
||||||
|
// Verifica conexão com Redis
|
||||||
|
await redis.ping();
|
||||||
|
|
||||||
|
// Verifica conexão com Supabase
|
||||||
|
const { data, error } = await supabase.from('stories').select('id').limit(1);
|
||||||
|
|
||||||
|
if (error) throw error;
|
||||||
|
|
||||||
|
res.status(200).json({ status: 'healthy' });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ status: 'unhealthy', error: error.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/pages/api/stories/[id].ts
Normal file
24
src/pages/api/stories/[id].ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import redis from '@/lib/redis';
|
||||||
|
import { supabase } from '@/lib/supabase';
|
||||||
|
|
||||||
|
export default async function handler(req, res) {
|
||||||
|
const { id } = req.query;
|
||||||
|
|
||||||
|
// Tenta pegar do cache primeiro
|
||||||
|
const cachedStory = await redis.get(`story:${id}`);
|
||||||
|
if (cachedStory) {
|
||||||
|
return res.json(JSON.parse(cachedStory));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Se não estiver no cache, busca do Supabase
|
||||||
|
const { data: story } = await supabase
|
||||||
|
.from('stories')
|
||||||
|
.select('*')
|
||||||
|
.eq('id', id)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
// Salva no cache por 1 hora
|
||||||
|
await redis.setex(`story:${id}`, 3600, JSON.stringify(story));
|
||||||
|
|
||||||
|
return res.json(story);
|
||||||
|
}
|
||||||
807
src/pages/landing/EducationalForParents.tsx
Normal file
807
src/pages/landing/EducationalForParents.tsx
Normal file
@ -0,0 +1,807 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import {
|
||||||
|
ArrowRight, Wand2, Shield, Star, BookOpen,
|
||||||
|
Brain, Target, Users, Award, CheckCircle,
|
||||||
|
Clock, Heart, Sparkles, ScrollText, Lock, X,
|
||||||
|
Facebook, Instagram, Twitter, Youtube
|
||||||
|
} from 'lucide-react';
|
||||||
|
|
||||||
|
export function EducationalForParents(): JSX.Element {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen">
|
||||||
|
{/* 1. Hero Section */}
|
||||||
|
<section className="relative overflow-hidden bg-gradient-to-b from-purple-50 via-white to-purple-50">
|
||||||
|
<div className="absolute inset-0 bg-[url('/patterns/magic.svg')] opacity-5" />
|
||||||
|
<div className="px-4 py-24 mx-auto max-w-7xl relative">
|
||||||
|
{/* Reading Time */}
|
||||||
|
<div className="absolute top-8 right-8 flex items-center gap-2 text-sm text-gray-500">
|
||||||
|
<Clock className="h-4 w-4" />
|
||||||
|
<span>Tempo de leitura: 5 minutos</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col md:flex-row items-center gap-16">
|
||||||
|
<div className="flex-1 space-y-8">
|
||||||
|
<h1 className="text-6xl font-bold text-gray-900 leading-tight">
|
||||||
|
Transforme o Aprendizado em Uma
|
||||||
|
<span className="block bg-gradient-to-r from-purple-600 to-blue-500 bg-clip-text text-transparent">
|
||||||
|
Aventura Mágica
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className="text-xl text-gray-600 leading-relaxed">
|
||||||
|
Histórias educativas personalizadas que encantam e ensinam, criadas especialmente
|
||||||
|
para o desenvolvimento único do seu filho.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<button
|
||||||
|
onClick={() => navigate('/register/parent')}
|
||||||
|
className="group px-8 py-4 bg-gradient-to-r from-purple-600 to-blue-500
|
||||||
|
text-white rounded-xl hover:from-purple-700 hover:to-blue-600
|
||||||
|
transform hover:scale-105 transition-all shadow-lg"
|
||||||
|
>
|
||||||
|
Comece Sua Aventura Mágica Grátis
|
||||||
|
<ArrowRight className="inline-block ml-2 h-5 w-5
|
||||||
|
group-hover:translate-x-1 transition-transform" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Social Proof */}
|
||||||
|
<div className="flex gap-8 text-sm text-gray-600">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<BookOpen className="h-5 w-5 text-purple-600" />
|
||||||
|
<span>Mais de 10.000 histórias mágicas criadas</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Users className="h-5 w-5 text-blue-500" />
|
||||||
|
<span>5.000 pequenos leitores encantados</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="relative">
|
||||||
|
<div className="absolute -inset-4 bg-gradient-to-r from-purple-600 to-blue-500
|
||||||
|
rounded-2xl blur-lg opacity-20" />
|
||||||
|
<img
|
||||||
|
src="/images/magic-book.webp"
|
||||||
|
alt="Crianças mergulhadas em um livro mágico"
|
||||||
|
className="relative rounded-2xl shadow-2xl transform hover:scale-[1.02]
|
||||||
|
transition-transform"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 2. Problema & Solução */}
|
||||||
|
<section className="px-4 py-24 bg-white">
|
||||||
|
<div className="mx-auto max-w-7xl">
|
||||||
|
<h2 className="text-4xl font-bold text-center text-gray-900 mb-16">
|
||||||
|
Desafios que Todo Pai Enfrenta
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-3 gap-8 mb-16">
|
||||||
|
{challenges.map((challenge, index) => (
|
||||||
|
<div key={index} className="p-6 bg-purple-50 rounded-xl">
|
||||||
|
<challenge.icon className="h-12 w-12 text-purple-600 mb-4" />
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-2">
|
||||||
|
{challenge.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-600">{challenge.description}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-4 gap-8">
|
||||||
|
{benefits.map((benefit, index) => (
|
||||||
|
<div key={index} className="text-center">
|
||||||
|
<div className="mx-auto w-16 h-16 flex items-center justify-center
|
||||||
|
bg-gradient-to-r from-purple-600 to-blue-500 rounded-full mb-4">
|
||||||
|
<benefit.icon className="h-8 w-8 text-white" />
|
||||||
|
</div>
|
||||||
|
<h4 className="font-bold text-gray-900 mb-2">{benefit.title}</h4>
|
||||||
|
<p className="text-sm text-gray-600">{benefit.description}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 3. Como a Magia Acontece */}
|
||||||
|
<section className="px-4 py-24 bg-gradient-to-br from-purple-50 to-blue-50">
|
||||||
|
<div className="mx-auto max-w-7xl">
|
||||||
|
<h2 className="text-4xl font-bold text-center text-gray-900 mb-16">
|
||||||
|
Como a Magia Acontece
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-2 gap-12 items-center">
|
||||||
|
<div className="space-y-12">
|
||||||
|
{magicSteps.map((step, index) => (
|
||||||
|
<div key={index} className="flex gap-6">
|
||||||
|
<div className="flex-shrink-0 w-12 h-12 bg-gradient-to-r from-purple-600
|
||||||
|
to-blue-500 text-white rounded-full flex items-center justify-center
|
||||||
|
text-xl font-bold">
|
||||||
|
{index + 1}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-2">
|
||||||
|
{step.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-600">{step.description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative">
|
||||||
|
<div className="aspect-video rounded-xl overflow-hidden shadow-xl">
|
||||||
|
<video
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
autoPlay
|
||||||
|
muted
|
||||||
|
loop
|
||||||
|
playsInline
|
||||||
|
poster="/images/demo-poster.webp"
|
||||||
|
>
|
||||||
|
<source src="/videos/demo.mp4" type="video/mp4" />
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
<div className="absolute -bottom-6 -right-6 bg-white p-4 rounded-lg shadow-lg">
|
||||||
|
<div className="flex items-center gap-2 text-sm text-purple-600 font-medium">
|
||||||
|
<Sparkles className="h-4 w-4" />
|
||||||
|
<span>Veja a mágica acontecer!</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 4. Comparação */}
|
||||||
|
<section className="px-4 py-24 bg-white">
|
||||||
|
<div className="mx-auto max-w-7xl">
|
||||||
|
<h2 className="text-4xl font-bold text-center text-gray-900 mb-16">
|
||||||
|
A Magia da Transformação
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-2 gap-8">
|
||||||
|
{/* Sem Histórias Mágicas */}
|
||||||
|
<div className="p-8 bg-gray-50 rounded-xl border border-gray-200">
|
||||||
|
<div className="flex items-center gap-3 mb-8">
|
||||||
|
<div className="w-12 h-12 bg-red-100 rounded-full flex items-center justify-center">
|
||||||
|
<X className="h-6 w-6 text-red-500" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900">
|
||||||
|
Sem Histórias Mágicas
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{comparisonData.map((category, index) => (
|
||||||
|
<div key={index} className="mb-8 last:mb-0">
|
||||||
|
<h4 className="font-bold text-gray-900 mb-4">{category.title}</h4>
|
||||||
|
<ul className="space-y-3">
|
||||||
|
{category.without.map((item, idx) => (
|
||||||
|
<li key={idx} className="flex items-start gap-2">
|
||||||
|
<X className="h-5 w-5 text-red-500 flex-shrink-0 mt-1" />
|
||||||
|
<span className="text-gray-600">{item}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Com Histórias Mágicas */}
|
||||||
|
<div className="p-8 bg-gradient-to-br from-purple-50 to-blue-50 rounded-xl
|
||||||
|
border-2 border-purple-200 transform hover:scale-[1.02] transition-transform">
|
||||||
|
<div className="flex items-center gap-3 mb-8">
|
||||||
|
<div className="w-12 h-12 bg-gradient-to-r from-purple-600 to-blue-500
|
||||||
|
rounded-full flex items-center justify-center">
|
||||||
|
<CheckCircle className="h-6 w-6 text-white" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900">
|
||||||
|
Com Histórias Mágicas
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{comparisonData.map((category, index) => (
|
||||||
|
<div key={index} className="mb-8 last:mb-0">
|
||||||
|
<h4 className="font-bold text-gray-900 mb-4">{category.title}</h4>
|
||||||
|
<ul className="space-y-3">
|
||||||
|
{category.with.map((item, idx) => (
|
||||||
|
<li key={idx} className="flex items-start gap-2">
|
||||||
|
<CheckCircle className="h-5 w-5 text-green-500 flex-shrink-0 mt-1" />
|
||||||
|
<span className="text-gray-600">{item}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 5. Benefícios Mágicos Detalhados */}
|
||||||
|
<section className="px-4 py-24 bg-gradient-to-br from-purple-50 to-blue-50">
|
||||||
|
<div className="mx-auto max-w-7xl">
|
||||||
|
<h2 className="text-4xl font-bold text-center text-gray-900 mb-16">
|
||||||
|
Benefícios Mágicos Detalhados
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="grid lg:grid-cols-5 gap-8 mb-16">
|
||||||
|
{detailedBenefits.map((benefit, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="bg-white p-6 rounded-xl shadow-sm hover:shadow-md
|
||||||
|
transform hover:scale-105 transition-all"
|
||||||
|
>
|
||||||
|
<div className="flex flex-col items-center text-center gap-4">
|
||||||
|
<div className="w-16 h-16 flex items-center justify-center
|
||||||
|
bg-gradient-to-r from-purple-600 to-blue-500 rounded-full">
|
||||||
|
<benefit.icon className="h-8 w-8 text-white" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900">{benefit.title}</h3>
|
||||||
|
<p className="text-gray-600">{benefit.description}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Preview do Portal */}
|
||||||
|
<div className="relative mt-20">
|
||||||
|
<div className="absolute -inset-4 bg-gradient-to-r from-purple-600 to-blue-500
|
||||||
|
rounded-2xl blur-lg opacity-20" />
|
||||||
|
<div className="relative bg-white p-8 rounded-xl shadow-xl">
|
||||||
|
<div className="grid md:grid-cols-2 gap-12 items-center">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-2xl font-bold text-gray-900 mb-6">
|
||||||
|
Portal dos Pais: Acompanhamento em Tempo Real
|
||||||
|
</h3>
|
||||||
|
<ul className="space-y-4">
|
||||||
|
<li className="flex items-start gap-3">
|
||||||
|
<CheckCircle className="h-6 w-6 text-green-500 flex-shrink-0 mt-1" />
|
||||||
|
<span className="text-gray-600">
|
||||||
|
Métricas detalhadas de progresso e desenvolvimento
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start gap-3">
|
||||||
|
<CheckCircle className="h-6 w-6 text-green-500 flex-shrink-0 mt-1" />
|
||||||
|
<span className="text-gray-600">
|
||||||
|
Relatórios semanais personalizados
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start gap-3">
|
||||||
|
<CheckCircle className="h-6 w-6 text-green-500 flex-shrink-0 mt-1" />
|
||||||
|
<span className="text-gray-600">
|
||||||
|
Recomendações pedagógicas baseadas em dados
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex items-start gap-3">
|
||||||
|
<CheckCircle className="h-6 w-6 text-green-500 flex-shrink-0 mt-1" />
|
||||||
|
<span className="text-gray-600">
|
||||||
|
Histórico completo de leituras e conquistas
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="relative">
|
||||||
|
<img
|
||||||
|
src="/images/dashboard-preview.webp"
|
||||||
|
alt="Portal dos Pais"
|
||||||
|
className="rounded-xl shadow-2xl"
|
||||||
|
/>
|
||||||
|
<div className="absolute -bottom-6 -right-6 bg-white p-4 rounded-lg shadow-lg">
|
||||||
|
<div className="flex items-center gap-2 text-sm text-purple-600 font-medium">
|
||||||
|
<Lock className="h-4 w-4" />
|
||||||
|
<span>Ambiente 100% seguro e monitorado</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 6. Testimoniais */}
|
||||||
|
<section className="px-4 py-24 bg-white">
|
||||||
|
<div className="mx-auto max-w-7xl">
|
||||||
|
<h2 className="text-4xl font-bold text-center text-gray-900 mb-16">
|
||||||
|
Histórias de Transformação
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-3 gap-8">
|
||||||
|
{testimonials.map((testimonial, index) => (
|
||||||
|
<div key={index} className="bg-gradient-to-br from-purple-50 to-blue-50
|
||||||
|
p-6 rounded-xl shadow-sm">
|
||||||
|
<div className="relative mb-8">
|
||||||
|
<img
|
||||||
|
src={testimonial.image}
|
||||||
|
alt={`Família de ${testimonial.name}`}
|
||||||
|
className="w-full h-48 object-cover rounded-lg"
|
||||||
|
/>
|
||||||
|
<div className="absolute -bottom-4 -right-4 bg-white p-2 rounded-full shadow-lg">
|
||||||
|
<Heart className="h-6 w-6 text-red-500" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-gray-600 mb-4 italic">"{testimonial.text}"</p>
|
||||||
|
<div>
|
||||||
|
<p className="font-bold text-gray-900">{testimonial.name}</p>
|
||||||
|
<p className="text-sm text-gray-500">{testimonial.role}</p>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 p-3 bg-white rounded-lg">
|
||||||
|
<p className="text-sm text-purple-600 font-medium">
|
||||||
|
✨ Momento mágico: {testimonial.magicMoment}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 7. Planos */}
|
||||||
|
<section className="px-4 py-24 bg-gradient-to-b from-purple-50 via-white to-purple-50">
|
||||||
|
<div className="mx-auto max-w-7xl">
|
||||||
|
<h2 className="text-4xl font-bold text-center text-gray-900 mb-4">
|
||||||
|
Planos Mágicos
|
||||||
|
</h2>
|
||||||
|
<p className="text-center text-gray-600 mb-16 max-w-2xl mx-auto">
|
||||||
|
Escolha o plano perfeito para a jornada mágica do seu filho
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-3 gap-8">
|
||||||
|
{plans.map((plan, index) => (
|
||||||
|
<div key={index} className={`
|
||||||
|
p-8 rounded-xl shadow-lg border-2
|
||||||
|
${index === 1 ? 'bg-gradient-to-br from-purple-50 to-blue-50 border-purple-200 transform scale-105'
|
||||||
|
: 'bg-white border-gray-100'}
|
||||||
|
`}>
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<h3 className="text-2xl font-bold text-gray-900 mb-2">{plan.title}</h3>
|
||||||
|
<p className="text-gray-600">{plan.description}</p>
|
||||||
|
<div className="mt-4">
|
||||||
|
<span className="text-4xl font-bold text-gray-900">R${plan.price}</span>
|
||||||
|
<span className="text-gray-500">/{plan.period}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul className="space-y-4 mb-8">
|
||||||
|
{plan.features.map((feature, idx) => (
|
||||||
|
<li key={idx} className="flex items-start gap-2">
|
||||||
|
<CheckCircle className="h-5 w-5 text-green-500 flex-shrink-0 mt-1" />
|
||||||
|
<span className="text-gray-600">{feature}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => navigate('/register/parent')}
|
||||||
|
className={`
|
||||||
|
w-full py-4 rounded-xl font-medium transition-all
|
||||||
|
${index === 1
|
||||||
|
? 'bg-gradient-to-r from-purple-600 to-blue-500 text-white hover:from-purple-700 hover:to-blue-600'
|
||||||
|
: 'border-2 border-purple-600 text-purple-600 hover:bg-purple-50'}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
Começar Agora
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{index === 1 && (
|
||||||
|
<div className="mt-4 text-center">
|
||||||
|
<span className="inline-block px-4 py-1 bg-purple-100 text-purple-600
|
||||||
|
rounded-full text-sm font-medium">
|
||||||
|
Mais Popular
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-12 text-center">
|
||||||
|
<p className="text-gray-600 mb-4">
|
||||||
|
Garantia mágica de 30 dias ou seu dinheiro de volta
|
||||||
|
</p>
|
||||||
|
<div className="flex justify-center gap-4">
|
||||||
|
{paymentMethods.map((method, index) => (
|
||||||
|
<img
|
||||||
|
key={index}
|
||||||
|
src={method.icon}
|
||||||
|
alt={method.name}
|
||||||
|
className="h-8"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 8. FAQ */}
|
||||||
|
<section className="px-4 py-24 bg-white">
|
||||||
|
<div className="mx-auto max-w-3xl">
|
||||||
|
<h2 className="text-4xl font-bold text-center text-gray-900 mb-16">
|
||||||
|
Perguntas Mágicas
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="space-y-8">
|
||||||
|
{faqItems.map((item, index) => (
|
||||||
|
<div key={index} className="bg-gray-50 rounded-xl p-6">
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-2">{item.question}</h3>
|
||||||
|
<p className="text-gray-600">{item.answer}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 9. CTA Final */}
|
||||||
|
<section className="px-4 py-24 bg-gradient-to-br from-purple-600 to-blue-500 text-white">
|
||||||
|
<div className="mx-auto max-w-3xl text-center">
|
||||||
|
<h2 className="text-4xl font-bold mb-8">
|
||||||
|
Comece a Jornada Mágica Hoje
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p className="text-xl opacity-90 mb-12">
|
||||||
|
Transforme a educação do seu filho em uma aventura inesquecível
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="bg-white/10 p-6 rounded-xl mb-8">
|
||||||
|
<p className="font-medium mb-2">
|
||||||
|
Oferta por tempo limitado!
|
||||||
|
</p>
|
||||||
|
<p className="text-sm opacity-90">
|
||||||
|
7 dias grátis + Bônus especial de boas-vindas
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => navigate('/register/parent')}
|
||||||
|
className="px-12 py-6 bg-white text-purple-600 rounded-xl text-xl font-bold
|
||||||
|
hover:bg-gray-100 transform hover:scale-105 transition-all shadow-lg"
|
||||||
|
>
|
||||||
|
Criar Conta Gratuita
|
||||||
|
<ArrowRight className="inline-block ml-2 h-6 w-6" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<p className="mt-6 text-sm opacity-75">
|
||||||
|
Garantia de 30 dias ou seu dinheiro de volta
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 10. Rodapé */}
|
||||||
|
<footer className="bg-gray-900 text-gray-400 py-16">
|
||||||
|
<div className="mx-auto max-w-7xl px-4">
|
||||||
|
<div className="grid md:grid-cols-4 gap-12">
|
||||||
|
<div>
|
||||||
|
<h4 className="text-white font-bold mb-4">Histórias Mágicas</h4>
|
||||||
|
<p className="text-sm">
|
||||||
|
Transformando a educação através da magia da leitura personalizada
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{footerLinks.map((column, index) => (
|
||||||
|
<div key={index}>
|
||||||
|
<h4 className="text-white font-bold mb-4">{column.title}</h4>
|
||||||
|
<ul className="space-y-2">
|
||||||
|
{column.links.map((link, idx) => (
|
||||||
|
<li key={idx}>
|
||||||
|
<a href={link.href} className="text-sm hover:text-white transition-colors">
|
||||||
|
{link.text}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-12 pt-8 border-t border-gray-800 text-sm">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<p>© 2024 Histórias Mágicas. Todos os direitos reservados.</p>
|
||||||
|
<div className="flex gap-4">
|
||||||
|
{socialLinks.map((social, index) => (
|
||||||
|
<a
|
||||||
|
key={index}
|
||||||
|
href={social.href}
|
||||||
|
className="text-gray-400 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
<social.icon className="h-5 w-5" />
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const challenges = [
|
||||||
|
{
|
||||||
|
icon: Brain,
|
||||||
|
title: "Manter as crianças interessadas em aprender",
|
||||||
|
description: "É difícil competir com jogos e vídeos para capturar a atenção das crianças."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: BookOpen,
|
||||||
|
title: "Encontrar conteúdo educativo de qualidade",
|
||||||
|
description: "Muito conteúdo disponível, mas pouco realmente educativo e envolvente."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Target,
|
||||||
|
title: "Acompanhar o desenvolvimento da criança",
|
||||||
|
description: "Falta de ferramentas para monitorar o progresso de forma clara e objetiva."
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const benefits = [
|
||||||
|
{
|
||||||
|
icon: Wand2,
|
||||||
|
title: "Personalização por IA",
|
||||||
|
description: "Histórias únicas criadas especialmente para cada criança"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Star,
|
||||||
|
title: "Monitoramento Educacional",
|
||||||
|
description: "Acompanhe o progresso com métricas claras e objetivas"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Shield,
|
||||||
|
title: "Segurança de Conteúdo",
|
||||||
|
description: "Ambiente seguro e controlado para o aprendizado"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Sparkles,
|
||||||
|
title: "Engajamento Garantido",
|
||||||
|
description: "Histórias que prendem a atenção e estimulam a imaginação"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const magicSteps = [
|
||||||
|
{
|
||||||
|
title: "Escolha o tema da aventura",
|
||||||
|
description: "Selecione entre diversos temas educativos alinhados com a BNCC e adequados à idade."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Personalize os personagens",
|
||||||
|
description: "Crie personagens que seu filho vai adorar, com características únicas e cativantes."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "A IA cria a história mágica",
|
||||||
|
description: "Nossa IA educacional gera uma história personalizada em segundos."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "A aventura educativa começa",
|
||||||
|
description: "Seu filho mergulha em uma jornada mágica de aprendizado e diversão."
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const comparisonData = [
|
||||||
|
{
|
||||||
|
title: "Tempo & Diversão",
|
||||||
|
without: [
|
||||||
|
"Horas procurando conteúdo educativo adequado",
|
||||||
|
"Crianças entediadas com leituras tradicionais",
|
||||||
|
"Histórias que não capturam a imaginação",
|
||||||
|
"Dificuldade em acompanhar o progresso"
|
||||||
|
],
|
||||||
|
with: [
|
||||||
|
"Histórias mágicas personalizadas em minutos",
|
||||||
|
"Crianças fascinadas por aventuras únicas",
|
||||||
|
"Mundos mágicos que educam e encantam",
|
||||||
|
"Portal mágico de acompanhamento do progresso"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Qualidade do Aprendizado",
|
||||||
|
without: [
|
||||||
|
"Conteúdo genérico e previsível",
|
||||||
|
"Falta de conexão emocional com a leitura",
|
||||||
|
"Dificuldade em manter o interesse",
|
||||||
|
"Aprendizado fragmentado"
|
||||||
|
],
|
||||||
|
with: [
|
||||||
|
"Histórias que evoluem com cada criança",
|
||||||
|
"Conexão emocional com personagens únicos",
|
||||||
|
"Aventuras que mesclam diversão e educação",
|
||||||
|
"Jornada de aprendizado mágica e integrada"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Resultados",
|
||||||
|
without: [
|
||||||
|
"Progresso lento e desmotivador",
|
||||||
|
"Resistência à leitura e aprendizado",
|
||||||
|
"Rotina de estudos cansativa",
|
||||||
|
"Pais preocupados com desenvolvimento"
|
||||||
|
],
|
||||||
|
with: [
|
||||||
|
"Evolução visível e empolgante",
|
||||||
|
"Amor natural pela leitura e conhecimento",
|
||||||
|
"Aventuras diárias de aprendizado",
|
||||||
|
"Pais confiantes no desenvolvimento mágico"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const detailedBenefits = [
|
||||||
|
{
|
||||||
|
icon: Wand2,
|
||||||
|
title: "Aprendizado Através de Aventuras",
|
||||||
|
description: "Histórias que se adaptam ao nível e interesses do seu filho, tornando o aprendizado natural e divertido."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: ScrollText,
|
||||||
|
title: "Portal dos Pais",
|
||||||
|
description: "Acompanhe em tempo real o progresso de leitura, compreensão e desenvolvimento do seu filho."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Shield,
|
||||||
|
title: "Proteção Mágica",
|
||||||
|
description: "Conteúdo 100% seguro e adequado, com moderação constante e controles parentais."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: BookOpen,
|
||||||
|
title: "Alinhamento com BNCC",
|
||||||
|
description: "Histórias criadas seguindo as diretrizes da Base Nacional Comum Curricular."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Brain,
|
||||||
|
title: "IA Educacional",
|
||||||
|
description: "Nossa inteligência artificial analisa o perfil do seu filho para criar histórias personalizadas e adaptativas."
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const testimonials = [
|
||||||
|
{
|
||||||
|
image: "/images/testimonial-1.webp",
|
||||||
|
text: "Minha filha passou de resistente à leitura para não querer parar de ler! As histórias personalizadas fizeram toda a diferença.",
|
||||||
|
name: "Ana Silva",
|
||||||
|
role: "Mãe da Maria, 8 anos",
|
||||||
|
magicMoment: "Primeira história completa lida sozinha"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/testimonial-2.webp",
|
||||||
|
text: "Como pai, é incrível ver o progresso do Pedro. O portal dos pais me ajuda a entender exatamente onde ele precisa de apoio.",
|
||||||
|
name: "Carlos Santos",
|
||||||
|
role: "Pai do Pedro, 10 anos",
|
||||||
|
magicMoment: "Superou a dificuldade com palavras complexas"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/testimonial-3.webp",
|
||||||
|
text: "As histórias são tão envolventes que meu filho pede para ler mais uma toda noite. O aprendizado acontece naturalmente!",
|
||||||
|
name: "Juliana Costa",
|
||||||
|
role: "Mãe do Lucas, 7 anos",
|
||||||
|
magicMoment: "Começou a criar suas próprias histórias"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const plans = [
|
||||||
|
{
|
||||||
|
title: "Aprendiz de Mago",
|
||||||
|
description: "Perfeito para começar",
|
||||||
|
price: "49,90",
|
||||||
|
period: "mês",
|
||||||
|
features: [
|
||||||
|
"5 histórias personalizadas por mês",
|
||||||
|
"Análise básica de progresso",
|
||||||
|
"Suporte por email",
|
||||||
|
"Acesso ao portal dos pais",
|
||||||
|
"Relatórios mensais"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Mago Experiente",
|
||||||
|
description: "Mais popular",
|
||||||
|
price: "39,90",
|
||||||
|
period: "mês",
|
||||||
|
features: [
|
||||||
|
"15 histórias personalizadas por mês",
|
||||||
|
"Análise avançada de progresso",
|
||||||
|
"Suporte prioritário",
|
||||||
|
"Portal dos pais premium",
|
||||||
|
"Relatórios semanais",
|
||||||
|
"Histórias temáticas especiais",
|
||||||
|
"Bônus: Kit de Atividades Mágicas"
|
||||||
|
],
|
||||||
|
highlight: true,
|
||||||
|
commitment: "Semestral"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Grão-Mestre",
|
||||||
|
description: "Melhor custo-benefício",
|
||||||
|
price: "29,90",
|
||||||
|
period: "mês",
|
||||||
|
features: [
|
||||||
|
"Histórias ilimitadas",
|
||||||
|
"Análise completa de progresso",
|
||||||
|
"Suporte VIP 24/7",
|
||||||
|
"Portal dos pais premium",
|
||||||
|
"Relatórios diários",
|
||||||
|
"Histórias temáticas especiais",
|
||||||
|
"Bônus: Kit de Atividades Mágicas",
|
||||||
|
"Bônus: Sessões com pedagogo"
|
||||||
|
],
|
||||||
|
commitment: "Anual"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const faqItems = [
|
||||||
|
{
|
||||||
|
question: "Como a magia da IA funciona?",
|
||||||
|
answer: "Nossa IA educacional analisa o perfil do seu filho, incluindo idade, interesses e nível de leitura, para criar histórias únicas que combinam diversão com aprendizado personalizado."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Como garantimos histórias seguras?",
|
||||||
|
answer: "Todas as histórias passam por múltiplas camadas de verificação, incluindo filtros de IA e revisão humana, garantindo conteúdo 100% adequado e seguro."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Como acompanhar a evolução mágica?",
|
||||||
|
answer: "Através do Portal dos Pais, você tem acesso a relatórios detalhados sobre fluência, compreensão, vocabulário e muito mais, com visualizações claras do progresso."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Qual é a política de cancelamento?",
|
||||||
|
answer: "Você pode cancelar sua assinatura a qualquer momento, sem multas. Oferecemos garantia de 30 dias - se não estiver satisfeito, devolvemos seu dinheiro."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Quantas histórias mágicas por mês?",
|
||||||
|
answer: "O número de histórias varia conforme o plano escolhido, desde 5 histórias mensais no plano básico até histórias ilimitadas no plano Grão-Mestre."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Como funciona o suporte aos pais?",
|
||||||
|
answer: "Oferecemos suporte via chat, email e telefone, com especialistas em educação prontos para ajudar. Planos premium incluem acesso a pedagogos."
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const footerLinks = [
|
||||||
|
{
|
||||||
|
title: "Produto",
|
||||||
|
links: [
|
||||||
|
{ text: "Recursos", href: "#recursos" },
|
||||||
|
{ text: "Preços", href: "#precos" },
|
||||||
|
{ text: "Como Funciona", href: "#como-funciona" },
|
||||||
|
{ text: "Histórias de Sucesso", href: "#testimoniais" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Suporte",
|
||||||
|
links: [
|
||||||
|
{ text: "Central de Ajuda", href: "#ajuda" },
|
||||||
|
{ text: "Contato", href: "#contato" },
|
||||||
|
{ text: "FAQ", href: "#faq" },
|
||||||
|
{ text: "Tutoriais", href: "#tutoriais" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Legal",
|
||||||
|
links: [
|
||||||
|
{ text: "Termos de Uso", href: "#termos" },
|
||||||
|
{ text: "Privacidade", href: "#privacidade" },
|
||||||
|
{ text: "Segurança", href: "#seguranca" },
|
||||||
|
{ text: "Cookies", href: "#cookies" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const socialLinks = [
|
||||||
|
{ icon: Facebook, href: "https://facebook.com/historias-magicas" },
|
||||||
|
{ icon: Instagram, href: "https://instagram.com/historias-magicas" },
|
||||||
|
{ icon: Twitter, href: "https://twitter.com/historias-magicas" },
|
||||||
|
{ icon: Youtube, href: "https://youtube.com/historias-magicas" }
|
||||||
|
];
|
||||||
|
|
||||||
|
const paymentMethods = [
|
||||||
|
{ name: "Cartão de Crédito", icon: "/icons/credit-card.svg" },
|
||||||
|
{ name: "Boleto", icon: "/icons/boleto.svg" },
|
||||||
|
{ name: "PIX", icon: "/icons/pix.svg" },
|
||||||
|
{ name: "PayPal", icon: "/icons/paypal.svg" }
|
||||||
|
];
|
||||||
@ -27,6 +27,7 @@ import { AchievementsPage } from './pages/student-dashboard/AchievementsPage';
|
|||||||
import { StudentClassPage } from './pages/student-dashboard/StudentClassPage';
|
import { StudentClassPage } from './pages/student-dashboard/StudentClassPage';
|
||||||
import { DemoPage } from './pages/demo/DemoPage';
|
import { DemoPage } from './pages/demo/DemoPage';
|
||||||
import { ParentsLandingPage } from './pages/landing/ParentsLandingPage';
|
import { ParentsLandingPage } from './pages/landing/ParentsLandingPage';
|
||||||
|
import { EducationalForParents } from './pages/landing/EducationalForParents';
|
||||||
|
|
||||||
export const router = createBrowserRouter([
|
export const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@ -187,5 +188,9 @@ export const router = createBrowserRouter([
|
|||||||
<UserManagementPage />
|
<UserManagementPage />
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
),
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/para-educadores',
|
||||||
|
element: <EducationalForParents />,
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
Loading…
Reference in New Issue
Block a user