@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,1021 @@
1
+ "use client";
2
+ import { OtpInput, BackupCodeGrid, PasswordStrengthMeter } from './chunk-NBCOVUQP.mjs';
3
+ import { StepFormPage, OptionGrid, Spinner, FormCheckbox, SectionCard, EmptyState, ListCard, ListCardItem, StatusBadge, Button, Sheet, PasswordInput, BaseForm, Avatar, InlineSpinner, Input, Badge, SegmentedControl } from './chunk-ZJQ5RLGK.mjs';
4
+ import { triggerHaptic } from './chunk-D2JF6C3E.mjs';
5
+ import { useTranslations, useFormatter, useLocale } from './chunk-7VJ7CMMT.mjs';
6
+ import { useState, useCallback, useRef, useEffect } from 'react';
7
+ import { ShieldCheckIcon, PlusIcon, ArrowPathIcon, CameraIcon, UserIcon, CheckCircleIcon, GlobeAltIcon, ComputerDesktopIcon, LinkIcon } from '@heroicons/react/24/outline';
8
+ import { useAuth } from '@datatechsolutions/windsock/client';
9
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
10
+ import { ShieldExclamationIcon } from '@heroicons/react/20/solid';
11
+ import * as sharedI18n from '@datatechsolutions/shared-domain/i18n';
12
+
13
+ function MfaSetup({
14
+ onSuccess,
15
+ onCancel,
16
+ enforcement = "optional",
17
+ onGenerateSecret,
18
+ onVerifySetup
19
+ }) {
20
+ const t = useTranslations("windsock");
21
+ const { client } = useAuth();
22
+ const setupSteps = [
23
+ {
24
+ id: "method",
25
+ title: t("mfa.setup.stepMethodTitle"),
26
+ description: t("mfa.setup.stepMethodDescription")
27
+ },
28
+ {
29
+ id: "scan",
30
+ title: t("mfa.setup.stepScanTitle"),
31
+ description: t("mfa.setup.stepScanDescription")
32
+ },
33
+ {
34
+ id: "verify",
35
+ title: t("mfa.setup.stepVerifyTitle"),
36
+ description: t("mfa.setup.stepVerifyDescription")
37
+ },
38
+ {
39
+ id: "backup",
40
+ title: t("mfa.setup.stepBackupTitle"),
41
+ description: t("mfa.setup.stepBackupDescription")
42
+ }
43
+ ];
44
+ const [currentStep, setCurrentStep] = useState(0);
45
+ const [selectedMethod, setSelectedMethod] = useState("totp");
46
+ const [qrCodeUri, setQrCodeUri] = useState("");
47
+ const [secret, setSecret] = useState("");
48
+ const [verifyCode, setVerifyCode] = useState("");
49
+ const [verifyError, setVerifyError] = useState(null);
50
+ const [backupCodes, setBackupCodes] = useState([]);
51
+ const [savedBackupCodes, setSavedBackupCodes] = useState(false);
52
+ const [isLoading, setIsLoading] = useState(false);
53
+ const handleStepChange = useCallback(
54
+ async (step) => {
55
+ if (step === 1 && currentStep === 0) {
56
+ setIsLoading(true);
57
+ try {
58
+ if (onGenerateSecret) {
59
+ const result = await onGenerateSecret();
60
+ setSecret(result.secret);
61
+ setQrCodeUri(result.qrCodeUri);
62
+ } else {
63
+ const result = await client.generateMfaSecret();
64
+ setSecret(result.secret);
65
+ setQrCodeUri(result.qrCode);
66
+ setBackupCodes(result.backupCodes);
67
+ }
68
+ } catch {
69
+ return;
70
+ } finally {
71
+ setIsLoading(false);
72
+ }
73
+ }
74
+ triggerHaptic("light");
75
+ setCurrentStep(step);
76
+ },
77
+ [currentStep, onGenerateSecret, client]
78
+ );
79
+ const handleMethodSelect = useCallback((method) => {
80
+ triggerHaptic("light");
81
+ setSelectedMethod(method);
82
+ }, []);
83
+ const handleVerifyComplete = useCallback(
84
+ async (code) => {
85
+ setVerifyError(null);
86
+ setIsLoading(true);
87
+ try {
88
+ if (onVerifySetup) {
89
+ const result = await onVerifySetup(code);
90
+ if (result.success) {
91
+ triggerHaptic("success");
92
+ if (result.backupCodes?.length) {
93
+ setBackupCodes(result.backupCodes);
94
+ }
95
+ setCurrentStep(3);
96
+ } else {
97
+ triggerHaptic("error");
98
+ setVerifyError(t("mfa.setup.errorInvalidCode"));
99
+ }
100
+ } else {
101
+ const result = await client.verifyMfaSetup(code);
102
+ if (result.success) {
103
+ triggerHaptic("success");
104
+ setCurrentStep(3);
105
+ } else {
106
+ triggerHaptic("error");
107
+ setVerifyError(t("mfa.setup.errorInvalidCode"));
108
+ }
109
+ }
110
+ } catch {
111
+ triggerHaptic("error");
112
+ setVerifyError(t("mfa.setup.errorVerificationFailed"));
113
+ } finally {
114
+ setIsLoading(false);
115
+ }
116
+ },
117
+ [onVerifySetup, client, t]
118
+ );
119
+ const handleSubmit = useCallback(() => {
120
+ triggerHaptic("success");
121
+ onSuccess?.();
122
+ }, [onSuccess]);
123
+ const handleCancel = useCallback(() => {
124
+ triggerHaptic("light");
125
+ onCancel?.();
126
+ }, [onCancel]);
127
+ const canAdvance = (() => {
128
+ switch (currentStep) {
129
+ case 0:
130
+ return !!selectedMethod;
131
+ case 1:
132
+ return !!qrCodeUri;
133
+ case 2:
134
+ return false;
135
+ // Advance via OTP complete callback
136
+ case 3:
137
+ return savedBackupCodes;
138
+ default:
139
+ return false;
140
+ }
141
+ })();
142
+ return /* @__PURE__ */ jsxs(
143
+ StepFormPage,
144
+ {
145
+ title: t("mfa.setup.title"),
146
+ subtitle: enforcement === "required" ? t("mfa.setup.subtitleRequired") : t("mfa.setup.subtitleOptional"),
147
+ label: t("mfa.setup.label"),
148
+ icon: /* @__PURE__ */ jsx(ShieldCheckIcon, { className: "h-5 w-5" }),
149
+ gradient: "from-emerald-500 via-teal-500 to-cyan-500",
150
+ steps: setupSteps,
151
+ currentStep,
152
+ onStepChange: handleStepChange,
153
+ onSubmit: handleSubmit,
154
+ onCancel: handleCancel,
155
+ isSubmitting: isLoading,
156
+ submitLabel: t("mfa.setup.submitLabel"),
157
+ canAdvance,
158
+ children: [
159
+ currentStep === 0 && /* @__PURE__ */ jsx(
160
+ OptionGrid,
161
+ {
162
+ icon: /* @__PURE__ */ jsx(ShieldCheckIcon, { className: "h-4 w-4" }),
163
+ title: t("mfa.setup.methodGridTitle"),
164
+ options: [
165
+ {
166
+ code: "totp",
167
+ flag: "\u{1F4F1}",
168
+ label: t("mfa.setup.methodAuthenticatorApp")
169
+ }
170
+ ],
171
+ selected: selectedMethod,
172
+ onSelect: handleMethodSelect,
173
+ columns: 2
174
+ }
175
+ ),
176
+ currentStep === 1 && /* @__PURE__ */ jsx("div", { className: "space-y-4", children: isLoading ? /* @__PURE__ */ jsx("div", { className: "flex justify-center py-8", children: /* @__PURE__ */ jsx(Spinner, {}) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
177
+ qrCodeUri && /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx("div", { className: "rounded-xl bg-white p-4", children: /* @__PURE__ */ jsx(
178
+ "img",
179
+ {
180
+ src: qrCodeUri,
181
+ alt: t("mfa.setup.qrCodeAlt"),
182
+ className: "h-48 w-48"
183
+ }
184
+ ) }) }),
185
+ secret && /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
186
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mb-1", children: t("mfa.setup.manualSecretLabel") }),
187
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-sm font-medium text-gray-900 dark:text-white tracking-widest", children: secret })
188
+ ] })
189
+ ] }) }),
190
+ currentStep === 2 && /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
191
+ /* @__PURE__ */ jsx(
192
+ OtpInput,
193
+ {
194
+ value: verifyCode,
195
+ onChange: setVerifyCode,
196
+ onComplete: handleVerifyComplete,
197
+ error: verifyError ?? void 0,
198
+ disabled: isLoading,
199
+ autoFocus: true
200
+ }
201
+ ),
202
+ isLoading && /* @__PURE__ */ jsx("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx(Spinner, {}) })
203
+ ] }),
204
+ currentStep === 3 && /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
205
+ /* @__PURE__ */ jsx(
206
+ BackupCodeGrid,
207
+ {
208
+ codes: backupCodes,
209
+ revealed: true
210
+ }
211
+ ),
212
+ /* @__PURE__ */ jsx(
213
+ FormCheckbox,
214
+ {
215
+ label: t("mfa.setup.backupCodesSavedConfirm"),
216
+ checked: savedBackupCodes,
217
+ onChange: setSavedBackupCodes
218
+ }
219
+ )
220
+ ] })
221
+ ]
222
+ }
223
+ );
224
+ }
225
+ function MfaManage({
226
+ mfaEnabled,
227
+ enrolledMethods = [],
228
+ onEnroll,
229
+ onDisable,
230
+ onRegenerateBackupCodes
231
+ }) {
232
+ const t = useTranslations("windsock");
233
+ const format = useFormatter();
234
+ const { client } = useAuth();
235
+ const [isBackupSheetOpen, setIsBackupSheetOpen] = useState(false);
236
+ const [newBackupCodes, setNewBackupCodes] = useState([]);
237
+ const [isRegenerating, setIsRegenerating] = useState(false);
238
+ const [isConfirmDisableOpen, setIsConfirmDisableOpen] = useState(false);
239
+ const [disablePassword, setDisablePassword] = useState("");
240
+ const [disableError, setDisableError] = useState(null);
241
+ const [isDisabling, setIsDisabling] = useState(false);
242
+ const handleOpenBackupSheet = useCallback(async () => {
243
+ triggerHaptic("medium");
244
+ setIsRegenerating(true);
245
+ try {
246
+ const codes = onRegenerateBackupCodes ? await onRegenerateBackupCodes() : await client.regenerateBackupCodes();
247
+ setNewBackupCodes(codes);
248
+ setIsBackupSheetOpen(true);
249
+ } catch {
250
+ } finally {
251
+ setIsRegenerating(false);
252
+ }
253
+ }, [onRegenerateBackupCodes, client]);
254
+ const handleCloseBackupSheet = useCallback(() => {
255
+ triggerHaptic("light");
256
+ setIsBackupSheetOpen(false);
257
+ setNewBackupCodes([]);
258
+ }, []);
259
+ const handleRequestDisable = useCallback(() => {
260
+ triggerHaptic("warning");
261
+ setDisablePassword("");
262
+ setDisableError(null);
263
+ setIsConfirmDisableOpen(true);
264
+ }, []);
265
+ const handleConfirmDisable = useCallback(async () => {
266
+ if (!disablePassword.trim()) {
267
+ setDisableError(t("mfa.manage.disablePasswordRequired"));
268
+ triggerHaptic("error");
269
+ return;
270
+ }
271
+ setDisableError(null);
272
+ setIsDisabling(true);
273
+ triggerHaptic("medium");
274
+ try {
275
+ if (onDisable) {
276
+ onDisable();
277
+ } else {
278
+ const result = await client.disableMfa(disablePassword);
279
+ if (!result.success) {
280
+ setDisableError(result.error ?? t("mfa.manage.disableError"));
281
+ triggerHaptic("error");
282
+ return;
283
+ }
284
+ }
285
+ setIsConfirmDisableOpen(false);
286
+ triggerHaptic("success");
287
+ } catch {
288
+ setDisableError(t("mfa.manage.disableError"));
289
+ triggerHaptic("error");
290
+ } finally {
291
+ setIsDisabling(false);
292
+ }
293
+ }, [disablePassword, onDisable, client, t]);
294
+ const handleCancelDisable = useCallback(() => {
295
+ triggerHaptic("light");
296
+ setIsConfirmDisableOpen(false);
297
+ setDisablePassword("");
298
+ setDisableError(null);
299
+ }, []);
300
+ const handleEnroll = useCallback(() => {
301
+ triggerHaptic("light");
302
+ onEnroll?.();
303
+ }, [onEnroll]);
304
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
305
+ /* @__PURE__ */ jsx(
306
+ SectionCard,
307
+ {
308
+ header: {
309
+ icon: /* @__PURE__ */ jsx(ShieldCheckIcon, { className: "h-5 w-5 text-white" }),
310
+ title: t("mfa.manage.title"),
311
+ subtitle: t("mfa.manage.subtitle"),
312
+ gradient: "from-emerald-500 via-teal-500 to-cyan-500",
313
+ rightContent: mfaEnabled ? /* @__PURE__ */ jsx(StatusBadge, { status: "active", label: t("mfa.manage.statusEnabled"), size: "sm" }) : /* @__PURE__ */ jsx(StatusBadge, { status: "inactive", label: t("mfa.manage.statusDisabled"), size: "sm" })
314
+ },
315
+ padded: false,
316
+ children: !mfaEnabled ? (
317
+ /* MFA not enabled — show empty state with enable CTA */
318
+ /* @__PURE__ */ jsx("div", { className: "p-4 sm:p-6", children: /* @__PURE__ */ jsx(
319
+ EmptyState,
320
+ {
321
+ message: t("mfa.manage.emptyStateMessage"),
322
+ description: t("mfa.manage.emptyStateDescription"),
323
+ icon: ShieldExclamationIcon,
324
+ action: {
325
+ label: t("mfa.manage.enableButton"),
326
+ onClick: handleEnroll
327
+ },
328
+ variant: "card"
329
+ }
330
+ ) })
331
+ ) : (
332
+ /* MFA enabled — show enrolled methods list + action buttons */
333
+ /* @__PURE__ */ jsxs("div", { className: "space-y-4 p-4 sm:p-6", children: [
334
+ enrolledMethods.length > 0 && /* @__PURE__ */ jsx(ListCard, { children: enrolledMethods.map((method, index) => /* @__PURE__ */ jsxs(
335
+ ListCardItem,
336
+ {
337
+ leading: /* @__PURE__ */ jsx("div", { className: "h-9 w-9 rounded-xl bg-gradient-to-br from-emerald-500 to-teal-600 flex items-center justify-center shadow-sm", children: /* @__PURE__ */ jsx(ShieldCheckIcon, { className: "h-5 w-5 text-white" }) }),
338
+ trailing: /* @__PURE__ */ jsx(StatusBadge, { status: "active", label: t("mfa.manage.methodStatusActive"), size: "sm" }),
339
+ children: [
340
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-900 dark:text-white", children: method.label }),
341
+ method.createdAt && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-0.5", children: t("mfa.manage.methodEnrolledAt", {
342
+ date: format.dateTime(new Date(method.createdAt), {
343
+ year: "numeric",
344
+ month: "short",
345
+ day: "numeric"
346
+ })
347
+ }) })
348
+ ]
349
+ },
350
+ `${method.type}-${index}`
351
+ )) }),
352
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 sm:flex-row sm:gap-3", children: [
353
+ onEnroll && /* @__PURE__ */ jsxs(
354
+ Button,
355
+ {
356
+ color: "ios-glass-blue",
357
+ onClick: handleEnroll,
358
+ className: "flex-1",
359
+ children: [
360
+ /* @__PURE__ */ jsx(PlusIcon, { className: "h-4 w-4 mr-1.5" }),
361
+ t("mfa.manage.addMethodButton")
362
+ ]
363
+ }
364
+ ),
365
+ /* @__PURE__ */ jsxs(
366
+ Button,
367
+ {
368
+ outline: true,
369
+ onClick: handleOpenBackupSheet,
370
+ loading: isRegenerating,
371
+ disabled: isRegenerating,
372
+ className: "flex-1",
373
+ children: [
374
+ /* @__PURE__ */ jsx(ArrowPathIcon, { className: "h-4 w-4 mr-1.5" }),
375
+ t("mfa.manage.regenerateBackupCodesButton")
376
+ ]
377
+ }
378
+ ),
379
+ /* @__PURE__ */ jsx(
380
+ Button,
381
+ {
382
+ color: "ios-glass-red",
383
+ onClick: handleRequestDisable,
384
+ className: "flex-1",
385
+ children: t("mfa.manage.disableMfaButton")
386
+ }
387
+ )
388
+ ] })
389
+ ] })
390
+ )
391
+ }
392
+ ),
393
+ /* @__PURE__ */ jsx(
394
+ Sheet,
395
+ {
396
+ open: isBackupSheetOpen,
397
+ onClose: handleCloseBackupSheet,
398
+ title: t("mfa.manage.backupCodesSheetTitle"),
399
+ side: "bottom",
400
+ size: "md",
401
+ children: /* @__PURE__ */ jsxs("div", { className: "space-y-4 p-4 sm:p-6", children: [
402
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: t("mfa.manage.backupCodesSheetDescription") }),
403
+ /* @__PURE__ */ jsx(
404
+ BackupCodeGrid,
405
+ {
406
+ codes: newBackupCodes,
407
+ revealed: true
408
+ }
409
+ ),
410
+ /* @__PURE__ */ jsx(
411
+ Button,
412
+ {
413
+ color: "ios-glass-blue",
414
+ fullWidth: true,
415
+ onClick: handleCloseBackupSheet,
416
+ children: t("mfa.manage.backupCodesDoneButton")
417
+ }
418
+ )
419
+ ] })
420
+ }
421
+ ),
422
+ /* @__PURE__ */ jsx(
423
+ Sheet,
424
+ {
425
+ open: isConfirmDisableOpen,
426
+ onClose: handleCancelDisable,
427
+ title: t("mfa.manage.confirmDisableTitle"),
428
+ side: "bottom",
429
+ size: "md",
430
+ children: /* @__PURE__ */ jsxs("div", { className: "space-y-4 p-4 sm:p-6", children: [
431
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
432
+ /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-xl bg-ios-red shadow-sm", children: /* @__PURE__ */ jsx(ShieldExclamationIcon, { className: "h-5 w-5 text-white" }) }),
433
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: t("mfa.manage.confirmDisableDescription") })
434
+ ] }),
435
+ /* @__PURE__ */ jsx(
436
+ PasswordInput,
437
+ {
438
+ label: t("mfa.manage.disablePasswordLabel"),
439
+ value: disablePassword,
440
+ onChange: (event) => setDisablePassword(event.target.value),
441
+ placeholder: t("mfa.manage.disablePasswordPlaceholder"),
442
+ disabled: isDisabling,
443
+ autoFocus: true,
444
+ autoComplete: "current-password"
445
+ }
446
+ ),
447
+ disableError && /* @__PURE__ */ jsx("p", { className: "text-sm text-red-600 dark:text-red-400", role: "alert", children: disableError }),
448
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-3", children: [
449
+ /* @__PURE__ */ jsx(
450
+ Button,
451
+ {
452
+ outline: true,
453
+ onClick: handleCancelDisable,
454
+ disabled: isDisabling,
455
+ className: "flex-1",
456
+ children: t("mfa.manage.confirmDisableCancelLabel")
457
+ }
458
+ ),
459
+ /* @__PURE__ */ jsx(
460
+ Button,
461
+ {
462
+ color: "ios-glass-red",
463
+ onClick: handleConfirmDisable,
464
+ loading: isDisabling,
465
+ disabled: isDisabling || !disablePassword.trim(),
466
+ className: "flex-1",
467
+ children: t("mfa.manage.confirmDisableConfirmLabel")
468
+ }
469
+ )
470
+ ] })
471
+ ] })
472
+ }
473
+ )
474
+ ] });
475
+ }
476
+ var ALL_TABS = ["profile", "security", "sessions", "linked-accounts"];
477
+ function ProfileTabContent({ onProfileUpdate }) {
478
+ const t = useTranslations("windsock");
479
+ const currentLocale = useLocale();
480
+ const { user, client } = useAuth();
481
+ const [name, setName] = useState(user?.name ?? "");
482
+ const [avatarPreview, setAvatarPreview] = useState(user?.image ?? null);
483
+ const [isUploadingAvatar, setIsUploadingAvatar] = useState(false);
484
+ const fileInputRef = useRef(null);
485
+ const [selectedLocale, setSelectedLocale] = useState(currentLocale);
486
+ const [isSaving, setIsSaving] = useState(false);
487
+ const [saveError, setSaveError] = useState(null);
488
+ const [saveSuccess, setSaveSuccess] = useState(false);
489
+ const handleAvatarChange = useCallback(async (event) => {
490
+ const file = event.target.files?.[0];
491
+ if (!file) return;
492
+ if (!["image/jpeg", "image/png", "image/webp"].includes(file.type)) {
493
+ setSaveError(t("userProfile.profile.avatarInvalidType"));
494
+ return;
495
+ }
496
+ if (file.size > 5 * 1024 * 1024) {
497
+ setSaveError(t("userProfile.profile.avatarTooLarge"));
498
+ return;
499
+ }
500
+ const previewUrl = URL.createObjectURL(file);
501
+ setAvatarPreview(previewUrl);
502
+ setSaveError(null);
503
+ setIsUploadingAvatar(true);
504
+ triggerHaptic("light");
505
+ try {
506
+ const uploadResult = await client.uploadFile(file, "avatars");
507
+ if (!uploadResult.success || !uploadResult.key) {
508
+ throw new Error(uploadResult.error ?? "Upload failed");
509
+ }
510
+ const updateResult = await client.updateProfile({ image: uploadResult.key });
511
+ if (!updateResult.success) {
512
+ throw new Error(updateResult.error ?? "Profile update failed");
513
+ }
514
+ triggerHaptic("success");
515
+ } catch (error) {
516
+ setSaveError(error instanceof Error ? error.message : "Upload failed");
517
+ setAvatarPreview(user?.image ?? null);
518
+ triggerHaptic("error");
519
+ } finally {
520
+ setIsUploadingAvatar(false);
521
+ if (fileInputRef.current) fileInputRef.current.value = "";
522
+ }
523
+ }, [client, user, t]);
524
+ const handleSave = useCallback(async () => {
525
+ setSaveError(null);
526
+ setSaveSuccess(false);
527
+ setIsSaving(true);
528
+ triggerHaptic("light");
529
+ try {
530
+ if (onProfileUpdate) {
531
+ await onProfileUpdate({ name, email: user?.email ?? "", locale: selectedLocale });
532
+ } else {
533
+ const result = await client.updateProfile({ name, locale: selectedLocale });
534
+ if (!result.success) {
535
+ throw new Error(result.error);
536
+ }
537
+ }
538
+ setSaveSuccess(true);
539
+ triggerHaptic("success");
540
+ if (selectedLocale !== currentLocale) {
541
+ sharedI18n.setLocaleCookie(selectedLocale);
542
+ window.location.reload();
543
+ }
544
+ } catch {
545
+ setSaveError(t("userProfile.profile.saveError"));
546
+ triggerHaptic("error");
547
+ } finally {
548
+ setIsSaving(false);
549
+ }
550
+ }, [name, selectedLocale, currentLocale, user, onProfileUpdate, client, t]);
551
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
552
+ /* @__PURE__ */ jsx(
553
+ SectionCard,
554
+ {
555
+ header: {
556
+ icon: /* @__PURE__ */ jsx(UserIcon, { className: "h-5 w-5 text-white" }),
557
+ title: t("userProfile.profile.title"),
558
+ subtitle: t("userProfile.profile.subtitle"),
559
+ gradient: "from-blue-500 via-indigo-500 to-violet-500"
560
+ },
561
+ children: /* @__PURE__ */ jsxs(BaseForm, { onSubmit: handleSave, submitLabel: t("userProfile.profile.saveButton"), showFooter: false, children: [
562
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3 pb-4", children: [
563
+ /* @__PURE__ */ jsxs(
564
+ "button",
565
+ {
566
+ type: "button",
567
+ onClick: () => fileInputRef.current?.click(),
568
+ disabled: isUploadingAvatar,
569
+ className: "group relative",
570
+ children: [
571
+ /* @__PURE__ */ jsx(
572
+ Avatar,
573
+ {
574
+ src: avatarPreview ?? void 0,
575
+ initials: user?.name?.slice(0, 2).toUpperCase() ?? user?.email?.slice(0, 2).toUpperCase(),
576
+ alt: user?.name ?? "Profile",
577
+ className: "h-20 w-20 text-2xl"
578
+ }
579
+ ),
580
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center rounded-full bg-black/0 transition group-hover:bg-black/40", children: isUploadingAvatar ? /* @__PURE__ */ jsx(InlineSpinner, {}) : /* @__PURE__ */ jsx(CameraIcon, { className: "h-6 w-6 text-white opacity-0 transition group-hover:opacity-100" }) })
581
+ ]
582
+ }
583
+ ),
584
+ /* @__PURE__ */ jsx(
585
+ "input",
586
+ {
587
+ ref: fileInputRef,
588
+ type: "file",
589
+ accept: "image/jpeg,image/png,image/webp",
590
+ onChange: handleAvatarChange,
591
+ className: "hidden"
592
+ }
593
+ ),
594
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: t("userProfile.profile.avatarHint") })
595
+ ] }),
596
+ /* @__PURE__ */ jsx(
597
+ Input,
598
+ {
599
+ label: t("userProfile.profile.nameLabel"),
600
+ value: name,
601
+ onChange: (event) => setName(event.target.value),
602
+ disabled: isSaving,
603
+ autoComplete: "name"
604
+ }
605
+ ),
606
+ /* @__PURE__ */ jsx(
607
+ Input,
608
+ {
609
+ label: t("userProfile.profile.emailLabel"),
610
+ type: "email",
611
+ value: user?.email ?? "",
612
+ readOnly: true,
613
+ disabled: true,
614
+ autoComplete: "email"
615
+ }
616
+ ),
617
+ saveError && /* @__PURE__ */ jsx("p", { className: "text-sm text-red-600 dark:text-red-400", children: saveError }),
618
+ saveSuccess && /* @__PURE__ */ jsx("p", { className: "text-sm text-green-600 dark:text-green-400", children: t("userProfile.profile.saveSuccess") }),
619
+ /* @__PURE__ */ jsx(
620
+ Button,
621
+ {
622
+ type: "submit",
623
+ color: "ios-glass-blue",
624
+ fullWidth: true,
625
+ loading: isSaving,
626
+ disabled: isSaving,
627
+ children: t("userProfile.profile.saveButton")
628
+ }
629
+ )
630
+ ] })
631
+ }
632
+ ),
633
+ /* @__PURE__ */ jsxs(
634
+ SectionCard,
635
+ {
636
+ header: {
637
+ icon: /* @__PURE__ */ jsx(GlobeAltIcon, { className: "h-5 w-5 text-white" }),
638
+ title: t("userProfile.profile.languageTitle"),
639
+ subtitle: t("userProfile.profile.languageSubtitle"),
640
+ gradient: "from-lime-400 via-emerald-500 to-green-600"
641
+ },
642
+ children: [
643
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-2", children: sharedI18n.SUPPORTED_LOCALES.map((code) => {
644
+ const meta = sharedI18n.LANGUAGE_META[code];
645
+ const checked = selectedLocale === code;
646
+ return /* @__PURE__ */ jsxs(
647
+ "button",
648
+ {
649
+ type: "button",
650
+ onClick: () => {
651
+ setSelectedLocale(code);
652
+ triggerHaptic("light");
653
+ },
654
+ className: `group relative flex flex-col items-center justify-center rounded-xl border px-2 py-2.5 backdrop-blur-xl shadow-[inset_0_1px_0_rgba(255,255,255,0.9),0_10px_22px_-16px_rgba(15,23,42,0.45)] dark:shadow-[inset_0_1px_0_rgba(255,255,255,0.18),0_10px_22px_-16px_rgba(0,0,0,0.75)] transition ${checked ? "border-indigo-400/80 bg-gradient-to-br from-indigo-100/85 via-white/80 to-sky-100/75 dark:border-indigo-300/70 dark:bg-[linear-gradient(140deg,rgba(99,102,241,0.32)_0%,rgba(30,41,59,0.72)_100%)]" : "border-white/55 bg-gradient-to-br from-white/82 via-white/66 to-slate-100/62 hover:from-white/92 hover:to-sky-100/72 dark:border-white/15 dark:bg-[linear-gradient(140deg,rgba(30,41,59,0.72)_0%,rgba(15,23,42,0.62)_100%)] dark:hover:bg-[linear-gradient(140deg,rgba(51,65,85,0.76)_0%,rgba(30,41,59,0.68)_100%)]"}`,
655
+ children: [
656
+ /* @__PURE__ */ jsx("span", { className: "text-lg leading-5", children: meta.flag }),
657
+ /* @__PURE__ */ jsx("span", { className: `mt-1.5 text-xs font-semibold ${checked ? "text-slate-900 dark:text-indigo-100" : "text-slate-700 dark:text-slate-100"}`, children: meta.nativeName }),
658
+ checked && /* @__PURE__ */ jsx(CheckCircleIcon, { className: "absolute right-1 top-1 h-3.5 w-3.5 text-indigo-600 dark:text-indigo-300" })
659
+ ]
660
+ },
661
+ code
662
+ );
663
+ }) }),
664
+ selectedLocale !== currentLocale && /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(
665
+ Button,
666
+ {
667
+ type: "button",
668
+ color: "ios-glass-blue",
669
+ fullWidth: true,
670
+ loading: isSaving,
671
+ disabled: isSaving,
672
+ onClick: handleSave,
673
+ children: t("userProfile.profile.saveButton")
674
+ }
675
+ ) })
676
+ ]
677
+ }
678
+ )
679
+ ] });
680
+ }
681
+ function SecurityTabContent() {
682
+ const t = useTranslations("windsock");
683
+ const { client, user, refresh } = useAuth();
684
+ const [showMfaSetup, setShowMfaSetup] = useState(false);
685
+ const [currentPassword, setCurrentPassword] = useState("");
686
+ const [newPassword, setNewPassword] = useState("");
687
+ const [confirmPassword, setConfirmPassword] = useState("");
688
+ const [passwordError, setPasswordError] = useState(null);
689
+ const [passwordSuccess, setPasswordSuccess] = useState(false);
690
+ const [isSavingPassword, setIsSavingPassword] = useState(false);
691
+ const handleMfaSetupSuccess = useCallback(() => {
692
+ setShowMfaSetup(false);
693
+ refresh();
694
+ }, [refresh]);
695
+ const handleChangePassword = useCallback(async () => {
696
+ setPasswordError(null);
697
+ setPasswordSuccess(false);
698
+ if (!currentPassword) {
699
+ setPasswordError(t("userProfile.security.errorCurrentRequired"));
700
+ return;
701
+ }
702
+ if (!newPassword) {
703
+ setPasswordError(t("userProfile.security.errorNewRequired"));
704
+ return;
705
+ }
706
+ if (newPassword !== confirmPassword) {
707
+ setPasswordError(t("userProfile.security.errorPasswordMismatch"));
708
+ return;
709
+ }
710
+ setIsSavingPassword(true);
711
+ triggerHaptic("light");
712
+ try {
713
+ const result = await client.changePassword(currentPassword, newPassword);
714
+ if (!result.success) {
715
+ setPasswordError(result.error ?? t("userProfile.security.errorSaveFailed"));
716
+ triggerHaptic("error");
717
+ return;
718
+ }
719
+ setCurrentPassword("");
720
+ setNewPassword("");
721
+ setConfirmPassword("");
722
+ setPasswordSuccess(true);
723
+ triggerHaptic("success");
724
+ } catch {
725
+ setPasswordError(t("userProfile.security.errorSaveFailed"));
726
+ triggerHaptic("error");
727
+ } finally {
728
+ setIsSavingPassword(false);
729
+ }
730
+ }, [currentPassword, newPassword, confirmPassword, client, t]);
731
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
732
+ showMfaSetup ? /* @__PURE__ */ jsx(
733
+ MfaSetup,
734
+ {
735
+ onSuccess: handleMfaSetupSuccess,
736
+ onCancel: () => setShowMfaSetup(false)
737
+ }
738
+ ) : /* @__PURE__ */ jsx(
739
+ MfaManage,
740
+ {
741
+ mfaEnabled: user?.mfaEnabled ?? false,
742
+ enrolledMethods: user?.mfaEnabled ? [{ type: "totp", label: t("userProfile.security.mfaTotpLabel") }] : [],
743
+ onEnroll: () => setShowMfaSetup(true)
744
+ }
745
+ ),
746
+ /* @__PURE__ */ jsx(
747
+ SectionCard,
748
+ {
749
+ header: {
750
+ icon: /* @__PURE__ */ jsx(UserIcon, { className: "h-5 w-5 text-white" }),
751
+ title: t("userProfile.security.changePasswordTitle"),
752
+ subtitle: t("userProfile.security.changePasswordSubtitle"),
753
+ gradient: "from-blue-500 via-sky-500 to-cyan-500"
754
+ },
755
+ children: /* @__PURE__ */ jsxs(BaseForm, { onSubmit: handleChangePassword, submitLabel: t("userProfile.security.changePasswordButton"), showFooter: false, children: [
756
+ /* @__PURE__ */ jsx(
757
+ PasswordInput,
758
+ {
759
+ label: t("userProfile.security.currentPasswordLabel"),
760
+ value: currentPassword,
761
+ onChange: (event) => setCurrentPassword(event.target.value),
762
+ disabled: isSavingPassword,
763
+ autoComplete: "current-password"
764
+ }
765
+ ),
766
+ /* @__PURE__ */ jsx(
767
+ PasswordInput,
768
+ {
769
+ label: t("userProfile.security.newPasswordLabel"),
770
+ value: newPassword,
771
+ onChange: (event) => setNewPassword(event.target.value),
772
+ disabled: isSavingPassword,
773
+ autoComplete: "new-password"
774
+ }
775
+ ),
776
+ /* @__PURE__ */ jsx(PasswordStrengthMeter, { password: newPassword }),
777
+ /* @__PURE__ */ jsx(
778
+ PasswordInput,
779
+ {
780
+ label: t("userProfile.security.confirmPasswordLabel"),
781
+ value: confirmPassword,
782
+ onChange: (event) => setConfirmPassword(event.target.value),
783
+ disabled: isSavingPassword,
784
+ autoComplete: "new-password"
785
+ }
786
+ ),
787
+ passwordError && /* @__PURE__ */ jsx("p", { className: "text-sm text-red-600 dark:text-red-400", children: passwordError }),
788
+ passwordSuccess && /* @__PURE__ */ jsx("p", { className: "text-sm text-green-600 dark:text-green-400", children: t("userProfile.security.changePasswordSuccess") }),
789
+ /* @__PURE__ */ jsx(
790
+ Button,
791
+ {
792
+ type: "submit",
793
+ color: "ios-glass-blue",
794
+ fullWidth: true,
795
+ loading: isSavingPassword,
796
+ disabled: isSavingPassword,
797
+ children: t("userProfile.security.changePasswordButton")
798
+ }
799
+ )
800
+ ] })
801
+ }
802
+ )
803
+ ] });
804
+ }
805
+ function SessionsTabContent() {
806
+ const t = useTranslations("windsock");
807
+ const format = useFormatter();
808
+ const { client } = useAuth();
809
+ const [sessions, setSessions] = useState([]);
810
+ const [isLoading, setIsLoading] = useState(true);
811
+ const [revokingId, setRevokingId] = useState(null);
812
+ useEffect(() => {
813
+ let cancelled = false;
814
+ const fetchSessions = async () => {
815
+ setIsLoading(true);
816
+ try {
817
+ const rawSessions = await client.getSessions();
818
+ if (!cancelled) {
819
+ setSessions(rawSessions.map((session) => ({
820
+ id: session.id,
821
+ device: session.userAgent ?? "Unknown",
822
+ location: session.ipAddress ?? "Unknown",
823
+ lastActive: typeof session.createdAt === "string" ? session.createdAt : session.createdAt.toISOString(),
824
+ current: false
825
+ })));
826
+ }
827
+ } catch {
828
+ } finally {
829
+ if (!cancelled) setIsLoading(false);
830
+ }
831
+ };
832
+ fetchSessions();
833
+ return () => {
834
+ cancelled = true;
835
+ };
836
+ }, [client]);
837
+ const handleRevoke = useCallback(async (sessionId) => {
838
+ setRevokingId(sessionId);
839
+ triggerHaptic("medium");
840
+ try {
841
+ await client.revokeSession(sessionId);
842
+ setSessions((previous) => previous.filter((s) => s.id !== sessionId));
843
+ triggerHaptic("success");
844
+ } catch {
845
+ triggerHaptic("error");
846
+ } finally {
847
+ setRevokingId(null);
848
+ }
849
+ }, [client]);
850
+ return /* @__PURE__ */ jsx(
851
+ SectionCard,
852
+ {
853
+ header: {
854
+ icon: /* @__PURE__ */ jsx(ComputerDesktopIcon, { className: "h-5 w-5 text-white" }),
855
+ title: t("userProfile.sessions.title"),
856
+ subtitle: t("userProfile.sessions.subtitle"),
857
+ gradient: "from-teal-500 via-cyan-500 to-sky-500"
858
+ },
859
+ padded: false,
860
+ children: isLoading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2 p-8 text-sm text-gray-500 dark:text-gray-400", children: [
861
+ /* @__PURE__ */ jsx(InlineSpinner, {}),
862
+ /* @__PURE__ */ jsx("span", { children: t("userProfile.sessions.loading") })
863
+ ] }) : sessions.length === 0 ? /* @__PURE__ */ jsx("p", { className: "p-6 text-center text-sm text-gray-500 dark:text-gray-400", children: t("userProfile.sessions.empty") }) : /* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
864
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b border-gray-200/60 dark:border-white/10", children: [
865
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400", children: t("userProfile.sessions.columnDevice") }),
866
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400", children: t("userProfile.sessions.columnLocation") }),
867
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400", children: t("userProfile.sessions.columnLastActive") }),
868
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-right text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400", children: t("userProfile.sessions.columnAction") })
869
+ ] }) }),
870
+ /* @__PURE__ */ jsx("tbody", { className: "divide-y divide-gray-200/60 dark:divide-white/10", children: sessions.map((session) => /* @__PURE__ */ jsxs("tr", { children: [
871
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-3 text-gray-900 dark:text-white", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
872
+ session.device,
873
+ session.current && /* @__PURE__ */ jsx(Badge, { color: "green", children: t("userProfile.sessions.currentBadge") })
874
+ ] }) }),
875
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-3 text-gray-500 dark:text-gray-400", children: session.location }),
876
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-3 text-gray-500 dark:text-gray-400", children: format.relativeTime(new Date(session.lastActive)) }),
877
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-3 text-right", children: !session.current && /* @__PURE__ */ jsx(
878
+ Button,
879
+ {
880
+ size: "sm",
881
+ color: "ios-glass-red",
882
+ onClick: () => handleRevoke(session.id),
883
+ loading: revokingId === session.id,
884
+ disabled: revokingId !== null,
885
+ children: t("userProfile.sessions.revokeButton")
886
+ }
887
+ ) })
888
+ ] }, session.id)) })
889
+ ] }) })
890
+ }
891
+ );
892
+ }
893
+ function LinkedAccountsTabContent() {
894
+ const t = useTranslations("windsock");
895
+ const { client } = useAuth();
896
+ const [accounts, setAccounts] = useState([]);
897
+ const [isLoading, setIsLoading] = useState(true);
898
+ const [actioningProvider, setActioningProvider] = useState(null);
899
+ useEffect(() => {
900
+ let cancelled = false;
901
+ const fetchAccounts = async () => {
902
+ setIsLoading(true);
903
+ try {
904
+ const rawAccounts = await client.getLinkedAccounts();
905
+ if (!cancelled) {
906
+ setAccounts(rawAccounts.map((account) => ({
907
+ provider: account.provider,
908
+ providerUserId: account.providerAccountId,
909
+ connected: true
910
+ })));
911
+ }
912
+ } catch {
913
+ } finally {
914
+ if (!cancelled) setIsLoading(false);
915
+ }
916
+ };
917
+ fetchAccounts();
918
+ return () => {
919
+ cancelled = true;
920
+ };
921
+ }, [client]);
922
+ const handleDisconnect = useCallback(async (provider) => {
923
+ setActioningProvider(provider);
924
+ triggerHaptic("medium");
925
+ try {
926
+ await client.disconnectProvider(provider);
927
+ setAccounts((previous) => previous.filter((a) => a.provider !== provider));
928
+ triggerHaptic("success");
929
+ } catch {
930
+ triggerHaptic("error");
931
+ } finally {
932
+ setActioningProvider(null);
933
+ }
934
+ }, [client]);
935
+ const providerInitials = (provider) => provider.slice(0, 2).toUpperCase();
936
+ return /* @__PURE__ */ jsx(
937
+ SectionCard,
938
+ {
939
+ header: {
940
+ icon: /* @__PURE__ */ jsx(LinkIcon, { className: "h-5 w-5 text-white" }),
941
+ title: t("userProfile.linkedAccounts.title"),
942
+ subtitle: t("userProfile.linkedAccounts.subtitle"),
943
+ gradient: "from-violet-500 via-purple-500 to-pink-500"
944
+ },
945
+ padded: false,
946
+ children: isLoading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2 p-8 text-sm text-gray-500 dark:text-gray-400", children: [
947
+ /* @__PURE__ */ jsx(InlineSpinner, {}),
948
+ /* @__PURE__ */ jsx("span", { children: t("userProfile.linkedAccounts.loading") })
949
+ ] }) : accounts.length === 0 ? /* @__PURE__ */ jsx("p", { className: "p-6 text-center text-sm text-gray-500 dark:text-gray-400", children: t("userProfile.linkedAccounts.empty") }) : /* @__PURE__ */ jsx(ListCard, { children: accounts.map((account) => /* @__PURE__ */ jsx(
950
+ ListCardItem,
951
+ {
952
+ leading: /* @__PURE__ */ jsx(
953
+ Avatar,
954
+ {
955
+ src: account.logoUrl,
956
+ initials: providerInitials(account.provider),
957
+ alt: account.provider
958
+ }
959
+ ),
960
+ trailing: /* @__PURE__ */ jsx(
961
+ Button,
962
+ {
963
+ size: "sm",
964
+ color: "ios-glass-red",
965
+ onClick: () => handleDisconnect(account.provider),
966
+ loading: actioningProvider === account.provider,
967
+ disabled: actioningProvider !== null,
968
+ children: t("userProfile.linkedAccounts.disconnectButton")
969
+ }
970
+ ),
971
+ children: /* @__PURE__ */ jsxs("div", { children: [
972
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium capitalize text-gray-900 dark:text-white", children: account.provider }),
973
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: t("userProfile.linkedAccounts.connectedLabel") })
974
+ ] })
975
+ },
976
+ account.provider
977
+ )) })
978
+ }
979
+ );
980
+ }
981
+ function UserProfile({
982
+ defaultTab = "profile",
983
+ tabs = ALL_TABS,
984
+ onSignOut: _onSignOut,
985
+ onProfileUpdate
986
+ }) {
987
+ const t = useTranslations("windsock");
988
+ const { status } = useAuth();
989
+ const [activeTab, setActiveTab] = useState(
990
+ tabs.includes(defaultTab) ? defaultTab : tabs[0] ?? "profile"
991
+ );
992
+ if (status === "loading") {
993
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx(InlineSpinner, {}) });
994
+ }
995
+ if (status !== "authenticated") {
996
+ return null;
997
+ }
998
+ const segments = tabs.map((tab) => ({
999
+ value: tab,
1000
+ label: t(`userProfile.tabs.${tab}`)
1001
+ }));
1002
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
1003
+ tabs.length > 1 && /* @__PURE__ */ jsx(
1004
+ SegmentedControl,
1005
+ {
1006
+ segments,
1007
+ value: activeTab,
1008
+ onChange: (value) => setActiveTab(value),
1009
+ fullWidth: true
1010
+ }
1011
+ ),
1012
+ activeTab === "profile" && /* @__PURE__ */ jsx(ProfileTabContent, { onProfileUpdate }),
1013
+ activeTab === "security" && /* @__PURE__ */ jsx(SecurityTabContent, {}),
1014
+ activeTab === "sessions" && /* @__PURE__ */ jsx(SessionsTabContent, {}),
1015
+ activeTab === "linked-accounts" && /* @__PURE__ */ jsx(LinkedAccountsTabContent, {})
1016
+ ] });
1017
+ }
1018
+
1019
+ export { LinkedAccountsTabContent, MfaManage, MfaSetup, ProfileTabContent, SecurityTabContent, SessionsTabContent, UserProfile };
1020
+ //# sourceMappingURL=chunk-SUHNSUMH.mjs.map
1021
+ //# sourceMappingURL=chunk-SUHNSUMH.mjs.map