Implements winofyfrontenddoc (1).md §3-4: unified guest-home/authenticated-home
screen (header with login chip vs cart badge, pill search bar, dismissible
location-reminder banner, category chips, store cards, interleaved seller
promo card, sticky bottom nav), plus the full post-OTP location-onboarding
sub-flow (geolocation permission -> error/retry -> manual neighborhood
search, unified idle/results/empty states per the doc's own recommendation).
Location state persists in a plain (non-httpOnly) cookie readable both
client- and server-side, since it drives SSR store queries (neighborhood
or lat/lng) as well as client interactions.
Moved cart/checkout/store/product/orders/addresses under a new (shop) route
group so the new home-shell header doesn't double up with the existing
simple nav header on inner pages.
Real bug caught via live staging data: Neighborhood.city is a nested
{uuid, name, slug} object, not a bare uuid string as typed -- broke both
the address form's city filter and the new neighborhood search. Fixed and
verified against real API responses, not just assumed from the guide.
183 lines
3.9 KiB
TypeScript
183 lines
3.9 KiB
TypeScript
/** Shared API types mirrored from FRONTEND_GUIDE.md. Money fields are integer Toman. */
|
|
|
|
export interface City {
|
|
uuid: string;
|
|
name: string;
|
|
slug?: string;
|
|
}
|
|
|
|
export interface Neighborhood {
|
|
uuid: string;
|
|
name: string;
|
|
/** Nested object, confirmed against staging — not a bare uuid string. */
|
|
city: City;
|
|
}
|
|
|
|
export interface StoreCategory {
|
|
uuid: string;
|
|
name: string;
|
|
icon: string | null;
|
|
order: number;
|
|
}
|
|
|
|
export interface StoreListItem {
|
|
uuid: string;
|
|
name: string;
|
|
category: StoreCategory;
|
|
logo: string | null;
|
|
cover_image: string | null;
|
|
/** DRF serializes DecimalField as a string, e.g. "3.76" — confirmed against staging. */
|
|
rating_avg: string;
|
|
rating_count: number;
|
|
min_order_amount: number;
|
|
delivery_fee: number;
|
|
free_delivery_threshold: number | null;
|
|
is_open: boolean;
|
|
}
|
|
|
|
|
|
export interface WorkingHours {
|
|
weekday: 0 | 1 | 2 | 3 | 4 | 5 | 6; // 0 = Saturday .. 6 = Friday
|
|
opens_at: string;
|
|
closes_at: string;
|
|
is_closed: boolean;
|
|
}
|
|
|
|
export interface StoreDetail extends StoreListItem {
|
|
description: string;
|
|
address: string;
|
|
phone_number: string;
|
|
working_hours: WorkingHours[];
|
|
accepts_wallet: boolean;
|
|
accepts_online: boolean;
|
|
accepts_cash_on_delivery: boolean;
|
|
}
|
|
|
|
export type UnitType = "gram" | "ml" | "piece";
|
|
|
|
export interface ProductCategory {
|
|
uuid: string;
|
|
name: string;
|
|
parent: string | null;
|
|
}
|
|
|
|
export interface ProductListItem {
|
|
uuid: string;
|
|
name: string;
|
|
image: string | null;
|
|
price: number;
|
|
unit_type: UnitType;
|
|
unit_value: number;
|
|
is_active: boolean;
|
|
is_out_of_stock: boolean;
|
|
}
|
|
|
|
export interface ProductDetail extends ProductListItem {
|
|
description: string;
|
|
category: ProductCategory;
|
|
store: StoreListItem;
|
|
}
|
|
|
|
export type AddressLabel = "home" | "work" | "other";
|
|
|
|
export interface Address {
|
|
uuid: string;
|
|
label: AddressLabel;
|
|
title: string;
|
|
full_address: string;
|
|
plaque: string;
|
|
floor: string;
|
|
unit: string;
|
|
recipient_name: string;
|
|
recipient_phone: string;
|
|
city: City;
|
|
neighborhood: Neighborhood | null;
|
|
latitude: number;
|
|
longitude: number;
|
|
is_default: boolean;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface CartItem {
|
|
uuid: string;
|
|
product: ProductListItem;
|
|
quantity: number;
|
|
line_total: number;
|
|
}
|
|
|
|
export interface CartGroup {
|
|
store: StoreListItem;
|
|
items: CartItem[];
|
|
items_subtotal: number;
|
|
delivery_fee: number;
|
|
total: number;
|
|
meets_minimum_order: boolean;
|
|
}
|
|
|
|
export interface Cart {
|
|
groups: CartGroup[];
|
|
grand_total: number;
|
|
}
|
|
|
|
export type DeliveryType = "express" | "scheduled";
|
|
export type PaymentMethod = "wallet" | "online" | "cash_on_delivery";
|
|
export type PaymentStatus = "pending" | "paid" | "failed";
|
|
export type OrderStatus =
|
|
| "placed"
|
|
| "preparing"
|
|
| "ready_to_ship"
|
|
| "handed_to_courier"
|
|
| "delivered"
|
|
| "cancelled";
|
|
|
|
export interface OrderItem {
|
|
uuid: string;
|
|
product: string;
|
|
product_name_snapshot: string;
|
|
unit_price_snapshot: number;
|
|
quantity: number;
|
|
line_total: number;
|
|
}
|
|
|
|
export interface OrderStatusLog {
|
|
from_status: string;
|
|
to_status: string;
|
|
note: string;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface Order {
|
|
uuid: string;
|
|
store: StoreListItem;
|
|
status: OrderStatus;
|
|
items: OrderItem[];
|
|
items_subtotal: number;
|
|
delivery_fee: number;
|
|
commission_amount: number;
|
|
seller_payout_amount: number;
|
|
total_amount: number;
|
|
cancel_reason: string;
|
|
courier_name: string;
|
|
courier_phone: string;
|
|
placed_at: string;
|
|
delivered_at: string | null;
|
|
status_logs: OrderStatusLog[];
|
|
is_cancellable: boolean;
|
|
}
|
|
|
|
export interface OrderGroup {
|
|
uuid: string;
|
|
recipient_name: string;
|
|
recipient_phone: string;
|
|
full_address: string;
|
|
delivery_type: DeliveryType;
|
|
scheduled_at: string | null;
|
|
payment_method: PaymentMethod;
|
|
payment_status: PaymentStatus;
|
|
notes: string;
|
|
total_amount: number;
|
|
orders: Order[];
|
|
created_at: string;
|
|
/** `feature/payment` only. */
|
|
payment?: { method: PaymentMethod; status: string; payment_url?: string };
|
|
}
|