SearchView: Removed the dynamic collection-tile grid (e.g. "پرفروشهای این هفته") and the "نتایج / N محصول" section header from the UI. Underlying data/filtering logic untouched.
DashboardView ("میز کار"): Rebuilt from scratch with a new structure, distinct from the old card+grid layout but visually aligned with HomeView (same blurred bar, @ink/@wine palette, staged reveal animation on mount):
Single hero card (ink→wine gradient) showing balance, gold/fiat debt, and identity — no quick-action icons inside it anymore.
Live gold rate folded into a slim inline row at the bottom instead of a titled section.
Iterated shadow/spacing on the hero card per feedback (softer, less clipped).
DialogVant / DialogVantOneButton (shared modal components, used in 7+ call sites: Dashboard, Cart, SingleProduct, Register, App): Replaced Vant's default dialog chrome with the app's own rounded-card language — circular icon badge for the illustration, pill-shaped primary/secondary buttons. No call-site markup changed.
LoginView: Rebuilt from scratch — full-bleed dark hero (brand + step copy) overlapped by a floating white form sheet, animated crossfade transition between the mobile-number and OTP steps, same staged entrance animation as Home/Dashboard. All auth logic (API check, OTP stub, session login) unchanged.
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
import type { CreateOrderDto, CustomerBalanceDto } from '@gold-bank/shared-types';
|
||||
|
||||
const BASE = '/api';
|
||||
|
||||
async function request<T>(path: string, options?: RequestInit): Promise<T> {
|
||||
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 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;
|
||||
}
|
||||
|
||||
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[];
|
||||
images: Array<{ url: string }>;
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
export const api = {
|
||||
listProducts: () => request<ProductSummary[]>('/catalog/products'),
|
||||
listCustomers: () => request<CustomerSummary[]>('/customers'),
|
||||
getCustomer: (id: string) => request<CustomerDetail>(`/customers/${id}`),
|
||||
getBalance: (customerId: string) => request<CustomerBalanceDto>(`/accounting/customers/${customerId}/balance`),
|
||||
getLedgerEntries: (customerId: string) => request<LedgerEntrySummary[]>(`/customers/${customerId}/ledger-entries`),
|
||||
createOrder: (dto: CreateOrderDto) => request<OrderSummary>('/orders', { method: 'POST', body: JSON.stringify(dto) }),
|
||||
getCustomerOrders: (customerId: string) => request<OrderDetail[]>(`/customers/${customerId}/orders`),
|
||||
getLatestGoldPrice: (karatPerMille = 750) => request<GoldPriceQuote>(`/pricing/latest?karatPerMille=${karatPerMille}`),
|
||||
};
|
||||
Reference in New Issue
Block a user