TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1import type { Metadata } from 'next';2import { desc, eq } from '@/lib/db';3import { db, apiKeys } from '@/lib/db';4import { requireUser } from '@/lib/auth/session';5import { revokeApiKeyAction } from '@/lib/auth/account-actions';6import { PageHeader, btnDanger } from '@/components/account/page-header';7import { Card, CardHeader, Badge, Table, th, td } from '@/components/ui/primitives';8import { fmtRelative } from '@/lib/format';9import { CreateKeyForm } from './create-key-form';1011export const metadata: Metadata = { title: 'API keys', robots: { index: false } };1213export default async function ApiKeysPage() {14 const u = await requireUser('/account/api-keys');15 const keys = await db().select().from(apiKeys).where(eq(apiKeys.userId, u.id)).orderBy(desc(apiKeys.createdAt));16 return (17 <>18 <PageHeader title="API keys" description="Programmatic access to RareIndex data. Keys are shown once and stored hashed. Send them as `Authorization: Bearer <key>`." />19 <div className="grid gap-4 lg:grid-cols-[minmax(0,1fr)_320px]">20 <Card>21 <CardHeader title="Your keys" subtitle={`${keys.filter((k) => !k.revokedAt).length} active`} />22 <Table>23 <thead>24 <tr>25 <th className={th}>Name</th>26 <th className={th}>Prefix</th>27 <th className={th}>Tier</th>28 <th className={th}>Limits</th>29 <th className={th}>Last used</th>30 <th className={th}></th>31 </tr>32 </thead>33 <tbody>34 {keys.map((k) => (35 <tr key={k.id} className={k.revokedAt ? 'opacity-50' : ''}>36 <td className={td}>{k.name}</td>37 <td className={`${td} mono-num text-xs`}>{k.prefix}…</td>38 <td className={td}>39 <Badge tone={k.tier === 'free' ? 'neutral' : 'index'}>{k.tier}</Badge>40 </td>41 <td className={`${td} text-xs text-muted`}>42 {k.rateLimitPerMinute}/min · {k.dailyQuota.toLocaleString()}/day43 </td>44 <td className={`${td} text-xs text-muted`}>{k.lastUsedAt ? fmtRelative(k.lastUsedAt) : 'never'}</td>45 <td className={td}>46 {k.revokedAt ? (47 <span className="text-xs text-subtle">revoked</span>48 ) : (49 <form action={revokeApiKeyAction}>50 <input type="hidden" name="keyId" value={k.id} />51 <button className={btnDanger}>Revoke</button>52 </form>53 )}54 </td>55 </tr>56 ))}57 {keys.length === 0 ? (58 <tr>59 <td className={td} colSpan={6}>60 <span className="text-muted">No keys yet. Create one to query `/v1/assets/search`, `/v1/indices` and more.</span>61 </td>62 </tr>63 ) : null}64 </tbody>65 </Table>66 </Card>67 <Card>68 <CardHeader title="Create a key" subtitle="Free tier: 30 requests/min, 1,000/day." />69 <div className="p-4">70 <CreateKeyForm />71 </div>72 </Card>73 </div>74 </>75 );76}77