dauth-context-react 6.1.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,16 +882,87 @@ 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]);
545
952
  const themeVars = (0, import_react.useMemo)(() => {
546
953
  const t = domain.modalTheme;
547
954
  if (!t) return {};
548
955
  const vars = {};
549
956
  if (t.accent) vars["--dauth-accent"] = t.accent;
550
- if (t.accentHover) vars["--dauth-accent-hover"] = t.accentHover;
957
+ if (t.accentHover)
958
+ vars["--dauth-accent-hover"] = t.accentHover;
551
959
  if (t.surface) vars["--dauth-surface"] = t.surface;
552
- if (t.surfaceHover) vars["--dauth-surface-hover"] = t.surfaceHover;
553
- if (t.textPrimary) vars["--dauth-text-primary"] = t.textPrimary;
554
- if (t.textSecondary) vars["--dauth-text-secondary"] = t.textSecondary;
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;
555
966
  if (t.border) vars["--dauth-border"] = t.border;
556
967
  return vars;
557
968
  }, [domain.modalTheme]);
@@ -602,6 +1013,11 @@ function DauthProfileModal({ open, onClose }) {
602
1013
  transition: `transform ${dur}ms ${easing}`
603
1014
  };
604
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
+ ];
605
1021
  return (0, import_react_dom.createPortal)(
606
1022
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
607
1023
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -645,191 +1061,542 @@ function DauthProfileModal({ open, onClose }) {
645
1061
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { id: "dauth-profile-title", style: titleStyle, children: "Your Profile" }),
646
1062
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { width: 36 } })
647
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
+ )) }),
648
1080
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: bodyStyle, children: [
649
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: avatarSection, children: [
650
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: avatarCircle, children: user.avatar?.url ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
651
- "img",
652
- {
653
- src: user.avatar.url,
654
- alt: "",
655
- style: {
656
- width: "100%",
657
- height: "100%",
658
- objectFit: "cover"
659
- }
660
- }
661
- ) : avatarInitial }),
662
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: emailText, children: user.email })
663
- ] }),
664
- status && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
665
- "div",
666
- {
667
- role: "status",
668
- "aria-live": "polite",
669
- style: statusMsg(status.type),
670
- children: status.message
671
- }
672
- ),
673
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
674
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: fieldGroup, children: [
675
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "dauth-name", style: label, children: "Name *" }),
676
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
677
- "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",
678
1085
  {
679
- id: "dauth-name",
680
- type: "text",
681
- value: name,
682
- onChange: (e) => setName(e.target.value),
683
- placeholder: "Your name",
684
- disabled: saving,
685
- style: input,
686
- onFocus: inputFocusHandler,
687
- 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
+ ]
688
1107
  }
689
- )
690
- ] }),
691
- hasField("lastname") && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: fieldGroup, children: [
692
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { htmlFor: "dauth-lastname", style: label, children: [
693
- "Last name",
694
- isRequired("lastname") ? " *" : ""
695
- ] }),
696
- /* @__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)(
697
1111
  "input",
698
1112
  {
699
- id: "dauth-lastname",
700
- type: "text",
701
- value: lastname,
702
- onChange: (e) => setLastname(e.target.value),
703
- placeholder: "Your last name",
704
- disabled: saving,
705
- style: input,
706
- onFocus: inputFocusHandler,
707
- onBlur: inputBlurHandler
1113
+ ref: avatarInputRef,
1114
+ type: "file",
1115
+ accept: "image/*",
1116
+ style: { display: "none" },
1117
+ onChange: handleAvatarChange
708
1118
  }
709
1119
  )
710
1120
  ] }),
711
- hasField("nickname") && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: fieldGroup, children: [
712
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { htmlFor: "dauth-nickname", style: label, children: [
713
- "Nickname",
714
- 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
+ )
715
1154
  ] }),
716
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
717
- "input",
718
- {
719
- id: "dauth-nickname",
720
- type: "text",
721
- value: nickname,
722
- onChange: (e) => setNickname(e.target.value),
723
- placeholder: "Choose a nickname",
724
- disabled: saving,
725
- style: input,
726
- onFocus: inputFocusHandler,
727
- onBlur: inputBlurHandler
728
- }
729
- )
730
- ] }),
731
- hasField("country") && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: fieldGroup, children: [
732
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { htmlFor: "dauth-country", style: label, children: [
733
- "Country",
734
- 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
+ )
735
1181
  ] }),
736
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
737
- "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",
738
1242
  {
739
- id: "dauth-country",
740
- type: "text",
741
- value: country,
742
- onChange: (e) => setCountry(e.target.value),
743
- placeholder: "Your country",
744
- disabled: saving,
745
- style: input,
746
- onFocus: inputFocusHandler,
747
- onBlur: inputBlurHandler
748
- }
749
- )
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
+ )) })
750
1254
  ] })
751
1255
  ] }),
752
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("hr", { style: separator }),
753
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
754
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: dangerTitle, children: "Delete account" }),
755
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: dangerDesc, children: "Permanently delete your account and all associated data." }),
756
- !showDelete ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
757
- "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",
758
1259
  {
759
- type: "button",
760
- style: deleteBtn,
761
- onClick: () => setShowDelete(true),
762
- onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "rgba(239, 68, 68, 0.2)",
763
- onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "var(--dauth-error-bg, rgba(239, 68, 68, 0.1))",
764
- 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
+ ]
765
1290
  }
766
- ) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: deletePanel, children: [
767
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: deletePanelText, children: [
768
- "This action is permanent and cannot be undone. Type",
769
- " ",
770
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: CONFIRM_WORD }),
771
- " 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
+ )
772
1316
  ] }),
773
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
774
- "input",
775
- {
776
- type: "text",
777
- value: deleteText,
778
- onChange: (e) => setDeleteText(e.target.value),
779
- placeholder: `Type ${CONFIRM_WORD}`,
780
- style: input,
781
- onFocus: inputFocusHandler,
782
- onBlur: inputBlurHandler,
783
- disabled: deleting
784
- }
785
- ),
786
1317
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
787
1318
  "div",
788
1319
  {
789
1320
  style: {
790
1321
  display: "flex",
791
- gap: 8,
792
- marginTop: 12
1322
+ gap: 8
793
1323
  },
794
1324
  children: [
795
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1325
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
796
1326
  "button",
797
1327
  {
798
1328
  type: "button",
799
- style: cancelBtn,
800
- onClick: () => {
801
- setShowDelete(false);
802
- setDeleteText("");
1329
+ style: {
1330
+ ...smallAccentBtn,
1331
+ opacity: registering ? 0.6 : 1
803
1332
  },
804
- onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "var(--dauth-surface-hover, #232340)",
805
- onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "transparent",
806
- 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
+ ]
807
1339
  }
808
1340
  ),
809
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1341
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
810
1342
  "button",
811
1343
  {
812
1344
  type: "button",
813
- style: {
814
- ...deleteConfirmBtn,
815
- opacity: deleteText !== CONFIRM_WORD || deleting ? 0.5 : 1,
816
- cursor: deleteText !== CONFIRM_WORD || deleting ? "not-allowed" : "pointer"
817
- },
818
- disabled: deleteText !== CONFIRM_WORD || deleting,
819
- onClick: handleDelete,
820
- children: [
821
- deleting && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}),
822
- "Delete my account"
823
- ]
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"
824
1350
  }
825
1351
  )
826
1352
  ]
827
1353
  }
828
1354
  )
829
- ] })
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
+ )
830
1597
  ] })
831
1598
  ] }),
832
- /* @__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)(
833
1600
  "button",
834
1601
  {
835
1602
  type: "button",
@@ -867,8 +1634,7 @@ var headerStyle = (isDesktop) => ({
867
1634
  display: "flex",
868
1635
  alignItems: "center",
869
1636
  justifyContent: "space-between",
870
- padding: "16px 24px",
871
- borderBottom: "1px solid var(--dauth-border, rgba(255, 255, 255, 0.08))",
1637
+ padding: "16px 24px 0",
872
1638
  flexShrink: 0,
873
1639
  ...!isDesktop ? {
874
1640
  position: "sticky",
@@ -899,6 +1665,25 @@ var closeBtn = {
899
1665
  transition: "background-color 150ms, color 150ms",
900
1666
  padding: 0
901
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
+ };
902
1687
  var bodyStyle = {
903
1688
  flex: 1,
904
1689
  overflowY: "auto",
@@ -925,6 +1710,18 @@ var avatarCircle = {
925
1710
  fontSize: "1.5rem",
926
1711
  fontWeight: 600
927
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
+ };
928
1725
  var emailText = {
929
1726
  fontSize: "var(--dauth-font-size-sm, 0.875rem)",
930
1727
  color: "var(--dauth-text-secondary, #a1a1aa)"
@@ -939,7 +1736,9 @@ var statusMsg = (type) => ({
939
1736
  textAlign: "center",
940
1737
  lineHeight: 1.5
941
1738
  });
942
- var fieldGroup = { marginBottom: 16 };
1739
+ var fieldGroup = {
1740
+ marginBottom: 16
1741
+ };
943
1742
  var label = {
944
1743
  display: "block",
945
1744
  fontSize: "var(--dauth-font-size-sm, 0.875rem)",
@@ -969,12 +1768,88 @@ var inputBlurHandler = (e) => {
969
1768
  e.currentTarget.style.borderColor = "var(--dauth-border, rgba(255, 255, 255, 0.08))";
970
1769
  e.currentTarget.style.boxShadow = "none";
971
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
+ };
972
1781
  var separator = {
973
1782
  height: 1,
974
1783
  backgroundColor: "var(--dauth-border, rgba(255, 255, 255, 0.08))",
975
1784
  margin: "24px 0",
976
1785
  border: "none"
977
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
+ };
978
1853
  var dangerTitle = {
979
1854
  fontSize: "var(--dauth-font-size-sm, 0.875rem)",
980
1855
  fontWeight: 600,
@@ -1013,7 +1888,6 @@ var deletePanelText = {
1013
1888
  lineHeight: 1.5
1014
1889
  };
1015
1890
  var cancelBtn = {
1016
- flex: 1,
1017
1891
  padding: "8px 16px",
1018
1892
  fontSize: "var(--dauth-font-size-sm, 0.875rem)",
1019
1893
  fontWeight: 500,
@@ -1042,6 +1916,23 @@ var deleteConfirmBtn = {
1042
1916
  justifyContent: "center",
1043
1917
  gap: 8
1044
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
+ };
1045
1936
  var footerStyle = (isDesktop) => ({
1046
1937
  padding: "16px 24px",
1047
1938
  borderTop: "1px solid var(--dauth-border, rgba(255, 255, 255, 0.08))",
@@ -1153,15 +2044,39 @@ var DauthProvider = (props) => {
1153
2044
  () => deleteAccountAction(ctx),
1154
2045
  [ctx]
1155
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
+ );
1156
2059
  const memoProvider = (0, import_react2.useMemo)(
1157
2060
  () => ({
1158
2061
  ...dauthState,
1159
2062
  loginWithRedirect,
1160
2063
  logout,
1161
2064
  updateUser,
1162
- deleteAccount
2065
+ deleteAccount,
2066
+ getPasskeyCredentials,
2067
+ registerPasskey,
2068
+ deletePasskeyCredential
1163
2069
  }),
1164
- [dauthState, loginWithRedirect, logout, updateUser, deleteAccount]
2070
+ [
2071
+ dauthState,
2072
+ loginWithRedirect,
2073
+ logout,
2074
+ updateUser,
2075
+ deleteAccount,
2076
+ getPasskeyCredentials,
2077
+ registerPasskey,
2078
+ deletePasskeyCredential
2079
+ ]
1165
2080
  );
1166
2081
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DauthContext.Provider, { value: memoProvider, children });
1167
2082
  };