BricqsBricqs

Headless SDK: points and tiers

Render points, tier, and badges in your own UI with one hook each. The SDK handles polling, formatting, and the messy edge cases (tier transitions, points expiry, locale formatting).

Reading time7 minutes
Last updatedMay 2026

Key takeaways

Quick read
  • usePoints returns available, lifetime, and pending. Three numbers cover every UI you will build.
  • useTier returns current tier, next tier, and points-to-next. Pair with a progress bar.
  • useBadges returns earned and unearned badges. Filter by tier or by recency.
  • All three hooks poll every 30 seconds. Override with pollInterval where the screen is idle.
  • Format with the user's locale. The SDK exposes raw numbers; pass them to Intl.NumberFormat.

usePoints

Render the balance

components/PointsBadge.tsx·tsx
"use client";
import { usePoints } from "@bricqs/headless-react";

export function PointsBadge({ locale = "en-IN" }: { locale?: string }) {
  const { available, lifetime, pending, isLoading } = usePoints();
  const fmt = new Intl.NumberFormat(locale).format;

  if (isLoading) return <span className="opacity-50">…</span>;

  return (
    <span className="inline-flex items-center gap-2">
      <strong>{fmt(available)}</strong> pts
      <small className="text-slate-500">
        Lifetime {fmt(lifetime)}
        {pending > 0 ? ` · Pending ${fmt(pending)}` : null}
      </small>
    </span>
  );
}

useTier

Render the tier and the next-tier track

components/TierTrack.tsx·tsx
"use client";
import { useTier } from "@bricqs/headless-react";

export function TierTrack() {
  const { current, next, progressPercent, pointsToNext, isLoading } = useTier();

  if (isLoading || !current) return null;

  return (
    <section className="rounded-xl border p-5">
      <header className="flex items-baseline justify-between mb-2">
        <h3 className="font-bold">{current.label}</h3>
        {next ? (
          <span className="text-sm text-slate-500">
            {pointsToNext.toLocaleString()} pts to {next.label}
          </span>
        ) : (
          <span className="text-sm text-slate-500">Top tier</span>
        )}
      </header>
      <div className="h-2 bg-slate-100 rounded-full overflow-hidden">
        <div
          className="h-full bg-orange-500"
          style={{ width: `${progressPercent}%` }}
        />
      </div>
    </section>
  );
}

useBadges

Render earned and unearned badges

components/BadgeShelf.tsx·tsx
"use client";
import { useBadges } from "@bricqs/headless-react";

export function BadgeShelf() {
  const { badges, isLoading } = useBadges({ includeLocked: true });

  if (isLoading) return null;

  return (
    <ul className="grid grid-cols-3 gap-3">
      {badges.map((b) => (
        <li
          key={b.id}
          className={`rounded-xl border p-3 text-center ${b.earned ? "" : "opacity-40"}`}
          title={b.earned ? `Earned ${b.earnedAt}` : "Locked"}
        >
          <img src={b.iconUrl} alt="" className="w-10 h-10 mx-auto" />
          <div className="text-xs mt-2 font-semibold">{b.label}</div>
        </li>
      ))}
    </ul>
  );
}

Tier transitions

Catch the upgrade moment

onTierUp

Imperative callback fired once when the participant moves up. Use for a celebration UI or a one-shot push notification.

Suppress noise

Tier-up callbacks fire even on hot-reload during dev. Guard with a session flag in development; use the imperative API in production.

tsx
const { onTierUp } = useTier();

onTierUp((event) => {
  showCelebrationToast(`You reached ${event.to.label}!`);
});

Common mistakes

What goes wrong

01Mistake

Showing pending points without explanation. Users think their balance is wrong.

Fix

Label pending: 'Pending: 50 (clears in 2 days)'. Or hide pending until you have a UI for it.

02Mistake

Rendering tier label in code as 'tier_silver'. Looks like a bug.

Fix

Use current.label which is the human-readable string. The id is for code paths.

03Mistake

Polling every hook on a busy dashboard. Network panel goes wild.

Fix

Increase pollInterval on idle widgets (60 to 120 seconds). The default 30 is fine for primary screens only.

04Mistake

Not localizing the number format. 1,250 in en-IN should render as 1,250; in en-US as 1,250 too, but in fr-FR as 1 250.

Fix

Always use Intl.NumberFormat with the user's locale. The SDK ships raw numbers.

Developer FAQ

Common questions when integrating gamification with Bricqs.

Ready to ship?

Wire it up with the Bricqs SDK or API

Headless SDK for React UIs, REST API for any backend. Same engine behind both.

1 brief to align the room2 mechanics max in version one
What happens next
01
Pick the mechanic
Choose the smallest working system for the brief.
02
Launch without rebuilds
Configure rules and rewards in one place.