Skip to main content
The order history section allows authenticated customers to view all their past orders, drill into order details, re-add items to cart, and track shipments. All routes require authentication.

Routes

RouteComponentAuth
/ordersOrdersYes
/orders/:orderIdOrderDetailYes
/track-orderInline tracking formNo
/checkoutCheckoutNo

OrderRecord Interface

Orders returned from Supabase use the OrderRecord type (from src/types/order.ts), which maps directly to the orders table:
const ORDER_SELECT =
    'id, order_number, customer_id, items, subtotal, shipping_cost, ' +
    'discount, total, status, payment_method, payment_status, ' +
    'shipping_address_id, billing_address_id, tracking_notes, ' +
    'whatsapp_sent, whatsapp_sent_at, created_at, updated_at';
The Order interface from src/types/cart.ts (used in checkout context):
export interface Order extends CheckoutFormData {
    id: string;
    items: CartItem[];
    subtotal: number;
    total: number;
    createdAt: string;
    payment_status?: 'pending' | 'paid' | 'failed' | 'refunded';
    mp_preference_id?: string | null;   // Mercado Pago preference ID
    mp_payment_id?: string | null;      // Mercado Pago payment ID
    mp_payment_data?: MercadoPagoPaymentData | null;
}

Order Statuses

type OrderStatus =
    | 'pending'      // Created, awaiting confirmation
    | 'confirmed'    // Admin confirmed the order
    | 'processing'   // Being prepared
    | 'shipped'      // Handed off to carrier
    | 'delivered'    // Confirmed delivered
    | 'cancelled';   // Cancelled by admin or customer
Status transitions are managed by admins via AdminOrders. The customer-facing OrderDetail page reflects the current status with a visual progress indicator.

Payment Statuses

type PaymentStatus = 'pending' | 'paid' | 'failed' | 'refunded';
StatusWhen set
pendingOrder created, payment not yet confirmed
paidMercado Pago webhook confirmed payment, or admin marked as paid
failedMercado Pago returned failure, or transfer never received
refundedAdmin issued a refund

Creating an Order

createOrder(data) from orders.service.ts:
export async function createOrder(data: CreateOrderData): Promise<OrderRecord>
CreateOrderData type:
interface CreateOrderData {
    customer_id: string;
    items: CartItem[];             // Serialized as JSONB in DB
    subtotal: number;
    shipping_cost?: number;        // Default: 0
    discount?: number;             // Default: 0
    total: number;
    payment_method: PaymentMethod; // 'whatsapp' | 'mercadopago' | 'cash' | 'transfer' | 'card'
    shipping_address_id?: string | null;
    billing_address_id?: string | null;
    tracking_notes?: string | null;
    earned_points?: number;        // Optional override; defaults to calculateLoyaltyPoints(total)
}
After inserting the order, loyalty points are calculated and awarded automatically. See Cart & Checkout for details.

Hooks

import { useCustomerOrders, useOrder, useCreateOrder } from '@/hooks/useOrders';

// All orders for the authenticated customer
const { data: orders, isLoading } = useCustomerOrders(customerId);
// Query key: ['orders', customerId]
// staleTime: 2 minutes

// Single order by ID
const { data: order } = useOrder(orderId);
// Query key: ['orders', 'detail', orderId]
// staleTime: 1 minute

// Mutation to create an order
const createOrder = useCreateOrder();
createOrder.mutate(orderData, {
    onSuccess: (order) => { /* redirect or show confirmation */ }
});
// On success: invalidates ['orders', customerId], ['loyalty', 'balance', customerId], ['loyalty', 'tier', customerId]

Order History Page

Orders (src/pages/Orders.tsx) fetches all customer orders with useCustomerOrders(user.id) and renders them as a list ordered by created_at DESC. Each row shows:
  • order_number — auto-generated human-readable identifier
  • status — colored badge
  • total — formatted as MXN currency
  • created_at — formatted with formatTimeAgo() from src/lib/utils.ts
  • Item count and first product thumbnail

Order Detail Page

OrderDetail (src/pages/OrderDetail.tsx) shows the full order breakdown: line items with images and prices, delivery type, payment method, and a status timeline.

Re-order Feature

The detail page includes a “Volver a pedir” (Re-order) button that calls loadOrderItems() from the cart store:
import { useCartStore } from '@/stores/cart.store';

const loadOrderItems = useCartStore((s) => s.loadOrderItems);

// Replaces the entire cart with the historical order's items
loadOrderItems(order.items);

// Then navigate to checkout
nav('/checkout');
loadOrderItems() replaces the current cart entirely. Any existing cart items will be lost. The UI should warn the customer before triggering a re-order.

Order Tracking (/track-order)

Customers can track a shipment by entering a tracking number. The useOrderTracking() hook fires a mutation:
export function useOrderTracking() {
    return useMutation({
        mutationFn: (trackingNumber: string) =>
            ordersService.getTrackingInfo(trackingNumber),
    });
}
This calls getTrackingInfo() from orders.service.ts which queries the tracking_notes field and any external carrier API integration (carrier API integration is on the roadmap).

Mercado Pago Fields

Orders paid via Mercado Pago carry additional fields:
mp_preference_id: string | null;  // MP checkout preference
mp_payment_id: string | null;     // MP transaction ID after payment
mp_payment_data: MercadoPagoPaymentData | null;  // Full MP response
export interface MercadoPagoPaymentData {
    id: string;
    status: string;
    status_detail: string;
    payment_method_id: string;
    payment_type_id: string;
    external_reference?: string;
    preference_id?: string;
    transaction_amount: number;
    currency_id: string;
    date_approved?: string;
    [key: string]: unknown;
}

Build docs developers (and LLMs) love