Files
fabric_trade_user/src/services/http.ts
T
safavimohadeseh78andCursor 72cc6000f1 Improve auth, checkout, and API client for real backend.
Wire OTP and cart/catalog to the API with Vite proxy, fix resend/register name persistence, harden order submit errors, and polish OTP and cart footer UI.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-08 11:50:18 +02:00

124 lines
3.8 KiB
TypeScript

const TOKEN_KEY = 'ft_access_token'
export function getApiBaseUrl(): string {
return (import.meta.env.VITE_API_BASE_URL as string | undefined)?.replace(/\/$/, '')
|| 'https://parche.montech.ir/server/api'
}
export function getAccessToken(): string | null {
return localStorage.getItem(TOKEN_KEY)
}
export function setAccessToken(token: string | null): void {
if (token) localStorage.setItem(TOKEN_KEY, token)
else localStorage.removeItem(TOKEN_KEY)
}
export class ApiError extends Error {
status: number
constructor(status: number, message: string) {
super(message)
this.status = status
}
}
type RequestOptions = {
method?: string
body?: unknown
auth?: boolean
query?: Record<string, string | number | boolean | undefined | null>
}
function buildUrl(path: string, query?: RequestOptions['query']): string {
const base = getApiBaseUrl()
const joined = path.startsWith('http')
? path
: `${base.replace(/\/$/, '')}/${path.replace(/^\//, '')}`
const url = joined.startsWith('http')
? new URL(joined)
: new URL(joined, typeof window !== 'undefined' ? window.location.origin : 'http://127.0.0.1')
if (query) {
for (const [key, value] of Object.entries(query)) {
if (value === undefined || value === null || value === '') continue
url.searchParams.set(key, String(value))
}
}
return url.toString()
}
function errorMessageFromBody(data: unknown, status: number): string {
if (typeof data === 'object' && data) {
if ('message' in data) {
const message = (data as { message: unknown }).message
if (Array.isArray(message)) return message.join('، ')
if (message != null && String(message).trim()) return String(message)
}
if ('error' in data) {
const error = (data as { error: unknown }).error
if (typeof error === 'string' && error.trim()) return `${error} (${status})`
}
}
if (typeof data === 'string') {
if (/upstream error/i.test(data)) {
return 'سرور API در دسترس نیست. کمی بعد دوباره تلاش کنید.'
}
if (data.trim() && !data.trimStart().startsWith('<')) {
return data.trim().slice(0, 200)
}
}
if (status === 400) return 'درخواست نامعتبر است. اطلاعات سفارش را بررسی کنید.'
if (status === 401) return 'نشست شما منقضی شده است. دوباره وارد شوید.'
return `خطای سرور (${status})`
}
export async function apiRequest<T>(path: string, options: RequestOptions = {}): Promise<T> {
const headers: Record<string, string> = {
Accept: 'application/json',
}
if (options.body !== undefined) {
headers['Content-Type'] = 'application/json'
}
if (options.auth !== false) {
const token = getAccessToken()
if (token) headers.Authorization = `Bearer ${token}`
}
let res: Response
try {
res = await fetch(buildUrl(path, options.query), {
method: options.method ?? (options.body !== undefined ? 'POST' : 'GET'),
headers,
body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
signal: AbortSignal.timeout(20_000),
})
} catch (err) {
const timedOut =
(err instanceof DOMException && err.name === 'TimeoutError') ||
(err instanceof Error && err.name === 'TimeoutError')
throw new ApiError(
0,
timedOut
? 'پاسخ سرور طول کشید. دوباره تلاش کنید.'
: 'اتصال به سرور برقرار نشد. اینترنت یا آدرس API را بررسی کنید.',
)
}
const text = await res.text()
let data: unknown = null
if (text) {
try {
data = JSON.parse(text)
} catch {
data = text
}
}
if (!res.ok) {
throw new ApiError(res.status, errorMessageFromBody(data, res.status))
}
return data as T
}