Files
petlink_final/docs/10-orientacoes-desenvolvimento.md

9.0 KiB

10. Orientações de Desenvolvimento

10.1 Configuração do Ambiente Local

Pré-requisitos

  • Node.js 20+ — instalar via nvm (recomendado)
  • pnpm 8+npm install -g pnpm
  • Git 2.40+
  • Conta Supabase (base de dados + storage)
  • Conta Stripe (modo teste disponível gratuitamente)
  • Conta Resend (emails)
  • Chave API Anthropic (IA)

Setup Inicial

# 1. Clonar o repositório
git clone https://github.com/pawlink/pawlink.git
cd pawlink

# 2. Instalar dependências
pnpm install

# 3. Copiar template de variáveis de ambiente
cp .env.example .env.local
# Preencher .env.local com as tuas chaves

# 4. Aplicar migrações da base de dados
npx prisma migrate dev

# 5. Popular a base de dados com dados de teste
npx prisma db seed

# 6. Iniciar o servidor de desenvolvimento
pnpm dev
# Aplicação disponível em http://localhost:3000

10.2 Convenções de Código

TypeScript

// ✅ Usar tipos explícitos em funções públicas
export async function getAnimalById(id: string): Promise<Animal | null> { ... }

// ✅ Interfaces para props de componentes
interface AnimalCardProps {
  animal: Animal;
  onReserve?: (id: string) => void;
}

// ✅ Zod para validação — nunca "as" para fazer cast de dados externos
const schema = z.object({ id: z.string().cuid() });
const data = schema.parse(body); // lança erro se inválido

// ❌ Evitar
const data = body as { id: string }; // não valida nada

Componentes React

// ✅ Server Components por defeito (sem 'use client')
// Apenas adicionar 'use client' quando necessário (hooks, eventos, browser APIs)

// ✅ Async Server Components para fetch de dados
async function AnimalPage({ params }: { params: { id: string } }) {
  const animal = await getAnimalById(params.id); // fetch directo no servidor
  if (!animal) notFound();
  return <AnimalProfile animal={animal} />;
}

// ✅ Separar lógica de UI — componente de apresentação vs. componente de dados
// AnimalProfile.tsx — recebe dados como props, sem fetch
// app/(main)/animals/[id]/page.tsx — faz fetch, passa para AnimalProfile

API Routes

// ✅ Padrão de API Route
export async function GET(req: Request) {
  try {
    // 1. Autenticação (se necessário)
    const session = await getServerSession(authConfig);
    if (!session) return Response.json({ error: 'Unauthorized' }, { status: 401 });

    // 2. Validação de inputs
    const { searchParams } = new URL(req.url);
    const params = FiltersSchema.safeParse(Object.fromEntries(searchParams));
    if (!params.success) return Response.json({ error: params.error.issues }, { status: 400 });

    // 3. Lógica de negócio
    const data = await getAnimals(params.data);

    // 4. Resposta
    return Response.json(data);
  } catch (error) {
    console.error('[GET /api/animals]', error);
    return Response.json({ error: 'Internal Server Error' }, { status: 500 });
  }
}

10.3 Gestão de Estado

Tipo de Estado Solução Quando usar
Dados do servidor TanStack Query Listas de animais, detalhes, reservas — qualquer dado que vem da API
Estado global do cliente Zustand Sessão do utilizador, preferências UI (tema), estado do menu
Estado de formulários React Hook Form Todos os formulários — registo, doação, reserva
Estado de UI local useState Toggles, modais, estados temporários dentro de um componente
// ✅ Exemplo: store Zustand para preferências do utilizador
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface UIStore {
  darkMode: boolean;
  toggleDarkMode: () => void;
}

export const useUIStore = create<UIStore>()(
  persist(
    (set) => ({
      darkMode: false,
      toggleDarkMode: () => set(state => ({ darkMode: !state.darkMode })),
    }),
    { name: 'ui-preferences' }
  )
);

10.4 Fluxo de Trabalho Git

Branches

main          → código estável em produção
develop       → integração contínua de funcionalidades
feature/*     → novas funcionalidades (ex: feature/donation-flow)
fix/*         → correcção de bugs (ex: fix/age-validation)
docs/*        → alterações de documentação

Processo de Desenvolvimento

# 1. Criar branch a partir de develop
git checkout develop
git pull origin develop
git checkout -b feature/nome-da-funcionalidade

# 2. Desenvolver + commits frequentes
git add .
git commit -m "feat: add animal filter by district"

# 3. Push e Pull Request para develop
git push origin feature/nome-da-funcionalidade
# Abrir PR no GitHub com descrição do que foi feito

# 4. Após review e testes → merge para develop
# 5. Periodicamente → release: merge develop para main

Mensagens de Commit (Conventional Commits)

Prefixo Uso
feat: Nova funcionalidade
fix: Correcção de bug
docs: Alteração de documentação
style: Formatação, espaços (sem alteração de lógica)
refactor: Refactorização sem nova funcionalidade
test: Adicionar ou corrigir testes
chore: Tarefas de manutenção (deps, config)
# ✅ Exemplos de boas mensagens
feat: add reservation calendar with available dates
fix: correct age validation for February 29 birthdays
docs: update API routes documentation
refactor: extract donation logic to separate service
test: add E2E tests for adoption flow

10.5 Testes

Estrutura de Testes

pawlink/
├── __tests__/
│   ├── unit/
│   │   ├── age-validation.test.ts
│   │   ├── format.test.ts
│   │   └── schemas.test.ts
│   ├── integration/
│   │   ├── api/
│   │   │   ├── animals.test.ts
│   │   │   └── reservations.test.ts
│   └── components/
│       ├── AnimalCard.test.tsx
│       └── DonationFlow.test.tsx
└── e2e/
    ├── auth.spec.ts
    ├── adoption.spec.ts
    └── donation.spec.ts

Exemplos de Testes

// __tests__/unit/age-validation.test.ts
import { isAdult } from '@/lib/auth/age-validation';
import { describe, it, expect } from 'vitest';

describe('isAdult', () => {
  it('deve retornar true para utilizador com exactamente 18 anos hoje', () => {
    const today = new Date();
    const birthdate = new Date(today.getFullYear() - 18, today.getMonth(), today.getDate());
    expect(isAdult(birthdate)).toBe(true);
  });

  it('deve retornar false para utilizador com 17 anos', () => {
    const today = new Date();
    const birthdate = new Date(today.getFullYear() - 17, today.getMonth(), today.getDate());
    expect(isAdult(birthdate)).toBe(false);
  });
});
// e2e/adoption.spec.ts (Playwright)
import { test, expect } from '@playwright/test';

test('fluxo completo de adopção', async ({ page }) => {
  // Login
  await page.goto('/login');
  await page.fill('[name="email"]', 'teste@petlink.pt');
  await page.fill('[name="password"]', 'Password123!');
  await page.click('[type="submit"]');

  // Navegar para animal
  await page.goto('/animals');
  await page.click('[data-testid="animal-card"]:first-child');

  // Reservar
  await page.click('[data-testid="adopt-button"]');
  await page.click('[data-testid="calendar-day-available"]:first-child');
  await page.click('[data-testid="confirm-reservation"]');

  // Verificar confirmação
  await expect(page.locator('[data-testid="reservation-success"]')).toBeVisible();
});

10.6 Performance

  • Imagens: usar sempre next/image com sizes adequado — geração automática WebP e lazy loading
  • Paginação: API de animais usa cursor-based pagination — nunca carregar mais de 20 por pedido
  • Cache TanStack Query: staleTime: 5 * 60 * 1000 (5 minutos) para listas de animais
  • Connection Pooling: Supabase Pooler activado para PostgreSQL em produção
  • Bundle Analysis: executar pnpm analyze antes de cada release para identificar regressões
// Exemplo: query paginada com cursor
export async function getAnimals(params: AnimalFilters & { cursor?: string }) {
  return prisma.animal.findMany({
    where: buildWhereClause(params),
    take: 20,
    skip: params.cursor ? 1 : 0,
    cursor: params.cursor ? { id: params.cursor } : undefined,
    orderBy: [{ urgent: 'desc' }, { createdAt: 'desc' }],
    include: { photos: { where: { isPrimary: true }, take: 1 }, shelter: true }
  });
}

10.7 Acessibilidade

  • Imagens: alt descritivo obrigatório em todas as imagens (ex: "Bobi — Labrador macho de 2 anos no Canil de Lisboa")
  • Formulários: labels associados a todos os inputs, mensagens de erro ligadas via aria-describedby
  • Foco: ordem de foco lógica, indicadores de foco visíveis, skip links no topo
  • Contraste: mínimo 4.5:1 para texto normal, 3:1 para texto grande (testar com Lighthouse)
  • Teclado: todos os elementos interactivos acessíveis por teclado (Tab, Enter, Espaço, Esc)
  • Screen readers: testar com NVDA (Windows) ou VoiceOver (Mac/iOS) nas páginas principais