200 lines
5.8 KiB
TypeScript
200 lines
5.8 KiB
TypeScript
import api from './api';
|
|
|
|
export interface User {
|
|
id: string;
|
|
firstName: string;
|
|
lastName: string;
|
|
email: string;
|
|
mobile: string | null;
|
|
role: string;
|
|
walletBalance: number;
|
|
charityDonationTotal: number;
|
|
hasPassword?: boolean;
|
|
password?: string;
|
|
currentPassword?: string;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
pets?: unknown[];
|
|
orders?: unknown[];
|
|
}
|
|
|
|
export interface AuthResponse {
|
|
success: boolean;
|
|
data: {
|
|
user: User;
|
|
accessToken: string;
|
|
refreshToken?: string;
|
|
};
|
|
}
|
|
|
|
interface ApiErr {
|
|
response?: {
|
|
status?: number;
|
|
data?: {
|
|
message?: string | string[];
|
|
};
|
|
};
|
|
}
|
|
|
|
export class AuthService {
|
|
private static instance: AuthService;
|
|
|
|
private constructor() {}
|
|
|
|
public static getInstance(): AuthService {
|
|
if (!AuthService.instance) {
|
|
AuthService.instance = new AuthService();
|
|
}
|
|
return AuthService.instance;
|
|
}
|
|
|
|
/**
|
|
* Send SMS verification code (OTP)
|
|
*/
|
|
public async sendOtp(phoneNumber: string): Promise<{ success: boolean; message: string }> {
|
|
try {
|
|
const response = await api.post('/auth/send-otp', { phoneNumber });
|
|
return response.data;
|
|
} catch (error) {
|
|
const err = error as ApiErr;
|
|
const message = err.response?.data?.message || 'خطا در ارسال کد تایید';
|
|
throw new Error(Array.isArray(message) ? message[0] : message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verify SMS verification code and login/register
|
|
*/
|
|
public async verifyOtp(phoneNumber: string, code: string): Promise<AuthResponse> {
|
|
try {
|
|
const response = await api.post('/auth/verify-otp', { phoneNumber, code });
|
|
const { data } = response.data;
|
|
if (data?.accessToken) {
|
|
localStorage.setItem('accessToken', data.accessToken);
|
|
}
|
|
if (data?.refreshToken) {
|
|
localStorage.setItem('refreshToken', data.refreshToken);
|
|
}
|
|
return response.data;
|
|
} catch (error) {
|
|
const err = error as ApiErr;
|
|
const message = err.response?.data?.message || 'کد تایید نامعتبر است';
|
|
throw new Error(Array.isArray(message) ? message[0] : message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register with email/mobile and password
|
|
*/
|
|
public async register(data: Record<string, unknown>): Promise<AuthResponse> {
|
|
try {
|
|
const response = await api.post('/auth/register', data);
|
|
const { data: resData } = response.data;
|
|
if (resData?.accessToken) {
|
|
localStorage.setItem('accessToken', resData.accessToken);
|
|
}
|
|
if (resData?.refreshToken) {
|
|
localStorage.setItem('refreshToken', resData.refreshToken);
|
|
}
|
|
return response.data;
|
|
} catch (error) {
|
|
const err = error as ApiErr;
|
|
const message = err.response?.data?.message || 'خطا در ثبتنام';
|
|
throw new Error(Array.isArray(message) ? message[0] : message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Login with mobile and password
|
|
*/
|
|
public async login(mobile: string, password: string): Promise<AuthResponse> {
|
|
try {
|
|
const response = await api.post('/auth/login', { mobile, password });
|
|
const { data } = response.data;
|
|
if (data?.accessToken) {
|
|
localStorage.setItem('accessToken', data.accessToken);
|
|
}
|
|
if (data?.refreshToken) {
|
|
localStorage.setItem('refreshToken', data.refreshToken);
|
|
}
|
|
return response.data;
|
|
} catch (error) {
|
|
const err = error as ApiErr;
|
|
const message = err.response?.data?.message || 'نام کاربری یا رمز عبور اشتباه است';
|
|
throw new Error(Array.isArray(message) ? message[0] : message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Refresh auth tokens using the stored refresh token
|
|
*/
|
|
public async refreshAuthToken(): Promise<string | null> {
|
|
try {
|
|
const refreshToken = typeof window !== 'undefined' ? localStorage.getItem('refreshToken') : null;
|
|
if (!refreshToken) return null;
|
|
|
|
const response = await api.post('/auth/refresh-token', { refreshToken }, { hideErrorToast: true } as unknown as import('axios').AxiosRequestConfig);
|
|
const { data } = response.data;
|
|
if (data?.accessToken) {
|
|
localStorage.setItem('accessToken', data.accessToken);
|
|
}
|
|
if (data?.refreshToken) {
|
|
localStorage.setItem('refreshToken', data.refreshToken);
|
|
}
|
|
return data?.accessToken || null;
|
|
} catch {
|
|
this.logout();
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get current user profile
|
|
*/
|
|
public async getProfile(): Promise<User | null> {
|
|
try {
|
|
const response = await api.get('/users/profile', { hideErrorToast: true } as unknown as import('axios').AxiosRequestConfig);
|
|
return response.data;
|
|
} catch (error) {
|
|
const err = error as ApiErr;
|
|
if (err.response?.status === 401 || err.response?.status === 403) {
|
|
return null;
|
|
}
|
|
const message = err.response?.data?.message || 'خطا در دریافت اطلاعات کاربری';
|
|
throw new Error(Array.isArray(message) ? message[0] : message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update current user profile
|
|
*/
|
|
public async updateProfile(profileData: Partial<User>): Promise<User> {
|
|
try {
|
|
const response = await api.patch('/users/profile', profileData);
|
|
return response.data;
|
|
} catch (error) {
|
|
const err = error as ApiErr;
|
|
const message = err.response?.data?.message || 'خطا در ویرایش اطلاعات کاربری';
|
|
throw new Error(Array.isArray(message) ? message[0] : message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Logout user and clear tokens
|
|
*/
|
|
public logout(): void {
|
|
if (typeof window !== 'undefined') {
|
|
try {
|
|
const token = localStorage.getItem('accessToken');
|
|
if (token) {
|
|
api.post('/auth/logout').catch(() => {});
|
|
}
|
|
} catch {}
|
|
localStorage.removeItem('accessToken');
|
|
localStorage.removeItem('refreshToken');
|
|
}
|
|
}
|
|
}
|
|
|
|
export const authService = AuthService.getInstance();
|