first commit

This commit is contained in:
2026-05-04 09:43:36 +01:00
commit dfae1b5335
37 changed files with 10343 additions and 0 deletions

View File

@@ -0,0 +1,302 @@
# 10. Orientações de Desenvolvimento
## 10.1 Configuração do Ambiente Local
### Pré-requisitos
- **Node.js 20+** — instalar via [nvm](https://github.com/nvm-sh/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
```bash
# 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
```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
```typescript
// ✅ 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
```typescript
// ✅ 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 |
```typescript
// ✅ 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
```bash
# 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) |
```bash
# ✅ 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
```typescript
// __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);
});
});
```
```typescript
// 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@pawlink.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
```typescript
// 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