SPB Git

Repository Public MIT

page.tsx 5.0 KB 68 lines tsx

1import Link from "next/link";2import { ARTIFACT_TYPES, TYPE_LABELS } from "@skills/registry-core";3import { ActionForm, Field } from "@/components/action-form";4import { StateBadge } from "@/components/badges";5import { relative } from "@/lib/format";6import { requireSession } from "@/lib/session";7import { listWritableNamespaces } from "@/server/auth";8import { createArtifactAction } from "../actions";910export const metadata = { title: "Artifacts" };1112export default async function Page() {13  const { ctx, principal } = await requireSession();14  const namespaces = await listWritableNamespaces(ctx, principal.user);15  const rows = namespaces.length16    ? await ctx.sql<{ id: string; namespace: string; slug: string; type: string; visibility: string; state: string; updated_at: Date; latest: string | null; releases: number }[]>`17        SELECT a.id, a.namespace, a.slug, a.type, a.visibility, a.state, a.updated_at, lr.version AS latest, (SELECT count(*)::int FROM releases r WHERE r.artifact_id = a.id) AS releases18        FROM artifacts a LEFT JOIN releases lr ON lr.id = a.latest_release_id WHERE a.namespace IN ${ctx.sql(namespaces)} ORDER BY a.updated_at DESC`19    : [];20  return (21    <div>22      <h1 className="text-2xl font-semibold tracking-tight">Artifacts</h1>23      <div className="mt-6 overflow-x-auto rounded-lg border border-line">24        <table className="w-full text-left text-sm">25          <thead className="border-b border-line text-xs text-faint">26            <tr><th scope="col" className="px-3 py-2 font-medium">Name</th><th scope="col" className="px-3 py-2 font-medium">Type</th><th scope="col" className="px-3 py-2 font-medium">Visibility</th><th scope="col" className="px-3 py-2 font-medium">Latest</th><th scope="col" className="px-3 py-2 font-medium">Releases</th><th scope="col" className="px-3 py-2 font-medium">Updated</th></tr>27          </thead>28          <tbody className="divide-y divide-line">29            {rows.map((a) => (30              <tr key={a.id}>31                <td className="px-3 py-2 font-mono"><Link className="link" href={`/dashboard/artifacts/${a.id}`}>@{a.namespace}/{a.slug}</Link> <StateBadge state={a.state} /></td>32                <td className="px-3 py-2 text-muted">{a.type}</td>33                <td className="px-3 py-2 text-muted">{a.visibility}</td>34                <td className="px-3 py-2 font-mono text-muted">{a.latest ?? "—"}</td>35                <td className="px-3 py-2 text-muted">{a.releases}</td>36                <td className="whitespace-nowrap px-3 py-2 text-muted">{relative(a.updated_at)}</td>37              </tr>38            ))}39            {rows.length === 0 && <tr><td colSpan={6} className="px-3 py-6 text-center text-muted">You have not created an artifact yet.</td></tr>}40          </tbody>41        </table>42      </div>4344      <section className="mt-10 max-w-2xl">45        <h2 className="mb-1 text-base font-semibold">New artifact</h2>46        <p className="mb-4 text-sm text-muted">This reserves the name. Content arrives with the first release; <code className="font-mono">skills publish</code> can also create the artifact for you.</p>47        {namespaces.length === 0 ? (48          <p className="card p-5 text-sm text-muted">You need a namespace before creating artifacts.</p>49        ) : (50          <ActionForm action={createArtifactAction} submit="Create artifact" className="card space-y-4 p-6">51            <div className="grid gap-4 sm:grid-cols-2">52              <Field label="Namespace" name="namespace"><select id="f-namespace" name="namespace" className="input">{namespaces.map((n) => <option key={n} value={n}>@{n}</option>)}</select></Field>53              <Field label="Slug" name="slug" required pattern="[a-z0-9]([a-z0-9]|-[a-z0-9])*" maxLength={64} placeholder="my-skill" autoComplete="off" hint="Lowercase letters, digits, single hyphens. Cannot be changed later." />54            </div>55            <div className="grid gap-4 sm:grid-cols-2">56              <Field label="Type" name="type"><select id="f-type" name="type" className="input">{ARTIFACT_TYPES.map((x) => <option key={x} value={x}>{TYPE_LABELS[x].singular}</option>)}</select></Field>57              <Field label="Visibility" name="visibility"><select id="f-visibility" name="visibility" defaultValue="private" className="input"><option value="private">Private</option><option value="unlisted">Unlisted (link only)</option><option value="public">Public</option></select></Field>58            </div>59            <Field label="Summary" name="summary" required minLength={10} maxLength={300} placeholder="One sentence: what it does and when to use it." />60            <label className="flex items-start gap-2 text-sm text-muted"><input type="checkbox" name="confirmPublic" className="mt-1" /> I confirm this artifact may be publicly visible (required for Public).</label>61            <Field label="Password" name="password" type="password" autoComplete="current-password" hint="Only needed for a public artifact if you signed in more than 15 minutes ago." />62          </ActionForm>63        )}64      </section>65    </div>66  );67}68
Files main
Menu

Theme