dauth-context-react 6.0.0 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -40,7 +40,10 @@ var initialDauthState = {
40
40
  logout: () => {
41
41
  },
42
42
  updateUser: () => Promise.resolve(false),
43
- deleteAccount: () => Promise.resolve(false)
43
+ deleteAccount: () => Promise.resolve(false),
44
+ getPasskeyCredentials: () => Promise.resolve([]),
45
+ registerPasskey: () => Promise.resolve(null),
46
+ deletePasskeyCredential: () => Promise.resolve(false)
44
47
  };
45
48
  var initialDauthState_default = initialDauthState;
46
49
 
@@ -147,6 +150,134 @@ async function deleteAccountAPI(basePath) {
147
150
  const data = await response.json();
148
151
  return { response, data };
149
152
  }
153
+ async function getPasskeyCredentialsAPI(basePath) {
154
+ const response = await fetch(
155
+ `${basePath}/passkey/credentials`,
156
+ {
157
+ method: "GET",
158
+ headers: { "X-CSRF-Token": getCsrfToken() },
159
+ credentials: "include"
160
+ }
161
+ );
162
+ const data = await response.json();
163
+ return { response, data };
164
+ }
165
+ async function startPasskeyRegistrationAPI(basePath) {
166
+ const response = await fetch(
167
+ `${basePath}/passkey/register/start`,
168
+ {
169
+ method: "POST",
170
+ headers: {
171
+ "Content-Type": "application/json",
172
+ "X-CSRF-Token": getCsrfToken()
173
+ },
174
+ credentials: "include"
175
+ }
176
+ );
177
+ const data = await response.json();
178
+ return { response, data };
179
+ }
180
+ async function finishPasskeyRegistrationAPI(basePath, body) {
181
+ const response = await fetch(
182
+ `${basePath}/passkey/register/finish`,
183
+ {
184
+ method: "POST",
185
+ headers: {
186
+ "Content-Type": "application/json",
187
+ "X-CSRF-Token": getCsrfToken()
188
+ },
189
+ credentials: "include",
190
+ body: JSON.stringify(body)
191
+ }
192
+ );
193
+ const data = await response.json();
194
+ return { response, data };
195
+ }
196
+ async function deletePasskeyCredentialAPI(basePath, credentialId) {
197
+ const response = await fetch(
198
+ `${basePath}/passkey/credentials/${credentialId}`,
199
+ {
200
+ method: "DELETE",
201
+ headers: { "X-CSRF-Token": getCsrfToken() },
202
+ credentials: "include"
203
+ }
204
+ );
205
+ const data = await response.json();
206
+ return { response, data };
207
+ }
208
+
209
+ // src/webauthn.ts
210
+ function base64urlToBuffer(base64url) {
211
+ const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
212
+ const pad = base64.length % 4;
213
+ const padded = pad ? base64 + "=".repeat(4 - pad) : base64;
214
+ const binary = atob(padded);
215
+ const bytes = new Uint8Array(binary.length);
216
+ for (let i = 0; i < binary.length; i++) {
217
+ bytes[i] = binary.charCodeAt(i);
218
+ }
219
+ return bytes.buffer;
220
+ }
221
+ function bufferToBase64url(buffer) {
222
+ const bytes = new Uint8Array(buffer);
223
+ let binary = "";
224
+ for (let i = 0; i < bytes.length; i++) {
225
+ binary += String.fromCharCode(bytes[i]);
226
+ }
227
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
228
+ }
229
+ async function createPasskeyCredential(options) {
230
+ const publicKey = {
231
+ ...options,
232
+ challenge: base64urlToBuffer(options.challenge),
233
+ user: {
234
+ ...options.user,
235
+ id: base64urlToBuffer(options.user.id)
236
+ },
237
+ excludeCredentials: (options.excludeCredentials ?? []).map(
238
+ (c) => ({
239
+ ...c,
240
+ id: base64urlToBuffer(c.id)
241
+ })
242
+ )
243
+ };
244
+ const credential = await navigator.credentials.create({
245
+ publicKey
246
+ });
247
+ if (!credential) {
248
+ throw new Error("Passkey registration was cancelled");
249
+ }
250
+ const attestation = credential.response;
251
+ return {
252
+ id: credential.id,
253
+ rawId: bufferToBase64url(credential.rawId),
254
+ type: credential.type,
255
+ response: {
256
+ clientDataJSON: bufferToBase64url(
257
+ attestation.clientDataJSON
258
+ ),
259
+ attestationObject: bufferToBase64url(
260
+ attestation.attestationObject
261
+ ),
262
+ ...attestation.getTransports ? { transports: attestation.getTransports() } : {},
263
+ ...attestation.getPublicKeyAlgorithm ? {
264
+ publicKeyAlgorithm: attestation.getPublicKeyAlgorithm()
265
+ } : {},
266
+ ...attestation.getPublicKey ? {
267
+ publicKey: bufferToBase64url(
268
+ attestation.getPublicKey()
269
+ )
270
+ } : {},
271
+ ...attestation.getAuthenticatorData ? {
272
+ authenticatorData: bufferToBase64url(
273
+ attestation.getAuthenticatorData()
274
+ )
275
+ } : {}
276
+ },
277
+ clientExtensionResults: credential.getClientExtensionResults(),
278
+ authenticatorAttachment: credential.authenticatorAttachment ?? void 0
279
+ };
280
+ }
150
281
 
151
282
  // src/reducer/dauth.actions.ts
152
283
  async function exchangeCodeAction(ctx, code) {
@@ -270,6 +401,65 @@ async function deleteAccountAction(ctx) {
270
401
  return false;
271
402
  }
272
403
  }
404
+ async function getPasskeyCredentialsAction(ctx) {
405
+ const { authProxyPath, onError } = ctx;
406
+ try {
407
+ const result = await getPasskeyCredentialsAPI(authProxyPath);
408
+ if (result.response.status === 200) {
409
+ return result.data.credentials ?? [];
410
+ }
411
+ return [];
412
+ } catch (error) {
413
+ onError(
414
+ error instanceof Error ? error : new Error("Get passkey credentials error")
415
+ );
416
+ return [];
417
+ }
418
+ }
419
+ async function registerPasskeyAction(ctx, name) {
420
+ const { authProxyPath, onError } = ctx;
421
+ try {
422
+ const startResult = await startPasskeyRegistrationAPI(authProxyPath);
423
+ if (startResult.response.status !== 200) {
424
+ onError(new Error("Failed to start passkey registration"));
425
+ return null;
426
+ }
427
+ const credential = await createPasskeyCredential(
428
+ startResult.data
429
+ );
430
+ const finishResult = await finishPasskeyRegistrationAPI(
431
+ authProxyPath,
432
+ { credential, name }
433
+ );
434
+ if (finishResult.response.status === 200 || finishResult.response.status === 201) {
435
+ return finishResult.data.credential;
436
+ }
437
+ onError(
438
+ new Error("Failed to finish passkey registration")
439
+ );
440
+ return null;
441
+ } catch (error) {
442
+ onError(
443
+ error instanceof Error ? error : new Error("Passkey registration error")
444
+ );
445
+ return null;
446
+ }
447
+ }
448
+ async function deletePasskeyCredentialAction(ctx, credentialId) {
449
+ const { authProxyPath, onError } = ctx;
450
+ try {
451
+ const result = await deletePasskeyCredentialAPI(
452
+ authProxyPath,
453
+ credentialId
454
+ );
455
+ return result.response.status === 200;
456
+ } catch (error) {
457
+ onError(
458
+ error instanceof Error ? error : new Error("Delete passkey error")
459
+ );
460
+ return false;
461
+ }
462
+ }
273
463
  var resetUser = (dispatch) => {
274
464
  return dispatch({
275
465
  type: LOGIN,
@@ -361,6 +551,107 @@ function IconBack() {
361
551
  }
362
552
  );
363
553
  }
554
+ function IconFingerprint() {
555
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
556
+ "svg",
557
+ {
558
+ width: "16",
559
+ height: "16",
560
+ viewBox: "0 0 24 24",
561
+ fill: "none",
562
+ stroke: "currentColor",
563
+ strokeWidth: "2",
564
+ strokeLinecap: "round",
565
+ strokeLinejoin: "round",
566
+ children: [
567
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 10a2 2 0 0 0-2 2c0 1.02-.1 2.51-.26 4" }),
568
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M14 13.12c0 2.38 0 6.38-1 8.88" }),
569
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M17.29 21.02c.12-.6.43-2.3.5-3.02" }),
570
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M2 12a10 10 0 0 1 18-6" }),
571
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M2 16h.01" }),
572
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M21.8 16c.2-2 .131-5.354 0-6" }),
573
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5 19.5C5.5 18 6 15 6 12a6 6 0 0 1 .34-2" }),
574
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8.65 22c.21-.66.45-1.32.57-2" }),
575
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M9 6.8a6 6 0 0 1 9 5.2v2" })
576
+ ]
577
+ }
578
+ );
579
+ }
580
+ function IconShield() {
581
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
582
+ "svg",
583
+ {
584
+ width: "20",
585
+ height: "20",
586
+ viewBox: "0 0 24 24",
587
+ fill: "none",
588
+ stroke: "currentColor",
589
+ strokeWidth: "2",
590
+ strokeLinecap: "round",
591
+ strokeLinejoin: "round",
592
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" })
593
+ }
594
+ );
595
+ }
596
+ function IconTrash() {
597
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
598
+ "svg",
599
+ {
600
+ width: "14",
601
+ height: "14",
602
+ viewBox: "0 0 24 24",
603
+ fill: "none",
604
+ stroke: "currentColor",
605
+ strokeWidth: "2",
606
+ strokeLinecap: "round",
607
+ strokeLinejoin: "round",
608
+ children: [
609
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 6h18" }),
610
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }),
611
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" })
612
+ ]
613
+ }
614
+ );
615
+ }
616
+ function IconLogOut() {
617
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
618
+ "svg",
619
+ {
620
+ width: "16",
621
+ height: "16",
622
+ viewBox: "0 0 24 24",
623
+ fill: "none",
624
+ stroke: "currentColor",
625
+ strokeWidth: "2",
626
+ strokeLinecap: "round",
627
+ strokeLinejoin: "round",
628
+ children: [
629
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }),
630
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("polyline", { points: "16 17 21 12 16 7" }),
631
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "21", y1: "12", x2: "9", y2: "12" })
632
+ ]
633
+ }
634
+ );
635
+ }
636
+ function IconCamera() {
637
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
638
+ "svg",
639
+ {
640
+ width: "14",
641
+ height: "14",
642
+ viewBox: "0 0 24 24",
643
+ fill: "none",
644
+ stroke: "currentColor",
645
+ strokeWidth: "2",
646
+ strokeLinecap: "round",
647
+ strokeLinejoin: "round",
648
+ children: [
649
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z" }),
650
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "13", r: "3" })
651
+ ]
652
+ }
653
+ );
654
+ }
364
655
  function Spinner() {
365
656
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: spinnerStyle, "aria-hidden": "true" });
366
657
  }
@@ -388,7 +679,10 @@ function useModalAnimation(open) {
388
679
  }
389
680
  if (phase === "entered" || phase === "entering") {
390
681
  setPhase("exiting");
391
- const timer = setTimeout(() => setPhase("exited"), MOBILE_TRANSITION_MS);
682
+ const timer = setTimeout(
683
+ () => setPhase("exited"),
684
+ MOBILE_TRANSITION_MS
685
+ );
392
686
  return () => clearTimeout(timer);
393
687
  }
394
688
  return void 0;
@@ -456,11 +750,27 @@ function useScrollLock(active) {
456
750
  };
457
751
  }, [active]);
458
752
  }
459
- function DauthProfileModal({ open, onClose }) {
460
- const { user, domain, updateUser, deleteAccount } = useDauth();
753
+ function DauthProfileModal({
754
+ open,
755
+ onClose,
756
+ onAvatarUpload
757
+ }) {
758
+ const {
759
+ user,
760
+ domain,
761
+ updateUser,
762
+ deleteAccount,
763
+ logout,
764
+ getPasskeyCredentials,
765
+ registerPasskey,
766
+ deletePasskeyCredential
767
+ } = useDauth();
461
768
  const isDesktop = useMediaQuery("(min-width: 641px)");
462
769
  const phase = useModalAnimation(open);
463
770
  const modalRef = (0, import_react.useRef)(null);
771
+ const avatarInputRef = (0, import_react.useRef)(null);
772
+ const showSecurity = domain.authMethods?.passkey === true;
773
+ const [activeTab, setActiveTab] = (0, import_react.useState)("profile");
464
774
  const [name, setName] = (0, import_react.useState)("");
465
775
  const [lastname, setLastname] = (0, import_react.useState)("");
466
776
  const [nickname, setNickname] = (0, import_react.useState)("");
@@ -471,6 +781,13 @@ function DauthProfileModal({ open, onClose }) {
471
781
  const [showDelete, setShowDelete] = (0, import_react.useState)(false);
472
782
  const [deleteText, setDeleteText] = (0, import_react.useState)("");
473
783
  const [deleting, setDeleting] = (0, import_react.useState)(false);
784
+ const [credentials, setCredentials] = (0, import_react.useState)([]);
785
+ const [loadingCreds, setLoadingCreds] = (0, import_react.useState)(false);
786
+ const [showRegister, setShowRegister] = (0, import_react.useState)(false);
787
+ const [passkeyName, setPasskeyName] = (0, import_react.useState)("");
788
+ const [registering, setRegistering] = (0, import_react.useState)(false);
789
+ const [passkeyStatus, setPasskeyStatus] = (0, import_react.useState)(null);
790
+ const [uploadingAvatar, setUploadingAvatar] = (0, import_react.useState)(false);
474
791
  (0, import_react.useEffect)(() => {
475
792
  if (open && user?._id && !populated) {
476
793
  setName(user.name || "");
@@ -484,13 +801,36 @@ function DauthProfileModal({ open, onClose }) {
484
801
  setStatus(null);
485
802
  setShowDelete(false);
486
803
  setDeleteText("");
804
+ setActiveTab("profile");
805
+ setPasskeyStatus(null);
806
+ setShowRegister(false);
807
+ setPasskeyName("");
487
808
  }
488
809
  }, [open, user, populated]);
810
+ (0, import_react.useEffect)(() => {
811
+ if (activeTab !== "security" || !showSecurity) return;
812
+ setLoadingCreds(true);
813
+ getPasskeyCredentials().then((creds) => {
814
+ setCredentials(creds);
815
+ setLoadingCreds(false);
816
+ });
817
+ }, [activeTab, showSecurity, getPasskeyCredentials]);
489
818
  (0, import_react.useEffect)(() => {
490
819
  if (status?.type !== "success") return;
491
- const timer = setTimeout(() => setStatus(null), SUCCESS_TIMEOUT_MS);
820
+ const timer = setTimeout(
821
+ () => setStatus(null),
822
+ SUCCESS_TIMEOUT_MS
823
+ );
492
824
  return () => clearTimeout(timer);
493
825
  }, [status]);
826
+ (0, import_react.useEffect)(() => {
827
+ if (passkeyStatus?.type !== "success") return;
828
+ const timer = setTimeout(
829
+ () => setPasskeyStatus(null),
830
+ SUCCESS_TIMEOUT_MS
831
+ );
832
+ return () => clearTimeout(timer);
833
+ }, [passkeyStatus]);
494
834
  useFocusTrap(modalRef, phase === "entered", onClose);
495
835
  useScrollLock(phase !== "exited");
496
836
  const hasField = (0, import_react.useCallback)(
@@ -542,6 +882,90 @@ function DauthProfileModal({ open, onClose }) {
542
882
  setDeleteText("");
543
883
  }
544
884
  }, [deleteAccount, onClose]);
885
+ const handleLanguage = (0, import_react.useCallback)(
886
+ async (lang) => {
887
+ await updateUser({ language: lang });
888
+ },
889
+ [updateUser]
890
+ );
891
+ const handleRegisterPasskey = (0, import_react.useCallback)(async () => {
892
+ setRegistering(true);
893
+ setPasskeyStatus(null);
894
+ const cred = await registerPasskey(
895
+ passkeyName || void 0
896
+ );
897
+ setRegistering(false);
898
+ if (cred) {
899
+ setCredentials((prev) => [...prev, cred]);
900
+ setPasskeyName("");
901
+ setShowRegister(false);
902
+ setPasskeyStatus({
903
+ type: "success",
904
+ message: "Passkey registered successfully"
905
+ });
906
+ } else {
907
+ setPasskeyStatus({
908
+ type: "error",
909
+ message: "Failed to register passkey"
910
+ });
911
+ }
912
+ }, [passkeyName, registerPasskey]);
913
+ const handleDeletePasskey = (0, import_react.useCallback)(
914
+ async (credentialId) => {
915
+ const ok = await deletePasskeyCredential(credentialId);
916
+ if (ok) {
917
+ setCredentials(
918
+ (prev) => prev.filter((c) => c._id !== credentialId)
919
+ );
920
+ }
921
+ },
922
+ [deletePasskeyCredential]
923
+ );
924
+ const handleAvatarClick = (0, import_react.useCallback)(() => {
925
+ if (onAvatarUpload) {
926
+ avatarInputRef.current?.click();
927
+ }
928
+ }, [onAvatarUpload]);
929
+ const handleAvatarChange = (0, import_react.useCallback)(
930
+ async (e) => {
931
+ const file = e.target.files?.[0];
932
+ if (!file || !onAvatarUpload) return;
933
+ setUploadingAvatar(true);
934
+ try {
935
+ const url = await onAvatarUpload(file);
936
+ if (url) {
937
+ await updateUser({ avatar: url });
938
+ }
939
+ } catch {
940
+ }
941
+ setUploadingAvatar(false);
942
+ if (avatarInputRef.current) {
943
+ avatarInputRef.current.value = "";
944
+ }
945
+ },
946
+ [onAvatarUpload, updateUser]
947
+ );
948
+ const handleSignOut = (0, import_react.useCallback)(() => {
949
+ logout();
950
+ onClose();
951
+ }, [logout, onClose]);
952
+ const themeVars = (0, import_react.useMemo)(() => {
953
+ const t = domain.modalTheme;
954
+ if (!t) return {};
955
+ const vars = {};
956
+ if (t.accent) vars["--dauth-accent"] = t.accent;
957
+ if (t.accentHover)
958
+ vars["--dauth-accent-hover"] = t.accentHover;
959
+ if (t.surface) vars["--dauth-surface"] = t.surface;
960
+ if (t.surfaceHover)
961
+ vars["--dauth-surface-hover"] = t.surfaceHover;
962
+ if (t.textPrimary)
963
+ vars["--dauth-text-primary"] = t.textPrimary;
964
+ if (t.textSecondary)
965
+ vars["--dauth-text-secondary"] = t.textSecondary;
966
+ if (t.border) vars["--dauth-border"] = t.border;
967
+ return vars;
968
+ }, [domain.modalTheme]);
545
969
  if (phase === "exited") return null;
546
970
  const dur = isDesktop ? TRANSITION_MS : MOBILE_TRANSITION_MS;
547
971
  const easing = "cubic-bezier(0.16, 1, 0.3, 1)";
@@ -589,6 +1013,11 @@ function DauthProfileModal({ open, onClose }) {
589
1013
  transition: `transform ${dur}ms ${easing}`
590
1014
  };
591
1015
  const avatarInitial = (user.name || user.email || "?").charAt(0).toUpperCase();
1016
+ const tabs = [
1017
+ { key: "profile", label: "Profile" },
1018
+ ...showSecurity ? [{ key: "security", label: "Security" }] : [],
1019
+ { key: "account", label: "Account" }
1020
+ ];
592
1021
  return (0, import_react_dom.createPortal)(
593
1022
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
594
1023
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -602,7 +1031,7 @@ function DauthProfileModal({ open, onClose }) {
602
1031
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
603
1032
  "div",
604
1033
  {
605
- style: backdrop,
1034
+ style: { ...backdrop, ...themeVars },
606
1035
  onClick: isDesktop ? onClose : void 0,
607
1036
  "data-testid": "dauth-profile-backdrop",
608
1037
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
@@ -632,191 +1061,542 @@ function DauthProfileModal({ open, onClose }) {
632
1061
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { id: "dauth-profile-title", style: titleStyle, children: "Your Profile" }),
633
1062
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { width: 36 } })
634
1063
  ] }),
1064
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: tabBar, role: "tablist", children: tabs.map((t) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1065
+ "button",
1066
+ {
1067
+ role: "tab",
1068
+ type: "button",
1069
+ "aria-selected": activeTab === t.key,
1070
+ style: {
1071
+ ...tabBtn,
1072
+ color: activeTab === t.key ? "var(--dauth-accent, #6366f1)" : "var(--dauth-text-secondary, #a1a1aa)",
1073
+ borderBottomColor: activeTab === t.key ? "var(--dauth-accent, #6366f1)" : "transparent"
1074
+ },
1075
+ onClick: () => setActiveTab(t.key),
1076
+ children: t.label
1077
+ },
1078
+ t.key
1079
+ )) }),
635
1080
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: bodyStyle, children: [
636
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: avatarSection, children: [
637
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: avatarCircle, children: user.avatar?.url ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
638
- "img",
639
- {
640
- src: user.avatar.url,
641
- alt: "",
642
- style: {
643
- width: "100%",
644
- height: "100%",
645
- objectFit: "cover"
646
- }
647
- }
648
- ) : avatarInitial }),
649
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: emailText, children: user.email })
650
- ] }),
651
- status && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
652
- "div",
653
- {
654
- role: "status",
655
- "aria-live": "polite",
656
- style: statusMsg(status.type),
657
- children: status.message
658
- }
659
- ),
660
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
661
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: fieldGroup, children: [
662
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "dauth-name", style: label, children: "Name *" }),
663
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
664
- "input",
1081
+ activeTab === "profile" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1082
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: avatarSection, children: [
1083
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1084
+ "div",
665
1085
  {
666
- id: "dauth-name",
667
- type: "text",
668
- value: name,
669
- onChange: (e) => setName(e.target.value),
670
- placeholder: "Your name",
671
- disabled: saving,
672
- style: input,
673
- onFocus: inputFocusHandler,
674
- onBlur: inputBlurHandler
1086
+ style: {
1087
+ ...avatarCircle,
1088
+ cursor: onAvatarUpload ? "pointer" : "default",
1089
+ position: "relative"
1090
+ },
1091
+ onClick: handleAvatarClick,
1092
+ children: [
1093
+ uploadingAvatar ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}) : user.avatar?.url ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1094
+ "img",
1095
+ {
1096
+ src: user.avatar.url,
1097
+ alt: "",
1098
+ style: {
1099
+ width: "100%",
1100
+ height: "100%",
1101
+ objectFit: "cover"
1102
+ }
1103
+ }
1104
+ ) : avatarInitial,
1105
+ onAvatarUpload && !uploadingAvatar && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: avatarOverlay, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconCamera, {}) })
1106
+ ]
675
1107
  }
676
- )
677
- ] }),
678
- hasField("lastname") && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: fieldGroup, children: [
679
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { htmlFor: "dauth-lastname", style: label, children: [
680
- "Last name",
681
- isRequired("lastname") ? " *" : ""
682
- ] }),
683
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1108
+ ),
1109
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: emailText, children: user.email }),
1110
+ onAvatarUpload && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
684
1111
  "input",
685
1112
  {
686
- id: "dauth-lastname",
687
- type: "text",
688
- value: lastname,
689
- onChange: (e) => setLastname(e.target.value),
690
- placeholder: "Your last name",
691
- disabled: saving,
692
- style: input,
693
- onFocus: inputFocusHandler,
694
- onBlur: inputBlurHandler
1113
+ ref: avatarInputRef,
1114
+ type: "file",
1115
+ accept: "image/*",
1116
+ style: { display: "none" },
1117
+ onChange: handleAvatarChange
695
1118
  }
696
1119
  )
697
1120
  ] }),
698
- hasField("nickname") && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: fieldGroup, children: [
699
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { htmlFor: "dauth-nickname", style: label, children: [
700
- "Nickname",
701
- isRequired("nickname") ? " *" : ""
1121
+ status && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1122
+ "div",
1123
+ {
1124
+ role: "status",
1125
+ "aria-live": "polite",
1126
+ style: statusMsg(status.type),
1127
+ children: status.message
1128
+ }
1129
+ ),
1130
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1131
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: fieldGroup, children: [
1132
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1133
+ "label",
1134
+ {
1135
+ htmlFor: "dauth-name",
1136
+ style: label,
1137
+ children: "Name *"
1138
+ }
1139
+ ),
1140
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1141
+ "input",
1142
+ {
1143
+ id: "dauth-name",
1144
+ type: "text",
1145
+ value: name,
1146
+ onChange: (e) => setName(e.target.value),
1147
+ placeholder: "Your name",
1148
+ disabled: saving,
1149
+ style: input,
1150
+ onFocus: inputFocusHandler,
1151
+ onBlur: inputBlurHandler
1152
+ }
1153
+ )
702
1154
  ] }),
703
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
704
- "input",
705
- {
706
- id: "dauth-nickname",
707
- type: "text",
708
- value: nickname,
709
- onChange: (e) => setNickname(e.target.value),
710
- placeholder: "Choose a nickname",
711
- disabled: saving,
712
- style: input,
713
- onFocus: inputFocusHandler,
714
- onBlur: inputBlurHandler
715
- }
716
- )
717
- ] }),
718
- hasField("country") && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: fieldGroup, children: [
719
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { htmlFor: "dauth-country", style: label, children: [
720
- "Country",
721
- isRequired("country") ? " *" : ""
1155
+ hasField("lastname") && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: fieldGroup, children: [
1156
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1157
+ "label",
1158
+ {
1159
+ htmlFor: "dauth-lastname",
1160
+ style: label,
1161
+ children: [
1162
+ "Last name",
1163
+ isRequired("lastname") ? " *" : ""
1164
+ ]
1165
+ }
1166
+ ),
1167
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1168
+ "input",
1169
+ {
1170
+ id: "dauth-lastname",
1171
+ type: "text",
1172
+ value: lastname,
1173
+ onChange: (e) => setLastname(e.target.value),
1174
+ placeholder: "Your last name",
1175
+ disabled: saving,
1176
+ style: input,
1177
+ onFocus: inputFocusHandler,
1178
+ onBlur: inputBlurHandler
1179
+ }
1180
+ )
722
1181
  ] }),
723
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
724
- "input",
1182
+ hasField("nickname") && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: fieldGroup, children: [
1183
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1184
+ "label",
1185
+ {
1186
+ htmlFor: "dauth-nickname",
1187
+ style: label,
1188
+ children: [
1189
+ "Nickname",
1190
+ isRequired("nickname") ? " *" : ""
1191
+ ]
1192
+ }
1193
+ ),
1194
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1195
+ "input",
1196
+ {
1197
+ id: "dauth-nickname",
1198
+ type: "text",
1199
+ value: nickname,
1200
+ onChange: (e) => setNickname(e.target.value),
1201
+ placeholder: "Choose a nickname",
1202
+ disabled: saving,
1203
+ style: input,
1204
+ onFocus: inputFocusHandler,
1205
+ onBlur: inputBlurHandler
1206
+ }
1207
+ )
1208
+ ] }),
1209
+ hasField("country") && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: fieldGroup, children: [
1210
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1211
+ "label",
1212
+ {
1213
+ htmlFor: "dauth-country",
1214
+ style: label,
1215
+ children: [
1216
+ "Country",
1217
+ isRequired("country") ? " *" : ""
1218
+ ]
1219
+ }
1220
+ ),
1221
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1222
+ "input",
1223
+ {
1224
+ id: "dauth-country",
1225
+ type: "text",
1226
+ value: country,
1227
+ onChange: (e) => setCountry(e.target.value),
1228
+ placeholder: "Your country",
1229
+ disabled: saving,
1230
+ style: input,
1231
+ onFocus: inputFocusHandler,
1232
+ onBlur: inputBlurHandler
1233
+ }
1234
+ )
1235
+ ] })
1236
+ ] }),
1237
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("hr", { style: separator }),
1238
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: fieldGroup, children: [
1239
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: label, children: "Language" }),
1240
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", gap: 8 }, children: ["es", "en"].map((lang) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1241
+ "button",
725
1242
  {
726
- id: "dauth-country",
727
- type: "text",
728
- value: country,
729
- onChange: (e) => setCountry(e.target.value),
730
- placeholder: "Your country",
731
- disabled: saving,
732
- style: input,
733
- onFocus: inputFocusHandler,
734
- onBlur: inputBlurHandler
735
- }
736
- )
1243
+ type: "button",
1244
+ style: {
1245
+ ...langBtn,
1246
+ backgroundColor: user.language === lang ? "var(--dauth-accent, #6366f1)" : "var(--dauth-surface-secondary, rgba(255, 255, 255, 0.04))",
1247
+ color: user.language === lang ? "#ffffff" : "var(--dauth-text-secondary, #a1a1aa)"
1248
+ },
1249
+ onClick: () => handleLanguage(lang),
1250
+ children: lang === "es" ? "Espa\xF1ol" : "English"
1251
+ },
1252
+ lang
1253
+ )) })
737
1254
  ] })
738
1255
  ] }),
739
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("hr", { style: separator }),
740
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
741
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: dangerTitle, children: "Delete account" }),
742
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: dangerDesc, children: "Permanently delete your account and all associated data." }),
743
- !showDelete ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
744
- "button",
1256
+ activeTab === "security" && showSecurity && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1257
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1258
+ "div",
745
1259
  {
746
- type: "button",
747
- style: deleteBtn,
748
- onClick: () => setShowDelete(true),
749
- onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "rgba(239, 68, 68, 0.2)",
750
- onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "var(--dauth-error-bg, rgba(239, 68, 68, 0.1))",
751
- children: "Delete account"
1260
+ style: {
1261
+ display: "flex",
1262
+ alignItems: "center",
1263
+ justifyContent: "space-between",
1264
+ marginBottom: 16
1265
+ },
1266
+ children: [
1267
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1268
+ "div",
1269
+ {
1270
+ style: {
1271
+ ...label,
1272
+ marginBottom: 0,
1273
+ fontWeight: 600
1274
+ },
1275
+ children: "Passkeys"
1276
+ }
1277
+ ),
1278
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1279
+ "button",
1280
+ {
1281
+ type: "button",
1282
+ style: outlineBtn,
1283
+ onClick: () => setShowRegister(!showRegister),
1284
+ onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "var(--dauth-surface-hover, #232340)",
1285
+ onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "transparent",
1286
+ children: "+ Add passkey"
1287
+ }
1288
+ )
1289
+ ]
752
1290
  }
753
- ) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: deletePanel, children: [
754
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: deletePanelText, children: [
755
- "This action is permanent and cannot be undone. Type",
756
- " ",
757
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: CONFIRM_WORD }),
758
- " to confirm."
1291
+ ),
1292
+ showRegister && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: registerPanel, children: [
1293
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: fieldGroup, children: [
1294
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1295
+ "label",
1296
+ {
1297
+ htmlFor: "dauth-passkey-name",
1298
+ style: label,
1299
+ children: "Passkey name (optional)"
1300
+ }
1301
+ ),
1302
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1303
+ "input",
1304
+ {
1305
+ id: "dauth-passkey-name",
1306
+ type: "text",
1307
+ value: passkeyName,
1308
+ onChange: (e) => setPasskeyName(e.target.value),
1309
+ placeholder: "e.g. MacBook Touch ID",
1310
+ disabled: registering,
1311
+ style: input,
1312
+ onFocus: inputFocusHandler,
1313
+ onBlur: inputBlurHandler
1314
+ }
1315
+ )
759
1316
  ] }),
760
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
761
- "input",
762
- {
763
- type: "text",
764
- value: deleteText,
765
- onChange: (e) => setDeleteText(e.target.value),
766
- placeholder: `Type ${CONFIRM_WORD}`,
767
- style: input,
768
- onFocus: inputFocusHandler,
769
- onBlur: inputBlurHandler,
770
- disabled: deleting
771
- }
772
- ),
773
1317
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
774
1318
  "div",
775
1319
  {
776
1320
  style: {
777
1321
  display: "flex",
778
- gap: 8,
779
- marginTop: 12
1322
+ gap: 8
780
1323
  },
781
1324
  children: [
782
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1325
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
783
1326
  "button",
784
1327
  {
785
1328
  type: "button",
786
- style: cancelBtn,
787
- onClick: () => {
788
- setShowDelete(false);
789
- setDeleteText("");
1329
+ style: {
1330
+ ...smallAccentBtn,
1331
+ opacity: registering ? 0.6 : 1
790
1332
  },
791
- onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "var(--dauth-surface-hover, #232340)",
792
- onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "transparent",
793
- children: "Cancel"
1333
+ disabled: registering,
1334
+ onClick: handleRegisterPasskey,
1335
+ children: [
1336
+ registering ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconFingerprint, {}),
1337
+ registering ? "Registering..." : "Register"
1338
+ ]
794
1339
  }
795
1340
  ),
796
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1341
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
797
1342
  "button",
798
1343
  {
799
1344
  type: "button",
800
- style: {
801
- ...deleteConfirmBtn,
802
- opacity: deleteText !== CONFIRM_WORD || deleting ? 0.5 : 1,
803
- cursor: deleteText !== CONFIRM_WORD || deleting ? "not-allowed" : "pointer"
804
- },
805
- disabled: deleteText !== CONFIRM_WORD || deleting,
806
- onClick: handleDelete,
807
- children: [
808
- deleting && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}),
809
- "Delete my account"
810
- ]
1345
+ style: cancelBtn,
1346
+ onClick: () => setShowRegister(false),
1347
+ onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "var(--dauth-surface-hover, #232340)",
1348
+ onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "transparent",
1349
+ children: "Cancel"
811
1350
  }
812
1351
  )
813
1352
  ]
814
1353
  }
815
1354
  )
816
- ] })
1355
+ ] }),
1356
+ passkeyStatus && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1357
+ "div",
1358
+ {
1359
+ role: "status",
1360
+ "aria-live": "polite",
1361
+ style: {
1362
+ ...statusMsg(passkeyStatus.type),
1363
+ marginTop: 12
1364
+ },
1365
+ children: passkeyStatus.message
1366
+ }
1367
+ ),
1368
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginTop: 12 }, children: loadingCreds ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1369
+ "div",
1370
+ {
1371
+ style: {
1372
+ textAlign: "center",
1373
+ padding: 24
1374
+ },
1375
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {})
1376
+ }
1377
+ ) : credentials.length > 0 ? credentials.map((cred) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1378
+ "div",
1379
+ {
1380
+ style: credentialRow,
1381
+ children: [
1382
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1383
+ "div",
1384
+ {
1385
+ style: {
1386
+ display: "flex",
1387
+ alignItems: "center",
1388
+ gap: 12,
1389
+ flex: 1,
1390
+ minWidth: 0
1391
+ },
1392
+ children: [
1393
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1394
+ "span",
1395
+ {
1396
+ style: {
1397
+ color: "var(--dauth-accent, #6366f1)",
1398
+ flexShrink: 0
1399
+ },
1400
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconFingerprint, {})
1401
+ }
1402
+ ),
1403
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1404
+ "div",
1405
+ {
1406
+ style: {
1407
+ minWidth: 0,
1408
+ flex: 1
1409
+ },
1410
+ children: [
1411
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1412
+ "div",
1413
+ {
1414
+ style: {
1415
+ fontSize: "var(--dauth-font-size-sm, 0.875rem)",
1416
+ fontWeight: 500,
1417
+ color: "var(--dauth-text-primary, #e4e4e7)",
1418
+ overflow: "hidden",
1419
+ textOverflow: "ellipsis",
1420
+ whiteSpace: "nowrap"
1421
+ },
1422
+ children: cred.name || "Passkey"
1423
+ }
1424
+ ),
1425
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1426
+ "div",
1427
+ {
1428
+ style: {
1429
+ fontSize: "var(--dauth-font-size-xs, 0.75rem)",
1430
+ color: "var(--dauth-text-muted, #71717a)"
1431
+ },
1432
+ children: [
1433
+ cred.deviceType === "multiDevice" ? "Synced" : "Device-bound",
1434
+ cred.createdAt && ` \xB7 Created ${new Date(cred.createdAt).toLocaleDateString()}`
1435
+ ]
1436
+ }
1437
+ )
1438
+ ]
1439
+ }
1440
+ )
1441
+ ]
1442
+ }
1443
+ ),
1444
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1445
+ "button",
1446
+ {
1447
+ type: "button",
1448
+ onClick: () => handleDeletePasskey(cred._id),
1449
+ style: trashBtn,
1450
+ onMouseEnter: (e) => e.currentTarget.style.color = "var(--dauth-error, #ef4444)",
1451
+ onMouseLeave: (e) => e.currentTarget.style.color = "var(--dauth-text-muted, #71717a)",
1452
+ "aria-label": `Delete passkey ${cred.name || ""}`,
1453
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconTrash, {})
1454
+ }
1455
+ )
1456
+ ]
1457
+ },
1458
+ cred._id
1459
+ )) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: emptyState, children: [
1460
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1461
+ "span",
1462
+ {
1463
+ style: {
1464
+ color: "var(--dauth-accent, #6366f1)"
1465
+ },
1466
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconShield, {})
1467
+ }
1468
+ ),
1469
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1470
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1471
+ "div",
1472
+ {
1473
+ style: {
1474
+ fontSize: "var(--dauth-font-size-sm, 0.875rem)",
1475
+ fontWeight: 500,
1476
+ color: "var(--dauth-text-primary, #e4e4e7)"
1477
+ },
1478
+ children: "No passkeys registered"
1479
+ }
1480
+ ),
1481
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1482
+ "div",
1483
+ {
1484
+ style: {
1485
+ fontSize: "var(--dauth-font-size-xs, 0.75rem)",
1486
+ color: "var(--dauth-text-secondary, #a1a1aa)"
1487
+ },
1488
+ children: "Add a passkey for faster, more secure sign-in."
1489
+ }
1490
+ )
1491
+ ] })
1492
+ ] }) })
1493
+ ] }),
1494
+ activeTab === "account" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1495
+ status && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1496
+ "div",
1497
+ {
1498
+ role: "status",
1499
+ "aria-live": "polite",
1500
+ style: statusMsg(status.type),
1501
+ children: status.message
1502
+ }
1503
+ ),
1504
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
1505
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: dangerTitle, children: "Delete account" }),
1506
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: dangerDesc, children: "Permanently delete your account and all associated data." }),
1507
+ !showDelete ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1508
+ "button",
1509
+ {
1510
+ type: "button",
1511
+ style: deleteBtn,
1512
+ onClick: () => setShowDelete(true),
1513
+ onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "rgba(239, 68, 68, 0.2)",
1514
+ onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "var(--dauth-error-bg, rgba(239, 68, 68, 0.1))",
1515
+ children: "Delete account"
1516
+ }
1517
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: deletePanel, children: [
1518
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: deletePanelText, children: [
1519
+ "This action is permanent and cannot be undone. Type",
1520
+ " ",
1521
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: CONFIRM_WORD }),
1522
+ " to confirm."
1523
+ ] }),
1524
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1525
+ "input",
1526
+ {
1527
+ type: "text",
1528
+ value: deleteText,
1529
+ onChange: (e) => setDeleteText(e.target.value),
1530
+ placeholder: `Type ${CONFIRM_WORD}`,
1531
+ style: input,
1532
+ onFocus: inputFocusHandler,
1533
+ onBlur: inputBlurHandler,
1534
+ disabled: deleting
1535
+ }
1536
+ ),
1537
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1538
+ "div",
1539
+ {
1540
+ style: {
1541
+ display: "flex",
1542
+ gap: 8,
1543
+ marginTop: 12
1544
+ },
1545
+ children: [
1546
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1547
+ "button",
1548
+ {
1549
+ type: "button",
1550
+ style: cancelBtn,
1551
+ onClick: () => {
1552
+ setShowDelete(false);
1553
+ setDeleteText("");
1554
+ },
1555
+ onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "var(--dauth-surface-hover, #232340)",
1556
+ onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "transparent",
1557
+ children: "Cancel"
1558
+ }
1559
+ ),
1560
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1561
+ "button",
1562
+ {
1563
+ type: "button",
1564
+ style: {
1565
+ ...deleteConfirmBtn,
1566
+ opacity: deleteText !== CONFIRM_WORD || deleting ? 0.5 : 1,
1567
+ cursor: deleteText !== CONFIRM_WORD || deleting ? "not-allowed" : "pointer"
1568
+ },
1569
+ disabled: deleteText !== CONFIRM_WORD || deleting,
1570
+ onClick: handleDelete,
1571
+ children: [
1572
+ deleting && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}),
1573
+ "Delete my account"
1574
+ ]
1575
+ }
1576
+ )
1577
+ ]
1578
+ }
1579
+ )
1580
+ ] })
1581
+ ] }),
1582
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("hr", { style: separator }),
1583
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1584
+ "button",
1585
+ {
1586
+ type: "button",
1587
+ style: signOutBtn,
1588
+ onClick: handleSignOut,
1589
+ onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "rgba(239, 68, 68, 0.2)",
1590
+ onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "var(--dauth-error-bg, rgba(239, 68, 68, 0.1))",
1591
+ children: [
1592
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconLogOut, {}),
1593
+ "Sign out"
1594
+ ]
1595
+ }
1596
+ )
817
1597
  ] })
818
1598
  ] }),
819
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: footerStyle(isDesktop), children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1599
+ activeTab === "profile" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: footerStyle(isDesktop), children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
820
1600
  "button",
821
1601
  {
822
1602
  type: "button",
@@ -854,8 +1634,7 @@ var headerStyle = (isDesktop) => ({
854
1634
  display: "flex",
855
1635
  alignItems: "center",
856
1636
  justifyContent: "space-between",
857
- padding: "16px 24px",
858
- borderBottom: "1px solid var(--dauth-border, rgba(255, 255, 255, 0.08))",
1637
+ padding: "16px 24px 0",
859
1638
  flexShrink: 0,
860
1639
  ...!isDesktop ? {
861
1640
  position: "sticky",
@@ -886,6 +1665,25 @@ var closeBtn = {
886
1665
  transition: "background-color 150ms, color 150ms",
887
1666
  padding: 0
888
1667
  };
1668
+ var tabBar = {
1669
+ display: "flex",
1670
+ padding: "0 24px",
1671
+ borderBottom: "1px solid var(--dauth-border, rgba(255, 255, 255, 0.08))",
1672
+ flexShrink: 0
1673
+ };
1674
+ var tabBtn = {
1675
+ flex: 1,
1676
+ padding: "12px 4px",
1677
+ fontSize: "var(--dauth-font-size-sm, 0.875rem)",
1678
+ fontWeight: 500,
1679
+ border: "none",
1680
+ borderBottom: "2px solid transparent",
1681
+ backgroundColor: "transparent",
1682
+ cursor: "pointer",
1683
+ transition: "color 150ms, border-color 150ms",
1684
+ fontFamily: "inherit",
1685
+ textAlign: "center"
1686
+ };
889
1687
  var bodyStyle = {
890
1688
  flex: 1,
891
1689
  overflowY: "auto",
@@ -912,6 +1710,18 @@ var avatarCircle = {
912
1710
  fontSize: "1.5rem",
913
1711
  fontWeight: 600
914
1712
  };
1713
+ var avatarOverlay = {
1714
+ position: "absolute",
1715
+ inset: 0,
1716
+ backgroundColor: "rgba(0, 0, 0, 0.4)",
1717
+ display: "flex",
1718
+ alignItems: "center",
1719
+ justifyContent: "center",
1720
+ borderRadius: "50%",
1721
+ opacity: 0.7,
1722
+ transition: "opacity 150ms",
1723
+ color: "#ffffff"
1724
+ };
915
1725
  var emailText = {
916
1726
  fontSize: "var(--dauth-font-size-sm, 0.875rem)",
917
1727
  color: "var(--dauth-text-secondary, #a1a1aa)"
@@ -926,7 +1736,9 @@ var statusMsg = (type) => ({
926
1736
  textAlign: "center",
927
1737
  lineHeight: 1.5
928
1738
  });
929
- var fieldGroup = { marginBottom: 16 };
1739
+ var fieldGroup = {
1740
+ marginBottom: 16
1741
+ };
930
1742
  var label = {
931
1743
  display: "block",
932
1744
  fontSize: "var(--dauth-font-size-sm, 0.875rem)",
@@ -956,12 +1768,88 @@ var inputBlurHandler = (e) => {
956
1768
  e.currentTarget.style.borderColor = "var(--dauth-border, rgba(255, 255, 255, 0.08))";
957
1769
  e.currentTarget.style.boxShadow = "none";
958
1770
  };
1771
+ var langBtn = {
1772
+ padding: "8px 16px",
1773
+ fontSize: "var(--dauth-font-size-sm, 0.875rem)",
1774
+ fontWeight: 500,
1775
+ border: "none",
1776
+ borderRadius: "var(--dauth-radius-sm, 8px)",
1777
+ cursor: "pointer",
1778
+ transition: "background-color 150ms, color 150ms",
1779
+ fontFamily: "inherit"
1780
+ };
959
1781
  var separator = {
960
1782
  height: 1,
961
1783
  backgroundColor: "var(--dauth-border, rgba(255, 255, 255, 0.08))",
962
1784
  margin: "24px 0",
963
1785
  border: "none"
964
1786
  };
1787
+ var outlineBtn = {
1788
+ padding: "6px 12px",
1789
+ fontSize: "var(--dauth-font-size-xs, 0.75rem)",
1790
+ fontWeight: 500,
1791
+ color: "var(--dauth-text-secondary, #a1a1aa)",
1792
+ backgroundColor: "transparent",
1793
+ border: "1px solid var(--dauth-border, rgba(255, 255, 255, 0.08))",
1794
+ borderRadius: "var(--dauth-radius-sm, 8px)",
1795
+ cursor: "pointer",
1796
+ transition: "background-color 150ms",
1797
+ fontFamily: "inherit"
1798
+ };
1799
+ var registerPanel = {
1800
+ padding: 16,
1801
+ borderRadius: "var(--dauth-radius-sm, 8px)",
1802
+ border: "1px solid var(--dauth-border, rgba(255, 255, 255, 0.08))",
1803
+ backgroundColor: "var(--dauth-surface-secondary, rgba(255, 255, 255, 0.04))",
1804
+ marginBottom: 12
1805
+ };
1806
+ var smallAccentBtn = {
1807
+ padding: "8px 16px",
1808
+ fontSize: "var(--dauth-font-size-sm, 0.875rem)",
1809
+ fontWeight: 500,
1810
+ color: "#ffffff",
1811
+ backgroundColor: "var(--dauth-accent, #6366f1)",
1812
+ border: "none",
1813
+ borderRadius: "var(--dauth-radius-sm, 8px)",
1814
+ cursor: "pointer",
1815
+ transition: "opacity 150ms",
1816
+ fontFamily: "inherit",
1817
+ display: "flex",
1818
+ alignItems: "center",
1819
+ gap: 6
1820
+ };
1821
+ var credentialRow = {
1822
+ display: "flex",
1823
+ alignItems: "center",
1824
+ justifyContent: "space-between",
1825
+ padding: 12,
1826
+ borderRadius: "var(--dauth-radius-sm, 8px)",
1827
+ backgroundColor: "var(--dauth-surface-secondary, rgba(255, 255, 255, 0.04))",
1828
+ marginBottom: 8
1829
+ };
1830
+ var trashBtn = {
1831
+ display: "flex",
1832
+ alignItems: "center",
1833
+ justifyContent: "center",
1834
+ width: 28,
1835
+ height: 28,
1836
+ border: "none",
1837
+ backgroundColor: "transparent",
1838
+ color: "var(--dauth-text-muted, #71717a)",
1839
+ cursor: "pointer",
1840
+ transition: "color 150ms",
1841
+ padding: 0,
1842
+ borderRadius: "var(--dauth-radius-sm, 8px)",
1843
+ flexShrink: 0
1844
+ };
1845
+ var emptyState = {
1846
+ display: "flex",
1847
+ alignItems: "center",
1848
+ gap: 12,
1849
+ padding: 16,
1850
+ borderRadius: "var(--dauth-radius-sm, 8px)",
1851
+ backgroundColor: "var(--dauth-surface-secondary, rgba(255, 255, 255, 0.04))"
1852
+ };
965
1853
  var dangerTitle = {
966
1854
  fontSize: "var(--dauth-font-size-sm, 0.875rem)",
967
1855
  fontWeight: 600,
@@ -1000,7 +1888,6 @@ var deletePanelText = {
1000
1888
  lineHeight: 1.5
1001
1889
  };
1002
1890
  var cancelBtn = {
1003
- flex: 1,
1004
1891
  padding: "8px 16px",
1005
1892
  fontSize: "var(--dauth-font-size-sm, 0.875rem)",
1006
1893
  fontWeight: 500,
@@ -1029,6 +1916,23 @@ var deleteConfirmBtn = {
1029
1916
  justifyContent: "center",
1030
1917
  gap: 8
1031
1918
  };
1919
+ var signOutBtn = {
1920
+ width: "100%",
1921
+ padding: "12px 24px",
1922
+ fontSize: "var(--dauth-font-size-base, 1rem)",
1923
+ fontWeight: 500,
1924
+ color: "var(--dauth-error, #ef4444)",
1925
+ backgroundColor: "var(--dauth-error-bg, rgba(239, 68, 68, 0.1))",
1926
+ border: "1px solid rgba(239, 68, 68, 0.2)",
1927
+ borderRadius: "var(--dauth-radius-sm, 8px)",
1928
+ cursor: "pointer",
1929
+ transition: "background-color 150ms",
1930
+ fontFamily: "inherit",
1931
+ display: "flex",
1932
+ alignItems: "center",
1933
+ justifyContent: "center",
1934
+ gap: 8
1935
+ };
1032
1936
  var footerStyle = (isDesktop) => ({
1033
1937
  padding: "16px 24px",
1034
1938
  borderTop: "1px solid var(--dauth-border, rgba(255, 255, 255, 0.08))",
@@ -1140,15 +2044,39 @@ var DauthProvider = (props) => {
1140
2044
  () => deleteAccountAction(ctx),
1141
2045
  [ctx]
1142
2046
  );
2047
+ const getPasskeyCredentials = (0, import_react2.useCallback)(
2048
+ () => getPasskeyCredentialsAction(ctx),
2049
+ [ctx]
2050
+ );
2051
+ const registerPasskey = (0, import_react2.useCallback)(
2052
+ (name) => registerPasskeyAction(ctx, name),
2053
+ [ctx]
2054
+ );
2055
+ const deletePasskeyCredential = (0, import_react2.useCallback)(
2056
+ (credentialId) => deletePasskeyCredentialAction(ctx, credentialId),
2057
+ [ctx]
2058
+ );
1143
2059
  const memoProvider = (0, import_react2.useMemo)(
1144
2060
  () => ({
1145
2061
  ...dauthState,
1146
2062
  loginWithRedirect,
1147
2063
  logout,
1148
2064
  updateUser,
1149
- deleteAccount
2065
+ deleteAccount,
2066
+ getPasskeyCredentials,
2067
+ registerPasskey,
2068
+ deletePasskeyCredential
1150
2069
  }),
1151
- [dauthState, loginWithRedirect, logout, updateUser, deleteAccount]
2070
+ [
2071
+ dauthState,
2072
+ loginWithRedirect,
2073
+ logout,
2074
+ updateUser,
2075
+ deleteAccount,
2076
+ getPasskeyCredentials,
2077
+ registerPasskey,
2078
+ deletePasskeyCredential
2079
+ ]
1152
2080
  );
1153
2081
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DauthContext.Provider, { value: memoProvider, children });
1154
2082
  };