Skip to content
Twilight React Playground
ثيم رائدaren

Orders

route-moduleBeginnerserverbrowser

The signed-in customer's order history, optionally filtered by status, as a table that loads more orders on request.

import { Orders, OrdersPage, ordersLoader, OrdersPageProps, OrdersQuery, OrdersSearchParams } from '@salla.sa/twilight-theme-engine/routes/account/orders';

In plain words

/account/orders lists the customer's orders: number, total, date and status, each row linking to that order. Adding ?status=pending to the address shows only pending orders and changes the page title.

Signature

const Orders: {
  readonly id: 'customer.orders.index';
  readonly loader: (
    ctx: { search?: OrdersSearchParams; locale?: string; with_items?: boolean | number },
    extend?: (data: OrdersPageProps, ctx: { params: {} }) => Record<string, unknown> | Promise<Record<string, unknown>>
  ) => Promise<OrdersPageProps>;
  readonly head: (ctx: TwilightContext, data: OrdersPageProps) => HeadDescriptor;
  readonly Component: React.LazyExoticComponent<(props: OrdersPageProps) => JSX.Element>;
};

const OrdersPage: typeof Orders.Component;

function ordersLoader(
  ctx: { query?: OrdersQuery; with_items?: boolean | number },   // query, not search
  extend?: (data: OrdersPageProps, ctx: { params: {} }) => Record<string, unknown> | Promise<Record<string, unknown>>
): Promise<OrdersPageProps>;

interface OrdersQuery { status?: string; feedback_status?: string }
type OrdersSearchParams = OrdersQuery;

interface OrdersPageProps {
  page: Page;
  orders: OrderListItem[];
  cursor?: Pagination;
  query?: OrdersQuery;
}

Example

app/routes/account.orders.tsx (generated)
import { createFileRoute } from '@tanstack/react-router';
import { Orders } from '@salla.sa/twilight-theme-engine/routes/account/orders';
import type { OrdersPageProps } from '@salla.sa/twilight-theme-engine/routes/account/orders';
import { CustomerPageSkeleton } from '@salla.sa/twilight-theme-engine/skeleton';
import { withHead } from '@salla.sa/twilight-theme-engine/tanstack';

export const Route = createFileRoute('/{-$locale}/account/orders')({
  validateSearch: (search: Record<string, unknown>) => {
    const status = (search.status as string) || undefined;
    return status ? { status } : {};
  },
  loaderDeps: ({ search }) => ({ status: search.status }),
  loader: ({ deps, params }): Promise<OrdersPageProps> =>
    Orders.loader({ search: { status: deps.status }, locale: params.locale }),
  head: withHead(Orders),
  pendingComponent: () => <CustomerPageSkeleton />,
  component: OrdersComponent,
});

function OrdersComponent() {
  const data: OrdersPageProps = Route.useLoaderData();
  return <Orders.Component {...data} />;
}

How it behaves

  • ordersLoader wraps the API call in orUnauthorized: a 401 or 403 shows the 401 page, any other error propagates. It sends status, feedback_status and, when set, with_items.

  • OrdersPage shows NoContent for an empty list. Its load-more button passes status and feedback_status again, but not with_items.

  • head takes the title from the address, not the loader data: common.titles.pending_orders when location.search.status is pending, common.titles.orders otherwise.

  • OrdersPage ignores page (the account layout shows the title). It draws a table (ItemsList in button mode) whose load-more button fetches from cursor.next with the same status and feedback_status, between the slots customer:orders.index.items.start and customer:orders.index.items.end.

  • The generated route forwards only status from the address; feedback_status and with_items are options for a route you write.

  • Orders, OrdersPage, ordersLoader and the props types are also re-exported from @salla.sa/twilight-theme-engine/routes/account.

Gotchas

  • A guest does not get the 401 page. orUnauthorized turns only a 401 or 403 into it, and Salla answers a request without a customer token with 400 token_not_provided (checked on the demo store for orders, order details, notifications and the wallet), so the router's default error component shows a 400 page. The same holds for OrderSingle, Notifications and Wallet; the wishlist API does answer 401.

  • Orders.loader runs extend twice: it passes it to ordersLoader, which merges the result, and then merges extend again (packages/theme-engine/src/routes/account/orders/index.tsx). An extend that fetches makes two requests.

  • The raw loader's filter is query, the module loader's is search. ordersLoader({ search: { status: 'pending' } }) filters nothing.

  • docs/03-routing-system.md calls Orders.loader({ locale: 'ar', status: 'pending' }) and lists hasMore and status props. That status is ignored (use search: { status }), and neither prop exists.

Related

Source and docs