- 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
220 lines
5.4 KiB
TypeScript
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);
|