Files
vyndr/web/src/lib/tierGate.ts
T

54 lines
2.0 KiB
TypeScript

/**
* Tier-gating helpers (Session 19).
*
* Shared, declarative answers to the questions every list-rendering
* component asks: "should this user see everything, or a teaser?"
* Centralized so we don't drift across components (one screen
* showing 3 rows + upgrade hint, another showing 5 rows + lock
* icon, a third showing all rows with a partial blur — that path
* leads to chaos).
*
* The actual SECURITY boundary on tier-locked data lives on the
* server (the API routes that produce these lists already filter by
* the bearer token's tier). These helpers govern presentation only,
* matching the server-supplied limit so the UI doesn't promise more
* than the API will deliver.
*
* Free users see top 3. Paid (analyst + desk) see everything.
* Africa tier (entry-level subscription) also sees everything for
* lists; gradient features live behind canSeeGradeDetails.
*/
export type Tier = 'free' | 'africa' | 'analyst' | 'desk' | string;
const PAID_TIERS: ReadonlySet<string> = new Set(['analyst', 'desk']);
const FULL_LIST_TIERS: ReadonlySet<string> = new Set(['africa', 'analyst', 'desk']);
const FREE_VISIBLE_COUNT = 3;
export function canSeeFullLists(tier: Tier): boolean {
if (!tier) return false;
return FULL_LIST_TIERS.has(String(tier).toLowerCase());
}
export function canSeeGradeDetails(tier: Tier): boolean {
if (!tier) return false;
return PAID_TIERS.has(String(tier).toLowerCase());
}
export function getVisibleCount(tier: Tier, totalCount: number): number {
if (totalCount <= 0) return 0;
if (canSeeFullLists(tier)) return totalCount;
return Math.min(FREE_VISIBLE_COUNT, totalCount);
}
/**
* Inverse helper for UX copy — "Upgrade to see N more results."
* Returns 0 when the tier already sees everything (so the upsell
* line can short-circuit without arithmetic).
*/
export function getHiddenCount(tier: Tier, totalCount: number): number {
if (canSeeFullLists(tier)) return 0;
return Math.max(0, totalCount - FREE_VISIBLE_COUNT);
}