Site de voluntariado - sistema javascript
Objetivo: conectar voluntários a pessoas/ONGs que precisam de ajuda em serviços (ex.: design, jurídico, aulas, tecnologia) e consultorias (mentorias, aconselhamento).
Funcionalidades-chave:
- Cadastro/login (usuário e voluntário).
- Perfil de voluntário (bio, habilidades, áreas, disponibilidade, atendimento remoto/presencial).
- Busca/filtragem por habilidade, causa, cidade e modalidade.
- Página de oferta (serviço/consultoria) com botão “Agendar”.
- Agenda/slots de disponibilidade e sistema de reservas.
- Mensageria básica (DM) entre solicitante e voluntário.
- Avaliações pós-atendimento.
- Painel do admin (aprovar perfis/ofertas, moderar).
- Base para LGPD (consentimento, exclusão de dados, termos).
2) Arquitetura sugerida (simples e barata)
- Frontend/SSR: Next.js (App Router)
- UI: Tailwind CSS + shadcn/ui
- Banco: PostgreSQL (Supabase, Neon, Railway ou RDS)
- ORM: Prisma
- Auth: NextAuth (Email + Google/GitHub opcionais)
- Armazenamento de arquivos (opcional): Supabase Storage
- Mensagens em tempo real (opcional): Pusher/Ably (ou polling simples para começar)
- Emails transacionais: Resend / SendGrid
- Deploy: Vercel (web) + provedor do Postgres
Dá pra trocar por Node/Express puro + React/Vite, mas Next.js + Prisma acelera.
3) Modelo de dados (mínimo viável)
Entidades principais:
User
(todos): id, nome, email, role (USER
/VOLUNTEER
/ADMIN
), cidade, país, createdAt.VolunteerProfile
: userId, bio, causas (lista), modalidades (ONLINE
/PRESENCIAL
), rating.Skill
: id, nome (ex.: “Design”, “Aulas de Matemática”).VolunteerSkill
: (N:N) volunteerProfileId, skillId.Offering
(ofertas): id, volunteerId, título, descrição, categoria, status (ACTIVE
/PAUSED
).AvailabilitySlot
: id, volunteerId, start, end, timezone.Booking
: id, requesterId, offeringId, slotId, status (PENDING
/CONFIRMED
/DONE
/CANCELLED
), notas.Message
: id, bookingId, senderId, conteúdo, createdAt.Review
: id, bookingId, rating (1–5), comentário, authorId, createdAt.
Exemplo de schema Prisma (trecho)
prisma
Copiar código
model User { id String @id @default(cuid()) name String email String @unique role Role @default(USER) city String? country String? createdAt DateTime @default(now()) profile VolunteerProfile? bookings Booking[] @relation("UserBookings") messages Message[] @relation("UserMessages") } enum Role { USER VOLUNTEER ADMIN } model VolunteerProfile { id String @id @default(cuid()) user User @relation(fields: [userId], references: [id]) userId String @unique bio String? modalities String[] // ["ONLINE","PRESENCIAL"] causes String[] // ["Educação","Meio Ambiente"] rating Float? skills VolunteerSkill[] offerings Offering[] slots AvailabilitySlot[] } model Skill { id String @id @default(cuid()) name String @unique links VolunteerSkill[] } model VolunteerSkill { volunteerId String skillId String volunteer VolunteerProfile @relation(fields: [volunteerId], references: [id]) skill Skill @relation(fields: [skillId], references: [id]) @@id([volunteerId, skillId]) } model Offering { id String @id @default(cuid()) volunteer VolunteerProfile @relation(fields: [volunteerId], references: [id]) volunteerId String title String description String category String status String @default("ACTIVE") bookings Booking[] } model AvailabilitySlot { id String @id @default(cuid()) volunteer VolunteerProfile @relation(fields: [volunteerId], references: [id]) volunteerId String start DateTime end DateTime timezone String booking Booking? } model Booking { id String @id @default(cuid()) requester User @relation("UserBookings", fields: [requesterId], references: [id]) requesterId String offering Offering @relation(fields: [offeringId], references: [id]) offeringId String slot AvailabilitySlot @relation(fields: [slotId], references: [id]) slotId String @unique status String @default("PENDING") notes String? messages Message[] review Review? createdAt DateTime @default(now()) } model Message { id String @id @default(cuid()) booking Booking @relation(fields: [bookingId], references: [id]) bookingId String sender User @relation("UserMessages", fields: [senderId], references: [id]) senderId String content String createdAt DateTime @default(now()) } model Review { id String @id @default(cuid()) booking Booking @relation(fields: [bookingId], references: [id]) bookingId String @unique rating Int comment String? author User @relation(fields: [authorId], references: [id]) authorId String createdAt DateTime @default(now()) }
4) Fluxos principais
- Cadastro/Login: NextAuth (Email link mágico) → cria
User
. - Tornar-se voluntário: usuário preenche bio, causas, modalidades e skills → cria
VolunteerProfile
eVolunteerSkill
. - Criar ofertas: voluntário publica uma ou mais
Offering
. - Disponibilidade: voluntário cadastra
AvailabilitySlot
. - Busca e descoberta: visitante filtra por skill/causa/cidade/modalidade → lista de
Offering
. - Agendamento: solicitante escolhe oferta + slot → cria
Booking(PENDING)
→ voluntário confirma (CONFIRMED
). - Mensagens: canal privado por
Booking
. - Concluir & avaliar: status
DONE
eReview
(impacta média no perfil). - Admin: aprova perfis/ofertas suspeitas, bloqueia conteúdo, exporta métricas.
5) Passo a passo técnico
5.1 Projeto base
bash
Copiar código
npx create-next-app voluntariado --ts --eslint --tailwind --app cd voluntariado npm i @prisma/client prisma next-auth @auth/prisma-adapter zod npm i -D prisma npx prisma init
- Ajuste
DATABASE_URL
no.env
para seu Postgres. - Cole o
schema.prisma
(acima), depois:
bash
Copiar código
npx prisma migrate dev -n init npx prisma db seed # opcional, crie um script de seed
5.2 Autenticação (NextAuth)
- Rota
app/api/auth/[...nextauth]/route.ts
com EmailProvider (ou Google). - Use
PrismaAdapter
para persistência. - Proteja rotas server-side com
getServerSession
.
5.3 API (App Router – route handlers)
app/api/offerings/route.ts
(GET lista / POST cria)app/api/slots/route.ts
(GET/POST)app/api/bookings/route.ts
(POST cria; PATCH status)app/api/messages/route.ts
(GET/POST por bookingId)- Validação com Zod.
Exemplo (criar Booking)
ts
Copiar código
// app/api/bookings/route.ts import { NextResponse } from 'next/server'; import { z } from 'zod'; import { getServerSession } from 'next-auth'; import { prisma } from '@/lib/prisma'; const BookingSchema = z.object({ offeringId: z.string().cuid(), slotId: z.string().cuid(), notes: z.string().optional(), }); export async function POST(req: Request) { const session = await getServerSession(); if (!session?.user?.email) return NextResponse.json({error:'unauthorized'}, {status:401}); const body = await req.json(); const parsed = BookingSchema.safeParse(body); if (!parsed.success) return NextResponse.json(parsed.error.flatten(), {status:400}); const user = await prisma.user.findUnique({where:{email: session.user.email}}); if(!user) return NextResponse.json({error:'user not found'}, {status:404}); // Evita overbooking: slot só pode ter 1 booking const existing = await prisma.booking.findUnique({where:{slotId: parsed.data.slotId}}); if (existing) return NextResponse.json({error:'slot already booked'}, {status:409}); const booking = await prisma.booking.create({ data: { requesterId: user.id, offeringId: parsed.data.offeringId, slotId: parsed.data.slotId, notes: parsed.data.notes ?? null, } }); return NextResponse.json(booking, {status:201}); }
5.4 Páginas principais (Next.js)
- Home: destaque causas, busca rápida por skill/cidade.
- /voluntarios: filtros (skill, modalidade, cidade) + cards.
- /oferta/[id]: detalhes da oferta, perfil do voluntário, calendário de slots, botão “Agendar”.
- /dashboard (voluntário): gerenciar ofertas, slots, reservas.
- /inbox: mensagens por reserva.
- /admin: aprovações, relatórios.
Exemplo de card de voluntário (React + Tailwind)
tsx
Copiar código
// components/VolunteerCard.tsx import Link from "next/link"; export default function VolunteerCard({ v }: { v: { id: string, name: string, bio?: string, skills: string[], city?: string, rating?: number }}) { return ( <div className="rounded-2xl border p-4 shadow-sm hover:shadow-md transition"> <div className="flex justify-between items-start"> <h3 className="text-lg font-semibold">{v.name}</h3> {v.rating ? <span className="text-sm">⭐ {v.rating.toFixed(1)}</span> : null} </div> <p className="text-sm mt-1 line-clamp-3">{v.bio ?? "Voluntário(a) engajado(a) em causas sociais."}</p> <div className="flex flex-wrap gap-2 mt-3"> {v.skills.map(s => <span key={s} className="text-xs border rounded-full px-2 py-1">{s}</span>)} </div> <div className="flex justify-between items-center mt-4"> <span className="text-xs opacity-70">{v.city ?? "Remoto"}</span> <Link href={`/voluntarios/${v.id}`} className="text-sm underline">Ver perfil</Link> </div> </div> ) }
5.5 Mensagens
- MVP: tabela
Message
+ rotaGET/POST
porbookingId
+ polling (a cada 5–10s). - Depois, evoluir para Pusher/Ably/WebSockets.
5.6 Avaliações
- Permitir avaliação somente após
Booking.status === "DONE"
. - Média do perfil = média das notas nas reservas concluídas.
5.7 Admin
- Flag
Role.ADMIN
noUser
. - Painel para: listar novos perfis/ofertas, aprovar/reprovar, ver denúncias, banir usuário, exportar CSV.
6) Como usar o ChatGPT durante o desenvolvimento (prompts prontos)
Arquitetura & decisões
“Sou dev construindo uma plataforma de voluntariado com Next.js, Prisma e Postgres. Quero um diagrama simples da arquitetura (frontend, API, DB, Auth, mensagens), riscos e trade-offs de usar polling vs Pusher. Gere também uma checklist de segurança para LGPD.”
Modelagem de dados
“Considere as entidades User, VolunteerProfile, Skill, Offering, AvailabilitySlot, Booking, Message e Review. Proponha o schema Prisma, validações Zod e 3 queries otimizadas para busca por skill+cidade+modalidade.”
Geração de código
“Escreva um route handler Next.js (App Router) para criar uma reserva (‘Booking’). Valide com Zod, cheque slot único e trate erros 400/401/404/409/500. Inclua testes unitários com Vitest.”
UI/UX
“Crie um formulário acessível de criação de oferta (Offering) com React Hook Form + Zod, estados de loading, mensagens de erro/sucesso e i18n pt-BR.”
Testes
“Gere 10 testes de integração (Playwright) para o fluxo: login → virar voluntário → criar oferta → adicionar slot → fazer reserva → enviar mensagem → concluir → avaliar.”
Conteúdo & políticas
“Escreva Termos de Uso, Política de Privacidade e Código de Conduta para uma plataforma de voluntariado no Brasil, alinhados à LGPD.”
Migrações/Seeds
“Gere um script de seed Prisma com 8 skills populares, 5 perfis de voluntários com ofertas e slots ao longo das próximas 2 semanas.”
7) Segurança, privacidade e moderação
- LGPD: base legal (consentimento/interesse legítimo), política clara; rota para exportar e excluir dados do usuário.
- Rate limiting & Proteções: limitar POST /bookings /messages; validação forte; sanitização; CSRF nas rotas de formulário.
- Uploads: antivírus (se aceitar anexos), limitar tipos/tamanho.
- Moderação: botão “Denunciar”; fila para admin revisar.
- Logs e auditoria: sem dados sensíveis em logs; retenção mínima necessária.
- Spam/abuso: verificação de email; CAPTCHA no cadastro/primeiro post.
8) Deploy e custos
- Vercel (free/low): frontend + serverless.
- Postgres (free tiers): Supabase/Neon/Railway.
- Resend/SendGrid: camada gratuita para emails.
- Pusher/Ably: free tier para começar.
- Monitore limites do plano gratuito (conexões DB, invocações, cold starts).
9) Roadmap enxuto (2–3 sprints)
Sprint 1 (MVP núcleo):
- Auth, perfis, ofertas, slots, busca simples, agendamento PENDING → CONFIRMED.
- UI responsiva (mobile-first).
Sprint 2 (Engajamento & confiança):
- Mensagens, avaliações, média no perfil, denúncias, admin básico.
Sprint 3 (Qualidade & escala):
- Filtros avançados, calendário melhorado, emails transacionais, relatórios admin, Pusher em tempo real, SEO.
10) Próximos passos práticos
- Criar projeto Next.js + Tailwind + Prisma (com schema acima).
- Subir Postgres gerenciado e aplicar
migrate
. - Implementar Auth e as rotas
offerings
,slots
,bookings
. - Construir páginas /voluntarios, /oferta/[id], /dashboard e /inbox.
- Habilitar avaliações e admin básico.
- Escrever Termos/Privacidade e telas de consentimento LGPD.
- Fazer deploy (Vercel) + monitorar logs.
se quiser, eu já te entrego:
- o schema completo do Prisma adaptado ao que você imaginar (ex.: categorias fixas, cidades do Brasil, etc.),
- um seed realista com voluntários e slots,
- e os route handlers base (GET/POST) para ofertas, slots, reservas e mensagens.