@forjio/auth-ui 0.6.0 → 0.8.0

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 (49) hide show
  1. package/dist/AuthForm.cjs +171 -36
  2. package/dist/AuthForm.cjs.map +1 -1
  3. package/dist/AuthForm.d.cts +2 -2
  4. package/dist/AuthForm.d.ts +2 -2
  5. package/dist/AuthForm.js +173 -38
  6. package/dist/AuthForm.js.map +1 -1
  7. package/dist/ForgotPasswordForm.cjs +8 -5
  8. package/dist/ForgotPasswordForm.cjs.map +1 -1
  9. package/dist/ForgotPasswordForm.d.cts +2 -2
  10. package/dist/ForgotPasswordForm.d.ts +2 -2
  11. package/dist/ForgotPasswordForm.js +8 -5
  12. package/dist/ForgotPasswordForm.js.map +1 -1
  13. package/dist/ResetPasswordForm.cjs +8 -5
  14. package/dist/ResetPasswordForm.cjs.map +1 -1
  15. package/dist/ResetPasswordForm.d.cts +2 -2
  16. package/dist/ResetPasswordForm.d.ts +2 -2
  17. package/dist/ResetPasswordForm.js +8 -5
  18. package/dist/ResetPasswordForm.js.map +1 -1
  19. package/dist/components/ui/button.cjs +84 -0
  20. package/dist/components/ui/button.cjs.map +1 -0
  21. package/dist/components/ui/button.d.cts +14 -0
  22. package/dist/components/ui/button.d.ts +14 -0
  23. package/dist/components/ui/button.js +49 -0
  24. package/dist/components/ui/button.js.map +1 -0
  25. package/dist/components/ui/input.cjs +58 -0
  26. package/dist/components/ui/input.cjs.map +1 -0
  27. package/dist/components/ui/input.d.cts +5 -0
  28. package/dist/components/ui/input.d.ts +5 -0
  29. package/dist/components/ui/input.js +24 -0
  30. package/dist/components/ui/input.js.map +1 -0
  31. package/dist/components/ui/label.cjs +56 -0
  32. package/dist/components/ui/label.cjs.map +1 -0
  33. package/dist/components/ui/label.d.cts +8 -0
  34. package/dist/components/ui/label.d.ts +8 -0
  35. package/dist/components/ui/label.js +22 -0
  36. package/dist/components/ui/label.js.map +1 -0
  37. package/dist/lib/utils.cjs +33 -0
  38. package/dist/lib/utils.cjs.map +1 -0
  39. package/dist/lib/utils.d.cts +9 -0
  40. package/dist/lib/utils.d.ts +9 -0
  41. package/dist/lib/utils.js +9 -0
  42. package/dist/lib/utils.js.map +1 -0
  43. package/dist/types.cjs +1 -0
  44. package/dist/types.cjs.map +1 -1
  45. package/dist/types.d.cts +6 -0
  46. package/dist/types.d.ts +6 -0
  47. package/dist/types.js +1 -0
  48. package/dist/types.js.map +1 -1
  49. package/package.json +7 -2
package/dist/AuthForm.js CHANGED
@@ -1,11 +1,14 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
- import { useState } from "react";
3
+ import { useEffect, useState } from "react";
4
4
  import { useRouter, useSearchParams } from "next/navigation";
5
5
  import Link from "next/link";
6
- import { Loader2, AlertCircle } from "lucide-react";
6
+ import { Loader2, AlertCircle, ShieldCheck, ArrowLeft } from "lucide-react";
7
7
  import { Turnstile } from "@marsidev/react-turnstile";
8
8
  import { useTurnstileTheme } from "./useTurnstileTheme";
9
+ import { Button } from "./components/ui/button";
10
+ import { Input } from "./components/ui/input";
11
+ import { Label } from "./components/ui/label";
9
12
  import { defaultEndpoints } from "./types";
10
13
  const TURNSTILE_SITE_KEY = process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY;
11
14
  function AuthForm({
@@ -36,6 +39,15 @@ function AuthForm({
36
39
  const [error, setError] = useState(
37
40
  ssoError ? `Sign-in failed: ${ssoDetail || ssoError}` : null
38
41
  );
42
+ const [step, setStep] = useState("credentials");
43
+ const [mfaChallengeToken, setMfaChallengeToken] = useState("");
44
+ const [mfaMethods, setMfaMethods] = useState([]);
45
+ const [mfaExpiresAt, setMfaExpiresAt] = useState(null);
46
+ const [code, setCode] = useState("");
47
+ function redirectAfterAuth() {
48
+ router.push(returnTo);
49
+ router.refresh();
50
+ }
39
51
  async function submit(e) {
40
52
  e.preventDefault();
41
53
  setError(null);
@@ -52,23 +64,80 @@ function AuthForm({
52
64
  body: JSON.stringify(body)
53
65
  });
54
66
  if (!res.ok) {
55
- const payload = await res.json().catch(() => null);
56
- if (payload?.error?.code === "MFA_REQUIRED") {
67
+ const payload2 = await res.json().catch(() => null);
68
+ if (payload2?.error?.code === "MFA_REQUIRED") {
57
69
  setRedirecting(true);
58
70
  const qs = new URLSearchParams({ return_to: returnTo, ...socialParams });
59
71
  window.location.href = `${ep.socialStart}?${qs.toString()}`;
60
72
  return;
61
73
  }
74
+ throw new Error(payload2?.error?.message ?? `Request failed (${res.status})`);
75
+ }
76
+ const payload = await res.json().catch(() => null);
77
+ const challenge = payload?.data ?? payload;
78
+ if (challenge?.mfaRequired && challenge.mfaChallengeToken) {
79
+ setMfaChallengeToken(challenge.mfaChallengeToken);
80
+ setMfaMethods(Array.isArray(challenge.methods) ? challenge.methods : []);
81
+ setMfaExpiresAt(challenge.expiresAt ?? null);
82
+ setCode("");
83
+ setStep("otp");
84
+ return;
85
+ }
86
+ redirectAfterAuth();
87
+ } catch (err) {
88
+ setError(err.message);
89
+ } finally {
90
+ setSubmitting(false);
91
+ }
92
+ }
93
+ async function submitMfa(e) {
94
+ e.preventDefault();
95
+ setError(null);
96
+ setSubmitting(true);
97
+ try {
98
+ const body = { mfaChallengeToken, code };
99
+ if (TURNSTILE_SITE_KEY && turnstileToken) body["cf-turnstile-response"] = turnstileToken;
100
+ const res = await fetch(ep.mfa, {
101
+ method: "POST",
102
+ headers: { "Content-Type": "application/json" },
103
+ credentials: "include",
104
+ body: JSON.stringify(body)
105
+ });
106
+ if (!res.ok) {
107
+ const payload = await res.json().catch(() => null);
62
108
  throw new Error(payload?.error?.message ?? `Request failed (${res.status})`);
63
109
  }
64
- router.push(returnTo);
65
- router.refresh();
110
+ redirectAfterAuth();
66
111
  } catch (err) {
67
112
  setError(err.message);
68
113
  } finally {
69
114
  setSubmitting(false);
70
115
  }
71
116
  }
117
+ function backToCredentials() {
118
+ setStep("credentials");
119
+ setError(null);
120
+ setCode("");
121
+ setMfaChallengeToken("");
122
+ setMfaMethods([]);
123
+ setMfaExpiresAt(null);
124
+ }
125
+ const [secondsLeft, setSecondsLeft] = useState(null);
126
+ useEffect(() => {
127
+ if (step !== "otp" || !mfaExpiresAt) {
128
+ setSecondsLeft(null);
129
+ return;
130
+ }
131
+ const target = new Date(mfaExpiresAt).getTime();
132
+ if (Number.isNaN(target)) {
133
+ setSecondsLeft(null);
134
+ return;
135
+ }
136
+ const tick = () => setSecondsLeft(Math.max(0, Math.round((target - Date.now()) / 1e3)));
137
+ tick();
138
+ const id = setInterval(tick, 1e3);
139
+ return () => clearInterval(id);
140
+ }, [step, mfaExpiresAt]);
72
141
  const otherMode = mode === "login" ? "signup" : "login";
73
142
  const otherHref = `${otherMode === "signup" ? signupHref : loginHref}?return_to=${encodeURIComponent(returnTo)}`;
74
143
  const socialUrl = (provider) => {
@@ -88,39 +157,105 @@ function AuthForm({
88
157
  /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 shrink-0 mt-0.5 animate-spin" }),
89
158
  /* @__PURE__ */ jsx("span", { children: "Redirecting you to complete two-factor sign-in\u2026" })
90
159
  ] }),
91
- hasAnySocial && /* @__PURE__ */ jsxs(Fragment, { children: [
92
- /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
93
- showGoogle && /* @__PURE__ */ jsxs(
94
- "a",
160
+ step === "otp" && /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
161
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
162
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
163
+ /* @__PURE__ */ jsx(ShieldCheck, { className: "h-4 w-4 shrink-0" }),
164
+ "Two-factor authentication"
165
+ ] }),
166
+ /* @__PURE__ */ jsx("p", { className: "text-xs leading-relaxed text-muted-foreground", children: "Enter the 6-digit code from your authenticator app." })
167
+ ] }),
168
+ /* @__PURE__ */ jsxs("form", { onSubmit: submitMfa, className: "space-y-3", children: [
169
+ /* @__PURE__ */ jsxs("div", { children: [
170
+ /* @__PURE__ */ jsx(
171
+ Label,
172
+ {
173
+ htmlFor: "mfa-code",
174
+ className: "mb-1 block text-xs leading-4 text-muted-foreground",
175
+ children: "Verification code"
176
+ }
177
+ ),
178
+ /* @__PURE__ */ jsx(
179
+ Input,
180
+ {
181
+ id: "mfa-code",
182
+ type: "text",
183
+ required: true,
184
+ autoFocus: true,
185
+ inputMode: "numeric",
186
+ autoComplete: "one-time-code",
187
+ pattern: "[0-9]*",
188
+ maxLength: 6,
189
+ value: code,
190
+ onChange: (e) => setCode(e.target.value.replace(/\D/g, "").slice(0, 6)),
191
+ "aria-label": "Six-digit verification code",
192
+ className: "h-auto border-border bg-background py-2 text-center text-lg tracking-[0.4em] shadow-none focus:outline-none focus:ring-1 focus:ring-primary"
193
+ }
194
+ ),
195
+ secondsLeft !== null && /* @__PURE__ */ jsx("p", { className: "mt-1 text-[11px] text-muted-foreground", children: secondsLeft > 0 ? `Code expires in ${Math.floor(secondsLeft / 60)}:${String(secondsLeft % 60).padStart(2, "0")}` : "Code expired \u2014 go back and sign in again." })
196
+ ] }),
197
+ /* @__PURE__ */ jsxs(
198
+ Button,
95
199
  {
96
- href: socialUrl("google"),
97
- className: "flex w-full items-center justify-center gap-2 rounded-md border border-border bg-background py-2 text-sm font-medium hover:bg-accent",
200
+ type: "submit",
201
+ disabled: submitting || code.length !== 6,
202
+ className: "flex h-auto w-full py-2.5 shadow-none hover:opacity-90",
98
203
  children: [
204
+ submitting && /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }),
205
+ submitting ? "Verifying\u2026" : "Verify"
206
+ ]
207
+ }
208
+ )
209
+ ] }),
210
+ /* @__PURE__ */ jsxs(
211
+ "button",
212
+ {
213
+ type: "button",
214
+ onClick: backToCredentials,
215
+ className: "flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground",
216
+ children: [
217
+ /* @__PURE__ */ jsx(ArrowLeft, { className: "h-3.5 w-3.5" }),
218
+ "Back to sign in"
219
+ ]
220
+ }
221
+ )
222
+ ] }),
223
+ step === "credentials" && hasAnySocial && /* @__PURE__ */ jsxs(Fragment, { children: [
224
+ /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
225
+ showGoogle && /* @__PURE__ */ jsx(
226
+ Button,
227
+ {
228
+ asChild: true,
229
+ variant: "outline",
230
+ className: "flex h-auto w-full border-border py-2 shadow-none",
231
+ children: /* @__PURE__ */ jsxs("a", { href: socialUrl("google"), children: [
99
232
  /* @__PURE__ */ jsx(GoogleMark, { className: "h-4 w-4" }),
100
233
  "Continue with Google"
101
- ]
234
+ ] })
102
235
  }
103
236
  ),
104
- showApple && /* @__PURE__ */ jsxs(
105
- "a",
237
+ showApple && /* @__PURE__ */ jsx(
238
+ Button,
106
239
  {
107
- href: socialUrl("apple"),
108
- className: "flex w-full items-center justify-center gap-2 rounded-md border border-border bg-background py-2 text-sm font-medium hover:bg-accent",
109
- children: [
240
+ asChild: true,
241
+ variant: "outline",
242
+ className: "flex h-auto w-full border-border py-2 shadow-none",
243
+ children: /* @__PURE__ */ jsxs("a", { href: socialUrl("apple"), children: [
110
244
  /* @__PURE__ */ jsx(AppleMark, { className: "h-4 w-4" }),
111
245
  "Continue with Apple"
112
- ]
246
+ ] })
113
247
  }
114
248
  ),
115
- showFacebook && /* @__PURE__ */ jsxs(
116
- "a",
249
+ showFacebook && /* @__PURE__ */ jsx(
250
+ Button,
117
251
  {
118
- href: socialUrl("facebook"),
119
- className: "flex w-full items-center justify-center gap-2 rounded-md border border-border bg-background py-2 text-sm font-medium hover:bg-accent",
120
- children: [
252
+ asChild: true,
253
+ variant: "outline",
254
+ className: "flex h-auto w-full border-border py-2 shadow-none",
255
+ children: /* @__PURE__ */ jsxs("a", { href: socialUrl("facebook"), children: [
121
256
  /* @__PURE__ */ jsx(FacebookMark, { className: "h-4 w-4" }),
122
257
  "Continue with Facebook"
123
- ]
258
+ ] })
124
259
  }
125
260
  )
126
261
  ] }),
@@ -130,25 +265,25 @@ function AuthForm({
130
265
  /* @__PURE__ */ jsx("div", { className: "flex-1 border-t border-border" })
131
266
  ] })
132
267
  ] }),
133
- /* @__PURE__ */ jsxs("form", { onSubmit: submit, className: "space-y-3", children: [
268
+ step === "credentials" && /* @__PURE__ */ jsxs("form", { onSubmit: submit, className: "space-y-3", children: [
134
269
  /* @__PURE__ */ jsxs("div", { children: [
135
- /* @__PURE__ */ jsx("label", { className: "mb-1 block text-xs font-medium text-muted-foreground", children: "Email" }),
270
+ /* @__PURE__ */ jsx(Label, { className: "mb-1 block text-xs leading-4 text-muted-foreground", children: "Email" }),
136
271
  /* @__PURE__ */ jsx(
137
- "input",
272
+ Input,
138
273
  {
139
274
  type: "email",
140
275
  required: true,
141
276
  value: email,
142
277
  onChange: (e) => setEmail(e.target.value),
143
278
  autoComplete: "email",
144
- className: "w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-primary"
279
+ className: "h-auto border-border bg-background py-2 text-sm shadow-none focus:outline-none focus:ring-1 focus:ring-primary"
145
280
  }
146
281
  )
147
282
  ] }),
148
283
  /* @__PURE__ */ jsxs("div", { children: [
149
- /* @__PURE__ */ jsx("label", { className: "mb-1 block text-xs font-medium text-muted-foreground", children: "Password" }),
284
+ /* @__PURE__ */ jsx(Label, { className: "mb-1 block text-xs leading-4 text-muted-foreground", children: "Password" }),
150
285
  /* @__PURE__ */ jsx(
151
- "input",
286
+ Input,
152
287
  {
153
288
  type: "password",
154
289
  required: true,
@@ -156,24 +291,24 @@ function AuthForm({
156
291
  value: password,
157
292
  onChange: (e) => setPassword(e.target.value),
158
293
  autoComplete: mode === "signup" ? "new-password" : "current-password",
159
- className: "w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-primary"
294
+ className: "h-auto border-border bg-background py-2 text-sm shadow-none focus:outline-none focus:ring-1 focus:ring-primary"
160
295
  }
161
296
  ),
162
297
  mode === "signup" && /* @__PURE__ */ jsx("p", { className: "mt-1 text-[11px] text-muted-foreground", children: "At least 10 characters, with a letter and a number." })
163
298
  ] }),
164
299
  mode === "signup" && /* @__PURE__ */ jsxs("div", { children: [
165
- /* @__PURE__ */ jsxs("label", { className: "mb-1 block text-xs font-medium text-muted-foreground", children: [
300
+ /* @__PURE__ */ jsxs(Label, { className: "mb-1 block text-xs leading-4 text-muted-foreground", children: [
166
301
  "Your name ",
167
302
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/60", children: "(optional)" })
168
303
  ] }),
169
304
  /* @__PURE__ */ jsx(
170
- "input",
305
+ Input,
171
306
  {
172
307
  type: "text",
173
308
  value: name,
174
309
  onChange: (e) => setName(e.target.value),
175
310
  autoComplete: "name",
176
- className: "w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-primary"
311
+ className: "h-auto border-border bg-background py-2 text-sm shadow-none focus:outline-none focus:ring-1 focus:ring-primary"
177
312
  }
178
313
  )
179
314
  ] }),
@@ -187,11 +322,11 @@ function AuthForm({
187
322
  }
188
323
  ) }),
189
324
  /* @__PURE__ */ jsxs(
190
- "button",
325
+ Button,
191
326
  {
192
327
  type: "submit",
193
328
  disabled: submitting || redirecting,
194
- className: "flex w-full items-center justify-center gap-2 rounded-md bg-primary py-2.5 text-sm font-medium text-primary-foreground transition hover:opacity-90 disabled:opacity-50",
329
+ className: "flex h-auto w-full py-2.5 shadow-none hover:opacity-90",
195
330
  children: [
196
331
  (submitting || redirecting) && /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }),
197
332
  redirecting ? "Redirecting\u2026" : submitting ? mode === "signup" ? "Creating\u2026" : "Signing in\u2026" : mode === "signup" ? "Create account" : "Sign in"
@@ -199,7 +334,7 @@ function AuthForm({
199
334
  }
200
335
  )
201
336
  ] }),
202
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between pt-2 text-xs text-muted-foreground", children: [
337
+ step === "credentials" && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between pt-2 text-xs text-muted-foreground", children: [
203
338
  mode === "login" && /* @__PURE__ */ jsx(Link, { href: forgotPasswordHref, className: "hover:text-foreground", children: "Forgot password?" }),
204
339
  /* @__PURE__ */ jsxs("span", { className: mode === "login" ? "" : "ml-auto", children: [
205
340
  mode === "login" ? `New to ${brand}?` : "Already have an account?",
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/AuthForm.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { useRouter, useSearchParams } from 'next/navigation';\nimport Link from 'next/link';\nimport { Loader2, AlertCircle } from 'lucide-react';\nimport { Turnstile } from '@marsidev/react-turnstile';\nimport { useTurnstileTheme } from './useTurnstileTheme';\nimport { defaultEndpoints, type AuthEndpoints, type SocialProviders } from './types';\n\n// Cloudflare Turnstile is enabled family-wide by setting\n// NEXT_PUBLIC_TURNSTILE_SITE_KEY at build time. Next inlines NEXT_PUBLIC_*\n// referenced here (in node_modules) into each product's bundle, so no\n// per-product page edit is needed — set the env var + the widget appears,\n// and the token rides the login/signup request as `cf-turnstile-response`.\n// The backend (@forjio/sdk/auth-server) verifies it when\n// TURNSTILE_SECRET_KEY is set; both sides bypass gracefully when unset.\nconst TURNSTILE_SITE_KEY = process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY;\n\nexport interface AuthFormProps {\n mode: 'login' | 'signup';\n /** Display name shown in copy (\"New to Plugipay?\", \"Welcome back\"). */\n brand: string;\n /** Override the auth endpoint paths. Default matches Forjio family\n * `@forjio/sdk/auth-handlers` mounts. */\n endpoints?: Partial<AuthEndpoints>;\n /** Which social providers to render. Host fetches the provider\n * status; pass undefined to show all (fail-open). */\n providers?: SocialProviders | null;\n /** Default redirect target after a successful auth. Default\n * `/dashboard`. Search-param `?return_to=` overrides at runtime. */\n defaultReturnTo?: string;\n /** Mode-switch link targets. Default `/login` / `/signup`. Override\n * for products with non-default auth routes (e.g. role-scoped\n * `/creators/login` + `/creators/onboarding`). `?return_to=` is\n * appended automatically. */\n loginHref?: string;\n signupHref?: string;\n /** \"Forgot password?\" link target. Default `/forgot-password`. */\n forgotPasswordHref?: string;\n /** Extra fields merged into the login/signup request body — e.g. a\n * `role` discriminator for multi-role products. */\n extraBody?: Record<string, unknown>;\n /** Extra query params appended to the social-start URL — e.g.\n * `{ role }`, which the Huudis OIDC start needs to mint the correct\n * per-role session on callback. */\n socialParams?: Record<string, string>;\n}\n\nexport function AuthForm({\n mode,\n brand,\n endpoints,\n providers,\n defaultReturnTo = '/dashboard',\n loginHref = '/login',\n signupHref = '/signup',\n forgotPasswordHref = '/forgot-password',\n extraBody,\n socialParams,\n}: AuthFormProps) {\n const router = useRouter();\n const params = useSearchParams();\n const returnTo = params?.get('return_to') || defaultReturnTo;\n const ssoError = params?.get('sso_error');\n const ssoDetail = params?.get('sso_detail');\n const ep: AuthEndpoints = { ...defaultEndpoints, ...endpoints };\n\n const [email, setEmail] = useState('');\n const [password, setPassword] = useState('');\n const [name, setName] = useState('');\n const [submitting, setSubmitting] = useState(false);\n const [redirecting, setRedirecting] = useState(false);\n const [turnstileToken, setTurnstileToken] = useState('');\n const turnstileTheme = useTurnstileTheme();\n const [error, setError] = useState<string | null>(\n ssoError ? `Sign-in failed: ${ssoDetail || ssoError}` : null,\n );\n\n async function submit(e: React.FormEvent) {\n e.preventDefault();\n setError(null);\n setSubmitting(true);\n try {\n const path = mode === 'signup' ? ep.signup : ep.login;\n const body: Record<string, unknown> = { email, password, ...extraBody };\n if (mode === 'signup' && name.trim()) body.name = name.trim();\n if (TURNSTILE_SITE_KEY) body['cf-turnstile-response'] = turnstileToken;\n const res = await fetch(path, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const payload = (await res.json().catch(() => null)) as {\n error?: { code?: string; message?: string };\n } | null;\n // The product BFF does not do inline MFA. When a user has MFA\n // enabled, Huudis ROPC fails and the SDK returns 401 with\n // `code: 'MFA_REQUIRED'`. Hand off to the Huudis hosted-login\n // flow (no `provider=` param) — Huudis performs the challenge.\n // `socialParams` (e.g. `{ role }`) MUST ride along so a\n // multi-role product mints the correct per-role session on the\n // OIDC callback — otherwise the role is lost and the user is\n // gated on the wrong portal.\n if (payload?.error?.code === 'MFA_REQUIRED') {\n setRedirecting(true);\n const qs = new URLSearchParams({ return_to: returnTo, ...socialParams });\n window.location.href = `${ep.socialStart}?${qs.toString()}`;\n return;\n }\n throw new Error(payload?.error?.message ?? `Request failed (${res.status})`);\n }\n router.push(returnTo);\n router.refresh();\n } catch (err) {\n setError((err as Error).message);\n } finally {\n setSubmitting(false);\n }\n }\n\n const otherMode = mode === 'login' ? 'signup' : 'login';\n const otherHref = `${otherMode === 'signup' ? signupHref : loginHref}?return_to=${encodeURIComponent(returnTo)}`;\n const socialUrl = (provider: 'google' | 'apple' | 'facebook') => {\n const qs = new URLSearchParams({ provider, return_to: returnTo, ...socialParams });\n return `${ep.socialStart}?${qs.toString()}`;\n };\n\n const showGoogle = providers?.google !== false;\n const showApple = providers?.apple !== false;\n const showFacebook = providers?.facebook !== false;\n const hasAnySocial = showGoogle || showApple || showFacebook;\n\n return (\n <div className=\"space-y-4\">\n {error && !redirecting && (\n <div className=\"flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive\">\n <AlertCircle className=\"h-4 w-4 shrink-0 mt-0.5\" />\n <span>{error}</span>\n </div>\n )}\n\n {redirecting && (\n <div className=\"flex items-start gap-2 rounded-md border border-border bg-accent px-3 py-2 text-sm text-muted-foreground\">\n <Loader2 className=\"h-4 w-4 shrink-0 mt-0.5 animate-spin\" />\n <span>Redirecting you to complete two-factor sign-in…</span>\n </div>\n )}\n\n {hasAnySocial && (\n <>\n <div className=\"grid gap-2\">\n {showGoogle && (\n <a\n href={socialUrl('google')}\n className=\"flex w-full items-center justify-center gap-2 rounded-md border border-border bg-background py-2 text-sm font-medium hover:bg-accent\"\n >\n <GoogleMark className=\"h-4 w-4\" />\n Continue with Google\n </a>\n )}\n {showApple && (\n <a\n href={socialUrl('apple')}\n className=\"flex w-full items-center justify-center gap-2 rounded-md border border-border bg-background py-2 text-sm font-medium hover:bg-accent\"\n >\n <AppleMark className=\"h-4 w-4\" />\n Continue with Apple\n </a>\n )}\n {showFacebook && (\n <a\n href={socialUrl('facebook')}\n className=\"flex w-full items-center justify-center gap-2 rounded-md border border-border bg-background py-2 text-sm font-medium hover:bg-accent\"\n >\n <FacebookMark className=\"h-4 w-4\" />\n Continue with Facebook\n </a>\n )}\n </div>\n\n <div className=\"my-4 flex items-center gap-3 text-[11px] text-muted-foreground\">\n <div className=\"flex-1 border-t border-border\" />\n OR\n <div className=\"flex-1 border-t border-border\" />\n </div>\n </>\n )}\n\n <form onSubmit={submit} className=\"space-y-3\">\n <div>\n <label className=\"mb-1 block text-xs font-medium text-muted-foreground\">Email</label>\n <input\n type=\"email\"\n required\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n autoComplete=\"email\"\n className=\"w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-primary\"\n />\n </div>\n <div>\n <label className=\"mb-1 block text-xs font-medium text-muted-foreground\">Password</label>\n <input\n type=\"password\"\n required\n minLength={mode === 'signup' ? 10 : undefined}\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n autoComplete={mode === 'signup' ? 'new-password' : 'current-password'}\n className=\"w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-primary\"\n />\n {mode === 'signup' && (\n <p className=\"mt-1 text-[11px] text-muted-foreground\">At least 10 characters, with a letter and a number.</p>\n )}\n </div>\n {mode === 'signup' && (\n <div>\n <label className=\"mb-1 block text-xs font-medium text-muted-foreground\">\n Your name <span className=\"text-muted-foreground/60\">(optional)</span>\n </label>\n <input\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n autoComplete=\"name\"\n className=\"w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-primary\"\n />\n </div>\n )}\n {TURNSTILE_SITE_KEY && (\n <div className=\"flex justify-center py-1\">\n <Turnstile\n siteKey={TURNSTILE_SITE_KEY}\n onSuccess={setTurnstileToken}\n onError={() => setError('Security check failed.')}\n options={{ theme: turnstileTheme }}\n />\n </div>\n )}\n <button\n type=\"submit\"\n disabled={submitting || redirecting}\n className=\"flex w-full items-center justify-center gap-2 rounded-md bg-primary py-2.5 text-sm font-medium text-primary-foreground transition hover:opacity-90 disabled:opacity-50\"\n >\n {(submitting || redirecting) && <Loader2 className=\"h-4 w-4 animate-spin\" />}\n {redirecting\n ? 'Redirecting…'\n : submitting\n ? mode === 'signup'\n ? 'Creating…'\n : 'Signing in…'\n : mode === 'signup'\n ? 'Create account'\n : 'Sign in'}\n </button>\n </form>\n\n <div className=\"flex items-center justify-between pt-2 text-xs text-muted-foreground\">\n {mode === 'login' && (\n <Link href={forgotPasswordHref} className=\"hover:text-foreground\">\n Forgot password?\n </Link>\n )}\n <span className={mode === 'login' ? '' : 'ml-auto'}>\n {mode === 'login' ? `New to ${brand}?` : 'Already have an account?'}{' '}\n <Link href={otherHref} className=\"font-medium text-foreground hover:underline\">\n {mode === 'login' ? 'Sign up' : 'Sign in'}\n </Link>\n </span>\n </div>\n <p className=\"pt-2 text-[11px] leading-relaxed text-muted-foreground/80\">\n Identity is powered by{' '}\n <a href=\"https://huudis.com\" className=\"underline hover:text-foreground\">\n Huudis\n </a>\n . One account for every Forjio product.\n </p>\n </div>\n );\n}\n\nfunction GoogleMark({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M21.6 12.227c0-.708-.064-1.39-.182-2.045H12v3.868h5.384a4.603 4.603 0 0 1-1.997 3.018v2.51h3.232c1.891-1.742 2.98-4.307 2.98-7.35Z\" fill=\"#4285F4\" />\n <path d=\"M12 22c2.7 0 4.965-.895 6.62-2.422l-3.233-2.51c-.895.6-2.041.955-3.386.955-2.604 0-4.81-1.76-5.596-4.122H3.067v2.59A9.996 9.996 0 0 0 12 22Z\" fill=\"#34A853\" />\n <path d=\"M6.404 13.9a6.016 6.016 0 0 1 0-3.8V7.512H3.067a9.996 9.996 0 0 0 0 8.977L6.404 13.9Z\" fill=\"#FBBC05\" />\n <path d=\"M12 5.977c1.468 0 2.786.505 3.823 1.497l2.868-2.868C16.96 2.986 14.696 2 12 2 8.118 2 4.76 4.232 3.067 7.51l3.337 2.59C7.19 7.737 9.396 5.977 12 5.977Z\" fill=\"#EA4335\" />\n </svg>\n );\n}\n\nfunction FacebookMark({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"#1877F2\" aria-hidden=\"true\">\n <path d=\"M24 12.073C24 5.404 18.627 0 12 0S0 5.404 0 12.073c0 6.026 4.388 11.022 10.125 11.927v-8.437H7.078v-3.49h3.047V9.41c0-3.026 1.792-4.697 4.533-4.697 1.313 0 2.686.236 2.686.236v2.971H15.83c-1.49 0-1.955.93-1.955 1.886v2.266h3.328l-.532 3.49h-2.796v8.437C19.612 23.095 24 18.099 24 12.073Z\" />\n </svg>\n );\n}\n\nfunction AppleMark({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M17.564 12.73c-.037-3.16 2.58-4.678 2.698-4.752-1.47-2.146-3.76-2.44-4.576-2.473-1.948-.2-3.8 1.148-4.788 1.148-.993 0-2.513-1.12-4.13-1.091-2.127.03-4.085 1.236-5.174 3.142-2.207 3.82-.562 9.463 1.58 12.56 1.052 1.514 2.306 3.216 3.952 3.155 1.586-.065 2.185-1.026 4.102-1.026 1.917 0 2.455 1.026 4.133.99 1.705-.03 2.785-1.546 3.83-3.066 1.207-1.757 1.702-3.462 1.731-3.55-.038-.018-3.325-1.274-3.358-5.037Zm-3.154-9.24c.878-1.06 1.467-2.542 1.306-4.014-1.26.051-2.79.838-3.695 1.898-.813.937-1.524 2.433-1.333 3.885 1.405.108 2.843-.712 3.722-1.77Z\" />\n </svg>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/AuthForm.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useState } from 'react';\nimport { useRouter, useSearchParams } from 'next/navigation';\nimport Link from 'next/link';\nimport { Loader2, AlertCircle, ShieldCheck, ArrowLeft } from 'lucide-react';\nimport { Turnstile } from '@marsidev/react-turnstile';\nimport { useTurnstileTheme } from './useTurnstileTheme';\nimport { Button } from './components/ui/button';\nimport { Input } from './components/ui/input';\nimport { Label } from './components/ui/label';\nimport { defaultEndpoints, type AuthEndpoints, type SocialProviders } from './types';\n\n// Cloudflare Turnstile is enabled family-wide by setting\n// NEXT_PUBLIC_TURNSTILE_SITE_KEY at build time. Next inlines NEXT_PUBLIC_*\n// referenced here (in node_modules) into each product's bundle, so no\n// per-product page edit is needed — set the env var + the widget appears,\n// and the token rides the login/signup request as `cf-turnstile-response`.\n// The backend (@forjio/sdk/auth-server) verifies it when\n// TURNSTILE_SECRET_KEY is set; both sides bypass gracefully when unset.\nconst TURNSTILE_SITE_KEY = process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY;\n\n/** Shape of the product-hosted MFA challenge the login POST returns when\n * the user has MFA enabled and the product opted in. */\ninterface MfaChallenge {\n mfaRequired?: boolean;\n mfaChallengeToken?: string;\n methods?: string[];\n expiresAt?: string;\n}\n\nexport interface AuthFormProps {\n mode: 'login' | 'signup';\n /** Display name shown in copy (\"New to Plugipay?\", \"Welcome back\"). */\n brand: string;\n /** Override the auth endpoint paths. Default matches Forjio family\n * `@forjio/sdk/auth-handlers` mounts. */\n endpoints?: Partial<AuthEndpoints>;\n /** Which social providers to render. Host fetches the provider\n * status; pass undefined to show all (fail-open). */\n providers?: SocialProviders | null;\n /** Default redirect target after a successful auth. Default\n * `/dashboard`. Search-param `?return_to=` overrides at runtime. */\n defaultReturnTo?: string;\n /** Mode-switch link targets. Default `/login` / `/signup`. Override\n * for products with non-default auth routes (e.g. role-scoped\n * `/creators/login` + `/creators/onboarding`). `?return_to=` is\n * appended automatically. */\n loginHref?: string;\n signupHref?: string;\n /** \"Forgot password?\" link target. Default `/forgot-password`. */\n forgotPasswordHref?: string;\n /** Extra fields merged into the login/signup request body — e.g. a\n * `role` discriminator for multi-role products. */\n extraBody?: Record<string, unknown>;\n /** Extra query params appended to the social-start URL — e.g.\n * `{ role }`, which the Huudis OIDC start needs to mint the correct\n * per-role session on callback. */\n socialParams?: Record<string, string>;\n}\n\nexport function AuthForm({\n mode,\n brand,\n endpoints,\n providers,\n defaultReturnTo = '/dashboard',\n loginHref = '/login',\n signupHref = '/signup',\n forgotPasswordHref = '/forgot-password',\n extraBody,\n socialParams,\n}: AuthFormProps) {\n const router = useRouter();\n const params = useSearchParams();\n const returnTo = params?.get('return_to') || defaultReturnTo;\n const ssoError = params?.get('sso_error');\n const ssoDetail = params?.get('sso_detail');\n const ep: AuthEndpoints = { ...defaultEndpoints, ...endpoints };\n\n const [email, setEmail] = useState('');\n const [password, setPassword] = useState('');\n const [name, setName] = useState('');\n const [submitting, setSubmitting] = useState(false);\n const [redirecting, setRedirecting] = useState(false);\n const [turnstileToken, setTurnstileToken] = useState('');\n const turnstileTheme = useTurnstileTheme();\n const [error, setError] = useState<string | null>(\n ssoError ? `Sign-in failed: ${ssoDetail || ssoError}` : null,\n );\n\n // Product-hosted MFA / OTP step. When the login POST returns\n // `{ mfaRequired, mfaChallengeToken, methods, expiresAt }` (optionally\n // wrapped in `{ data: … }`), the form swaps to the OTP step instead of\n // redirecting. Backward-compatible: a normal login response never sets\n // `mfaChallengeToken`, so the credentials step keeps today's behavior.\n const [step, setStep] = useState<'credentials' | 'otp'>('credentials');\n const [mfaChallengeToken, setMfaChallengeToken] = useState('');\n const [mfaMethods, setMfaMethods] = useState<string[]>([]);\n const [mfaExpiresAt, setMfaExpiresAt] = useState<string | null>(null);\n const [code, setCode] = useState('');\n\n function redirectAfterAuth() {\n router.push(returnTo);\n router.refresh();\n }\n\n async function submit(e: React.FormEvent) {\n e.preventDefault();\n setError(null);\n setSubmitting(true);\n try {\n const path = mode === 'signup' ? ep.signup : ep.login;\n const body: Record<string, unknown> = { email, password, ...extraBody };\n if (mode === 'signup' && name.trim()) body.name = name.trim();\n if (TURNSTILE_SITE_KEY) body['cf-turnstile-response'] = turnstileToken;\n const res = await fetch(path, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const payload = (await res.json().catch(() => null)) as {\n error?: { code?: string; message?: string };\n } | null;\n // The product BFF does not do inline MFA. When a user has MFA\n // enabled, Huudis ROPC fails and the SDK returns 401 with\n // `code: 'MFA_REQUIRED'`. Hand off to the Huudis hosted-login\n // flow (no `provider=` param) — Huudis performs the challenge.\n // `socialParams` (e.g. `{ role }`) MUST ride along so a\n // multi-role product mints the correct per-role session on the\n // OIDC callback — otherwise the role is lost and the user is\n // gated on the wrong portal.\n if (payload?.error?.code === 'MFA_REQUIRED') {\n setRedirecting(true);\n const qs = new URLSearchParams({ return_to: returnTo, ...socialParams });\n window.location.href = `${ep.socialStart}?${qs.toString()}`;\n return;\n }\n throw new Error(payload?.error?.message ?? `Request failed (${res.status})`);\n }\n // Product-hosted MFA opt-in: the SDK responds 200 with an MFA\n // challenge (possibly wrapped in `{ data: … }`) instead of a\n // session. Swap to the in-product OTP step rather than redirect.\n const payload = (await res.json().catch(() => null)) as\n | { data?: MfaChallenge } & MfaChallenge\n | null;\n const challenge = payload?.data ?? payload;\n if (challenge?.mfaRequired && challenge.mfaChallengeToken) {\n setMfaChallengeToken(challenge.mfaChallengeToken);\n setMfaMethods(Array.isArray(challenge.methods) ? challenge.methods : []);\n setMfaExpiresAt(challenge.expiresAt ?? null);\n setCode('');\n setStep('otp');\n return;\n }\n redirectAfterAuth();\n } catch (err) {\n setError((err as Error).message);\n } finally {\n setSubmitting(false);\n }\n }\n\n async function submitMfa(e: React.FormEvent) {\n e.preventDefault();\n setError(null);\n setSubmitting(true);\n try {\n const body: Record<string, unknown> = { mfaChallengeToken, code };\n // MFA verify doesn't need a fresh captcha; only forward a token the\n // credentials step already produced (never block on a missing one).\n if (TURNSTILE_SITE_KEY && turnstileToken) body['cf-turnstile-response'] = turnstileToken;\n const res = await fetch(ep.mfa, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const payload = (await res.json().catch(() => null)) as {\n error?: { code?: string; message?: string };\n } | null;\n throw new Error(payload?.error?.message ?? `Request failed (${res.status})`);\n }\n redirectAfterAuth();\n } catch (err) {\n setError((err as Error).message);\n } finally {\n setSubmitting(false);\n }\n }\n\n function backToCredentials() {\n setStep('credentials');\n setError(null);\n setCode('');\n setMfaChallengeToken('');\n setMfaMethods([]);\n setMfaExpiresAt(null);\n }\n\n // Live countdown to the challenge expiry, shown as a small hint on the\n // OTP step. Recomputed every second while the OTP step is visible.\n const [secondsLeft, setSecondsLeft] = useState<number | null>(null);\n useEffect(() => {\n if (step !== 'otp' || !mfaExpiresAt) {\n setSecondsLeft(null);\n return;\n }\n const target = new Date(mfaExpiresAt).getTime();\n if (Number.isNaN(target)) {\n setSecondsLeft(null);\n return;\n }\n const tick = () => setSecondsLeft(Math.max(0, Math.round((target - Date.now()) / 1000)));\n tick();\n const id = setInterval(tick, 1000);\n return () => clearInterval(id);\n }, [step, mfaExpiresAt]);\n\n const otherMode = mode === 'login' ? 'signup' : 'login';\n const otherHref = `${otherMode === 'signup' ? signupHref : loginHref}?return_to=${encodeURIComponent(returnTo)}`;\n const socialUrl = (provider: 'google' | 'apple' | 'facebook') => {\n const qs = new URLSearchParams({ provider, return_to: returnTo, ...socialParams });\n return `${ep.socialStart}?${qs.toString()}`;\n };\n\n const showGoogle = providers?.google !== false;\n const showApple = providers?.apple !== false;\n const showFacebook = providers?.facebook !== false;\n const hasAnySocial = showGoogle || showApple || showFacebook;\n\n return (\n <div className=\"space-y-4\">\n {error && !redirecting && (\n <div className=\"flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive\">\n <AlertCircle className=\"h-4 w-4 shrink-0 mt-0.5\" />\n <span>{error}</span>\n </div>\n )}\n\n {redirecting && (\n <div className=\"flex items-start gap-2 rounded-md border border-border bg-accent px-3 py-2 text-sm text-muted-foreground\">\n <Loader2 className=\"h-4 w-4 shrink-0 mt-0.5 animate-spin\" />\n <span>Redirecting you to complete two-factor sign-in…</span>\n </div>\n )}\n\n {step === 'otp' && (\n <div className=\"space-y-4\">\n <div className=\"space-y-1\">\n <div className=\"flex items-center gap-2 text-sm font-medium text-foreground\">\n <ShieldCheck className=\"h-4 w-4 shrink-0\" />\n Two-factor authentication\n </div>\n <p className=\"text-xs leading-relaxed text-muted-foreground\">\n Enter the 6-digit code from your authenticator app.\n </p>\n </div>\n\n <form onSubmit={submitMfa} className=\"space-y-3\">\n <div>\n <Label\n htmlFor=\"mfa-code\"\n className=\"mb-1 block text-xs leading-4 text-muted-foreground\"\n >\n Verification code\n </Label>\n <Input\n id=\"mfa-code\"\n type=\"text\"\n required\n autoFocus\n inputMode=\"numeric\"\n autoComplete=\"one-time-code\"\n pattern=\"[0-9]*\"\n maxLength={6}\n value={code}\n onChange={(e) => setCode(e.target.value.replace(/\\D/g, '').slice(0, 6))}\n aria-label=\"Six-digit verification code\"\n className=\"h-auto border-border bg-background py-2 text-center text-lg tracking-[0.4em] shadow-none focus:outline-none focus:ring-1 focus:ring-primary\"\n />\n {secondsLeft !== null && (\n <p className=\"mt-1 text-[11px] text-muted-foreground\">\n {secondsLeft > 0\n ? `Code expires in ${Math.floor(secondsLeft / 60)}:${String(secondsLeft % 60).padStart(2, '0')}`\n : 'Code expired — go back and sign in again.'}\n </p>\n )}\n </div>\n <Button\n type=\"submit\"\n disabled={submitting || code.length !== 6}\n className=\"flex h-auto w-full py-2.5 shadow-none hover:opacity-90\"\n >\n {submitting && <Loader2 className=\"h-4 w-4 animate-spin\" />}\n {submitting ? 'Verifying…' : 'Verify'}\n </Button>\n </form>\n\n <button\n type=\"button\"\n onClick={backToCredentials}\n className=\"flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground\"\n >\n <ArrowLeft className=\"h-3.5 w-3.5\" />\n Back to sign in\n </button>\n </div>\n )}\n\n {step === 'credentials' && hasAnySocial && (\n <>\n <div className=\"grid gap-2\">\n {showGoogle && (\n <Button\n asChild\n variant=\"outline\"\n className=\"flex h-auto w-full border-border py-2 shadow-none\"\n >\n <a href={socialUrl('google')}>\n <GoogleMark className=\"h-4 w-4\" />\n Continue with Google\n </a>\n </Button>\n )}\n {showApple && (\n <Button\n asChild\n variant=\"outline\"\n className=\"flex h-auto w-full border-border py-2 shadow-none\"\n >\n <a href={socialUrl('apple')}>\n <AppleMark className=\"h-4 w-4\" />\n Continue with Apple\n </a>\n </Button>\n )}\n {showFacebook && (\n <Button\n asChild\n variant=\"outline\"\n className=\"flex h-auto w-full border-border py-2 shadow-none\"\n >\n <a href={socialUrl('facebook')}>\n <FacebookMark className=\"h-4 w-4\" />\n Continue with Facebook\n </a>\n </Button>\n )}\n </div>\n\n <div className=\"my-4 flex items-center gap-3 text-[11px] text-muted-foreground\">\n <div className=\"flex-1 border-t border-border\" />\n OR\n <div className=\"flex-1 border-t border-border\" />\n </div>\n </>\n )}\n\n {step === 'credentials' && (\n <form onSubmit={submit} className=\"space-y-3\">\n <div>\n <Label className=\"mb-1 block text-xs leading-4 text-muted-foreground\">Email</Label>\n <Input\n type=\"email\"\n required\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n autoComplete=\"email\"\n className=\"h-auto border-border bg-background py-2 text-sm shadow-none focus:outline-none focus:ring-1 focus:ring-primary\"\n />\n </div>\n <div>\n <Label className=\"mb-1 block text-xs leading-4 text-muted-foreground\">Password</Label>\n <Input\n type=\"password\"\n required\n minLength={mode === 'signup' ? 10 : undefined}\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n autoComplete={mode === 'signup' ? 'new-password' : 'current-password'}\n className=\"h-auto border-border bg-background py-2 text-sm shadow-none focus:outline-none focus:ring-1 focus:ring-primary\"\n />\n {mode === 'signup' && (\n <p className=\"mt-1 text-[11px] text-muted-foreground\">At least 10 characters, with a letter and a number.</p>\n )}\n </div>\n {mode === 'signup' && (\n <div>\n <Label className=\"mb-1 block text-xs leading-4 text-muted-foreground\">\n Your name <span className=\"text-muted-foreground/60\">(optional)</span>\n </Label>\n <Input\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n autoComplete=\"name\"\n className=\"h-auto border-border bg-background py-2 text-sm shadow-none focus:outline-none focus:ring-1 focus:ring-primary\"\n />\n </div>\n )}\n {TURNSTILE_SITE_KEY && (\n <div className=\"flex justify-center py-1\">\n <Turnstile\n siteKey={TURNSTILE_SITE_KEY}\n onSuccess={setTurnstileToken}\n onError={() => setError('Security check failed.')}\n options={{ theme: turnstileTheme }}\n />\n </div>\n )}\n <Button\n type=\"submit\"\n disabled={submitting || redirecting}\n className=\"flex h-auto w-full py-2.5 shadow-none hover:opacity-90\"\n >\n {(submitting || redirecting) && <Loader2 className=\"h-4 w-4 animate-spin\" />}\n {redirecting\n ? 'Redirecting…'\n : submitting\n ? mode === 'signup'\n ? 'Creating…'\n : 'Signing in…'\n : mode === 'signup'\n ? 'Create account'\n : 'Sign in'}\n </Button>\n </form>\n )}\n\n {step === 'credentials' && (\n <div className=\"flex items-center justify-between pt-2 text-xs text-muted-foreground\">\n {mode === 'login' && (\n <Link href={forgotPasswordHref} className=\"hover:text-foreground\">\n Forgot password?\n </Link>\n )}\n <span className={mode === 'login' ? '' : 'ml-auto'}>\n {mode === 'login' ? `New to ${brand}?` : 'Already have an account?'}{' '}\n <Link href={otherHref} className=\"font-medium text-foreground hover:underline\">\n {mode === 'login' ? 'Sign up' : 'Sign in'}\n </Link>\n </span>\n </div>\n )}\n <p className=\"pt-2 text-[11px] leading-relaxed text-muted-foreground/80\">\n Identity is powered by{' '}\n <a href=\"https://huudis.com\" className=\"underline hover:text-foreground\">\n Huudis\n </a>\n . One account for every Forjio product.\n </p>\n </div>\n );\n}\n\nfunction GoogleMark({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path d=\"M21.6 12.227c0-.708-.064-1.39-.182-2.045H12v3.868h5.384a4.603 4.603 0 0 1-1.997 3.018v2.51h3.232c1.891-1.742 2.98-4.307 2.98-7.35Z\" fill=\"#4285F4\" />\n <path d=\"M12 22c2.7 0 4.965-.895 6.62-2.422l-3.233-2.51c-.895.6-2.041.955-3.386.955-2.604 0-4.81-1.76-5.596-4.122H3.067v2.59A9.996 9.996 0 0 0 12 22Z\" fill=\"#34A853\" />\n <path d=\"M6.404 13.9a6.016 6.016 0 0 1 0-3.8V7.512H3.067a9.996 9.996 0 0 0 0 8.977L6.404 13.9Z\" fill=\"#FBBC05\" />\n <path d=\"M12 5.977c1.468 0 2.786.505 3.823 1.497l2.868-2.868C16.96 2.986 14.696 2 12 2 8.118 2 4.76 4.232 3.067 7.51l3.337 2.59C7.19 7.737 9.396 5.977 12 5.977Z\" fill=\"#EA4335\" />\n </svg>\n );\n}\n\nfunction FacebookMark({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"#1877F2\" aria-hidden=\"true\">\n <path d=\"M24 12.073C24 5.404 18.627 0 12 0S0 5.404 0 12.073c0 6.026 4.388 11.022 10.125 11.927v-8.437H7.078v-3.49h3.047V9.41c0-3.026 1.792-4.697 4.533-4.697 1.313 0 2.686.236 2.686.236v2.971H15.83c-1.49 0-1.955.93-1.955 1.886v2.266h3.328l-.532 3.49h-2.796v8.437C19.612 23.095 24 18.099 24 12.073Z\" />\n </svg>\n );\n}\n\nfunction AppleMark({ className }: { className?: string }) {\n return (\n <svg className={className} viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M17.564 12.73c-.037-3.16 2.58-4.678 2.698-4.752-1.47-2.146-3.76-2.44-4.576-2.473-1.948-.2-3.8 1.148-4.788 1.148-.993 0-2.513-1.12-4.13-1.091-2.127.03-4.085 1.236-5.174 3.142-2.207 3.82-.562 9.463 1.58 12.56 1.052 1.514 2.306 3.216 3.952 3.155 1.586-.065 2.185-1.026 4.102-1.026 1.917 0 2.455 1.026 4.133.99 1.705-.03 2.785-1.546 3.83-3.066 1.207-1.757 1.702-3.462 1.731-3.55-.038-.018-3.325-1.274-3.358-5.037Zm-3.154-9.24c.878-1.06 1.467-2.542 1.306-4.014-1.26.051-2.79.838-3.695 1.898-.813.937-1.524 2.433-1.333 3.885 1.405.108 2.843-.712 3.722-1.77Z\" />\n </svg>\n );\n}\n"],"mappings":"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","names":["payload"]}
@@ -38,6 +38,9 @@ var import_link = __toESM(require("next/link"), 1);
38
38
  var import_lucide_react = require("lucide-react");
39
39
  var import_react_turnstile = require("@marsidev/react-turnstile");
40
40
  var import_useTurnstileTheme = require("./useTurnstileTheme");
41
+ var import_button = require("./components/ui/button");
42
+ var import_input = require("./components/ui/input");
43
+ var import_label = require("./components/ui/label");
41
44
  var import_types = require("./types");
42
45
  const TURNSTILE_SITE_KEY = process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY;
43
46
  function ForgotPasswordForm({ endpoints } = {}) {
@@ -91,9 +94,9 @@ function ForgotPasswordForm({ endpoints } = {}) {
91
94
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: error })
92
95
  ] }),
93
96
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
94
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "mb-1 block text-xs font-medium text-muted-foreground", children: "Email" }),
97
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_label.Label, { className: "mb-1 block text-xs leading-4 text-muted-foreground", children: "Email" }),
95
98
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
96
- "input",
99
+ import_input.Input,
97
100
  {
98
101
  type: "email",
99
102
  required: true,
@@ -101,7 +104,7 @@ function ForgotPasswordForm({ endpoints } = {}) {
101
104
  onChange: (e) => setEmail(e.target.value),
102
105
  autoComplete: "email",
103
106
  autoFocus: true,
104
- className: "w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-primary"
107
+ className: "h-auto border-border bg-background py-2 text-sm shadow-none focus:outline-none focus:ring-1 focus:ring-primary"
105
108
  }
106
109
  )
107
110
  ] }),
@@ -115,11 +118,11 @@ function ForgotPasswordForm({ endpoints } = {}) {
115
118
  }
116
119
  ) }),
117
120
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
118
- "button",
121
+ import_button.Button,
119
122
  {
120
123
  type: "submit",
121
124
  disabled: submitting,
122
- className: "flex w-full items-center justify-center gap-2 rounded-md bg-primary py-2.5 text-sm font-medium text-primary-foreground transition hover:opacity-90 disabled:opacity-50",
125
+ className: "flex h-auto w-full py-2.5 shadow-none hover:opacity-90",
123
126
  children: [
124
127
  submitting && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Loader2, { className: "h-4 w-4 animate-spin" }),
125
128
  submitting ? "Sending\u2026" : "Send reset link"
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ForgotPasswordForm.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport Link from 'next/link';\nimport { Loader2, AlertCircle, CheckCircle2 } from 'lucide-react';\nimport { Turnstile } from '@marsidev/react-turnstile';\nimport { useTurnstileTheme } from './useTurnstileTheme';\nimport { defaultEndpoints, type AuthEndpoints } from './types';\n\n// Enabled family-wide via NEXT_PUBLIC_TURNSTILE_SITE_KEY (see AuthForm).\nconst TURNSTILE_SITE_KEY = process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY;\n\nexport interface ForgotPasswordFormProps {\n endpoints?: Partial<AuthEndpoints>;\n}\n\nexport function ForgotPasswordForm({ endpoints }: ForgotPasswordFormProps = {}) {\n const ep: AuthEndpoints = { ...defaultEndpoints, ...endpoints };\n const [email, setEmail] = useState('');\n const [submitting, setSubmitting] = useState(false);\n const [sent, setSent] = useState(false);\n const [turnstileToken, setTurnstileToken] = useState('');\n const turnstileTheme = useTurnstileTheme();\n const [error, setError] = useState<string | null>(null);\n\n async function submit(e: React.FormEvent) {\n e.preventDefault();\n setError(null);\n setSubmitting(true);\n try {\n const res = await fetch(ep.forgotPassword, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n email,\n ...(TURNSTILE_SITE_KEY ? { 'cf-turnstile-response': turnstileToken } : {}),\n }),\n });\n if (!res.ok) {\n const payload = (await res.json().catch(() => null)) as { error?: { message?: string } } | null;\n throw new Error(payload?.error?.message ?? `Request failed (${res.status})`);\n }\n setSent(true);\n } catch (err) {\n setError((err as Error).message);\n } finally {\n setSubmitting(false);\n }\n }\n\n if (sent) {\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-start gap-2 rounded-md border border-primary/30 bg-primary/10 px-3 py-2 text-sm text-foreground\">\n <CheckCircle2 className=\"h-4 w-4 shrink-0 mt-0.5 text-primary\" />\n <span>\n If <strong>{email}</strong> has a Huudis account, we&rsquo;ve sent a reset link. It expires in 1 hour.\n </span>\n </div>\n <Link href=\"/login\" className=\"block text-center text-sm font-medium text-foreground hover:underline\">\n Back to sign in\n </Link>\n </div>\n );\n }\n\n return (\n <form onSubmit={submit} className=\"space-y-4\">\n {error && (\n <div className=\"flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive\">\n <AlertCircle className=\"h-4 w-4 shrink-0 mt-0.5\" />\n <span>{error}</span>\n </div>\n )}\n <div>\n <label className=\"mb-1 block text-xs font-medium text-muted-foreground\">Email</label>\n <input\n type=\"email\"\n required\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n autoComplete=\"email\"\n autoFocus\n className=\"w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-primary\"\n />\n </div>\n {TURNSTILE_SITE_KEY && (\n <div className=\"flex justify-center py-1\">\n <Turnstile\n siteKey={TURNSTILE_SITE_KEY}\n onSuccess={setTurnstileToken}\n onError={() => setError('Security check failed.')}\n options={{ theme: turnstileTheme }}\n />\n </div>\n )}\n <button\n type=\"submit\"\n disabled={submitting}\n className=\"flex w-full items-center justify-center gap-2 rounded-md bg-primary py-2.5 text-sm font-medium text-primary-foreground transition hover:opacity-90 disabled:opacity-50\"\n >\n {submitting && <Loader2 className=\"h-4 w-4 animate-spin\" />}\n {submitting ? 'Sending…' : 'Send reset link'}\n </button>\n <div className=\"text-center text-xs text-muted-foreground\">\n Remembered it?{' '}\n <Link href=\"/login\" className=\"font-medium text-foreground hover:underline\">\n Sign in\n </Link>\n </div>\n </form>\n );\n}\n"],"mappings":"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","names":["Link"]}
1
+ {"version":3,"sources":["../src/ForgotPasswordForm.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport Link from 'next/link';\nimport { Loader2, AlertCircle, CheckCircle2 } from 'lucide-react';\nimport { Turnstile } from '@marsidev/react-turnstile';\nimport { useTurnstileTheme } from './useTurnstileTheme';\nimport { Button } from './components/ui/button';\nimport { Input } from './components/ui/input';\nimport { Label } from './components/ui/label';\nimport { defaultEndpoints, type AuthEndpoints } from './types';\n\n// Enabled family-wide via NEXT_PUBLIC_TURNSTILE_SITE_KEY (see AuthForm).\nconst TURNSTILE_SITE_KEY = process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY;\n\nexport interface ForgotPasswordFormProps {\n endpoints?: Partial<AuthEndpoints>;\n}\n\nexport function ForgotPasswordForm({ endpoints }: ForgotPasswordFormProps = {}) {\n const ep: AuthEndpoints = { ...defaultEndpoints, ...endpoints };\n const [email, setEmail] = useState('');\n const [submitting, setSubmitting] = useState(false);\n const [sent, setSent] = useState(false);\n const [turnstileToken, setTurnstileToken] = useState('');\n const turnstileTheme = useTurnstileTheme();\n const [error, setError] = useState<string | null>(null);\n\n async function submit(e: React.FormEvent) {\n e.preventDefault();\n setError(null);\n setSubmitting(true);\n try {\n const res = await fetch(ep.forgotPassword, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n email,\n ...(TURNSTILE_SITE_KEY ? { 'cf-turnstile-response': turnstileToken } : {}),\n }),\n });\n if (!res.ok) {\n const payload = (await res.json().catch(() => null)) as { error?: { message?: string } } | null;\n throw new Error(payload?.error?.message ?? `Request failed (${res.status})`);\n }\n setSent(true);\n } catch (err) {\n setError((err as Error).message);\n } finally {\n setSubmitting(false);\n }\n }\n\n if (sent) {\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-start gap-2 rounded-md border border-primary/30 bg-primary/10 px-3 py-2 text-sm text-foreground\">\n <CheckCircle2 className=\"h-4 w-4 shrink-0 mt-0.5 text-primary\" />\n <span>\n If <strong>{email}</strong> has a Huudis account, we&rsquo;ve sent a reset link. It expires in 1 hour.\n </span>\n </div>\n <Link href=\"/login\" className=\"block text-center text-sm font-medium text-foreground hover:underline\">\n Back to sign in\n </Link>\n </div>\n );\n }\n\n return (\n <form onSubmit={submit} className=\"space-y-4\">\n {error && (\n <div className=\"flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive\">\n <AlertCircle className=\"h-4 w-4 shrink-0 mt-0.5\" />\n <span>{error}</span>\n </div>\n )}\n <div>\n <Label className=\"mb-1 block text-xs leading-4 text-muted-foreground\">Email</Label>\n <Input\n type=\"email\"\n required\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n autoComplete=\"email\"\n autoFocus\n className=\"h-auto border-border bg-background py-2 text-sm shadow-none focus:outline-none focus:ring-1 focus:ring-primary\"\n />\n </div>\n {TURNSTILE_SITE_KEY && (\n <div className=\"flex justify-center py-1\">\n <Turnstile\n siteKey={TURNSTILE_SITE_KEY}\n onSuccess={setTurnstileToken}\n onError={() => setError('Security check failed.')}\n options={{ theme: turnstileTheme }}\n />\n </div>\n )}\n <Button\n type=\"submit\"\n disabled={submitting}\n className=\"flex h-auto w-full py-2.5 shadow-none hover:opacity-90\"\n >\n {submitting && <Loader2 className=\"h-4 w-4 animate-spin\" />}\n {submitting ? 'Sending…' : 'Send reset link'}\n </Button>\n <div className=\"text-center text-xs text-muted-foreground\">\n Remembered it?{' '}\n <Link href=\"/login\" className=\"font-medium text-foreground hover:underline\">\n Sign in\n </Link>\n </div>\n </form>\n );\n}\n"],"mappings":"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","names":["Link"]}
@@ -1,9 +1,9 @@
1
- import * as react from 'react';
1
+ import * as React from 'react';
2
2
  import { AuthEndpoints } from './types.cjs';
3
3
 
4
4
  interface ForgotPasswordFormProps {
5
5
  endpoints?: Partial<AuthEndpoints>;
6
6
  }
7
- declare function ForgotPasswordForm({ endpoints }?: ForgotPasswordFormProps): react.JSX.Element;
7
+ declare function ForgotPasswordForm({ endpoints }?: ForgotPasswordFormProps): React.JSX.Element;
8
8
 
9
9
  export { ForgotPasswordForm, type ForgotPasswordFormProps };
@@ -1,9 +1,9 @@
1
- import * as react from 'react';
1
+ import * as React from 'react';
2
2
  import { AuthEndpoints } from './types.js';
3
3
 
4
4
  interface ForgotPasswordFormProps {
5
5
  endpoints?: Partial<AuthEndpoints>;
6
6
  }
7
- declare function ForgotPasswordForm({ endpoints }?: ForgotPasswordFormProps): react.JSX.Element;
7
+ declare function ForgotPasswordForm({ endpoints }?: ForgotPasswordFormProps): React.JSX.Element;
8
8
 
9
9
  export { ForgotPasswordForm, type ForgotPasswordFormProps };
@@ -5,6 +5,9 @@ import Link from "next/link";
5
5
  import { Loader2, AlertCircle, CheckCircle2 } from "lucide-react";
6
6
  import { Turnstile } from "@marsidev/react-turnstile";
7
7
  import { useTurnstileTheme } from "./useTurnstileTheme";
8
+ import { Button } from "./components/ui/button";
9
+ import { Input } from "./components/ui/input";
10
+ import { Label } from "./components/ui/label";
8
11
  import { defaultEndpoints } from "./types";
9
12
  const TURNSTILE_SITE_KEY = process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY;
10
13
  function ForgotPasswordForm({ endpoints } = {}) {
@@ -58,9 +61,9 @@ function ForgotPasswordForm({ endpoints } = {}) {
58
61
  /* @__PURE__ */ jsx("span", { children: error })
59
62
  ] }),
60
63
  /* @__PURE__ */ jsxs("div", { children: [
61
- /* @__PURE__ */ jsx("label", { className: "mb-1 block text-xs font-medium text-muted-foreground", children: "Email" }),
64
+ /* @__PURE__ */ jsx(Label, { className: "mb-1 block text-xs leading-4 text-muted-foreground", children: "Email" }),
62
65
  /* @__PURE__ */ jsx(
63
- "input",
66
+ Input,
64
67
  {
65
68
  type: "email",
66
69
  required: true,
@@ -68,7 +71,7 @@ function ForgotPasswordForm({ endpoints } = {}) {
68
71
  onChange: (e) => setEmail(e.target.value),
69
72
  autoComplete: "email",
70
73
  autoFocus: true,
71
- className: "w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-primary"
74
+ className: "h-auto border-border bg-background py-2 text-sm shadow-none focus:outline-none focus:ring-1 focus:ring-primary"
72
75
  }
73
76
  )
74
77
  ] }),
@@ -82,11 +85,11 @@ function ForgotPasswordForm({ endpoints } = {}) {
82
85
  }
83
86
  ) }),
84
87
  /* @__PURE__ */ jsxs(
85
- "button",
88
+ Button,
86
89
  {
87
90
  type: "submit",
88
91
  disabled: submitting,
89
- className: "flex w-full items-center justify-center gap-2 rounded-md bg-primary py-2.5 text-sm font-medium text-primary-foreground transition hover:opacity-90 disabled:opacity-50",
92
+ className: "flex h-auto w-full py-2.5 shadow-none hover:opacity-90",
90
93
  children: [
91
94
  submitting && /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }),
92
95
  submitting ? "Sending\u2026" : "Send reset link"