first commit
This commit is contained in:
302
docs/10-orientacoes-desenvolvimento.md
Normal file
302
docs/10-orientacoes-desenvolvimento.md
Normal 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
|
||||
Reference in New Issue
Block a user