import { extractNumericString, normalizePhone } from '@/utils/format' import { apiRequest, ApiError, getAccessToken, setAccessToken } from './http' import type { User } from '@/types' const USER_KEY = 'ft_user' const PENDING_OTP_KEY = 'ft_pending_otp' type PendingOtp = { phone: string isRegister: boolean firstName?: string lastName?: string referrerAgentCode?: string } let memoryPending: PendingOtp | null = null function cacheUser(user: User | null): void { if (user) localStorage.setItem(USER_KEY, JSON.stringify(user)) else localStorage.removeItem(USER_KEY) } export function getCachedUser(): User | null { try { const raw = localStorage.getItem(USER_KEY) if (!raw) return null return JSON.parse(raw) as User } catch { return null } } export function getCurrentUser(): User | null { if (!getAccessToken()) return null return getCachedUser() } export function isAuthenticated(): boolean { return !!getAccessToken() } function setPendingOtp(value: PendingOtp | null) { memoryPending = value try { if (value) sessionStorage.setItem(PENDING_OTP_KEY, JSON.stringify(value)) else sessionStorage.removeItem(PENDING_OTP_KEY) } catch { // ignore } } export async function sendOtp( phone: string, options: { firstName?: string lastName?: string isRegister: boolean referrerAgentCode?: string }, ): Promise<{ success: boolean; message: string }> { const normalized = normalizePhone(phone) if (!/^09\d{9}$/.test(normalized)) { return { success: false, message: 'شماره موبایل معتبر نیست.' } } if (options.isRegister && (!options.firstName?.trim() || !options.lastName?.trim())) { return { success: false, message: 'نام و نام خانوادگی الزامی است.' } } try { const referrerAgentCode = options.referrerAgentCode?.trim() || undefined setPendingOtp({ phone: normalized, isRegister: options.isRegister, firstName: options.firstName, lastName: options.lastName, referrerAgentCode, }) const res = await apiRequest<{ success: boolean; message: string; debugCode?: string }>( '/auth/otp/request', { method: 'POST', auth: false, body: { phone: normalized, firstName: options.firstName, lastName: options.lastName, isRegister: options.isRegister, referrerAgentCode, }, }, ) const hint = res.debugCode ? ` (کد: ${res.debugCode})` : '' return { success: true, message: `${res.message}${hint}` } } catch (e) { return { success: false, message: e instanceof ApiError ? e.message : 'ارسال کد ناموفق بود.', } } } export async function verifyOtp( phone: string, code: string, ): Promise<{ success: boolean; message: string; user?: User }> { const normalized = normalizePhone(phone) try { const res = await apiRequest<{ accessToken: string; user: User }>('/auth/otp/verify', { method: 'POST', auth: false, body: { phone: normalized, code: extractNumericString(code) }, }) setAccessToken(res.accessToken) cacheUser(res.user) setPendingOtp(null) return { success: true, message: 'ورود موفق.', user: res.user } } catch (e) { return { success: false, message: e instanceof ApiError ? e.message : 'تأیید کد ناموفق بود.', } } } export function logout(): void { setAccessToken(null) cacheUser(null) setPendingOtp(null) } export async function fetchMe(): Promise { const token = getAccessToken() if (!token) { cacheUser(null) return null } try { const me = await apiRequest('/auth/me') cacheUser(me) return me } catch { cacheUser(null) return null } } export async function updateProfile( data: Partial>, ): Promise { try { const updated = await apiRequest('/auth/profile', { method: 'PATCH', body: data, }) cacheUser(updated) return updated } catch (e) { const avatarOnly = data.avatarUrl !== undefined && data.firstName === undefined && data.lastName === undefined && data.nationalId === undefined && data.address === undefined const current = getCachedUser() if (!avatarOnly || !current) throw e const merged: User = { ...current } if (data.avatarUrl) merged.avatarUrl = data.avatarUrl else delete merged.avatarUrl cacheUser(merged) return merged } }