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:
+353
@@ -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
|
||||
> ```
|
||||
Reference in New Issue
Block a user