Files

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>
)
}