import { Avatar } from "~/ui/Avatar"; import { EmptyState } from "~/ui/EmptyState"; import { RelativeTime } from "~/ui/RelativeTime"; import type { JsonValue } from "~/lib/types"; import { TextLink } from "~/ui/TextLink"; export type FeedItem = { id: string; actor: { name: string }; repo: { owner: string; name: string } | null; kind: string; data: { [key: string]: JsonValue }; createdAt: number; }; function text(value: JsonValue | undefined): string { return typeof value === "string" || typeof value === "number" ? String(value) : ""; } /** One-line description of an activity row, e.g. "opened pull request #4 Fix parser". */ export function describeActivity(item: Pick): string { const { data } = item; switch (item.kind) { case "push": return `pushed ${text(data.total)} commit(s) to ${text(data.ref)}`; case "ref_created": return `created ${text(data.refType)} ${text(data.ref)}`; case "ref_deleted": return `deleted ${text(data.refType)} ${text(data.ref)}`; case "repo_created": return "created repository"; case "repo_imported": return `imported from ${text(data.source)}`; case "repo_forked": return `forked ${text(data.source)}`; case "issue_opened": return `opened issue #${text(data.number)} ${text(data.title)}`; case "pull_opened": return `opened pull request #${text(data.number)} ${text(data.title)}`; case "pull_merged": return `merged pull request #${text(data.number)} ${text(data.title)}`; case "comment": return `commented on #${text(data.number)} ${text(data.title)}`; case "release": return `published release ${text(data.name)}`; case "repo_deleted": return `deleted ${text(data.name)}`; default: return item.kind.replace(/_/g, " "); } } export function ActivityFeed({ items }: { items: FeedItem[] }) { if (items.length === 0) return ( Star or watch repositories to follow what happens in them. ); return (
    {items.map((item) => (
  1. {item.actor.name} {" "} {describeActivity(item)} {item.repo && ( <> {" "} in{" "} {item.repo.owner}/{item.repo.name} )}
  2. ))}
); }