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:
Lucas Santana 2024-12-25 12:57:08 -03:00
parent 563a62a517
commit 521a99a5c2
16 changed files with 1131 additions and 133 deletions

9
.dockerignore Normal file
View File

@ -0,0 +1,9 @@
node_modules
.git
.env*
.dockerignore
Dockerfile
README.md
.next
build
dist

View 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}

View 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
View 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'

View File

@ -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/),
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
- Landing page para pais
- Design moderno e atraente
- Seções de benefícios e funcionalidades
- Demonstração de métricas e análises
- Gráficos interativos com recharts
- Gráfico de evolução do aluno
- Comparativo antes/depois
- Depoimentos de usuários
- CTAs estratégicos
- Demonstração interativa
- Copywriting focado em resultados
- Imagens otimizadas e responsivas
- Rota dedicada em /para-pais
- Edge function `generate-story` para geração de histórias com IA
- 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
- Configuração Docker para ambiente de produção
- Pipeline de CI/CD no Gitea Actions
- Integração com Redis para cache
- Healthcheck da aplicação
- Adiciona seções:
- Hero com CTA e social proof
- Problemas e Soluções
- Como a Magia Acontece
- Comparação antes/depois
- Benefícios Mágicos em layout horizontal
- Testimoniais
- Planos e preços
- FAQ
- CTA final
- Footer
### Técnico
- Melhorias no tratamento de imagens
- Tratamento de URLs indefinidas
- Imagem padrão para fallback
- Otimização consistente em edge functions
- Melhor tipagem para URLs
- Prevenção de erros em runtime
- Dockerfile com multi-stage build para otimização
- Configuração de registry no Gitea
- Cache de histórias com Redis
- Scripts de deploy e monitoramento
- Implementação de logs estruturados com prefixos por contexto
- Validações de dados em múltiplas camadas
- Tratamento de respostas da IA com fallbacks
- Otimização de queries no banco de dados
- Feedback em tempo real do processo de geração
- Queries otimizadas para nova estrutura de dados
- Melhor tratamento de estados de loading e erro
- Implementação de componente ImageWithLoading
- Sistema de cache de imagens
- Otimização de URLs de imagem
- 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
### Modificado
- Atualização do next.config.js para suporte standalone
- Adaptação da API para usar Redis cache
- Configuração de redes Docker
- Otimiza UX/UI com:
- Animações suaves
- Gradientes modernos
- Layout responsivo
- Elementos interativos
- Social proof estratégico
### Segurança
- Validação de dados de entrada na edge function
- Verificação de permissões do usuário
- Sanitização de prompts para a IA
- 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
- Implementação de healthchecks
- Configuração de redes isoladas
- Proteção de variáveis de ambiente

40
Dockerfile Normal file
View 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
View 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
View 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

View File

@ -9,7 +9,10 @@
"preview": "vite preview",
"typecheck": "tsc --noEmit",
"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": {
"@radix-ui/react-accordion": "^1.2.2",

34
portainer-stack.yml Normal file
View 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
View File

@ -0,0 +1,5 @@
import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379');
export default redis;

View File

@ -1,3 +1,4 @@
import { StoryPrompt } from '@/types/story-generator'
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
@ -13,4 +14,26 @@ export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
persistSession: 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
View 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 });
}
}

View 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);
}

View 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" }
];

View File

@ -27,6 +27,7 @@ import { AchievementsPage } from './pages/student-dashboard/AchievementsPage';
import { StudentClassPage } from './pages/student-dashboard/StudentClassPage';
import { DemoPage } from './pages/demo/DemoPage';
import { ParentsLandingPage } from './pages/landing/ParentsLandingPage';
import { EducationalForParents } from './pages/landing/EducationalForParents';
export const router = createBrowserRouter([
{
@ -187,5 +188,9 @@ export const router = createBrowserRouter([
<UserManagementPage />
</ProtectedRoute>
),
},
{
path: '/para-educadores',
element: <EducationalForParents />,
}
]);