mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
Rows come from GET /v1/leaderboard/beta: a name only for a person who listed, a builder for the rest, the reader's own row below the top, the count on the waitlist. The listing choice is GET and PUT /v1/leaderboard/optin. Until the cloud answers a board, only the reader's own row is drawn; no floor of one point, no plan badge the server did not send. Claude-Session: https://claude.ai/code/session_017vrVus6PPBgP5YRzKV1iqW
91 lines
2.9 KiB
TypeScript
91 lines
2.9 KiB
TypeScript
/**
|
|
* THE BETA BOARD: everyone on the hanzo.ai/beta waitlist, ranked by the one score
|
|
* the cloud keeps (GET /v1/leaderboard/beta), and whether the reader's own row
|
|
* carries their name (GET and PUT /v1/leaderboard/optin). A row is named only for a
|
|
* person who chose to be listed; everyone else is a builder, and still counts.
|
|
*/
|
|
import { api } from '@hanzo/app/lib/api'
|
|
import { scope } from '@hanzo/app/lib/session'
|
|
|
|
/** One ranked record. `tokens` is a range; `tokensUsed` is exact on a named row and the reader's own. */
|
|
export interface Row {
|
|
rank: number
|
|
name: string | null
|
|
you: boolean
|
|
score: number
|
|
referrals: number
|
|
qualified: number
|
|
tokens: string
|
|
tokensUsed?: number
|
|
applied: string
|
|
}
|
|
|
|
export interface Board {
|
|
rows: Row[]
|
|
you: Row | null
|
|
of: number
|
|
/** False when the cloud could not count token use or referrals, and no score has them then. */
|
|
tokens: boolean
|
|
referrals: boolean
|
|
}
|
|
|
|
export interface Listing {
|
|
listed: boolean
|
|
handle: string
|
|
canSet: boolean
|
|
}
|
|
|
|
const json = { 'Content-Type': 'application/json' }
|
|
|
|
/** The top of the waitlist and the reader's own row; null while the cloud has no board to answer. */
|
|
export async function board(limit = 25): Promise<Board | null> {
|
|
const headers = scope()
|
|
if (!headers.Authorization) return null
|
|
try {
|
|
const res = await fetch(`${api()}/v1/leaderboard/beta?limit=${limit}`, { headers })
|
|
if (!res.ok) return null
|
|
const body = (await res.json()) as Partial<Board>
|
|
return {
|
|
rows: Array.isArray(body.rows) ? body.rows : [],
|
|
you: body.you ?? null,
|
|
of: typeof body.of === 'number' ? body.of : 0,
|
|
tokens: body.tokens !== false,
|
|
referrals: body.referrals !== false,
|
|
}
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
const own = (raw: unknown): Listing | null => {
|
|
const u = (raw as { user?: Partial<Listing> } | null)?.user
|
|
if (!u) return null
|
|
return { listed: u.listed === true, handle: typeof u.handle === 'string' ? u.handle : '', canSet: u.canSet === true }
|
|
}
|
|
|
|
/** Whether the reader's row carries their name. */
|
|
export async function listing(): Promise<Listing | null> {
|
|
const headers = scope()
|
|
if (!headers.Authorization) return null
|
|
try {
|
|
const res = await fetch(`${api()}/v1/leaderboard/optin`, { headers })
|
|
return res.ok ? own(await res.json()) : null
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
/** List the reader's row under a handle (their username when none is given), or take the name off. */
|
|
export async function list(listed: boolean, handle = ''): Promise<Listing | null> {
|
|
const headers = scope()
|
|
if (!headers.Authorization) return null
|
|
try {
|
|
const res = await fetch(`${api()}/v1/leaderboard/optin`, { method: 'PUT', headers: { ...json, ...headers }, body: JSON.stringify({ listed, handle }) })
|
|
if (!res.ok) return null
|
|
const body = (await res.json()) as Partial<Listing>
|
|
return { listed: body.listed === true, handle: typeof body.handle === 'string' ? body.handle : '', canSet: true }
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|