feat: features 025-032 - favoritos, contatos, trabalhe-conosco, area-cliente, navbar, hero-light-dark, performance-homepage
Some checks failed
CI/CD → Deploy via SSH / Build & Push Docker Images (push) Successful in 1m0s
CI/CD → Deploy via SSH / Deploy via SSH (push) Successful in 4m35s
CI/CD → Deploy via SSH / Validate HTTPS & Endpoints (push) Failing after 46s

- feat(025): favoritos locais com FavoritesContext, HeartButton, PublicFavoritesPage
- feat(026): central de contatos admin (leads/contatos unificados)
- feat(027): configuração da página de contato via admin
- feat(028): trabalhe conosco - candidaturas com upload e admin
- feat(029): UX área do cliente - visitas, comparação, perfil
- feat(030): navbar UX - menu mobile, ThemeToggle, useFavorites
- feat(031): hero light/dark - imagens separadas por tema, upload, preview, seed
- feat(032): performance homepage - Promise.all parallel fetches, sessionStorage cache,
  preload hero image, loading=lazy nos cards, useInView hook, will-change carrossel,
  keyframes em index.css, AgentsCarousel e HomeScrollScene via props
- fix: light mode HomeScrollScene - gradiente, cores de texto, scroll hint

migrations: g1h2i3j4k5l6 (source em leads), h1i2j3k4l5m6 (contact_config),
            i1j2k3l4m5n6 (job_applications), j2k3l4m5n6o7 (hero theme images)
This commit is contained in:
MatheusAlves96 2026-04-22 22:35:17 -03:00
parent 6ef5a7a17e
commit cf5603243c
106 changed files with 11927 additions and 1367 deletions

View file

@ -0,0 +1,210 @@
# API Contracts: Trabalhe Conosco
**Feature**: 028-trabalhe-conosco
**Phase**: 1 — Design & Contracts
**Base URL**: `/api/v1`
---
## Endpoints
| Método | Path | Auth | Descrição |
|--------|-------------------------|--------------|------------------------------------------|
| POST | `/jobs/apply` | Nenhuma | Submeter candidatura (público) |
| GET | `/admin/jobs` | `@require_admin` (JWT) | Listar candidaturas paginadas (admin) |
---
## POST /api/v1/jobs/apply
Endpoint público. Recebe os dados textuais da candidatura e persiste na tabela `job_applications`.
### Request
**Headers**
```
Content-Type: application/json
```
**Body** (JSON)
| Campo | Tipo | Obrigatório | Validações |
|-----------------|----------|-------------|-------------------------------------------------------------------------|
| `name` | string | Sim | Não pode ser vazio ou apenas espaços; strip aplicado |
| `email` | string | Sim | Formato de e-mail válido (RFC-5321 via `pydantic.EmailStr`) |
| `phone` | string | Não | Qualquer string; sem validação de formato nesta versão |
| `role_interest` | string | Sim | Deve ser exatamente um de: `"Corretor(a)"`, `"Assistente Administrativo"`, `"Estagiário(a)"`, `"Outro"` |
| `message` | string | Sim | Não pode ser vazio; máximo 5000 caracteres |
| `file_name` | string | Não | Nome do arquivo de currículo; sem conteúdo binário |
**Exemplo de request**
```json
{
"name": "Ana Lima",
"email": "ana.lima@email.com",
"phone": "(11) 98765-4321",
"role_interest": "Corretor(a)",
"message": "Tenho 5 anos de experiência no mercado imobiliário e gostaria de integrar a equipe.",
"file_name": "curriculo-ana-lima.pdf"
}
```
### Responses
#### 201 Created — Candidatura registrada com sucesso
```json
{
"message": "Candidatura recebida com sucesso"
}
```
#### 422 Unprocessable Entity — Dados inválidos
```json
{
"error": "Dados inválidos",
"details": [
{
"type": "value_error",
"loc": ["role_interest"],
"msg": "Value error, role_interest deve ser um de: Assistente Administrativo, Corretor(a), Estagiário(a), Outro",
"input": "Diretor",
"url": "https://errors.pydantic.dev/..."
}
]
}
```
#### 400 Bad Request — Body ausente ou não é JSON válido
```json
{
"error": "Dados inválidos",
"details": [...]
}
```
---
## GET /api/v1/admin/jobs
Endpoint protegido. Retorna listagem paginada de todas as candidaturas em ordem decrescente de `created_at`.
### Request
**Headers**
```
Authorization: Bearer <jwt_token>
Content-Type: application/json
```
**Query Parameters**
| Parâmetro | Tipo | Default | Restrições | Descrição |
|------------|---------|---------|-----------------|------------------------|
| `page` | integer | `1` | ≥ 1 | Número da página |
| `per_page` | integer | `20` | 1 100 | Registros por página |
**Exemplo de request**
```
GET /api/v1/admin/jobs?page=1&per_page=20
```
### Responses
#### 200 OK — Lista retornada com sucesso
```json
{
"items": [
{
"id": 7,
"name": "Ana Lima",
"email": "ana.lima@email.com",
"phone": "(11) 98765-4321",
"role_interest": "Corretor(a)",
"message": "Tenho 5 anos de experiência no mercado imobiliário...",
"file_name": "curriculo-ana-lima.pdf",
"status": "pending",
"created_at": "2026-04-21T14:35:00"
},
{
"id": 6,
"name": "Carlos Souza",
"email": "carlos@email.com",
"phone": null,
"role_interest": "Estagiário(a)",
"message": "Estudante de Administração em busca do primeiro emprego.",
"file_name": null,
"status": "pending",
"created_at": "2026-04-20T09:12:00"
}
],
"total": 42,
"page": 1,
"per_page": 20,
"pages": 3
}
```
**Schema do item** (`JobApplicationOut`)
| Campo | Tipo | Nullable | Descrição |
|-----------------|------------------|----------|----------------------------------|
| `id` | integer | Não | Identificador único |
| `name` | string | Não | Nome completo do candidato |
| `email` | string | Não | E-mail do candidato |
| `phone` | string \| null | Sim | Telefone (opcional) |
| `role_interest` | string | Não | Cargo de interesse selecionado |
| `message` | string | Não | Mensagem/apresentação |
| `file_name` | string \| null | Sim | Nome do arquivo de currículo |
| `status` | string | Não | Estado: `"pending"` (padrão) |
| `created_at` | string (ISO 8601)| Não | Data/hora do envio |
**Schema de paginação**
| Campo | Tipo | Descrição |
|------------|---------|-------------------------------------|
| `total` | integer | Total de candidaturas no sistema |
| `page` | integer | Página atual |
| `per_page` | integer | Registros retornados nesta página |
| `pages` | integer | Total de páginas |
#### 200 OK — Nenhuma candidatura registrada
```json
{
"items": [],
"total": 0,
"page": 1,
"per_page": 20,
"pages": 0
}
```
#### 401 Unauthorized — Token ausente ou inválido
```json
{
"error": "Token inválido ou ausente"
}
```
#### 403 Forbidden — Usuário autenticado sem permissão de admin
```json
{
"error": "Acesso negado"
}
```
---
## Notas de Implementação
- O endpoint `POST /api/v1/jobs/apply` **não** possui autenticação — qualquer cliente pode submeter.
- O endpoint `GET /api/v1/admin/jobs` usa o decorator `@require_admin` já existente no projeto, que valida o JWT e verifica a flag de administrador.
- O campo `created_at` é serializado pelo Pydantic como ISO 8601 sem timezone (`TIMESTAMP WITHOUT TIME ZONE` no PostgreSQL).
- `per_page` deve ser limitado a 100 no backend para evitar queries excessivamente grandes.
- Campos ausentes no body do POST são tratados pelo Pydantic: obrigatórios geram erro 422, opcionais recebem `None`.