Site oficial do Araújo Personal Trainer (consultoria online e presencial de treino e nutrição — bodybuilding e estética corporal).
- Frontend: React 18 + Vite 6 + TailwindCSS 3 (design tokens em
tailwind.config.js) - Formulário multi-etapas: 6 passos com barra de progresso, validação campo a campo e
persistência em
sessionStorage - Backend: funções serverless (
/api/*) que gravam as solicitações num Google Sheets e disparam e-mail de notificação (Resend) - Fotos: compressão no client (
browser-image-compression) + upload para Supabase Storage; só a URL vai pra planilha/e-mail
Decisão de stack: React + Vite, e não Next.js, porque o site é um funil essencialmente cliente (brochura + formulário pesado de interação no browser). As meta/OG tags são resolvidas no
index.html+react-helmet-async, e o backend vira funções serverless junto do mesmo repositório — SSR não agregaria valor aqui e adicionaria complexidade desnecessária.
npm install
npm run devIsso sobe duas coisas ao mesmo tempo:
| Serviço | Porta | O que é |
|---|---|---|
| Vite (front) | http://localhost:5173 | o site |
| API Node (dev) | http://localhost:8787 | /api/* (proxy automático do Vite) |
O backend local (server/index.mjs) e as funções de produção (api/) compartilham
a MESMA lógica em server/lib/. Ou seja: tudo que funciona local funciona na Vercel.
Sem o
.envpreenchido, o site abre num modo demonstração: o formulário de fotos vira data URL e a página/solicitarmostra um banner avisando que a planilha/e-mail ainda não foram configurados.
Copie .env.example para .env e preencha:
| Variável | Obrigatória? | Descrição |
|---|---|---|
GOOGLE_SERVICE_ACCOUNT_JSON |
✅ (planilha) | JSON completo do service account (com aspas simples no .env) |
GOOGLE_SHEET_ID |
✅ (planilha) | ID da planilha (trecho entre /d/ e /edit da URL) |
RESEND_API_KEY |
✅ (e-mail) | API key do Resend (https://resend.com) |
RESEND_FROM |
✅ (e-mail) | Remetente verificado, ex: Araújo <site@seudominio.com> |
NOTIFY_EMAIL |
✅ (e-mail) | E-mail que recebe a notificação (o do Araújo) |
VITE_SUPABASE_URL |
✅ (fotos) | URL do projeto Supabase (é pública) |
VITE_SUPABASE_ANON_KEY |
✅ (fotos) | Anon key do Supabase (é pública, mas revogável) |
VITE_SUPABASE_BUCKET |
✅ (fotos) | Nome do bucket, ex: fotos-solicitacoes |
API_PORT |
não | Porta do backend local (padrão 8787) |
SITE_URL |
não | URL canônica das meta tags |
.env com credenciais reais (ele está no .gitignore).
As variáveis VITE_* ficam públicas no bundle — use apenas chaves anon do
Supabase; a política do bucket é o que protege (seção 5).
-
Crie o service account
- https://console.cloud.google.com/iam-admin/serviceaccounts
- Crie uma conta (ex:
araujo-site) e gere uma chave JSON — esse é o conteúdo deGOOGLE_SERVICE_ACCOUNT_JSON.
-
Ative a API — em APIs & Serviços → Biblioteca, ative a Google Sheets API no mesmo projeto.
-
Crie/use a planilha
- Crie uma planilha no Google Sheets e compartilhe com o e-mail do service
account (o campo
client_emaildo JSON) com papel Editor. - Copie o
GOOGLE_SHEET_IDda URL:https://docs.google.com/spreadsheets/d/AQUI_FICA_O_ID/edit
- Crie uma planilha no Google Sheets e compartilhe com o e-mail do service
account (o campo
-
Aba "Solicitações" — renomeie a primeira aba pra
Solicitações. O cabeçalho esperado (ordem fixa) está emserver/lib/sheets.js→SHEET_HEADER. Se preferir criar na mão:Protocolo, Enviado em, Modalidade, Tipo, Plano, Status, Nome, Idade, E-mail, WhatsApp, Objetivo, Experiência, Lesões / limitações, Restrições médicas, Altura (cm), Peso (kg), Pescoço (cm), Ombro (cm), Peitoral (cm), Cintura (cm), Quadril (cm), Braço (cm), Antebraço (cm), Coxa (cm), Panturrilha (cm), Foto frente, Foto lado, Foto costas, Usar como depoimento, Consentimento LGPD -
Acompanhamento de status — a coluna Status nasce com
pendente. Quando entregar a planilha personalizada, o Araújo troca praplanilha enviadana célula (ou numa automação/Apps Script, se preferir). O endpointGET /api/status?id=AR-...devolve o status atual — útil pra um painel simples. Uma aba "Status" extra pode ser adicionada na planilha (coluna Protocolo + Status) alimentada manualmente ou por Apps Script; o site não depende dela.
- Crie conta em https://resend.com e gere uma API key.
- Verifique um domínio (ou use o domínio
onboarding@resend.devapenas pra testar) e use o e-mail verificado emRESEND_FROM. NOTIFY_EMAIL= e-mail do Araújo.
A cada envio, o Araújo recebe um e-mail estruturado com os dados da triagem, as
URLs das fotos e o protocolo. Alternativa SMTP: troque a função
sendNotification em server/lib/email.js por Nodemailer — a assinatura é a mesma.
- Crie um projeto em https://supabase.com (plano free resolve).
- Em Storage, crie o bucket
fotos-solicitacoes. - Política de segurança (SQL editor):
-- Upload com a chave anônima (o site) dentro de solicitacoes/*
create policy "upload anon solicitacoes"
on storage.objects for insert to anon
with check (bucket_id = 'fotos-solicitacoes'
and (storage.foldername(name))[1] = 'solicitacoes');
-- Leitura pública das fotos (URLs usadas na planilha/e-mail)
create policy "select public fotos"
on storage.objects for select to anon
using (bucket_id = 'fotos-solicitacoes');- Copie
SUPABASE_URLeANON_KEY(Project Settings → API) e o nome do bucket para as variáveisVITE_*.
As fotos ficam em solicitacoes/<protocolo>/frente|lado|costas.jpg — são essas
URLs que vão pra planilha e pro e-mail (nunca o binário).
npm i -g vercel
vercel- A pasta
api/é detectada automaticamente como serverless functions. vercel.jsonjá contém o rewrite de SPA (/((?!api/).*)→/index.html).- Adicione as variáveis de ambiente no painel (Project → Settings → Environment
Variables) — as mesmas do
.env.example. - Netlify: build
npm run build, public dirdist, adapte as funções paraexports.handler(o import deserver/libfunciona igual).
public/og-image.png (1200×630 com o logo de chifres) é gerada por um script
sem dependências:
npm run og:imageApós publicar, confira o domínio em index.html (meta og:url e twitter:*).
| O quê | Onde |
|---|---|
| Número do WhatsApp | src/data/site.js → whatsappNumber |
| Instagram / e-mail / cidade | src/data/site.js |
| Preços | src/data/plans.js → preco e precoNota |
| Depoimentos / antes-depois | src/data/resultados.js |
| Perguntas frequentes | src/data/faqs.js |
| Fotos reais de resultados | trocar o bloco placeholder em src/pages/Resultados.jsx |
| Endereço físico / mapa | src/pages/Contato.jsx (seção do mapa) |
| Design tokens (cores, fontes) | tailwind.config.js (única fonte de verdade) |
O formulário só envia dados com o consentimento explícito (checkbox LGPD). As fotos são usadas exclusivamente para montar/acompanhar o plano; divulgação como depoimento exige nova autorização específica em separado. Reveja o texto com um advogado antes do lançamento público.