mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
122 lines
4.9 KiB
TypeScript
122 lines
4.9 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import { Text, XStack, YStack } from '@hanzo/ui'
|
|
import { Action, Chip, Title } from '@hanzo/ui/marketing'
|
|
import { useI18n } from '@hanzo/i18n/react'
|
|
import { connect, type Row } from '@/lib/hanzo/rewards'
|
|
import { Rules } from '@/components/rewards/rules'
|
|
import { LinkGithub } from '@/components/rewards/github'
|
|
import { useRules } from '@/components/rewards/use'
|
|
import { Card, Quiet } from './card'
|
|
import type { useStanding } from './standing'
|
|
|
|
/**
|
|
* Step 2: the board as the rewards ledger ranks it (the top of GET /v1/rewards
|
|
* and the member's own row), what earns from the rules the cloud awards by, and
|
|
* the ways to earn more: a bounty, and the GitHub link that makes a merge count.
|
|
* The whole board, the badges and the levels are /leaderboard's.
|
|
*/
|
|
export function Rank({ s, handle }: { s: ReturnType<typeof useStanding>; handle: string }) {
|
|
const { t, format } = useI18n()
|
|
const rules = useRules()
|
|
const [linking, setLinking] = useState(false)
|
|
const [wrong, setWrong] = useState('')
|
|
const rows: Row[] = s.ranked ? [...s.ranked.rows] : []
|
|
if (s.ranked?.you && !rows.some((r) => r.you)) rows.push(s.ranked.you)
|
|
if (!s.ranked && s.me) rows.push({ rank: s.me.rank, name: null, you: true, points: s.me.points, level: s.me.level.name, since: s.me.since })
|
|
const linked = s.github?.connected === true
|
|
|
|
const link = async () => {
|
|
setLinking(true)
|
|
setWrong('')
|
|
try {
|
|
window.location.assign(await connect())
|
|
} catch (err) {
|
|
setWrong(err instanceof Error ? err.message : String(err))
|
|
setLinking(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<YStack gap={16}>
|
|
<Card gap={20} data-slot="beta-leaderboard">
|
|
<XStack items="center" justify="space-between" flexWrap="wrap" gap={12}>
|
|
<Title quiet>{t('Leaderboard')}</Title>
|
|
{s.ranked && s.ranked.of > 0 ? (
|
|
<Text fontFamily="$mono" fontSize="$2" color="var(--muted-foreground)">
|
|
{t('{n} on the board', { n: format.number(s.ranked.of) })}
|
|
</Text>
|
|
) : null}
|
|
</XStack>
|
|
|
|
<Rules rules={rules} />
|
|
|
|
{rows.length ? (
|
|
<YStack borderWidth={1} borderColor="var(--border)" rounded={14} overflow="hidden">
|
|
{rows.map((r, i) => (
|
|
<XStack key={`${r.rank}-${r.name ?? ''}-${r.you}`} items="center" gap={12} px={16} py={12} borderTopWidth={i ? 1 : 0} borderColor="var(--border)" bg={r.you ? 'var(--secondary)' : 'transparent'}>
|
|
<Text fontFamily="$mono" fontSize="$3" fontWeight="700" color="var(--foreground)" minW={48} data-i18n-ltr>
|
|
{r.rank > 0 ? `#${r.rank}` : '—'}
|
|
</Text>
|
|
<YStack flex={1} minW={0}>
|
|
<Text fontFamily="$mono" fontSize="$3" fontWeight="600" color="var(--foreground)" overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap">
|
|
{r.name ? `@${r.name}` : r.you ? handle : t('A builder')}
|
|
{r.you ? ` · ${t('you')}` : ''}
|
|
</Text>
|
|
{r.level ? (
|
|
<Text fontSize="$2" color="var(--muted-foreground)">
|
|
{r.level}
|
|
</Text>
|
|
) : null}
|
|
</YStack>
|
|
<Text fontFamily="$mono" fontSize="$3" fontWeight="700" color="var(--foreground)" data-i18n-ltr>
|
|
{t('{n} pts', { n: format.number(r.points) })}
|
|
</Text>
|
|
</XStack>
|
|
))}
|
|
</YStack>
|
|
) : null}
|
|
|
|
<XStack gap={12} flexWrap="wrap" items="center">
|
|
{s.listed?.canSet ? (
|
|
<Action render="button" type="button" onClick={() => void s.show(!s.listed?.listed)}>
|
|
{s.listed.listed ? t('Take my name off the board') : t('Show my name on the board')}
|
|
</Action>
|
|
) : null}
|
|
<Quiet href="/leaderboard">{t('The whole board')}</Quiet>
|
|
</XStack>
|
|
|
|
{s.referees.length ? (
|
|
<XStack gap={8} flexWrap="wrap">
|
|
{s.referees.map((r) => (
|
|
<Chip key={r.id} px={12} py={6}>
|
|
{r.referee}
|
|
{r.status === 'qualified' ? ` · ${t('pays')}` : ''}
|
|
</Chip>
|
|
))}
|
|
</XStack>
|
|
) : null}
|
|
</Card>
|
|
|
|
<Card gap={20} bg="transparent" data-slot="beta-earn">
|
|
<Title quiet>{t('Earn more')}</Title>
|
|
<XStack gap={10} flexWrap="wrap">
|
|
<Action href="/bounty" fill>
|
|
{t('Find a bounty')}
|
|
</Action>
|
|
<Action href="/leaderboard">{t('See what earns')}</Action>
|
|
</XStack>
|
|
{linked ? null : (
|
|
<YStack gap={8}>
|
|
<Text fontSize="$2" color="var(--muted-foreground)">
|
|
{t('Merged pull requests and accepted issues count for the GitHub account linked to yours.')}
|
|
</Text>
|
|
<LinkGithub signed github={s.github} link={() => void link()} busy={linking} wrong={wrong} />
|
|
</YStack>
|
|
)}
|
|
</Card>
|
|
</YStack>
|
|
)
|
|
}
|