page.tsx 4.1 KB

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