Files
edr-platform/apps/edr-passenger-web/portal/src/lib/auth-store.ts

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 });
}
},
})
);