import { useQuery } from '@tanstack/react-query'; import { CircleDotIcon, MessageSquareIcon } from 'lucide-react'; import { ButtonLink } from '#/components/atoms/button-link.jsx'; import { EmptyState } from '#/components/molecules/empty-state.jsx'; import { Handle } from '#/components/molecules/handle.jsx'; import { issueLook } from '#/components/molecules/issue-state.jsx'; import { ListBox } from '#/components/molecules/list-box.jsx'; import { Dot, ListCount, ListRow } from '#/components/molecules/list-row.jsx'; import { ListSkeleton } from '#/components/molecules/list-skeleton.jsx'; import { PageBar } from '#/components/molecules/page-bar.jsx'; import { PageNotice } from '#/components/molecules/page-notice.jsx'; import { Screen } from '#/components/molecules/screen.jsx'; import { TabBar } from '#/components/molecules/tab-bar.jsx'; import { timeAgo } from '#/lib/format.js'; import { account, repoRecord } from '#/lib/git.js'; import { issueThreads, loadIssueStatements } from '#/lib/issues.js'; /** * The two questions a reader has: what is live, and what was answered. A tab * is a filter rather than a state, so closed holds both of the words the * owner can settle a thread with, and the mark on each row says which one * they used. */ const TABS = [ { key: 'open', label: 'Open', holds: ['open'], nothing: 'Every issue anyone has filed has been answered.', }, { key: 'closed', label: 'Closed', holds: ['resolved', 'declined'], nothing: 'The owner has settled none of these.', }, ]; /** * The repository's issues: every thread, newest activity first, each with * the state the owner's latest disposition gives it. * * Anyone signed in files one, into their own repo. Listed voices show at * once; anybody else's arrives through the runners' index, so a stranger's * issue appears when a runner has seen it. * * One read fills both tabs, so moving between them costs nothing. */ export function IssuesPage({ repo, tab, space }) { const record = repoRecord(repo, space); const { data: statements } = useQuery({ queryKey: ['issues', repo, space ?? ''], enabled: Boolean(record), queryFn: () => loadIssueStatements(repo, space), }); if (!record) { return No such repository.; } const threads = statements ? issueThreads(statements, account.did) : null; const active = TABS.find((one) => one.key === tab) ?? TABS[0]; const at = `/${encodeURIComponent(repo)}/issues`; const held = (entry) => (threads ?? []).filter((one) => entry.holds.includes(one.state.state)); const shown = threads ? held(active) : []; return ( New issue } > ({ key: entry.key, label: entry.label, href: entry.key === 'open' ? at : `${at}/${entry.key}`, count: threads ? held(entry).length : undefined, }))} /> } > {!threads ? ( ) : shown.length === 0 ? ( } title={ threads.length === 0 ? 'No issues' : `Nothing ${active.label.toLowerCase()}` } description={ threads.length === 0 ? 'No one has filed an issue here yet.' : active.nothing } /> ) : (
{shown.map(({ root, thread, state }) => { const look = issueLook(state.state); const rkey = root.uri?.split('/').pop(); return ( } > {/* A sentence rather than a row of states. The mark and the tab both say the state, so the words say when the owner said it, which nothing else on the row can. */} opened {timeAgo(root.createdAt)} {state.said && ( <> {state.said.disposition} {timeAgo(state.said.createdAt)} )} ); })}
)}
); }