page.tsx103 lines · main
| 1 | import Image from 'next/image'; |
| 2 | import Link from 'next/link'; |
| 3 | |
| 4 | import { SignInForm } from './sign-in-form'; |
| 5 | |
| 6 | export const metadata = { |
| 7 | title: 'sign in', |
| 8 | }; |
| 9 | |
| 10 | function 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 | |
| 25 | export 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 | }; |
| 46 | |
| 47 | return ( |
| 48 | <main className="relative flex min-h-dvh items-center justify-center bg-[var(--color-bg)] px-6 text-[var(--color-text)]"> |
| 49 | <div className="w-full max-w-sm"> |
| 50 | <Link href="/" className="mb-10 flex items-center gap-3" aria-label="briven home"> |
| 51 | <Image src="/icon.svg" alt="" width={28} height={28} priority /> |
| 52 | <span className="font-mono text-sm">briven</span> |
| 53 | </Link> |
| 54 | |
| 55 | <h1 className="font-mono text-2xl tracking-tight">sign in</h1> |
| 56 | |
| 57 | {errorMessage ? ( |
| 58 | <div |
| 59 | role="alert" |
| 60 | className="mt-6 rounded-md border border-[var(--color-text-error)] bg-red-500/5 p-4 font-mono text-xs text-red-300" |
| 61 | > |
| 62 | {errorMessage} |
| 63 | </div> |
| 64 | ) : null} |
| 65 | |
| 66 | {justDeleted ? ( |
| 67 | <div |
| 68 | role="status" |
| 69 | 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)]" |
| 70 | > |
| 71 | <p className="text-[var(--color-text)]">your account was deleted.</p> |
| 72 | <p className="mt-1"> |
| 73 | we've sent a confirmation to your inbox. you have 30 days to revert via |
| 74 | support before the data is hard-deleted. |
| 75 | </p> |
| 76 | </div> |
| 77 | ) : null} |
| 78 | |
| 79 | <div className="mt-8"> |
| 80 | <SignInForm next={next} apiOrigin={apiOrigin} providers={providers} /> |
| 81 | </div> |
| 82 | |
| 83 | <p className="mt-10 font-mono text-xs text-[var(--color-text-subtle)]"> |
| 84 | by signing in you agree to the{' '} |
| 85 | <Link |
| 86 | href="/terms" |
| 87 | className="underline underline-offset-2 hover:text-[var(--color-text)]" |
| 88 | > |
| 89 | terms |
| 90 | </Link>{' '} |
| 91 | and{' '} |
| 92 | <Link |
| 93 | href="/privacy" |
| 94 | className="underline underline-offset-2 hover:text-[var(--color-text)]" |
| 95 | > |
| 96 | privacy |
| 97 | </Link>{' '} |
| 98 | policy. |
| 99 | </p> |
| 100 | </div> |
| 101 | </main> |
| 102 | ); |
| 103 | } |