diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts
index bdbbc65..3763c18 100644
--- a/apps/api/src/main.ts
+++ b/apps/api/src/main.ts
@@ -42,8 +42,9 @@ async function bootstrap() {
// Configuration Swagger
SwaggerModule.setup('api', app, openApiDocument);
- await app.listen(PORT);
+ await app.listen(PORT, '0.0.0.0');
console.log(`Application is running on: http://localhost:${PORT}`);
+ console.log(`Also accessible on network: http://192.168.1.147:${PORT}`);
}
bootstrap();
diff --git a/apps/mobile/App.tsx b/apps/mobile/App.tsx
index 4519659..00889fe 100644
--- a/apps/mobile/App.tsx
+++ b/apps/mobile/App.tsx
@@ -1,41 +1,11 @@
-import { StatusBar } from 'expo-status-bar';
-import { StyleSheet, Text, View } from 'react-native';
-import { athleteSchema } from '@dropit/schemas';
-import { z } from 'zod';
+import React from 'react';
+import AuthProvider from './src/components/AuthProvider';
+import DashboardScreen from './src/components/DashboardScreen';
export default function App() {
-
-
- return (
-
- Welcome to DropIt Mobile!
- ✅ Shared packages working Ouais!
- Test athlete: Sten Levasseur
-
-
- );
+ return (
+
+
+
+ );
}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- backgroundColor: '#fff',
- alignItems: 'center',
- justifyContent: 'center',
- },
- successText: {
- color: 'green',
- fontSize: 16,
- marginTop: 10,
- },
- errorText: {
- color: 'red',
- fontSize: 16,
- marginTop: 10,
- },
- smallText: {
- fontSize: 12,
- marginTop: 5,
- color: '#666',
- },
-});
diff --git a/apps/mobile/README.md b/apps/mobile/README.md
new file mode 100644
index 0000000..bfd8391
--- /dev/null
+++ b/apps/mobile/README.md
@@ -0,0 +1,213 @@
+# DropIt Mobile App
+
+Application mobile React Native avec Expo pour la gestion d'entraînements de musculation.
+
+## 🚀 Fonctionnalités
+
+- ✅ **Authentification Better Auth** - Connexion sécurisée avec email/mot de passe
+- ✅ **Support Bearer Token** - Gestion automatique des tokens pour les API calls
+- ✅ **AsyncStorage** - Persistance de session locale
+- ✅ **Packages partagés** - Utilisation des schémas et contrats du monorepo
+- ✅ **TypeScript** - Typage complet avec validation Zod
+- ✅ **Design responsive** - Interface optimisée mobile
+
+## 🛠 Architecture
+
+```
+src/
+├── components/
+│ ├── AuthProvider.tsx # Gestion globale de l'authentification
+│ ├── LoginScreen.tsx # Écran de connexion
+│ └── DashboardScreen.tsx # Écran principal après connexion
+├── lib/
+│ ├── auth-client.ts # Configuration Better Auth mobile
+│ └── api.ts # Client API avec Bearer token
+```
+
+## 📱 Configuration Better Auth
+
+### Client d'authentification (`src/lib/auth-client.ts`)
+
+Le client Better Auth est configuré spécialement pour React Native :
+
+```typescript
+export const authClient = createAuthClient({
+ baseURL: 'http://localhost:3000',
+ plugins: [organizationClient({ ac, roles: { owner, admin, member } })],
+ storage: {
+ // Configuration AsyncStorage pour React Native
+ get: async (key: string) => AsyncStorage.getItem(key),
+ set: async (key: string, value: any) => AsyncStorage.setItem(key, JSON.stringify(value)),
+ remove: async (key: string) => AsyncStorage.removeItem(key),
+ },
+});
+```
+
+### API Client avec Bearer Token (`src/lib/api.ts`)
+
+```typescript
+export const api = initClient(apiContract, {
+ baseUrl: 'http://localhost:3000/api',
+ api: async (args: any) => {
+ // Récupération automatique du token depuis AsyncStorage
+ const authData = await AsyncStorage.getItem('better-auth.session-token');
+ const token = authData ? JSON.parse(authData) : null;
+
+ const headers: Record = {
+ 'Content-Type': 'application/json',
+ };
+
+ if (token) {
+ headers.Authorization = `Bearer ${token}`;
+ }
+
+ // Requête avec Bearer token
+ return fetch(args.path, {
+ method: args.method,
+ headers,
+ body: args.body ? JSON.stringify(args.body) : undefined,
+ });
+ },
+});
+```
+
+## 🔐 Flux d'authentification
+
+### 1. Connexion
+```typescript
+const { data, error } = await authClient.signIn.email({
+ email: 'user@example.com',
+ password: 'password123'
+});
+```
+
+### 2. Gestion de session
+```typescript
+const sessionData = await authClient.getSession();
+if (sessionData.data) {
+ // Utilisateur connecté
+ setSession(sessionData.data);
+}
+```
+
+### 3. Déconnexion
+```typescript
+await authClient.signOut();
+setSession(null);
+```
+
+## 🧩 Intégration des packages partagés
+
+L'app mobile utilise tous les packages partagés du monorepo :
+
+- **@dropit/contract** - Contrats API typés
+- **@dropit/schemas** - Validation Zod
+- **@dropit/permissions** - Système de rôles et permissions
+- **@dropit/i18n** - Internationalisation
+
+### Exemple d'utilisation des schémas :
+
+```typescript
+import { athleteSchema } from '@dropit/schemas';
+
+const testAthlete = athleteSchema.parse({
+ id: '1',
+ firstName: 'John',
+ lastName: 'Doe',
+ email: 'john.doe@example.com',
+ organizationId: 'org-1'
+});
+```
+
+## 📋 Configuration du serveur
+
+Le serveur API doit être configuré avec support Bearer token :
+
+```typescript
+// better-auth.config.ts
+bearerToken: {
+ enabled: true, // ✅ Activé pour mobile
+},
+```
+
+## 🚀 Démarrage
+
+### Prérequis
+- API backend démarrée sur `localhost:3000`
+- Base de données configurée
+- Expo CLI installé
+
+### Commandes
+
+```bash
+# Installation des dépendances
+pnpm install
+
+# Démarrage de l'app mobile
+pnpm dev:mobile
+
+# Vérification TypeScript
+pnpm --filter mobile typecheck
+
+# Dans un autre terminal, démarrer l'API
+pnpm --filter api dev
+```
+
+### Test sur appareil
+
+1. Installer **Expo Go** sur votre smartphone
+2. Scanner le QR code affiché dans le terminal
+3. L'app se charge avec l'écran de connexion
+
+## 🔧 Configuration réseau
+
+Pour tester sur un appareil physique, assurez-vous que :
+
+1. **L'API est accessible** depuis votre réseau local
+2. **Les CORS sont configurés** pour accepter les requêtes mobiles
+3. **L'URL de l'API** correspond à votre IP locale si nécessaire
+
+```typescript
+// Remplacer localhost par votre IP locale si nécessaire
+baseURL: 'http://192.168.1.100:3000', // Exemple
+```
+
+## 🎨 Interface utilisateur
+
+### Écran de connexion
+- Design moderne et responsive
+- Validation des champs
+- Gestion d'erreurs
+- État de chargement
+
+### Dashboard
+- Navigation intuitive
+- Boutons d'action
+- Test des packages partagés
+- Déconnexion sécurisée
+
+## 🐛 Débogage
+
+### Logs utiles
+```typescript
+// Connexion réussie
+console.log('Login successful:', data.user.email);
+
+// Session trouvée
+console.log('Session found:', sessionData.data.user.email);
+
+// Erreur d'authentification
+console.error('Login error:', error);
+```
+
+### Problèmes courants
+
+1. **Token non envoyé** - Vérifier AsyncStorage et Bearer token
+2. **CORS errors** - Configurer `trustedOrigins` dans better-auth
+3. **Session expirée** - Implémenter refresh token automatique
+
+## 📚 Ressources
+
+- [Better Auth Documentation](https://www.better-auth.com/docs)
+- [Expo Documentation](https://docs.expo.dev/)
+- [React Native Documentation](https://reactnative.dev/docs/getting-started)
\ No newline at end of file
diff --git a/apps/mobile/package.json b/apps/mobile/package.json
index c61d94c..cb570b5 100644
--- a/apps/mobile/package.json
+++ b/apps/mobile/package.json
@@ -22,7 +22,10 @@
"react-dom": "19.0.0",
"react-native": "0.79.5",
"react-native-web": "~0.20.0",
- "zod": "^3.24.1"
+ "zod": "^3.24.1",
+ "better-auth": "^1.2.7",
+ "@react-native-async-storage/async-storage": "^2.1.0",
+ "@ts-rest/core": "^3.51.0"
},
"devDependencies": {
"@babel/core": "^7.25.2",
diff --git a/apps/mobile/src/components/AuthProvider.tsx b/apps/mobile/src/components/AuthProvider.tsx
new file mode 100644
index 0000000..9255f48
--- /dev/null
+++ b/apps/mobile/src/components/AuthProvider.tsx
@@ -0,0 +1,115 @@
+import React, { useEffect, useState } from 'react';
+import { View, Text, ActivityIndicator, StyleSheet } from 'react-native';
+import { authClient } from '../lib/auth-client';
+import LoginScreen from './LoginScreen';
+
+interface AuthProviderProps {
+ children: React.ReactNode;
+}
+
+interface User {
+ id: string;
+ email: string;
+ name: string;
+ emailVerified: boolean;
+}
+
+interface Session {
+ user: User;
+ session: {
+ id: string;
+ userId: string;
+ activeOrganizationId?: string | null;
+ };
+}
+
+export default function AuthProvider({ children }: AuthProviderProps) {
+ const [session, setSession] = useState(null);
+ const [isLoading, setIsLoading] = useState(true);
+ const [isInitialized, setIsInitialized] = useState(false);
+
+ useEffect(() => {
+ initializeAuth();
+ }, []);
+
+ const initializeAuth = async () => {
+ try {
+ setIsLoading(true);
+
+ // Attendre un peu pour éviter les erreurs d'initialisation
+ await new Promise(resolve => setTimeout(resolve, 100));
+
+ // Récupérer la session actuelle
+ const sessionData = await authClient.getSession();
+
+ if (sessionData.data) {
+ console.log('Session found:', sessionData.data.user.email);
+ setSession(sessionData.data);
+ } else {
+ console.log('No session found');
+ setSession(null);
+ }
+ } catch (error) {
+ console.error('Auth initialization error:', error);
+ setSession(null);
+ } finally {
+ setIsLoading(false);
+ setIsInitialized(true);
+ }
+ };
+
+ const handleLoginSuccess = async () => {
+ // Rafraîchir la session après connexion réussie
+ await initializeAuth();
+ };
+
+ const handleLogout = async () => {
+ try {
+ await authClient.signOut();
+ setSession(null);
+ console.log('Logged out successfully');
+ } catch (error) {
+ console.error('Logout error:', error);
+ }
+ };
+
+ // Écran de chargement initial
+ if (!isInitialized || isLoading) {
+ return (
+
+
+ Initialisation...
+
+ );
+ }
+
+ // Si pas de session, afficher l'écran de connexion
+ if (!session) {
+ return ;
+ }
+
+ // Session active, afficher l'app
+ return (
+
+ {children}
+ {/* Vous pouvez ajouter un bouton de déconnexion ici pour les tests */}
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ },
+ loadingContainer: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: '#FFFFFF',
+ },
+ loadingText: {
+ marginTop: 16,
+ fontSize: 16,
+ color: '#6B7280',
+ },
+});
\ No newline at end of file
diff --git a/apps/mobile/src/components/DashboardScreen.tsx b/apps/mobile/src/components/DashboardScreen.tsx
new file mode 100644
index 0000000..55a32f0
--- /dev/null
+++ b/apps/mobile/src/components/DashboardScreen.tsx
@@ -0,0 +1,179 @@
+import React from 'react';
+import {
+ View,
+ Text,
+ TouchableOpacity,
+ StyleSheet,
+ Alert,
+} from 'react-native';
+import { StatusBar } from 'expo-status-bar';
+import { authClient } from '../lib/auth-client';
+import { athleteSchema } from '@dropit/schemas';
+
+export default function DashboardScreen() {
+ const handleLogout = async () => {
+ Alert.alert(
+ 'Déconnexion',
+ 'Êtes-vous sûr de vouloir vous déconnecter ?',
+ [
+ { text: 'Annuler', style: 'cancel' },
+ {
+ text: 'Déconnexion',
+ style: 'destructive',
+ onPress: async () => {
+ try {
+ await authClient.signOut();
+ // L'AuthProvider détectera automatiquement la déconnexion
+ } catch (error) {
+ console.error('Logout error:', error);
+ Alert.alert('Erreur', 'Erreur lors de la déconnexion');
+ }
+ },
+ },
+ ]
+ );
+ };
+
+ const testSchemaValidation = () => {
+ try {
+ const testAthlete = athleteSchema.parse({
+ id: '1',
+ firstName: 'John',
+ lastName: 'Doe',
+ email: 'john.doe@example.com',
+ organizationId: 'org-1'
+ });
+
+ Alert.alert(
+ 'Test Schema',
+ `✅ Validation réussie!\nAthlete: ${testAthlete.firstName} ${testAthlete.lastName}`
+ );
+ } catch (error) {
+ Alert.alert('Test Schema', '❌ Erreur de validation');
+ }
+ };
+
+ return (
+
+
+
+ {/* Header */}
+
+ Tableau de bord
+ Bienvenue sur DropIt Mobile!
+
+
+ {/* Content */}
+
+
+ 🏋️ Entraînements
+
+ Gérez vos séances d'entraînement
+
+
+
+
+ 👤 Athlètes
+
+ Suivez vos performances et progrès
+
+
+
+
+ 📊 Statistiques
+
+ Analysez vos résultats d'entraînement
+
+
+
+ {/* Test button pour vérifier les packages partagés */}
+
+ Tester Schema Partagé
+
+
+
+ {/* Footer avec bouton de déconnexion */}
+
+
+ Se déconnecter
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#F9FAFB',
+ },
+ header: {
+ paddingHorizontal: 24,
+ paddingTop: 60,
+ paddingBottom: 32,
+ backgroundColor: '#FFFFFF',
+ },
+ title: {
+ fontSize: 28,
+ fontWeight: 'bold',
+ color: '#1F2937',
+ marginBottom: 8,
+ },
+ subtitle: {
+ fontSize: 16,
+ color: '#6B7280',
+ },
+ content: {
+ flex: 1,
+ paddingHorizontal: 24,
+ paddingTop: 24,
+ },
+ card: {
+ backgroundColor: '#FFFFFF',
+ borderRadius: 12,
+ padding: 20,
+ marginBottom: 16,
+ shadowColor: '#000',
+ shadowOffset: { width: 0, height: 1 },
+ shadowOpacity: 0.1,
+ shadowRadius: 3,
+ elevation: 2,
+ },
+ cardTitle: {
+ fontSize: 18,
+ fontWeight: '600',
+ color: '#1F2937',
+ marginBottom: 8,
+ },
+ cardDescription: {
+ fontSize: 14,
+ color: '#6B7280',
+ lineHeight: 20,
+ },
+ testButton: {
+ backgroundColor: '#10B981',
+ borderRadius: 8,
+ padding: 16,
+ alignItems: 'center',
+ marginTop: 16,
+ },
+ testButtonText: {
+ color: '#FFFFFF',
+ fontSize: 16,
+ fontWeight: '600',
+ },
+ footer: {
+ padding: 24,
+ },
+ logoutButton: {
+ backgroundColor: '#EF4444',
+ borderRadius: 8,
+ padding: 16,
+ alignItems: 'center',
+ },
+ logoutButtonText: {
+ color: '#FFFFFF',
+ fontSize: 16,
+ fontWeight: '600',
+ },
+});
\ No newline at end of file
diff --git a/apps/mobile/src/components/LoginScreen.tsx b/apps/mobile/src/components/LoginScreen.tsx
new file mode 100644
index 0000000..2f79f2b
--- /dev/null
+++ b/apps/mobile/src/components/LoginScreen.tsx
@@ -0,0 +1,234 @@
+import React, { useState } from 'react';
+import {
+ View,
+ Text,
+ TextInput,
+ TouchableOpacity,
+ StyleSheet,
+ Alert,
+ KeyboardAvoidingView,
+ Platform,
+ ScrollView,
+ ActivityIndicator,
+} from 'react-native';
+import { StatusBar } from 'expo-status-bar';
+import { authClient } from '../lib/auth-client';
+
+interface LoginScreenProps {
+ onLoginSuccess?: () => void;
+}
+
+export default function LoginScreen({ onLoginSuccess }: LoginScreenProps) {
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [isLoading, setIsLoading] = useState(false);
+
+ const handleLogin = async () => {
+ if (!email.trim() || !password.trim()) {
+ Alert.alert('Erreur', 'Veuillez remplir tous les champs');
+ return;
+ }
+
+ setIsLoading(true);
+
+ try {
+ const { data, error } = await authClient.signIn.email({
+ email: email.trim().toLowerCase(),
+ password,
+ });
+
+ if (error) {
+ console.error('Login error:', error);
+ Alert.alert(
+ 'Erreur de connexion',
+ error.message || 'Email ou mot de passe incorrect'
+ );
+ return;
+ }
+
+ if (data) {
+ console.log('Login successful:', data.user.email);
+ Alert.alert('Succès', 'Connexion réussie !', [
+ { text: 'OK', onPress: onLoginSuccess }
+ ]);
+ }
+ } catch (error) {
+ console.error('Unexpected login error:', error);
+ Alert.alert('Erreur', 'Une erreur inattendue est survenue');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const handleSignUp = () => {
+ Alert.alert('Inscription', 'Fonctionnalité d\'inscription à venir');
+ };
+
+ return (
+
+
+
+ {/* Header */}
+
+ DropIt
+ Votre assistant d'entraînement
+
+
+ {/* Login Form */}
+
+
+ Email
+
+
+
+
+ Mot de passe
+
+
+
+
+ {isLoading ? (
+
+ ) : (
+ Se connecter
+ )}
+
+
+
+
+ Pas encore de compte ? S'inscrire
+
+
+
+
+ {/* Footer */}
+
+
+ En vous connectant, vous acceptez nos conditions d'utilisation
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#FFFFFF',
+ },
+ scrollContainer: {
+ flexGrow: 1,
+ paddingHorizontal: 24,
+ paddingTop: 60,
+ paddingBottom: 40,
+ },
+ header: {
+ alignItems: 'center',
+ marginBottom: 48,
+ },
+ title: {
+ fontSize: 32,
+ fontWeight: 'bold',
+ color: '#1F2937',
+ marginBottom: 8,
+ },
+ subtitle: {
+ fontSize: 16,
+ color: '#6B7280',
+ textAlign: 'center',
+ },
+ form: {
+ flex: 1,
+ justifyContent: 'center',
+ },
+ inputContainer: {
+ marginBottom: 20,
+ },
+ inputLabel: {
+ fontSize: 14,
+ fontWeight: '600',
+ color: '#374151',
+ marginBottom: 8,
+ },
+ input: {
+ borderWidth: 1,
+ borderColor: '#D1D5DB',
+ borderRadius: 8,
+ paddingHorizontal: 16,
+ paddingVertical: 12,
+ fontSize: 16,
+ backgroundColor: '#FFFFFF',
+ color: '#1F2937',
+ },
+ loginButton: {
+ backgroundColor: '#3B82F6',
+ borderRadius: 8,
+ paddingVertical: 16,
+ alignItems: 'center',
+ marginTop: 12,
+ marginBottom: 16,
+ },
+ loginButtonDisabled: {
+ backgroundColor: '#9CA3AF',
+ },
+ loginButtonText: {
+ color: '#FFFFFF',
+ fontSize: 16,
+ fontWeight: '600',
+ },
+ signUpButton: {
+ alignItems: 'center',
+ paddingVertical: 12,
+ },
+ signUpButtonText: {
+ fontSize: 14,
+ color: '#6B7280',
+ },
+ signUpButtonTextBold: {
+ fontWeight: '600',
+ color: '#3B82F6',
+ },
+ footer: {
+ marginTop: 32,
+ alignItems: 'center',
+ },
+ footerText: {
+ fontSize: 12,
+ color: '#9CA3AF',
+ textAlign: 'center',
+ lineHeight: 16,
+ },
+});
\ No newline at end of file
diff --git a/apps/mobile/src/lib/api.ts b/apps/mobile/src/lib/api.ts
new file mode 100644
index 0000000..12dbf0f
--- /dev/null
+++ b/apps/mobile/src/lib/api.ts
@@ -0,0 +1,46 @@
+import { apiContract } from '@dropit/contract';
+import { initClient } from '@ts-rest/core';
+import AsyncStorage from '@react-native-async-storage/async-storage';
+
+// Client API pour React Native avec gestion du Bearer token
+export const api = initClient(apiContract, {
+ baseUrl: 'http://192.168.1.147:3000/api',
+ // Configuration pour React Native
+ // biome-ignore lint/suspicious/noExplicitAny: Better Auth type compatibility
+ api: async (args: any) => {
+ try {
+ // Récupération du token depuis AsyncStorage
+ const authData = await AsyncStorage.getItem('better-auth.session-token');
+ const token = authData ? JSON.parse(authData) : null;
+
+ // Configuration des headers avec Bearer token
+ const headers: Record = {
+ 'Content-Type': 'application/json',
+ };
+
+ if (token) {
+ headers.Authorization = `Bearer ${token}`;
+ }
+
+ // Effectuer la requête
+ const response = await fetch(args.path, {
+ method: args.method,
+ headers,
+ body: args.body ? JSON.stringify(args.body) : undefined,
+ });
+
+ return {
+ status: response.status,
+ body: await response.text(),
+ headers: response.headers,
+ };
+ } catch (error) {
+ console.error('API Request Error:', error);
+ return {
+ status: 500,
+ body: JSON.stringify({ error: 'Network error' }),
+ headers: new Headers(),
+ };
+ }
+ },
+});
\ No newline at end of file
diff --git a/apps/mobile/src/lib/auth-client.ts b/apps/mobile/src/lib/auth-client.ts
new file mode 100644
index 0000000..eaeda61
--- /dev/null
+++ b/apps/mobile/src/lib/auth-client.ts
@@ -0,0 +1,50 @@
+import { createAuthClient } from 'better-auth/react';
+import { organizationClient } from 'better-auth/client/plugins';
+import { ac, owner, admin, member } from '@dropit/permissions';
+import AsyncStorage from '@react-native-async-storage/async-storage';
+
+// Configuration du client d'authentification pour React Native
+export const authClient = createAuthClient({
+ baseURL: 'http://192.168.1.147:3000', // IP locale pour mobile
+ plugins: [
+ organizationClient({
+ // biome-ignore lint/suspicious/noExplicitAny: Better Auth type compatibility
+ ac: ac as any,
+ roles: {
+ owner,
+ admin,
+ member,
+ },
+ }),
+ ],
+ // Configuration pour React Native avec AsyncStorage
+ storage: {
+ get: async (key: string) => {
+ try {
+ const value = await AsyncStorage.getItem(key);
+ return value ? JSON.parse(value) : null;
+ } catch {
+ return null;
+ }
+ },
+ // biome-ignore lint/suspicious/noExplicitAny: Better Auth type compatibility
+ set: async (key: string, value: any) => {
+ try {
+ await AsyncStorage.setItem(key, JSON.stringify(value));
+ } catch {
+ // Ignore storage errors
+ }
+ },
+ remove: async (key: string) => {
+ try {
+ await AsyncStorage.removeItem(key);
+ } catch {
+ // Ignore storage errors
+ }
+ },
+ },
+ // Support du Bearer token pour mobile
+ fetchOptions: {
+ credentials: 'include',
+ },
+});
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c2e355c..dff7de5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -168,6 +168,15 @@ importers:
'@dropit/schemas':
specifier: workspace:*
version: link:../../packages/schemas
+ '@react-native-async-storage/async-storage':
+ specifier: ^2.1.0
+ version: 2.2.0(react-native@0.79.5)
+ '@ts-rest/core':
+ specifier: ^3.51.0
+ version: 3.52.1(@types/node@20.17.30)(zod@3.24.2)
+ better-auth:
+ specifier: ^1.2.7
+ version: 1.2.7
expo:
specifier: ~53.0.20
version: 53.0.20(@babel/core@7.26.10)(react-native@0.79.5)(react@19.0.0)
@@ -4345,6 +4354,15 @@ packages:
resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
dev: false
+ /@react-native-async-storage/async-storage@2.2.0(react-native@0.79.5):
+ resolution: {integrity: sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==}
+ peerDependencies:
+ react-native: ^0.0.0-0 || >=0.65 <1.0
+ dependencies:
+ merge-options: 3.0.4
+ react-native: 0.79.5(@babel/core@7.26.10)(@types/react@19.1.10)(react@19.0.0)
+ dev: false
+
/@react-native/assets-registry@0.79.5:
resolution: {integrity: sha512-N4Kt1cKxO5zgM/BLiyzuuDNquZPiIgfktEQ6TqJ/4nKA8zr4e8KJgU6Tb2eleihDO4E24HmkvGc73naybKRz/w==}
engines: {node: '>=18'}
@@ -8093,6 +8111,11 @@ packages:
engines: {node: '>=8'}
dev: false
+ /is-plain-obj@2.1.0:
+ resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==}
+ engines: {node: '>=8'}
+ dev: false
+
/is-stream@2.0.1:
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
engines: {node: '>=8'}
@@ -9125,6 +9148,13 @@ packages:
/merge-descriptors@1.0.3:
resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
+ /merge-options@3.0.4:
+ resolution: {integrity: sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==}
+ engines: {node: '>=10'}
+ dependencies:
+ is-plain-obj: 2.1.0
+ dev: false
+
/merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}