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

loyalty

objectBeginnerserverbrowserlive demo

Reads the store's loyalty program (ways to earn points, prizes) and the signed-in customer's points balance.

import { loyalty } from '@salla.sa/twilight-theme-engine/api/loyalty';

In plain words

Some stores reward customers with points (for buying, for sharing, for a birthday) and let them trade points for prizes. loyalty.get() returns the program: its name, the ways to earn and the prizes. loyalty.getPointsBalance() returns how many points the signed-in customer has.

The program is public; the balance belongs to a customer, so it needs a signed-in shopper.

Signature

loyalty.get(): Promise<Loyalty>                // GET loyalty
loyalty.getOrThrow(): Promise<Loyalty>         // any failure → NotFoundError
loyalty.getPointsBalance(): Promise<number>    // GET balance/points → response.balance
loyalty.queries.detail()   // key ['loyalty']
loyalty.queries.points()   // key ['loyalty', 'points'], enabled: !!getAuthToken(), staleTime 30 s

// The Loyalty type is not exported; use Awaited<ReturnType<typeof loyalty.get>>
// { id; name; description?; image?; customer: {…} | null; points: LoyaltyPoint[]; prizes: LoyaltyPrize[]; … }

Try it live

The store's loyalty program and the customer's points balance, asked of the demo store as a guest.Try this: compare the store flag with the answer: with the program off, the API refuses with a message instead of data.
Storefront canvas · ar · RTL
Runs in the browser…
What a theme writes
import { useQuery } from '@tanstack/react-query';
import { loyalty } from '@salla.sa/twilight-theme-engine/api/loyalty';
import { useStore } from '@salla.sa/twilight-theme-engine/hooks/useStore';

export function LoyaltyBadge() {
  const { settings } = useStore();
  const { data: program } = useQuery({
    ...loyalty.queries.detail(),
    enabled: Boolean(settings.is_loyalty_enabled), // the API refuses when the program is off
  });
  const { data: balance } = useQuery(loyalty.queries.points()); // idle for guests

  if (!program) return null;
  return (
    <p className="loyalty-badge">
      {program.name}
      {balance != null && <span> · {balance} points</span>}
    </p>
  );
}

Example

app/components/cart/PointsBalance.tsx
import { useQuery } from '@tanstack/react-query';
import { loyalty } from '@salla.sa/twilight-theme-engine/api/loyalty';

export function PointsBalance() {
  // Disabled for guests by the options themselves, and fresh for 30 seconds.
  const { data: balance } = useQuery(loyalty.queries.points());
  if (balance == null) return null;

  return <p className="points-balance">You have {balance} points</p>;
}

How it behaves

  • queries.points() decides enabled from getAuthToken() at the moment you call it. Build the options during render: options built once at module level stay disabled after a login.

  • The engine cart page reads loyalty.queries.points(), and the loyalty page loader calls loyalty.getOrThrow().

  • getPointsBalance reads balance from the top of the response body, not from data.

  • customer in the program is null for a guest.

  • LoyaltyPoint, LoyaltyPrize and LoyaltyPrizeItem are exported from @salla.sa/twilight-theme-engine/routes/loyalty.

Gotchas

  • On a store with loyalty switched off (store.settings.is_loyalty_enabled is false), GET loyalty answers HTTP 400 with a "temporarily unavailable" message (demo store). get throws, getOrThrow shows the 404 page, and a plain useQuery retries once before failing. Check useStore().settings.is_loyalty_enabled first.

  • Calling getPointsBalance() directly as a guest throws (HTTP 400 token_not_provided, demo store); only the query options guard against it.

  • Loyalty exported from @salla.sa/twilight-theme-engine/routes/loyalty is the loyalty route module, not the program type. import type { Loyalty } from there does not describe loyalty.get().

  • src/api/README.md calls the balance method loyalty.getPoints(). It is getPointsBalance().

Related

Source and docs