Wallet
The signed-in customer's wallet balance and transaction history, starting from the ?page= in the address and loading more as they scroll.
import { Wallet, WalletPage, walletLoader, WalletPageProps } from '@salla.sa/twilight-theme-engine/routes/account';In plain words
Stores can keep money for a customer in a wallet: refunds and cashback go in, purchases paid by wallet come out. /account/wallet shows the balance and a table of those transactions.
Signature
const Wallet: {
readonly id: 'customer.wallet';
readonly loader: (
ctx: LoaderContext, // { search?: { page?: string | number }; locale?: string }
extend?: (data: WalletPageProps, ctx: { params: {} }) => Record<string, unknown> | Promise<Record<string, unknown>>
) => Promise<WalletPageProps>;
readonly head: (ctx: TwilightContext, data: WalletPageProps) => HeadDescriptor;
readonly Component: React.LazyExoticComponent<(props: WalletPageProps) => JSX.Element>;
};
const WalletPage: typeof Wallet.Component;
function walletLoader(ctx: LoaderContext): Promise<WalletPageProps>;
interface WalletPageProps {
page: Page; // { title, slug: 'wallet' }
query: { page: number };
transactions: TransactionItem[];
balance: number;
pagination: RoutePagination; // only next is set
}Example
import { createFileRoute } from '@tanstack/react-router';
import { Wallet } from '@salla.sa/twilight-theme-engine/routes/account';
import type { WalletPageProps } from '@salla.sa/twilight-theme-engine/routes/account';
import { CustomerPageSkeleton } from '@salla.sa/twilight-theme-engine/skeleton';
import { withHead } from '@salla.sa/twilight-theme-engine/tanstack';
export const Route = createFileRoute('/{-$locale}/account/wallet')({
validateSearch: (search: Record<string, unknown>) => {
const page = Number(search.page) || 1;
return page > 1 ? { page } : {};
},
loaderDeps: ({ search }) => ({ page: search.page }),
loader: ({ deps }): Promise<WalletPageProps> =>
Wallet.loader({ search: { page: deps.page ?? 1 } }),
head: withHead(Wallet),
pendingComponent: () => <CustomerPageSkeleton />,
component: WalletComponent,
});
function WalletComponent() {
const data: WalletPageProps = Route.useLoaderData();
return <Wallet.Component {...data} />;
}
How it behaves
walletLoaderreadsNumber(ctx.search?.page) || 1and wraps the API call inorUnauthorized. The generated route passes no locale; none is needed.WalletPageformats the balance and amounts withuseMoney(typegeneral), loads further transactions in the browser as the customer scrolls, and renders the slotscustomer:wallet.transactions.startandcustomer:wallet.transactions.end.headsets the title and hreflang alternates only.
Gotchas
A guest sees a 400 page, not the 401 page: Salla answers the wallet request without a customer token with
400 token_not_provided, whichorUnauthorizedpasses on (see Orders).Transaction dates are formatted while rendering with
new Date(created_at * 1000).toLocaleDateString(locale, { …, hour: 'numeric' }), which uses the time zone of whatever runs the code. The server renders in UTC; a browser in another time zone can print a different hour or day, and React reports a hydration mismatch.
Related
Formats prices in the page language (with the riyal icon for SAR), and parses or validates amounts.
WishlistThe signed-in customer's saved products, starting from the ?page= in the address and loading further pages as they scroll.
Profile, SettingsThe signed-in customer's profile and account settings pages, built from Salla's ready-made web components and refused to guests.
Source and docs
- Engine source:
packages/theme-engine/src/routes/account/wallet/index.tsx