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