Skip to content
File

Blob: src/client/pages/me.tsx

typescript113 lines
1import { type ReactNode } from "react";
2import { Copy, UserCircle2 } from "lucide-react";
3 
4import { useMe } from "@/client/auth/use-me";
5import { ErrorBanner } from "@/client/components/error-banner";
6import { LoadingPanel } from "@/client/components/loading-panel";
7import { Card, CardDescription, CardHeader, CardTitle } from "@/client/components/ui/card";
8import { Copyable } from "@/client/components/ui/copyable";
9import { PageHeader } from "@/client/components/ui/page-header";
10import { Button } from "@/client/components/ui/button";
11import { toast } from "@/client/lib/toast";
12 
13function FieldRow({ label, children }: { label: string; children: ReactNode }) {
14 return (
15 <div className="flex flex-col gap-1.5 border-b border-zinc-800/60 py-3 last:border-0">
16 <dt className="text-xs font-medium uppercase tracking-widest text-zinc-500">{label}</dt>
17 <dd>{children}</dd>
18 </div>
19 );
20}
21 
22export function MePage() {
23 const me = useMe();
24 if (me.isPending) return <LoadingPanel label="Loading your profile…" />;
25 if (me.error) return <ErrorBanner error={me.error} title="Could not load your profile" />;
26 if (!me.data) return <ErrorBanner error={"Not signed in."} title="No active session" />;
27 
28 const { subject_id, host_label, control_plane_host, dav_host, urls } = me.data;
29 
30 const copyAll = () => {
31 const payload = `WebDAV / Files: ${urls.files}\nCalDAV: ${urls.caldav}\nCardDAV: ${urls.carddav}`;
32 void navigator.clipboard.writeText(payload).then(
33 () => toast.success("Copied all DAV URLs."),
34 () => toast.error("Could not copy all DAV URLs."),
35 );
36 };
37 
38 return (
39 <div className="space-y-8">
40 <PageHeader
41 icon={<UserCircle2 className="h-7 w-7" strokeWidth={2} aria-hidden="true" />}
42 title="Your profile"
43 description="Paste a discovery URL into your DAV client to reach this account."
44 />
45
46 <Card>
47 <CardHeader>
48 <div>
49 <CardTitle>Discovery URLs</CardTitle>
50 <CardDescription>
51 Most clients only need the root URL — calendar-home and addressbook-home are exposed via DAV discovery
52 from there.
53 </CardDescription>
54 </div>
55 <Button
56 size="sm"
57 variant="ghost"
58 onClick={copyAll}
59 leftIcon={<Copy className="h-3 w-3" aria-hidden="true" />}
60 >
61 Copy all
62 </Button>
63 </CardHeader>
64 <dl className="space-y-3">
65 <FieldRow label="Root">
66 <Copyable value={urls.root} label="root URL" mono={false} />
67 </FieldRow>
68 <FieldRow label="Files (WebDAV)">
69 <Copyable value={urls.files} label="files URL" mono={false} />
70 </FieldRow>
71 <FieldRow label="CalDAV">
72 <Copyable value={urls.caldav} label="CalDAV URL" mono={false} />
73 </FieldRow>
74 <FieldRow label="CardDAV">
75 <Copyable value={urls.carddav} label="CardDAV URL" mono={false} />
76 </FieldRow>
77 <FieldRow label="API base">
78 <Copyable value={urls.api} label="API base URL" mono={false} />
79 </FieldRow>
80 </dl>
81 </Card>
82
83 <Card>
84 <CardHeader>
85 <div>
86 <CardTitle>This host</CardTitle>
87 <CardDescription>The opaque label your subject is reached under.</CardDescription>
88 </div>
89 </CardHeader>
90 <dl className="space-y-3">
91 <FieldRow label="Host label">
92 <span className="font-mono text-sm text-zinc-200">{host_label}</span>
93 </FieldRow>
94 <FieldRow label="DAV host">
95 <span className="text-sm text-zinc-200">{dav_host}</span>
96 </FieldRow>
97 <FieldRow label="Control plane">
98 <span className="text-sm text-zinc-200">{control_plane_host}</span>
99 </FieldRow>
100 </dl>
101 <details className="mt-4 border-t border-zinc-800/60 pt-3 text-xs text-zinc-500">
102 <summary className="cursor-pointer text-zinc-400 hover:text-zinc-200">Internal identifiers</summary>
103 <dl className="mt-2">
104 <FieldRow label="Subject ID">
105 <span className="font-mono text-sm text-zinc-200">{subject_id}</span>
106 </FieldRow>
107 </dl>
108 </details>
109 </Card>
110 </div>
111 );
112}