feat: add expo mobile application source code
This commit is contained in:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user