dauth-context-react 6.0.0 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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,6 +869,90 @@ 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]);
939
+ const themeVars = useMemo(() => {
940
+ const t = domain.modalTheme;
941
+ if (!t) return {};
942
+ const vars = {};
943
+ if (t.accent) vars["--dauth-accent"] = t.accent;
944
+ if (t.accentHover)
945
+ vars["--dauth-accent-hover"] = t.accentHover;
946
+ if (t.surface) vars["--dauth-surface"] = t.surface;
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;
953
+ if (t.border) vars["--dauth-border"] = t.border;
954
+ return vars;
955
+ }, [domain.modalTheme]);
532
956
  if (phase === "exited") return null;
533
957
  const dur = isDesktop ? TRANSITION_MS : MOBILE_TRANSITION_MS;
534
958
  const easing = "cubic-bezier(0.16, 1, 0.3, 1)";
@@ -576,6 +1000,11 @@ function DauthProfileModal({ open, onClose }) {
576
1000
  transition: `transform ${dur}ms ${easing}`
577
1001
  };
578
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
+ ];
579
1008
  return createPortal(
580
1009
  /* @__PURE__ */ jsxs(Fragment, { children: [
581
1010
  /* @__PURE__ */ jsx(
@@ -589,7 +1018,7 @@ function DauthProfileModal({ open, onClose }) {
589
1018
  /* @__PURE__ */ jsx(
590
1019
  "div",
591
1020
  {
592
- style: backdrop,
1021
+ style: { ...backdrop, ...themeVars },
593
1022
  onClick: isDesktop ? onClose : void 0,
594
1023
  "data-testid": "dauth-profile-backdrop",
595
1024
  children: /* @__PURE__ */ jsxs(
@@ -619,191 +1048,542 @@ function DauthProfileModal({ open, onClose }) {
619
1048
  /* @__PURE__ */ jsx("h2", { id: "dauth-profile-title", style: titleStyle, children: "Your Profile" }),
620
1049
  /* @__PURE__ */ jsx("div", { style: { width: 36 } })
621
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
+ )) }),
622
1067
  /* @__PURE__ */ jsxs("div", { style: bodyStyle, children: [
623
- /* @__PURE__ */ jsxs("div", { style: avatarSection, children: [
624
- /* @__PURE__ */ jsx("div", { style: avatarCircle, children: user.avatar?.url ? /* @__PURE__ */ jsx(
625
- "img",
626
- {
627
- src: user.avatar.url,
628
- alt: "",
629
- style: {
630
- width: "100%",
631
- height: "100%",
632
- objectFit: "cover"
633
- }
634
- }
635
- ) : avatarInitial }),
636
- /* @__PURE__ */ jsx("div", { style: emailText, children: user.email })
637
- ] }),
638
- status && /* @__PURE__ */ jsx(
639
- "div",
640
- {
641
- role: "status",
642
- "aria-live": "polite",
643
- style: statusMsg(status.type),
644
- children: status.message
645
- }
646
- ),
647
- /* @__PURE__ */ jsxs("div", { children: [
648
- /* @__PURE__ */ jsxs("div", { style: fieldGroup, children: [
649
- /* @__PURE__ */ jsx("label", { htmlFor: "dauth-name", style: label, children: "Name *" }),
650
- /* @__PURE__ */ jsx(
651
- "input",
1068
+ activeTab === "profile" && /* @__PURE__ */ jsxs(Fragment, { children: [
1069
+ /* @__PURE__ */ jsxs("div", { style: avatarSection, children: [
1070
+ /* @__PURE__ */ jsxs(
1071
+ "div",
652
1072
  {
653
- id: "dauth-name",
654
- type: "text",
655
- value: name,
656
- onChange: (e) => setName(e.target.value),
657
- placeholder: "Your name",
658
- disabled: saving,
659
- style: input,
660
- onFocus: inputFocusHandler,
661
- 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
+ ]
662
1094
  }
663
- )
664
- ] }),
665
- hasField("lastname") && /* @__PURE__ */ jsxs("div", { style: fieldGroup, children: [
666
- /* @__PURE__ */ jsxs("label", { htmlFor: "dauth-lastname", style: label, children: [
667
- "Last name",
668
- isRequired("lastname") ? " *" : ""
669
- ] }),
670
- /* @__PURE__ */ jsx(
1095
+ ),
1096
+ /* @__PURE__ */ jsx("div", { style: emailText, children: user.email }),
1097
+ onAvatarUpload && /* @__PURE__ */ jsx(
671
1098
  "input",
672
1099
  {
673
- id: "dauth-lastname",
674
- type: "text",
675
- value: lastname,
676
- onChange: (e) => setLastname(e.target.value),
677
- placeholder: "Your last name",
678
- disabled: saving,
679
- style: input,
680
- onFocus: inputFocusHandler,
681
- onBlur: inputBlurHandler
1100
+ ref: avatarInputRef,
1101
+ type: "file",
1102
+ accept: "image/*",
1103
+ style: { display: "none" },
1104
+ onChange: handleAvatarChange
682
1105
  }
683
1106
  )
684
1107
  ] }),
685
- hasField("nickname") && /* @__PURE__ */ jsxs("div", { style: fieldGroup, children: [
686
- /* @__PURE__ */ jsxs("label", { htmlFor: "dauth-nickname", style: label, children: [
687
- "Nickname",
688
- 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
+ )
689
1141
  ] }),
690
- /* @__PURE__ */ jsx(
691
- "input",
692
- {
693
- id: "dauth-nickname",
694
- type: "text",
695
- value: nickname,
696
- onChange: (e) => setNickname(e.target.value),
697
- placeholder: "Choose a nickname",
698
- disabled: saving,
699
- style: input,
700
- onFocus: inputFocusHandler,
701
- onBlur: inputBlurHandler
702
- }
703
- )
704
- ] }),
705
- hasField("country") && /* @__PURE__ */ jsxs("div", { style: fieldGroup, children: [
706
- /* @__PURE__ */ jsxs("label", { htmlFor: "dauth-country", style: label, children: [
707
- "Country",
708
- 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
+ )
709
1168
  ] }),
710
- /* @__PURE__ */ jsx(
711
- "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",
712
1229
  {
713
- id: "dauth-country",
714
- type: "text",
715
- value: country,
716
- onChange: (e) => setCountry(e.target.value),
717
- placeholder: "Your country",
718
- disabled: saving,
719
- style: input,
720
- onFocus: inputFocusHandler,
721
- onBlur: inputBlurHandler
722
- }
723
- )
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
+ )) })
724
1241
  ] })
725
1242
  ] }),
726
- /* @__PURE__ */ jsx("hr", { style: separator }),
727
- /* @__PURE__ */ jsxs("div", { children: [
728
- /* @__PURE__ */ jsx("div", { style: dangerTitle, children: "Delete account" }),
729
- /* @__PURE__ */ jsx("div", { style: dangerDesc, children: "Permanently delete your account and all associated data." }),
730
- !showDelete ? /* @__PURE__ */ jsx(
731
- "button",
1243
+ activeTab === "security" && showSecurity && /* @__PURE__ */ jsxs(Fragment, { children: [
1244
+ /* @__PURE__ */ jsxs(
1245
+ "div",
732
1246
  {
733
- type: "button",
734
- style: deleteBtn,
735
- onClick: () => setShowDelete(true),
736
- onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "rgba(239, 68, 68, 0.2)",
737
- onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "var(--dauth-error-bg, rgba(239, 68, 68, 0.1))",
738
- 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
+ ]
739
1277
  }
740
- ) : /* @__PURE__ */ jsxs("div", { style: deletePanel, children: [
741
- /* @__PURE__ */ jsxs("div", { style: deletePanelText, children: [
742
- "This action is permanent and cannot be undone. Type",
743
- " ",
744
- /* @__PURE__ */ jsx("strong", { children: CONFIRM_WORD }),
745
- " 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
+ )
746
1303
  ] }),
747
- /* @__PURE__ */ jsx(
748
- "input",
749
- {
750
- type: "text",
751
- value: deleteText,
752
- onChange: (e) => setDeleteText(e.target.value),
753
- placeholder: `Type ${CONFIRM_WORD}`,
754
- style: input,
755
- onFocus: inputFocusHandler,
756
- onBlur: inputBlurHandler,
757
- disabled: deleting
758
- }
759
- ),
760
1304
  /* @__PURE__ */ jsxs(
761
1305
  "div",
762
1306
  {
763
1307
  style: {
764
1308
  display: "flex",
765
- gap: 8,
766
- marginTop: 12
1309
+ gap: 8
767
1310
  },
768
1311
  children: [
769
- /* @__PURE__ */ jsx(
1312
+ /* @__PURE__ */ jsxs(
770
1313
  "button",
771
1314
  {
772
1315
  type: "button",
773
- style: cancelBtn,
774
- onClick: () => {
775
- setShowDelete(false);
776
- setDeleteText("");
1316
+ style: {
1317
+ ...smallAccentBtn,
1318
+ opacity: registering ? 0.6 : 1
777
1319
  },
778
- onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "var(--dauth-surface-hover, #232340)",
779
- onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "transparent",
780
- children: "Cancel"
1320
+ disabled: registering,
1321
+ onClick: handleRegisterPasskey,
1322
+ children: [
1323
+ registering ? /* @__PURE__ */ jsx(Spinner, {}) : /* @__PURE__ */ jsx(IconFingerprint, {}),
1324
+ registering ? "Registering..." : "Register"
1325
+ ]
781
1326
  }
782
1327
  ),
783
- /* @__PURE__ */ jsxs(
1328
+ /* @__PURE__ */ jsx(
784
1329
  "button",
785
1330
  {
786
1331
  type: "button",
787
- style: {
788
- ...deleteConfirmBtn,
789
- opacity: deleteText !== CONFIRM_WORD || deleting ? 0.5 : 1,
790
- cursor: deleteText !== CONFIRM_WORD || deleting ? "not-allowed" : "pointer"
791
- },
792
- disabled: deleteText !== CONFIRM_WORD || deleting,
793
- onClick: handleDelete,
794
- children: [
795
- deleting && /* @__PURE__ */ jsx(Spinner, {}),
796
- "Delete my account"
797
- ]
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"
798
1337
  }
799
1338
  )
800
1339
  ]
801
1340
  }
802
1341
  )
803
- ] })
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
+ )
804
1584
  ] })
805
1585
  ] }),
806
- /* @__PURE__ */ jsx("div", { style: footerStyle(isDesktop), children: /* @__PURE__ */ jsxs(
1586
+ activeTab === "profile" && /* @__PURE__ */ jsx("div", { style: footerStyle(isDesktop), children: /* @__PURE__ */ jsxs(
807
1587
  "button",
808
1588
  {
809
1589
  type: "button",
@@ -841,8 +1621,7 @@ var headerStyle = (isDesktop) => ({
841
1621
  display: "flex",
842
1622
  alignItems: "center",
843
1623
  justifyContent: "space-between",
844
- padding: "16px 24px",
845
- borderBottom: "1px solid var(--dauth-border, rgba(255, 255, 255, 0.08))",
1624
+ padding: "16px 24px 0",
846
1625
  flexShrink: 0,
847
1626
  ...!isDesktop ? {
848
1627
  position: "sticky",
@@ -873,6 +1652,25 @@ var closeBtn = {
873
1652
  transition: "background-color 150ms, color 150ms",
874
1653
  padding: 0
875
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
+ };
876
1674
  var bodyStyle = {
877
1675
  flex: 1,
878
1676
  overflowY: "auto",
@@ -899,6 +1697,18 @@ var avatarCircle = {
899
1697
  fontSize: "1.5rem",
900
1698
  fontWeight: 600
901
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
+ };
902
1712
  var emailText = {
903
1713
  fontSize: "var(--dauth-font-size-sm, 0.875rem)",
904
1714
  color: "var(--dauth-text-secondary, #a1a1aa)"
@@ -913,7 +1723,9 @@ var statusMsg = (type) => ({
913
1723
  textAlign: "center",
914
1724
  lineHeight: 1.5
915
1725
  });
916
- var fieldGroup = { marginBottom: 16 };
1726
+ var fieldGroup = {
1727
+ marginBottom: 16
1728
+ };
917
1729
  var label = {
918
1730
  display: "block",
919
1731
  fontSize: "var(--dauth-font-size-sm, 0.875rem)",
@@ -943,12 +1755,88 @@ var inputBlurHandler = (e) => {
943
1755
  e.currentTarget.style.borderColor = "var(--dauth-border, rgba(255, 255, 255, 0.08))";
944
1756
  e.currentTarget.style.boxShadow = "none";
945
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
+ };
946
1768
  var separator = {
947
1769
  height: 1,
948
1770
  backgroundColor: "var(--dauth-border, rgba(255, 255, 255, 0.08))",
949
1771
  margin: "24px 0",
950
1772
  border: "none"
951
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
+ };
952
1840
  var dangerTitle = {
953
1841
  fontSize: "var(--dauth-font-size-sm, 0.875rem)",
954
1842
  fontWeight: 600,
@@ -987,7 +1875,6 @@ var deletePanelText = {
987
1875
  lineHeight: 1.5
988
1876
  };
989
1877
  var cancelBtn = {
990
- flex: 1,
991
1878
  padding: "8px 16px",
992
1879
  fontSize: "var(--dauth-font-size-sm, 0.875rem)",
993
1880
  fontWeight: 500,
@@ -1016,6 +1903,23 @@ var deleteConfirmBtn = {
1016
1903
  justifyContent: "center",
1017
1904
  gap: 8
1018
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
+ };
1019
1923
  var footerStyle = (isDesktop) => ({
1020
1924
  padding: "16px 24px",
1021
1925
  borderTop: "1px solid var(--dauth-border, rgba(255, 255, 255, 0.08))",
@@ -1127,15 +2031,39 @@ var DauthProvider = (props) => {
1127
2031
  () => deleteAccountAction(ctx),
1128
2032
  [ctx]
1129
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
+ );
1130
2046
  const memoProvider = useMemo2(
1131
2047
  () => ({
1132
2048
  ...dauthState,
1133
2049
  loginWithRedirect,
1134
2050
  logout,
1135
2051
  updateUser,
1136
- deleteAccount
2052
+ deleteAccount,
2053
+ getPasskeyCredentials,
2054
+ registerPasskey,
2055
+ deletePasskeyCredential
1137
2056
  }),
1138
- [dauthState, loginWithRedirect, logout, updateUser, deleteAccount]
2057
+ [
2058
+ dauthState,
2059
+ loginWithRedirect,
2060
+ logout,
2061
+ updateUser,
2062
+ deleteAccount,
2063
+ getPasskeyCredentials,
2064
+ registerPasskey,
2065
+ deletePasskeyCredential
2066
+ ]
1139
2067
  );
1140
2068
  return /* @__PURE__ */ jsx2(DauthContext.Provider, { value: memoProvider, children });
1141
2069
  };