feat: add domain models and repositories for user, spool presets, and filaments

- Introduced SpoolPreset and User domain models with necessary DTOs and utility functions.
- Created AuthRepository, FilamentRepository, and SpoolPresetRepository interfaces for authentication and data management.
- Implemented UI components for filament display, including ColorSwatch, FilamentCard, and StockBar.
- Developed layout components such as Header and Screen for consistent app structure.
- Added reusable UI components like Badge, Button, Card, and Input for better user interaction.
- Established global constants and theme settings for consistent styling across the application.
- Implemented utility functions for filament calculations and formatting.
- Created Zustand stores for managing authentication, filament, and preset states.
- Configured TypeScript settings for improved development experience.
This commit is contained in:
2026-03-14 10:36:13 -03:00
parent abdc2fe8ce
commit d7fb768d3b
76 changed files with 19681 additions and 0 deletions
+353
View File
@@ -0,0 +1,353 @@
# MeowSpool Mobile — Agent Guide
## Visão Geral
Aplicação mobile do **MeowSpool** construída com **React Native** + **Expo** (SDK ~51), usando **expo-router** para navegação baseada em sistema de arquivos. A arquitetura espelha o backend Rust: **Clean Code / Hexagonal**, com camadas bem delimitadas do domínio até a apresentação.
O app é **offline-first**: todos os dados são persistidos localmente em **SQLite** (expo-sqlite + SQLCipher) e sincronizados com o backend Rust em background usando estratégia **last-write-wins** via campo `updatedAt`.
---
## Stack
| Componente | Biblioteca | Observação |
|-----------------------|------------------------------------|-----------------------------------------------|
| Framework | `expo` ~51 | Managed Workflow |
| Navigation | `expo-router` ~3 | File-system routing |
| Linguagem | TypeScript 5.x | strict mode |
| Node Version | 22 LTS | Gerenciado via `mise` (`.mise.toml`) |
| UI | React Native + `@expo/vector-icons`| Ionicons |
| Forms | `react-hook-form` + `zod` | Validação em runtime |
| Estado global | `zustand` | Stores em `src/store/` |
| HTTP | `axios` | Interceptor Bearer + refresh automático |
| DB local | `expo-sqlite` + SQLCipher | WAL mode, foreign keys |
| Auth persistência | `expo-secure-store` | JWT cifrado no keychain |
| Safe Area | `react-native-safe-area-context` | |
| Gesture Handler | `react-native-gesture-handler` | |
| Animations | `react-native-reanimated` | |
---
## Arquitetura de Camadas
```
src/
├── domain/ ← Entidades puras (sem dependências externas)
├── ports/ ← Interfaces/contratos (traits equivalentes ao Rust)
├── application/ ← Use Cases (orquestram domínio + ports)
├── adapters/
│ ├── remote/ ← Implementações HTTP (Axios → API Rust)
│ └── local/ ← Implementações SQLite (expo-sqlite)
├── store/ ← Estado em memória (Zustand) — cache das queries
├── shared/ ← Design tokens, constantes, utilitários
└── presentation/
└── components/ ← Componentes UI reutilizáveis
```
### Regra de dependência
```
presentation → store → application → ports ← adapters
domain
```
Nenhuma camada interna importa de camadas externas. Os `adapters` implementam os `ports`.
---
## Estrutura de Arquivos
```
mobile/
├── .mise.toml ← node 22 LTS
├── app.json ← Expo config, scheme "meowspool"
├── babel.config.js ← module-resolver + reanimated
├── tsconfig.json ← aliases @domain, @ports, @application,
│ @adapters, @presentation, @store, @shared
├── index.js ← expo-router entry point
├── package.json
├── app/ ← expo-router file-system routes
│ ├── _layout.tsx ← Root layout (carrega sessão)
│ ├── (auth)/
│ │ ├── _layout.tsx ← Redireciona se já autenticado
│ │ ├── login.tsx ← G3-0
│ │ ├── register.tsx ← IX-0
│ │ ├── forgot-password.tsx ← L2-0
│ │ ├── verify-email.tsx ← RU-0
│ │ ├── reset-password.tsx ← 117-0
│ │ └── password-reset-done.tsx ← 135-0
│ └── (app)/
│ ├── _layout.tsx ← Stack autenticado
│ ├── (tabs)/
│ │ ├── _layout.tsx ← Bottom tabs: Início|Estoque|[+]|Config|Perfil
│ │ ├── home.tsx ← 1-0 Dashboard
│ │ ├── inventory.tsx ← CW-0 Inventário
│ │ ├── add.tsx ← FAB → redireciona para /inventory/new
│ │ ├── config.tsx ← M4-0 Presets de Carretéis
│ │ └── profile.tsx ← 1LQ-0 Perfil
│ ├── inventory/
│ │ ├── new.tsx ← 2X-0 Cadastro de Filamento
│ │ ├── filters.tsx ← 17H-0 Filtros (bottom sheet)
│ │ ├── [id].tsx ← 6L-0 Detalhe do Filamento
│ │ └── [id]/
│ │ └── edit.tsx ← 13O-0 Editar Filamento
│ ├── config/
│ │ └── presets/
│ │ ├── new.tsx ← QM-0 Novo Preset
│ │ └── [id]/
│ │ └── edit.tsx ← 1KD-0 Editar Preset
│ └── filaments/
│ └── [id]/
│ ├── qrcode.tsx ← 1CF-0 Ver QR Code
│ └── label.tsx ← 1FZ-0 Exportar Etiqueta SVG
└── src/
├── domain/
│ ├── Filament.ts ← interface Filament, calcNetWeight
│ ├── SpoolPreset.ts ← interface SpoolPreset, isUserOwnedPreset
│ ├── User.ts ← interface User, AuthSession, DTOs
│ └── Dashboard.ts ← DashboardData, MaterialSummary, etc.
├── ports/
│ ├── FilamentRepository.ts ← interface IFilamentRepository
│ ├── SpoolPresetRepository.ts ← interface ISpoolPresetRepository
│ └── AuthRepository.ts ← interface IAuthRepository
├── application/
│ ├── filament/
│ │ ├── CreateFilamentUseCase.ts
│ │ ├── UpdateFilamentUseCase.ts
│ │ ├── ListFilamentsUseCase.ts
│ │ └── DeleteFilamentUseCase.ts
│ ├── preset/
│ │ └── PresetUseCases.ts ← List, Create, Update, Delete
│ └── auth/
│ └── AuthUseCases.ts ← Login, Register, Google, Logout, etc.
├── adapters/
│ ├── remote/
│ │ ├── httpClient.ts ← Axios + interceptors JWT + refresh
│ │ ├── ApiAuthRepository.ts ← /api/v1/auth/*
│ │ ├── ApiFilamentRepository.ts ← /api/v1/filaments/*
│ │ └── ApiSpoolPresetRepository.ts ← /api/v1/spool-presets/*
│ └── local/
│ ├── database.ts ← init SQLite, WAL, foreign keys, tabelas
│ ├── LocalFilamentRepository.ts
│ └── LocalSpoolPresetRepository.ts
├── store/
│ ├── authStore.ts ← Zustand: sessão, SecureStore
│ ├── filamentStore.ts ← Zustand: lista + filtros em memória
│ └── presetStore.ts ← Zustand: systemPresets + userPresets
├── shared/
│ ├── theme.ts ← Design tokens (cores, tipografia, espaçamento)
│ ├── constants.ts ← API_BASE_URL, SecureStore keys, MATERIALS
│ └── utils/
│ └── filament.ts ← calcFilamentPercentage, formatWeight, etc.
└── presentation/
└── components/
├── ui/
│ ├── Button.tsx ← primary, secondary, ghost, danger
│ ├── Input.tsx ← label, leftIcon, rightLabel, isPassword, error
│ ├── Card.tsx ← container surface com borda
│ └── Badge.tsx ← badge dinâmico de estoque
├── layout/
│ ├── Screen.tsx ← SafeArea + scroll + keyboardAvoiding
│ └── Header.tsx ← título centralizado, back, slot direito
└── filament/
├── StockBar.tsx ← barra de progresso + badge numérico
├── ColorSwatch.tsx ← quadrado/círculo com cor hex
└── FilamentCard.tsx ← card de lista
```
---
## Design Tokens
Arquivo: `src/shared/theme.ts`
### Paleta (siamês)
| Token | Valor | Uso |
|------------------|-------------|--------------------------------------|
| `bgBase` | `#1E1B18` | Fundo geral |
| `bgSurface` | `#2A2622` | Cards, containers |
| `bgHover` | `#332F2B` | Hover/pressed em cards |
| `textPrimary` | `#F5EEDC` | Títulos, peso líquido |
| `textSecondary` | `#C9C1B0` | Descrições, labels |
| `accent` | `#38BCC2` | Botões de ação, elementos ativos |
| `accentMuted` | `#38BCC226` | Background de badges accent (10%) |
| `stockLow` | `#FF6B6B` | ≤ 15% — vermelho |
| `stockMedium` | `#FF9F43` | ≤ 35% — laranja |
| `stockOk` | `#38BCC2` | > 35% — accent |
| `border` | `#3D3830` | Bordas sutis |
| `error` | `#FF6B6B` | Mensagens de erro |
### Tipografia
- UI: **Inter**
- Mono (slugs, hex, URLs): **JetBrains Mono**
- App é **dark-mode exclusivo** — nenhum suporte a light mode
---
## Navegação (expo-router)
### Estrutura de grupos
```
(auth) ← sem autenticação; redireciona para (app) se sessão válida
(app) ← com autenticação; redireciona para (auth) se sem sessão
(tabs) ← bottom tabs fixos
```
### Deep links
- Scheme: `meowspool://`
- Filamento: `meowspool://filament/<id>``/(app)/inventory/<id>`
- QR público: `meowspool.app/f/<slug>` (web)
---
## Estratégia Offline-First
1. **Escrita**: toda mutação persiste primeiro no SQLite (`synced = 0`) e enfileira em `sync_queue`.
2. **Leitura**: lê sempre do SQLite; a API é usada apenas para sync.
3. **Sync**: background job consome `sync_queue` e envia para o backend Rust.
4. **Conflito**: last-write-wins via `updatedAt` (ISO 8601). O backend é a fonte de verdade em conflitos.
5. **Auth offline**: token armazenado no `expo-secure-store`; refresh automático via interceptor Axios.
### Schema SQLite
```sql
-- filaments
id TEXT PRIMARY KEY, user_id TEXT, material TEXT, brand TEXT, model TEXT,
color_hex TEXT, spool_preset_id TEXT, total_weight_g REAL, net_weight_g REAL,
temp_hotend_c REAL, temp_bed_c REAL, flow_factor_pct REAL, notes TEXT,
synced INTEGER DEFAULT 0, updated_at TEXT, created_at TEXT
-- spool_presets
id TEXT PRIMARY KEY, user_id TEXT, name TEXT, spool_weight_g REAL,
is_system INTEGER DEFAULT 0, synced INTEGER DEFAULT 0, created_at TEXT
-- sync_queue
id INTEGER PRIMARY KEY AUTOINCREMENT, entity_type TEXT, entity_id TEXT,
operation TEXT, payload TEXT, created_at TEXT
```
---
## Convenções de Código
### Nomenclatura
- **Componentes React**: PascalCase (`FilamentCard.tsx`)
- **Hooks**: camelCase com prefixo `use` (`useFilamentStore`)
- **Use Cases**: PascalCase com sufixo `UseCase` (`CreateFilamentUseCase`)
- **Repositórios**: PascalCase com prefixo de implementação (`ApiFilamentRepository`, `LocalFilamentRepository`)
- **Stores**: camelCase (`filamentStore.ts`), exportados como `useXxxStore`
### Imports
Sempre usar aliases em vez de caminhos relativos:
```ts
import { Filament } from '@domain/Filament';
import { useFilamentStore } from '@store/filamentStore';
import { colors } from '@shared/theme';
import { Button } from '@presentation/components/ui/Button';
```
### Componentes de tela
- Toda tela usa `SafeAreaView` com `backgroundColor: colors.bgBase`
- Header manual (não usa o header do expo-router): `headerShown: false`
- CTA fixo: `View` com `padding: spacing[5]` + `borderTopWidth: 1` + `borderTopColor: colors.border`
- ScrollViews com `showsVerticalScrollIndicator={false}` e `keyboardShouldPersistTaps="handled"`
### Formulários
- `react-hook-form` + `zodResolver` para todos os formulários
- Validações no schema Zod (nunca inline)
- `isLoading` local no componente durante submit
- Erros de API exibidos via `Alert.alert`
---
## API do Backend (rotas relevantes)
Base: `EXPO_PUBLIC_API_URL` (padrão: `http://localhost:3000/api/v1`)
### Auth
| Método | Rota | Descrição |
|--------|-------------------------------|------------------------------|
| POST | `/auth/login` | Login email/senha |
| POST | `/auth/register` | Cadastro |
| POST | `/auth/google` | OAuth Google |
| POST | `/auth/refresh` | Refresh token |
| POST | `/auth/logout` | Logout |
| POST | `/auth/forgot-password` | Solicitar reset de senha |
| POST | `/auth/reset-password` | Confirmar reset com token |
| POST | `/auth/verify-email` | Verificar e-mail com código |
### Filaments
| Método | Rota | Descrição |
|--------|-------------------------------|------------------------------|
| GET | `/filaments` | Listar (com filtros) |
| POST | `/filaments` | Criar |
| GET | `/filaments/:id` | Detalhe |
| PATCH | `/filaments/:id` | Atualizar |
| DELETE | `/filaments/:id` | Excluir |
| GET | `/filaments/:id/qrcode` | QR Code SVG |
| GET | `/filaments/:id/label` | Etiqueta SVG |
### Spool Presets
| Método | Rota | Descrição |
|--------|-------------------------------|------------------------------|
| GET | `/spool-presets` | Listar (sistema + usuário) |
| POST | `/spool-presets` | Criar preset do usuário |
| PATCH | `/spool-presets/:id` | Atualizar preset do usuário |
| DELETE | `/spool-presets/:id` | Excluir preset do usuário |
### Dashboard
| Método | Rota | Descrição |
|--------|-------------------------------|------------------------------|
| GET | `/dashboard` | Dados agregados do dashboard |
---
## Próximos Passos (integrações pendentes)
- [ ] Instanciar e injetar repositórios concretos nos Use Cases (DI container simples ou Context)
- [ ] Implementar sync background com `sync_queue` SQLite → API
- [ ] Integrar `react-native-qrcode-svg` para render real do QR Code
- [ ] Gerar SVG de etiqueta (integração com `/filaments/:id/label` do backend)
- [ ] Expo Notifications para alertas de estoque baixo
- [ ] Google OAuth com `expo-auth-session`
- [ ] Testes de integração com Jest + Testing Library
---
## Executando o Projeto
```bash
# Instalar dependências (na pasta mobile/)
mise install # garante Node 22
npm install
# Iniciar servidor de desenvolvimento
npx expo start
# iOS
npx expo run:ios
# Android
npx expo run:android
```
> **Variável de ambiente**: crie `mobile/.env` com:
> ```
> EXPO_PUBLIC_API_URL=http://localhost:3000/api/v1
> ```