mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 02:58:11 +00:00
191 lines
6.3 KiB
TypeScript
191 lines
6.3 KiB
TypeScript
import { create } from 'zustand';
|
|
import { apiClient } from './api-client';
|
|
|
|
interface User {
|
|
id: string;
|
|
email: string;
|
|
fullName: string;
|
|
phone?: string;
|
|
role: string;
|
|
passengerId?: string;
|
|
dateOfBirth?: string;
|
|
gender?: string;
|
|
nationality?: string;
|
|
nationalityCode?: string;
|
|
nationalId?: string;
|
|
passportNumber?: string;
|
|
passportCountry?: string;
|
|
passportIssueDate?: string;
|
|
passportExpiryDate?: string;
|
|
passportIssuingAuthority?: string;
|
|
faydaVerified?: boolean;
|
|
faydaSub?: string;
|
|
faydaVerifiedAt?: string;
|
|
lastLoginAt?: string;
|
|
createdAt?: string;
|
|
}
|
|
|
|
interface AuthState {
|
|
user: User | null;
|
|
token: string | null;
|
|
isAuthenticated: boolean;
|
|
isInitialized: boolean;
|
|
login: (email: string, password: string) => Promise<void>;
|
|
register: (data: RegisterData) => Promise<RegisterResult>;
|
|
logout: () => Promise<void>;
|
|
setUser: (user: User, token: string) => void;
|
|
updateUser: (userData: Partial<User>) => void;
|
|
initialize: () => Promise<void>;
|
|
fetchProfile: () => Promise<void>;
|
|
}
|
|
|
|
interface RegisterData {
|
|
fullName: string;
|
|
email: string;
|
|
phone: string;
|
|
}
|
|
|
|
interface RegisterResult {
|
|
iamUserId: string;
|
|
email: string;
|
|
phoneNumber: string;
|
|
}
|
|
|
|
export const useAuthStore = create<AuthState>((set, get) => ({
|
|
user: null,
|
|
token: null,
|
|
isAuthenticated: false,
|
|
isInitialized: false,
|
|
|
|
initialize: async () => {
|
|
if (typeof window === 'undefined') return;
|
|
const token = localStorage.getItem('auth_token');
|
|
const userStr = localStorage.getItem('auth_user');
|
|
|
|
if (token && userStr) {
|
|
try {
|
|
const user = JSON.parse(userStr);
|
|
set({ user, token, isAuthenticated: true, isInitialized: true });
|
|
|
|
// Fetch fresh profile data in background
|
|
get().fetchProfile().catch(() => {
|
|
// If profile fetch fails, token might be expired
|
|
console.warn('Failed to fetch profile, token might be expired');
|
|
});
|
|
} catch (e) {
|
|
localStorage.removeItem('auth_token');
|
|
localStorage.removeItem('auth_user');
|
|
set({ isInitialized: true });
|
|
}
|
|
} else {
|
|
set({ isInitialized: true });
|
|
}
|
|
},
|
|
|
|
fetchProfile: async () => {
|
|
if (typeof window === 'undefined') return;
|
|
const token = localStorage.getItem('auth_token');
|
|
if (!token) return;
|
|
|
|
try {
|
|
const response: any = await apiClient.get('/auth/profile', {
|
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
});
|
|
const userData = response.data || response;
|
|
|
|
if (typeof window !== 'undefined') {
|
|
localStorage.setItem('auth_user', JSON.stringify(userData));
|
|
}
|
|
set({ user: userData });
|
|
} catch (error: any) {
|
|
// If 401, token is invalid - logout
|
|
if (error.response?.status === 401) {
|
|
if (typeof window !== 'undefined') {
|
|
localStorage.removeItem('auth_token');
|
|
localStorage.removeItem('auth_user');
|
|
}
|
|
set({ user: null, token: null, isAuthenticated: false });
|
|
}
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
login: async (email: string, password: string) => {
|
|
const response: any = await apiClient.post('/auth/login', { email, password });
|
|
const { token, user } = response.data || response;
|
|
// `setUser` is the one place a session is persisted. The staged sign-in's
|
|
// password-setup branch establishes a session without going through /auth/login,
|
|
// so it calls the same action rather than duplicating the storage writes.
|
|
get().setUser(user, token);
|
|
},
|
|
|
|
register: async (data: RegisterData): Promise<RegisterResult> => {
|
|
// Shape required by the passenger-api RegisterDto; username = email by convention.
|
|
// Registration no longer takes a password — the account is created as pending and
|
|
// an SMS verification code is sent. The user completes signup on the verify-account
|
|
// page (set-password). No token is issued here; the user is NOT logged in yet.
|
|
const payload = {
|
|
email: data.email,
|
|
username: data.email,
|
|
phoneNumber: data.phone,
|
|
name: { en: data.fullName, am: data.fullName },
|
|
};
|
|
const response: any = await apiClient.post('/auth/register', payload);
|
|
const result = response.data || response;
|
|
return {
|
|
iamUserId: result.iamUserId,
|
|
email: result.email,
|
|
phoneNumber: result.phoneNumber,
|
|
};
|
|
},
|
|
|
|
logout: async () => {
|
|
try {
|
|
const token = typeof window !== 'undefined' ? localStorage.getItem('auth_token') : null;
|
|
if (token) {
|
|
// Call logout endpoint to invalidate session on backend
|
|
await apiClient.post('/auth/logout', {}, {
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('Logout API call failed:', error);
|
|
// Continue with logout even if API call fails
|
|
}
|
|
|
|
// Clear local storage and state
|
|
if (typeof window !== 'undefined') {
|
|
localStorage.removeItem('auth_token');
|
|
localStorage.removeItem('auth_user');
|
|
}
|
|
set({ user: null, token: null, isAuthenticated: false });
|
|
|
|
// Redirect to home page after state is updated
|
|
if (typeof window !== 'undefined') {
|
|
setTimeout(() => {
|
|
window.location.href = '/';
|
|
}, 100);
|
|
}
|
|
},
|
|
|
|
setUser: (user: User, token: string) => {
|
|
if (typeof window !== 'undefined') {
|
|
localStorage.setItem('auth_token', token);
|
|
localStorage.setItem('auth_user', JSON.stringify(user));
|
|
}
|
|
set({ user, token, isAuthenticated: true });
|
|
},
|
|
|
|
updateUser: (userData: Partial<User>) => {
|
|
if (typeof window !== 'undefined') {
|
|
const currentUser = JSON.parse(localStorage.getItem('auth_user') || '{}');
|
|
const updatedUser = { ...currentUser, ...userData };
|
|
localStorage.setItem('auth_user', JSON.stringify(updatedUser));
|
|
set({ user: updatedUser });
|
|
}
|
|
},
|
|
})
|
|
);
|