page.tsx105 lines · main
1import Image from 'next/image';
2import Link from 'next/link';
3
4import { SignInForm } from './sign-in-form';
5
6export const metadata = {
7 title: 'sign in',
8};
9
10function describeError(code: string | undefined): string | null {
11 if (!code) return null;
12 // Map the most common Better Auth + provider error codes to human copy.
13 // Default fallback: surface the code verbatim so an operator chasing
14 // a regression can grep it.
15 if (code.startsWith('oauth_')) {
16 const provider = code.slice('oauth_'.length);
17 return `the ${provider} sign-in didn't complete. this is usually a temporary issue — try again. if it keeps failing, check that your ${provider} app's authorized redirect URI matches https://api.briven.tech/v1/auth/callback/${provider}.`;
18 }
19 if (code === 'state_mismatch') {
20 return 'the sign-in didn\'t complete because your browser blocked a cookie. try again in a private window, or check your browser settings.';
21 }
22 return `sign-in failed (${code}). try again or use a different method.`;
23}
24
25export default async function SignInPage({
26 searchParams,
27}: {
28 searchParams: Promise<{ next?: string; error?: string; deleted?: string }>;
29}) {
30 const params = await searchParams;
31 const next = params.next ?? '/dashboard';
32 const errorMessage = describeError(params.error);
33 const justDeleted = params.deleted === '1';
34
35 // Public api origin — surfaced via NEXT_PUBLIC_BRIVEN_API_ORIGIN so the
36 // signin form posts directly to it instead of going through Next.js's
37 // /api/* rewrite (which proxy edges sometimes mangle).
38 const apiOrigin = process.env.NEXT_PUBLIC_BRIVEN_API_ORIGIN ?? '';
39 // Each provider flag mirrors a server-side env: when the credentials
40 // aren't configured, hide the button so users don't trigger a 500.
41 const providers = {
42 google: process.env.NEXT_PUBLIC_BRIVEN_HAS_GOOGLE_OAUTH === 'true',
43 github: process.env.NEXT_PUBLIC_BRIVEN_HAS_GITHUB_OAUTH === 'true',
44 discord: process.env.NEXT_PUBLIC_BRIVEN_HAS_DISCORD_OAUTH === 'true',
45 // Git at code.konnos.org — platform genericOAuth (see apps/api/src/lib/auth.ts)
46 konnos: process.env.NEXT_PUBLIC_BRIVEN_HAS_KONNOS_OAUTH === 'true',
47 };
48
49 return (
50 <main className="relative flex min-h-dvh items-center justify-center bg-[var(--color-bg)] px-6 text-[var(--color-text)]">
51 <div className="w-full max-w-sm">
52 <Link href="/" className="mb-10 flex items-center gap-3" aria-label="briven home">
53 <Image src="/icon.svg" alt="" width={28} height={28} priority />
54 <span className="font-mono text-sm">briven</span>
55 </Link>
56
57 <h1 className="font-mono text-2xl tracking-tight">sign in</h1>
58
59 {errorMessage ? (
60 <div
61 role="alert"
62 className="mt-6 rounded-md border border-[var(--color-text-error)] bg-red-500/5 p-4 font-mono text-xs text-red-300"
63 >
64 {errorMessage}
65 </div>
66 ) : null}
67
68 {justDeleted ? (
69 <div
70 role="status"
71 className="mt-6 rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] p-4 font-mono text-xs text-[var(--color-text-muted)]"
72 >
73 <p className="text-[var(--color-text)]">your account was deleted.</p>
74 <p className="mt-1">
75 we&apos;ve sent a confirmation to your inbox. you have 30 days to revert via
76 support before the data is hard-deleted.
77 </p>
78 </div>
79 ) : null}
80
81 <div className="mt-8">
82 <SignInForm next={next} apiOrigin={apiOrigin} providers={providers} />
83 </div>
84
85 <p className="mt-10 font-mono text-xs text-[var(--color-text-subtle)]">
86 by signing in you agree to the{' '}
87 <Link
88 href="/terms"
89 className="underline underline-offset-2 hover:text-[var(--color-text)]"
90 >
91 terms
92 </Link>{' '}
93 and{' '}
94 <Link
95 href="/privacy"
96 className="underline underline-offset-2 hover:text-[var(--color-text)]"
97 >
98 privacy
99 </Link>{' '}
100 policy.
101 </p>
102 </div>
103 </main>
104 );
105}