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).
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
"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
"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
"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.
const { onTierUp } = useTier();
onTierUp((event) => {
showCelebrationToast(`You reached ${event.to.label}!`);
});Common mistakes
What goes wrong
Showing pending points without explanation. Users think their balance is wrong.
Label pending: 'Pending: 50 (clears in 2 days)'. Or hide pending until you have a UI for it.
Rendering tier label in code as 'tier_silver'. Looks like a bug.
Use current.label which is the human-readable string. The id is for code paths.
Polling every hook on a busy dashboard. Network panel goes wild.
Increase pollInterval on idle widgets (60 to 120 seconds). The default 30 is fine for primary screens only.
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.
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.
