SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
10 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%
3.3 KB · 77 lines tsx
Raw Blame History
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