TypeScript 97.5%
SQL 1.4%
Python 0.8%
1import Link from "next/link";2import { ExternalLink, FileCheck2 } from "lucide-react";3import { getWorkspace } from "@/lib/session";4import { listLegalAcceptances } from "@/lib/queries/account";5import { formatDate } from "@/lib/format";6import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";7import { EmptyState } from "@/components/ui/empty-state";8import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";910export const dynamic = "force-dynamic";1112const DOCUMENTS: Record<string, { label: string; href: string }> = {13 terms: { label: "Terms of Service", href: "/legal/terms" },14 privacy: { label: "Privacy Policy", href: "/legal/privacy" },15 acceptable_use: { label: "Acceptable Use Policy", href: "/legal/acceptable-use" },16};1718export default async function LegalPage() {19 const ws = await getWorkspace();20 const rows = await listLegalAcceptances(ws.user.id);2122 return (23 <div className="grid gap-6">24 <div className="grid gap-4">25 <div>26 <h2 className="text-[15px] font-semibold tracking-tight">Accepted documents</h2>27 <p className="text-[13px] text-fg-muted">Every version you agreed to, with the time and IP address recorded at acceptance.</p>28 </div>29 {rows.length === 0 ? (30 <EmptyState icon={FileCheck2} title="No acceptances recorded" description="Acceptances are recorded when you sign up. If your account predates this record, they will be requested the next time terms change." />31 ) : (32 <div className="overflow-hidden rounded-lg border border-border bg-bg-elevated shadow-xs">33 <Table>34 <TableHeader>35 <TableRow className="hover:bg-transparent">36 <TableHead>Document</TableHead>37 <TableHead>Version</TableHead>38 <TableHead>Accepted</TableHead>39 <TableHead>IP address</TableHead>40 </TableRow>41 </TableHeader>42 <TableBody>43 {rows.map((r) => {44 const doc = DOCUMENTS[r.document];45 return (46 <TableRow key={r.id}>47 <TableCell>48 {doc ? (49 <Link href={doc.href} className="inline-flex items-center gap-1 font-medium underline-offset-4 hover:underline">50 {doc.label} <ExternalLink className="size-3 text-fg-subtle" aria-hidden />51 </Link>52 ) : (53 <span className="font-medium">{r.document}</span>54 )}55 </TableCell>56 <TableCell className="font-mono text-[12.5px] tabular">{r.version}</TableCell>57 <TableCell className="whitespace-nowrap tabular text-fg-muted">{formatDate(r.acceptedAt)}</TableCell>58 <TableCell className="font-mono text-[12.5px] tabular">{r.ipAddress ?? "—"}</TableCell>59 </TableRow>60 );61 })}62 </TableBody>63 </Table>64 </div>65 )}66 </div>6768 <Card>69 <CardHeader>70 <CardTitle>Current documents</CardTitle>71 <CardDescription>The versions in force today. We email you before material changes take effect.</CardDescription>72 </CardHeader>73 <CardContent>74 <ul className="grid gap-2 text-[13.5px] sm:grid-cols-3">75 {Object.values(DOCUMENTS).map((d) => (76 <li key={d.href}>77 <Link href={d.href} className="inline-flex items-center gap-1.5 text-fg underline-offset-4 hover:underline">78 {d.label} <ExternalLink className="size-3 text-fg-subtle" aria-hidden />79 </Link>80 </li>81 ))}82 </ul>83 <p className="mt-4 text-[12.5px] text-fg-subtle">84 Data processing questions: <a href="mailto:privacy@fetcha.co" className="underline-offset-4 hover:underline">privacy@fetcha.co</a>85 </p>86 </CardContent>87 </Card>88 </div>89 );90}91