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
+90
View File
@@ -0,0 +1,90 @@
import axios, { type AxiosInstance, type InternalAxiosRequestConfig } from 'axios';
import * as SecureStore from 'expo-secure-store';
import { API_BASE_URL, SECURE_STORE_ACCESS_TOKEN, SECURE_STORE_REFRESH_TOKEN } from '@shared/constants';
/**
* Cliente HTTP centralizado.
*
* - Injeta o Bearer token automaticamente em todas as requisições.
* - Intercepta 401 para tentar refresh do token automaticamente.
* - Garante que todas as chamadas apontem para a base URL da API.
*/
const httpClient: AxiosInstance = axios.create({
baseURL: API_BASE_URL,
timeout: 15_000,
headers: {
'Content-Type': 'application/json',
},
});
// ── Interceptor de request: anexa Bearer token ──────────────
httpClient.interceptors.request.use(async (config: InternalAxiosRequestConfig) => {
const token = await SecureStore.getItemAsync(SECURE_STORE_ACCESS_TOKEN);
if (token && config.headers) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// ── Interceptor de response: refresh automático em 401 ──────
let isRefreshing = false;
let pendingQueue: Array<{
resolve: (token: string) => void;
reject: (err: unknown) => void;
}> = [];
function processQueue(error: unknown, token: string | null): void {
pendingQueue.forEach(({ resolve, reject }) => {
if (error) reject(error);
else if (token) resolve(token);
});
pendingQueue = [];
}
httpClient.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
return new Promise((resolve, reject) => {
pendingQueue.push({ resolve, reject });
}).then((token) => {
originalRequest.headers.Authorization = `Bearer ${token}`;
return httpClient(originalRequest);
});
}
originalRequest._retry = true;
isRefreshing = true;
try {
const refreshToken = await SecureStore.getItemAsync(SECURE_STORE_REFRESH_TOKEN);
if (!refreshToken) throw new Error('No refresh token');
const { data } = await axios.post(`${API_BASE_URL}/auth/refresh`, { refreshToken });
await SecureStore.setItemAsync(SECURE_STORE_ACCESS_TOKEN, data.access_token);
await SecureStore.setItemAsync(SECURE_STORE_REFRESH_TOKEN, data.refresh_token);
processQueue(null, data.access_token);
originalRequest.headers.Authorization = `Bearer ${data.access_token}`;
return httpClient(originalRequest);
} catch (refreshError) {
processQueue(refreshError, null);
// Limpa tokens inválidos — a store de auth detecta e redireciona para login
await SecureStore.deleteItemAsync(SECURE_STORE_ACCESS_TOKEN);
await SecureStore.deleteItemAsync(SECURE_STORE_REFRESH_TOKEN);
return Promise.reject(refreshError);
} finally {
isRefreshing = false;
}
}
return Promise.reject(error);
},
);
export { httpClient };