@optare/react 0.1.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,1510 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AccountButton: () => AccountButton,
24
+ DEFAULT_AUTH_METHODS: () => import_client2.DEFAULT_AUTH_METHODS,
25
+ DEFAULT_OPTARE_ORIGIN: () => import_client2.DEFAULT_OPTARE_ORIGIN,
26
+ DEFAULT_PRIMARY: () => DEFAULT_PRIMARY,
27
+ DEFAULT_RADIUS: () => DEFAULT_RADIUS,
28
+ ForgotPassword: () => ForgotPassword,
29
+ OptareConfigError: () => import_client2.OptareConfigError,
30
+ OptareProvider: () => OptareProvider,
31
+ ResetPassword: () => ResetPassword,
32
+ SignIn: () => SignIn,
33
+ SignUp: () => SignUp,
34
+ SocialButtons: () => SocialButtons,
35
+ TwoFactorChallenge: () => TwoFactorChallenge,
36
+ brandingToCssVars: () => brandingToCssVars,
37
+ checkPassword: () => checkPassword,
38
+ contrastText: () => contrastText,
39
+ createReactAuthClient: () => createReactAuthClient,
40
+ isValidEmail: () => isValidEmail,
41
+ notifySessionChanged: () => notifySessionChanged,
42
+ resolveOptareConfig: () => import_client2.resolveOptareConfig,
43
+ safeColor: () => safeColor,
44
+ safeLength: () => safeLength,
45
+ useActiveOrganization: () => useActiveOrganization,
46
+ useAuthMethods: () => useAuthMethods,
47
+ useBranding: () => useBranding,
48
+ useOptare: () => useOptare,
49
+ useOptareAuth: () => useOptareAuth,
50
+ useOptareConfig: () => useOptareConfig,
51
+ useSession: () => useSession,
52
+ useSignOut: () => useSignOut,
53
+ useUser: () => useUser,
54
+ validateSignIn: () => validateSignIn,
55
+ validateSignUp: () => validateSignUp
56
+ });
57
+ module.exports = __toCommonJS(index_exports);
58
+
59
+ // src/context.tsx
60
+ var import_react2 = require("react");
61
+ var import_client = require("@optare/client");
62
+
63
+ // src/auth-client.ts
64
+ var import_react = require("better-auth/react");
65
+ var import_plugins = require("better-auth/client/plugins");
66
+ function optarePlugins() {
67
+ return [
68
+ (0, import_plugins.organizationClient)(),
69
+ (0, import_plugins.twoFactorClient)(),
70
+ (0, import_plugins.emailOTPClient)(),
71
+ (0, import_plugins.magicLinkClient)(),
72
+ (0, import_plugins.multiSessionClient)(),
73
+ (0, import_plugins.jwtClient)()
74
+ ];
75
+ }
76
+ function createReactAuthClient(options) {
77
+ return (0, import_react.createAuthClient)({
78
+ baseURL: options.bootstrap.baseURL,
79
+ fetchOptions: {
80
+ ...options.fetch ? { customFetchImpl: options.fetch } : {},
81
+ headers: {
82
+ "x-optare-publishable-key": options.publishableKey,
83
+ ...options.headers
84
+ }
85
+ },
86
+ plugins: [...optarePlugins()]
87
+ });
88
+ }
89
+
90
+ // src/theme.ts
91
+ var DEFAULT_PRIMARY = "#4f46e5";
92
+ var DEFAULT_RADIUS = "10px";
93
+ var CSS_COLOR = /^#(?:[0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
94
+ var CSS_LENGTH = /^-?\d+(?:\.\d+)?(?:px|rem|em|%)$/;
95
+ function safeColor(value, fallback) {
96
+ return value && CSS_COLOR.test(value.trim()) ? value.trim() : fallback;
97
+ }
98
+ function safeLength(value, fallback) {
99
+ const v = (value ?? "").trim();
100
+ if (CSS_LENGTH.test(v)) return v;
101
+ if (/^\d+$/.test(v)) return `${v}px`;
102
+ return fallback;
103
+ }
104
+ function normalizeHex(hex) {
105
+ const m = /^#?([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.exec(hex.trim());
106
+ if (!m) return null;
107
+ let h = m[1];
108
+ if (h.length === 3) h = h.split("").map((c) => c + c).join("");
109
+ return `#${h.slice(0, 6)}`;
110
+ }
111
+ function relativeLuminance(hex) {
112
+ const n = normalizeHex(hex);
113
+ if (!n) return 0;
114
+ const int = parseInt(n.slice(1), 16);
115
+ const chan = [int >> 16 & 255, int >> 8 & 255, int & 255].map((v) => {
116
+ const s2 = v / 255;
117
+ return s2 <= 0.03928 ? s2 / 12.92 : ((s2 + 0.055) / 1.055) ** 2.4;
118
+ });
119
+ return 0.2126 * chan[0] + 0.7152 * chan[1] + 0.0722 * chan[2];
120
+ }
121
+ function contrastText(hex) {
122
+ const bg = relativeLuminance(hex);
123
+ const whiteTextRatio = (1 + 0.05) / (bg + 0.05);
124
+ const darkTextRatio = (bg + 0.05) / (relativeLuminance("#111827") + 0.05);
125
+ return darkTextRatio >= whiteTextRatio ? "#111827" : "#ffffff";
126
+ }
127
+ function brandingToCssVars(branding) {
128
+ const primary = safeColor(branding?.primaryColor, DEFAULT_PRIMARY);
129
+ const radius = safeLength(branding?.radius, DEFAULT_RADIUS);
130
+ return {
131
+ "--optare-primary": primary,
132
+ "--optare-primary-text": contrastText(primary),
133
+ // A translucent wash of the primary for hover/focus rings — layered over
134
+ // white it reads as a tint without needing a second colour from branding.
135
+ "--optare-primary-soft": "color-mix(in srgb, var(--optare-primary) 12%, #ffffff)",
136
+ "--optare-ring": "color-mix(in srgb, var(--optare-primary) 35%, transparent)",
137
+ "--optare-radius": radius,
138
+ "--optare-bg": "#ffffff",
139
+ "--optare-bg-subtle": "#f9fafb",
140
+ "--optare-fg": "#111827",
141
+ "--optare-fg-muted": "#4b5563",
142
+ "--optare-muted": "#6b7280",
143
+ "--optare-border": "#e5e7eb",
144
+ "--optare-border-strong": "#d1d5db",
145
+ "--optare-error": "#dc2626",
146
+ "--optare-error-soft": "#fef2f2",
147
+ "--optare-success": "#059669",
148
+ "--optare-shadow": "0 1px 2px rgba(16,24,40,0.04), 0 8px 24px rgba(16,24,40,0.08)",
149
+ "--optare-font": 'system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif'
150
+ };
151
+ }
152
+
153
+ // src/context.tsx
154
+ var OptareContext = (0, import_react2.createContext)(null);
155
+ function OptareProvider(props) {
156
+ const {
157
+ publishableKey,
158
+ bootstrap: bootstrapProp,
159
+ baseURL,
160
+ configURL,
161
+ fetch: fetchImpl,
162
+ headers,
163
+ loadingFallback,
164
+ errorFallback,
165
+ children
166
+ } = props;
167
+ const [bootstrap, setBootstrap] = (0, import_react2.useState)(
168
+ bootstrapProp ?? null
169
+ );
170
+ const [status, setStatus] = (0, import_react2.useState)(
171
+ bootstrapProp ? "ready" : "loading"
172
+ );
173
+ const [error, setError] = (0, import_react2.useState)(null);
174
+ const [attempt, setAttempt] = (0, import_react2.useState)(0);
175
+ (0, import_react2.useEffect)(() => {
176
+ if (bootstrapProp) {
177
+ setBootstrap(bootstrapProp);
178
+ setStatus("ready");
179
+ setError(null);
180
+ return;
181
+ }
182
+ let cancelled = false;
183
+ setStatus("loading");
184
+ setError(null);
185
+ (0, import_client.resolveOptareConfig)({ publishableKey, baseURL, configURL, fetch: fetchImpl }).then((resolved) => {
186
+ if (cancelled) return;
187
+ setBootstrap(resolved);
188
+ setStatus("ready");
189
+ }).catch((err) => {
190
+ if (cancelled) return;
191
+ setError(
192
+ err instanceof Error ? err : new import_client.OptareConfigError("Failed to resolve Optare config")
193
+ );
194
+ setStatus("error");
195
+ });
196
+ return () => {
197
+ cancelled = true;
198
+ };
199
+ }, [publishableKey, baseURL, configURL, fetchImpl, bootstrapProp, attempt]);
200
+ const authRef = (0, import_react2.useRef)(
201
+ null
202
+ );
203
+ const auth = (0, import_react2.useMemo)(() => {
204
+ if (!bootstrap) return null;
205
+ const cached = authRef.current;
206
+ if (cached && cached.baseURL === bootstrap.baseURL) return cached.client;
207
+ const client = createReactAuthClient({
208
+ bootstrap,
209
+ publishableKey,
210
+ fetch: fetchImpl,
211
+ headers
212
+ });
213
+ authRef.current = { baseURL: bootstrap.baseURL, client };
214
+ return client;
215
+ }, [bootstrap, publishableKey, fetchImpl, headers]);
216
+ const retry = (0, import_react2.useMemo)(() => () => setAttempt((n) => n + 1), []);
217
+ const value = (0, import_react2.useMemo)(
218
+ () => ({
219
+ status,
220
+ error,
221
+ bootstrap,
222
+ branding: bootstrap?.branding ?? null,
223
+ project: bootstrap?.project ?? null,
224
+ authMethods: bootstrap?.authMethods ?? import_client.DEFAULT_AUTH_METHODS,
225
+ cssVars: brandingToCssVars(bootstrap?.branding ?? null),
226
+ auth,
227
+ publishableKey,
228
+ retry
229
+ }),
230
+ [status, error, bootstrap, auth, publishableKey, retry]
231
+ );
232
+ let body = children;
233
+ if (status === "loading" && loadingFallback !== void 0) body = loadingFallback;
234
+ if (status === "error" && errorFallback !== void 0 && error) {
235
+ body = typeof errorFallback === "function" ? errorFallback(error, retry) : errorFallback;
236
+ }
237
+ return (0, import_react2.createElement)(OptareContext.Provider, { value }, body);
238
+ }
239
+ function useOptare() {
240
+ const ctx = (0, import_react2.useContext)(OptareContext);
241
+ if (!ctx) {
242
+ throw new Error("useOptare must be used inside <OptareProvider>");
243
+ }
244
+ return ctx;
245
+ }
246
+ function useOptareAuth() {
247
+ const { auth, status, error } = useOptare();
248
+ if (!auth) {
249
+ throw new Error(
250
+ status === "error" ? `Optare provider failed to initialise: ${error?.message ?? "unknown error"}` : "Optare provider is still resolving \u2014 gate on `useOptare().status === 'ready'`"
251
+ );
252
+ }
253
+ return auth;
254
+ }
255
+ function useOptareConfig() {
256
+ const { bootstrap, project, branding, status } = useOptare();
257
+ return { bootstrap, project, branding, status };
258
+ }
259
+ function useBranding() {
260
+ return useOptare().branding;
261
+ }
262
+ function useAuthMethods() {
263
+ return useOptare().authMethods;
264
+ }
265
+
266
+ // src/hooks.ts
267
+ var import_react3 = require("react");
268
+
269
+ // src/session.ts
270
+ var listeners = /* @__PURE__ */ new Set();
271
+ function subscribeSessionChanged(listener) {
272
+ listeners.add(listener);
273
+ return () => listeners.delete(listener);
274
+ }
275
+ function notifySessionChanged() {
276
+ for (const l of [...listeners]) l();
277
+ }
278
+
279
+ // src/hooks.ts
280
+ function useSession() {
281
+ const { auth, status } = useOptare();
282
+ const [data, setData] = (0, import_react3.useState)(null);
283
+ const [error, setError] = (0, import_react3.useState)(null);
284
+ const [isPending, setIsPending] = (0, import_react3.useState)(true);
285
+ const [tick, setTick] = (0, import_react3.useState)(0);
286
+ const refetch = (0, import_react3.useCallback)(() => setTick((n) => n + 1), []);
287
+ (0, import_react3.useEffect)(() => subscribeSessionChanged(refetch), [refetch]);
288
+ (0, import_react3.useEffect)(() => {
289
+ if (status === "error") {
290
+ setIsPending(false);
291
+ return;
292
+ }
293
+ if (!auth) return;
294
+ let cancelled = false;
295
+ setIsPending(true);
296
+ Promise.resolve(auth.getSession()).then((res) => {
297
+ if (cancelled) return;
298
+ const r = res;
299
+ setData(r?.data ?? null);
300
+ setError(r?.error ? new Error(r.error.message ?? "Session error") : null);
301
+ }).catch((err) => {
302
+ if (!cancelled) setError(err instanceof Error ? err : new Error(String(err)));
303
+ }).finally(() => {
304
+ if (!cancelled) setIsPending(false);
305
+ });
306
+ return () => {
307
+ cancelled = true;
308
+ };
309
+ }, [auth, status, tick]);
310
+ (0, import_react3.useEffect)(() => {
311
+ if (typeof window === "undefined") return;
312
+ const onFocus = () => refetch();
313
+ window.addEventListener("focus", onFocus);
314
+ return () => window.removeEventListener("focus", onFocus);
315
+ }, [refetch]);
316
+ return (0, import_react3.useMemo)(
317
+ () => ({
318
+ data,
319
+ user: data?.user ?? null,
320
+ isPending,
321
+ isAuthenticated: !!data,
322
+ error,
323
+ refetch
324
+ }),
325
+ [data, isPending, error, refetch]
326
+ );
327
+ }
328
+ function useUser() {
329
+ return useSession().user;
330
+ }
331
+ function useSignOut() {
332
+ const { auth } = useOptare();
333
+ const [isPending, setIsPending] = (0, import_react3.useState)(false);
334
+ const [error, setError] = (0, import_react3.useState)(null);
335
+ const signOut = (0, import_react3.useCallback)(async () => {
336
+ if (!auth) return;
337
+ setIsPending(true);
338
+ setError(null);
339
+ try {
340
+ await auth.signOut();
341
+ notifySessionChanged();
342
+ } catch (err) {
343
+ setError(err instanceof Error ? err : new Error(String(err)));
344
+ throw err;
345
+ } finally {
346
+ setIsPending(false);
347
+ }
348
+ }, [auth]);
349
+ return { signOut, isPending, error };
350
+ }
351
+ function useActiveOrganization() {
352
+ const { auth } = useOptare();
353
+ const { data } = useSession();
354
+ const [isPending, setIsPending] = (0, import_react3.useState)(false);
355
+ const activeOrganizationId = data?.session?.activeOrganizationId ?? null;
356
+ const setActive = (0, import_react3.useCallback)(
357
+ async (organizationId) => {
358
+ if (!auth) return;
359
+ setIsPending(true);
360
+ try {
361
+ await auth.organization.setActive({ organizationId });
362
+ notifySessionChanged();
363
+ } finally {
364
+ setIsPending(false);
365
+ }
366
+ },
367
+ [auth]
368
+ );
369
+ return { activeOrganizationId, setActive, isPending };
370
+ }
371
+
372
+ // src/validation.ts
373
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
374
+ function isValidEmail(value) {
375
+ return EMAIL_RE.test(value.trim());
376
+ }
377
+ function checkPassword(value, min = 8) {
378
+ if (value.length === 0) return { ok: false, score: 0, message: "Password is required" };
379
+ if (value.length < min)
380
+ return { ok: false, score: 1, message: `Use at least ${min} characters` };
381
+ let score = 1;
382
+ if (value.length >= 12) score++;
383
+ if (/[a-z]/.test(value) && /[A-Z]/.test(value)) score++;
384
+ if (/\d/.test(value)) score++;
385
+ if (/[^A-Za-z0-9]/.test(value)) score++;
386
+ return { ok: true, score: Math.min(score, 4), message: null };
387
+ }
388
+ function validateSignIn(fields) {
389
+ const errors = {};
390
+ if (!isValidEmail(fields.email)) errors.email = "Enter a valid email address";
391
+ if (fields.password.length === 0) errors.password = "Password is required";
392
+ return errors;
393
+ }
394
+ function validateSignUp(fields, passwordMin = 8) {
395
+ const errors = {};
396
+ if (fields.name.trim().length === 0) errors.name = "Name is required";
397
+ if (!isValidEmail(fields.email)) errors.email = "Enter a valid email address";
398
+ const pw = checkPassword(fields.password, passwordMin);
399
+ if (!pw.ok && pw.message) errors.password = pw.message;
400
+ return errors;
401
+ }
402
+
403
+ // src/components/SignIn.tsx
404
+ var import_react6 = require("react");
405
+
406
+ // src/ui.tsx
407
+ var import_react4 = require("react");
408
+ var import_jsx_runtime = require("react/jsx-runtime");
409
+ var base = {
410
+ card: {
411
+ boxSizing: "border-box",
412
+ width: "100%",
413
+ maxWidth: 400,
414
+ padding: 28,
415
+ background: "var(--optare-bg)",
416
+ color: "var(--optare-fg)",
417
+ border: "1px solid var(--optare-border)",
418
+ borderRadius: "var(--optare-radius)",
419
+ boxShadow: "var(--optare-shadow)",
420
+ fontFamily: "var(--optare-font)",
421
+ fontSize: 14,
422
+ lineHeight: 1.5,
423
+ WebkitFontSmoothing: "antialiased",
424
+ display: "flex",
425
+ flexDirection: "column",
426
+ gap: 20
427
+ },
428
+ header: {
429
+ display: "flex",
430
+ flexDirection: "column",
431
+ alignItems: "center",
432
+ gap: 6,
433
+ textAlign: "center"
434
+ },
435
+ logo: { height: 32, maxWidth: 160, objectFit: "contain", marginBottom: 2 },
436
+ brandName: {
437
+ fontSize: 13,
438
+ fontWeight: 600,
439
+ letterSpacing: 0.2,
440
+ color: "var(--optare-fg-muted)",
441
+ textTransform: "uppercase"
442
+ },
443
+ title: {
444
+ margin: 0,
445
+ fontSize: 19,
446
+ fontWeight: 650,
447
+ color: "var(--optare-fg)"
448
+ },
449
+ subtitle: {
450
+ margin: 0,
451
+ fontSize: 13.5,
452
+ color: "var(--optare-muted)"
453
+ },
454
+ field: { display: "flex", flexDirection: "column", gap: 6 },
455
+ label: { fontSize: 13, fontWeight: 550, color: "var(--optare-fg)" },
456
+ input: {
457
+ boxSizing: "border-box",
458
+ width: "100%",
459
+ padding: "10px 12px",
460
+ fontSize: 14,
461
+ fontFamily: "inherit",
462
+ color: "var(--optare-fg)",
463
+ background: "var(--optare-bg)",
464
+ border: "1px solid var(--optare-border-strong)",
465
+ borderRadius: "var(--optare-radius)",
466
+ outline: "none",
467
+ transition: "border-color 120ms, box-shadow 120ms"
468
+ },
469
+ fieldError: { fontSize: 12, color: "var(--optare-error)" },
470
+ button: {
471
+ boxSizing: "border-box",
472
+ display: "inline-flex",
473
+ alignItems: "center",
474
+ justifyContent: "center",
475
+ gap: 8,
476
+ width: "100%",
477
+ padding: "10px 16px",
478
+ fontSize: 14,
479
+ fontWeight: 600,
480
+ fontFamily: "inherit",
481
+ lineHeight: 1.2,
482
+ cursor: "pointer",
483
+ color: "var(--optare-primary-text)",
484
+ background: "var(--optare-primary)",
485
+ border: "1px solid transparent",
486
+ borderRadius: "var(--optare-radius)",
487
+ transition: "filter 120ms, opacity 120ms"
488
+ },
489
+ buttonSecondary: {
490
+ color: "var(--optare-fg)",
491
+ background: "var(--optare-bg)",
492
+ border: "1px solid var(--optare-border-strong)"
493
+ },
494
+ buttonDisabled: { opacity: 0.55, cursor: "not-allowed" },
495
+ alert: {
496
+ padding: "9px 12px",
497
+ fontSize: 13,
498
+ borderRadius: "var(--optare-radius)",
499
+ background: "var(--optare-error-soft)",
500
+ color: "var(--optare-error)",
501
+ border: "1px solid color-mix(in srgb, var(--optare-error) 30%, transparent)"
502
+ },
503
+ notice: {
504
+ padding: "9px 12px",
505
+ fontSize: 13,
506
+ borderRadius: "var(--optare-radius)",
507
+ background: "var(--optare-bg-subtle)",
508
+ color: "var(--optare-fg-muted)",
509
+ border: "1px solid var(--optare-border)"
510
+ },
511
+ link: {
512
+ background: "none",
513
+ border: "none",
514
+ padding: 0,
515
+ font: "inherit",
516
+ fontSize: 13,
517
+ color: "var(--optare-primary)",
518
+ cursor: "pointer",
519
+ textDecoration: "none",
520
+ fontWeight: 550
521
+ },
522
+ footer: { fontSize: 13, color: "var(--optare-muted)", textAlign: "center" },
523
+ divider: {
524
+ display: "flex",
525
+ alignItems: "center",
526
+ gap: 10,
527
+ fontSize: 12,
528
+ color: "var(--optare-muted)"
529
+ },
530
+ dividerRule: { flex: 1, height: 1, background: "var(--optare-border)" },
531
+ socialGrid: { display: "grid", gap: 8 },
532
+ spinner: {
533
+ display: "inline-block",
534
+ width: 14,
535
+ height: 14,
536
+ border: "2px solid currentColor",
537
+ borderTopColor: "transparent",
538
+ borderRadius: "50%",
539
+ animation: "optare-spin 0.6s linear infinite"
540
+ }
541
+ };
542
+ function SpinnerKeyframes() {
543
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: "@keyframes optare-spin{to{transform:rotate(360deg)}}" });
544
+ }
545
+ function Spinner() {
546
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
547
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SpinnerKeyframes, {}),
548
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": true, style: base.spinner })
549
+ ] });
550
+ }
551
+ function Card(props) {
552
+ const { cssVars } = useOptare();
553
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
554
+ "div",
555
+ {
556
+ className: props.className,
557
+ style: { ...cssVars, ...base.card, ...props.style },
558
+ children: props.children
559
+ }
560
+ );
561
+ }
562
+ function BrandHeader(props) {
563
+ const { branding } = useOptare();
564
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: base.header, children: [
565
+ branding?.logoUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: branding.logoUrl, alt: branding.name, style: base.logo }) : branding?.name ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: base.brandName, children: branding.name }) : null,
566
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { style: base.title, children: props.title }),
567
+ props.subtitle ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: base.subtitle, children: props.subtitle }) : null
568
+ ] });
569
+ }
570
+ var TextInput = (0, import_react4.forwardRef)(function TextInput2({ label, error, hint, id, onFocus, onBlur, ...rest }, ref) {
571
+ const inputId = id ?? `optare-${label.toLowerCase().replace(/\s+/g, "-")}`;
572
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: base.field, children: [
573
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: inputId, style: base.label, children: label }),
574
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
575
+ "input",
576
+ {
577
+ ref,
578
+ id: inputId,
579
+ style: {
580
+ ...base.input,
581
+ ...error ? { borderColor: "var(--optare-error)" } : null
582
+ },
583
+ "aria-invalid": error ? true : void 0,
584
+ onFocus: (e) => {
585
+ if (!error) {
586
+ e.currentTarget.style.borderColor = "var(--optare-primary)";
587
+ e.currentTarget.style.boxShadow = "0 0 0 3px var(--optare-ring)";
588
+ }
589
+ onFocus?.(e);
590
+ },
591
+ onBlur: (e) => {
592
+ e.currentTarget.style.borderColor = error ? "var(--optare-error)" : "var(--optare-border-strong)";
593
+ e.currentTarget.style.boxShadow = "none";
594
+ onBlur?.(e);
595
+ },
596
+ ...rest
597
+ }
598
+ ),
599
+ error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: base.fieldError, children: error }) : hint ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { ...base.subtitle, fontSize: 12 }, children: hint }) : null
600
+ ] });
601
+ });
602
+ function Button({
603
+ variant = "primary",
604
+ busy = false,
605
+ style,
606
+ disabled,
607
+ children,
608
+ ...rest
609
+ }) {
610
+ const isDisabled = disabled || busy;
611
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
612
+ "button",
613
+ {
614
+ style: {
615
+ ...base.button,
616
+ ...variant === "secondary" ? base.buttonSecondary : null,
617
+ ...isDisabled ? base.buttonDisabled : null,
618
+ ...style
619
+ },
620
+ disabled: isDisabled,
621
+ onMouseEnter: (e) => {
622
+ if (!isDisabled) e.currentTarget.style.filter = "brightness(0.94)";
623
+ },
624
+ onMouseLeave: (e) => {
625
+ e.currentTarget.style.filter = "none";
626
+ },
627
+ ...rest,
628
+ children: [
629
+ busy ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}) : null,
630
+ children
631
+ ]
632
+ }
633
+ );
634
+ }
635
+ function Alert({ children }) {
636
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "alert", style: base.alert, children });
637
+ }
638
+ function Notice({ children }) {
639
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: base.notice, children });
640
+ }
641
+ function LinkButton(props) {
642
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", style: { ...base.link, ...props.style }, ...props });
643
+ }
644
+ function Footer({ children }) {
645
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: base.footer, children });
646
+ }
647
+ function Divider({ children }) {
648
+ if (!children) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: base.dividerRule });
649
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: base.divider, children: [
650
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: base.dividerRule }),
651
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children }),
652
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: base.dividerRule })
653
+ ] });
654
+ }
655
+
656
+ // src/components/SocialButtons.tsx
657
+ var import_react5 = require("react");
658
+ var import_jsx_runtime2 = require("react/jsx-runtime");
659
+ var MARKS = {
660
+ google: {
661
+ name: "Google",
662
+ icon: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 48 48", "aria-hidden": true, children: [
663
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { fill: "#EA4335", d: "M24 9.5c3.5 0 6.6 1.2 9 3.6l6.7-6.7C35.6 2.4 30.2 0 24 0 14.6 0 6.5 5.4 2.6 13.2l7.8 6.1C12.2 13.3 17.6 9.5 24 9.5z" }),
664
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { fill: "#4285F4", d: "M46.5 24.5c0-1.6-.1-3.1-.4-4.5H24v9h12.7c-.6 3-2.3 5.5-4.9 7.2l7.6 5.9c4.4-4.1 7.1-10.1 7.1-17.6z" }),
665
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { fill: "#FBBC05", d: "M10.4 28.3c-.5-1.5-.8-3.1-.8-4.8s.3-3.3.8-4.8l-7.8-6.1C.9 15.9 0 19.8 0 23.5s.9 7.6 2.6 10.9l7.8-6.1z" }),
666
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { fill: "#34A853", d: "M24 47c6.2 0 11.5-2 15.3-5.5l-7.6-5.9c-2.1 1.4-4.8 2.3-7.7 2.3-6.4 0-11.8-3.8-13.6-9.3l-7.8 6.1C6.5 41.6 14.6 47 24 47z" })
667
+ ] })
668
+ },
669
+ github: {
670
+ name: "GitHub",
671
+ icon: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 .5A11.5 11.5 0 0 0 .5 12c0 5.08 3.29 9.39 7.86 10.91.58.1.79-.25.79-.56v-2c-3.2.7-3.88-1.37-3.88-1.37-.53-1.34-1.3-1.7-1.3-1.7-1.06-.72.08-.71.08-.71 1.17.08 1.79 1.2 1.79 1.2 1.04 1.78 2.73 1.27 3.4.97.1-.76.41-1.27.74-1.56-2.55-.29-5.24-1.28-5.24-5.68 0-1.26.45-2.28 1.19-3.09-.12-.29-.52-1.46.11-3.05 0 0 .97-.31 3.18 1.18a11 11 0 0 1 5.8 0c2.2-1.49 3.17-1.18 3.17-1.18.63 1.59.23 2.76.12 3.05.74.81 1.18 1.83 1.18 3.09 0 4.41-2.69 5.38-5.25 5.67.42.36.8 1.08.8 2.18v3.23c0 .31.21.67.8.56A11.5 11.5 0 0 0 23.5 12 11.5 11.5 0 0 0 12 .5z" }) })
672
+ },
673
+ microsoft: {
674
+ name: "Microsoft",
675
+ icon: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 23 23", "aria-hidden": true, children: [
676
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { fill: "#F25022", d: "M1 1h10v10H1z" }),
677
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { fill: "#7FBA00", d: "M12 1h10v10H12z" }),
678
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { fill: "#00A4EF", d: "M1 12h10v10H1z" }),
679
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { fill: "#FFB900", d: "M12 12h10v10H12z" })
680
+ ] })
681
+ },
682
+ facebook: {
683
+ name: "Facebook",
684
+ icon: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "#1877F2", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M24 12.07C24 5.4 18.63 0 12 0S0 5.4 0 12.07C0 18.1 4.39 23.1 10.13 24v-8.44H7.08v-3.49h3.05V9.41c0-3.02 1.79-4.69 4.53-4.69 1.31 0 2.68.24 2.68.24v2.97h-1.51c-1.49 0-1.95.93-1.95 1.89v2.26h3.32l-.53 3.49h-2.79V24C19.61 23.1 24 18.1 24 12.07z" }) })
685
+ },
686
+ twitter: {
687
+ name: "X",
688
+ icon: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M18.9 1.15h3.68l-8.04 9.19L24 22.85h-7.41l-5.8-7.58-6.64 7.58H.46l8.6-9.83L0 1.15h7.6l5.24 6.93 6.06-6.93zm-1.29 19.5h2.04L6.49 3.24H4.3l13.31 17.41z" }) })
689
+ }
690
+ };
691
+ function SocialButtons(props) {
692
+ const { auth, status } = useOptare();
693
+ const { social } = useAuthMethods();
694
+ const [pending, setPending] = (0, import_react5.useState)(null);
695
+ const providers = (props.only ?? social).filter((p) => social.includes(p));
696
+ if (providers.length === 0) return null;
697
+ const verb = props.label ?? "Continue with";
698
+ const nameOnly = providers.length > 1;
699
+ const ready = status === "ready" && !!auth;
700
+ async function go(provider) {
701
+ if (!auth) return;
702
+ setPending(provider);
703
+ try {
704
+ await auth.signIn.social({
705
+ provider,
706
+ callbackURL: props.callbackURL ?? (typeof window !== "undefined" ? window.location.href : void 0),
707
+ errorCallbackURL: props.errorCallbackURL
708
+ });
709
+ } catch {
710
+ setPending(null);
711
+ }
712
+ }
713
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
714
+ props.divider !== false ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Divider, { children: props.dividerLabel ?? "or" }) : null,
715
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
716
+ "div",
717
+ {
718
+ style: {
719
+ display: "grid",
720
+ gap: 8,
721
+ gridTemplateColumns: providers.length > 1 ? "1fr 1fr" : "1fr"
722
+ },
723
+ children: providers.map((p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
724
+ Button,
725
+ {
726
+ type: "button",
727
+ variant: "secondary",
728
+ busy: pending === p,
729
+ disabled: !ready || pending !== null && pending !== p,
730
+ onClick: () => go(p),
731
+ style: { fontWeight: 550 },
732
+ children: [
733
+ pending === p ? null : MARKS[p].icon,
734
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: nameOnly ? MARKS[p].name : `${verb} ${MARKS[p].name}` })
735
+ ]
736
+ },
737
+ p
738
+ ))
739
+ }
740
+ )
741
+ ] });
742
+ }
743
+
744
+ // src/components/SignIn.tsx
745
+ var import_jsx_runtime3 = require("react/jsx-runtime");
746
+ function SignIn(props) {
747
+ const { auth, status } = useOptare();
748
+ const methods = useAuthMethods();
749
+ const allowMagicLink = (props.allowMagicLink ?? methods.magicLink) && methods.magicLink;
750
+ const allowEmailOtp = (props.allowEmailOtp ?? methods.emailOtp) && methods.emailOtp;
751
+ const allowSocial = props.allowSocial ?? true;
752
+ const allowSso = (props.allowSso ?? methods.sso) && methods.sso;
753
+ const callbackURL = props.callbackURL ?? (typeof window !== "undefined" ? window.location.href : void 0);
754
+ const [mode, setMode] = (0, import_react6.useState)("password");
755
+ const [email, setEmail] = (0, import_react6.useState)("");
756
+ const [password, setPassword] = (0, import_react6.useState)("");
757
+ const [otp, setOtp] = (0, import_react6.useState)("");
758
+ const [otpSent, setOtpSent] = (0, import_react6.useState)(false);
759
+ const [fieldErrors, setFieldErrors] = (0, import_react6.useState)({});
760
+ const [formError, setFormError] = (0, import_react6.useState)(null);
761
+ const [busy, setBusy] = (0, import_react6.useState)(false);
762
+ const [magicLinkSent, setMagicLinkSent] = (0, import_react6.useState)(false);
763
+ const ready = status === "ready" && !!auth;
764
+ function resetTransient() {
765
+ setFormError(null);
766
+ setFieldErrors({});
767
+ }
768
+ function switchMode(next) {
769
+ setMode(next);
770
+ setOtp("");
771
+ setOtpSent(false);
772
+ resetTransient();
773
+ }
774
+ async function onSubmit(e) {
775
+ e.preventDefault();
776
+ if (!auth) return;
777
+ setFormError(null);
778
+ if (mode === "sso") {
779
+ const errs2 = isValidEmail(email) ? {} : { email: "Enter your work email address" };
780
+ setFieldErrors(errs2);
781
+ if (Object.keys(errs2).length) return;
782
+ setBusy(true);
783
+ try {
784
+ const res = await auth.signIn.sso({
785
+ email,
786
+ callbackURL
787
+ });
788
+ if (res?.error) {
789
+ setFormError(
790
+ res.error.message ?? "We couldn't find a single sign-on provider for that email domain."
791
+ );
792
+ }
793
+ } catch (err) {
794
+ setFormError(err instanceof Error ? err.message : "SSO sign-in failed");
795
+ } finally {
796
+ setBusy(false);
797
+ }
798
+ return;
799
+ }
800
+ if (mode === "magic-link") {
801
+ const errs2 = isValidEmail(email) ? {} : { email: "Enter a valid email address" };
802
+ setFieldErrors(errs2);
803
+ if (Object.keys(errs2).length) return;
804
+ setBusy(true);
805
+ try {
806
+ const res = await auth.signIn.magicLink({ email, callbackURL });
807
+ if (res.error) {
808
+ setFormError(res.error.message ?? "Could not send the link");
809
+ } else {
810
+ setMagicLinkSent(true);
811
+ }
812
+ } catch (err) {
813
+ setFormError(err instanceof Error ? err.message : "Could not send the link");
814
+ } finally {
815
+ setBusy(false);
816
+ }
817
+ return;
818
+ }
819
+ if (mode === "email-otp") {
820
+ if (!otpSent) {
821
+ const errs3 = isValidEmail(email) ? {} : { email: "Enter a valid email address" };
822
+ setFieldErrors(errs3);
823
+ if (Object.keys(errs3).length) return;
824
+ setBusy(true);
825
+ try {
826
+ const res = await auth.emailOtp.sendVerificationOtp({ email, type: "sign-in" });
827
+ if (res.error) {
828
+ setFormError(res.error.message ?? "Could not send the code");
829
+ } else {
830
+ setOtpSent(true);
831
+ }
832
+ } catch (err) {
833
+ setFormError(err instanceof Error ? err.message : "Could not send the code");
834
+ } finally {
835
+ setBusy(false);
836
+ }
837
+ return;
838
+ }
839
+ const errs2 = otp.trim().length >= 4 ? {} : { otp: "Enter the code from your email" };
840
+ setFieldErrors(errs2);
841
+ if (Object.keys(errs2).length) return;
842
+ setBusy(true);
843
+ try {
844
+ const res = await auth.signIn.emailOtp({ email, otp: otp.trim() });
845
+ if (res.error) {
846
+ setFormError(res.error.message ?? "That code didn't work");
847
+ return;
848
+ }
849
+ if (res.data?.twoFactorRedirect) return props.onTwoFactor?.();
850
+ notifySessionChanged();
851
+ if (props.onSuccess) props.onSuccess();
852
+ else if (props.redirectTo && typeof window !== "undefined")
853
+ window.location.assign(props.redirectTo);
854
+ } catch (err) {
855
+ setFormError(err instanceof Error ? err.message : "Sign-in failed");
856
+ } finally {
857
+ setBusy(false);
858
+ }
859
+ return;
860
+ }
861
+ const errs = validateSignIn({ email, password });
862
+ setFieldErrors(errs);
863
+ if (Object.keys(errs).length) return;
864
+ setBusy(true);
865
+ try {
866
+ const res = await auth.signIn.email({
867
+ email,
868
+ password,
869
+ callbackURL
870
+ });
871
+ if (res.error) {
872
+ setFormError(res.error.message ?? "Incorrect email or password");
873
+ return;
874
+ }
875
+ if (res.data?.twoFactorRedirect) {
876
+ props.onTwoFactor?.();
877
+ if (!props.onTwoFactor) setFormError("Two-factor authentication is required to continue.");
878
+ return;
879
+ }
880
+ notifySessionChanged();
881
+ if (props.onSuccess) props.onSuccess();
882
+ else if (props.redirectTo && typeof window !== "undefined")
883
+ window.location.assign(props.redirectTo);
884
+ } catch (err) {
885
+ setFormError(err instanceof Error ? err.message : "Sign-in failed");
886
+ } finally {
887
+ setBusy(false);
888
+ }
889
+ }
890
+ if (magicLinkSent) {
891
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Card, { children: [
892
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(BrandHeader, { title: "Check your email", subtitle: `We sent a sign-in link to ${email}.` }),
893
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Footer, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(LinkButton, { onClick: () => {
894
+ setMagicLinkSent(false);
895
+ switchMode("password");
896
+ }, children: "Back to sign in" }) })
897
+ ] });
898
+ }
899
+ const submitLabel = busy ? "Please wait\u2026" : mode === "magic-link" ? "Email me a link" : mode === "email-otp" ? otpSent ? "Sign in" : "Email me a code" : mode === "sso" ? "Continue with SSO" : "Sign in";
900
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Card, { children: [
901
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(BrandHeader, { title: props.title ?? "Sign in", subtitle: props.subtitle }),
902
+ allowSocial ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SocialButtons, { callbackURL, divider: false }) : null,
903
+ allowSocial && methods.social.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Divider, { children: "or" }) : null,
904
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("form", { onSubmit, noValidate: true, style: { display: "flex", flexDirection: "column", gap: 16 }, children: [
905
+ formError ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Alert, { children: formError }) : null,
906
+ mode === "email-otp" && otpSent ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
907
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Notice, { children: [
908
+ "Enter the 6-digit code we emailed to ",
909
+ email,
910
+ "."
911
+ ] }),
912
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
913
+ TextInput,
914
+ {
915
+ label: "Code",
916
+ inputMode: "numeric",
917
+ autoComplete: "one-time-code",
918
+ value: otp,
919
+ onChange: (e) => setOtp(e.currentTarget.value),
920
+ error: fieldErrors.otp,
921
+ disabled: busy
922
+ }
923
+ )
924
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
925
+ mode === "sso" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Notice, { children: "Sign in through your organization's identity provider." }) : null,
926
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
927
+ TextInput,
928
+ {
929
+ label: mode === "sso" ? "Work email" : "Email",
930
+ type: "email",
931
+ autoComplete: "email",
932
+ value: email,
933
+ onChange: (e) => setEmail(e.currentTarget.value),
934
+ error: fieldErrors.email,
935
+ disabled: busy
936
+ }
937
+ ),
938
+ mode === "password" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
939
+ TextInput,
940
+ {
941
+ label: "Password",
942
+ type: "password",
943
+ autoComplete: "current-password",
944
+ value: password,
945
+ onChange: (e) => setPassword(e.currentTarget.value),
946
+ error: fieldErrors.password,
947
+ disabled: busy
948
+ }
949
+ ) : null
950
+ ] }),
951
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Button, { type: "submit", busy, disabled: !ready, children: submitLabel }),
952
+ mode === "password" && props.onForgotPassword ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Footer, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(LinkButton, { onClick: props.onForgotPassword, children: "Forgot password?" }) }) : null,
953
+ allowMagicLink || allowEmailOtp || allowSso ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Footer, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", gap: 12, justifyContent: "center", flexWrap: "wrap" }, children: [
954
+ mode !== "password" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(LinkButton, { onClick: () => switchMode("password"), children: "Use a password" }) : null,
955
+ allowMagicLink && mode !== "magic-link" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(LinkButton, { onClick: () => switchMode("magic-link"), children: "Email me a link" }) : null,
956
+ allowEmailOtp && mode !== "email-otp" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(LinkButton, { onClick: () => switchMode("email-otp"), children: "Email me a code" }) : null,
957
+ allowSso && mode !== "sso" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(LinkButton, { onClick: () => switchMode("sso"), children: "Single sign-on" }) : null
958
+ ] }) }) : null
959
+ ] }),
960
+ props.footer ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Footer, { children: props.footer }) : null
961
+ ] });
962
+ }
963
+
964
+ // src/components/SignUp.tsx
965
+ var import_react7 = require("react");
966
+ var import_jsx_runtime4 = require("react/jsx-runtime");
967
+ function SignUp(props) {
968
+ const passwordMin = props.passwordMinLength ?? 8;
969
+ const { auth, status } = useOptare();
970
+ const methods = useAuthMethods();
971
+ const allowSocial = props.allowSocial ?? true;
972
+ const callbackURL = props.callbackURL ?? (typeof window !== "undefined" ? window.location.href : void 0);
973
+ const [name, setName] = (0, import_react7.useState)("");
974
+ const [email, setEmail] = (0, import_react7.useState)("");
975
+ const [password, setPassword] = (0, import_react7.useState)("");
976
+ const [fieldErrors, setFieldErrors] = (0, import_react7.useState)({});
977
+ const [formError, setFormError] = (0, import_react7.useState)(null);
978
+ const [busy, setBusy] = (0, import_react7.useState)(false);
979
+ const ready = status === "ready" && !!auth;
980
+ const pwCheck = checkPassword(password, passwordMin);
981
+ async function onSubmit(e) {
982
+ e.preventDefault();
983
+ if (!auth) return;
984
+ setFormError(null);
985
+ const errs = validateSignUp({ name, email, password }, passwordMin);
986
+ setFieldErrors(errs);
987
+ if (Object.keys(errs).length) return;
988
+ setBusy(true);
989
+ try {
990
+ const res = await auth.signUp.email({
991
+ name,
992
+ email,
993
+ password,
994
+ callbackURL
995
+ });
996
+ if (res.error) {
997
+ setFormError(res.error.message ?? "Could not create your account");
998
+ return;
999
+ }
1000
+ if (res.data && !res.data.token) {
1001
+ props.onVerificationRequired?.(email);
1002
+ if (!props.onVerificationRequired)
1003
+ setFormError("Check your email to confirm your address, then sign in.");
1004
+ return;
1005
+ }
1006
+ notifySessionChanged();
1007
+ if (props.onSuccess) props.onSuccess();
1008
+ else if (props.redirectTo && typeof window !== "undefined")
1009
+ window.location.assign(props.redirectTo);
1010
+ } catch (err) {
1011
+ setFormError(err instanceof Error ? err.message : "Sign-up failed");
1012
+ } finally {
1013
+ setBusy(false);
1014
+ }
1015
+ }
1016
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Card, { children: [
1017
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BrandHeader, { title: props.title ?? "Create your account", subtitle: props.subtitle }),
1018
+ allowSocial ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SocialButtons, { label: "Sign up with", callbackURL, divider: false }) : null,
1019
+ allowSocial && methods.social.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Divider, { children: "or" }) : null,
1020
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("form", { onSubmit, noValidate: true, style: { display: "flex", flexDirection: "column", gap: 16 }, children: [
1021
+ formError ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Alert, { children: formError }) : null,
1022
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1023
+ TextInput,
1024
+ {
1025
+ label: "Name",
1026
+ autoComplete: "name",
1027
+ value: name,
1028
+ onChange: (e) => setName(e.currentTarget.value),
1029
+ error: fieldErrors.name,
1030
+ disabled: busy
1031
+ }
1032
+ ),
1033
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1034
+ TextInput,
1035
+ {
1036
+ label: "Email",
1037
+ type: "email",
1038
+ autoComplete: "email",
1039
+ value: email,
1040
+ onChange: (e) => setEmail(e.currentTarget.value),
1041
+ error: fieldErrors.email,
1042
+ disabled: busy
1043
+ }
1044
+ ),
1045
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1046
+ TextInput,
1047
+ {
1048
+ label: "Password",
1049
+ type: "password",
1050
+ autoComplete: "new-password",
1051
+ value: password,
1052
+ onChange: (e) => setPassword(e.currentTarget.value),
1053
+ error: fieldErrors.password,
1054
+ disabled: busy
1055
+ }
1056
+ ),
1057
+ password && !fieldErrors.password ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1058
+ "div",
1059
+ {
1060
+ "aria-hidden": true,
1061
+ style: {
1062
+ height: 4,
1063
+ borderRadius: 2,
1064
+ background: "var(--optare-border)",
1065
+ overflow: "hidden"
1066
+ },
1067
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1068
+ "div",
1069
+ {
1070
+ style: {
1071
+ height: "100%",
1072
+ width: `${pwCheck.score / 4 * 100}%`,
1073
+ background: pwCheck.score >= 3 ? "var(--optare-primary)" : "var(--optare-muted)",
1074
+ transition: "width 150ms"
1075
+ }
1076
+ }
1077
+ )
1078
+ }
1079
+ ) : null,
1080
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Button, { type: "submit", busy, disabled: !ready, children: busy ? "Creating account\u2026" : "Sign up" })
1081
+ ] }),
1082
+ props.footer ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Footer, { children: props.footer }) : null
1083
+ ] });
1084
+ }
1085
+
1086
+ // src/components/AccountButton.tsx
1087
+ var import_react8 = require("react");
1088
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1089
+ function initials(nameOrEmail) {
1090
+ const parts = nameOrEmail.trim().split(/\s+/);
1091
+ if (parts.length >= 2) return (parts[0][0] + parts[1][0]).toUpperCase();
1092
+ return nameOrEmail.slice(0, 2).toUpperCase();
1093
+ }
1094
+ var s = {
1095
+ wrap: { position: "relative", display: "inline-block", fontFamily: "var(--optare-font)" },
1096
+ trigger: {
1097
+ display: "flex",
1098
+ alignItems: "center",
1099
+ gap: 8,
1100
+ padding: "6px 10px",
1101
+ background: "var(--optare-bg)",
1102
+ color: "var(--optare-fg)",
1103
+ border: "1px solid var(--optare-border)",
1104
+ borderRadius: "var(--optare-radius)",
1105
+ cursor: "pointer",
1106
+ font: "inherit",
1107
+ fontSize: 14
1108
+ },
1109
+ avatar: {
1110
+ width: 28,
1111
+ height: 28,
1112
+ borderRadius: "50%",
1113
+ background: "var(--optare-primary)",
1114
+ color: "var(--optare-primary-text)",
1115
+ display: "flex",
1116
+ alignItems: "center",
1117
+ justifyContent: "center",
1118
+ fontSize: 12,
1119
+ fontWeight: 700,
1120
+ objectFit: "cover",
1121
+ flexShrink: 0
1122
+ },
1123
+ menu: {
1124
+ position: "absolute",
1125
+ right: 0,
1126
+ marginTop: 6,
1127
+ minWidth: 200,
1128
+ background: "var(--optare-bg)",
1129
+ color: "var(--optare-fg)",
1130
+ border: "1px solid var(--optare-border)",
1131
+ borderRadius: "var(--optare-radius)",
1132
+ boxShadow: "0 8px 24px rgba(0,0,0,0.12)",
1133
+ padding: 6,
1134
+ zIndex: 50
1135
+ },
1136
+ meta: { padding: "8px 10px", borderBottom: "1px solid var(--optare-border)", marginBottom: 4 },
1137
+ name: { fontSize: 14, fontWeight: 600 },
1138
+ email: { fontSize: 12, color: "var(--optare-muted)" },
1139
+ item: {
1140
+ display: "block",
1141
+ width: "100%",
1142
+ textAlign: "left",
1143
+ padding: "8px 10px",
1144
+ background: "none",
1145
+ border: "none",
1146
+ borderRadius: "calc(var(--optare-radius) - 2px)",
1147
+ font: "inherit",
1148
+ fontSize: 13,
1149
+ color: "inherit",
1150
+ cursor: "pointer"
1151
+ }
1152
+ };
1153
+ function AccountButton(props) {
1154
+ const { cssVars } = useOptare();
1155
+ const { user, isPending, isAuthenticated } = useSession();
1156
+ const { signOut, isPending: signingOut } = useSignOut();
1157
+ const [open, setOpen] = (0, import_react8.useState)(false);
1158
+ const wrapRef = (0, import_react8.useRef)(null);
1159
+ (0, import_react8.useEffect)(() => {
1160
+ if (!open) return;
1161
+ const onDoc = (e) => {
1162
+ if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false);
1163
+ };
1164
+ const onEsc = (e) => e.key === "Escape" && setOpen(false);
1165
+ document.addEventListener("mousedown", onDoc);
1166
+ document.addEventListener("keydown", onEsc);
1167
+ return () => {
1168
+ document.removeEventListener("mousedown", onDoc);
1169
+ document.removeEventListener("keydown", onEsc);
1170
+ };
1171
+ }, [open]);
1172
+ if (isPending) return null;
1173
+ if (!isAuthenticated || !user) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: props.signInSlot ?? null });
1174
+ const u = user;
1175
+ const display = u.name || u.email || "Account";
1176
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { ref: wrapRef, style: { ...cssVars, ...s.wrap }, children: [
1177
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1178
+ "button",
1179
+ {
1180
+ type: "button",
1181
+ style: s.trigger,
1182
+ "aria-haspopup": "menu",
1183
+ "aria-expanded": open,
1184
+ onClick: () => setOpen((o) => !o),
1185
+ children: [
1186
+ u.image ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("img", { src: u.image, alt: "", style: s.avatar }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: s.avatar, children: initials(display) }),
1187
+ !props.compact ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: display }) : null
1188
+ ]
1189
+ }
1190
+ ),
1191
+ open ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { role: "menu", style: s.menu, children: [
1192
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: s.meta, children: [
1193
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: s.name, children: u.name || "\u2014" }),
1194
+ u.email ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: s.email, children: u.email }) : null
1195
+ ] }),
1196
+ props.menuItems,
1197
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1198
+ "button",
1199
+ {
1200
+ type: "button",
1201
+ role: "menuitem",
1202
+ style: s.item,
1203
+ disabled: signingOut,
1204
+ onClick: async () => {
1205
+ await signOut();
1206
+ setOpen(false);
1207
+ props.onSignedOut?.();
1208
+ },
1209
+ children: signingOut ? "Signing out\u2026" : "Sign out"
1210
+ }
1211
+ )
1212
+ ] }) : null
1213
+ ] });
1214
+ }
1215
+
1216
+ // src/components/ForgotPassword.tsx
1217
+ var import_react9 = require("react");
1218
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1219
+ function ForgotPassword(props) {
1220
+ const { auth, status } = useOptare();
1221
+ const [email, setEmail] = (0, import_react9.useState)("");
1222
+ const [fieldError, setFieldError] = (0, import_react9.useState)();
1223
+ const [formError, setFormError] = (0, import_react9.useState)(null);
1224
+ const [busy, setBusy] = (0, import_react9.useState)(false);
1225
+ const [sent, setSent] = (0, import_react9.useState)(false);
1226
+ const ready = status === "ready" && !!auth;
1227
+ async function onSubmit(e) {
1228
+ e.preventDefault();
1229
+ if (!auth) return;
1230
+ if (!isValidEmail(email)) {
1231
+ setFieldError("Enter a valid email address");
1232
+ return;
1233
+ }
1234
+ setFieldError(void 0);
1235
+ setFormError(null);
1236
+ setBusy(true);
1237
+ try {
1238
+ const res = await auth.forgetPassword({
1239
+ email,
1240
+ // Absolute by default — the reset link is built on the Optare origin, so
1241
+ // a relative path would drop the user there instead of on this app.
1242
+ redirectTo: props.redirectTo ?? (typeof window !== "undefined" ? `${window.location.origin}/reset-password` : "/reset-password")
1243
+ });
1244
+ if (res?.error && res.error.message && /rate|too many/i.test(res.error.message)) {
1245
+ setFormError(res.error.message);
1246
+ } else {
1247
+ setSent(true);
1248
+ }
1249
+ } catch (err) {
1250
+ setFormError(err instanceof Error ? err.message : "Could not send the email");
1251
+ } finally {
1252
+ setBusy(false);
1253
+ }
1254
+ }
1255
+ if (sent) {
1256
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Card, { children: [
1257
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1258
+ BrandHeader,
1259
+ {
1260
+ title: "Check your email",
1261
+ subtitle: `If an account exists for ${email}, a reset link is on its way.`
1262
+ }
1263
+ ),
1264
+ props.onBack ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Footer, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LinkButton, { onClick: props.onBack, children: "Back to sign in" }) }) : null
1265
+ ] });
1266
+ }
1267
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Card, { children: [
1268
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1269
+ BrandHeader,
1270
+ {
1271
+ title: props.title ?? "Reset your password",
1272
+ subtitle: props.subtitle ?? "We'll email you a link to set a new one."
1273
+ }
1274
+ ),
1275
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("form", { onSubmit, noValidate: true, style: { display: "flex", flexDirection: "column", gap: 16 }, children: [
1276
+ formError ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Alert, { children: formError }) : null,
1277
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1278
+ TextInput,
1279
+ {
1280
+ label: "Email",
1281
+ type: "email",
1282
+ autoComplete: "email",
1283
+ value: email,
1284
+ onChange: (e) => setEmail(e.currentTarget.value),
1285
+ error: fieldError,
1286
+ disabled: busy
1287
+ }
1288
+ ),
1289
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Button, { type: "submit", busy, disabled: !ready, children: busy ? "Sending\u2026" : "Send reset link" }),
1290
+ props.onBack ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Footer, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(LinkButton, { onClick: props.onBack, children: "Back to sign in" }) }) : null
1291
+ ] }),
1292
+ props.footer ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Footer, { children: props.footer }) : null
1293
+ ] });
1294
+ }
1295
+
1296
+ // src/components/ResetPassword.tsx
1297
+ var import_react10 = require("react");
1298
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1299
+ function tokenFromUrl() {
1300
+ if (typeof window === "undefined") return null;
1301
+ const q = new URLSearchParams(window.location.search);
1302
+ return q.get("token") || q.get("t") || null;
1303
+ }
1304
+ function ResetPassword(props) {
1305
+ const { auth, status } = useOptare();
1306
+ const min = props.passwordMinLength ?? 12;
1307
+ const [token, setToken] = (0, import_react10.useState)(props.token ?? null);
1308
+ const [password, setPassword] = (0, import_react10.useState)("");
1309
+ const [confirm, setConfirm] = (0, import_react10.useState)("");
1310
+ const [fieldErrors, setFieldErrors] = (0, import_react10.useState)({});
1311
+ const [formError, setFormError] = (0, import_react10.useState)(null);
1312
+ const [busy, setBusy] = (0, import_react10.useState)(false);
1313
+ const [done, setDone] = (0, import_react10.useState)(false);
1314
+ (0, import_react10.useEffect)(() => {
1315
+ if (!props.token) setToken(tokenFromUrl());
1316
+ }, [props.token]);
1317
+ const ready = status === "ready" && !!auth;
1318
+ const pw = checkPassword(password, min);
1319
+ async function onSubmit(e) {
1320
+ e.preventDefault();
1321
+ if (!auth || !token) return;
1322
+ const errs = {};
1323
+ if (!pw.ok && pw.message) errs.password = pw.message;
1324
+ if (confirm !== password) errs.confirm = "Passwords don't match";
1325
+ setFieldErrors(errs);
1326
+ if (Object.keys(errs).length) return;
1327
+ setFormError(null);
1328
+ setBusy(true);
1329
+ try {
1330
+ const res = await auth.resetPassword({ newPassword: password, token });
1331
+ if (res?.error) {
1332
+ setFormError(res.error.message ?? "That reset link is no longer valid");
1333
+ return;
1334
+ }
1335
+ setDone(true);
1336
+ if (props.onSuccess) props.onSuccess();
1337
+ else if (props.redirectTo && typeof window !== "undefined")
1338
+ window.location.assign(props.redirectTo);
1339
+ } catch (err) {
1340
+ setFormError(err instanceof Error ? err.message : "Could not reset the password");
1341
+ } finally {
1342
+ setBusy(false);
1343
+ }
1344
+ }
1345
+ if (done) {
1346
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Card, { children: [
1347
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(BrandHeader, { title: "Password updated", subtitle: "You can sign in with your new password now." }),
1348
+ props.onBack ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Footer, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(LinkButton, { onClick: props.onBack, children: "Go to sign in" }) }) : null
1349
+ ] });
1350
+ }
1351
+ if (!token) {
1352
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Card, { children: [
1353
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1354
+ BrandHeader,
1355
+ {
1356
+ title: "Link expired",
1357
+ subtitle: "This reset link is missing or has already been used. Request a new one."
1358
+ }
1359
+ ),
1360
+ props.onBack ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Footer, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(LinkButton, { onClick: props.onBack, children: "Back to sign in" }) }) : null
1361
+ ] });
1362
+ }
1363
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Card, { children: [
1364
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(BrandHeader, { title: props.title ?? "Set a new password" }),
1365
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("form", { onSubmit, noValidate: true, style: { display: "flex", flexDirection: "column", gap: 16 }, children: [
1366
+ formError ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Alert, { children: formError }) : null,
1367
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1368
+ TextInput,
1369
+ {
1370
+ label: "New password",
1371
+ type: "password",
1372
+ autoComplete: "new-password",
1373
+ value: password,
1374
+ onChange: (e) => setPassword(e.currentTarget.value),
1375
+ error: fieldErrors.password,
1376
+ disabled: busy
1377
+ }
1378
+ ),
1379
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1380
+ TextInput,
1381
+ {
1382
+ label: "Confirm password",
1383
+ type: "password",
1384
+ autoComplete: "new-password",
1385
+ value: confirm,
1386
+ onChange: (e) => setConfirm(e.currentTarget.value),
1387
+ error: fieldErrors.confirm,
1388
+ disabled: busy
1389
+ }
1390
+ ),
1391
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Button, { type: "submit", busy, disabled: !ready, children: busy ? "Saving\u2026" : "Update password" })
1392
+ ] }),
1393
+ props.footer ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Footer, { children: props.footer }) : null
1394
+ ] });
1395
+ }
1396
+
1397
+ // src/components/TwoFactorChallenge.tsx
1398
+ var import_react11 = require("react");
1399
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1400
+ function TwoFactorChallenge(props) {
1401
+ const { auth, status } = useOptare();
1402
+ const allowBackup = props.allowBackupCode ?? true;
1403
+ const [useBackup, setUseBackup] = (0, import_react11.useState)(false);
1404
+ const [code, setCode] = (0, import_react11.useState)("");
1405
+ const [fieldError, setFieldError] = (0, import_react11.useState)();
1406
+ const [formError, setFormError] = (0, import_react11.useState)(null);
1407
+ const [busy, setBusy] = (0, import_react11.useState)(false);
1408
+ const ready = status === "ready" && !!auth;
1409
+ async function onSubmit(e) {
1410
+ e.preventDefault();
1411
+ if (!auth) return;
1412
+ const trimmed = code.trim().replace(/\s+/g, "");
1413
+ if (trimmed.length < (useBackup ? 8 : 6)) {
1414
+ setFieldError(useBackup ? "Enter a full backup code" : "Enter the 6-digit code");
1415
+ return;
1416
+ }
1417
+ setFieldError(void 0);
1418
+ setFormError(null);
1419
+ setBusy(true);
1420
+ try {
1421
+ const call = useBackup ? auth.twoFactor.verifyBackupCode({ code: trimmed }) : auth.twoFactor.verifyTotp({ code: trimmed });
1422
+ const res = await call;
1423
+ if (res?.error) {
1424
+ setFormError(res.error.message ?? "That code didn't work");
1425
+ return;
1426
+ }
1427
+ notifySessionChanged();
1428
+ if (props.onSuccess) props.onSuccess();
1429
+ else if (props.redirectTo && typeof window !== "undefined")
1430
+ window.location.assign(props.redirectTo);
1431
+ } catch (err) {
1432
+ setFormError(err instanceof Error ? err.message : "Verification failed");
1433
+ } finally {
1434
+ setBusy(false);
1435
+ }
1436
+ }
1437
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Card, { children: [
1438
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1439
+ BrandHeader,
1440
+ {
1441
+ title: props.title ?? "Two-factor authentication",
1442
+ subtitle: props.subtitle ?? (useBackup ? "Enter one of your saved backup codes." : "Enter the code from your authenticator app.")
1443
+ }
1444
+ ),
1445
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("form", { onSubmit, noValidate: true, style: { display: "flex", flexDirection: "column", gap: 16 }, children: [
1446
+ formError ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Alert, { children: formError }) : null,
1447
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1448
+ TextInput,
1449
+ {
1450
+ label: useBackup ? "Backup code" : "Authentication code",
1451
+ inputMode: useBackup ? "text" : "numeric",
1452
+ autoComplete: "one-time-code",
1453
+ value: code,
1454
+ onChange: (e) => setCode(e.currentTarget.value),
1455
+ error: fieldError,
1456
+ disabled: busy
1457
+ }
1458
+ ),
1459
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Button, { type: "submit", busy, disabled: !ready, children: busy ? "Verifying\u2026" : "Verify" }),
1460
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Footer, { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", gap: 12, justifyContent: "center", flexWrap: "wrap" }, children: [
1461
+ allowBackup ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LinkButton, { onClick: () => {
1462
+ setUseBackup((b) => !b);
1463
+ setCode("");
1464
+ setFieldError(void 0);
1465
+ }, children: useBackup ? "Use an authenticator code" : "Use a backup code" }) : null,
1466
+ props.onCancel ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LinkButton, { onClick: props.onCancel, children: "Cancel" }) : null
1467
+ ] }) })
1468
+ ] }),
1469
+ props.footer ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Footer, { children: props.footer }) : null
1470
+ ] });
1471
+ }
1472
+
1473
+ // src/index.ts
1474
+ var import_client2 = require("@optare/client");
1475
+ // Annotate the CommonJS export names for ESM import in node:
1476
+ 0 && (module.exports = {
1477
+ AccountButton,
1478
+ DEFAULT_AUTH_METHODS,
1479
+ DEFAULT_OPTARE_ORIGIN,
1480
+ DEFAULT_PRIMARY,
1481
+ DEFAULT_RADIUS,
1482
+ ForgotPassword,
1483
+ OptareConfigError,
1484
+ OptareProvider,
1485
+ ResetPassword,
1486
+ SignIn,
1487
+ SignUp,
1488
+ SocialButtons,
1489
+ TwoFactorChallenge,
1490
+ brandingToCssVars,
1491
+ checkPassword,
1492
+ contrastText,
1493
+ createReactAuthClient,
1494
+ isValidEmail,
1495
+ notifySessionChanged,
1496
+ resolveOptareConfig,
1497
+ safeColor,
1498
+ safeLength,
1499
+ useActiveOrganization,
1500
+ useAuthMethods,
1501
+ useBranding,
1502
+ useOptare,
1503
+ useOptareAuth,
1504
+ useOptareConfig,
1505
+ useSession,
1506
+ useSignOut,
1507
+ useUser,
1508
+ validateSignIn,
1509
+ validateSignUp
1510
+ });