feat: add expo mobile application source code

This commit is contained in:
2026-05-21 16:06:35 +07:00
parent 76d7a5c5c6
commit 0c65a7811b
77 changed files with 20356 additions and 0 deletions
+165
View File
@@ -0,0 +1,165 @@
import React, { createContext, useContext, useState, useEffect } from 'react';
import * as Haptics from 'expo-haptics';
import * as LocalAuthentication from 'expo-local-authentication';
import { storage } from '../utils/storage';
import { Platform } from 'react-native';
import { router } from 'expo-router';
import { ApiService } from '../services/api';
import { DebugLogger } from '../utils/logger';
interface User {
id: string;
name: string;
email: string;
avatar?: string;
}
interface AuthContextType {
user: User | null;
isLoading: boolean;
signIn: (email: string, pass: string) => Promise<void>;
signOut: () => void;
signUp: (name: string, email: string, pass: string) => Promise<void>;
updateProfile: (name: string, email: string) => Promise<void>;
syncUser: () => Promise<void>;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
// Storage helper is now imported from utils/storage
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
loadStoredToken();
}, []);
const loadStoredToken = async () => {
try {
const token = await storage.get('user_token');
const storedUser = await storage.get('user_data');
if (token && storedUser) {
setUser(JSON.parse(storedUser));
}
} catch (e) {
console.warn('Authentication failed during boot:', e);
await storage.remove('user_token');
await storage.remove('user_data');
setUser(null);
} finally {
setIsLoading(false);
}
};
const signIn = async (email: string, pass: string) => {
setIsLoading(true);
try {
const response = await ApiService.login(email, pass);
const userData = response.data.user;
const token = response.data.token;
setUser(userData);
await storage.save('user_token', token);
await storage.save('user_data', JSON.stringify(userData));
await storage.save('saved_email', email);
await storage.save('saved_pass', pass);
DebugLogger.log(`User signed in: ${email}`, 'info');
if (Platform.OS !== 'web') Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
} catch (error: any) {
DebugLogger.log(`Login failed for ${email}: ${error.message}`, 'error');
if (Platform.OS !== 'web') Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
throw error;
} finally {
setIsLoading(false);
}
};
const signUp = async (name: string, email: string, pass: string) => {
setIsLoading(true);
try {
const response = await ApiService.register(name, email, pass);
const userData = response.data.user;
const token = response.data.token;
setUser(userData);
await storage.save('user_token', token);
await storage.save('user_data', JSON.stringify(userData));
await storage.save('saved_email', email);
await storage.save('saved_pass', pass);
if (Platform.OS !== 'web') Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
} catch (error) {
if (Platform.OS !== 'web') Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
throw error;
} finally {
setIsLoading(false);
}
};
const updateProfile = async (name: string, email: string) => {
setIsLoading(true);
try {
const response = await ApiService.updateProfile(name, email);
const userData = response.data.user;
setUser(userData);
await storage.save('user_data', JSON.stringify(userData));
if (Platform.OS !== 'web') Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
} catch (error) {
if (Platform.OS !== 'web') Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
throw error;
} finally {
setIsLoading(false);
}
};
const signOut = async () => {
setUser(null);
await storage.remove('user_token');
await storage.remove('user_data');
if (Platform.OS !== 'web') Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
DebugLogger.log('User signed out', 'info');
router.replace('/(auth)/login');
};
const syncUser = async () => {
if (!user) return;
try {
const response = await ApiService.getUser();
const userData = response.data.user;
setUser(userData);
await storage.save('user_data', JSON.stringify(userData));
} catch (error: any) {
DebugLogger.log(`Sync failed: ${error.message}`, 'error');
// If it's a 401, we might want to sign out, but for now let's be silent
// to avoid the "Global refresh failed" loop that annoys the user.
if (error.message.includes('Unauthenticated')) {
console.warn('Silent sync failure: User is unauthenticated but staying in app.');
} else {
throw error;
}
}
};
return (
<AuthContext.Provider value={{ user, isLoading, signIn, signOut, signUp, updateProfile, syncUser }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (context === undefined) {
return {
user: null,
isLoading: false,
signIn: async () => {},
signOut: () => {},
signUp: async () => {},
updateProfile: async () => {},
syncUser: async () => {}
} as any;
}
return context;
}