page.tsx61 lines · main
1import { redirect } from 'next/navigation';
2
3import { apiFetch } from '../../../../../lib/api';
4
5export const metadata = { title: 'accept invitation' };
6export const dynamic = 'force-dynamic';
7
8interface AcceptResult {
9 projectId: string;
10 userId: string;
11 role: string;
12}
13
14export default async function AcceptInvitationPage({
15 searchParams,
16}: {
17 searchParams: Promise<{ token?: string }>;
18}) {
19 const { token } = await searchParams;
20
21 if (!token) {
22 return (
23 <main className="mx-auto max-w-lg px-6 py-16 font-mono text-sm">
24 <h1 className="text-xl">invitation token missing</h1>
25 <p className="mt-2 text-[var(--color-text-muted)]">
26 use the link from the invitation email. if it's expired, ask the project owner to resend.
27 </p>
28 </main>
29 );
30 }
31
32 const res = await apiFetch('/v1/me/invitations/accept', {
33 method: 'POST',
34 headers: { 'content-type': 'application/json' },
35 body: JSON.stringify({ token }),
36 });
37
38 if (res.status === 401) {
39 redirect(`/signin?next=${encodeURIComponent(`/dashboard/invitations/accept?token=${token}`)}`);
40 }
41
42 if (!res.ok) {
43 const body = (await res.json().catch(() => ({}))) as { message?: string };
44 return (
45 <main className="mx-auto max-w-lg px-6 py-16 font-mono text-sm">
46 <h1 className="text-xl">couldn't accept this invitation</h1>
47 <p className="mt-2 text-[var(--color-text-muted)]">
48 {body.message ?? `http ${res.status}`}
49 </p>
50 <p className="mt-6">
51 <a href="/dashboard" className="text-[var(--color-text-link)]">
52 back to dashboard
53 </a>
54 </p>
55 </main>
56 );
57 }
58
59 const result = (await res.json()) as AcceptResult;
60 redirect(`/dashboard/projects/${result.projectId}`);
61}