morethanadiagnosis-hub/mobile/app/lib/auth-store.ts
Claude eb04163b3b
feat: implement authentication system for mobile app
- Add API integration layer with axios and token management
- Create Zustand auth store for state management
- Implement login screen with validation
- Implement signup screen with password strength indicator
- Implement forgot password flow with multi-step UI
- Update root layout with auth state protection
- Integrate profile screen with auth store
- Install AsyncStorage and expo-secure-store dependencies
2025-11-18 19:32:16 +00:00

220 lines
5.4 KiB
TypeScript

import { create } from 'zustand';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { authApi, userApi, handleApiError } from './api';
import { User, LoginRequest, SignupRequest } from './types';
// Storage keys
const USER_KEY = 'auth_user';
interface AuthState {
// State
user: User | null;
isAuthenticated: boolean;
isLoading: boolean;
isInitialized: boolean;
error: string | null;
// Actions
initialize: () => Promise<void>;
login: (data: LoginRequest) => Promise<void>;
signup: (data: SignupRequest) => Promise<void>;
logout: () => Promise<void>;
clearError: () => void;
setUser: (user: User) => void;
updateUser: (data: Partial<User>) => Promise<void>;
}
export const useAuthStore = create<AuthState>((set, get) => ({
// Initial state
user: null,
isAuthenticated: false,
isLoading: false,
isInitialized: false,
error: null,
// Initialize auth state on app start
initialize: async () => {
try {
set({ isLoading: true, error: null });
// Check for stored token
const token = await authApi.getStoredToken();
if (token) {
// Try to fetch current user
try {
const user = await userApi.getMe();
await AsyncStorage.setItem(USER_KEY, JSON.stringify(user));
set({
user,
isAuthenticated: true,
isInitialized: true,
isLoading: false,
});
} catch {
// Token invalid or expired - try to refresh
try {
await authApi.refreshToken();
const user = await userApi.getMe();
await AsyncStorage.setItem(USER_KEY, JSON.stringify(user));
set({
user,
isAuthenticated: true,
isInitialized: true,
isLoading: false,
});
} catch {
// Refresh failed - clear auth state
await authApi.logout();
await AsyncStorage.removeItem(USER_KEY);
set({
user: null,
isAuthenticated: false,
isInitialized: true,
isLoading: false,
});
}
}
} else {
// No token - check for cached user data for offline display
const cachedUser = await AsyncStorage.getItem(USER_KEY);
set({
user: cachedUser ? JSON.parse(cachedUser) : null,
isAuthenticated: false,
isInitialized: true,
isLoading: false,
});
}
} catch (error) {
set({
user: null,
isAuthenticated: false,
isInitialized: true,
isLoading: false,
error: handleApiError(error),
});
}
},
// Login
login: async (data: LoginRequest) => {
try {
set({ isLoading: true, error: null });
const response = await authApi.login(data);
const { user } = response;
// Cache user data
await AsyncStorage.setItem(USER_KEY, JSON.stringify(user));
set({
user,
isAuthenticated: true,
isLoading: false,
error: null,
});
} catch (error) {
set({
isLoading: false,
error: handleApiError(error),
});
throw error;
}
},
// Signup
signup: async (data: SignupRequest) => {
try {
set({ isLoading: true, error: null });
const response = await authApi.signup(data);
const { user } = response;
// Cache user data
await AsyncStorage.setItem(USER_KEY, JSON.stringify(user));
set({
user,
isAuthenticated: true,
isLoading: false,
error: null,
});
} catch (error) {
set({
isLoading: false,
error: handleApiError(error),
});
throw error;
}
},
// Logout
logout: async () => {
try {
set({ isLoading: true });
await authApi.logout();
await AsyncStorage.removeItem(USER_KEY);
set({
user: null,
isAuthenticated: false,
isLoading: false,
error: null,
});
} catch (error) {
// Still clear local state even if API call fails
await AsyncStorage.removeItem(USER_KEY);
set({
user: null,
isAuthenticated: false,
isLoading: false,
error: null,
});
}
},
// Clear error
clearError: () => {
set({ error: null });
},
// Set user (for updates from other parts of the app)
setUser: (user: User) => {
set({ user });
AsyncStorage.setItem(USER_KEY, JSON.stringify(user));
},
// Update user profile
updateUser: async (data: Partial<User>) => {
try {
set({ isLoading: true, error: null });
const updatedUser = await userApi.updateMe(data);
await AsyncStorage.setItem(USER_KEY, JSON.stringify(updatedUser));
set({
user: updatedUser,
isLoading: false,
});
} catch (error) {
set({
isLoading: false,
error: handleApiError(error),
});
throw error;
}
},
}));
// Selector hooks for specific state slices
export const useUser = () => useAuthStore((state) => state.user);
export const useIsAuthenticated = () => useAuthStore((state) => state.isAuthenticated);
export const useIsLoading = () => useAuthStore((state) => state.isLoading);
export const useAuthError = () => useAuthStore((state) => state.error);
export const useIsInitialized = () => useAuthStore((state) => state.isInitialized);