@puzzle-ui/fullstack 0.0.0-stage → 0.5.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 (80) hide show
  1. package/README.md +65 -2
  2. package/dist/LICENSE +21 -0
  3. package/dist/auth/client/auth.css +206 -0
  4. package/dist/auth/client/experience.cjs +340 -0
  5. package/dist/auth/client/experience.cjs.map +1 -0
  6. package/dist/auth/client/experience.d.cts +67 -0
  7. package/dist/auth/client/experience.d.ts +67 -0
  8. package/dist/auth/client/experience.js +340 -0
  9. package/dist/auth/client/experience.js.map +1 -0
  10. package/dist/auth/client/index.cjs +234 -0
  11. package/dist/auth/client/index.cjs.map +1 -0
  12. package/dist/auth/client/index.d.cts +48 -0
  13. package/dist/auth/client/index.d.ts +48 -0
  14. package/dist/auth/client/index.js +216 -0
  15. package/dist/auth/client/index.js.map +1 -0
  16. package/dist/auth/client/messages.cjs +80 -0
  17. package/dist/auth/client/messages.cjs.map +1 -0
  18. package/dist/auth/client/messages.d.cts +92 -0
  19. package/dist/auth/client/messages.d.ts +92 -0
  20. package/dist/auth/client/messages.js +79 -0
  21. package/dist/auth/client/messages.js.map +1 -0
  22. package/dist/auth/client/passkeys.cjs +43 -0
  23. package/dist/auth/client/passkeys.cjs.map +1 -0
  24. package/dist/auth/client/passkeys.d.cts +23 -0
  25. package/dist/auth/client/passkeys.d.ts +23 -0
  26. package/dist/auth/client/passkeys.js +40 -0
  27. package/dist/auth/client/passkeys.js.map +1 -0
  28. package/dist/auth/client/react.cjs +859 -0
  29. package/dist/auth/client/react.cjs.map +1 -0
  30. package/dist/auth/client/react.d.cts +72 -0
  31. package/dist/auth/client/react.d.ts +72 -0
  32. package/dist/auth/client/react.js +850 -0
  33. package/dist/auth/client/react.js.map +1 -0
  34. package/dist/auth/next/index.cjs +40 -0
  35. package/dist/auth/next/index.cjs.map +1 -0
  36. package/dist/auth/next/index.d.cts +12 -0
  37. package/dist/auth/next/index.d.ts +12 -0
  38. package/dist/auth/next/index.js +38 -0
  39. package/dist/auth/next/index.js.map +1 -0
  40. package/dist/auth/server/health.cjs +51 -0
  41. package/dist/auth/server/health.cjs.map +1 -0
  42. package/dist/auth/server/health.d.cts +15 -0
  43. package/dist/auth/server/health.d.ts +15 -0
  44. package/dist/auth/server/health.js +51 -0
  45. package/dist/auth/server/health.js.map +1 -0
  46. package/dist/auth/server/index.cjs +108 -0
  47. package/dist/auth/server/index.cjs.map +1 -0
  48. package/dist/auth/server/index.d.cts +22 -0
  49. package/dist/auth/server/index.d.ts +22 -0
  50. package/dist/auth/server/index.js +88 -0
  51. package/dist/auth/server/index.js.map +1 -0
  52. package/dist/auth/server/lifecycle.cjs +126 -0
  53. package/dist/auth/server/lifecycle.cjs.map +1 -0
  54. package/dist/auth/server/lifecycle.d.cts +20 -0
  55. package/dist/auth/server/lifecycle.d.ts +20 -0
  56. package/dist/auth/server/lifecycle.js +126 -0
  57. package/dist/auth/server/lifecycle.js.map +1 -0
  58. package/dist/auth/server/mfa.cjs +682 -0
  59. package/dist/auth/server/mfa.cjs.map +1 -0
  60. package/dist/auth/server/mfa.d.cts +90 -0
  61. package/dist/auth/server/mfa.d.ts +90 -0
  62. package/dist/auth/server/mfa.js +673 -0
  63. package/dist/auth/server/mfa.js.map +1 -0
  64. package/dist/auth/server/session.cjs +58 -0
  65. package/dist/auth/server/session.cjs.map +1 -0
  66. package/dist/auth/server/session.d.cts +14 -0
  67. package/dist/auth/server/session.d.ts +14 -0
  68. package/dist/auth/server/session.js +53 -0
  69. package/dist/auth/server/session.js.map +1 -0
  70. package/dist/auth/shared/index.cjs +0 -0
  71. package/dist/auth/shared/index.d.cts +64 -0
  72. package/dist/auth/shared/index.d.ts +64 -0
  73. package/dist/auth/shared/index.js +1 -0
  74. package/dist/auth/tanstack/index.cjs +34 -0
  75. package/dist/auth/tanstack/index.cjs.map +1 -0
  76. package/dist/auth/tanstack/index.d.cts +7 -0
  77. package/dist/auth/tanstack/index.d.ts +7 -0
  78. package/dist/auth/tanstack/index.js +32 -0
  79. package/dist/auth/tanstack/index.js.map +1 -0
  80. package/package.json +113 -4
@@ -0,0 +1,859 @@
1
+ "use client";
2
+ const require_auth_client_index = require("./index.cjs");
3
+ const require_messages = require("./messages.cjs");
4
+ ;/* empty css */
5
+ let react = require("react");
6
+ let _puzzle_ui_ui = require("@puzzle-ui/ui");
7
+ let react_jsx_runtime = require("react/jsx-runtime");
8
+ // src/auth/client/react.tsx
9
+ const RequiredMfaEnrollment = ({ className }) => {
10
+ const { client, snapshot, messages, refresh } = useAuth();
11
+ const [enrollment, setEnrollment] = (0, react.useState)(null);
12
+ const [code, setCode] = (0, react.useState)("");
13
+ const [recoveryCodes, setRecoveryCodes] = (0, react.useState)([]);
14
+ const [loading, setLoading] = (0, react.useState)(false);
15
+ const [error, setError] = (0, react.useState)(null);
16
+ if (snapshot.status !== "mfa-enrollment-required" && snapshot.status !== "mfa-enrollment-pending") return null;
17
+ const handleTotpStart = async () => {
18
+ setError(null);
19
+ setLoading(true);
20
+ try {
21
+ setEnrollment(await client.beginRequiredTotpEnrollment());
22
+ } catch (cause) {
23
+ setError(getErrorMessage(cause, messages));
24
+ } finally {
25
+ setLoading(false);
26
+ }
27
+ };
28
+ const handleTotpFinish = async (event) => {
29
+ event.preventDefault();
30
+ if (!enrollment) return;
31
+ setError(null);
32
+ setLoading(true);
33
+ try {
34
+ const result = await client.verifyRequiredTotpEnrollment(enrollment.factorId, code);
35
+ setRecoveryCodes(result.recoveryCodes);
36
+ setEnrollment(null);
37
+ await refresh();
38
+ } catch (cause) {
39
+ setError(getErrorMessage(cause, messages));
40
+ } finally {
41
+ setLoading(false);
42
+ }
43
+ };
44
+ const handlePasskey = async () => {
45
+ setError(null);
46
+ setLoading(true);
47
+ try {
48
+ const result = await client.registerRequiredPasskey();
49
+ setRecoveryCodes(result.recoveryCodes);
50
+ await refresh();
51
+ } catch (cause) {
52
+ setError(getErrorMessage(cause, messages));
53
+ } finally {
54
+ setLoading(false);
55
+ }
56
+ };
57
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
58
+ className: `puzzle-auth ${className ?? ""}`,
59
+ children: [
60
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
61
+ className: "puzzle-auth__header",
62
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", { children: messages.mfaTitle }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: "Additional authentication setup is required before you can continue." })]
63
+ }),
64
+ error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.Alert, {
65
+ severity: "error",
66
+ children: error
67
+ }),
68
+ recoveryCodes.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
69
+ className: "puzzle-auth__recovery-codes",
70
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.Alert, {
71
+ severity: "success",
72
+ children: messages.saveRecoveryCodes
73
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { children: recoveryCodes.map((value) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: value }) }, value)) })]
74
+ }) : enrollment ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
75
+ className: "puzzle-auth__form",
76
+ onSubmit: handleTotpFinish,
77
+ children: [
78
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.FormField, {
79
+ label: messages.totpSecret,
80
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.TextInput, {
81
+ value: enrollment.secret,
82
+ readOnly: true,
83
+ "aria-label": messages.totpSecret
84
+ })
85
+ }),
86
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
87
+ href: enrollment.otpauthUrl,
88
+ children: "Open authenticator application"
89
+ }) }),
90
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.FormField, {
91
+ label: messages.verificationCode,
92
+ required: true,
93
+ controlId: "puzzle-auth-required-totp",
94
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.TextInput, {
95
+ value: code,
96
+ autoComplete: "one-time-code",
97
+ onChange: (event) => setCode(event.currentTarget.value),
98
+ required: true
99
+ })
100
+ }),
101
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.Button, {
102
+ type: "submit",
103
+ loading,
104
+ disabled: !code.trim(),
105
+ children: messages.verifyTotp
106
+ })
107
+ ]
108
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
109
+ className: "puzzle-auth__actions",
110
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.Button, {
111
+ disabled: loading,
112
+ loading,
113
+ onClick: () => void handleTotpStart(),
114
+ children: messages.addTotp
115
+ }), client.isPasskeySupported() && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.Button, {
116
+ variant: "outline",
117
+ disabled: loading,
118
+ onClick: () => void handlePasskey(),
119
+ children: messages.addPasskey
120
+ })]
121
+ })
122
+ ]
123
+ });
124
+ };
125
+ const loadingSnapshot = {
126
+ status: "loading",
127
+ user: null
128
+ };
129
+ const AuthContext = (0, react.createContext)(null);
130
+ const AuthProvider = ({ children, client: suppliedClient, options, initialSnapshot = loadingSnapshot, locale: localeInput, messages: messageOverrides, onAuthStateChange }) => {
131
+ const [createdClient] = (0, react.useState)(() => require_auth_client_index.createAuthClient(options));
132
+ const client = suppliedClient ?? createdClient;
133
+ const locale = require_messages.getAuthLocale(localeInput);
134
+ const resolvedMessages = (0, react.useMemo)(() => require_messages.resolveAuthMessages(locale, void 0, messageOverrides), [locale, messageOverrides]);
135
+ const [snapshot, setSnapshot] = (0, react.useState)(initialSnapshot);
136
+ const [error, setError] = (0, react.useState)(null);
137
+ const publish = (0, react.useCallback)((next) => {
138
+ setSnapshot(next);
139
+ setError(null);
140
+ onAuthStateChange?.(next);
141
+ }, [onAuthStateChange]);
142
+ const refresh = (0, react.useCallback)(async () => {
143
+ try {
144
+ const next = await client.getSnapshot();
145
+ publish(next);
146
+ return next;
147
+ } catch (cause) {
148
+ const nextError = cause instanceof Error ? cause : new Error(resolvedMessages.unexpectedError);
149
+ setError(nextError);
150
+ throw nextError;
151
+ }
152
+ }, [
153
+ client,
154
+ publish,
155
+ resolvedMessages.unexpectedError
156
+ ]);
157
+ (0, react.useEffect)(() => {
158
+ let active = true;
159
+ client.getSnapshot().then((next) => {
160
+ if (active) publish(next);
161
+ }, (cause) => {
162
+ if (active) setError(cause instanceof Error ? cause : new Error(resolvedMessages.unexpectedError));
163
+ });
164
+ return () => {
165
+ active = false;
166
+ };
167
+ }, [
168
+ client,
169
+ publish,
170
+ resolvedMessages.unexpectedError
171
+ ]);
172
+ const signIn = (0, react.useCallback)(async (input) => {
173
+ const next = await client.signIn(input);
174
+ publish(next);
175
+ return next;
176
+ }, [client, publish]);
177
+ const signUp = (0, react.useCallback)(async (input) => {
178
+ const next = await client.register(input);
179
+ publish(next);
180
+ return next;
181
+ }, [client, publish]);
182
+ const signOut = (0, react.useCallback)(async () => {
183
+ await client.signOut();
184
+ publish({
185
+ status: "unauthenticated",
186
+ user: null
187
+ });
188
+ }, [client, publish]);
189
+ const verifyMfaTotp = (0, react.useCallback)(async (code) => {
190
+ const next = await client.verifyMfaTotp(code);
191
+ publish(next);
192
+ return next;
193
+ }, [client, publish]);
194
+ const verifyMfaRecoveryCode = (0, react.useCallback)(async (code) => {
195
+ const next = await client.verifyMfaRecoveryCode(code);
196
+ publish(next);
197
+ return next;
198
+ }, [client, publish]);
199
+ const signInWithPasskey = (0, react.useCallback)(async () => {
200
+ const next = await client.signInWithPasskey();
201
+ publish(next);
202
+ return next;
203
+ }, [client, publish]);
204
+ const verifyMfaWithPasskey = (0, react.useCallback)(async () => {
205
+ const next = await client.verifyMfaWithPasskey();
206
+ publish(next);
207
+ return next;
208
+ }, [client, publish]);
209
+ const requestEmailVerification = (0, react.useCallback)(() => client.requestEmailVerification(), [client]);
210
+ const verifyEmail = (0, react.useCallback)(async (token) => {
211
+ const result = await client.verifyEmail(token);
212
+ publish(await client.getSnapshot());
213
+ return result;
214
+ }, [client, publish]);
215
+ const requestPasswordReset = (0, react.useCallback)((email) => client.requestPasswordReset(email), [client]);
216
+ const resetPassword = (0, react.useCallback)((token, newPassword) => client.resetPassword(token, newPassword), [client]);
217
+ const changePassword = (0, react.useCallback)(async (currentPassword, newPassword) => {
218
+ const result = await client.changePassword(currentPassword, newPassword);
219
+ publish(await client.getSnapshot());
220
+ return result;
221
+ }, [client, publish]);
222
+ const value = (0, react.useMemo)(() => ({
223
+ client,
224
+ snapshot,
225
+ messages: resolvedMessages,
226
+ locale,
227
+ error,
228
+ isLoading: snapshot.status === "loading",
229
+ isAuthenticated: snapshot.status === "authenticated",
230
+ isPendingMfa: snapshot.status === "mfa-pending" || snapshot.status === "step-up-pending",
231
+ refresh,
232
+ signIn,
233
+ signUp,
234
+ signOut,
235
+ verifyMfaTotp,
236
+ verifyMfaRecoveryCode,
237
+ signInWithPasskey,
238
+ verifyMfaWithPasskey,
239
+ requestEmailVerification,
240
+ verifyEmail,
241
+ requestPasswordReset,
242
+ resetPassword,
243
+ changePassword
244
+ }), [
245
+ client,
246
+ snapshot,
247
+ resolvedMessages,
248
+ locale,
249
+ error,
250
+ refresh,
251
+ signIn,
252
+ signUp,
253
+ signOut,
254
+ verifyMfaTotp,
255
+ verifyMfaRecoveryCode,
256
+ signInWithPasskey,
257
+ verifyMfaWithPasskey
258
+ ]);
259
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AuthContext.Provider, {
260
+ value,
261
+ children
262
+ });
263
+ };
264
+ const useAuth = () => {
265
+ const context = (0, react.useContext)(AuthContext);
266
+ if (!context) throw new Error("useAuth must be used inside an AuthProvider.");
267
+ return context;
268
+ };
269
+ const useSession = () => {
270
+ const { snapshot, isLoading, isAuthenticated } = useAuth();
271
+ return {
272
+ status: snapshot.status,
273
+ mfa: snapshot.mfa,
274
+ isLoading,
275
+ isAuthenticated
276
+ };
277
+ };
278
+ const useUser = () => useAuth().snapshot.user;
279
+ const getErrorMessage = (error, messages) => {
280
+ if (typeof error === "object" && error !== null && "code" in error) {
281
+ if (error.code === "INVALID_CREDENTIALS") return messages.invalidCredentials;
282
+ }
283
+ return error instanceof Error ? error.message : messages.unexpectedError;
284
+ };
285
+ const redirectAfterSuccess = (redirectTo) => {
286
+ if (!redirectTo || typeof window === "undefined" || !redirectTo.startsWith("/") || redirectTo.startsWith("//")) return;
287
+ window.location.assign(redirectTo);
288
+ };
289
+ const SignInForm = ({ className, redirectTo, onSuccess, onForgotPassword, onSignUp }) => {
290
+ const { signIn, signInWithPasskey, messages, client } = useAuth();
291
+ const [email, setEmail] = (0, react.useState)("");
292
+ const [password, setPassword] = (0, react.useState)("");
293
+ const [error, setError] = (0, react.useState)(null);
294
+ const [loading, setLoading] = (0, react.useState)(false);
295
+ const handleSubmit = async (event) => {
296
+ event.preventDefault();
297
+ setError(null);
298
+ setLoading(true);
299
+ try {
300
+ if ((await signIn({
301
+ email,
302
+ password
303
+ })).status === "authenticated") {
304
+ onSuccess?.();
305
+ redirectAfterSuccess(redirectTo);
306
+ }
307
+ } catch (cause) {
308
+ setError(getErrorMessage(cause, messages));
309
+ } finally {
310
+ setLoading(false);
311
+ }
312
+ };
313
+ const handlePasskey = async () => {
314
+ setError(null);
315
+ setLoading(true);
316
+ try {
317
+ if ((await signInWithPasskey()).status === "authenticated") {
318
+ onSuccess?.();
319
+ redirectAfterSuccess(redirectTo);
320
+ }
321
+ } catch (cause) {
322
+ setError(getErrorMessage(cause, messages));
323
+ } finally {
324
+ setLoading(false);
325
+ }
326
+ };
327
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
328
+ className: `puzzle-auth ${className ?? ""}`,
329
+ children: [
330
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("header", {
331
+ className: "puzzle-auth__header",
332
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", { children: messages.signIn })
333
+ }),
334
+ error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.Alert, {
335
+ severity: "error",
336
+ children: error
337
+ }),
338
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
339
+ className: "puzzle-auth__form",
340
+ onSubmit: handleSubmit,
341
+ noValidate: true,
342
+ children: [
343
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.FormField, {
344
+ label: messages.email,
345
+ required: true,
346
+ controlId: "puzzle-auth-email",
347
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.TextInput, {
348
+ type: "email",
349
+ name: "email",
350
+ autoComplete: "email",
351
+ value: email,
352
+ onChange: (event) => setEmail(event.currentTarget.value),
353
+ required: true,
354
+ disabled: loading
355
+ })
356
+ }),
357
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.FormField, {
358
+ label: messages.password,
359
+ required: true,
360
+ controlId: "puzzle-auth-password",
361
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.TextInput, {
362
+ type: "password",
363
+ name: "password",
364
+ autoComplete: "current-password",
365
+ value: password,
366
+ onChange: (event) => setPassword(event.currentTarget.value),
367
+ required: true,
368
+ disabled: loading
369
+ })
370
+ }),
371
+ onForgotPassword && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
372
+ className: "puzzle-auth__text-button",
373
+ type: "button",
374
+ onClick: onForgotPassword,
375
+ children: messages.forgotPassword
376
+ }),
377
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.Button, {
378
+ type: "submit",
379
+ fullWidth: true,
380
+ loading,
381
+ disabled: !email.trim() || !password,
382
+ children: loading ? messages.signingIn : messages.submit
383
+ })
384
+ ]
385
+ }),
386
+ client.isPasskeySupported() && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.Button, {
387
+ variant: "outline",
388
+ fullWidth: true,
389
+ disabled: loading,
390
+ onClick: handlePasskey,
391
+ children: messages.usePasskey
392
+ }),
393
+ onSignUp && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
394
+ className: "puzzle-auth__footer",
395
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: messages.noAccount }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
396
+ type: "button",
397
+ className: "puzzle-auth__text-button",
398
+ onClick: onSignUp,
399
+ children: messages.createAccount
400
+ })]
401
+ })
402
+ ]
403
+ });
404
+ };
405
+ const SignUpForm = ({ className, redirectTo, onSuccess, onSignIn }) => {
406
+ const { signUp, messages } = useAuth();
407
+ const [name, setName] = (0, react.useState)("");
408
+ const [email, setEmail] = (0, react.useState)("");
409
+ const [password, setPassword] = (0, react.useState)("");
410
+ const [confirmation, setConfirmation] = (0, react.useState)("");
411
+ const [error, setError] = (0, react.useState)(null);
412
+ const [loading, setLoading] = (0, react.useState)(false);
413
+ const handleSubmit = async (event) => {
414
+ event.preventDefault();
415
+ setError(null);
416
+ if (password !== confirmation) {
417
+ setError(messages.passwordMismatch);
418
+ return;
419
+ }
420
+ if (!password) {
421
+ setError(messages.passwordRequired);
422
+ return;
423
+ }
424
+ setLoading(true);
425
+ try {
426
+ if ((await signUp({
427
+ name: name.trim() || null,
428
+ email,
429
+ password
430
+ })).status === "authenticated") {
431
+ onSuccess?.();
432
+ redirectAfterSuccess(redirectTo);
433
+ }
434
+ } catch (cause) {
435
+ setError(getErrorMessage(cause, messages));
436
+ } finally {
437
+ setLoading(false);
438
+ }
439
+ };
440
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
441
+ className: `puzzle-auth ${className ?? ""}`,
442
+ children: [
443
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("header", {
444
+ className: "puzzle-auth__header",
445
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", { children: messages.signUp })
446
+ }),
447
+ error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.Alert, {
448
+ severity: "error",
449
+ children: error
450
+ }),
451
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
452
+ className: "puzzle-auth__form",
453
+ onSubmit: handleSubmit,
454
+ noValidate: true,
455
+ children: [
456
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.FormField, {
457
+ label: messages.name,
458
+ controlId: "puzzle-auth-name",
459
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.TextInput, {
460
+ name: "name",
461
+ autoComplete: "name",
462
+ value: name,
463
+ onChange: (event) => setName(event.currentTarget.value),
464
+ disabled: loading
465
+ })
466
+ }),
467
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.FormField, {
468
+ label: messages.email,
469
+ required: true,
470
+ controlId: "puzzle-auth-signup-email",
471
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.TextInput, {
472
+ type: "email",
473
+ name: "email",
474
+ autoComplete: "email",
475
+ value: email,
476
+ onChange: (event) => setEmail(event.currentTarget.value),
477
+ required: true,
478
+ disabled: loading
479
+ })
480
+ }),
481
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.FormField, {
482
+ label: messages.password,
483
+ required: true,
484
+ controlId: "puzzle-auth-signup-password",
485
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.TextInput, {
486
+ type: "password",
487
+ name: "new-password",
488
+ autoComplete: "new-password",
489
+ value: password,
490
+ onChange: (event) => setPassword(event.currentTarget.value),
491
+ required: true,
492
+ disabled: loading
493
+ })
494
+ }),
495
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.FormField, {
496
+ label: messages.confirmPassword,
497
+ required: true,
498
+ controlId: "puzzle-auth-confirm-password",
499
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.TextInput, {
500
+ type: "password",
501
+ name: "confirm-password",
502
+ autoComplete: "new-password",
503
+ value: confirmation,
504
+ onChange: (event) => setConfirmation(event.currentTarget.value),
505
+ required: true,
506
+ disabled: loading
507
+ })
508
+ }),
509
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.Button, {
510
+ type: "submit",
511
+ fullWidth: true,
512
+ loading,
513
+ disabled: !email.trim() || !password || !confirmation,
514
+ children: loading ? messages.signingUp : messages.createAccount
515
+ })
516
+ ]
517
+ }),
518
+ onSignIn && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
519
+ className: "puzzle-auth__footer",
520
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: messages.existingAccount }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
521
+ type: "button",
522
+ className: "puzzle-auth__text-button",
523
+ onClick: onSignIn,
524
+ children: messages.signIn
525
+ })]
526
+ })
527
+ ]
528
+ });
529
+ };
530
+ const MfaChallengeForm = ({ className, redirectTo, onSuccess }) => {
531
+ const { snapshot, verifyMfaTotp, verifyMfaRecoveryCode, verifyMfaWithPasskey, client, messages } = useAuth();
532
+ const [code, setCode] = (0, react.useState)("");
533
+ const [recoveryMode, setRecoveryMode] = (0, react.useState)(false);
534
+ const [error, setError] = (0, react.useState)(null);
535
+ const [loading, setLoading] = (0, react.useState)(false);
536
+ const finish = (result) => {
537
+ if (result.status === "authenticated") {
538
+ onSuccess?.();
539
+ redirectAfterSuccess(redirectTo);
540
+ }
541
+ };
542
+ const handleSubmit = async (event) => {
543
+ event.preventDefault();
544
+ setError(null);
545
+ setLoading(true);
546
+ try {
547
+ const result = recoveryMode ? await verifyMfaRecoveryCode(code) : await verifyMfaTotp(code);
548
+ finish(result);
549
+ } catch (cause) {
550
+ setError(getErrorMessage(cause, messages));
551
+ } finally {
552
+ setLoading(false);
553
+ }
554
+ };
555
+ const handlePasskey = async () => {
556
+ setError(null);
557
+ setLoading(true);
558
+ try {
559
+ finish(await verifyMfaWithPasskey());
560
+ } catch (cause) {
561
+ setError(getErrorMessage(cause, messages));
562
+ } finally {
563
+ setLoading(false);
564
+ }
565
+ };
566
+ if (snapshot.status !== "mfa-pending" && snapshot.status !== "step-up-pending") return null;
567
+ const availableFactors = snapshot.mfa?.availableFactors;
568
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
569
+ className: `puzzle-auth ${className ?? ""}`,
570
+ children: [
571
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
572
+ className: "puzzle-auth__header",
573
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", { children: messages.mfaTitle }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: messages.mfaDescription })]
574
+ }),
575
+ error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.Alert, {
576
+ severity: "error",
577
+ children: error
578
+ }),
579
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
580
+ className: "puzzle-auth__form",
581
+ onSubmit: handleSubmit,
582
+ noValidate: true,
583
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.FormField, {
584
+ label: recoveryMode ? messages.recoveryCode : messages.verificationCode,
585
+ required: true,
586
+ controlId: "puzzle-auth-mfa-code",
587
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.TextInput, {
588
+ name: "code",
589
+ type: "text",
590
+ autoComplete: "one-time-code",
591
+ value: code,
592
+ onChange: (event) => setCode(event.currentTarget.value),
593
+ required: true,
594
+ disabled: loading
595
+ })
596
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.Button, {
597
+ type: "submit",
598
+ fullWidth: true,
599
+ loading,
600
+ disabled: !code.trim(),
601
+ children: loading ? messages.verifying : messages.verify
602
+ })]
603
+ }),
604
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
605
+ className: "puzzle-auth__actions",
606
+ children: [availableFactors?.includes("recovery-code") && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
607
+ type: "button",
608
+ className: "puzzle-auth__text-button",
609
+ onClick: () => {
610
+ setRecoveryMode((current) => !current);
611
+ setCode("");
612
+ setError(null);
613
+ },
614
+ children: recoveryMode ? messages.useAuthenticator : messages.useRecoveryCode
615
+ }), availableFactors?.includes("passkey") && client.isPasskeySupported() && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.Button, {
616
+ variant: "outline",
617
+ fullWidth: true,
618
+ disabled: loading,
619
+ onClick: handlePasskey,
620
+ children: messages.usePasskey
621
+ })]
622
+ })
623
+ ]
624
+ });
625
+ };
626
+ const AuthScreen = ({ mode = "sign-in", className, redirectTo, onSuccess, onForgotPassword, children }) => {
627
+ const { snapshot, messages, signOut } = useAuth();
628
+ if (snapshot.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
629
+ className: `puzzle-auth ${className ?? ""}`,
630
+ role: "status",
631
+ "aria-live": "polite",
632
+ children: messages.loading
633
+ });
634
+ if (snapshot.status === "mfa-pending" || snapshot.status === "step-up-pending") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MfaChallengeForm, {
635
+ className,
636
+ redirectTo,
637
+ onSuccess
638
+ });
639
+ if (snapshot.status === "authenticated") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
640
+ className: `puzzle-auth ${className ?? ""}`,
641
+ children: [children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: messages.authenticated }), snapshot.user && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: snapshot.user.email })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.Button, {
642
+ variant: "outline",
643
+ onClick: () => void signOut(),
644
+ children: messages.signOut
645
+ })]
646
+ });
647
+ if (snapshot.status === "mfa-enrollment-required" || snapshot.status === "mfa-enrollment-pending") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RequiredMfaEnrollment, { className });
648
+ return mode === "sign-up" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SignUpForm, {
649
+ className,
650
+ redirectTo,
651
+ onSuccess
652
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SignInForm, {
653
+ className,
654
+ redirectTo,
655
+ onSuccess,
656
+ onForgotPassword
657
+ });
658
+ };
659
+ const MfaSettings = ({ className }) => {
660
+ const { client, messages } = useAuth();
661
+ const [factors, setFactors] = (0, react.useState)(null);
662
+ const [enrollment, setEnrollment] = (0, react.useState)(null);
663
+ const [recoveryCodes, setRecoveryCodes] = (0, react.useState)([]);
664
+ const [password, setPassword] = (0, react.useState)("");
665
+ const [code, setCode] = (0, react.useState)("");
666
+ const [error, setError] = (0, react.useState)(null);
667
+ const [loading, setLoading] = (0, react.useState)(false);
668
+ const loadFactors = (0, react.useCallback)(async () => {
669
+ setFactors(await client.listMfaFactors());
670
+ }, [client]);
671
+ (0, react.useEffect)(() => {
672
+ loadFactors().catch((cause) => {
673
+ setError(cause instanceof Error ? cause.message : messages.unexpectedError);
674
+ });
675
+ }, [loadFactors, messages.unexpectedError]);
676
+ const beginTotp = async () => {
677
+ setError(null);
678
+ setLoading(true);
679
+ try {
680
+ setEnrollment(await client.beginTotpEnrollment(password));
681
+ setPassword("");
682
+ } catch (cause) {
683
+ setError(getErrorMessage(cause, messages));
684
+ } finally {
685
+ setLoading(false);
686
+ }
687
+ };
688
+ const finishTotp = async (event) => {
689
+ event.preventDefault();
690
+ if (!enrollment) return;
691
+ setError(null);
692
+ setLoading(true);
693
+ try {
694
+ const result = await client.verifyTotpEnrollment(enrollment.factorId, code);
695
+ setRecoveryCodes(result.recoveryCodes);
696
+ setEnrollment(null);
697
+ setCode("");
698
+ await loadFactors();
699
+ } catch (cause) {
700
+ setError(getErrorMessage(cause, messages));
701
+ } finally {
702
+ setLoading(false);
703
+ }
704
+ };
705
+ const addPasskey = async () => {
706
+ setError(null);
707
+ setLoading(true);
708
+ try {
709
+ const result = await client.registerPasskey(password);
710
+ setRecoveryCodes(result.recoveryCodes);
711
+ setPassword("");
712
+ await loadFactors();
713
+ } catch (cause) {
714
+ setError(getErrorMessage(cause, messages));
715
+ } finally {
716
+ setLoading(false);
717
+ }
718
+ };
719
+ const removeFactor = async (factorId) => {
720
+ setError(null);
721
+ setLoading(true);
722
+ try {
723
+ await client.removeMfaFactor(factorId, password);
724
+ setPassword("");
725
+ await loadFactors();
726
+ } catch (cause) {
727
+ setError(getErrorMessage(cause, messages));
728
+ } finally {
729
+ setLoading(false);
730
+ }
731
+ };
732
+ const regenerateCodes = async () => {
733
+ setError(null);
734
+ setLoading(true);
735
+ try {
736
+ const result = await client.regenerateRecoveryCodes(password);
737
+ setRecoveryCodes(result.recoveryCodes);
738
+ setPassword("");
739
+ await loadFactors();
740
+ } catch (cause) {
741
+ setError(getErrorMessage(cause, messages));
742
+ } finally {
743
+ setLoading(false);
744
+ }
745
+ };
746
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
747
+ className: `puzzle-auth puzzle-auth--settings ${className ?? ""}`,
748
+ children: [
749
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", { children: messages.mfaSettings }),
750
+ error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.Alert, {
751
+ severity: "error",
752
+ children: error
753
+ }),
754
+ factors && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
755
+ className: "puzzle-auth__factor-list",
756
+ children: factors.factors.map((factor) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: factor.label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: factor.type })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.Button, {
757
+ variant: "ghost",
758
+ size: "sm",
759
+ disabled: loading,
760
+ onClick: () => void removeFactor(factor.id),
761
+ children: messages.removeFactor
762
+ })] }, factor.id))
763
+ }),
764
+ !enrollment && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.FormField, {
765
+ label: messages.currentPassword,
766
+ controlId: "puzzle-auth-settings-password",
767
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.TextInput, {
768
+ type: "password",
769
+ autoComplete: "current-password",
770
+ value: password,
771
+ onChange: (event) => setPassword(event.currentTarget.value),
772
+ disabled: loading
773
+ })
774
+ }),
775
+ enrollment ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
776
+ className: "puzzle-auth__form",
777
+ onSubmit: finishTotp,
778
+ children: [
779
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.FormField, {
780
+ label: messages.totpSecret,
781
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.TextInput, {
782
+ readOnly: true,
783
+ value: enrollment.secret,
784
+ "aria-label": messages.totpSecret
785
+ })
786
+ }),
787
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
788
+ href: enrollment.otpauthUrl,
789
+ children: "Open authenticator application"
790
+ }) }),
791
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.FormField, {
792
+ label: messages.verificationCode,
793
+ controlId: "puzzle-auth-enrollment-code",
794
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.TextInput, {
795
+ value: code,
796
+ autoComplete: "one-time-code",
797
+ onChange: (event) => setCode(event.currentTarget.value),
798
+ required: true
799
+ })
800
+ }),
801
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.Button, {
802
+ type: "submit",
803
+ loading,
804
+ disabled: !code.trim(),
805
+ children: messages.verifyTotp
806
+ }),
807
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.Button, {
808
+ variant: "ghost",
809
+ disabled: loading,
810
+ onClick: () => setEnrollment(null),
811
+ children: messages.cancel
812
+ })
813
+ ]
814
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
815
+ className: "puzzle-auth__actions",
816
+ children: [
817
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.Button, {
818
+ variant: "outline",
819
+ disabled: loading || !password,
820
+ onClick: () => void beginTotp(),
821
+ children: messages.addTotp
822
+ }),
823
+ client.isPasskeySupported() && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.Button, {
824
+ variant: "outline",
825
+ disabled: loading || !password,
826
+ onClick: () => void addPasskey(),
827
+ children: messages.addPasskey
828
+ }),
829
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.Button, {
830
+ variant: "ghost",
831
+ disabled: loading || !password,
832
+ onClick: () => void regenerateCodes(),
833
+ children: messages.recoveryCodes
834
+ })
835
+ ]
836
+ }),
837
+ recoveryCodes.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
838
+ className: "puzzle-auth__recovery-codes",
839
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_puzzle_ui_ui.Alert, {
840
+ severity: "warning",
841
+ title: messages.recoveryCodes,
842
+ children: messages.saveRecoveryCodes
843
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { children: recoveryCodes.map((recoveryCode) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: recoveryCode }) }, recoveryCode)) })]
844
+ })
845
+ ]
846
+ });
847
+ };
848
+ exports.AuthProvider = AuthProvider;
849
+ exports.AuthScreen = AuthScreen;
850
+ exports.MfaChallengeForm = MfaChallengeForm;
851
+ exports.MfaSettings = MfaSettings;
852
+ exports.RequiredMfaEnrollment = RequiredMfaEnrollment;
853
+ exports.SignInForm = SignInForm;
854
+ exports.SignUpForm = SignUpForm;
855
+ exports.useAuth = useAuth;
856
+ exports.useSession = useSession;
857
+ exports.useUser = useUser;
858
+
859
+ //# sourceMappingURL=react.cjs.map