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

Loyalty

route-moduleBeginnerserverbrowser

The loyalty program page, listing ways to earn points and prizes to redeem, or a 404 page when the store runs no program.

import { Loyalty, LoyaltyPage, loyaltyLoader, LoyaltyPageProps, LoyaltyPoint, LoyaltyPrize, LoyaltyPrizeItem } from '@salla.sa/twilight-theme-engine/routes/loyalty';

In plain words

Some stores reward shoppers with points: for signing up, ordering, sharing the store. /loyalty explains the program, shows the shopper's balance and lists the prizes the points can buy. The page title is the program's own name.

Salla's demo store runs no loyalty program right now, so the page on Built-in pages shows the 404 page: exactly what your theme shows for such a store.

Signature

const Loyalty: {
  readonly id: 'loyalty';
  readonly loader: (
    ctx?: { locale?: string },
    extend?: (data: LoyaltyPageProps, ctx: { params: {} }) => Record<string, unknown> | Promise<Record<string, unknown>>
  ) => Promise<LoyaltyPageProps>;
  readonly head: (ctx: TwilightContext, data: LoyaltyPageProps) => HeadDescriptor;
  readonly Component: React.LazyExoticComponent<(props: LoyaltyPageProps) => JSX.Element>;
};

const LoyaltyPage: typeof Loyalty.Component;
function loyaltyLoader(): Promise<LoyaltyPageProps>;

interface LoyaltyPageProps {
  page: Page;          // title: the program name
  loyalty: Loyalty;    // the program: name, description, image, points, prizes… (type not exported)
}

interface LoyaltyPoint {
  key: string; name: string; description: string; points: number; type: string;
  icon: string; color: string; status: boolean; is_completed: boolean; conditions: LoyaltyCondition[]; …
}
interface LoyaltyPrize {
  type: 'coupon_discount' | 'free_product' | 'free_shipping' | string;
  title: string;
  items: LoyaltyPrizeItem[];
}
interface LoyaltyPrizeItem {
  id: number; name: string; cost_points: number;
  description?: string; image?: string; url?: string; coupon_type?: string; coupon_amount?: number; …
}

Example

app/routes/loyalty.tsx (generated)
import { createFileRoute } from '@tanstack/react-router';
import { Loyalty } from '@salla.sa/twilight-theme-engine/routes/loyalty';
import type { LoyaltyPageProps } from '@salla.sa/twilight-theme-engine/routes/loyalty';
import { withHead } from '@salla.sa/twilight-theme-engine/tanstack';

export const Route = createFileRoute('/{-$locale}/loyalty')({
  loader: ({ params }): Promise<LoyaltyPageProps> => Loyalty.loader({ locale: params.locale }),
  head: withHead(Loyalty),
  component: LoyaltyComponent,
});

function LoyaltyComponent() {
  const data: LoyaltyPageProps = Route.useLoaderData();
  return <Loyalty.Component {...data} />;
}

How it behaves

  • loyaltyLoader calls loyalty.getOrThrow(): when the store has no program, or the request fails, it throws the engine's NotFoundError and the router's default error component shows a 404 page. (The demo store's API answers 400 with the message that the loyalty program is temporarily unavailable.) page.title is the program's name.

  • head sets the title and hreflang alternates only.

  • LoyaltyPage also loads the signed-in customer's points in the browser (loyalty.queries.points()) and uses Salla's loyalty, button and slider web components. It renders no hook slots.

  • Lazy: outside a route, wrap LoyaltyPage in <Suspense>.

Gotchas

  • The program's data type is not exported, and its name Loyalty belongs to the route module. Type it as LoyaltyPageProps['loyalty'].

Related

Source and docs