@liedsonc/core-auth-kit 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +802 -759
  2. package/auth-ui/bin/init.js +126 -0
  3. package/auth-ui/components/auth-card.tsx +49 -49
  4. package/auth-ui/components/auth-form.tsx +53 -53
  5. package/auth-ui/components/error-message.tsx +24 -24
  6. package/auth-ui/components/form-field.tsx +39 -39
  7. package/auth-ui/components/loading-spinner.tsx +19 -19
  8. package/auth-ui/components/oauth-buttons.tsx +49 -49
  9. package/auth-ui/components/password-input.tsx +93 -93
  10. package/auth-ui/components/success-message.tsx +24 -24
  11. package/auth-ui/package.json +59 -55
  12. package/auth-ui/pages/forgot-password/index.tsx +83 -83
  13. package/auth-ui/pages/login/index.tsx +119 -119
  14. package/auth-ui/pages/register/index.tsx +149 -149
  15. package/auth-ui/pages/reset-password/index.tsx +133 -133
  16. package/auth-ui/pages/verify-email/index.tsx +143 -143
  17. package/auth-ui/templates/app/(auth)/forgot-password/page.tsx +5 -0
  18. package/auth-ui/templates/app/(auth)/layout.tsx +12 -0
  19. package/auth-ui/templates/app/(auth)/login/page.tsx +5 -0
  20. package/auth-ui/templates/app/(auth)/register/page.tsx +5 -0
  21. package/auth-ui/templates/app/(auth)/reset-password/page.tsx +5 -0
  22. package/auth-ui/templates/app/(auth)/verify-email/page.tsx +5 -0
  23. package/auth-ui/templates/app/api/auth/forgot-password/route.ts +16 -0
  24. package/auth-ui/templates/app/api/auth/login/route.ts +16 -0
  25. package/auth-ui/templates/app/api/auth/logout/route.ts +8 -0
  26. package/auth-ui/templates/app/api/auth/register/route.ts +16 -0
  27. package/auth-ui/templates/app/api/auth/reset-password/route.ts +16 -0
  28. package/auth-ui/templates/app/api/auth/session/route.ts +8 -0
  29. package/auth-ui/templates/app/api/auth/verify-email/route.ts +16 -0
  30. package/auth-ui/templates/env.example +25 -0
  31. package/auth-ui/templates/lib/auth-client.ts +20 -0
  32. package/auth-ui/templates/lib/auth-config.ts +43 -0
  33. package/auth-ui/tsconfig.json +4 -15
  34. package/package.json +17 -6
  35. package/tailwind.config.ts +41 -41
@@ -1,133 +1,133 @@
1
- "use client";
2
-
3
- import Link from "next/link";
4
- import { useAuth } from "../../hooks/use-auth";
5
- import { useSearchParams } from "next/navigation";
6
- import { AuthCard } from "../../components/auth-card";
7
- import { AuthForm } from "../../components/auth-form";
8
- import { FormField } from "../../components/form-field";
9
- import { PasswordInput } from "../../components/password-input";
10
- import { Button } from "../../components/ui/button";
11
- import { useState, Suspense } from "react";
12
-
13
- const genericError = "Something went wrong. Please request a new reset link.";
14
-
15
- function ResetPasswordForm() {
16
- const searchParams = useSearchParams();
17
- const token = searchParams.get("token") ?? "";
18
- const { resetPassword, loading, error, clearError } = useAuth();
19
- const [password, setPassword] = useState("");
20
- const [confirmPassword, setConfirmPassword] = useState("");
21
- const [fieldErrors, setFieldErrors] = useState<{ password?: string; confirm?: string }>({});
22
-
23
- const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
24
- e.preventDefault();
25
- clearError();
26
- setFieldErrors({});
27
- const err: { password?: string; confirm?: string } = {};
28
- if (!token) err.password = "Invalid or missing reset link.";
29
- if (!password) err.password = "Password is required.";
30
- if (password.length < 8) err.password = "Password must be at least 8 characters.";
31
- if (password !== confirmPassword) err.confirm = "Passwords do not match.";
32
- if (Object.keys(err).length) {
33
- setFieldErrors(err);
34
- return;
35
- }
36
- const result = await resetPassword(token, password);
37
- if (!result.success) setFieldErrors({ password: genericError });
38
- };
39
-
40
- if (!token) {
41
- return (
42
- <div className="flex min-h-screen flex-col items-center justify-center p-4">
43
- <AuthCard
44
- title="Reset password"
45
- subtitle="Invalid or expired link"
46
- footer={
47
- <Link
48
- href="/forgot-password"
49
- className="text-sm text-muted-foreground underline underline-offset-4 hover:text-foreground"
50
- >
51
- Request a new reset link
52
- </Link>
53
- }
54
- >
55
- <p className="text-sm text-muted-foreground">
56
- This reset link is invalid or has expired. Please request a new one.
57
- </p>
58
- </AuthCard>
59
- </div>
60
- );
61
- }
62
-
63
- return (
64
- <div className="flex min-h-screen flex-col items-center justify-center p-4">
65
- <AuthCard
66
- title="Reset password"
67
- subtitle="Enter your new password"
68
- footer={
69
- <div className="flex w-full justify-center text-sm text-muted-foreground">
70
- <Link
71
- href="/login"
72
- className="underline underline-offset-4 hover:text-foreground"
73
- >
74
- Back to sign in
75
- </Link>
76
- </div>
77
- }
78
- >
79
- <AuthForm onSubmit={handleSubmit} loading={loading} error={error ? genericError : undefined}>
80
- <FormField label="New password" htmlFor="reset-password" error={fieldErrors.password} required>
81
- <PasswordInput
82
- id="reset-password"
83
- placeholder="••••••••"
84
- autoComplete="new-password"
85
- value={password}
86
- onChange={(e) => setPassword(e.target.value)}
87
- disabled={loading}
88
- showStrength
89
- aria-invalid={!!fieldErrors.password}
90
- />
91
- </FormField>
92
- <FormField
93
- label="Confirm password"
94
- htmlFor="reset-confirm"
95
- error={fieldErrors.confirm}
96
- required
97
- >
98
- <PasswordInput
99
- id="reset-confirm"
100
- placeholder="••••••••"
101
- autoComplete="new-password"
102
- value={confirmPassword}
103
- onChange={(e) => setConfirmPassword(e.target.value)}
104
- disabled={loading}
105
- aria-invalid={!!fieldErrors.confirm}
106
- />
107
- </FormField>
108
- <Button type="submit" className="w-full" disabled={loading}>
109
- {loading ? (
110
- <span className="inline-block size-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
111
- ) : (
112
- "Reset password"
113
- )}
114
- </Button>
115
- </AuthForm>
116
- </AuthCard>
117
- </div>
118
- );
119
- }
120
-
121
- export function ResetPasswordPage() {
122
- return (
123
- <Suspense
124
- fallback={
125
- <div className="flex min-h-screen items-center justify-center">
126
- <span className="inline-block size-8 animate-spin rounded-full border-2 border-current border-t-transparent" />
127
- </div>
128
- }
129
- >
130
- <ResetPasswordForm />
131
- </Suspense>
132
- );
133
- }
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { useAuth } from "../../hooks/use-auth";
5
+ import { useSearchParams } from "next/navigation";
6
+ import { AuthCard } from "../../components/auth-card";
7
+ import { AuthForm } from "../../components/auth-form";
8
+ import { FormField } from "../../components/form-field";
9
+ import { PasswordInput } from "../../components/password-input";
10
+ import { Button } from "../../components/ui/button";
11
+ import { useState, Suspense } from "react";
12
+
13
+ const genericError = "Something went wrong. Please request a new reset link.";
14
+
15
+ function ResetPasswordForm() {
16
+ const searchParams = useSearchParams();
17
+ const token = searchParams.get("token") ?? "";
18
+ const { resetPassword, loading, error, clearError } = useAuth();
19
+ const [password, setPassword] = useState("");
20
+ const [confirmPassword, setConfirmPassword] = useState("");
21
+ const [fieldErrors, setFieldErrors] = useState<{ password?: string; confirm?: string }>({});
22
+
23
+ const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
24
+ e.preventDefault();
25
+ clearError();
26
+ setFieldErrors({});
27
+ const err: { password?: string; confirm?: string } = {};
28
+ if (!token) err.password = "Invalid or missing reset link.";
29
+ if (!password) err.password = "Password is required.";
30
+ if (password.length < 8) err.password = "Password must be at least 8 characters.";
31
+ if (password !== confirmPassword) err.confirm = "Passwords do not match.";
32
+ if (Object.keys(err).length) {
33
+ setFieldErrors(err);
34
+ return;
35
+ }
36
+ const result = await resetPassword(token, password);
37
+ if (!result.success) setFieldErrors({ password: genericError });
38
+ };
39
+
40
+ if (!token) {
41
+ return (
42
+ <div className="flex min-h-screen flex-col items-center justify-center p-4">
43
+ <AuthCard
44
+ title="Reset password"
45
+ subtitle="Invalid or expired link"
46
+ footer={
47
+ <Link
48
+ href="/forgot-password"
49
+ className="text-sm text-muted-foreground underline underline-offset-4 hover:text-foreground"
50
+ >
51
+ Request a new reset link
52
+ </Link>
53
+ }
54
+ >
55
+ <p className="text-sm text-muted-foreground">
56
+ This reset link is invalid or has expired. Please request a new one.
57
+ </p>
58
+ </AuthCard>
59
+ </div>
60
+ );
61
+ }
62
+
63
+ return (
64
+ <div className="flex min-h-screen flex-col items-center justify-center p-4">
65
+ <AuthCard
66
+ title="Reset password"
67
+ subtitle="Enter your new password"
68
+ footer={
69
+ <div className="flex w-full justify-center text-sm text-muted-foreground">
70
+ <Link
71
+ href="/login"
72
+ className="underline underline-offset-4 hover:text-foreground"
73
+ >
74
+ Back to sign in
75
+ </Link>
76
+ </div>
77
+ }
78
+ >
79
+ <AuthForm onSubmit={handleSubmit} loading={loading} error={error ? genericError : undefined}>
80
+ <FormField label="New password" htmlFor="reset-password" error={fieldErrors.password} required>
81
+ <PasswordInput
82
+ id="reset-password"
83
+ placeholder="••••••••"
84
+ autoComplete="new-password"
85
+ value={password}
86
+ onChange={(e) => setPassword(e.target.value)}
87
+ disabled={loading}
88
+ showStrength
89
+ aria-invalid={!!fieldErrors.password}
90
+ />
91
+ </FormField>
92
+ <FormField
93
+ label="Confirm password"
94
+ htmlFor="reset-confirm"
95
+ error={fieldErrors.confirm}
96
+ required
97
+ >
98
+ <PasswordInput
99
+ id="reset-confirm"
100
+ placeholder="••••••••"
101
+ autoComplete="new-password"
102
+ value={confirmPassword}
103
+ onChange={(e) => setConfirmPassword(e.target.value)}
104
+ disabled={loading}
105
+ aria-invalid={!!fieldErrors.confirm}
106
+ />
107
+ </FormField>
108
+ <Button type="submit" className="w-full" disabled={loading}>
109
+ {loading ? (
110
+ <span className="inline-block size-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
111
+ ) : (
112
+ "Reset password"
113
+ )}
114
+ </Button>
115
+ </AuthForm>
116
+ </AuthCard>
117
+ </div>
118
+ );
119
+ }
120
+
121
+ export function ResetPasswordPage() {
122
+ return (
123
+ <Suspense
124
+ fallback={
125
+ <div className="flex min-h-screen items-center justify-center">
126
+ <span className="inline-block size-8 animate-spin rounded-full border-2 border-current border-t-transparent" />
127
+ </div>
128
+ }
129
+ >
130
+ <ResetPasswordForm />
131
+ </Suspense>
132
+ );
133
+ }
@@ -1,143 +1,143 @@
1
- "use client";
2
-
3
- import Link from "next/link";
4
- import { useAuth } from "../../hooks/use-auth";
5
- import { useSearchParams } from "next/navigation";
6
- import { AuthCard } from "../../components/auth-card";
7
- import { SuccessMessage } from "../../components/success-message";
8
- import { ErrorMessage } from "../../components/error-message";
9
- import { Button } from "../../components/ui/button";
10
- import { useEffect, useState, Suspense } from "react";
11
-
12
- type VerifyState = "idle" | "loading" | "success" | "expired" | "invalid";
13
-
14
- function VerifyEmailContent() {
15
- const searchParams = useSearchParams();
16
- const token = searchParams.get("token") ?? "";
17
- const { verifyEmail, loading } = useAuth();
18
- const [state, setState] = useState<VerifyState>("idle");
19
- const [checked, setChecked] = useState(false);
20
-
21
- useEffect(() => {
22
- if (!token || checked) return;
23
- setChecked(true);
24
- setState("loading");
25
- verifyEmail(token).then((result) => {
26
- if (result.success) setState("success");
27
- else if (result.error.code === "EXPIRED" || result.error.code === "TOKEN_EXPIRED")
28
- setState("expired");
29
- else setState("invalid");
30
- });
31
- }, [token, checked, verifyEmail]);
32
-
33
- if (!token) {
34
- return (
35
- <div className="flex min-h-screen flex-col items-center justify-center p-4">
36
- <AuthCard
37
- title="Verify email"
38
- subtitle="Invalid verification link"
39
- footer={
40
- <Link
41
- href="/login"
42
- className="text-sm text-muted-foreground underline underline-offset-4 hover:text-foreground"
43
- >
44
- Go to sign in
45
- </Link>
46
- }
47
- >
48
- <ErrorMessage>This verification link is invalid.</ErrorMessage>
49
- </AuthCard>
50
- </div>
51
- );
52
- }
53
-
54
- if (state === "loading" || (state === "idle" && loading)) {
55
- return (
56
- <div className="flex min-h-screen flex-col items-center justify-center p-4">
57
- <AuthCard title="Verify email" subtitle="Verifying your email...">
58
- <div className="flex justify-center py-4">
59
- <span
60
- role="status"
61
- aria-label="Verifying"
62
- className="inline-block size-8 animate-spin rounded-full border-2 border-current border-t-transparent"
63
- />
64
- </div>
65
- </AuthCard>
66
- </div>
67
- );
68
- }
69
-
70
- if (state === "success") {
71
- return (
72
- <div className="flex min-h-screen flex-col items-center justify-center p-4">
73
- <AuthCard
74
- title="Email verified"
75
- subtitle="Your account is ready"
76
- footer={
77
- <Button asChild className="w-full">
78
- <Link href="/login">Sign in</Link>
79
- </Button>
80
- }
81
- >
82
- <SuccessMessage>Your email has been verified. You can now sign in.</SuccessMessage>
83
- </AuthCard>
84
- </div>
85
- );
86
- }
87
-
88
- if (state === "expired") {
89
- return (
90
- <div className="flex min-h-screen flex-col items-center justify-center p-4">
91
- <AuthCard
92
- title="Link expired"
93
- subtitle="This verification link has expired"
94
- footer={
95
- <Link
96
- href="/login"
97
- className="text-sm text-muted-foreground underline underline-offset-4 hover:text-foreground"
98
- >
99
- Go to sign in
100
- </Link>
101
- }
102
- >
103
- <ErrorMessage>
104
- This verification link has expired. Please sign in to request a new one.
105
- </ErrorMessage>
106
- </AuthCard>
107
- </div>
108
- );
109
- }
110
-
111
- return (
112
- <div className="flex min-h-screen flex-col items-center justify-center p-4">
113
- <AuthCard
114
- title="Verification failed"
115
- subtitle="We couldn't verify your email"
116
- footer={
117
- <Link
118
- href="/login"
119
- className="text-sm text-muted-foreground underline underline-offset-4 hover:text-foreground"
120
- >
121
- Go to sign in
122
- </Link>
123
- }
124
- >
125
- <ErrorMessage>This verification link is invalid or has already been used.</ErrorMessage>
126
- </AuthCard>
127
- </div>
128
- );
129
- }
130
-
131
- export function VerifyEmailPage() {
132
- return (
133
- <Suspense
134
- fallback={
135
- <div className="flex min-h-screen items-center justify-center">
136
- <span className="inline-block size-8 animate-spin rounded-full border-2 border-current border-t-transparent" />
137
- </div>
138
- }
139
- >
140
- <VerifyEmailContent />
141
- </Suspense>
142
- );
143
- }
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { useAuth } from "../../hooks/use-auth";
5
+ import { useSearchParams } from "next/navigation";
6
+ import { AuthCard } from "../../components/auth-card";
7
+ import { SuccessMessage } from "../../components/success-message";
8
+ import { ErrorMessage } from "../../components/error-message";
9
+ import { Button } from "../../components/ui/button";
10
+ import { useEffect, useState, Suspense } from "react";
11
+
12
+ type VerifyState = "idle" | "loading" | "success" | "expired" | "invalid";
13
+
14
+ function VerifyEmailContent() {
15
+ const searchParams = useSearchParams();
16
+ const token = searchParams.get("token") ?? "";
17
+ const { verifyEmail, loading } = useAuth();
18
+ const [state, setState] = useState<VerifyState>("idle");
19
+ const [checked, setChecked] = useState(false);
20
+
21
+ useEffect(() => {
22
+ if (!token || checked) return;
23
+ setChecked(true);
24
+ setState("loading");
25
+ verifyEmail(token).then((result) => {
26
+ if (result.success) setState("success");
27
+ else if (result.error.code === "EXPIRED" || result.error.code === "TOKEN_EXPIRED")
28
+ setState("expired");
29
+ else setState("invalid");
30
+ });
31
+ }, [token, checked, verifyEmail]);
32
+
33
+ if (!token) {
34
+ return (
35
+ <div className="flex min-h-screen flex-col items-center justify-center p-4">
36
+ <AuthCard
37
+ title="Verify email"
38
+ subtitle="Invalid verification link"
39
+ footer={
40
+ <Link
41
+ href="/login"
42
+ className="text-sm text-muted-foreground underline underline-offset-4 hover:text-foreground"
43
+ >
44
+ Go to sign in
45
+ </Link>
46
+ }
47
+ >
48
+ <ErrorMessage>This verification link is invalid.</ErrorMessage>
49
+ </AuthCard>
50
+ </div>
51
+ );
52
+ }
53
+
54
+ if (state === "loading" || (state === "idle" && loading)) {
55
+ return (
56
+ <div className="flex min-h-screen flex-col items-center justify-center p-4">
57
+ <AuthCard title="Verify email" subtitle="Verifying your email...">
58
+ <div className="flex justify-center py-4">
59
+ <span
60
+ role="status"
61
+ aria-label="Verifying"
62
+ className="inline-block size-8 animate-spin rounded-full border-2 border-current border-t-transparent"
63
+ />
64
+ </div>
65
+ </AuthCard>
66
+ </div>
67
+ );
68
+ }
69
+
70
+ if (state === "success") {
71
+ return (
72
+ <div className="flex min-h-screen flex-col items-center justify-center p-4">
73
+ <AuthCard
74
+ title="Email verified"
75
+ subtitle="Your account is ready"
76
+ footer={
77
+ <Button asChild className="w-full">
78
+ <Link href="/login">Sign in</Link>
79
+ </Button>
80
+ }
81
+ >
82
+ <SuccessMessage>Your email has been verified. You can now sign in.</SuccessMessage>
83
+ </AuthCard>
84
+ </div>
85
+ );
86
+ }
87
+
88
+ if (state === "expired") {
89
+ return (
90
+ <div className="flex min-h-screen flex-col items-center justify-center p-4">
91
+ <AuthCard
92
+ title="Link expired"
93
+ subtitle="This verification link has expired"
94
+ footer={
95
+ <Link
96
+ href="/login"
97
+ className="text-sm text-muted-foreground underline underline-offset-4 hover:text-foreground"
98
+ >
99
+ Go to sign in
100
+ </Link>
101
+ }
102
+ >
103
+ <ErrorMessage>
104
+ This verification link has expired. Please sign in to request a new one.
105
+ </ErrorMessage>
106
+ </AuthCard>
107
+ </div>
108
+ );
109
+ }
110
+
111
+ return (
112
+ <div className="flex min-h-screen flex-col items-center justify-center p-4">
113
+ <AuthCard
114
+ title="Verification failed"
115
+ subtitle="We couldn't verify your email"
116
+ footer={
117
+ <Link
118
+ href="/login"
119
+ className="text-sm text-muted-foreground underline underline-offset-4 hover:text-foreground"
120
+ >
121
+ Go to sign in
122
+ </Link>
123
+ }
124
+ >
125
+ <ErrorMessage>This verification link is invalid or has already been used.</ErrorMessage>
126
+ </AuthCard>
127
+ </div>
128
+ );
129
+ }
130
+
131
+ export function VerifyEmailPage() {
132
+ return (
133
+ <Suspense
134
+ fallback={
135
+ <div className="flex min-h-screen items-center justify-center">
136
+ <span className="inline-block size-8 animate-spin rounded-full border-2 border-current border-t-transparent" />
137
+ </div>
138
+ }
139
+ >
140
+ <VerifyEmailContent />
141
+ </Suspense>
142
+ );
143
+ }
@@ -0,0 +1,5 @@
1
+ import { ForgotPasswordPage } from "@liedsonc/core-auth-kit";
2
+
3
+ export default function ForgotPassword() {
4
+ return <ForgotPasswordPage />;
5
+ }
@@ -0,0 +1,12 @@
1
+ "use client";
2
+
3
+ import { AuthUIProvider } from "@liedsonc/core-auth-kit";
4
+ import { getAuthUIConfig } from "@/lib/auth-config";
5
+
6
+ export default function AuthLayout({ children }: { children: React.ReactNode }) {
7
+ return (
8
+ <AuthUIProvider config={getAuthUIConfig()}>
9
+ {children}
10
+ </AuthUIProvider>
11
+ );
12
+ }
@@ -0,0 +1,5 @@
1
+ import { LoginPage } from "@liedsonc/core-auth-kit";
2
+
3
+ export default function Login() {
4
+ return <LoginPage />;
5
+ }
@@ -0,0 +1,5 @@
1
+ import { RegisterPage } from "@liedsonc/core-auth-kit";
2
+
3
+ export default function Register() {
4
+ return <RegisterPage />;
5
+ }
@@ -0,0 +1,5 @@
1
+ import { ResetPasswordPage } from "@liedsonc/core-auth-kit";
2
+
3
+ export default function ResetPassword() {
4
+ return <ResetPasswordPage />;
5
+ }
@@ -0,0 +1,5 @@
1
+ import { VerifyEmailPage } from "@liedsonc/core-auth-kit";
2
+
3
+ export default function VerifyEmail() {
4
+ return <VerifyEmailPage />;
5
+ }
@@ -0,0 +1,16 @@
1
+ import { NextResponse } from "next/server";
2
+
3
+ export async function POST(request: Request) {
4
+ const body = await request.json().catch(() => ({}));
5
+ const { email } = body as { email?: string };
6
+ if (!email) {
7
+ return NextResponse.json(
8
+ { error: "Missing email" },
9
+ { status: 400 }
10
+ );
11
+ }
12
+ return NextResponse.json(
13
+ { error: "Implement this route: create reset token, store it, and send reset email (e.g. via Resend)." },
14
+ { status: 501 }
15
+ );
16
+ }
@@ -0,0 +1,16 @@
1
+ import { NextResponse } from "next/server";
2
+
3
+ export async function POST(request: Request) {
4
+ const body = await request.json().catch(() => ({}));
5
+ const { email, password } = body as { email?: string; password?: string };
6
+ if (!email || !password) {
7
+ return NextResponse.json(
8
+ { error: "Missing email or password" },
9
+ { status: 400 }
10
+ );
11
+ }
12
+ return NextResponse.json(
13
+ { error: "Implement this route: call your auth backend (e.g. Supabase, custom API) and set session." },
14
+ { status: 501 }
15
+ );
16
+ }
@@ -0,0 +1,8 @@
1
+ import { NextResponse } from "next/server";
2
+
3
+ export async function POST() {
4
+ return NextResponse.json(
5
+ { error: "Implement this route: clear the session (e.g. delete cookie or revoke token)." },
6
+ { status: 501 }
7
+ );
8
+ }
@@ -0,0 +1,16 @@
1
+ import { NextResponse } from "next/server";
2
+
3
+ export async function POST(request: Request) {
4
+ const body = await request.json().catch(() => ({}));
5
+ const { email, password } = body as { email?: string; password?: string };
6
+ if (!email || !password) {
7
+ return NextResponse.json(
8
+ { error: "Missing email or password" },
9
+ { status: 400 }
10
+ );
11
+ }
12
+ return NextResponse.json(
13
+ { error: "Implement this route: call your auth backend to create the user and optionally send verification email." },
14
+ { status: 501 }
15
+ );
16
+ }