@datatechsolutions/ui 2.11.86 → 2.11.88

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 (75) hide show
  1. package/dist/billing-panel-DsHhhJqG.d.mts +18 -0
  2. package/dist/billing-panel-DsHhhJqG.d.ts +18 -0
  3. package/dist/chunk-4667D2ZT.mjs +61 -0
  4. package/dist/chunk-4667D2ZT.mjs.map +1 -0
  5. package/dist/chunk-5HXDJBVX.mjs +1330 -0
  6. package/dist/chunk-5HXDJBVX.mjs.map +1 -0
  7. package/dist/chunk-DJ33CSGJ.mjs +126 -0
  8. package/dist/chunk-DJ33CSGJ.mjs.map +1 -0
  9. package/dist/chunk-F4TOOARV.mjs +503 -0
  10. package/dist/chunk-F4TOOARV.mjs.map +1 -0
  11. package/dist/chunk-GEUGFYLO.mjs +237 -0
  12. package/dist/chunk-GEUGFYLO.mjs.map +1 -0
  13. package/dist/chunk-LBALE4JX.js +1342 -0
  14. package/dist/chunk-LBALE4JX.js.map +1 -0
  15. package/dist/chunk-MXFEU7A6.js +148 -0
  16. package/dist/chunk-MXFEU7A6.js.map +1 -0
  17. package/dist/chunk-NBCOVUQP.mjs +142 -0
  18. package/dist/chunk-NBCOVUQP.mjs.map +1 -0
  19. package/dist/chunk-P4RVGMZL.js +128 -0
  20. package/dist/chunk-P4RVGMZL.js.map +1 -0
  21. package/dist/chunk-Q2MG7S2E.js +239 -0
  22. package/dist/chunk-Q2MG7S2E.js.map +1 -0
  23. package/dist/chunk-RV555OEO.mjs +1009 -0
  24. package/dist/chunk-RV555OEO.mjs.map +1 -0
  25. package/dist/chunk-SAYVWIMJ.js +63 -0
  26. package/dist/chunk-SAYVWIMJ.js.map +1 -0
  27. package/dist/chunk-SUHNSUMH.mjs +1021 -0
  28. package/dist/chunk-SUHNSUMH.mjs.map +1 -0
  29. package/dist/chunk-TOEMSC4P.mjs +99 -0
  30. package/dist/chunk-TOEMSC4P.mjs.map +1 -0
  31. package/dist/chunk-UUHV5KHF.js +505 -0
  32. package/dist/chunk-UUHV5KHF.js.map +1 -0
  33. package/dist/chunk-UVEPTYZC.js +101 -0
  34. package/dist/chunk-UVEPTYZC.js.map +1 -0
  35. package/dist/chunk-X2KCCQPL.js +1049 -0
  36. package/dist/chunk-X2KCCQPL.js.map +1 -0
  37. package/dist/chunk-ZARCUQA6.js +1015 -0
  38. package/dist/chunk-ZARCUQA6.js.map +1 -0
  39. package/dist/platform/admin/index.d.mts +17 -0
  40. package/dist/platform/admin/index.d.ts +17 -0
  41. package/dist/platform/admin/index.js +39 -0
  42. package/dist/platform/admin/index.js.map +1 -0
  43. package/dist/platform/admin/index.mjs +10 -0
  44. package/dist/platform/admin/index.mjs.map +1 -0
  45. package/dist/platform/auth/index.d.mts +73 -0
  46. package/dist/platform/auth/index.d.ts +73 -0
  47. package/dist/platform/auth/index.js +107 -0
  48. package/dist/platform/auth/index.js.map +1 -0
  49. package/dist/platform/auth/index.mjs +10 -0
  50. package/dist/platform/auth/index.mjs.map +1 -0
  51. package/dist/platform/billing/index.d.mts +29 -0
  52. package/dist/platform/billing/index.d.ts +29 -0
  53. package/dist/platform/billing/index.js +22 -0
  54. package/dist/platform/billing/index.js.map +1 -0
  55. package/dist/platform/billing/index.mjs +9 -0
  56. package/dist/platform/billing/index.mjs.map +1 -0
  57. package/dist/platform/impersonation/index.d.mts +19 -0
  58. package/dist/platform/impersonation/index.d.ts +19 -0
  59. package/dist/platform/impersonation/index.js +17 -0
  60. package/dist/platform/impersonation/index.js.map +1 -0
  61. package/dist/platform/impersonation/index.mjs +8 -0
  62. package/dist/platform/impersonation/index.mjs.map +1 -0
  63. package/dist/platform/index.d.mts +45 -2
  64. package/dist/platform/index.d.ts +45 -2
  65. package/dist/platform/index.js +4850 -0
  66. package/dist/platform/index.js.map +1 -1
  67. package/dist/platform/index.mjs +4716 -3
  68. package/dist/platform/index.mjs.map +1 -1
  69. package/dist/platform/settings/index.d.mts +31 -0
  70. package/dist/platform/settings/index.d.ts +31 -0
  71. package/dist/platform/settings/index.js +21 -0
  72. package/dist/platform/settings/index.js.map +1 -0
  73. package/dist/platform/settings/index.mjs +12 -0
  74. package/dist/platform/settings/index.mjs.map +1 -0
  75. package/package.json +26 -1
@@ -0,0 +1,1330 @@
1
+ "use client";
2
+ import { OtpInput, SocialLoginButtons, AuthLayout, PasswordStrengthMeter } from './chunk-NBCOVUQP.mjs';
3
+ import { SegmentedControl, Spinner, Input, Button, Card, PasswordInput, FormCheckbox, EmptyState, BaseForm, CardContent, Avatar, Badge, ListCard, ListCardItem, InlineSpinner, DynamicIslandConfirm } from './chunk-ZJQ5RLGK.mjs';
4
+ import { triggerHaptic } from './chunk-D2JF6C3E.mjs';
5
+ import { useTranslations } from './chunk-7VJ7CMMT.mjs';
6
+ import { useState, useCallback, useEffect, Fragment as Fragment$1 } from 'react';
7
+ import { EnvelopeIcon, UserIcon, LockClosedIcon, CheckCircleIcon, DevicePhoneMobileIcon, PaperAirplaneIcon, ShieldExclamationIcon, BuildingOffice2Icon } from '@heroicons/react/24/outline';
8
+ import { useAuth, useSocialProviders } from '@datatechsolutions/windsock/client';
9
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
10
+ import { ChevronRightIcon, ChevronUpDownIcon, ArrowRightStartOnRectangleIcon } from '@heroicons/react/20/solid';
11
+ import { Menu, Transition } from '@headlessui/react';
12
+
13
+ function MfaChallenge({
14
+ mfaToken,
15
+ onSuccess,
16
+ onCancel,
17
+ availableMethods = ["totp", "backup"],
18
+ embedded = false
19
+ }) {
20
+ const t = useTranslations("windsock");
21
+ const { verifyMFA } = useAuth();
22
+ const [method, setMethod] = useState("totp");
23
+ const [totpCode, setTotpCode] = useState("");
24
+ const [backupCode, setBackupCode] = useState("");
25
+ const [error, setError] = useState(null);
26
+ const [isSubmitting, setIsSubmitting] = useState(false);
27
+ const [failureCount, setFailureCount] = useState(0);
28
+ const handleVerify = useCallback(
29
+ async (code) => {
30
+ setError(null);
31
+ setIsSubmitting(true);
32
+ triggerHaptic("light");
33
+ try {
34
+ const result = await verifyMFA(mfaToken, code);
35
+ if (result.success) {
36
+ triggerHaptic("success");
37
+ onSuccess?.();
38
+ } else if (result.error) {
39
+ triggerHaptic("error");
40
+ const count = failureCount + 1;
41
+ setFailureCount(count);
42
+ if (count >= 3) {
43
+ setError(t("mfa.challenge.errorLockout"));
44
+ } else {
45
+ setError(t("mfa.challenge.errorInvalidCode"));
46
+ }
47
+ }
48
+ } catch {
49
+ triggerHaptic("error");
50
+ setError(t("mfa.challenge.errorVerificationFailed"));
51
+ } finally {
52
+ setIsSubmitting(false);
53
+ }
54
+ },
55
+ [verifyMFA, mfaToken, onSuccess, failureCount, t]
56
+ );
57
+ const handleTotpComplete = useCallback(
58
+ (code) => {
59
+ handleVerify(code);
60
+ },
61
+ [handleVerify]
62
+ );
63
+ const handleBackupSubmit = useCallback(() => {
64
+ if (backupCode.trim()) {
65
+ handleVerify(backupCode.trim());
66
+ }
67
+ }, [backupCode, handleVerify]);
68
+ const handleMethodChange = useCallback((value) => {
69
+ triggerHaptic("light");
70
+ setMethod(value);
71
+ setError(null);
72
+ setTotpCode("");
73
+ setBackupCode("");
74
+ }, []);
75
+ const segments = availableMethods.map((m) => ({
76
+ value: m,
77
+ label: m === "totp" ? t("mfa.challenge.methodTotp") : t("mfa.challenge.methodBackup")
78
+ }));
79
+ const content = /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
80
+ /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
81
+ /* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold text-gray-900 dark:text-white", children: t("mfa.challenge.title") }),
82
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: method === "totp" ? t("mfa.challenge.descriptionTotp") : t("mfa.challenge.descriptionBackup") })
83
+ ] }),
84
+ availableMethods.length > 1 && /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx(
85
+ SegmentedControl,
86
+ {
87
+ segments,
88
+ value: method,
89
+ onChange: handleMethodChange,
90
+ size: "sm"
91
+ }
92
+ ) }),
93
+ failureCount >= 3 && /* @__PURE__ */ jsx("div", { className: "rounded-xl border border-red-500/30 bg-red-500/10 px-4 py-3 text-sm text-red-600 dark:text-red-400 text-center", children: t("mfa.challenge.lockoutWarning") }),
94
+ method === "totp" && /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
95
+ /* @__PURE__ */ jsx(
96
+ OtpInput,
97
+ {
98
+ value: totpCode,
99
+ onChange: setTotpCode,
100
+ onComplete: handleTotpComplete,
101
+ error: error ?? void 0,
102
+ disabled: isSubmitting,
103
+ autoFocus: true
104
+ }
105
+ ),
106
+ isSubmitting && /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx(Spinner, {}) })
107
+ ] }),
108
+ method === "backup" && /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
109
+ /* @__PURE__ */ jsx(
110
+ Input,
111
+ {
112
+ value: backupCode,
113
+ onChange: (event) => setBackupCode(event.target.value),
114
+ placeholder: t("mfa.challenge.backupCodePlaceholder"),
115
+ error: error ?? void 0,
116
+ disabled: isSubmitting,
117
+ autoFocus: true,
118
+ className: "font-mono text-center tracking-widest"
119
+ }
120
+ ),
121
+ /* @__PURE__ */ jsx(
122
+ Button,
123
+ {
124
+ color: "ios-glass-blue",
125
+ fullWidth: true,
126
+ onClick: handleBackupSubmit,
127
+ disabled: !backupCode.trim() || isSubmitting,
128
+ loading: isSubmitting,
129
+ children: t("mfa.challenge.verifyBackupCode")
130
+ }
131
+ )
132
+ ] }),
133
+ onCancel && /* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsx(
134
+ "button",
135
+ {
136
+ type: "button",
137
+ onClick: () => {
138
+ triggerHaptic("light");
139
+ onCancel();
140
+ },
141
+ className: "text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors",
142
+ children: t("mfa.challenge.useDifferentAccount")
143
+ }
144
+ ) })
145
+ ] });
146
+ if (embedded) return content;
147
+ return /* @__PURE__ */ jsx(Card, { variant: "glass", className: "mx-auto max-w-md p-6 sm:p-8", children: content });
148
+ }
149
+ function SignIn({
150
+ onSuccess,
151
+ onSignUp,
152
+ onForgotPassword,
153
+ onPasswordless,
154
+ socialProviders,
155
+ allowPasswordless,
156
+ defaultEmail = "",
157
+ embedded = false,
158
+ heroPanel
159
+ }) {
160
+ const t = useTranslations("windsock");
161
+ const { loginWithCredentials, loginWithProvider } = useAuth();
162
+ const discovered = useSocialProviders();
163
+ const effectiveSocialProviders = socialProviders ?? discovered.providers;
164
+ const [email, setEmail] = useState(defaultEmail);
165
+ const [password, setPassword] = useState("");
166
+ const [error, setError] = useState(null);
167
+ const [isSubmitting, setIsSubmitting] = useState(false);
168
+ const [mfaRequired, setMfaRequired] = useState(null);
169
+ const [socialLoading, setSocialLoading] = useState(null);
170
+ const handleSubmit = useCallback(
171
+ async (event) => {
172
+ event.preventDefault();
173
+ setError(null);
174
+ setIsSubmitting(true);
175
+ try {
176
+ const result = await loginWithCredentials(email, password);
177
+ if (result.success) {
178
+ onSuccess?.();
179
+ } else if (result.mfaRequired && result.mfaToken) {
180
+ setMfaRequired({ mfaToken: result.mfaToken });
181
+ } else if (result.error) {
182
+ setError(t("signInError"));
183
+ }
184
+ } catch {
185
+ setError(t("signInError"));
186
+ } finally {
187
+ setIsSubmitting(false);
188
+ }
189
+ },
190
+ [email, password, loginWithCredentials, onSuccess, t]
191
+ );
192
+ const handleSocialLogin = useCallback((provider) => {
193
+ setSocialLoading(provider);
194
+ loginWithProvider(provider);
195
+ }, [loginWithProvider]);
196
+ if (mfaRequired) {
197
+ return /* @__PURE__ */ jsx(
198
+ MfaChallenge,
199
+ {
200
+ mfaToken: mfaRequired.mfaToken,
201
+ onSuccess,
202
+ onCancel: () => setMfaRequired(null),
203
+ embedded
204
+ }
205
+ );
206
+ }
207
+ const content = /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
208
+ /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
209
+ /* @__PURE__ */ jsx("h2", { className: "text-2xl font-bold text-gray-900 dark:text-white", children: t("signInTitle") }),
210
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: t("signInSubtitle") })
211
+ ] }),
212
+ error && /* @__PURE__ */ jsx("div", { className: "rounded-xl bg-red-50 dark:bg-red-900/20 px-4 py-3 text-sm text-ios-red", role: "alert", children: error }),
213
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
214
+ /* @__PURE__ */ jsx(
215
+ Input,
216
+ {
217
+ label: t("emailLabel"),
218
+ type: "email",
219
+ value: email,
220
+ onChange: (event) => setEmail(event.target.value),
221
+ placeholder: t("emailPlaceholder"),
222
+ autoComplete: "email",
223
+ disabled: isSubmitting,
224
+ required: true
225
+ }
226
+ ),
227
+ /* @__PURE__ */ jsx(
228
+ PasswordInput,
229
+ {
230
+ label: t("passwordLabel"),
231
+ value: password,
232
+ onChange: (event) => setPassword(event.target.value),
233
+ placeholder: t("passwordPlaceholder"),
234
+ autoComplete: "current-password",
235
+ disabled: isSubmitting,
236
+ required: true
237
+ }
238
+ ),
239
+ onForgotPassword && /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
240
+ "button",
241
+ {
242
+ type: "button",
243
+ onClick: onForgotPassword,
244
+ className: "text-sm font-medium text-ios-blue hover:text-blue-700 dark:hover:text-blue-400 transition-colors",
245
+ children: t("forgotPassword")
246
+ }
247
+ ) }),
248
+ /* @__PURE__ */ jsx(
249
+ Button,
250
+ {
251
+ type: "submit",
252
+ color: "ios-glass-green",
253
+ size: "lg",
254
+ fullWidth: true,
255
+ disabled: isSubmitting || !email || !password,
256
+ loading: isSubmitting,
257
+ children: t("signInButton")
258
+ }
259
+ )
260
+ ] }),
261
+ effectiveSocialProviders.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
262
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
263
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center", children: /* @__PURE__ */ jsx("div", { className: "w-full border-t border-gray-200/60 dark:border-white/10" }) }),
264
+ /* @__PURE__ */ jsx("div", { className: "relative flex justify-center text-xs", children: /* @__PURE__ */ jsx("span", { className: "bg-white/80 px-3 text-gray-400 dark:bg-gray-900/80 dark:text-gray-500", children: t("orContinueWith") }) })
265
+ ] }),
266
+ /* @__PURE__ */ jsx(
267
+ SocialLoginButtons,
268
+ {
269
+ providers: [...effectiveSocialProviders],
270
+ onProviderClick: handleSocialLogin,
271
+ loading: socialLoading,
272
+ disabled: isSubmitting
273
+ }
274
+ )
275
+ ] }),
276
+ allowPasswordless && /* @__PURE__ */ jsxs(
277
+ Button,
278
+ {
279
+ type: "button",
280
+ color: "ios-glass-blue",
281
+ size: "lg",
282
+ fullWidth: true,
283
+ onClick: onPasswordless,
284
+ children: [
285
+ /* @__PURE__ */ jsx(EnvelopeIcon, { className: "h-5 w-5" }),
286
+ t("signInPasswordless")
287
+ ]
288
+ }
289
+ ),
290
+ onSignUp && /* @__PURE__ */ jsxs("p", { className: "text-center text-sm text-gray-500 dark:text-gray-400", children: [
291
+ t("noAccount"),
292
+ " ",
293
+ /* @__PURE__ */ jsx(
294
+ "button",
295
+ {
296
+ type: "button",
297
+ onClick: onSignUp,
298
+ className: "font-medium text-ios-blue hover:text-blue-700 dark:hover:text-blue-400 transition-colors",
299
+ children: t("signUpLink")
300
+ }
301
+ )
302
+ ] })
303
+ ] });
304
+ if (embedded) return content;
305
+ return /* @__PURE__ */ jsx(AuthLayout, { heroPanel, children: content });
306
+ }
307
+ var RESEND_COOLDOWN_SECONDS = 60;
308
+ function VerifyEmail({
309
+ email,
310
+ onSuccess,
311
+ onResend,
312
+ embedded = false
313
+ }) {
314
+ const t = useTranslations("windsock");
315
+ const { client } = useAuth();
316
+ const [code, setCode] = useState("");
317
+ const [error, setError] = useState(null);
318
+ const [isSubmitting, setIsSubmitting] = useState(false);
319
+ const [isResending, setIsResending] = useState(false);
320
+ const [cooldown, setCooldown] = useState(RESEND_COOLDOWN_SECONDS);
321
+ useEffect(() => {
322
+ if (cooldown <= 0) return;
323
+ const timer = setInterval(() => {
324
+ setCooldown((previous) => {
325
+ if (previous <= 1) {
326
+ clearInterval(timer);
327
+ return 0;
328
+ }
329
+ return previous - 1;
330
+ });
331
+ }, 1e3);
332
+ return () => clearInterval(timer);
333
+ }, [cooldown]);
334
+ const handleComplete = useCallback(
335
+ async (completedCode) => {
336
+ setError(null);
337
+ setIsSubmitting(true);
338
+ try {
339
+ await client.verifyEmail(email, completedCode);
340
+ onSuccess?.();
341
+ } catch {
342
+ setError(t("verifyEmailError"));
343
+ setCode("");
344
+ } finally {
345
+ setIsSubmitting(false);
346
+ }
347
+ },
348
+ [client, email, onSuccess, t]
349
+ );
350
+ const handleResend = useCallback(async () => {
351
+ if (cooldown > 0 || isResending) return;
352
+ setIsResending(true);
353
+ setError(null);
354
+ try {
355
+ onResend?.();
356
+ setCooldown(RESEND_COOLDOWN_SECONDS);
357
+ } catch {
358
+ setError(t("resendEmailError"));
359
+ } finally {
360
+ setIsResending(false);
361
+ }
362
+ }, [cooldown, isResending, onResend, t]);
363
+ const content = /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
364
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3 text-center", children: [
365
+ /* @__PURE__ */ jsx("div", { className: "flex h-16 w-16 items-center justify-center rounded-full bg-blue-50 dark:bg-blue-500/10", children: /* @__PURE__ */ jsx(EnvelopeIcon, { className: "h-8 w-8 text-ios-blue" }) }),
366
+ /* @__PURE__ */ jsxs("div", { children: [
367
+ /* @__PURE__ */ jsx("h2", { className: "text-xl font-bold text-gray-900 dark:text-white", children: t("verifyEmailTitle") }),
368
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: t("verifyEmailSubtitle", { email }) })
369
+ ] })
370
+ ] }),
371
+ error && /* @__PURE__ */ jsx("div", { className: "rounded-xl bg-red-50 dark:bg-red-900/20 px-4 py-3 text-sm text-ios-red text-center", role: "alert", children: error }),
372
+ /* @__PURE__ */ jsx(
373
+ OtpInput,
374
+ {
375
+ value: code,
376
+ onChange: setCode,
377
+ onComplete: handleComplete,
378
+ error: error ?? void 0,
379
+ disabled: isSubmitting,
380
+ autoFocus: true
381
+ }
382
+ ),
383
+ isSubmitting && /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx(Spinner, {}) }),
384
+ /* @__PURE__ */ jsxs("div", { className: "text-center space-y-1", children: [
385
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: t("verifyEmailNoCode") }),
386
+ /* @__PURE__ */ jsx(
387
+ Button,
388
+ {
389
+ type: "button",
390
+ outline: true,
391
+ onClick: handleResend,
392
+ disabled: cooldown > 0 || isResending || isSubmitting,
393
+ loading: isResending,
394
+ children: cooldown > 0 ? t("resendEmailCooldown", { seconds: cooldown }) : t("resendEmailButton")
395
+ }
396
+ )
397
+ ] })
398
+ ] });
399
+ if (embedded) return content;
400
+ return /* @__PURE__ */ jsx(Card, { variant: "glass", className: "mx-auto max-w-md p-6 sm:p-8", children: content });
401
+ }
402
+ function SignUp({
403
+ onSuccess,
404
+ onSignIn,
405
+ socialProviders,
406
+ passwordPolicy,
407
+ embedded = false,
408
+ heroPanel
409
+ }) {
410
+ const t = useTranslations("windsock");
411
+ const { client, loginWithProvider } = useAuth();
412
+ const discovered = useSocialProviders();
413
+ const effectiveSocialProviders = socialProviders ?? discovered.providers;
414
+ const [phase, setPhase] = useState("form");
415
+ const [registeredEmail, setRegisteredEmail] = useState("");
416
+ const [name, setName] = useState("");
417
+ const [email, setEmail] = useState("");
418
+ const [password, setPassword] = useState("");
419
+ const [termsAccepted, setTermsAccepted] = useState(false);
420
+ const [error, setError] = useState(null);
421
+ const [isSubmitting, setIsSubmitting] = useState(false);
422
+ const [socialLoading, setSocialLoading] = useState(null);
423
+ const handleSubmit = useCallback(
424
+ async (event) => {
425
+ event.preventDefault();
426
+ setError(null);
427
+ setIsSubmitting(true);
428
+ try {
429
+ await client.register({ name, email, password });
430
+ setRegisteredEmail(email);
431
+ setPhase("verify");
432
+ } catch {
433
+ setError(t("signUpError"));
434
+ } finally {
435
+ setIsSubmitting(false);
436
+ }
437
+ },
438
+ [client, name, email, password, t]
439
+ );
440
+ const handleSocialLogin = useCallback((provider) => {
441
+ setSocialLoading(provider);
442
+ loginWithProvider(provider);
443
+ }, [loginWithProvider]);
444
+ if (phase === "verify") {
445
+ return /* @__PURE__ */ jsx(
446
+ VerifyEmail,
447
+ {
448
+ email: registeredEmail,
449
+ onSuccess,
450
+ embedded
451
+ }
452
+ );
453
+ }
454
+ const isFormValid = name.trim() !== "" && email.trim() !== "" && password !== "" && termsAccepted;
455
+ const content = /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
456
+ /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
457
+ /* @__PURE__ */ jsx("h2", { className: "text-2xl font-bold text-gray-900 dark:text-white", children: t("signUpTitle") }),
458
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: t("signUpSubtitle") })
459
+ ] }),
460
+ error && /* @__PURE__ */ jsx("div", { className: "rounded-xl bg-red-50 dark:bg-red-900/20 px-4 py-3 text-sm text-ios-red", role: "alert", children: error }),
461
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
462
+ /* @__PURE__ */ jsx(
463
+ Input,
464
+ {
465
+ label: t("nameLabel"),
466
+ type: "text",
467
+ value: name,
468
+ onChange: (event) => setName(event.target.value),
469
+ placeholder: t("namePlaceholder"),
470
+ icon: /* @__PURE__ */ jsx(UserIcon, { className: "h-5 w-5" }),
471
+ autoComplete: "name",
472
+ disabled: isSubmitting,
473
+ required: true
474
+ }
475
+ ),
476
+ /* @__PURE__ */ jsx(
477
+ Input,
478
+ {
479
+ label: t("emailLabel"),
480
+ type: "email",
481
+ value: email,
482
+ onChange: (event) => setEmail(event.target.value),
483
+ placeholder: t("emailPlaceholder"),
484
+ icon: /* @__PURE__ */ jsx(EnvelopeIcon, { className: "h-5 w-5" }),
485
+ autoComplete: "email",
486
+ disabled: isSubmitting,
487
+ required: true
488
+ }
489
+ ),
490
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
491
+ /* @__PURE__ */ jsx(
492
+ PasswordInput,
493
+ {
494
+ label: t("passwordLabel"),
495
+ value: password,
496
+ onChange: (event) => setPassword(event.target.value),
497
+ placeholder: t("passwordPlaceholder"),
498
+ icon: /* @__PURE__ */ jsx(LockClosedIcon, { className: "h-5 w-5" }),
499
+ autoComplete: "new-password",
500
+ disabled: isSubmitting,
501
+ required: true
502
+ }
503
+ ),
504
+ /* @__PURE__ */ jsx(
505
+ PasswordStrengthMeter,
506
+ {
507
+ password,
508
+ policy: passwordPolicy,
509
+ showRequirements: true
510
+ }
511
+ )
512
+ ] }),
513
+ /* @__PURE__ */ jsx(
514
+ FormCheckbox,
515
+ {
516
+ checked: termsAccepted,
517
+ onChange: setTermsAccepted,
518
+ label: t("termsLabel"),
519
+ description: t("termsDescription"),
520
+ disabled: isSubmitting
521
+ }
522
+ ),
523
+ /* @__PURE__ */ jsx(
524
+ Button,
525
+ {
526
+ type: "submit",
527
+ color: "ios-glass-blue",
528
+ fullWidth: true,
529
+ disabled: isSubmitting || !isFormValid,
530
+ loading: isSubmitting,
531
+ children: t("signUpButton")
532
+ }
533
+ )
534
+ ] }),
535
+ effectiveSocialProviders.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
536
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
537
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center", children: /* @__PURE__ */ jsx("div", { className: "w-full border-t border-gray-200 dark:border-white/10" }) }),
538
+ /* @__PURE__ */ jsx("div", { className: "relative flex justify-center text-xs", children: /* @__PURE__ */ jsx("span", { className: "bg-white dark:bg-gray-900 px-3 text-gray-400 dark:text-gray-500", children: t("orContinueWith") }) })
539
+ ] }),
540
+ /* @__PURE__ */ jsx(
541
+ SocialLoginButtons,
542
+ {
543
+ providers: [...effectiveSocialProviders],
544
+ onProviderClick: handleSocialLogin,
545
+ loading: socialLoading,
546
+ disabled: isSubmitting
547
+ }
548
+ )
549
+ ] }),
550
+ onSignIn && /* @__PURE__ */ jsxs("p", { className: "text-center text-sm text-gray-500 dark:text-gray-400", children: [
551
+ t("alreadyHaveAccount"),
552
+ " ",
553
+ /* @__PURE__ */ jsx(
554
+ "button",
555
+ {
556
+ type: "button",
557
+ onClick: onSignIn,
558
+ className: "font-medium text-ios-blue hover:text-blue-700 dark:hover:text-blue-400 transition-colors",
559
+ children: t("signInLink")
560
+ }
561
+ )
562
+ ] })
563
+ ] });
564
+ if (embedded) return content;
565
+ return /* @__PURE__ */ jsx(AuthLayout, { heroPanel, children: content });
566
+ }
567
+ function ForgotPassword({
568
+ onSuccess,
569
+ onBack,
570
+ defaultEmail = "",
571
+ embedded = false
572
+ }) {
573
+ const t = useTranslations("windsock");
574
+ const { client } = useAuth();
575
+ const [phase, setPhase] = useState("form");
576
+ const [email, setEmail] = useState(defaultEmail);
577
+ const [error, setError] = useState(null);
578
+ const [isSubmitting, setIsSubmitting] = useState(false);
579
+ const handleSubmit = useCallback(
580
+ async (event) => {
581
+ event.preventDefault();
582
+ setError(null);
583
+ setIsSubmitting(true);
584
+ try {
585
+ await client.requestPasswordReset(email);
586
+ setPhase("sent");
587
+ onSuccess?.();
588
+ } catch {
589
+ setError(t("forgotPasswordError"));
590
+ } finally {
591
+ setIsSubmitting(false);
592
+ }
593
+ },
594
+ [client, email, onSuccess, t]
595
+ );
596
+ const sentContent = /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
597
+ /* @__PURE__ */ jsx(
598
+ EmptyState,
599
+ {
600
+ variant: "minimal",
601
+ icon: CheckCircleIcon,
602
+ message: t("forgotPasswordSuccessTitle"),
603
+ description: t("forgotPasswordSuccessDescription", { email })
604
+ }
605
+ ),
606
+ onBack && /* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsx(
607
+ "button",
608
+ {
609
+ type: "button",
610
+ onClick: onBack,
611
+ className: "text-sm font-medium text-ios-blue hover:text-blue-700 dark:hover:text-blue-400 transition-colors",
612
+ children: t("backToSignIn")
613
+ }
614
+ ) })
615
+ ] });
616
+ const formContent = /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
617
+ /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
618
+ /* @__PURE__ */ jsx("h2", { className: "text-2xl font-bold text-gray-900 dark:text-white", children: t("forgotPasswordTitle") }),
619
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: t("forgotPasswordSubtitle") })
620
+ ] }),
621
+ error && /* @__PURE__ */ jsx("div", { className: "rounded-xl bg-red-50 dark:bg-red-900/20 px-4 py-3 text-sm text-ios-red", role: "alert", children: error }),
622
+ /* @__PURE__ */ jsx(
623
+ BaseForm,
624
+ {
625
+ icon: /* @__PURE__ */ jsx(EnvelopeIcon, { className: "h-5 w-5" }),
626
+ iconColor: "blue",
627
+ title: t("forgotPasswordFormTitle"),
628
+ subtitle: t("forgotPasswordFormSubtitle"),
629
+ onSubmit: handleSubmit,
630
+ submitLabel: t("forgotPasswordSubmit"),
631
+ isLoading: isSubmitting,
632
+ submitDisabled: !email.trim(),
633
+ showHeader: false,
634
+ children: /* @__PURE__ */ jsx(
635
+ Input,
636
+ {
637
+ label: t("emailLabel"),
638
+ type: "email",
639
+ value: email,
640
+ onChange: (event) => setEmail(event.target.value),
641
+ placeholder: t("emailPlaceholder"),
642
+ icon: /* @__PURE__ */ jsx(EnvelopeIcon, { className: "h-5 w-5" }),
643
+ autoComplete: "email",
644
+ disabled: isSubmitting,
645
+ required: true,
646
+ autoFocus: true
647
+ }
648
+ )
649
+ }
650
+ ),
651
+ onBack && /* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsx(
652
+ "button",
653
+ {
654
+ type: "button",
655
+ onClick: onBack,
656
+ className: "text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors",
657
+ children: t("backToSignIn")
658
+ }
659
+ ) })
660
+ ] });
661
+ const activeContent = phase === "sent" ? sentContent : formContent;
662
+ if (embedded) return activeContent;
663
+ return /* @__PURE__ */ jsx(Card, { variant: "glass", className: "mx-auto max-w-md p-6 sm:p-8", children: activeContent });
664
+ }
665
+ function ResetPassword({
666
+ token,
667
+ onSuccess,
668
+ passwordPolicy,
669
+ embedded = false
670
+ }) {
671
+ const t = useTranslations("windsock");
672
+ const { client } = useAuth();
673
+ const [phase, setPhase] = useState("form");
674
+ const [password, setPassword] = useState("");
675
+ const [confirmPassword, setConfirmPassword] = useState("");
676
+ const [error, setError] = useState(null);
677
+ const [isSubmitting, setIsSubmitting] = useState(false);
678
+ const passwordsMatch = password !== "" && confirmPassword !== "" && password === confirmPassword;
679
+ const hasPasswordMismatch = confirmPassword !== "" && password !== confirmPassword;
680
+ const handleSubmit = useCallback(
681
+ async (event) => {
682
+ event.preventDefault();
683
+ setError(null);
684
+ if (!passwordsMatch) {
685
+ setError(t("passwordMismatchError"));
686
+ return;
687
+ }
688
+ setIsSubmitting(true);
689
+ try {
690
+ await client.resetPassword(token, password);
691
+ setPhase("success");
692
+ onSuccess?.();
693
+ } catch {
694
+ setError(t("resetPasswordError"));
695
+ } finally {
696
+ setIsSubmitting(false);
697
+ }
698
+ },
699
+ [client, token, password, passwordsMatch, onSuccess, t]
700
+ );
701
+ const successContent = /* @__PURE__ */ jsx("div", { className: "space-y-5", children: /* @__PURE__ */ jsx(
702
+ EmptyState,
703
+ {
704
+ variant: "minimal",
705
+ icon: CheckCircleIcon,
706
+ message: t("resetPasswordSuccessTitle"),
707
+ description: t("resetPasswordSuccessDescription")
708
+ }
709
+ ) });
710
+ const formContent = /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
711
+ /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
712
+ /* @__PURE__ */ jsx("h2", { className: "text-2xl font-bold text-gray-900 dark:text-white", children: t("resetPasswordTitle") }),
713
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: t("resetPasswordSubtitle") })
714
+ ] }),
715
+ error && /* @__PURE__ */ jsx("div", { className: "rounded-xl bg-red-50 dark:bg-red-900/20 px-4 py-3 text-sm text-ios-red", role: "alert", children: error }),
716
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
717
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
718
+ /* @__PURE__ */ jsx(
719
+ PasswordInput,
720
+ {
721
+ label: t("newPasswordLabel"),
722
+ value: password,
723
+ onChange: (event) => setPassword(event.target.value),
724
+ placeholder: t("newPasswordPlaceholder"),
725
+ icon: /* @__PURE__ */ jsx(LockClosedIcon, { className: "h-5 w-5" }),
726
+ autoComplete: "new-password",
727
+ disabled: isSubmitting,
728
+ required: true,
729
+ autoFocus: true
730
+ }
731
+ ),
732
+ /* @__PURE__ */ jsx(
733
+ PasswordStrengthMeter,
734
+ {
735
+ password,
736
+ policy: passwordPolicy,
737
+ showRequirements: true
738
+ }
739
+ )
740
+ ] }),
741
+ /* @__PURE__ */ jsx(
742
+ PasswordInput,
743
+ {
744
+ label: t("confirmPasswordLabel"),
745
+ value: confirmPassword,
746
+ onChange: (event) => setConfirmPassword(event.target.value),
747
+ placeholder: t("confirmPasswordPlaceholder"),
748
+ icon: /* @__PURE__ */ jsx(LockClosedIcon, { className: "h-5 w-5" }),
749
+ autoComplete: "new-password",
750
+ disabled: isSubmitting,
751
+ error: hasPasswordMismatch ? t("passwordMismatchError") : void 0,
752
+ success: passwordsMatch,
753
+ required: true
754
+ }
755
+ ),
756
+ /* @__PURE__ */ jsx(
757
+ Button,
758
+ {
759
+ type: "submit",
760
+ color: "ios-glass-blue",
761
+ fullWidth: true,
762
+ disabled: isSubmitting || !passwordsMatch,
763
+ loading: isSubmitting,
764
+ children: t("resetPasswordButton")
765
+ }
766
+ )
767
+ ] })
768
+ ] });
769
+ const activeContent = phase === "success" ? successContent : formContent;
770
+ if (embedded) return activeContent;
771
+ return /* @__PURE__ */ jsx(Card, { variant: "glass", className: "mx-auto max-w-md p-6 sm:p-8", children: activeContent });
772
+ }
773
+ function PasswordlessSignIn({
774
+ defaultMethod = "email",
775
+ allowedChannels = ["email", "sms"],
776
+ onSuccess,
777
+ onBack,
778
+ defaultEmail = "",
779
+ embedded = false
780
+ }) {
781
+ const t = useTranslations("windsock");
782
+ const { client } = useAuth();
783
+ const [step, setStep] = useState("input");
784
+ const [channel, setChannel] = useState(
785
+ allowedChannels.includes(defaultMethod) ? defaultMethod : allowedChannels[0] ?? "email"
786
+ );
787
+ const [identifier, setIdentifier] = useState(defaultEmail);
788
+ const [code, setCode] = useState("");
789
+ const [error, setError] = useState(null);
790
+ const [isSending, setIsSending] = useState(false);
791
+ const [isVerifying, setIsVerifying] = useState(false);
792
+ const channelSegments = allowedChannels.map((ch) => ({
793
+ value: ch,
794
+ label: ch === "email" ? t("channelEmail") : t("channelSms")
795
+ }));
796
+ const handleSendCode = useCallback(async () => {
797
+ if (!identifier.trim()) return;
798
+ setError(null);
799
+ setIsSending(true);
800
+ try {
801
+ await client.sendPasswordlessCode(channel, identifier.trim());
802
+ setStep("sent");
803
+ setTimeout(() => setStep("verify"), 1500);
804
+ } catch {
805
+ setError(t("passwordlessSendError"));
806
+ } finally {
807
+ setIsSending(false);
808
+ }
809
+ }, [client, channel, identifier, t]);
810
+ const handleVerifyCode = useCallback(
811
+ async (completedCode) => {
812
+ setError(null);
813
+ setIsVerifying(true);
814
+ try {
815
+ await client.verifyPasswordlessCode(channel, identifier.trim(), completedCode);
816
+ onSuccess?.();
817
+ } catch {
818
+ setError(t("passwordlessVerifyError"));
819
+ setCode("");
820
+ } finally {
821
+ setIsVerifying(false);
822
+ }
823
+ },
824
+ [client, channel, identifier, onSuccess, t]
825
+ );
826
+ const inputContent = /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
827
+ /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
828
+ /* @__PURE__ */ jsx("h2", { className: "text-2xl font-bold text-gray-900 dark:text-white", children: t("passwordlessTitle") }),
829
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: t("passwordlessSubtitle") })
830
+ ] }),
831
+ allowedChannels.length > 1 && /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx(
832
+ SegmentedControl,
833
+ {
834
+ segments: channelSegments,
835
+ value: channel,
836
+ onChange: (value) => {
837
+ setChannel(value);
838
+ setIdentifier(value === "email" ? defaultEmail : "");
839
+ setError(null);
840
+ },
841
+ size: "sm"
842
+ }
843
+ ) }),
844
+ error && /* @__PURE__ */ jsx("div", { className: "rounded-xl bg-red-50 dark:bg-red-900/20 px-4 py-3 text-sm text-ios-red", role: "alert", children: error }),
845
+ /* @__PURE__ */ jsx(
846
+ Input,
847
+ {
848
+ label: channel === "email" ? t("emailLabel") : t("phoneLabel"),
849
+ type: channel === "email" ? "email" : "tel",
850
+ value: identifier,
851
+ onChange: (event) => setIdentifier(event.target.value),
852
+ placeholder: channel === "email" ? t("emailPlaceholder") : t("phonePlaceholder"),
853
+ icon: channel === "email" ? /* @__PURE__ */ jsx(EnvelopeIcon, { className: "h-5 w-5" }) : /* @__PURE__ */ jsx(DevicePhoneMobileIcon, { className: "h-5 w-5" }),
854
+ autoComplete: channel === "email" ? "email" : "tel",
855
+ disabled: isSending,
856
+ autoFocus: true
857
+ }
858
+ ),
859
+ /* @__PURE__ */ jsx(
860
+ Button,
861
+ {
862
+ type: "button",
863
+ color: "ios-glass-blue",
864
+ fullWidth: true,
865
+ onClick: handleSendCode,
866
+ disabled: isSending || !identifier.trim(),
867
+ loading: isSending,
868
+ children: t("passwordlessSendButton")
869
+ }
870
+ ),
871
+ onBack && /* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsx(
872
+ "button",
873
+ {
874
+ type: "button",
875
+ onClick: onBack,
876
+ className: "text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors",
877
+ children: t("backToSignIn")
878
+ }
879
+ ) })
880
+ ] });
881
+ const sentContent = /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
882
+ /* @__PURE__ */ jsx(
883
+ EmptyState,
884
+ {
885
+ variant: "minimal",
886
+ icon: PaperAirplaneIcon,
887
+ message: t("passwordlessCodeSentTitle"),
888
+ description: channel === "email" ? t("passwordlessCodeSentEmailDescription", { email: identifier }) : t("passwordlessCodeSentSmsDescription", { phone: identifier })
889
+ }
890
+ ),
891
+ /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx(Spinner, {}) })
892
+ ] });
893
+ const verifyContent = /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
894
+ /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
895
+ /* @__PURE__ */ jsx("h2", { className: "text-xl font-bold text-gray-900 dark:text-white", children: t("passwordlessEnterCodeTitle") }),
896
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: channel === "email" ? t("passwordlessEnterCodeEmailSubtitle", { email: identifier }) : t("passwordlessEnterCodeSmsSubtitle", { phone: identifier }) })
897
+ ] }),
898
+ error && /* @__PURE__ */ jsx("div", { className: "rounded-xl bg-red-50 dark:bg-red-900/20 px-4 py-3 text-sm text-ios-red text-center", role: "alert", children: error }),
899
+ /* @__PURE__ */ jsx(
900
+ OtpInput,
901
+ {
902
+ value: code,
903
+ onChange: setCode,
904
+ onComplete: handleVerifyCode,
905
+ error: error ?? void 0,
906
+ disabled: isVerifying,
907
+ autoFocus: true
908
+ }
909
+ ),
910
+ isVerifying && /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx(Spinner, {}) }),
911
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-sm", children: [
912
+ /* @__PURE__ */ jsx(
913
+ "button",
914
+ {
915
+ type: "button",
916
+ onClick: () => {
917
+ setStep("input");
918
+ setCode("");
919
+ setError(null);
920
+ },
921
+ className: "text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors",
922
+ children: t("passwordlessChangeIdentifier")
923
+ }
924
+ ),
925
+ /* @__PURE__ */ jsx(
926
+ "button",
927
+ {
928
+ type: "button",
929
+ onClick: () => {
930
+ setCode("");
931
+ setError(null);
932
+ handleSendCode();
933
+ },
934
+ disabled: isSending,
935
+ className: "font-medium text-ios-blue hover:text-blue-700 dark:hover:text-blue-400 transition-colors disabled:opacity-50",
936
+ children: t("resendEmailButton")
937
+ }
938
+ )
939
+ ] })
940
+ ] });
941
+ const activeContent = step === "input" ? inputContent : step === "sent" ? sentContent : verifyContent;
942
+ if (embedded) return activeContent;
943
+ return /* @__PURE__ */ jsx(Card, { variant: "glass", className: "mx-auto max-w-md p-6 sm:p-8", children: activeContent });
944
+ }
945
+ function ConsentScreen({
946
+ application,
947
+ scopes,
948
+ grantedScopes,
949
+ onApprove,
950
+ onDeny,
951
+ isSubmitting = false
952
+ }) {
953
+ const t = useTranslations("windsock");
954
+ const [checkedScopes, setCheckedScopes] = useState(
955
+ () => new Set(grantedScopes && grantedScopes.length > 0 ? grantedScopes : scopes.map((s) => s.id))
956
+ );
957
+ const handleScopeToggle = useCallback((scopeId, checked) => {
958
+ setCheckedScopes((previous) => {
959
+ const next = new Set(previous);
960
+ if (checked) {
961
+ next.add(scopeId);
962
+ } else {
963
+ next.delete(scopeId);
964
+ }
965
+ return next;
966
+ });
967
+ }, []);
968
+ const handleApprove = useCallback(() => {
969
+ triggerHaptic("light");
970
+ onApprove(Array.from(checkedScopes));
971
+ }, [checkedScopes, onApprove]);
972
+ const handleDeny = useCallback(() => {
973
+ triggerHaptic("light");
974
+ onDeny();
975
+ }, [onDeny]);
976
+ const appInitials = application.name.split(" ").map((word) => word[0]).join("").slice(0, 2).toUpperCase();
977
+ return /* @__PURE__ */ jsx(AuthLayout, { children: /* @__PURE__ */ jsx(Card, { variant: "glass", className: "mx-auto w-full max-w-md", children: /* @__PURE__ */ jsxs(CardContent, { className: "space-y-6 p-6 sm:p-8", children: [
978
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3 text-center", children: [
979
+ /* @__PURE__ */ jsx(
980
+ Avatar,
981
+ {
982
+ src: application.logoUrl,
983
+ initials: appInitials,
984
+ alt: application.name
985
+ }
986
+ ),
987
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
988
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2", children: [
989
+ /* @__PURE__ */ jsx("span", { className: "text-lg font-semibold text-gray-900 dark:text-white", children: application.name }),
990
+ application.verified ? /* @__PURE__ */ jsx(Badge, { color: "green", children: t("consent.verified") }) : /* @__PURE__ */ jsx(Badge, { color: "amber", children: t("consent.unverified") })
991
+ ] }),
992
+ application.url && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: application.url }),
993
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600 dark:text-gray-300", children: t("consent.wantsAccess") })
994
+ ] })
995
+ ] }),
996
+ !application.verified && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3 rounded-xl border border-amber-500/30 bg-amber-500/10 p-3", children: [
997
+ /* @__PURE__ */ jsx(ShieldExclamationIcon, { className: "mt-0.5 h-5 w-5 shrink-0 text-amber-600 dark:text-amber-400" }),
998
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-amber-700 dark:text-amber-300", children: t("consent.unverifiedWarning", { name: application.name }) })
999
+ ] }),
1000
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1001
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400", children: t("consent.permissionsLabel") }),
1002
+ /* @__PURE__ */ jsx(ListCard, { children: scopes.map((scope) => /* @__PURE__ */ jsx(
1003
+ ListCardItem,
1004
+ {
1005
+ leading: /* @__PURE__ */ jsx(
1006
+ FormCheckbox,
1007
+ {
1008
+ checked: checkedScopes.has(scope.id),
1009
+ onChange: (checked) => handleScopeToggle(scope.id, checked),
1010
+ label: ""
1011
+ }
1012
+ ),
1013
+ onClick: () => handleScopeToggle(scope.id, !checkedScopes.has(scope.id)),
1014
+ children: /* @__PURE__ */ jsxs("div", { children: [
1015
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-900 dark:text-white", children: scope.name }),
1016
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: scope.description })
1017
+ ] })
1018
+ },
1019
+ scope.id
1020
+ )) })
1021
+ ] }),
1022
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 sm:flex-row-reverse", children: [
1023
+ /* @__PURE__ */ jsx(
1024
+ Button,
1025
+ {
1026
+ color: "ios-glass-blue",
1027
+ fullWidth: true,
1028
+ onClick: handleApprove,
1029
+ loading: isSubmitting,
1030
+ disabled: isSubmitting || checkedScopes.size === 0,
1031
+ children: t("consent.approveButton")
1032
+ }
1033
+ ),
1034
+ /* @__PURE__ */ jsx(
1035
+ Button,
1036
+ {
1037
+ outline: true,
1038
+ fullWidth: true,
1039
+ onClick: handleDeny,
1040
+ disabled: isSubmitting,
1041
+ children: t("consent.denyButton")
1042
+ }
1043
+ )
1044
+ ] }),
1045
+ /* @__PURE__ */ jsx("p", { className: "text-center text-xs text-gray-400 dark:text-gray-500", children: t("consent.footerNote") })
1046
+ ] }) }) });
1047
+ }
1048
+ function SsoConnectorList({ connectors, onSelect, loading = false }) {
1049
+ const t = useTranslations("windsock");
1050
+ if (loading) {
1051
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2 py-8 text-sm text-gray-500 dark:text-gray-400", children: [
1052
+ /* @__PURE__ */ jsx(InlineSpinner, {}),
1053
+ /* @__PURE__ */ jsx("span", { children: t("sso.connectorList.loading") })
1054
+ ] });
1055
+ }
1056
+ if (connectors.length === 0) {
1057
+ return /* @__PURE__ */ jsx("p", { className: "py-6 text-center text-sm text-gray-500 dark:text-gray-400", children: t("sso.connectorList.empty") });
1058
+ }
1059
+ return /* @__PURE__ */ jsx(ListCard, { children: connectors.map((connector) => {
1060
+ const initials = connector.name.split(" ").map((word) => word[0]).join("").slice(0, 2).toUpperCase();
1061
+ return /* @__PURE__ */ jsx(
1062
+ ListCardItem,
1063
+ {
1064
+ leading: /* @__PURE__ */ jsx(
1065
+ Avatar,
1066
+ {
1067
+ src: connector.logoUrl,
1068
+ initials,
1069
+ alt: connector.name
1070
+ }
1071
+ ),
1072
+ trailing: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1073
+ /* @__PURE__ */ jsx(Badge, { color: "zinc", children: connector.type === "saml" ? t("sso.connectorList.typeSaml") : t("sso.connectorList.typeOidc") }),
1074
+ /* @__PURE__ */ jsx(ChevronRightIcon, { className: "h-4 w-4 text-gray-400" })
1075
+ ] }),
1076
+ onClick: () => onSelect(connector),
1077
+ children: /* @__PURE__ */ jsxs("div", { children: [
1078
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-900 dark:text-white", children: connector.name }),
1079
+ connector.domain && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: connector.domain })
1080
+ ] })
1081
+ },
1082
+ connector.id
1083
+ );
1084
+ }) });
1085
+ }
1086
+ function SsoEmailForm({
1087
+ onEmailSubmit,
1088
+ onConnectorSelect,
1089
+ defaultEmail = "",
1090
+ embedded = false
1091
+ }) {
1092
+ const t = useTranslations("windsock");
1093
+ const [phase, setPhase] = useState("email");
1094
+ const [email, setEmail] = useState(defaultEmail);
1095
+ const [emailError, setEmailError] = useState(null);
1096
+ const [connectors, setConnectors] = useState([]);
1097
+ const [isSubmitting, setIsSubmitting] = useState(false);
1098
+ const [loadingConnectors, setLoadingConnectors] = useState(false);
1099
+ const validateEmail = useCallback(
1100
+ (value) => {
1101
+ if (!value.trim()) {
1102
+ setEmailError(t("sso.emailForm.errorEmailRequired"));
1103
+ return false;
1104
+ }
1105
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
1106
+ setEmailError(t("sso.emailForm.errorEmailInvalid"));
1107
+ return false;
1108
+ }
1109
+ setEmailError(null);
1110
+ return true;
1111
+ },
1112
+ [t]
1113
+ );
1114
+ const handleEmailSubmit = useCallback(async () => {
1115
+ if (!validateEmail(email)) return;
1116
+ setIsSubmitting(true);
1117
+ setLoadingConnectors(true);
1118
+ try {
1119
+ const discovered = await onEmailSubmit(email);
1120
+ setConnectors(discovered);
1121
+ setPhase("connectors");
1122
+ } catch {
1123
+ setEmailError(t("sso.emailForm.errorDiscoveryFailed"));
1124
+ } finally {
1125
+ setIsSubmitting(false);
1126
+ setLoadingConnectors(false);
1127
+ }
1128
+ }, [email, onEmailSubmit, validateEmail, t]);
1129
+ const handleBackToEmail = useCallback(() => {
1130
+ setPhase("email");
1131
+ setConnectors([]);
1132
+ setEmailError(null);
1133
+ }, []);
1134
+ if (phase === "connectors") {
1135
+ const connectorsContent = /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1136
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1137
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-900 dark:text-white", children: t("sso.emailForm.connectorsTitle") }),
1138
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: t("sso.emailForm.connectorsSubtitle", { email }) })
1139
+ ] }),
1140
+ /* @__PURE__ */ jsx(
1141
+ SsoConnectorList,
1142
+ {
1143
+ connectors,
1144
+ onSelect: onConnectorSelect,
1145
+ loading: loadingConnectors
1146
+ }
1147
+ ),
1148
+ /* @__PURE__ */ jsx("div", { className: "text-center", children: /* @__PURE__ */ jsx(
1149
+ "button",
1150
+ {
1151
+ type: "button",
1152
+ onClick: handleBackToEmail,
1153
+ className: "text-sm text-gray-500 transition-colors hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200",
1154
+ children: t("sso.emailForm.backButton")
1155
+ }
1156
+ ) })
1157
+ ] });
1158
+ if (embedded) return connectorsContent;
1159
+ return /* @__PURE__ */ jsx(Card, { variant: "glass", className: "mx-auto w-full max-w-md", children: /* @__PURE__ */ jsx(CardContent, { className: "p-6 sm:p-8", children: connectorsContent }) });
1160
+ }
1161
+ const emailContent = /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1162
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1163
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-900 dark:text-white", children: t("sso.emailForm.title") }),
1164
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: t("sso.emailForm.subtitle") })
1165
+ ] }),
1166
+ /* @__PURE__ */ jsxs(BaseForm, { onSubmit: handleEmailSubmit, submitLabel: t("sso.emailForm.continueButton"), showFooter: false, children: [
1167
+ /* @__PURE__ */ jsx(
1168
+ Input,
1169
+ {
1170
+ type: "email",
1171
+ label: t("sso.emailForm.emailLabel"),
1172
+ placeholder: t("sso.emailForm.emailPlaceholder"),
1173
+ value: email,
1174
+ onChange: (event) => {
1175
+ setEmail(event.target.value);
1176
+ if (emailError) setEmailError(null);
1177
+ },
1178
+ error: emailError ?? void 0,
1179
+ icon: /* @__PURE__ */ jsx(BuildingOffice2Icon, { className: "h-5 w-5" }),
1180
+ disabled: isSubmitting,
1181
+ autoFocus: true,
1182
+ autoComplete: "email"
1183
+ }
1184
+ ),
1185
+ /* @__PURE__ */ jsx(
1186
+ Button,
1187
+ {
1188
+ type: "submit",
1189
+ color: "ios-glass-blue",
1190
+ fullWidth: true,
1191
+ loading: isSubmitting,
1192
+ disabled: isSubmitting,
1193
+ children: t("sso.emailForm.continueButton")
1194
+ }
1195
+ )
1196
+ ] })
1197
+ ] });
1198
+ if (embedded) return emailContent;
1199
+ return /* @__PURE__ */ jsx(Card, { variant: "glass", className: "mx-auto w-full max-w-md", children: /* @__PURE__ */ jsx(CardContent, { className: "p-6 sm:p-8", children: emailContent }) });
1200
+ }
1201
+ var DEFAULT_LABELS = {
1202
+ menuLabel: "User menu",
1203
+ userAlt: "User",
1204
+ signOut: "Sign out",
1205
+ signOutConfirmTitle: "Sign out?",
1206
+ signOutConfirmButton: "Sign out",
1207
+ signOutCancelButton: "Cancel"
1208
+ };
1209
+ function UserButton({ menuItems = [], onSignOut, avatarUrl, labels }) {
1210
+ const resolvedLabels = { ...DEFAULT_LABELS, ...labels };
1211
+ const { user, logout, status } = useAuth();
1212
+ const [confirmSignOut, setConfirmSignOut] = useState(false);
1213
+ if (status !== "authenticated") {
1214
+ return null;
1215
+ }
1216
+ const userInitials = user?.name ? user.name.split(" ").map((word) => word[0]).join("").slice(0, 2).toUpperCase() : user?.email?.slice(0, 2).toUpperCase() ?? "??";
1217
+ const resolvedAvatarUrl = avatarUrl ?? user?.image;
1218
+ const handleSignOutRequest = () => {
1219
+ triggerHaptic("light");
1220
+ setConfirmSignOut(true);
1221
+ };
1222
+ const handleSignOutConfirm = async () => {
1223
+ triggerHaptic("success");
1224
+ setConfirmSignOut(false);
1225
+ try {
1226
+ onSignOut?.();
1227
+ await logout("/");
1228
+ } catch {
1229
+ }
1230
+ };
1231
+ const handleSignOutCancel = () => {
1232
+ triggerHaptic("light");
1233
+ setConfirmSignOut(false);
1234
+ };
1235
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1236
+ /* @__PURE__ */ jsxs(Menu, { as: "div", className: "relative inline-block text-left", children: [
1237
+ /* @__PURE__ */ jsxs(
1238
+ Menu.Button,
1239
+ {
1240
+ className: "flex items-center gap-2 rounded-full p-1 transition-opacity hover:opacity-80 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500",
1241
+ "aria-label": resolvedLabels.menuLabel,
1242
+ children: [
1243
+ /* @__PURE__ */ jsx(
1244
+ Avatar,
1245
+ {
1246
+ src: resolvedAvatarUrl,
1247
+ initials: userInitials,
1248
+ alt: user?.name ?? user?.email ?? resolvedLabels.userAlt
1249
+ }
1250
+ ),
1251
+ /* @__PURE__ */ jsx(ChevronUpDownIcon, { className: "h-4 w-4 text-gray-500 dark:text-gray-400" })
1252
+ ]
1253
+ }
1254
+ ),
1255
+ /* @__PURE__ */ jsx(
1256
+ Transition,
1257
+ {
1258
+ as: Fragment$1,
1259
+ enter: "transition ease-out duration-100",
1260
+ enterFrom: "transform opacity-0 scale-95",
1261
+ enterTo: "transform opacity-100 scale-100",
1262
+ leave: "transition ease-in duration-75",
1263
+ leaveFrom: "transform opacity-100 scale-100",
1264
+ leaveTo: "transform opacity-0 scale-95",
1265
+ children: /* @__PURE__ */ jsxs(Menu.Items, { className: "liquid-surface absolute right-0 z-50 mt-2 w-60 origin-top-right rounded-2xl shadow-lg ring-1 ring-black/5 focus:outline-none dark:ring-white/10", children: [
1266
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 border-b border-gray-200/60 px-4 py-3 dark:border-white/10", children: [
1267
+ /* @__PURE__ */ jsx(
1268
+ Avatar,
1269
+ {
1270
+ src: resolvedAvatarUrl,
1271
+ initials: userInitials,
1272
+ alt: user?.name ?? user?.email ?? resolvedLabels.userAlt
1273
+ }
1274
+ ),
1275
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1276
+ user?.name && /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-gray-900 dark:text-white", children: user.name }),
1277
+ user?.email && /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-gray-500 dark:text-gray-400", children: user.email })
1278
+ ] })
1279
+ ] }),
1280
+ menuItems.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
1281
+ /* @__PURE__ */ jsx("div", { className: "px-1 py-1", children: menuItems.map((item, index) => /* @__PURE__ */ jsx(Menu.Item, { children: ({ active }) => /* @__PURE__ */ jsxs(
1282
+ "button",
1283
+ {
1284
+ type: "button",
1285
+ onClick: () => {
1286
+ triggerHaptic("light");
1287
+ item.onClick();
1288
+ },
1289
+ className: `flex w-full items-center gap-2 rounded-xl px-3 py-2 text-sm transition-colors ${active ? "bg-gray-100 text-gray-900 dark:bg-white/10 dark:text-white" : "text-gray-700 dark:text-gray-300"}`,
1290
+ children: [
1291
+ item.icon ? /* @__PURE__ */ jsx("span", { className: "h-4 w-4 shrink-0 text-gray-400", children: item.icon }) : null,
1292
+ item.label
1293
+ ]
1294
+ }
1295
+ ) }, index)) }),
1296
+ /* @__PURE__ */ jsx("div", { className: "border-t border-gray-200/60 dark:border-white/10" })
1297
+ ] }),
1298
+ /* @__PURE__ */ jsx("div", { className: "border-t border-gray-200/60 px-1 py-1 dark:border-white/10", children: /* @__PURE__ */ jsx(Menu.Item, { children: ({ active }) => /* @__PURE__ */ jsxs(
1299
+ "button",
1300
+ {
1301
+ type: "button",
1302
+ onClick: handleSignOutRequest,
1303
+ className: `flex w-full items-center gap-2 rounded-xl px-3 py-2 text-sm transition-colors ${active ? "bg-red-50 text-red-700 dark:bg-red-500/10 dark:text-red-400" : "text-red-600 dark:text-red-400"}`,
1304
+ children: [
1305
+ /* @__PURE__ */ jsx(ArrowRightStartOnRectangleIcon, { className: "h-4 w-4 shrink-0" }),
1306
+ resolvedLabels.signOut
1307
+ ]
1308
+ }
1309
+ ) }) })
1310
+ ] })
1311
+ }
1312
+ )
1313
+ ] }),
1314
+ /* @__PURE__ */ jsx(
1315
+ DynamicIslandConfirm,
1316
+ {
1317
+ open: confirmSignOut,
1318
+ onClose: handleSignOutCancel,
1319
+ onConfirm: handleSignOutConfirm,
1320
+ title: resolvedLabels.signOutConfirmTitle,
1321
+ confirmLabel: resolvedLabels.signOutConfirmButton,
1322
+ cancelLabel: resolvedLabels.signOutCancelButton
1323
+ }
1324
+ )
1325
+ ] });
1326
+ }
1327
+
1328
+ export { ConsentScreen, ForgotPassword, MfaChallenge, PasswordlessSignIn, ResetPassword, SignIn, SignUp, SsoConnectorList, SsoEmailForm, UserButton, VerifyEmail };
1329
+ //# sourceMappingURL=chunk-5HXDJBVX.mjs.map
1330
+ //# sourceMappingURL=chunk-5HXDJBVX.mjs.map