mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
134 lines
5.9 KiB
TypeScript
134 lines
5.9 KiB
TypeScript
import { phrase } from '@hanzo/i18n'
|
||
import { T } from '@hanzo/i18n/react'
|
||
import { Page, PageHero, Prose, Section, CardGrid, Cta } from '@/components/marketing/page-kit'
|
||
import { Earn, Mine } from '@/components/rewards/contribute'
|
||
import { BOUNTY } from '@/components/rewards/words'
|
||
import { ours } from '@/lib/hanzo/orgs'
|
||
import { SNAPSHOT, kind, tier } from '@/lib/hanzo/rewards'
|
||
import open from '@/lib/data/bounties.json'
|
||
|
||
/**
|
||
* /bounty — contributions earn points. What earns, the bounty tiers and the
|
||
* daily cap are the rewards rules (the build's snapshot of GET /v1/rewards/rules);
|
||
* the open bounties are the issues GitHub lists, at build, with the rules'
|
||
* bounty label in a public repository of Hanzo's orgs (scripts/sync-bounties.ts),
|
||
* each with the tier its `bounty:<tier>` label names and what that tier earns.
|
||
* The points themselves are the cloud's: a merge or a maintainer's label on
|
||
* GitHub reaches it, and nothing on this page counts anything.
|
||
*/
|
||
|
||
const rules = SNAPSHOT
|
||
const orgs = rules.github.orgs.filter(ours)
|
||
const labels = rules.github.labels.bounty ?? []
|
||
const cap = rules.caps.find((c) => c.source === 'contributions' && c.window === 'day')
|
||
const security = kind(rules, 'security')
|
||
/** How each org asks to be contributed to: its own CONTRIBUTING, in the org's .github repository. */
|
||
const guide = (org: string) => `https://github.com/${org}/.github/blob/main/CONTRIBUTING.md`
|
||
/** Every open issue in those orgs, on GitHub, for when no issue carries a bounty label. */
|
||
const issues = `https://github.com/search?type=issues&q=${encodeURIComponent(['is:issue', 'is:open', 'is:public', ...orgs.map((o) => `org:${o}`)].join(' '))}`
|
||
/** An issue's tier and what it earns: the tier its label names, or the ordinary award for the work. */
|
||
const worth = (labels: string[]) => {
|
||
const k = tier(rules, labels)
|
||
return k ? `${k.title} · +${k.points}` : null
|
||
}
|
||
|
||
export default function Bounty() {
|
||
return (
|
||
<Page>
|
||
<PageHero eyebrow={phrase('Bounties')} title={BOUNTY.title} lede={BOUNTY.line} />
|
||
|
||
<Section title={phrase('What earns')}>
|
||
<Earn />
|
||
</Section>
|
||
|
||
<Section title={phrase('Open bounties')}>
|
||
{open.issues.length ? (
|
||
<CardGrid
|
||
columns={2}
|
||
items={open.issues.slice(0, 24).map((i) => ({
|
||
title: i.title,
|
||
description: [`${i.repo}#${i.number}`, worth(i.labels)].filter(Boolean).join(' · '),
|
||
href: i.url,
|
||
}))}
|
||
/>
|
||
) : (
|
||
<Prose>
|
||
<p>
|
||
<T values={{ labels: labels.join(' or '), orgs: orgs.join(', ') }}>
|
||
{'No open issue in {orgs} is labelled {labels} right now. Every open issue there is fair game: a merged fix earns the same points.'}
|
||
</T>
|
||
</p>
|
||
</Prose>
|
||
)}
|
||
<Cta href={issues}>
|
||
<T>Browse the open issues on GitHub</T>
|
||
</Cta>
|
||
</Section>
|
||
|
||
<Section title={phrase('Claim a bounty in four steps')}>
|
||
<CardGrid
|
||
columns={2}
|
||
items={[
|
||
{ title: phrase('1. Pick an issue'), description: phrase('An open bounty below, or any open issue in a public Hanzo repository. Say on the issue that you are taking it, so two people do not do the same work.') },
|
||
{ title: phrase('2. Link GitHub'), description: phrase('Link the GitHub account you work from to your Hanzo account, so the merge counts for you.') },
|
||
{ title: phrase('3. Open a pull request'), description: phrase('Follow the org’s contributing guide and name the issue in the pull request. A maintainer reviews it.') },
|
||
{ title: phrase('4. Merged, then awarded'), description: phrase('The points come when a maintainer merges it, never when it opens. A bounty’s tier replaces the ordinary award for the same work.') },
|
||
]}
|
||
/>
|
||
</Section>
|
||
|
||
<Section title={phrase('How each org takes contributions')}>
|
||
<Prose>
|
||
<ul>
|
||
{orgs.map((o) => (
|
||
<li key={o}>
|
||
<a href={guide(o)}>{`${o}/.github/CONTRIBUTING.md`}</a>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</Prose>
|
||
</Section>
|
||
|
||
<Section title={phrase('My contributions')}>
|
||
<Mine rules={rules} />
|
||
</Section>
|
||
|
||
<Section title={phrase('The rules')}>
|
||
<Prose>
|
||
<ul>
|
||
<li>
|
||
<T>Maintainers decide. Points come only after a maintainer merges your pull request or labels your issue.</T>
|
||
</li>
|
||
<li>
|
||
<T>Each pull request and each issue earns once: a bug fix, docs, a translation or a bounty counts in place of the merge, not beside it. A pull request you merge yourself earns nothing.</T>
|
||
</li>
|
||
{cap ? (
|
||
<li>
|
||
<T values={{ n: cap.limit }}>{'At most {n} contribution points per person per UTC day. An exceptional award beyond it is made by hand, after review.'}</T>
|
||
</li>
|
||
) : null}
|
||
<li>
|
||
{security ? (
|
||
<T values={{ email: <a href="mailto:[email protected]">security@hanzo.ai</a>, n: security.points }}>
|
||
{'Report a security issue to {email} only, never in a public issue or pull request. A valid report earns +{n}, awarded by the security team after review, and it never shows on the public board.'}
|
||
</T>
|
||
) : (
|
||
<T values={{ email: <a href="mailto:[email protected]">security@hanzo.ai</a> }}>{'Report a security issue to {email} only, never in a public issue or pull request.'}</T>
|
||
)}
|
||
</li>
|
||
<li>
|
||
<T>Work done before you link GitHub is held for your GitHub account and joins your points when you link it.</T>
|
||
</li>
|
||
{rules.excluded.map((x) => (
|
||
<li key={x}>{x}</li>
|
||
))}
|
||
</ul>
|
||
</Prose>
|
||
<Cta href="/leaderboard">
|
||
<T>See the leaderboard</T>
|
||
</Cta>
|
||
</Section>
|
||
</Page>
|
||
)
|
||
}
|