RTOPP / frontend /src /api /client.ts
vinay-pepakayala's picture
Upload 84 files
a247911 verified
Raw
History Blame Contribute Delete
3.73 kB
import type {
CreateOrderRequest,
ForgotPasswordRequest,
LoginRequest,
LoginResponse,
Order,
OrderStatusUpdateRequest,
Product,
RegisterRequest
} from '../types/domain';
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? '';
export class ApiError extends Error {
constructor(
message: string,
public readonly status: number,
public readonly payload?: unknown
) {
super(message);
this.name = 'ApiError';
}
}
interface RequestOptions extends Omit<RequestInit, 'body'> {
token?: string | null;
body?: unknown;
}
function url(path: string) {
return `${API_BASE_URL}${path}`;
}
async function parseResponse(response: Response) {
const contentType = response.headers.get('content-type') ?? '';
if (response.status === 204) {
return undefined;
}
if (contentType.includes('application/json')) {
return response.json();
}
return response.text();
}
async function request<T>(path: string, options: RequestOptions = {}): Promise<T> {
const headers = new Headers(options.headers);
if (options.body !== undefined && !headers.has('Content-Type')) {
headers.set('Content-Type', 'application/json');
}
if (options.token) {
headers.set('Authorization', `Bearer ${options.token}`);
}
const response = await fetch(url(path), {
...options,
headers,
body: options.body !== undefined ? JSON.stringify(options.body) : undefined
});
const payload = await parseResponse(response);
if (!response.ok) {
const message =
typeof payload === 'string'
? payload
: payload && typeof payload === 'object' && 'message' in payload
? String((payload as { message: unknown }).message)
: `Request failed with status ${response.status}`;
throw new ApiError(message, response.status, payload);
}
return payload as T;
}
export const api = {
login: (body: LoginRequest) => request<LoginResponse>('/auth/login', { method: 'POST', body }),
register: (body: RegisterRequest) => request('/auth/register', { method: 'POST', body }),
forgotPassword: (body: ForgotPasswordRequest) =>
request<string>('/auth/forgot-password', { method: 'POST', body }),
getProducts: (token: string, activeOnly = false) =>
request<Product[]>(activeOnly ? '/products/active' : '/products', { token }),
createProduct: (token: string, body: Product) => request<Product>('/products', { method: 'POST', token, body }),
updateProduct: (token: string, pid: number, body: Product) =>
request<Product>(`/products/${pid}`, { method: 'PUT', token, body }),
deleteProduct: (token: string, pid: number) => request<void>(`/products/${pid}`, { method: 'DELETE', token }),
addStock: (token: string, pid: number, quantity: number, async = false) =>
request<Product | string>(`/products/${pid}/add-stock${async ? '/async' : ''}`, {
method: 'PATCH',
token,
body: { quantity }
}),
reserveStock: (token: string, pid: number, quantity: number, async = false) =>
request<Product | string>(`/products/${pid}/reserve-stock${async ? '/async' : ''}`, {
method: 'PATCH',
token,
body: { quantity }
}),
createOrder: (token: string, body: CreateOrderRequest) =>
request<Order>('/orders', { method: 'POST', token, body }),
getMyOrders: (token: string) => request<Order[]>('/orders/mine', { token }),
getAllOrders: (token: string) => request<Order[]>('/orders', { token }),
updateOrderStatus: (token: string, orderId: number, body: OrderStatusUpdateRequest) =>
request<Order>(`/orders/${orderId}/status`, { method: 'PATCH', token, body })
};
export function streamUrl(admin: boolean) {
return url(admin ? '/orders/stream' : '/orders/stream/mine');
}