import type { CreateOrderDto, CustomerBalanceDto } from '@gold-bank/shared-types'; const BASE = '/api'; async function request(path: string, options?: RequestInit): Promise { const res = await fetch(`${BASE}${path}`, { headers: { 'Content-Type': 'application/json' }, ...options, }); if (!res.ok) { const body = await res.text(); throw new Error(`API ${path} failed (${res.status}): ${body}`); } return res.json(); } export interface ProductImageSummary { id: string; url: string; sortOrder: number; role: 'FEATURED' | 'FEATURED_MOBILE' | 'GALLERY' | 'GALLERY_MOBILE'; } export interface ProductVariantSummary { id: string; title: string; rashWeightGrams: string; defaultWagePercent: string; /// On-hand stock and the "orderable at zero stock" flag. Both exist on /// ProductVariant and are already returned by GET /catalog/products (the /// catalog service includes every variant field) — declared here so the UI /// can read them. qty?: number; isPreOrder?: boolean; /// The gallery image chosen for this variant in the admin panel. It is one of /// the product's OWN images — the same ProductImage row, not a copy — so /// several variants (e.g. different weights of one design) may share it. /// Null when no picture was picked; callers fall back to the product image. imageId?: string | null; image?: ProductImageSummary | null; } export interface CategorySummary { id: string; title: string; parentId: string | null; } export interface BrandSummary { id: string; title: string; } export interface CollectionSummary { id: string; title: string; description: string | null; isActive: boolean; } export interface ProductSummary { id: string; title: string; description: string | null; /// Legacy Product.product_code — the human-facing code. Already returned by /// GET /catalog/products. productCode?: string | null; variants: ProductVariantSummary[]; /// Returned sorted by sortOrder, so images[0] really is the product's main /// photo rather than whichever row the database happened to hand back. images: ProductImageSummary[]; category: CategorySummary | null; brand: BrandSummary | null; collections: Array<{ collectionId: string; collection: CollectionSummary }>; } export interface LedgerEntrySummary { id: string; currency: string; direction: string; sourceType: string; amount: string; confirmed: boolean; createdAt: string; note: string | null; } export interface CustomerSummary { id: string; fullName: string; mobile: string; goldDebtGrams: string; fiatDebtRial: string; goldCreditLimitGrams: string; isActive: boolean; isArchived: boolean; } export interface OrderSummary { id: string; orderNumber: string; status: string; totalGoldWeightGrams: string; isSuspendedOverCreditLimit: boolean; } export interface OrderItemDetail { id: string; productVariantId: string; quantity: number; rashWeightGramsSnapshot: string; wagePercentSnapshot: string; discountPercentSnapshot: string; computedWeightGrams: string; flatRialCharge: string; } export interface OrderDetail { id: string; orderNumber: string; status: string; totalGoldWeightGrams: string; wageWeightGrams: string; flatRialCharge: string; isSuspendedOverCreditLimit: boolean; deliveredAt: string | null; createdAt: string; items: OrderItemDetail[]; } export interface CustomerDetail { id: string; fullName: string; mobile: string; nationalCode: string; cityId: string | null; goldCreditLimitGrams: string; goldDebtGrams: string; fiatDebtRial: string; verificationStep: string; createdAt: string; } export interface GoldPriceQuote { id: string; karatPerMille: number; pricePerGramRial: string; source: string; quotedAt: string; } /** One admin-uploaded home banner slide — raw shape as saved by the admin * panel's manage/Const.vue "اسلایدر موبایل" tab (image + optional link). */ export interface AdminSlide { image: string; link: string; } export const api = { listProducts: () => request('/catalog/products'), listCustomers: () => request('/customers'), getCustomer: (id: string) => request(`/customers/${id}`), getBalance: (customerId: string) => request(`/accounting/customers/${customerId}/balance`), getLedgerEntries: (customerId: string) => request(`/customers/${customerId}/ledger-entries`), createOrder: (dto: CreateOrderDto) => request('/orders', { method: 'POST', body: JSON.stringify(dto) }), getCustomerOrders: (customerId: string) => request(`/customers/${customerId}/orders`), getLatestGoldPrice: (karatPerMille = 750) => request(`/pricing/latest?karatPerMille=${karatPerMille}`), /** GET /home/slider — public, no auth. Empty array when the admin hasn't * configured any slides (the caller falls back to a derived hero). */ getHomeSlider: () => request('/home/slider'), };