enable-button.tsx73 lines · main
1'use client';
2
3import { useRouter } from 'next/navigation';
4import { useState } from 'react';
5
6interface Props {
7 projectId: string;
8}
9
10interface EnableResponse {
11 ok: true;
12 tables: number;
13 authUrl: string;
14 basePath: string;
15}
16
17interface ErrorBody {
18 code?: string;
19 message?: string;
20}
21
22/**
23 * Triggers POST /v1/projects/:id/auth/enable, then refreshes the route so
24 * the server component re-renders in the enabled state. Idempotent — the
25 * api endpoint re-running on an already-enabled project is a no-op (every
26 * DDL statement is IF NOT EXISTS).
27 */
28export function EnableAuthButton({ projectId }: Props) {
29 const router = useRouter();
30 const [pending, setPending] = useState(false);
31 const [errMsg, setErrMsg] = useState<string | null>(null);
32
33 async function enable(): Promise<void> {
34 setPending(true);
35 setErrMsg(null);
36 try {
37 const res = await fetch(`/api/v1/projects/${projectId}/auth/enable`, {
38 method: 'POST',
39 credentials: 'include',
40 headers: { 'content-type': 'application/json' },
41 });
42 if (!res.ok) {
43 const body = (await res.json().catch(() => ({}))) as ErrorBody;
44 throw new Error(body.message ?? body.code ?? `http ${res.status}`);
45 }
46 const body = (await res.json()) as EnableResponse;
47 if (!body.ok) {
48 throw new Error('enable returned non-ok response');
49 }
50 // Server component re-fetches `{enabled, config}` and renders the
51 // configured state. router.refresh() forces a fresh server render
52 // without a full page reload.
53 router.refresh();
54 } catch (err) {
55 setErrMsg(err instanceof Error ? err.message : 'enable failed');
56 setPending(false);
57 }
58 }
59
60 return (
61 <div className="flex flex-col gap-2">
62 <button
63 type="button"
64 onClick={() => void enable()}
65 disabled={pending}
66 className="self-start rounded-md bg-[var(--color-primary)] px-4 py-2 font-mono text-xs font-medium text-[var(--color-text-inverse)] transition hover:bg-[var(--color-primary-hover)] disabled:opacity-50"
67 >
68 {pending ? 'provisioning…' : 'enable auth'}
69 </button>
70 {errMsg ? <p className="font-mono text-xs text-[var(--color-error)]">{errMsg}</p> : null}
71 </div>
72 );
73}