@arch-cadre/auth 1.0.7 → 1.0.8

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 (57) hide show
  1. package/dist/actions/basic.d.ts +1 -1
  2. package/dist/actions/email.d.ts +1 -1
  3. package/dist/actions/index.cjs +2 -2
  4. package/dist/actions/index.d.ts +2 -2
  5. package/dist/actions/index.mjs +2 -2
  6. package/dist/index.cjs +1 -1
  7. package/dist/index.mjs +1 -1
  8. package/dist/routes.cjs +7 -7
  9. package/dist/routes.mjs +7 -7
  10. package/dist/ui/forgot-password/components.cjs +2 -2
  11. package/dist/ui/forgot-password/components.mjs +2 -2
  12. package/dist/ui/forgot-password/page.cjs +1 -1
  13. package/dist/ui/forgot-password/page.mjs +1 -1
  14. package/dist/ui/reset-password/components.cjs +2 -2
  15. package/dist/ui/reset-password/components.mjs +2 -2
  16. package/dist/ui/reset-password/page.cjs +1 -1
  17. package/dist/ui/reset-password/page.mjs +1 -1
  18. package/dist/ui/reset-password/verify-email/components.cjs +2 -2
  19. package/dist/ui/reset-password/verify-email/components.mjs +2 -2
  20. package/dist/ui/reset-password/verify-email/page.cjs +1 -1
  21. package/dist/ui/reset-password/verify-email/page.mjs +1 -1
  22. package/dist/ui/signin/components.cjs +2 -2
  23. package/dist/ui/signin/components.d.ts +1 -1
  24. package/dist/ui/signin/components.mjs +2 -2
  25. package/dist/ui/signin/page.cjs +1 -1
  26. package/dist/ui/signin/page.mjs +1 -1
  27. package/dist/ui/signup/components.cjs +2 -2
  28. package/dist/ui/signup/components.d.ts +1 -1
  29. package/dist/ui/signup/components.mjs +2 -2
  30. package/dist/ui/signup/page.cjs +1 -1
  31. package/dist/ui/signup/page.mjs +1 -1
  32. package/dist/ui/verify-email/components.cjs +2 -2
  33. package/dist/ui/verify-email/components.mjs +2 -2
  34. package/dist/ui/verify-email/page.cjs +1 -1
  35. package/dist/ui/verify-email/page.mjs +1 -1
  36. package/package.json +8 -7
  37. package/src/actions/basic.ts +53 -0
  38. package/src/actions/email.ts +241 -0
  39. package/src/actions/index.ts +2 -0
  40. package/src/index.ts +13 -0
  41. package/src/intl.d.ts +13 -0
  42. package/src/routes.ts +55 -0
  43. package/src/types.ts +12 -0
  44. package/src/ui/forgot-password/components.tsx +101 -0
  45. package/src/ui/forgot-password/page.tsx +6 -0
  46. package/src/ui/layout.tsx +42 -0
  47. package/src/ui/reset-password/components.tsx +128 -0
  48. package/src/ui/reset-password/page.tsx +25 -0
  49. package/src/ui/reset-password/verify-email/components.tsx +110 -0
  50. package/src/ui/reset-password/verify-email/page.tsx +25 -0
  51. package/src/ui/signin/components.tsx +215 -0
  52. package/src/ui/signin/page.tsx +43 -0
  53. package/src/ui/signup/components.tsx +230 -0
  54. package/src/ui/signup/page.tsx +35 -0
  55. package/src/ui/verify-email/components.tsx +135 -0
  56. package/src/ui/verify-email/page.tsx +36 -0
  57. package/src/validation.ts +61 -0
@@ -0,0 +1,110 @@
1
+ "use client";
2
+ import { useTranslation } from "@arch-cadre/intl";
3
+ import { Button } from "@arch-cadre/ui/components/button";
4
+ import {
5
+ Field,
6
+ FieldError,
7
+ FieldGroup,
8
+ FieldLabel,
9
+ } from "@arch-cadre/ui/components/field";
10
+ import {
11
+ InputOTP,
12
+ InputOTPGroup,
13
+ InputOTPSlot,
14
+ } from "@arch-cadre/ui/components/input-otp";
15
+ import { cn } from "@arch-cadre/ui/lib/utils";
16
+ import { Loader } from "@arch-cadre/ui/shared/loader";
17
+ import { zodResolver } from "@hookform/resolvers/zod";
18
+ import { AlertCircle } from "lucide-react";
19
+ import * as React from "react";
20
+ import { useState } from "react";
21
+ import { Controller, useForm } from "react-hook-form";
22
+ import { toast } from "sonner";
23
+ import { verifyPasswordResetEmailAction } from "../../../actions/index.js";
24
+ import { type VerifyEmailInput, verifyEmailSchema } from "../../../validation.js";
25
+
26
+ export function PasswordResetEmailVerificationForm({ email = "" }) {
27
+ const [generalError, setGeneralError] = useState("");
28
+ const { t } = useTranslation();
29
+ const form = useForm<VerifyEmailInput>({
30
+ resolver: zodResolver(verifyEmailSchema),
31
+ });
32
+
33
+ async function onSubmit(data: VerifyEmailInput) {
34
+ setGeneralError("");
35
+
36
+ try {
37
+ const response = await verifyPasswordResetEmailAction(data);
38
+
39
+ if (response.error) {
40
+ setGeneralError(
41
+ response.message || t("Verification failed. Please try again."),
42
+ );
43
+ return;
44
+ }
45
+
46
+ toast(response.message);
47
+ } catch (_error) {
48
+ // setGeneralError(t("error_occurred"));
49
+ }
50
+ }
51
+
52
+ return (
53
+ <div className={cn("flex flex-col space-y-3")}>
54
+ <div className="space-y-2 text-center">
55
+ <h1 className="text-3xl font-semibold">
56
+ {t("Verify your email address")}
57
+ </h1>
58
+ <p className="text-muted-foreground">
59
+ {t("We sent an 6-digit code to {email}.", { email })}
60
+ </p>
61
+ </div>
62
+
63
+ <div className="grid gap-5">
64
+ {generalError && (
65
+ <div className="flex gap-2 p-3 bg-red-50 border border-red-200 rounded-lg">
66
+ <AlertCircle className="w-4 h-4 text-red-600 flex-shrink-0 mt-0.5" />
67
+ <p className="text-sm text-red-600">{generalError}</p>
68
+ </div>
69
+ )}
70
+
71
+ <form onSubmit={form.handleSubmit(onSubmit)} className="grid gap-6">
72
+ <FieldGroup className="w-full mx-auto">
73
+ <Controller
74
+ name="code"
75
+ control={form.control}
76
+ render={({ field, fieldState }) => (
77
+ <Field className="w-full" data-invalid={fieldState.invalid}>
78
+ <InputOTP
79
+ {...field}
80
+ className="mx-auto w-full"
81
+ maxLength={6}
82
+ autoFocus={true}
83
+ inputMode="text"
84
+ >
85
+ <InputOTPGroup className="mx-auto">
86
+ <InputOTPSlot index={0} />
87
+ <InputOTPSlot index={1} />
88
+ <InputOTPSlot index={2} />
89
+ <InputOTPSlot index={3} />
90
+ <InputOTPSlot index={4} />
91
+ <InputOTPSlot index={5} />
92
+ </InputOTPGroup>
93
+ </InputOTP>
94
+ {fieldState.invalid && (
95
+ <FieldError errors={[fieldState.error]} />
96
+ )}
97
+ </Field>
98
+ )}
99
+ />
100
+ </FieldGroup>
101
+
102
+ <Button disabled={form.formState.isSubmitting} className="w-full">
103
+ {form.formState.isSubmitting ? t("Please wait...") : t("Verify")}
104
+ {form.formState.isSubmitting && <Loader variant="dark" />}
105
+ </Button>
106
+ </form>
107
+ </div>
108
+ </div>
109
+ );
110
+ }
@@ -0,0 +1,25 @@
1
+ import {
2
+ checkSecurity,
3
+ getCurrentPasswordResetSession,
4
+ } from "@arch-cadre/core/server";
5
+ import { redirect } from "next/navigation";
6
+ import * as React from "react";
7
+ import { PasswordResetEmailVerificationForm } from "./components.js";
8
+
9
+ export default async function Page() {
10
+ const { session, user } = await getCurrentPasswordResetSession();
11
+
12
+ if (session === null || user === null) {
13
+ return redirect("/forgot-password");
14
+ }
15
+
16
+ if (session.emailVerified) {
17
+ const security = await checkSecurity(session as any, user);
18
+ if (!security.satisfied && security.redirect) {
19
+ return redirect(security.redirect);
20
+ }
21
+ return redirect("/reset-password");
22
+ }
23
+
24
+ return <PasswordResetEmailVerificationForm email={session.email} />;
25
+ }
@@ -0,0 +1,215 @@
1
+ "use client";
2
+
3
+ import { useTranslation } from "@arch-cadre/intl";
4
+ import { Button } from "@arch-cadre/ui/components/button";
5
+ import { Checkbox } from "@arch-cadre/ui/components/checkbox";
6
+ import {
7
+ Field,
8
+ FieldError,
9
+ FieldGroup,
10
+ FieldLabel,
11
+ } from "@arch-cadre/ui/components/field";
12
+ import { Input } from "@arch-cadre/ui/components/input";
13
+ import { Separator } from "@arch-cadre/ui/components/separator";
14
+ import { cn } from "@arch-cadre/ui/lib/utils";
15
+ import { Loader } from "@arch-cadre/ui/shared/loader";
16
+ import { zodResolver } from "@hookform/resolvers/zod";
17
+ import { AlertCircle } from "lucide-react";
18
+ import Link from "next/link";
19
+ import * as React from "react";
20
+ import { createContext, useContext, useState } from "react";
21
+ import type { UseFormReturn } from "react-hook-form";
22
+ import { Controller, useForm } from "react-hook-form";
23
+ import { toast } from "sonner";
24
+ import { loginAction } from "../../actions/index.js";
25
+ import { type LoginInput, loginSchema } from "../../validation.js";
26
+
27
+ const LoginFormContext = createContext<{
28
+ form: UseFormReturn<LoginInput>;
29
+ } | null>(null);
30
+
31
+ export function useLoginForm() {
32
+ const context = useContext(LoginFormContext);
33
+ if (!context) {
34
+ throw new Error("useLoginForm must be used within a LoginForm");
35
+ }
36
+ return context;
37
+ }
38
+
39
+ export function LoginForm({
40
+ extraButtons,
41
+ extraFields,
42
+ hasAllowedExtensions,
43
+ }: {
44
+ hasAllowedExtensions?: boolean;
45
+ extraButtons?: React.ReactNode;
46
+ extraFields?: React.ReactNode;
47
+ }) {
48
+ const [generalError, setGeneralError] = useState("");
49
+ const { t } = useTranslation();
50
+
51
+ const form = useForm<LoginInput>({
52
+ resolver: zodResolver(loginSchema),
53
+ });
54
+
55
+ async function onSubmit(data: LoginInput) {
56
+ setGeneralError("");
57
+
58
+ try {
59
+ const response = await loginAction(data);
60
+
61
+ if (response.error) {
62
+ setGeneralError(response.message || t("error_occurred"));
63
+ return;
64
+ }
65
+
66
+ toast(response.message);
67
+ } catch (_error) {
68
+ // setGeneralError(t("error_occurred"));
69
+ }
70
+ }
71
+
72
+ return (
73
+ <LoginFormContext.Provider value={{ form }}>
74
+ <div className={cn("flex flex-col space-y-6")}>
75
+ <div className="space-y-2 text-center">
76
+ <h1 className="text-3xl font-semibold">{t("Sign In")}</h1>
77
+ <p className="text-muted-foreground">
78
+ {t("Sign in to access your dashboard, settings and projects.")}
79
+ </p>
80
+ </div>
81
+
82
+ <div className="grid gap-5">
83
+ {hasAllowedExtensions && (
84
+ <>
85
+ <div className="flex flex-col gap-2">{extraButtons}</div>
86
+
87
+ <div className="flex items-center gap-2">
88
+ <Separator className="flex-1" />
89
+ <span className="text-sm text-muted-foreground">
90
+ {t("or sign in with email")}
91
+ </span>
92
+ <Separator className="flex-1" />
93
+ </div>
94
+ </>
95
+ )}
96
+
97
+ {generalError && (
98
+ <div className="flex gap-2 p-3 bg-red-50 border border-red-200 rounded-lg">
99
+ <AlertCircle className="w-4 h-4 text-red-600 flex-shrink-0 mt-0.5" />
100
+ <p className="text-sm text-red-600">{generalError}</p>
101
+ </div>
102
+ )}
103
+
104
+ <form onSubmit={form.handleSubmit(onSubmit)} className="grid gap-6">
105
+ <FieldGroup>
106
+ <Controller
107
+ name="email"
108
+ control={form.control}
109
+ render={({ field, fieldState }) => (
110
+ <Field data-invalid={fieldState.invalid}>
111
+ <FieldLabel htmlFor="email">
112
+ {t("Email address")}
113
+ </FieldLabel>
114
+ <Input
115
+ {...field}
116
+ id="email"
117
+ type="email"
118
+ aria-invalid={fieldState.invalid}
119
+ placeholder={t("Email address")}
120
+ autoComplete="off"
121
+ />
122
+ {fieldState.invalid && (
123
+ <FieldError errors={[fieldState.error]} />
124
+ )}
125
+ </Field>
126
+ )}
127
+ />
128
+
129
+ <Controller
130
+ name="password"
131
+ control={form.control}
132
+ render={({ field, fieldState }) => (
133
+ <Field data-invalid={fieldState.invalid}>
134
+ <div className="flex items-center">
135
+ <FieldLabel htmlFor="password">
136
+ {t("Password")}
137
+ </FieldLabel>
138
+
139
+ <Link
140
+ href="/forgot-password"
141
+ className="ml-auto text-sm text-primary! underline-offset-4 hover:underline"
142
+ >
143
+ {t("Forgot your password?")}
144
+ </Link>
145
+ </div>
146
+
147
+ <Input
148
+ {...field}
149
+ id="password"
150
+ type="password"
151
+ aria-invalid={fieldState.invalid}
152
+ placeholder="********"
153
+ autoComplete="off"
154
+ />
155
+ {fieldState.invalid && (
156
+ <FieldError errors={[fieldState.error]} />
157
+ )}
158
+ </Field>
159
+ )}
160
+ />
161
+
162
+ {extraFields}
163
+
164
+ <Controller
165
+ name="remember"
166
+ control={form.control}
167
+ render={({ field, fieldState }) => (
168
+ <Field
169
+ data-invalid={fieldState.invalid}
170
+ className="flex items-center space-x-2"
171
+ >
172
+ <div className="flex items-center space-x-2">
173
+ <Checkbox
174
+ id="remember-me"
175
+ aria-invalid={fieldState.invalid}
176
+ checked={field.value}
177
+ onCheckedChange={(checked) => field.onChange(checked)}
178
+ />
179
+ <FieldLabel
180
+ htmlFor="remember-me"
181
+ className="text-sm text-muted-foreground"
182
+ >
183
+ {t("Remember me")}
184
+ </FieldLabel>
185
+ </div>
186
+
187
+ {fieldState.invalid && (
188
+ <FieldError errors={[fieldState.error]} />
189
+ )}
190
+ </Field>
191
+ )}
192
+ />
193
+ </FieldGroup>
194
+
195
+ <Button disabled={form.formState.isSubmitting} className="w-full">
196
+ {form.formState.isSubmitting ? t("Please wait...") : t("Sign In")}
197
+ {form.formState.isSubmitting && <Loader variant="dark" />}
198
+ </Button>
199
+ </form>
200
+
201
+ <div className="text-center text-sm">
202
+ {t("No account?")}{" "}
203
+ <Link
204
+ key="signup"
205
+ href="/signup"
206
+ className="underline underline-offset-4"
207
+ >
208
+ {t("Sign Up")}
209
+ </Link>
210
+ </div>
211
+ </div>
212
+ </div>
213
+ </LoginFormContext.Provider>
214
+ );
215
+ }
@@ -0,0 +1,43 @@
1
+ // import { ExtensionPoint } from "@arch-cadre/ui/shared/extension-point";
2
+ import { checkSecurity, getCurrentSession } from "@arch-cadre/core/server";
3
+ import { ExtensionPoint } from "@arch-cadre/modules";
4
+ import { hasExtension } from "@arch-cadre/modules/server";
5
+ import { redirect } from "next/navigation";
6
+ import * as React from "react";
7
+ import { LoginForm } from "./components.js";
8
+
9
+ export default async function Page() {
10
+ const { session, user } = await getCurrentSession();
11
+
12
+ if (session !== null && user !== null) {
13
+ // Check if there are any pending security requirements (like 2FA)
14
+ const security = await checkSecurity(session, user);
15
+ if (!security.satisfied && security.redirect) {
16
+ return redirect(security.redirect);
17
+ }
18
+ return redirect("/");
19
+ }
20
+
21
+ const hasAllowed = await hasExtension("auth", "signin:extra-buttons");
22
+
23
+ return (
24
+ <LoginForm
25
+ hasAllowedExtensions={hasAllowed}
26
+ extraButtons={
27
+
28
+ <ExtensionPoint
29
+ module="auth"
30
+ point="signin:extra-buttons"
31
+ className="flex flex-col gap-2"
32
+ />
33
+ }
34
+ extraFields={
35
+ <ExtensionPoint
36
+ module="auth"
37
+ point="signin:extra-fields"
38
+ className="flex flex-col gap-4"
39
+ />
40
+ }
41
+ />
42
+ );
43
+ }
@@ -0,0 +1,230 @@
1
+ "use client";
2
+ import { useTranslation } from "@arch-cadre/intl";
3
+ import { Button } from "@arch-cadre/ui/components/button";
4
+ import { Checkbox } from "@arch-cadre/ui/components/checkbox";
5
+ import {
6
+ Field,
7
+ FieldError,
8
+ FieldGroup,
9
+ FieldLabel,
10
+ } from "@arch-cadre/ui/components/field";
11
+ import { Input } from "@arch-cadre/ui/components/input";
12
+ import { Separator } from "@arch-cadre/ui/components/separator";
13
+ import { cn } from "@arch-cadre/ui/lib/utils";
14
+ import { Loader } from "@arch-cadre/ui/shared/loader";
15
+ import { zodResolver } from "@hookform/resolvers/zod";
16
+ import { AlertCircle } from "lucide-react";
17
+ import Link from "next/link";
18
+ import * as React from "react";
19
+ import { createContext, useContext, useState } from "react";
20
+ import type { UseFormReturn } from "react-hook-form";
21
+ import { Controller, useForm } from "react-hook-form";
22
+ import { toast } from "sonner";
23
+ import { signupAction } from "../../actions/index.js";
24
+ import { type RegisterInput, registerSchema } from "../../validation.js";
25
+
26
+ const SignUpFormContext = createContext<{
27
+ form: UseFormReturn<RegisterInput>;
28
+ } | null>(null);
29
+
30
+ export function useSignUpForm() {
31
+ const context = useContext(SignUpFormContext);
32
+ if (!context) {
33
+ throw new Error("useSignUpForm must be used within a SignUpForm");
34
+ }
35
+ return context;
36
+ }
37
+
38
+ export function SignUpForm({
39
+ extraButtons,
40
+ extraFields,
41
+ hasAllowedExtraButtons,
42
+ }: {
43
+ extraButtons?: React.ReactNode;
44
+ extraFields?: React.ReactNode;
45
+ hasAllowedExtraButtons?: boolean;
46
+ }) {
47
+ const [generalError, setGeneralError] = useState("");
48
+ const { t } = useTranslation();
49
+
50
+ const form = useForm<RegisterInput>({
51
+ resolver: zodResolver(registerSchema),
52
+ });
53
+
54
+ async function onSubmit(data: RegisterInput) {
55
+ setGeneralError("");
56
+
57
+ try {
58
+ const response = await signupAction(data);
59
+
60
+ if (response.error) {
61
+ setGeneralError(response.message || t("error_occurred"));
62
+ return;
63
+ }
64
+
65
+ toast(response.message);
66
+ } catch (_error) {
67
+ // setGeneralError(t("error_occurred"));
68
+ }
69
+ }
70
+
71
+ return (
72
+ <SignUpFormContext.Provider value={{ form }}>
73
+ <div className={cn("flex flex-col space-y-6")}>
74
+ <div className="space-y-2 text-center">
75
+ <h1 className="text-3xl font-semibold">{t("Sign Up")}</h1>
76
+ <p className="text-muted-foreground">
77
+ {t("Sign up to access your dashboard, settings and projects.")}
78
+ </p>
79
+ </div>
80
+
81
+ <div className="grid gap-5">
82
+ {hasAllowedExtraButtons && (
83
+ <>
84
+ <div className="flex flex-col gap-2">{extraButtons}</div>
85
+
86
+ <div className="flex items-center gap-2">
87
+ <Separator className="flex-1" />
88
+ <span className="text-sm text-muted-foreground">
89
+ {t("or sign up with email")}
90
+ </span>
91
+ <Separator className="flex-1" />
92
+ </div>
93
+ </>
94
+ )}
95
+
96
+ {generalError && (
97
+ <div className="flex gap-2 p-3 bg-red-50 border border-red-200 rounded-lg">
98
+ <AlertCircle className="w-4 h-4 text-red-600 flex-shrink-0 mt-0.5" />
99
+ <p className="text-sm text-red-600">{generalError}</p>
100
+ </div>
101
+ )}
102
+
103
+ <form onSubmit={form.handleSubmit(onSubmit)} className="grid gap-6">
104
+ <FieldGroup>
105
+ <Controller
106
+ name="email"
107
+ control={form.control}
108
+ render={({ field, fieldState }) => (
109
+ <Field data-invalid={fieldState.invalid}>
110
+ <FieldLabel htmlFor="email">
111
+ {t("Email address")}
112
+ </FieldLabel>
113
+ <Input
114
+ {...field}
115
+ id="email"
116
+ type="email"
117
+ aria-invalid={fieldState.invalid}
118
+ placeholder={t("Email address")}
119
+ autoComplete="off"
120
+ />
121
+ {fieldState.invalid && (
122
+ <FieldError errors={[fieldState.error]} />
123
+ )}
124
+ </Field>
125
+ )}
126
+ />
127
+ <Controller
128
+ name="username"
129
+ control={form.control}
130
+ render={({ field, fieldState }) => (
131
+ <Field data-invalid={fieldState.invalid}>
132
+ <FieldLabel htmlFor="username">{t("Username")}</FieldLabel>
133
+ <Input
134
+ {...field}
135
+ id="username"
136
+ type="text"
137
+ aria-invalid={fieldState.invalid}
138
+ placeholder={t("Username")}
139
+ autoComplete="off"
140
+ />
141
+ {fieldState.invalid && (
142
+ <FieldError errors={[fieldState.error]} />
143
+ )}
144
+ </Field>
145
+ )}
146
+ />
147
+ <Controller
148
+ name="password"
149
+ control={form.control}
150
+ render={({ field, fieldState }) => (
151
+ <Field data-invalid={fieldState.invalid}>
152
+ <FieldLabel htmlFor="password">{t("Password")}</FieldLabel>
153
+ <Input
154
+ {...field}
155
+ id="password"
156
+ type="password"
157
+ aria-invalid={fieldState.invalid}
158
+ placeholder={t("Password")}
159
+ autoComplete="off"
160
+ />
161
+ {fieldState.invalid && (
162
+ <FieldError errors={[fieldState.error]} />
163
+ )}
164
+ </Field>
165
+ )}
166
+ />
167
+
168
+ {extraFields}
169
+
170
+ <Controller
171
+ name="terms"
172
+ control={form.control}
173
+ render={({ field, fieldState }) => (
174
+ <Field
175
+ data-invalid={fieldState.invalid}
176
+ className="flex items-center space-x-2"
177
+ >
178
+ <div className="flex items-center space-x-2">
179
+ <Checkbox
180
+ id="terms"
181
+ aria-invalid={fieldState.invalid}
182
+ checked={field.value}
183
+ onCheckedChange={(checked) => field.onChange(checked)}
184
+ />
185
+ <FieldLabel
186
+ htmlFor="terms"
187
+ className="text-sm text-muted-foreground"
188
+ >
189
+ {t("I agree to the")}{" "}
190
+ <Link href="#" className="text-primary hover:underline">
191
+ {t("Terms")}
192
+ </Link>{" "}
193
+ {t("and")}{" "}
194
+ <Link href="#" className="text-primary hover:underline">
195
+ {t("Privacy Policy")}
196
+ </Link>
197
+ </FieldLabel>
198
+ </div>
199
+
200
+ {fieldState.invalid && (
201
+ <FieldError errors={[fieldState.error]} />
202
+ )}
203
+ </Field>
204
+ )}
205
+ />
206
+ </FieldGroup>
207
+
208
+ <Button disabled={form.formState.isSubmitting} className="w-full">
209
+ {form.formState.isSubmitting ? t("Please wait...") : t("Sign Up")}
210
+ {form.formState.isSubmitting && <Loader variant="dark" />}
211
+ </Button>
212
+
213
+ {/* {extraButtons} */}
214
+ </form>
215
+
216
+ <div className="text-center text-sm">
217
+ {t("Already have an account?")}{" "}
218
+ <Link
219
+ key="signin"
220
+ href="/signin"
221
+ className="underline underline-offset-4"
222
+ >
223
+ {t("Sign In")}
224
+ </Link>
225
+ </div>
226
+ </div>
227
+ </div>
228
+ </SignUpFormContext.Provider>
229
+ );
230
+ }
@@ -0,0 +1,35 @@
1
+ import { getCurrentSession } from "@arch-cadre/core/server";
2
+ import { ExtensionPoint } from "@arch-cadre/modules";
3
+ import { hasExtension } from "@arch-cadre/modules/server";
4
+ import { redirect } from "next/navigation";
5
+ import * as React from "react";
6
+ import { SignUpForm } from "./components.js";
7
+
8
+ export default async function Page() {
9
+ const { session, user } = await getCurrentSession();
10
+
11
+ if (session !== null && user !== null) {
12
+ return redirect("/");
13
+ }
14
+ const hasAllowed = await hasExtension("auth", "signup:extra-buttons");
15
+
16
+ return (
17
+ <SignUpForm
18
+ hasAllowedExtraButtons={hasAllowed}
19
+ extraButtons={
20
+ <ExtensionPoint
21
+ module="auth"
22
+ point="signup:extra-buttons"
23
+ className="flex flex-col gap-2 mt-4"
24
+ />
25
+ }
26
+ extraFields={
27
+ <ExtensionPoint
28
+ module="auth"
29
+ point="signup:extra-fields"
30
+ className="flex flex-col gap-4"
31
+ />
32
+ }
33
+ />
34
+ );
35
+ }