dauth-context-react 6.1.0 → 6.3.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,27 +737,64 @@ 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("");
454
764
  const [country, setCountry] = useState("");
765
+ const [telPrefix, setTelPrefix] = useState("");
766
+ const [telSuffix, setTelSuffix] = useState("");
767
+ const [birthDate, setBirthDate] = useState("");
768
+ const [customFieldValues, setCustomFieldValues] = useState({});
455
769
  const [populated, setPopulated] = useState(false);
456
770
  const [saving, setSaving] = useState(false);
457
771
  const [status, setStatus] = useState(null);
458
772
  const [showDelete, setShowDelete] = useState(false);
459
773
  const [deleteText, setDeleteText] = useState("");
460
774
  const [deleting, setDeleting] = useState(false);
775
+ const [credentials, setCredentials] = useState([]);
776
+ const [loadingCreds, setLoadingCreds] = useState(false);
777
+ const [showRegister, setShowRegister] = useState(false);
778
+ const [passkeyName, setPasskeyName] = useState("");
779
+ const [registering, setRegistering] = useState(false);
780
+ const [passkeyStatus, setPasskeyStatus] = useState(null);
781
+ const [uploadingAvatar, setUploadingAvatar] = useState(false);
461
782
  useEffect(() => {
462
783
  if (open && user?._id && !populated) {
463
784
  setName(user.name || "");
464
785
  setLastname(user.lastname || "");
465
786
  setNickname(user.nickname || "");
466
787
  setCountry(user.country || "");
788
+ setTelPrefix(user.telPrefix || "");
789
+ setTelSuffix(user.telSuffix || "");
790
+ setBirthDate(
791
+ user.birthDate ? new Date(user.birthDate).toISOString().split("T")[0] : ""
792
+ );
793
+ const cf = {};
794
+ for (const f of domain.customFields ?? []) {
795
+ cf[f.key] = user.customFields?.[f.key] ?? "";
796
+ }
797
+ setCustomFieldValues(cf);
467
798
  setPopulated(true);
468
799
  }
469
800
  if (!open) {
@@ -471,13 +802,36 @@ function DauthProfileModal({ open, onClose }) {
471
802
  setStatus(null);
472
803
  setShowDelete(false);
473
804
  setDeleteText("");
805
+ setActiveTab("profile");
806
+ setPasskeyStatus(null);
807
+ setShowRegister(false);
808
+ setPasskeyName("");
474
809
  }
475
810
  }, [open, user, populated]);
811
+ useEffect(() => {
812
+ if (activeTab !== "security" || !showSecurity) return;
813
+ setLoadingCreds(true);
814
+ getPasskeyCredentials().then((creds) => {
815
+ setCredentials(creds);
816
+ setLoadingCreds(false);
817
+ });
818
+ }, [activeTab, showSecurity, getPasskeyCredentials]);
476
819
  useEffect(() => {
477
820
  if (status?.type !== "success") return;
478
- const timer = setTimeout(() => setStatus(null), SUCCESS_TIMEOUT_MS);
821
+ const timer = setTimeout(
822
+ () => setStatus(null),
823
+ SUCCESS_TIMEOUT_MS
824
+ );
479
825
  return () => clearTimeout(timer);
480
826
  }, [status]);
827
+ useEffect(() => {
828
+ if (passkeyStatus?.type !== "success") return;
829
+ const timer = setTimeout(
830
+ () => setPasskeyStatus(null),
831
+ SUCCESS_TIMEOUT_MS
832
+ );
833
+ return () => clearTimeout(timer);
834
+ }, [passkeyStatus]);
481
835
  useFocusTrap(modalRef, phase === "entered", onClose);
482
836
  useScrollLock(phase !== "exited");
483
837
  const hasField = useCallback(
@@ -490,8 +844,23 @@ function DauthProfileModal({ open, onClose }) {
490
844
  );
491
845
  const hasChanges = useMemo(() => {
492
846
  if (!user?._id) return false;
493
- return name !== (user.name || "") || lastname !== (user.lastname || "") || nickname !== (user.nickname || "") || country !== (user.country || "");
494
- }, [name, lastname, nickname, country, user]);
847
+ const origBirthDate = user.birthDate ? new Date(user.birthDate).toISOString().split("T")[0] : "";
848
+ const cfChanged = (domain.customFields ?? []).some(
849
+ (f) => (customFieldValues[f.key] ?? "") !== (user.customFields?.[f.key] ?? "")
850
+ );
851
+ return name !== (user.name || "") || lastname !== (user.lastname || "") || nickname !== (user.nickname || "") || country !== (user.country || "") || telPrefix !== (user.telPrefix || "") || telSuffix !== (user.telSuffix || "") || birthDate !== origBirthDate || cfChanged;
852
+ }, [
853
+ name,
854
+ lastname,
855
+ nickname,
856
+ country,
857
+ telPrefix,
858
+ telSuffix,
859
+ birthDate,
860
+ customFieldValues,
861
+ user,
862
+ domain.customFields
863
+ ]);
495
864
  const canSave = name.trim().length > 0 && hasChanges && !saving;
496
865
  const handleSave = useCallback(async () => {
497
866
  setSaving(true);
@@ -500,6 +869,14 @@ function DauthProfileModal({ open, onClose }) {
500
869
  if (hasField("lastname")) fields.lastname = lastname;
501
870
  if (hasField("nickname")) fields.nickname = nickname;
502
871
  if (hasField("country")) fields.country = country;
872
+ if (hasField("tel_prefix"))
873
+ fields.telPrefix = telPrefix;
874
+ if (hasField("tel_suffix"))
875
+ fields.telSuffix = telSuffix;
876
+ if (hasField("birth_date") && birthDate)
877
+ fields.birthDate = birthDate;
878
+ if ((domain.customFields ?? []).length > 0)
879
+ fields.customFields = customFieldValues;
503
880
  const ok = await updateUser(fields);
504
881
  setSaving(false);
505
882
  if (ok) {
@@ -513,7 +890,19 @@ function DauthProfileModal({ open, onClose }) {
513
890
  message: "Something went wrong. Please try again."
514
891
  });
515
892
  }
516
- }, [name, lastname, nickname, country, hasField, updateUser]);
893
+ }, [
894
+ name,
895
+ lastname,
896
+ nickname,
897
+ country,
898
+ telPrefix,
899
+ telSuffix,
900
+ birthDate,
901
+ customFieldValues,
902
+ hasField,
903
+ updateUser,
904
+ domain.customFields
905
+ ]);
517
906
  const handleDelete = useCallback(async () => {
518
907
  setDeleting(true);
519
908
  const ok = await deleteAccount();
@@ -529,16 +918,87 @@ function DauthProfileModal({ open, onClose }) {
529
918
  setDeleteText("");
530
919
  }
531
920
  }, [deleteAccount, onClose]);
921
+ const handleLanguage = useCallback(
922
+ async (lang) => {
923
+ await updateUser({ language: lang });
924
+ },
925
+ [updateUser]
926
+ );
927
+ const handleRegisterPasskey = useCallback(async () => {
928
+ setRegistering(true);
929
+ setPasskeyStatus(null);
930
+ const cred = await registerPasskey(
931
+ passkeyName || void 0
932
+ );
933
+ setRegistering(false);
934
+ if (cred) {
935
+ setCredentials((prev) => [...prev, cred]);
936
+ setPasskeyName("");
937
+ setShowRegister(false);
938
+ setPasskeyStatus({
939
+ type: "success",
940
+ message: "Passkey registered successfully"
941
+ });
942
+ } else {
943
+ setPasskeyStatus({
944
+ type: "error",
945
+ message: "Failed to register passkey"
946
+ });
947
+ }
948
+ }, [passkeyName, registerPasskey]);
949
+ const handleDeletePasskey = useCallback(
950
+ async (credentialId) => {
951
+ const ok = await deletePasskeyCredential(credentialId);
952
+ if (ok) {
953
+ setCredentials(
954
+ (prev) => prev.filter((c) => c._id !== credentialId)
955
+ );
956
+ }
957
+ },
958
+ [deletePasskeyCredential]
959
+ );
960
+ const handleAvatarClick = useCallback(() => {
961
+ if (onAvatarUpload) {
962
+ avatarInputRef.current?.click();
963
+ }
964
+ }, [onAvatarUpload]);
965
+ const handleAvatarChange = useCallback(
966
+ async (e) => {
967
+ const file = e.target.files?.[0];
968
+ if (!file || !onAvatarUpload) return;
969
+ setUploadingAvatar(true);
970
+ try {
971
+ const url = await onAvatarUpload(file);
972
+ if (url) {
973
+ await updateUser({ avatar: url });
974
+ }
975
+ } catch {
976
+ }
977
+ setUploadingAvatar(false);
978
+ if (avatarInputRef.current) {
979
+ avatarInputRef.current.value = "";
980
+ }
981
+ },
982
+ [onAvatarUpload, updateUser]
983
+ );
984
+ const handleSignOut = useCallback(() => {
985
+ logout();
986
+ onClose();
987
+ }, [logout, onClose]);
532
988
  const themeVars = useMemo(() => {
533
989
  const t = domain.modalTheme;
534
990
  if (!t) return {};
535
991
  const vars = {};
536
992
  if (t.accent) vars["--dauth-accent"] = t.accent;
537
- if (t.accentHover) vars["--dauth-accent-hover"] = t.accentHover;
993
+ if (t.accentHover)
994
+ vars["--dauth-accent-hover"] = t.accentHover;
538
995
  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;
996
+ if (t.surfaceHover)
997
+ vars["--dauth-surface-hover"] = t.surfaceHover;
998
+ if (t.textPrimary)
999
+ vars["--dauth-text-primary"] = t.textPrimary;
1000
+ if (t.textSecondary)
1001
+ vars["--dauth-text-secondary"] = t.textSecondary;
542
1002
  if (t.border) vars["--dauth-border"] = t.border;
543
1003
  return vars;
544
1004
  }, [domain.modalTheme]);
@@ -589,6 +1049,11 @@ function DauthProfileModal({ open, onClose }) {
589
1049
  transition: `transform ${dur}ms ${easing}`
590
1050
  };
591
1051
  const avatarInitial = (user.name || user.email || "?").charAt(0).toUpperCase();
1052
+ const tabs = [
1053
+ { key: "profile", label: "Profile" },
1054
+ ...showSecurity ? [{ key: "security", label: "Security" }] : [],
1055
+ { key: "account", label: "Account" }
1056
+ ];
592
1057
  return createPortal(
593
1058
  /* @__PURE__ */ jsxs(Fragment, { children: [
594
1059
  /* @__PURE__ */ jsx(
@@ -632,191 +1097,663 @@ function DauthProfileModal({ open, onClose }) {
632
1097
  /* @__PURE__ */ jsx("h2", { id: "dauth-profile-title", style: titleStyle, children: "Your Profile" }),
633
1098
  /* @__PURE__ */ jsx("div", { style: { width: 36 } })
634
1099
  ] }),
1100
+ /* @__PURE__ */ jsx("div", { style: tabBar, role: "tablist", children: tabs.map((t) => /* @__PURE__ */ jsx(
1101
+ "button",
1102
+ {
1103
+ role: "tab",
1104
+ type: "button",
1105
+ "aria-selected": activeTab === t.key,
1106
+ style: {
1107
+ ...tabBtn,
1108
+ color: activeTab === t.key ? "var(--dauth-accent, #6366f1)" : "var(--dauth-text-secondary, #a1a1aa)",
1109
+ borderBottomColor: activeTab === t.key ? "var(--dauth-accent, #6366f1)" : "transparent"
1110
+ },
1111
+ onClick: () => setActiveTab(t.key),
1112
+ children: t.label
1113
+ },
1114
+ t.key
1115
+ )) }),
635
1116
  /* @__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",
1117
+ activeTab === "profile" && /* @__PURE__ */ jsxs(Fragment, { children: [
1118
+ /* @__PURE__ */ jsxs("div", { style: avatarSection, children: [
1119
+ /* @__PURE__ */ jsxs(
1120
+ "div",
665
1121
  {
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
1122
+ style: {
1123
+ ...avatarCircle,
1124
+ cursor: onAvatarUpload ? "pointer" : "default",
1125
+ position: "relative"
1126
+ },
1127
+ onClick: handleAvatarClick,
1128
+ children: [
1129
+ uploadingAvatar ? /* @__PURE__ */ jsx(Spinner, {}) : user.avatar?.url ? /* @__PURE__ */ jsx(
1130
+ "img",
1131
+ {
1132
+ src: user.avatar.url,
1133
+ alt: "",
1134
+ style: {
1135
+ width: "100%",
1136
+ height: "100%",
1137
+ objectFit: "cover"
1138
+ }
1139
+ }
1140
+ ) : avatarInitial,
1141
+ onAvatarUpload && !uploadingAvatar && /* @__PURE__ */ jsx("div", { style: avatarOverlay, children: /* @__PURE__ */ jsx(IconCamera, {}) })
1142
+ ]
675
1143
  }
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(
1144
+ ),
1145
+ /* @__PURE__ */ jsx("div", { style: emailText, children: user.email }),
1146
+ onAvatarUpload && /* @__PURE__ */ jsx(
684
1147
  "input",
685
1148
  {
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
1149
+ ref: avatarInputRef,
1150
+ type: "file",
1151
+ accept: "image/*",
1152
+ style: { display: "none" },
1153
+ onChange: handleAvatarChange
695
1154
  }
696
1155
  )
697
1156
  ] }),
698
- hasField("nickname") && /* @__PURE__ */ jsxs("div", { style: fieldGroup, children: [
699
- /* @__PURE__ */ jsxs("label", { htmlFor: "dauth-nickname", style: label, children: [
700
- "Nickname",
701
- isRequired("nickname") ? " *" : ""
1157
+ status && /* @__PURE__ */ jsx(
1158
+ "div",
1159
+ {
1160
+ role: "status",
1161
+ "aria-live": "polite",
1162
+ style: statusMsg(status.type),
1163
+ children: status.message
1164
+ }
1165
+ ),
1166
+ /* @__PURE__ */ jsxs("div", { children: [
1167
+ /* @__PURE__ */ jsxs("div", { style: fieldGroup, children: [
1168
+ /* @__PURE__ */ jsx(
1169
+ "label",
1170
+ {
1171
+ htmlFor: "dauth-name",
1172
+ style: label,
1173
+ children: "Name *"
1174
+ }
1175
+ ),
1176
+ /* @__PURE__ */ jsx(
1177
+ "input",
1178
+ {
1179
+ id: "dauth-name",
1180
+ type: "text",
1181
+ value: name,
1182
+ onChange: (e) => setName(e.target.value),
1183
+ placeholder: "Your name",
1184
+ disabled: saving,
1185
+ style: input,
1186
+ onFocus: inputFocusHandler,
1187
+ onBlur: inputBlurHandler
1188
+ }
1189
+ )
702
1190
  ] }),
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") ? " *" : ""
1191
+ hasField("lastname") && /* @__PURE__ */ jsxs("div", { style: fieldGroup, children: [
1192
+ /* @__PURE__ */ jsxs(
1193
+ "label",
1194
+ {
1195
+ htmlFor: "dauth-lastname",
1196
+ style: label,
1197
+ children: [
1198
+ "Last name",
1199
+ isRequired("lastname") ? " *" : ""
1200
+ ]
1201
+ }
1202
+ ),
1203
+ /* @__PURE__ */ jsx(
1204
+ "input",
1205
+ {
1206
+ id: "dauth-lastname",
1207
+ type: "text",
1208
+ value: lastname,
1209
+ onChange: (e) => setLastname(e.target.value),
1210
+ placeholder: "Your last name",
1211
+ disabled: saving,
1212
+ style: input,
1213
+ onFocus: inputFocusHandler,
1214
+ onBlur: inputBlurHandler
1215
+ }
1216
+ )
722
1217
  ] }),
723
- /* @__PURE__ */ jsx(
724
- "input",
1218
+ hasField("nickname") && /* @__PURE__ */ jsxs("div", { style: fieldGroup, children: [
1219
+ /* @__PURE__ */ jsxs(
1220
+ "label",
1221
+ {
1222
+ htmlFor: "dauth-nickname",
1223
+ style: label,
1224
+ children: [
1225
+ "Nickname",
1226
+ isRequired("nickname") ? " *" : ""
1227
+ ]
1228
+ }
1229
+ ),
1230
+ /* @__PURE__ */ jsx(
1231
+ "input",
1232
+ {
1233
+ id: "dauth-nickname",
1234
+ type: "text",
1235
+ value: nickname,
1236
+ onChange: (e) => setNickname(e.target.value),
1237
+ placeholder: "Choose a nickname",
1238
+ disabled: saving,
1239
+ style: input,
1240
+ onFocus: inputFocusHandler,
1241
+ onBlur: inputBlurHandler
1242
+ }
1243
+ )
1244
+ ] }),
1245
+ hasField("country") && /* @__PURE__ */ jsxs("div", { style: fieldGroup, children: [
1246
+ /* @__PURE__ */ jsxs(
1247
+ "label",
1248
+ {
1249
+ htmlFor: "dauth-country",
1250
+ style: label,
1251
+ children: [
1252
+ "Country",
1253
+ isRequired("country") ? " *" : ""
1254
+ ]
1255
+ }
1256
+ ),
1257
+ /* @__PURE__ */ jsx(
1258
+ "input",
1259
+ {
1260
+ id: "dauth-country",
1261
+ type: "text",
1262
+ value: country,
1263
+ onChange: (e) => setCountry(e.target.value),
1264
+ placeholder: "Your country",
1265
+ disabled: saving,
1266
+ style: input,
1267
+ onFocus: inputFocusHandler,
1268
+ onBlur: inputBlurHandler
1269
+ }
1270
+ )
1271
+ ] }),
1272
+ (hasField("tel_prefix") || hasField("tel_suffix")) && /* @__PURE__ */ jsxs("div", { style: fieldGroup, children: [
1273
+ /* @__PURE__ */ jsxs("div", { style: label, children: [
1274
+ "Phone",
1275
+ isRequired("tel_prefix") || isRequired("tel_suffix") ? " *" : ""
1276
+ ] }),
1277
+ /* @__PURE__ */ jsxs(
1278
+ "div",
1279
+ {
1280
+ style: {
1281
+ display: "flex",
1282
+ gap: 8
1283
+ },
1284
+ children: [
1285
+ hasField("tel_prefix") && /* @__PURE__ */ jsx(
1286
+ "input",
1287
+ {
1288
+ id: "dauth-tel-prefix",
1289
+ type: "text",
1290
+ value: telPrefix,
1291
+ onChange: (e) => setTelPrefix(e.target.value),
1292
+ placeholder: "+34",
1293
+ disabled: saving,
1294
+ style: {
1295
+ ...input,
1296
+ width: 80,
1297
+ flexShrink: 0
1298
+ },
1299
+ onFocus: inputFocusHandler,
1300
+ onBlur: inputBlurHandler,
1301
+ "aria-label": "Phone prefix"
1302
+ }
1303
+ ),
1304
+ hasField("tel_suffix") && /* @__PURE__ */ jsx(
1305
+ "input",
1306
+ {
1307
+ id: "dauth-tel-suffix",
1308
+ type: "tel",
1309
+ value: telSuffix,
1310
+ onChange: (e) => setTelSuffix(e.target.value),
1311
+ placeholder: "612 345 678",
1312
+ disabled: saving,
1313
+ style: {
1314
+ ...input,
1315
+ flex: 1
1316
+ },
1317
+ onFocus: inputFocusHandler,
1318
+ onBlur: inputBlurHandler,
1319
+ "aria-label": "Phone number"
1320
+ }
1321
+ )
1322
+ ]
1323
+ }
1324
+ )
1325
+ ] }),
1326
+ hasField("birth_date") && /* @__PURE__ */ jsxs("div", { style: fieldGroup, children: [
1327
+ /* @__PURE__ */ jsxs(
1328
+ "label",
1329
+ {
1330
+ htmlFor: "dauth-birthdate",
1331
+ style: label,
1332
+ children: [
1333
+ "Birth date",
1334
+ isRequired("birth_date") ? " *" : ""
1335
+ ]
1336
+ }
1337
+ ),
1338
+ /* @__PURE__ */ jsx(
1339
+ "input",
1340
+ {
1341
+ id: "dauth-birthdate",
1342
+ type: "date",
1343
+ value: birthDate,
1344
+ onChange: (e) => setBirthDate(e.target.value),
1345
+ disabled: saving,
1346
+ style: input,
1347
+ onFocus: inputFocusHandler,
1348
+ onBlur: inputBlurHandler
1349
+ }
1350
+ )
1351
+ ] }),
1352
+ (domain.customFields ?? []).length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
1353
+ /* @__PURE__ */ jsx("hr", { style: separator }),
1354
+ domain.customFields.map((cf) => /* @__PURE__ */ jsxs(
1355
+ "div",
1356
+ {
1357
+ style: fieldGroup,
1358
+ children: [
1359
+ /* @__PURE__ */ jsxs(
1360
+ "label",
1361
+ {
1362
+ htmlFor: `dauth-cf-${cf.key}`,
1363
+ style: label,
1364
+ children: [
1365
+ cf.label,
1366
+ cf.required ? " *" : ""
1367
+ ]
1368
+ }
1369
+ ),
1370
+ /* @__PURE__ */ jsx(
1371
+ "input",
1372
+ {
1373
+ id: `dauth-cf-${cf.key}`,
1374
+ type: "text",
1375
+ value: customFieldValues[cf.key] ?? "",
1376
+ onChange: (e) => setCustomFieldValues(
1377
+ (prev) => ({
1378
+ ...prev,
1379
+ [cf.key]: e.target.value
1380
+ })
1381
+ ),
1382
+ disabled: saving,
1383
+ style: input,
1384
+ onFocus: inputFocusHandler,
1385
+ onBlur: inputBlurHandler
1386
+ }
1387
+ )
1388
+ ]
1389
+ },
1390
+ cf.key
1391
+ ))
1392
+ ] })
1393
+ ] }),
1394
+ /* @__PURE__ */ jsx("hr", { style: separator }),
1395
+ /* @__PURE__ */ jsxs("div", { style: fieldGroup, children: [
1396
+ /* @__PURE__ */ jsx("div", { style: label, children: "Language" }),
1397
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 8 }, children: ["es", "en"].map((lang) => /* @__PURE__ */ jsx(
1398
+ "button",
725
1399
  {
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
- )
1400
+ type: "button",
1401
+ style: {
1402
+ ...langBtn,
1403
+ backgroundColor: user.language === lang ? "var(--dauth-accent, #6366f1)" : "var(--dauth-surface-secondary, rgba(255, 255, 255, 0.04))",
1404
+ color: user.language === lang ? "#ffffff" : "var(--dauth-text-secondary, #a1a1aa)"
1405
+ },
1406
+ onClick: () => handleLanguage(lang),
1407
+ children: lang === "es" ? "Espa\xF1ol" : "English"
1408
+ },
1409
+ lang
1410
+ )) })
737
1411
  ] })
738
1412
  ] }),
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",
1413
+ activeTab === "security" && showSecurity && /* @__PURE__ */ jsxs(Fragment, { children: [
1414
+ /* @__PURE__ */ jsxs(
1415
+ "div",
745
1416
  {
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"
1417
+ style: {
1418
+ display: "flex",
1419
+ alignItems: "center",
1420
+ justifyContent: "space-between",
1421
+ marginBottom: 16
1422
+ },
1423
+ children: [
1424
+ /* @__PURE__ */ jsx(
1425
+ "div",
1426
+ {
1427
+ style: {
1428
+ ...label,
1429
+ marginBottom: 0,
1430
+ fontWeight: 600
1431
+ },
1432
+ children: "Passkeys"
1433
+ }
1434
+ ),
1435
+ /* @__PURE__ */ jsx(
1436
+ "button",
1437
+ {
1438
+ type: "button",
1439
+ style: outlineBtn,
1440
+ onClick: () => setShowRegister(!showRegister),
1441
+ onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "var(--dauth-surface-hover, #232340)",
1442
+ onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "transparent",
1443
+ children: "+ Add passkey"
1444
+ }
1445
+ )
1446
+ ]
752
1447
  }
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."
1448
+ ),
1449
+ showRegister && /* @__PURE__ */ jsxs("div", { style: registerPanel, children: [
1450
+ /* @__PURE__ */ jsxs("div", { style: fieldGroup, children: [
1451
+ /* @__PURE__ */ jsx(
1452
+ "label",
1453
+ {
1454
+ htmlFor: "dauth-passkey-name",
1455
+ style: label,
1456
+ children: "Passkey name (optional)"
1457
+ }
1458
+ ),
1459
+ /* @__PURE__ */ jsx(
1460
+ "input",
1461
+ {
1462
+ id: "dauth-passkey-name",
1463
+ type: "text",
1464
+ value: passkeyName,
1465
+ onChange: (e) => setPasskeyName(e.target.value),
1466
+ placeholder: "e.g. MacBook Touch ID",
1467
+ disabled: registering,
1468
+ style: input,
1469
+ onFocus: inputFocusHandler,
1470
+ onBlur: inputBlurHandler
1471
+ }
1472
+ )
759
1473
  ] }),
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
1474
  /* @__PURE__ */ jsxs(
774
1475
  "div",
775
1476
  {
776
1477
  style: {
777
1478
  display: "flex",
778
- gap: 8,
779
- marginTop: 12
1479
+ gap: 8
780
1480
  },
781
1481
  children: [
782
- /* @__PURE__ */ jsx(
1482
+ /* @__PURE__ */ jsxs(
783
1483
  "button",
784
1484
  {
785
1485
  type: "button",
786
- style: cancelBtn,
787
- onClick: () => {
788
- setShowDelete(false);
789
- setDeleteText("");
1486
+ style: {
1487
+ ...smallAccentBtn,
1488
+ opacity: registering ? 0.6 : 1
790
1489
  },
791
- onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "var(--dauth-surface-hover, #232340)",
792
- onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "transparent",
793
- children: "Cancel"
1490
+ disabled: registering,
1491
+ onClick: handleRegisterPasskey,
1492
+ children: [
1493
+ registering ? /* @__PURE__ */ jsx(Spinner, {}) : /* @__PURE__ */ jsx(IconFingerprint, {}),
1494
+ registering ? "Registering..." : "Register"
1495
+ ]
794
1496
  }
795
1497
  ),
796
- /* @__PURE__ */ jsxs(
1498
+ /* @__PURE__ */ jsx(
797
1499
  "button",
798
1500
  {
799
1501
  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
- ]
1502
+ style: cancelBtn,
1503
+ onClick: () => setShowRegister(false),
1504
+ onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "var(--dauth-surface-hover, #232340)",
1505
+ onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "transparent",
1506
+ children: "Cancel"
811
1507
  }
812
1508
  )
813
1509
  ]
814
1510
  }
815
1511
  )
816
- ] })
1512
+ ] }),
1513
+ passkeyStatus && /* @__PURE__ */ jsx(
1514
+ "div",
1515
+ {
1516
+ role: "status",
1517
+ "aria-live": "polite",
1518
+ style: {
1519
+ ...statusMsg(passkeyStatus.type),
1520
+ marginTop: 12
1521
+ },
1522
+ children: passkeyStatus.message
1523
+ }
1524
+ ),
1525
+ /* @__PURE__ */ jsx("div", { style: { marginTop: 12 }, children: loadingCreds ? /* @__PURE__ */ jsx(
1526
+ "div",
1527
+ {
1528
+ style: {
1529
+ textAlign: "center",
1530
+ padding: 24
1531
+ },
1532
+ children: /* @__PURE__ */ jsx(Spinner, {})
1533
+ }
1534
+ ) : credentials.length > 0 ? credentials.map((cred) => /* @__PURE__ */ jsxs(
1535
+ "div",
1536
+ {
1537
+ style: credentialRow,
1538
+ children: [
1539
+ /* @__PURE__ */ jsxs(
1540
+ "div",
1541
+ {
1542
+ style: {
1543
+ display: "flex",
1544
+ alignItems: "center",
1545
+ gap: 12,
1546
+ flex: 1,
1547
+ minWidth: 0
1548
+ },
1549
+ children: [
1550
+ /* @__PURE__ */ jsx(
1551
+ "span",
1552
+ {
1553
+ style: {
1554
+ color: "var(--dauth-accent, #6366f1)",
1555
+ flexShrink: 0
1556
+ },
1557
+ children: /* @__PURE__ */ jsx(IconFingerprint, {})
1558
+ }
1559
+ ),
1560
+ /* @__PURE__ */ jsxs(
1561
+ "div",
1562
+ {
1563
+ style: {
1564
+ minWidth: 0,
1565
+ flex: 1
1566
+ },
1567
+ children: [
1568
+ /* @__PURE__ */ jsx(
1569
+ "div",
1570
+ {
1571
+ style: {
1572
+ fontSize: "var(--dauth-font-size-sm, 0.875rem)",
1573
+ fontWeight: 500,
1574
+ color: "var(--dauth-text-primary, #e4e4e7)",
1575
+ overflow: "hidden",
1576
+ textOverflow: "ellipsis",
1577
+ whiteSpace: "nowrap"
1578
+ },
1579
+ children: cred.name || "Passkey"
1580
+ }
1581
+ ),
1582
+ /* @__PURE__ */ jsxs(
1583
+ "div",
1584
+ {
1585
+ style: {
1586
+ fontSize: "var(--dauth-font-size-xs, 0.75rem)",
1587
+ color: "var(--dauth-text-muted, #71717a)"
1588
+ },
1589
+ children: [
1590
+ cred.deviceType === "multiDevice" ? "Synced" : "Device-bound",
1591
+ cred.createdAt && ` \xB7 Created ${new Date(cred.createdAt).toLocaleDateString()}`
1592
+ ]
1593
+ }
1594
+ )
1595
+ ]
1596
+ }
1597
+ )
1598
+ ]
1599
+ }
1600
+ ),
1601
+ /* @__PURE__ */ jsx(
1602
+ "button",
1603
+ {
1604
+ type: "button",
1605
+ onClick: () => handleDeletePasskey(cred._id),
1606
+ style: trashBtn,
1607
+ onMouseEnter: (e) => e.currentTarget.style.color = "var(--dauth-error, #ef4444)",
1608
+ onMouseLeave: (e) => e.currentTarget.style.color = "var(--dauth-text-muted, #71717a)",
1609
+ "aria-label": `Delete passkey ${cred.name || ""}`,
1610
+ children: /* @__PURE__ */ jsx(IconTrash, {})
1611
+ }
1612
+ )
1613
+ ]
1614
+ },
1615
+ cred._id
1616
+ )) : /* @__PURE__ */ jsxs("div", { style: emptyState, children: [
1617
+ /* @__PURE__ */ jsx(
1618
+ "span",
1619
+ {
1620
+ style: {
1621
+ color: "var(--dauth-accent, #6366f1)"
1622
+ },
1623
+ children: /* @__PURE__ */ jsx(IconShield, {})
1624
+ }
1625
+ ),
1626
+ /* @__PURE__ */ jsxs("div", { children: [
1627
+ /* @__PURE__ */ jsx(
1628
+ "div",
1629
+ {
1630
+ style: {
1631
+ fontSize: "var(--dauth-font-size-sm, 0.875rem)",
1632
+ fontWeight: 500,
1633
+ color: "var(--dauth-text-primary, #e4e4e7)"
1634
+ },
1635
+ children: "No passkeys registered"
1636
+ }
1637
+ ),
1638
+ /* @__PURE__ */ jsx(
1639
+ "div",
1640
+ {
1641
+ style: {
1642
+ fontSize: "var(--dauth-font-size-xs, 0.75rem)",
1643
+ color: "var(--dauth-text-secondary, #a1a1aa)"
1644
+ },
1645
+ children: "Add a passkey for faster, more secure sign-in."
1646
+ }
1647
+ )
1648
+ ] })
1649
+ ] }) })
1650
+ ] }),
1651
+ activeTab === "account" && /* @__PURE__ */ jsxs(Fragment, { children: [
1652
+ status && /* @__PURE__ */ jsx(
1653
+ "div",
1654
+ {
1655
+ role: "status",
1656
+ "aria-live": "polite",
1657
+ style: statusMsg(status.type),
1658
+ children: status.message
1659
+ }
1660
+ ),
1661
+ /* @__PURE__ */ jsxs("div", { children: [
1662
+ /* @__PURE__ */ jsx("div", { style: dangerTitle, children: "Delete account" }),
1663
+ /* @__PURE__ */ jsx("div", { style: dangerDesc, children: "Permanently delete your account and all associated data." }),
1664
+ !showDelete ? /* @__PURE__ */ jsx(
1665
+ "button",
1666
+ {
1667
+ type: "button",
1668
+ style: deleteBtn,
1669
+ onClick: () => setShowDelete(true),
1670
+ onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "rgba(239, 68, 68, 0.2)",
1671
+ onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "var(--dauth-error-bg, rgba(239, 68, 68, 0.1))",
1672
+ children: "Delete account"
1673
+ }
1674
+ ) : /* @__PURE__ */ jsxs("div", { style: deletePanel, children: [
1675
+ /* @__PURE__ */ jsxs("div", { style: deletePanelText, children: [
1676
+ "This action is permanent and cannot be undone. Type",
1677
+ " ",
1678
+ /* @__PURE__ */ jsx("strong", { children: CONFIRM_WORD }),
1679
+ " to confirm."
1680
+ ] }),
1681
+ /* @__PURE__ */ jsx(
1682
+ "input",
1683
+ {
1684
+ type: "text",
1685
+ value: deleteText,
1686
+ onChange: (e) => setDeleteText(e.target.value),
1687
+ placeholder: `Type ${CONFIRM_WORD}`,
1688
+ style: input,
1689
+ onFocus: inputFocusHandler,
1690
+ onBlur: inputBlurHandler,
1691
+ disabled: deleting
1692
+ }
1693
+ ),
1694
+ /* @__PURE__ */ jsxs(
1695
+ "div",
1696
+ {
1697
+ style: {
1698
+ display: "flex",
1699
+ gap: 8,
1700
+ marginTop: 12
1701
+ },
1702
+ children: [
1703
+ /* @__PURE__ */ jsx(
1704
+ "button",
1705
+ {
1706
+ type: "button",
1707
+ style: cancelBtn,
1708
+ onClick: () => {
1709
+ setShowDelete(false);
1710
+ setDeleteText("");
1711
+ },
1712
+ onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "var(--dauth-surface-hover, #232340)",
1713
+ onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "transparent",
1714
+ children: "Cancel"
1715
+ }
1716
+ ),
1717
+ /* @__PURE__ */ jsxs(
1718
+ "button",
1719
+ {
1720
+ type: "button",
1721
+ style: {
1722
+ ...deleteConfirmBtn,
1723
+ opacity: deleteText !== CONFIRM_WORD || deleting ? 0.5 : 1,
1724
+ cursor: deleteText !== CONFIRM_WORD || deleting ? "not-allowed" : "pointer"
1725
+ },
1726
+ disabled: deleteText !== CONFIRM_WORD || deleting,
1727
+ onClick: handleDelete,
1728
+ children: [
1729
+ deleting && /* @__PURE__ */ jsx(Spinner, {}),
1730
+ "Delete my account"
1731
+ ]
1732
+ }
1733
+ )
1734
+ ]
1735
+ }
1736
+ )
1737
+ ] })
1738
+ ] }),
1739
+ /* @__PURE__ */ jsx("hr", { style: separator }),
1740
+ /* @__PURE__ */ jsxs(
1741
+ "button",
1742
+ {
1743
+ type: "button",
1744
+ style: signOutBtn,
1745
+ onClick: handleSignOut,
1746
+ onMouseEnter: (e) => e.currentTarget.style.backgroundColor = "rgba(239, 68, 68, 0.2)",
1747
+ onMouseLeave: (e) => e.currentTarget.style.backgroundColor = "var(--dauth-error-bg, rgba(239, 68, 68, 0.1))",
1748
+ children: [
1749
+ /* @__PURE__ */ jsx(IconLogOut, {}),
1750
+ "Sign out"
1751
+ ]
1752
+ }
1753
+ )
817
1754
  ] })
818
1755
  ] }),
819
- /* @__PURE__ */ jsx("div", { style: footerStyle(isDesktop), children: /* @__PURE__ */ jsxs(
1756
+ activeTab === "profile" && /* @__PURE__ */ jsx("div", { style: footerStyle(isDesktop), children: /* @__PURE__ */ jsxs(
820
1757
  "button",
821
1758
  {
822
1759
  type: "button",
@@ -854,8 +1791,7 @@ var headerStyle = (isDesktop) => ({
854
1791
  display: "flex",
855
1792
  alignItems: "center",
856
1793
  justifyContent: "space-between",
857
- padding: "16px 24px",
858
- borderBottom: "1px solid var(--dauth-border, rgba(255, 255, 255, 0.08))",
1794
+ padding: "16px 24px 0",
859
1795
  flexShrink: 0,
860
1796
  ...!isDesktop ? {
861
1797
  position: "sticky",
@@ -886,6 +1822,25 @@ var closeBtn = {
886
1822
  transition: "background-color 150ms, color 150ms",
887
1823
  padding: 0
888
1824
  };
1825
+ var tabBar = {
1826
+ display: "flex",
1827
+ padding: "0 24px",
1828
+ borderBottom: "1px solid var(--dauth-border, rgba(255, 255, 255, 0.08))",
1829
+ flexShrink: 0
1830
+ };
1831
+ var tabBtn = {
1832
+ flex: 1,
1833
+ padding: "12px 4px",
1834
+ fontSize: "var(--dauth-font-size-sm, 0.875rem)",
1835
+ fontWeight: 500,
1836
+ border: "none",
1837
+ borderBottom: "2px solid transparent",
1838
+ backgroundColor: "transparent",
1839
+ cursor: "pointer",
1840
+ transition: "color 150ms, border-color 150ms",
1841
+ fontFamily: "inherit",
1842
+ textAlign: "center"
1843
+ };
889
1844
  var bodyStyle = {
890
1845
  flex: 1,
891
1846
  overflowY: "auto",
@@ -912,6 +1867,18 @@ var avatarCircle = {
912
1867
  fontSize: "1.5rem",
913
1868
  fontWeight: 600
914
1869
  };
1870
+ var avatarOverlay = {
1871
+ position: "absolute",
1872
+ inset: 0,
1873
+ backgroundColor: "rgba(0, 0, 0, 0.4)",
1874
+ display: "flex",
1875
+ alignItems: "center",
1876
+ justifyContent: "center",
1877
+ borderRadius: "50%",
1878
+ opacity: 0.7,
1879
+ transition: "opacity 150ms",
1880
+ color: "#ffffff"
1881
+ };
915
1882
  var emailText = {
916
1883
  fontSize: "var(--dauth-font-size-sm, 0.875rem)",
917
1884
  color: "var(--dauth-text-secondary, #a1a1aa)"
@@ -926,7 +1893,9 @@ var statusMsg = (type) => ({
926
1893
  textAlign: "center",
927
1894
  lineHeight: 1.5
928
1895
  });
929
- var fieldGroup = { marginBottom: 16 };
1896
+ var fieldGroup = {
1897
+ marginBottom: 16
1898
+ };
930
1899
  var label = {
931
1900
  display: "block",
932
1901
  fontSize: "var(--dauth-font-size-sm, 0.875rem)",
@@ -956,12 +1925,88 @@ var inputBlurHandler = (e) => {
956
1925
  e.currentTarget.style.borderColor = "var(--dauth-border, rgba(255, 255, 255, 0.08))";
957
1926
  e.currentTarget.style.boxShadow = "none";
958
1927
  };
1928
+ var langBtn = {
1929
+ padding: "8px 16px",
1930
+ fontSize: "var(--dauth-font-size-sm, 0.875rem)",
1931
+ fontWeight: 500,
1932
+ border: "none",
1933
+ borderRadius: "var(--dauth-radius-sm, 8px)",
1934
+ cursor: "pointer",
1935
+ transition: "background-color 150ms, color 150ms",
1936
+ fontFamily: "inherit"
1937
+ };
959
1938
  var separator = {
960
1939
  height: 1,
961
1940
  backgroundColor: "var(--dauth-border, rgba(255, 255, 255, 0.08))",
962
1941
  margin: "24px 0",
963
1942
  border: "none"
964
1943
  };
1944
+ var outlineBtn = {
1945
+ padding: "6px 12px",
1946
+ fontSize: "var(--dauth-font-size-xs, 0.75rem)",
1947
+ fontWeight: 500,
1948
+ color: "var(--dauth-text-secondary, #a1a1aa)",
1949
+ backgroundColor: "transparent",
1950
+ border: "1px solid var(--dauth-border, rgba(255, 255, 255, 0.08))",
1951
+ borderRadius: "var(--dauth-radius-sm, 8px)",
1952
+ cursor: "pointer",
1953
+ transition: "background-color 150ms",
1954
+ fontFamily: "inherit"
1955
+ };
1956
+ var registerPanel = {
1957
+ padding: 16,
1958
+ borderRadius: "var(--dauth-radius-sm, 8px)",
1959
+ border: "1px solid var(--dauth-border, rgba(255, 255, 255, 0.08))",
1960
+ backgroundColor: "var(--dauth-surface-secondary, rgba(255, 255, 255, 0.04))",
1961
+ marginBottom: 12
1962
+ };
1963
+ var smallAccentBtn = {
1964
+ padding: "8px 16px",
1965
+ fontSize: "var(--dauth-font-size-sm, 0.875rem)",
1966
+ fontWeight: 500,
1967
+ color: "#ffffff",
1968
+ backgroundColor: "var(--dauth-accent, #6366f1)",
1969
+ border: "none",
1970
+ borderRadius: "var(--dauth-radius-sm, 8px)",
1971
+ cursor: "pointer",
1972
+ transition: "opacity 150ms",
1973
+ fontFamily: "inherit",
1974
+ display: "flex",
1975
+ alignItems: "center",
1976
+ gap: 6
1977
+ };
1978
+ var credentialRow = {
1979
+ display: "flex",
1980
+ alignItems: "center",
1981
+ justifyContent: "space-between",
1982
+ padding: 12,
1983
+ borderRadius: "var(--dauth-radius-sm, 8px)",
1984
+ backgroundColor: "var(--dauth-surface-secondary, rgba(255, 255, 255, 0.04))",
1985
+ marginBottom: 8
1986
+ };
1987
+ var trashBtn = {
1988
+ display: "flex",
1989
+ alignItems: "center",
1990
+ justifyContent: "center",
1991
+ width: 28,
1992
+ height: 28,
1993
+ border: "none",
1994
+ backgroundColor: "transparent",
1995
+ color: "var(--dauth-text-muted, #71717a)",
1996
+ cursor: "pointer",
1997
+ transition: "color 150ms",
1998
+ padding: 0,
1999
+ borderRadius: "var(--dauth-radius-sm, 8px)",
2000
+ flexShrink: 0
2001
+ };
2002
+ var emptyState = {
2003
+ display: "flex",
2004
+ alignItems: "center",
2005
+ gap: 12,
2006
+ padding: 16,
2007
+ borderRadius: "var(--dauth-radius-sm, 8px)",
2008
+ backgroundColor: "var(--dauth-surface-secondary, rgba(255, 255, 255, 0.04))"
2009
+ };
965
2010
  var dangerTitle = {
966
2011
  fontSize: "var(--dauth-font-size-sm, 0.875rem)",
967
2012
  fontWeight: 600,
@@ -1000,7 +2045,6 @@ var deletePanelText = {
1000
2045
  lineHeight: 1.5
1001
2046
  };
1002
2047
  var cancelBtn = {
1003
- flex: 1,
1004
2048
  padding: "8px 16px",
1005
2049
  fontSize: "var(--dauth-font-size-sm, 0.875rem)",
1006
2050
  fontWeight: 500,
@@ -1029,6 +2073,23 @@ var deleteConfirmBtn = {
1029
2073
  justifyContent: "center",
1030
2074
  gap: 8
1031
2075
  };
2076
+ var signOutBtn = {
2077
+ width: "100%",
2078
+ padding: "12px 24px",
2079
+ fontSize: "var(--dauth-font-size-base, 1rem)",
2080
+ fontWeight: 500,
2081
+ color: "var(--dauth-error, #ef4444)",
2082
+ backgroundColor: "var(--dauth-error-bg, rgba(239, 68, 68, 0.1))",
2083
+ border: "1px solid rgba(239, 68, 68, 0.2)",
2084
+ borderRadius: "var(--dauth-radius-sm, 8px)",
2085
+ cursor: "pointer",
2086
+ transition: "background-color 150ms",
2087
+ fontFamily: "inherit",
2088
+ display: "flex",
2089
+ alignItems: "center",
2090
+ justifyContent: "center",
2091
+ gap: 8
2092
+ };
1032
2093
  var footerStyle = (isDesktop) => ({
1033
2094
  padding: "16px 24px",
1034
2095
  borderTop: "1px solid var(--dauth-border, rgba(255, 255, 255, 0.08))",
@@ -1140,15 +2201,39 @@ var DauthProvider = (props) => {
1140
2201
  () => deleteAccountAction(ctx),
1141
2202
  [ctx]
1142
2203
  );
2204
+ const getPasskeyCredentials = useCallback2(
2205
+ () => getPasskeyCredentialsAction(ctx),
2206
+ [ctx]
2207
+ );
2208
+ const registerPasskey = useCallback2(
2209
+ (name) => registerPasskeyAction(ctx, name),
2210
+ [ctx]
2211
+ );
2212
+ const deletePasskeyCredential = useCallback2(
2213
+ (credentialId) => deletePasskeyCredentialAction(ctx, credentialId),
2214
+ [ctx]
2215
+ );
1143
2216
  const memoProvider = useMemo2(
1144
2217
  () => ({
1145
2218
  ...dauthState,
1146
2219
  loginWithRedirect,
1147
2220
  logout,
1148
2221
  updateUser,
1149
- deleteAccount
2222
+ deleteAccount,
2223
+ getPasskeyCredentials,
2224
+ registerPasskey,
2225
+ deletePasskeyCredential
1150
2226
  }),
1151
- [dauthState, loginWithRedirect, logout, updateUser, deleteAccount]
2227
+ [
2228
+ dauthState,
2229
+ loginWithRedirect,
2230
+ logout,
2231
+ updateUser,
2232
+ deleteAccount,
2233
+ getPasskeyCredentials,
2234
+ registerPasskey,
2235
+ deletePasskeyCredential
2236
+ ]
1152
2237
  );
1153
2238
  return /* @__PURE__ */ jsx2(DauthContext.Provider, { value: memoProvider, children });
1154
2239
  };