learning_ai_common_plat/packages/referral-client/src/client.ts
saravanakumardb1 be03efa111 feat(shared-packages): add 9 @bytelyst/* client packages with 100% API coverage
Packages added:
- @bytelyst/referral-client — referral API client + share helpers
- @bytelyst/subscription-client — subscription/plan API client + cache
- @bytelyst/celebrations — milestone triggers, confetti, positive messages
- @bytelyst/gentle-notifications — ND-friendly messaging, forbidden phrases
- @bytelyst/accessibility — VoiceOver/TalkBack label generators
- @bytelyst/quick-actions — progressive disclosure, smart defaults
- @bytelyst/time-references — familiar duration references
- @bytelyst/org-client — org/workspace/membership/license API client
- @bytelyst/marketplace-client — listing/review/install API client

All packages: pure TS, ESM, globalThis.fetch, no Node.js deps.
99 Vitest tests across 9 packages, 79/79 public methods covered.

Review fixes applied:
- time-references: fix module-level mutable state leak + add clearCustomReferences()
- accessibility: fix parameter reassignment in formatDurationForA11y/numberToWords
- subscription-client: fix flaky daysRemaining test (ms boundary race)
2026-03-19 13:10:09 -07:00

123 lines
4.0 KiB
TypeScript

/**
* Browser/React Native-safe referral client for platform-service.
*
* Wraps platform-service /referrals/* endpoints.
* No Node.js dependencies — uses globalThis.fetch.
*/
import type { ReferralClient, ReferralClientConfig, ReferralDoc } from './types.js';
function generateRequestId(): string {
return typeof globalThis.crypto?.randomUUID === 'function'
? globalThis.crypto.randomUUID()
: `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
}
export function createReferralClient(config: ReferralClientConfig): ReferralClient {
const { baseUrl, productId, getAccessToken, defaultRewardTokens } = config;
const defaultReferrerTokens = defaultRewardTokens?.referrer ?? 1000;
const defaultReferredTokens = defaultRewardTokens?.referred ?? 500;
function headers(): Record<string, string> {
const h: Record<string, string> = {
'Content-Type': 'application/json',
'x-product-id': productId,
'x-request-id': generateRequestId(),
};
const token = getAccessToken();
if (token) h['Authorization'] = `Bearer ${token}`;
return h;
}
async function listMyReferrals(
referrerId: string
): Promise<{ referrals: ReferralDoc[]; count: number }> {
const res = await globalThis.fetch(
`${baseUrl}/referrals/by-referrer/${encodeURIComponent(referrerId)}`,
{ headers: headers() }
);
if (!res.ok) throw new Error(`listMyReferrals failed: ${res.status}`);
const data = (await res.json()) as { referrals: ReferralDoc[]; count: number };
return data;
}
async function getReferralStats(): Promise<{
total: number;
completed: number;
rewarded: number;
}> {
const res = await globalThis.fetch(`${baseUrl}/referrals/stats`, { headers: headers() });
if (!res.ok) throw new Error(`getReferralStats failed: ${res.status}`);
return (await res.json()) as { total: number; completed: number; rewarded: number };
}
async function createReferral(input: {
referrerId: string;
referrerEmail: string;
referredEmail: string;
}): Promise<ReferralDoc> {
const body = {
...input,
productId,
referrerRewardTokens: defaultReferrerTokens,
referredRewardTokens: defaultReferredTokens,
};
const res = await globalThis.fetch(`${baseUrl}/referrals`, {
method: 'POST',
headers: headers(),
body: JSON.stringify(body),
});
if (!res.ok) throw new Error(`createReferral failed: ${res.status}`);
return (await res.json()) as ReferralDoc;
}
async function updateReferralStatus(
id: string,
referrerId: string,
status: ReferralDoc['status']
): Promise<ReferralDoc> {
const res = await globalThis.fetch(`${baseUrl}/referrals/${encodeURIComponent(id)}`, {
method: 'PUT',
headers: headers(),
body: JSON.stringify({ referrerId, status }),
});
if (!res.ok) throw new Error(`updateReferralStatus failed: ${res.status}`);
return (await res.json()) as ReferralDoc;
}
async function getByEmail(email: string): Promise<ReferralDoc | null> {
const res = await globalThis.fetch(
`${baseUrl}/referrals/by-email/${encodeURIComponent(email)}`,
{ headers: headers() }
);
if (res.status === 404) return null;
if (!res.ok) throw new Error(`getByEmail failed: ${res.status}`);
return (await res.json()) as ReferralDoc;
}
function buildShareLink(code: string): string {
return `https://bytelyst.com/refer/${encodeURIComponent(code)}?product=${encodeURIComponent(productId)}`;
}
function buildShareMessage(code: string, productName: string): string {
const link = buildShareLink(code);
return `Try ${productName}! Use my referral link to get started: ${link}`;
}
function calculateEarnedDays(conversions: number, daysPerReferral = 7): number {
return Math.max(0, conversions * daysPerReferral);
}
return {
listMyReferrals,
getReferralStats,
createReferral,
updateReferralStatus,
getByEmail,
buildShareLink,
buildShareMessage,
calculateEarnedDays,
};
}