Files

141 lines
4.4 KiB
TypeScript

import { api } from '@hanzo/app/lib/api'
import { scope } from '@hanzo/app/lib/session'
import type { Program } from '@/components/beta/program'
/**
* Beta records and ecosystem beta requests on hanzo-inc/cloud apps/marketing/beta.go:
*
* GET /v1/marketing/beta the person's record and the betas they asked for
* PUT /v1/marketing/beta save program, questionnaire answers, and requested betas
* POST /v1/marketing/beta/request request access to specific feature-flagged ecosystem betas (or alpha)
*
* The person's points and place are the rewards ledger's (lib/hanzo/rewards):
* the record never holds points.
*/
export interface BetaAnswer {
id: string
question: string
answer: string[]
}
export interface BetaAction {
action: 'star' | 'follow'
target: string
at: string
}
export interface BetaStanding {
applied: boolean
program: Program
answers: BetaAnswer[]
betas: string[]
requested: Record<string, string>
github: string
actions: BetaAction[]
lead: string
since: string
}
/**
* Stored with `hz_` prefix (like `hz_ref` in referrals.ts) so `own(subject())`
* does not clear them when an anonymous visitor signs up or logs in.
*/
const LOCAL_BETAS_KEY = 'hz_beta_requested'
export function getLocalRequestedBetas(): string[] {
if (typeof window === 'undefined') return []
try {
const raw = JSON.parse(window.localStorage.getItem(LOCAL_BETAS_KEY) ?? '[]')
return Array.isArray(raw) ? raw.filter((x): x is string => typeof x === 'string') : []
} catch {
return []
}
}
export function setLocalRequestedBetas(ids: string[]): void {
if (typeof window === 'undefined') return
try {
const unique = Array.from(new Set(ids))
window.localStorage.setItem(LOCAL_BETAS_KEY, JSON.stringify(unique))
} catch {
/* ignore storage errors */
}
}
function normalizeStanding(raw: Partial<BetaStanding>): BetaStanding {
const prog = raw.program === 'research' || raw.program === 'partner' ? raw.program : 'beta'
return {
applied: Boolean(raw.applied || raw.program),
program: prog,
answers: Array.isArray(raw.answers) ? raw.answers : [],
betas: Array.isArray(raw.betas) ? raw.betas : [],
requested: raw.requested && typeof raw.requested === 'object' ? raw.requested : {},
github: typeof raw.github === 'string' ? raw.github : '',
actions: Array.isArray(raw.actions) ? raw.actions : [],
lead: typeof raw.lead === 'string' ? raw.lead : '',
since: typeof raw.since === 'string' ? raw.since : '',
}
}
/** Read the authenticated user's beta record and waitlist standing. */
export async function standing(): Promise<BetaStanding | null> {
const headers = scope()
if (!headers.Authorization) return null
try {
const res = await fetch(`${api()}/v1/marketing/beta`, { headers })
if (!res.ok) return null
const body = (await res.json()) as Partial<BetaStanding>
return normalizeStanding(body)
} catch {
return null
}
}
/** Save program, answers, and requested betas on the user's record. */
export async function keep(payload: {
program: Program
answers?: BetaAnswer[]
betas?: string[]
lead?: string
}): Promise<BetaStanding | null> {
const headers = scope()
if (!headers.Authorization) return null
try {
const res = await fetch(`${api()}/v1/marketing/beta`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json', ...headers },
body: JSON.stringify(payload),
})
if (!res.ok) return null
const body = (await res.json()) as Partial<BetaStanding>
return normalizeStanding(body)
} catch {
return null
}
}
/** Request access to one or more ecosystem betas (or 'alpha'). Also persists locally when signed out. */
export async function ask(betas: string[]): Promise<BetaStanding | null> {
const clean = betas.map((b) => b.trim().toLowerCase()).filter(Boolean)
if (!clean.length) return null
const existingLocal = getLocalRequestedBetas()
setLocalRequestedBetas([...existingLocal, ...clean])
const headers = scope()
if (!headers.Authorization) return null
try {
const res = await fetch(`${api()}/v1/marketing/beta/request`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...headers },
body: JSON.stringify({ betas: clean }),
})
if (!res.ok) return null
const body = (await res.json()) as Partial<BetaStanding>
return normalizeStanding(body)
} catch {
return null
}
}