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.mjs CHANGED
@@ -21,7 +21,10 @@ var initialDauthState = {
21
21
  logout: () => {
22
22
  },
23
23
  updateUser: () => Promise.resolve(false),
24
- deleteAccount: () => Promise.resolve(false)
24
+ deleteAccount: () => Promise.resolve(false),
25
+ getPasskeyCredentials: () => Promise.resolve([]),
26
+ registerPasskey: () => Promise.resolve(null),
27
+ deletePasskeyCredential: () => Promise.resolve(false)
25
28
  };
26
29
  var initialDauthState_default = initialDauthState;
27
30
 
@@ -128,6 +131,134 @@ async function deleteAccountAPI(basePath) {
128
131
  const data = await response.json();
129
132
  return { response, data };
130
133
  }
134
+ async function getPasskeyCredentialsAPI(basePath) {
135
+ const response = await fetch(
136
+ `${basePath}/passkey/credentials`,
137
+ {
138
+ method: "GET",
139
+ headers: { "X-CSRF-Token": getCsrfToken() },
140
+ credentials: "include"
141
+ }
142
+ );
143
+ const data = await response.json();
144
+ return { response, data };
145
+ }
146
+ async function startPasskeyRegistrationAPI(basePath) {
147
+ const response = await fetch(
148
+ `${basePath}/passkey/register/start`,
149
+ {
150
+ method: "POST",
151
+ headers: {
152
+ "Content-Type": "application/json",
153
+ "X-CSRF-Token": getCsrfToken()
154
+ },
155
+ credentials: "include"
156
+ }
157
+ );
158
+ const data = await response.json();
159
+ return { response, data };
160
+ }
161
+ async function finishPasskeyRegistrationAPI(basePath, body) {
162
+ const response = await fetch(
163
+ `${basePath}/passkey/register/finish`,
164
+ {
165
+ method: "POST",
166
+ headers: {
167
+ "Content-Type": "application/json",
168
+ "X-CSRF-Token": getCsrfToken()
169
+ },
170
+ credentials: "include",
171
+ body: JSON.stringify(body)
172
+ }
173
+ );
174
+ const data = await response.json();
175
+ return { response, data };
176
+ }
177
+ async function deletePasskeyCredentialAPI(basePath, credentialId) {
178
+ const response = await fetch(
179
+ `${basePath}/passkey/credentials/${credentialId}`,
180
+ {
181
+ method: "DELETE",
182
+ headers: { "X-CSRF-Token": getCsrfToken() },
183
+ credentials: "include"
184
+ }
185
+ );
186
+ const data = await response.json();
187
+ return { response, data };
188
+ }
189
+
190
+ // src/webauthn.ts
191
+ function base64urlToBuffer(base64url) {
192
+ const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
193
+ const pad = base64.length % 4;
194
+ const padded = pad ? base64 + "=".repeat(4 - pad) : base64;
195
+ const binary = atob(padded);
196
+ const bytes = new Uint8Array(binary.length);
197
+ for (let i = 0; i < binary.length; i++) {
198
+ bytes[i] = binary.charCodeAt(i);
199
+ }
200
+ return bytes.buffer;
201
+ }
202
+ function bufferToBase64url(buffer) {
203
+ const bytes = new Uint8Array(buffer);
204
+ let binary = "";
205
+ for (let i = 0; i < bytes.length; i++) {
206
+ binary += String.fromCharCode(bytes[i]);
207
+ }
208
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
209
+ }
210
+ async function createPasskeyCredential(options) {
211
+ const publicKey = {
212
+ ...options,
213
+ challenge: base64urlToBuffer(options.challenge),
214
+ user: {
215
+ ...options.user,
216
+ id: base64urlToBuffer(options.user.id)
217
+ },
218
+ excludeCredentials: (options.excludeCredentials ?? []).map(
219
+ (c) => ({
220
+ ...c,
221
+ id: base64urlToBuffer(c.id)
222
+ })
223
+ )
224
+ };
225
+ const credential = await navigator.credentials.create({
226
+ publicKey
227
+ });
228
+ if (!credential) {
229
+ throw new Error("Passkey registration was cancelled");
230
+ }
231
+ const attestation = credential.response;
232
+ return {
233
+ id: credential.id,
234
+ rawId: bufferToBase64url(credential.rawId),
235
+ type: credential.type,
236
+ response: {
237
+ clientDataJSON: bufferToBase64url(
238
+ attestation.clientDataJSON
239
+ ),
240
+ attestationObject: bufferToBase64url(
241
+ attestation.attestationObject
242
+ ),
243
+ ...attestation.getTransports ? { transports: attestation.getTransports() } : {},
244
+ ...attestation.getPublicKeyAlgorithm ? {
245
+ publicKeyAlgorithm: attestation.getPublicKeyAlgorithm()
246
+ } : {},
247
+ ...attestation.getPublicKey ? {
248
+ publicKey: bufferToBase64url(
249
+ attestation.getPublicKey()
250
+ )
251
+ } : {},
252
+ ...attestation.getAuthenticatorData ? {
253
+ authenticatorData: bufferToBase64url(
254
+ attestation.getAuthenticatorData()
255
+ )
256
+ } : {}
257
+ },
258
+ clientExtensionResults: credential.getClientExtensionResults(),
259
+ authenticatorAttachment: credential.authenticatorAttachment ?? void 0
260
+ };
261
+ }
131
262
 
132
263
  // src/reducer/dauth.actions.ts
133
264
  async function exchangeCodeAction(ctx, code) {
@@ -251,6 +382,65 @@ async function deleteAccountAction(ctx) {
251
382
  return false;
252
383
  }
253
384
  }
385
+ async function getPasskeyCredentialsAction(ctx) {
386
+ const { authProxyPath, onError } = ctx;
387
+ try {
388
+ const result = await getPasskeyCredentialsAPI(authProxyPath);
389
+ if (result.response.status === 200) {
390
+ return result.data.credentials ?? [];
391
+ }
392
+ return [];
393
+ } catch (error) {
394
+ onError(
395
+ error instanceof Error ? error : new Error("Get passkey credentials error")
396
+ );
397
+ return [];
398
+ }
399
+ }
400
+ async function registerPasskeyAction(ctx, name) {
401
+ const { authProxyPath, onError } = ctx;
402
+ try {
403
+ const startResult = await startPasskeyRegistrationAPI(authProxyPath);
404
+ if (startResult.response.status !== 200) {
405
+ onError(new Error("Failed to start passkey registration"));
406
+ return null;
407
+ }
408
+ const credential = await createPasskeyCredential(
409
+ startResult.data
410
+ );
411
+ const finishResult = await finishPasskeyRegistrationAPI(
412
+ authProxyPath,
413
+ { credential, name }
414
+ );
415
+ if (finishResult.response.status === 200 || finishResult.response.status === 201) {
416
+ return finishResult.data.credential;
417
+ }
418
+ onError(
419
+ new Error("Failed to finish passkey registration")
420
+ );
421
+ return null;
422
+ } catch (error) {
423
+ onError(
424
+ error instanceof Error ? error : new Error("Passkey registration error")
425
+ );
426
+ return null;
427
+ }
428
+ }
429
+ async function deletePasskeyCredentialAction(ctx, credentialId) {
430
+ const { authProxyPath, onError } = ctx;
431
+ try {
432
+ const result = await deletePasskeyCredentialAPI(
433
+ authProxyPath,
434
+ credentialId
435
+ );
436
+ return result.response.status === 200;
437
+ } catch (error) {
438
+ onError(
439
+ error instanceof Error ? error : new Error("Delete passkey error")
440
+ );
441
+ return false;
442
+ }
443
+ }
254
444
  var resetUser = (dispatch) => {
255
445
  return dispatch({
256
446
  type: LOGIN,
@@ -348,6 +538,107 @@ function IconBack() {
348
538
  }
349
539
  );
350
540
  }
541
+ function IconFingerprint() {
542
+ return /* @__PURE__ */ jsxs(
543
+ "svg",
544
+ {
545
+ width: "16",
546
+ height: "16",
547
+ viewBox: "0 0 24 24",
548
+ fill: "none",
549
+ stroke: "currentColor",
550
+ strokeWidth: "2",
551
+ strokeLinecap: "round",
552
+ strokeLinejoin: "round",
553
+ children: [
554
+ /* @__PURE__ */ jsx("path", { d: "M12 10a2 2 0 0 0-2 2c0 1.02-.1 2.51-.26 4" }),
555
+ /* @__PURE__ */ jsx("path", { d: "M14 13.12c0 2.38 0 6.38-1 8.88" }),
556
+ /* @__PURE__ */ jsx("path", { d: "M17.29 21.02c.12-.6.43-2.3.5-3.02" }),
557
+ /* @__PURE__ */ jsx("path", { d: "M2 12a10 10 0 0 1 18-6" }),
558
+ /* @__PURE__ */ jsx("path", { d: "M2 16h.01" }),
559
+ /* @__PURE__ */ jsx("path", { d: "M21.8 16c.2-2 .131-5.354 0-6" }),
560
+ /* @__PURE__ */ jsx("path", { d: "M5 19.5C5.5 18 6 15 6 12a6 6 0 0 1 .34-2" }),
561
+ /* @__PURE__ */ jsx("path", { d: "M8.65 22c.21-.66.45-1.32.57-2" }),
562
+ /* @__PURE__ */ jsx("path", { d: "M9 6.8a6 6 0 0 1 9 5.2v2" })
563
+ ]
564
+ }
565
+ );
566
+ }
567
+ function IconShield() {
568
+ return /* @__PURE__ */ jsx(
569
+ "svg",
570
+ {
571
+ width: "20",
572
+ height: "20",
573
+ viewBox: "0 0 24 24",
574
+ fill: "none",
575
+ stroke: "currentColor",
576
+ strokeWidth: "2",
577
+ strokeLinecap: "round",
578
+ strokeLinejoin: "round",
579
+ children: /* @__PURE__ */ 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" })
580
+ }
581
+ );
582
+ }
583
+ function IconTrash() {
584
+ return /* @__PURE__ */ jsxs(
585
+ "svg",
586
+ {
587
+ width: "14",
588
+ height: "14",
589
+ viewBox: "0 0 24 24",
590
+ fill: "none",
591
+ stroke: "currentColor",
592
+ strokeWidth: "2",
593
+ strokeLinecap: "round",
594
+ strokeLinejoin: "round",
595
+ children: [
596
+ /* @__PURE__ */ jsx("path", { d: "M3 6h18" }),
597
+ /* @__PURE__ */ jsx("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }),
598
+ /* @__PURE__ */ jsx("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" })
599
+ ]
600
+ }
601
+ );
602
+ }
603
+ function IconLogOut() {
604
+ return /* @__PURE__ */ jsxs(
605
+ "svg",
606
+ {
607
+ width: "16",
608
+ height: "16",
609
+ viewBox: "0 0 24 24",
610
+ fill: "none",
611
+ stroke: "currentColor",
612
+ strokeWidth: "2",
613
+ strokeLinecap: "round",
614
+ strokeLinejoin: "round",
615
+ children: [
616
+ /* @__PURE__ */ jsx("path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }),
617
+ /* @__PURE__ */ jsx("polyline", { points: "16 17 21 12 16 7" }),
618
+ /* @__PURE__ */ jsx("line", { x1: "21", y1: "12", x2: "9", y2: "12" })
619
+ ]
620
+ }
621
+ );
622
+ }
623
+ function IconCamera() {
624
+ return /* @__PURE__ */ jsxs(
625
+ "svg",
626
+ {
627
+ width: "14",
628
+ height: "14",
629
+ viewBox: "0 0 24 24",
630
+ fill: "none",
631
+ stroke: "currentColor",
632
+ strokeWidth: "2",
633
+ strokeLinecap: "round",
634
+ strokeLinejoin: "round",
635
+ children: [
636
+ /* @__PURE__ */ 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" }),
637
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "13", r: "3" })
638
+ ]
639
+ }
640
+ );
641
+ }
351
642
  function Spinner() {
352
643
  return /* @__PURE__ */ jsx("span", { style: spinnerStyle, "aria-hidden": "true" });
353
644
  }
@@ -375,7 +666,10 @@ function useModalAnimation(open) {
375
666
  }
376
667
  if (phase === "entered" || phase === "entering") {
377
668
  setPhase("exiting");
378
- const timer = setTimeout(() => setPhase("exited"), MOBILE_TRANSITION_MS);
669
+ const timer = setTimeout(
670
+ () => setPhase("exited"),
671
+ MOBILE_TRANSITION_MS
672
+ );
379
673
  return () => clearTimeout(timer);
380
674
  }
381
675
  return void 0;
@@ -443,11 +737,27 @@ function useScrollLock(active) {
443
737
  };
444
738
  }, [active]);
445
739
  }
446
- function DauthProfileModal({ open, onClose }) {
447
- const { user, domain, updateUser, deleteAccount } = useDauth();
740
+ function DauthProfileModal({
741
+ open,
742
+ onClose,
743
+ onAvatarUpload
744
+ }) {
745
+ const {
746
+ user,
747
+ domain,
748
+ updateUser,
749
+ deleteAccount,
750
+ logout,
751
+ getPasskeyCredentials,
752
+ registerPasskey,
753
+ deletePasskeyCredential
754
+ } = useDauth();
448
755
  const isDesktop = useMediaQuery("(min-width: 641px)");
449
756
  const phase = useModalAnimation(open);
450
757
  const modalRef = useRef(null);
758
+ const avatarInputRef = useRef(null);
759
+ const showSecurity = domain.authMethods?.passkey === true;
760
+ const [activeTab, setActiveTab] = useState("profile");
451
761
  const [name, setName] = useState("");
452
762
  const [lastname, setLastname] = useState("");
453
763
  const [nickname, setNickname] = useState("");
@@ -458,6 +768,13 @@ function DauthProfileModal({ open, onClose }) {
458
768
  const [showDelete, setShowDelete] = useState(false);
459
769
  const [deleteText, setDeleteText] = useState("");
460
770
  const [deleting, setDeleting] = useState(false);
771
+ const [credentials, setCredentials] = useState([]);
772
+ const [loadingCreds, setLoadingCreds] = useState(false);
773
+ const [showRegister, setShowRegister] = useState(false);
774
+ const [passkeyName, setPasskeyName] = useState("");
775
+ const [registering, setRegistering] = useState(false);
776
+ const [passkeyStatus, setPasskeyStatus] = useState(null);
777
+ const [uploadingAvatar, setUploadingAvatar] = useState(false);
461
778
  useEffect(() => {
462
779
  if (open && user?._id && !populated) {
463
780
  setName(user.name || "");
@@ -471,13 +788,36 @@ function DauthProfileModal({ open, onClose }) {
471
788
  setStatus(null);
472
789
  setShowDelete(false);
473
790
  setDeleteText("");
791
+ setActiveTab("profile");
792
+ setPasskeyStatus(null);
793
+ setShowRegister(false);
794
+ setPasskeyName("");
474
795
  }
475
796
  }, [open, user, populated]);
797
+ useEffect(() => {
798
+ if (activeTab !== "security" || !showSecurity) return;
799
+ setLoadingCreds(true);
800
+ getPasskeyCredentials().then((creds) => {
801
+ setCredentials(creds);
802
+ setLoadingCreds(false);
803
+ });
804
+ }, [activeTab, showSecurity, getPasskeyCredentials]);
476
805
  useEffect(() => {
477
806
  if (status?.type !== "success") return;
478
- const timer = setTimeout(() => setStatus(null), SUCCESS_TIMEOUT_MS);
807
+ const timer = setTimeout(
808
+ () => setStatus(null),
809
+ SUCCESS_TIMEOUT_MS
810
+ );
479
811
  return () => clearTimeout(timer);
480
812
  }, [status]);
813
+ useEffect(() => {
814
+ if (passkeyStatus?.type !== "success") return;
815
+ const timer = setTimeout(
816
+ () => setPasskeyStatus(null),
817
+ SUCCESS_TIMEOUT_MS
818
+ );
819
+ return () => clearTimeout(timer);
820
+ }, [passkeyStatus]);
481
821
  useFocusTrap(modalRef, phase === "entered", onClose);
482
822
  useScrollLock(phase !== "exited");
483
823
  const hasField = useCallback(
@@ -529,16 +869,87 @@ function DauthProfileModal({ open, onClose }) {
529
869
  setDeleteText("");
530
870
  }
531
871
  }, [deleteAccount, onClose]);
872
+ const handleLanguage = useCallback(
873
+ async (lang) => {
874
+ await updateUser({ language: lang });
875
+ },
876
+ [updateUser]
877
+ );
878
+ const handleRegisterPasskey = useCallback(async () => {
879
+ setRegistering(true);
880
+ setPasskeyStatus(null);
881
+ const cred = await registerPasskey(
882
+ passkeyName || void 0
883
+ );
884
+ setRegistering(false);
885
+ if (cred) {
886
+ setCredentials((prev) => [...prev, cred]);
887
+ setPasskeyName("");
888
+ setShowRegister(false);
889
+ setPasskeyStatus({
890
+ type: "success",
891
+ message: "Passkey registered successfully"
892
+ });
893
+ } else {
894
+ setPasskeyStatus({
895
+ type: "error",
896
+ message: "Failed to register passkey"
897
+ });
898
+ }
899
+ }, [passkeyName, registerPasskey]);
900
+ const handleDeletePasskey = useCallback(
901
+ async (credentialId) => {
902
+ const ok = await deletePasskeyCredential(credentialId);
903
+ if (ok) {
904
+ setCredentials(
905
+ (prev) => prev.filter((c) => c._id !== credentialId)
906
+ );
907
+ }
908
+ },
909
+ [deletePasskeyCredential]
910
+ );
911
+ const handleAvatarClick = useCallback(() => {
912
+ if (onAvatarUpload) {
913
+ avatarInputRef.current?.click();
914
+ }
915
+ }, [onAvatarUpload]);
916
+ const handleAvatarChange = useCallback(
917
+ async (e) => {
918
+ const file = e.target.files?.[0];
919
+ if (!file || !onAvatarUpload) return;
920
+ setUploadingAvatar(true);
921
+ try {
922
+ const url = await onAvatarUpload(file);
923
+ if (url) {
924
+ await updateUser({ avatar: url });
925
+ }
926
+ } catch {
927
+ }
928
+ setUploadingAvatar(false);
929
+ if (avatarInputRef.current) {
930
+ avatarInputRef.current.value = "";
931
+ }
932
+ },
933
+ [onAvatarUpload, updateUser]
934
+ );
935
+ const handleSignOut = useCallback(() => {
936
+ logout();
937
+ onClose();
938
+ }, [logout, onClose]);
532
939
  const themeVars = useMemo(() => {
533
940
  const t = domain.modalTheme;
534
941
  if (!t) return {};
535
942
  const vars = {};
536
943
  if (t.accent) vars["--dauth-accent"] = t.accent;
537
- if (t.accentHover) vars["--dauth-accent-hover"] = t.accentHover;
944
+ if (t.accentHover)
945
+ vars["--dauth-accent-hover"] = t.accentHover;
538
946
  if (t.surface) vars["--dauth-surface"] = t.surface;
539
- if (t.surfaceHover) vars["--dauth-surface-hover"] = t.surfaceHover;
540
- if (t.textPrimary) vars["--dauth-text-primary"] = t.textPrimary;
541
- if (t.textSecondary) vars["--dauth-text-secondary"] = t.textSecondary;
947
+ if (t.surfaceHover)
948
+ vars["--dauth-surface-hover"] = t.surfaceHover;
949
+ if (t.textPrimary)
950
+ vars["--dauth-text-primary"] = t.textPrimary;
951
+ if (t.textSecondary)
952
+ vars["--dauth-text-secondary"] = t.textSecondary;
542
953
  if (t.border) vars["--dauth-border"] = t.border;
543
954
  return vars;
544
955
  }, [domain.modalTheme]);
@@ -589,6 +1000,11 @@ function DauthProfileModal({ open, onClose }) {
589
1000
  transition: `transform ${dur}ms ${easing}`
590
1001
  };
591
1002
  const avatarInitial = (user.name || user.email || "?").charAt(0).toUpperCase();
1003
+ const tabs = [
1004
+ { key: "profile", label: "Profile" },
1005
+ ...showSecurity ? [{ key: "security", label: "Security" }] : [],
1006
+ { key: "account", label: "Account" }
1007
+ ];
592
1008
  return createPortal(
593
1009
  /* @__PURE__ */ jsxs(Fragment, { children: [
594
1010
  /* @__PURE__ */ jsx(
@@ -632,191 +1048,542 @@ function DauthProfileModal({ open, onClose }) {
632
1048
  /* @__PURE__ */ jsx("h2", { id: "dauth-profile-title", style: titleStyle, children: "Your Profile" }),
633
1049
  /* @__PURE__ */ jsx("div", { style: { width: 36 } })
634
1050
  ] }),
1051
+ /* @__PURE__ */ jsx("div", { style: tabBar, role: "tablist", children: tabs.map((t) => /* @__PURE__ */ jsx(
1052
+ "button",
1053
+ {
1054
+ role: "tab",
1055
+ type: "button",
1056
+ "aria-selected": activeTab === t.key,
1057
+ style: {
1058
+ ...tabBtn,
1059
+ color: activeTab === t.key ? "var(--dauth-accent, #6366f1)" : "var(--dauth-text-secondary, #a1a1aa)",
1060
+ borderBottomColor: activeTab === t.key ? "var(--dauth-accent, #6366f1)" : "transparent"
1061
+ },
1062
+ onClick: () => setActiveTab(t.key),
1063
+ children: t.label
1064
+ },
1065
+ t.key
1066
+ )) }),
635
1067
  /* @__PURE__ */ jsxs("div", { style: bodyStyle, children: [
636
- /* @__PURE__ */ jsxs("div", { style: avatarSection, children: [
637
- /* @__PURE__ */ jsx("div", { style: avatarCircle, children: user.avatar?.url ? /* @__PURE__ */ 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__ */ jsx("div", { style: emailText, children: user.email })
650
- ] }),
651
- status && /* @__PURE__ */ jsx(
652
- "div",
653
- {
654
- role: "status",
655
- "aria-live": "polite",
656
- style: statusMsg(status.type),
657
- children: status.message
658
- }
659
- ),
660
- /* @__PURE__ */ jsxs("div", { children: [
661
- /* @__PURE__ */ jsxs("div", { style: fieldGroup, children: [
662
- /* @__PURE__ */ jsx("label", { htmlFor: "dauth-name", style: label, children: "Name *" }),
663
- /* @__PURE__ */ jsx(
664
- "input",
1068
+ activeTab === "profile" && /* @__PURE__ */ jsxs(Fragment, { children: [
1069
+ /* @__PURE__ */ jsxs("div", { style: avatarSection, children: [
1070
+ /* @__PURE__ */ jsxs(
1071
+ "div",
665
1072
  {
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
1073
+ style: {
1074
+ ...avatarCircle,
1075
+ cursor: onAvatarUpload ? "pointer" : "default",
1076
+ position: "relative"
1077
+ },
1078
+ onClick: handleAvatarClick,
1079
+ children: [
1080
+ uploadingAvatar ? /* @__PURE__ */ jsx(Spinner, {}) : user.avatar?.url ? /* @__PURE__ */ jsx(
1081
+ "img",
1082
+ {
1083
+ src: user.avatar.url,
1084
+ alt: "",
1085
+ style: {
1086
+ width: "100%",
1087
+ height: "100%",
1088
+ objectFit: "cover"
1089
+ }
1090
+ }
1091
+ ) : avatarInitial,
1092
+ onAvatarUpload && !uploadingAvatar && /* @__PURE__ */ jsx("div", { style: avatarOverlay, children: /* @__PURE__ */ jsx(IconCamera, {}) })
1093
+ ]
675
1094
  }
676
- )
677
- ] }),
678
- hasField("lastname") && /* @__PURE__ */ jsxs("div", { style: fieldGroup, children: [
679
- /* @__PURE__ */ jsxs("label", { htmlFor: "dauth-lastname", style: label, children: [
680
- "Last name",
681
- isRequired("lastname") ? " *" : ""
682
- ] }),
683
- /* @__PURE__ */ jsx(
1095
+ ),
1096
+ /* @__PURE__ */ jsx("div", { style: emailText, children: user.email }),
1097
+ onAvatarUpload && /* @__PURE__ */ jsx(
684
1098
  "input",
685
1099
  {
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
1100
+ ref: avatarInputRef,
1101
+ type: "file",
1102
+ accept: "image/*",
1103
+ style: { display: "none" },
1104
+ onChange: handleAvatarChange
695
1105
  }
696
1106
  )
697
1107
  ] }),
698
- hasField("nickname") && /* @__PURE__ */ jsxs("div", { style: fieldGroup, children: [
699
- /* @__PURE__ */ jsxs("label", { htmlFor: "dauth-nickname", style: label, children: [
700
- "Nickname",
701
- isRequired("nickname") ? " *" : ""
1108
+ status && /* @__PURE__ */ jsx(
1109
+ "div",
1110
+ {
1111
+ role: "status",
1112
+ "aria-live": "polite",
1113
+ style: statusMsg(status.type),
1114
+ children: status.message
1115
+ }
1116
+ ),
1117
+ /* @__PURE__ */ jsxs("div", { children: [
1118
+ /* @__PURE__ */ jsxs("div", { style: fieldGroup, children: [
1119
+ /* @__PURE__ */ jsx(
1120
+ "label",
1121
+ {
1122
+ htmlFor: "dauth-name",
1123
+ style: label,
1124
+ children: "Name *"
1125
+ }
1126
+ ),
1127
+ /* @__PURE__ */ jsx(
1128
+ "input",
1129
+ {
1130
+ id: "dauth-name",
1131
+ type: "text",
1132
+ value: name,
1133
+ onChange: (e) => setName(e.target.value),
1134
+ placeholder: "Your name",
1135
+ disabled: saving,
1136
+ style: input,
1137
+ onFocus: inputFocusHandler,
1138
+ onBlur: inputBlurHandler
1139
+ }
1140
+ )
702
1141
  ] }),
703
- /* @__PURE__ */ 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__ */ jsxs("div", { style: fieldGroup, children: [
719
- /* @__PURE__ */ jsxs("label", { htmlFor: "dauth-country", style: label, children: [
720
- "Country",
721
- isRequired("country") ? " *" : ""
1142
+ hasField("lastname") && /* @__PURE__ */ jsxs("div", { style: fieldGroup, children: [
1143
+ /* @__PURE__ */ jsxs(
1144
+ "label",
1145
+ {
1146
+ htmlFor: "dauth-lastname",
1147
+ style: label,
1148
+ children: [
1149
+ "Last name",
1150
+ isRequired("lastname") ? " *" : ""
1151
+ ]
1152
+ }
1153
+ ),
1154
+ /* @__PURE__ */ jsx(
1155
+ "input",
1156
+ {
1157
+ id: "dauth-lastname",
1158
+ type: "text",
1159
+ value: lastname,
1160
+ onChange: (e) => setLastname(e.target.value),
1161
+ placeholder: "Your last name",
1162
+ disabled: saving,
1163
+ style: input,
1164
+ onFocus: inputFocusHandler,
1165
+ onBlur: inputBlurHandler
1166
+ }
1167
+ )
722
1168
  ] }),
723
- /* @__PURE__ */ jsx(
724
- "input",
1169
+ hasField("nickname") && /* @__PURE__ */ jsxs("div", { style: fieldGroup, children: [
1170
+ /* @__PURE__ */ jsxs(
1171
+ "label",
1172
+ {
1173
+ htmlFor: "dauth-nickname",
1174
+ style: label,
1175
+ children: [
1176
+ "Nickname",
1177
+ isRequired("nickname") ? " *" : ""
1178
+ ]
1179
+ }
1180
+ ),
1181
+ /* @__PURE__ */ jsx(
1182
+ "input",
1183
+ {
1184
+ id: "dauth-nickname",
1185
+ type: "text",
1186
+ value: nickname,
1187
+ onChange: (e) => setNickname(e.target.value),
1188
+ placeholder: "Choose a nickname",
1189
+ disabled: saving,
1190
+ style: input,
1191
+ onFocus: inputFocusHandler,
1192
+ onBlur: inputBlurHandler
1193
+ }
1194
+ )
1195
+ ] }),
1196
+ hasField("country") && /* @__PURE__ */ jsxs("div", { style: fieldGroup, children: [
1197
+ /* @__PURE__ */ jsxs(
1198
+ "label",
1199
+ {
1200
+ htmlFor: "dauth-country",
1201
+ style: label,
1202
+ children: [
1203
+ "Country",
1204
+ isRequired("country") ? " *" : ""
1205
+ ]
1206
+ }
1207
+ ),
1208
+ /* @__PURE__ */ jsx(
1209
+ "input",
1210
+ {
1211
+ id: "dauth-country",
1212
+ type: "text",
1213
+ value: country,
1214
+ onChange: (e) => setCountry(e.target.value),
1215
+ placeholder: "Your country",
1216
+ disabled: saving,
1217
+ style: input,
1218
+ onFocus: inputFocusHandler,
1219
+ onBlur: inputBlurHandler
1220
+ }
1221
+ )
1222
+ ] })
1223
+ ] }),
1224
+ /* @__PURE__ */ jsx("hr", { style: separator }),
1225
+ /* @__PURE__ */ jsxs("div", { style: fieldGroup, children: [
1226
+ /* @__PURE__ */ jsx("div", { style: label, children: "Language" }),
1227
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 8 }, children: ["es", "en"].map((lang) => /* @__PURE__ */ jsx(
1228
+ "button",
725
1229
  {
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
- )
1230
+ type: "button",
1231
+ style: {
1232
+ ...langBtn,
1233
+ backgroundColor: user.language === lang ? "var(--dauth-accent, #6366f1)" : "var(--dauth-surface-secondary, rgba(255, 255, 255, 0.04))",
1234
+ color: user.language === lang ? "#ffffff" : "var(--dauth-text-secondary, #a1a1aa)"
1235
+ },
1236
+ onClick: () => handleLanguage(lang),
1237
+ children: lang === "es" ? "Espa\xF1ol" : "English"
1238
+ },
1239
+ lang
1240
+ )) })
737
1241
  ] })
738
1242
  ] }),
739
- /* @__PURE__ */ jsx("hr", { style: separator }),
740
- /* @__PURE__ */ jsxs("div", { children: [
741
- /* @__PURE__ */ jsx("div", { style: dangerTitle, children: "Delete account" }),
742
- /* @__PURE__ */ jsx("div", { style: dangerDesc, children: "Permanently delete your account and all associated data." }),
743
- !showDelete ? /* @__PURE__ */ jsx(
744
- "button",
1243
+ activeTab === "security" && showSecurity && /* @__PURE__ */ jsxs(Fragment, { children: [
1244
+ /* @__PURE__ */ jsxs(
1245
+ "div",
745
1246
  {
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"
1247
+ style: {
1248
+ display: "flex",
1249
+ alignItems: "center",
1250
+ justifyContent: "space-between",
1251
+ marginBottom: 16
1252
+ },
1253
+ children: [
1254
+ /* @__PURE__ */ jsx(
1255
+ "div",
1256
+ {
1257
+ style: {
1258
+ ...label,
1259
+ marginBottom: 0,
1260
+ fontWeight: 600
1261
+ },
1262
+ children: "Passkeys"
1263
+ }
1264
+ ),
1265
+ /* @__PURE__ */ jsx(
1266
+ "button",
1267
+ {
1268
+ type: "button",
1269
+ style: outlineBtn,
1270
+ onClick: () => setShowRegister(!showRegister),
1271
+ onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "var(--dauth-surface-hover, #232340)",
1272
+ onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "transparent",
1273
+ children: "+ Add passkey"
1274
+ }
1275
+ )
1276
+ ]
752
1277
  }
753
- ) : /* @__PURE__ */ jsxs("div", { style: deletePanel, children: [
754
- /* @__PURE__ */ jsxs("div", { style: deletePanelText, children: [
755
- "This action is permanent and cannot be undone. Type",
756
- " ",
757
- /* @__PURE__ */ jsx("strong", { children: CONFIRM_WORD }),
758
- " to confirm."
1278
+ ),
1279
+ showRegister && /* @__PURE__ */ jsxs("div", { style: registerPanel, children: [
1280
+ /* @__PURE__ */ jsxs("div", { style: fieldGroup, children: [
1281
+ /* @__PURE__ */ jsx(
1282
+ "label",
1283
+ {
1284
+ htmlFor: "dauth-passkey-name",
1285
+ style: label,
1286
+ children: "Passkey name (optional)"
1287
+ }
1288
+ ),
1289
+ /* @__PURE__ */ jsx(
1290
+ "input",
1291
+ {
1292
+ id: "dauth-passkey-name",
1293
+ type: "text",
1294
+ value: passkeyName,
1295
+ onChange: (e) => setPasskeyName(e.target.value),
1296
+ placeholder: "e.g. MacBook Touch ID",
1297
+ disabled: registering,
1298
+ style: input,
1299
+ onFocus: inputFocusHandler,
1300
+ onBlur: inputBlurHandler
1301
+ }
1302
+ )
759
1303
  ] }),
760
- /* @__PURE__ */ 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
1304
  /* @__PURE__ */ jsxs(
774
1305
  "div",
775
1306
  {
776
1307
  style: {
777
1308
  display: "flex",
778
- gap: 8,
779
- marginTop: 12
1309
+ gap: 8
780
1310
  },
781
1311
  children: [
782
- /* @__PURE__ */ jsx(
1312
+ /* @__PURE__ */ jsxs(
783
1313
  "button",
784
1314
  {
785
1315
  type: "button",
786
- style: cancelBtn,
787
- onClick: () => {
788
- setShowDelete(false);
789
- setDeleteText("");
1316
+ style: {
1317
+ ...smallAccentBtn,
1318
+ opacity: registering ? 0.6 : 1
790
1319
  },
791
- onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "var(--dauth-surface-hover, #232340)",
792
- onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "transparent",
793
- children: "Cancel"
1320
+ disabled: registering,
1321
+ onClick: handleRegisterPasskey,
1322
+ children: [
1323
+ registering ? /* @__PURE__ */ jsx(Spinner, {}) : /* @__PURE__ */ jsx(IconFingerprint, {}),
1324
+ registering ? "Registering..." : "Register"
1325
+ ]
794
1326
  }
795
1327
  ),
796
- /* @__PURE__ */ jsxs(
1328
+ /* @__PURE__ */ jsx(
797
1329
  "button",
798
1330
  {
799
1331
  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__ */ jsx(Spinner, {}),
809
- "Delete my account"
810
- ]
1332
+ style: cancelBtn,
1333
+ onClick: () => setShowRegister(false),
1334
+ onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "var(--dauth-surface-hover, #232340)",
1335
+ onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "transparent",
1336
+ children: "Cancel"
811
1337
  }
812
1338
  )
813
1339
  ]
814
1340
  }
815
1341
  )
816
- ] })
1342
+ ] }),
1343
+ passkeyStatus && /* @__PURE__ */ jsx(
1344
+ "div",
1345
+ {
1346
+ role: "status",
1347
+ "aria-live": "polite",
1348
+ style: {
1349
+ ...statusMsg(passkeyStatus.type),
1350
+ marginTop: 12
1351
+ },
1352
+ children: passkeyStatus.message
1353
+ }
1354
+ ),
1355
+ /* @__PURE__ */ jsx("div", { style: { marginTop: 12 }, children: loadingCreds ? /* @__PURE__ */ jsx(
1356
+ "div",
1357
+ {
1358
+ style: {
1359
+ textAlign: "center",
1360
+ padding: 24
1361
+ },
1362
+ children: /* @__PURE__ */ jsx(Spinner, {})
1363
+ }
1364
+ ) : credentials.length > 0 ? credentials.map((cred) => /* @__PURE__ */ jsxs(
1365
+ "div",
1366
+ {
1367
+ style: credentialRow,
1368
+ children: [
1369
+ /* @__PURE__ */ jsxs(
1370
+ "div",
1371
+ {
1372
+ style: {
1373
+ display: "flex",
1374
+ alignItems: "center",
1375
+ gap: 12,
1376
+ flex: 1,
1377
+ minWidth: 0
1378
+ },
1379
+ children: [
1380
+ /* @__PURE__ */ jsx(
1381
+ "span",
1382
+ {
1383
+ style: {
1384
+ color: "var(--dauth-accent, #6366f1)",
1385
+ flexShrink: 0
1386
+ },
1387
+ children: /* @__PURE__ */ jsx(IconFingerprint, {})
1388
+ }
1389
+ ),
1390
+ /* @__PURE__ */ jsxs(
1391
+ "div",
1392
+ {
1393
+ style: {
1394
+ minWidth: 0,
1395
+ flex: 1
1396
+ },
1397
+ children: [
1398
+ /* @__PURE__ */ jsx(
1399
+ "div",
1400
+ {
1401
+ style: {
1402
+ fontSize: "var(--dauth-font-size-sm, 0.875rem)",
1403
+ fontWeight: 500,
1404
+ color: "var(--dauth-text-primary, #e4e4e7)",
1405
+ overflow: "hidden",
1406
+ textOverflow: "ellipsis",
1407
+ whiteSpace: "nowrap"
1408
+ },
1409
+ children: cred.name || "Passkey"
1410
+ }
1411
+ ),
1412
+ /* @__PURE__ */ jsxs(
1413
+ "div",
1414
+ {
1415
+ style: {
1416
+ fontSize: "var(--dauth-font-size-xs, 0.75rem)",
1417
+ color: "var(--dauth-text-muted, #71717a)"
1418
+ },
1419
+ children: [
1420
+ cred.deviceType === "multiDevice" ? "Synced" : "Device-bound",
1421
+ cred.createdAt && ` \xB7 Created ${new Date(cred.createdAt).toLocaleDateString()}`
1422
+ ]
1423
+ }
1424
+ )
1425
+ ]
1426
+ }
1427
+ )
1428
+ ]
1429
+ }
1430
+ ),
1431
+ /* @__PURE__ */ jsx(
1432
+ "button",
1433
+ {
1434
+ type: "button",
1435
+ onClick: () => handleDeletePasskey(cred._id),
1436
+ style: trashBtn,
1437
+ onMouseEnter: (e) => e.currentTarget.style.color = "var(--dauth-error, #ef4444)",
1438
+ onMouseLeave: (e) => e.currentTarget.style.color = "var(--dauth-text-muted, #71717a)",
1439
+ "aria-label": `Delete passkey ${cred.name || ""}`,
1440
+ children: /* @__PURE__ */ jsx(IconTrash, {})
1441
+ }
1442
+ )
1443
+ ]
1444
+ },
1445
+ cred._id
1446
+ )) : /* @__PURE__ */ jsxs("div", { style: emptyState, children: [
1447
+ /* @__PURE__ */ jsx(
1448
+ "span",
1449
+ {
1450
+ style: {
1451
+ color: "var(--dauth-accent, #6366f1)"
1452
+ },
1453
+ children: /* @__PURE__ */ jsx(IconShield, {})
1454
+ }
1455
+ ),
1456
+ /* @__PURE__ */ jsxs("div", { children: [
1457
+ /* @__PURE__ */ jsx(
1458
+ "div",
1459
+ {
1460
+ style: {
1461
+ fontSize: "var(--dauth-font-size-sm, 0.875rem)",
1462
+ fontWeight: 500,
1463
+ color: "var(--dauth-text-primary, #e4e4e7)"
1464
+ },
1465
+ children: "No passkeys registered"
1466
+ }
1467
+ ),
1468
+ /* @__PURE__ */ jsx(
1469
+ "div",
1470
+ {
1471
+ style: {
1472
+ fontSize: "var(--dauth-font-size-xs, 0.75rem)",
1473
+ color: "var(--dauth-text-secondary, #a1a1aa)"
1474
+ },
1475
+ children: "Add a passkey for faster, more secure sign-in."
1476
+ }
1477
+ )
1478
+ ] })
1479
+ ] }) })
1480
+ ] }),
1481
+ activeTab === "account" && /* @__PURE__ */ jsxs(Fragment, { children: [
1482
+ status && /* @__PURE__ */ jsx(
1483
+ "div",
1484
+ {
1485
+ role: "status",
1486
+ "aria-live": "polite",
1487
+ style: statusMsg(status.type),
1488
+ children: status.message
1489
+ }
1490
+ ),
1491
+ /* @__PURE__ */ jsxs("div", { children: [
1492
+ /* @__PURE__ */ jsx("div", { style: dangerTitle, children: "Delete account" }),
1493
+ /* @__PURE__ */ jsx("div", { style: dangerDesc, children: "Permanently delete your account and all associated data." }),
1494
+ !showDelete ? /* @__PURE__ */ jsx(
1495
+ "button",
1496
+ {
1497
+ type: "button",
1498
+ style: deleteBtn,
1499
+ onClick: () => setShowDelete(true),
1500
+ onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "rgba(239, 68, 68, 0.2)",
1501
+ onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "var(--dauth-error-bg, rgba(239, 68, 68, 0.1))",
1502
+ children: "Delete account"
1503
+ }
1504
+ ) : /* @__PURE__ */ jsxs("div", { style: deletePanel, children: [
1505
+ /* @__PURE__ */ jsxs("div", { style: deletePanelText, children: [
1506
+ "This action is permanent and cannot be undone. Type",
1507
+ " ",
1508
+ /* @__PURE__ */ jsx("strong", { children: CONFIRM_WORD }),
1509
+ " to confirm."
1510
+ ] }),
1511
+ /* @__PURE__ */ jsx(
1512
+ "input",
1513
+ {
1514
+ type: "text",
1515
+ value: deleteText,
1516
+ onChange: (e) => setDeleteText(e.target.value),
1517
+ placeholder: `Type ${CONFIRM_WORD}`,
1518
+ style: input,
1519
+ onFocus: inputFocusHandler,
1520
+ onBlur: inputBlurHandler,
1521
+ disabled: deleting
1522
+ }
1523
+ ),
1524
+ /* @__PURE__ */ jsxs(
1525
+ "div",
1526
+ {
1527
+ style: {
1528
+ display: "flex",
1529
+ gap: 8,
1530
+ marginTop: 12
1531
+ },
1532
+ children: [
1533
+ /* @__PURE__ */ jsx(
1534
+ "button",
1535
+ {
1536
+ type: "button",
1537
+ style: cancelBtn,
1538
+ onClick: () => {
1539
+ setShowDelete(false);
1540
+ setDeleteText("");
1541
+ },
1542
+ onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "var(--dauth-surface-hover, #232340)",
1543
+ onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "transparent",
1544
+ children: "Cancel"
1545
+ }
1546
+ ),
1547
+ /* @__PURE__ */ jsxs(
1548
+ "button",
1549
+ {
1550
+ type: "button",
1551
+ style: {
1552
+ ...deleteConfirmBtn,
1553
+ opacity: deleteText !== CONFIRM_WORD || deleting ? 0.5 : 1,
1554
+ cursor: deleteText !== CONFIRM_WORD || deleting ? "not-allowed" : "pointer"
1555
+ },
1556
+ disabled: deleteText !== CONFIRM_WORD || deleting,
1557
+ onClick: handleDelete,
1558
+ children: [
1559
+ deleting && /* @__PURE__ */ jsx(Spinner, {}),
1560
+ "Delete my account"
1561
+ ]
1562
+ }
1563
+ )
1564
+ ]
1565
+ }
1566
+ )
1567
+ ] })
1568
+ ] }),
1569
+ /* @__PURE__ */ jsx("hr", { style: separator }),
1570
+ /* @__PURE__ */ jsxs(
1571
+ "button",
1572
+ {
1573
+ type: "button",
1574
+ style: signOutBtn,
1575
+ onClick: handleSignOut,
1576
+ onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "rgba(239, 68, 68, 0.2)",
1577
+ onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "var(--dauth-error-bg, rgba(239, 68, 68, 0.1))",
1578
+ children: [
1579
+ /* @__PURE__ */ jsx(IconLogOut, {}),
1580
+ "Sign out"
1581
+ ]
1582
+ }
1583
+ )
817
1584
  ] })
818
1585
  ] }),
819
- /* @__PURE__ */ jsx("div", { style: footerStyle(isDesktop), children: /* @__PURE__ */ jsxs(
1586
+ activeTab === "profile" && /* @__PURE__ */ jsx("div", { style: footerStyle(isDesktop), children: /* @__PURE__ */ jsxs(
820
1587
  "button",
821
1588
  {
822
1589
  type: "button",
@@ -854,8 +1621,7 @@ var headerStyle = (isDesktop) => ({
854
1621
  display: "flex",
855
1622
  alignItems: "center",
856
1623
  justifyContent: "space-between",
857
- padding: "16px 24px",
858
- borderBottom: "1px solid var(--dauth-border, rgba(255, 255, 255, 0.08))",
1624
+ padding: "16px 24px 0",
859
1625
  flexShrink: 0,
860
1626
  ...!isDesktop ? {
861
1627
  position: "sticky",
@@ -886,6 +1652,25 @@ var closeBtn = {
886
1652
  transition: "background-color 150ms, color 150ms",
887
1653
  padding: 0
888
1654
  };
1655
+ var tabBar = {
1656
+ display: "flex",
1657
+ padding: "0 24px",
1658
+ borderBottom: "1px solid var(--dauth-border, rgba(255, 255, 255, 0.08))",
1659
+ flexShrink: 0
1660
+ };
1661
+ var tabBtn = {
1662
+ flex: 1,
1663
+ padding: "12px 4px",
1664
+ fontSize: "var(--dauth-font-size-sm, 0.875rem)",
1665
+ fontWeight: 500,
1666
+ border: "none",
1667
+ borderBottom: "2px solid transparent",
1668
+ backgroundColor: "transparent",
1669
+ cursor: "pointer",
1670
+ transition: "color 150ms, border-color 150ms",
1671
+ fontFamily: "inherit",
1672
+ textAlign: "center"
1673
+ };
889
1674
  var bodyStyle = {
890
1675
  flex: 1,
891
1676
  overflowY: "auto",
@@ -912,6 +1697,18 @@ var avatarCircle = {
912
1697
  fontSize: "1.5rem",
913
1698
  fontWeight: 600
914
1699
  };
1700
+ var avatarOverlay = {
1701
+ position: "absolute",
1702
+ inset: 0,
1703
+ backgroundColor: "rgba(0, 0, 0, 0.4)",
1704
+ display: "flex",
1705
+ alignItems: "center",
1706
+ justifyContent: "center",
1707
+ borderRadius: "50%",
1708
+ opacity: 0.7,
1709
+ transition: "opacity 150ms",
1710
+ color: "#ffffff"
1711
+ };
915
1712
  var emailText = {
916
1713
  fontSize: "var(--dauth-font-size-sm, 0.875rem)",
917
1714
  color: "var(--dauth-text-secondary, #a1a1aa)"
@@ -926,7 +1723,9 @@ var statusMsg = (type) => ({
926
1723
  textAlign: "center",
927
1724
  lineHeight: 1.5
928
1725
  });
929
- var fieldGroup = { marginBottom: 16 };
1726
+ var fieldGroup = {
1727
+ marginBottom: 16
1728
+ };
930
1729
  var label = {
931
1730
  display: "block",
932
1731
  fontSize: "var(--dauth-font-size-sm, 0.875rem)",
@@ -956,12 +1755,88 @@ var inputBlurHandler = (e) => {
956
1755
  e.currentTarget.style.borderColor = "var(--dauth-border, rgba(255, 255, 255, 0.08))";
957
1756
  e.currentTarget.style.boxShadow = "none";
958
1757
  };
1758
+ var langBtn = {
1759
+ padding: "8px 16px",
1760
+ fontSize: "var(--dauth-font-size-sm, 0.875rem)",
1761
+ fontWeight: 500,
1762
+ border: "none",
1763
+ borderRadius: "var(--dauth-radius-sm, 8px)",
1764
+ cursor: "pointer",
1765
+ transition: "background-color 150ms, color 150ms",
1766
+ fontFamily: "inherit"
1767
+ };
959
1768
  var separator = {
960
1769
  height: 1,
961
1770
  backgroundColor: "var(--dauth-border, rgba(255, 255, 255, 0.08))",
962
1771
  margin: "24px 0",
963
1772
  border: "none"
964
1773
  };
1774
+ var outlineBtn = {
1775
+ padding: "6px 12px",
1776
+ fontSize: "var(--dauth-font-size-xs, 0.75rem)",
1777
+ fontWeight: 500,
1778
+ color: "var(--dauth-text-secondary, #a1a1aa)",
1779
+ backgroundColor: "transparent",
1780
+ border: "1px solid var(--dauth-border, rgba(255, 255, 255, 0.08))",
1781
+ borderRadius: "var(--dauth-radius-sm, 8px)",
1782
+ cursor: "pointer",
1783
+ transition: "background-color 150ms",
1784
+ fontFamily: "inherit"
1785
+ };
1786
+ var registerPanel = {
1787
+ padding: 16,
1788
+ borderRadius: "var(--dauth-radius-sm, 8px)",
1789
+ border: "1px solid var(--dauth-border, rgba(255, 255, 255, 0.08))",
1790
+ backgroundColor: "var(--dauth-surface-secondary, rgba(255, 255, 255, 0.04))",
1791
+ marginBottom: 12
1792
+ };
1793
+ var smallAccentBtn = {
1794
+ padding: "8px 16px",
1795
+ fontSize: "var(--dauth-font-size-sm, 0.875rem)",
1796
+ fontWeight: 500,
1797
+ color: "#ffffff",
1798
+ backgroundColor: "var(--dauth-accent, #6366f1)",
1799
+ border: "none",
1800
+ borderRadius: "var(--dauth-radius-sm, 8px)",
1801
+ cursor: "pointer",
1802
+ transition: "opacity 150ms",
1803
+ fontFamily: "inherit",
1804
+ display: "flex",
1805
+ alignItems: "center",
1806
+ gap: 6
1807
+ };
1808
+ var credentialRow = {
1809
+ display: "flex",
1810
+ alignItems: "center",
1811
+ justifyContent: "space-between",
1812
+ padding: 12,
1813
+ borderRadius: "var(--dauth-radius-sm, 8px)",
1814
+ backgroundColor: "var(--dauth-surface-secondary, rgba(255, 255, 255, 0.04))",
1815
+ marginBottom: 8
1816
+ };
1817
+ var trashBtn = {
1818
+ display: "flex",
1819
+ alignItems: "center",
1820
+ justifyContent: "center",
1821
+ width: 28,
1822
+ height: 28,
1823
+ border: "none",
1824
+ backgroundColor: "transparent",
1825
+ color: "var(--dauth-text-muted, #71717a)",
1826
+ cursor: "pointer",
1827
+ transition: "color 150ms",
1828
+ padding: 0,
1829
+ borderRadius: "var(--dauth-radius-sm, 8px)",
1830
+ flexShrink: 0
1831
+ };
1832
+ var emptyState = {
1833
+ display: "flex",
1834
+ alignItems: "center",
1835
+ gap: 12,
1836
+ padding: 16,
1837
+ borderRadius: "var(--dauth-radius-sm, 8px)",
1838
+ backgroundColor: "var(--dauth-surface-secondary, rgba(255, 255, 255, 0.04))"
1839
+ };
965
1840
  var dangerTitle = {
966
1841
  fontSize: "var(--dauth-font-size-sm, 0.875rem)",
967
1842
  fontWeight: 600,
@@ -1000,7 +1875,6 @@ var deletePanelText = {
1000
1875
  lineHeight: 1.5
1001
1876
  };
1002
1877
  var cancelBtn = {
1003
- flex: 1,
1004
1878
  padding: "8px 16px",
1005
1879
  fontSize: "var(--dauth-font-size-sm, 0.875rem)",
1006
1880
  fontWeight: 500,
@@ -1029,6 +1903,23 @@ var deleteConfirmBtn = {
1029
1903
  justifyContent: "center",
1030
1904
  gap: 8
1031
1905
  };
1906
+ var signOutBtn = {
1907
+ width: "100%",
1908
+ padding: "12px 24px",
1909
+ fontSize: "var(--dauth-font-size-base, 1rem)",
1910
+ fontWeight: 500,
1911
+ color: "var(--dauth-error, #ef4444)",
1912
+ backgroundColor: "var(--dauth-error-bg, rgba(239, 68, 68, 0.1))",
1913
+ border: "1px solid rgba(239, 68, 68, 0.2)",
1914
+ borderRadius: "var(--dauth-radius-sm, 8px)",
1915
+ cursor: "pointer",
1916
+ transition: "background-color 150ms",
1917
+ fontFamily: "inherit",
1918
+ display: "flex",
1919
+ alignItems: "center",
1920
+ justifyContent: "center",
1921
+ gap: 8
1922
+ };
1032
1923
  var footerStyle = (isDesktop) => ({
1033
1924
  padding: "16px 24px",
1034
1925
  borderTop: "1px solid var(--dauth-border, rgba(255, 255, 255, 0.08))",
@@ -1140,15 +2031,39 @@ var DauthProvider = (props) => {
1140
2031
  () => deleteAccountAction(ctx),
1141
2032
  [ctx]
1142
2033
  );
2034
+ const getPasskeyCredentials = useCallback2(
2035
+ () => getPasskeyCredentialsAction(ctx),
2036
+ [ctx]
2037
+ );
2038
+ const registerPasskey = useCallback2(
2039
+ (name) => registerPasskeyAction(ctx, name),
2040
+ [ctx]
2041
+ );
2042
+ const deletePasskeyCredential = useCallback2(
2043
+ (credentialId) => deletePasskeyCredentialAction(ctx, credentialId),
2044
+ [ctx]
2045
+ );
1143
2046
  const memoProvider = useMemo2(
1144
2047
  () => ({
1145
2048
  ...dauthState,
1146
2049
  loginWithRedirect,
1147
2050
  logout,
1148
2051
  updateUser,
1149
- deleteAccount
2052
+ deleteAccount,
2053
+ getPasskeyCredentials,
2054
+ registerPasskey,
2055
+ deletePasskeyCredential
1150
2056
  }),
1151
- [dauthState, loginWithRedirect, logout, updateUser, deleteAccount]
2057
+ [
2058
+ dauthState,
2059
+ loginWithRedirect,
2060
+ logout,
2061
+ updateUser,
2062
+ deleteAccount,
2063
+ getPasskeyCredentials,
2064
+ registerPasskey,
2065
+ deletePasskeyCredential
2066
+ ]
1152
2067
  );
1153
2068
  return /* @__PURE__ */ jsx2(DauthContext.Provider, { value: memoProvider, children });
1154
2069
  };