dauth-context-react 6.2.0 → 6.4.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.d.mts CHANGED
@@ -18,6 +18,7 @@ interface IDauthUser {
18
18
  birthDate?: Date;
19
19
  country?: string;
20
20
  metadata?: Record<string, unknown>;
21
+ customFields?: Record<string, string>;
21
22
  createdAt: Date;
22
23
  updatedAt: Date;
23
24
  lastLogin: Date;
@@ -34,6 +35,11 @@ interface IFormField {
34
35
  field: string;
35
36
  required: boolean;
36
37
  }
38
+ interface ICustomField {
39
+ key: string;
40
+ label: string;
41
+ required: boolean;
42
+ }
37
43
  interface IModalTheme {
38
44
  accent?: string;
39
45
  accentHover?: string;
@@ -50,6 +56,7 @@ interface IDauthDomainState {
50
56
  allowedOrigins: string[];
51
57
  authMethods?: IDauthAuthMethods;
52
58
  formFields?: IFormField[];
59
+ customFields?: ICustomField[];
53
60
  modalTheme?: IModalTheme;
54
61
  }
55
62
  interface IPasskeyCredential {
@@ -72,13 +79,14 @@ interface IDauthState {
72
79
  getPasskeyCredentials: () => Promise<IPasskeyCredential[]>;
73
80
  registerPasskey: (name?: string) => Promise<IPasskeyCredential | null>;
74
81
  deletePasskeyCredential: (credentialId: string) => Promise<boolean>;
82
+ uploadAvatar: (file: File) => Promise<boolean>;
75
83
  }
76
84
  interface DauthProfileModalProps {
77
85
  open: boolean;
78
86
  onClose: () => void;
79
- /** Optional: provide a function to handle avatar upload.
87
+ /** Optional override for avatar upload.
80
88
  * Receives a File, should return the URL string.
81
- * If not provided, the avatar edit button is hidden. */
89
+ * If not provided, uses the built-in upload via the auth proxy. */
82
90
  onAvatarUpload?: (file: File) => Promise<string>;
83
91
  }
84
92
  interface IDauthProviderProps {
@@ -97,4 +105,4 @@ declare function DauthProfileModal({ open, onClose, onAvatarUpload, }: DauthProf
97
105
  declare const DauthProvider: React$1.FC<IDauthProviderProps>;
98
106
  declare const useDauth: () => IDauthState;
99
107
 
100
- export { DauthProfileModal, type DauthProfileModalProps, DauthProvider, type IDauthAuthMethods, type IDauthProviderProps, type IFormField, type IModalTheme, type IPasskeyCredential, useDauth };
108
+ export { DauthProfileModal, type DauthProfileModalProps, DauthProvider, type ICustomField, type IDauthAuthMethods, type IDauthProviderProps, type IFormField, type IModalTheme, type IPasskeyCredential, useDauth };
package/dist/index.d.ts CHANGED
@@ -18,6 +18,7 @@ interface IDauthUser {
18
18
  birthDate?: Date;
19
19
  country?: string;
20
20
  metadata?: Record<string, unknown>;
21
+ customFields?: Record<string, string>;
21
22
  createdAt: Date;
22
23
  updatedAt: Date;
23
24
  lastLogin: Date;
@@ -34,6 +35,11 @@ interface IFormField {
34
35
  field: string;
35
36
  required: boolean;
36
37
  }
38
+ interface ICustomField {
39
+ key: string;
40
+ label: string;
41
+ required: boolean;
42
+ }
37
43
  interface IModalTheme {
38
44
  accent?: string;
39
45
  accentHover?: string;
@@ -50,6 +56,7 @@ interface IDauthDomainState {
50
56
  allowedOrigins: string[];
51
57
  authMethods?: IDauthAuthMethods;
52
58
  formFields?: IFormField[];
59
+ customFields?: ICustomField[];
53
60
  modalTheme?: IModalTheme;
54
61
  }
55
62
  interface IPasskeyCredential {
@@ -72,13 +79,14 @@ interface IDauthState {
72
79
  getPasskeyCredentials: () => Promise<IPasskeyCredential[]>;
73
80
  registerPasskey: (name?: string) => Promise<IPasskeyCredential | null>;
74
81
  deletePasskeyCredential: (credentialId: string) => Promise<boolean>;
82
+ uploadAvatar: (file: File) => Promise<boolean>;
75
83
  }
76
84
  interface DauthProfileModalProps {
77
85
  open: boolean;
78
86
  onClose: () => void;
79
- /** Optional: provide a function to handle avatar upload.
87
+ /** Optional override for avatar upload.
80
88
  * Receives a File, should return the URL string.
81
- * If not provided, the avatar edit button is hidden. */
89
+ * If not provided, uses the built-in upload via the auth proxy. */
82
90
  onAvatarUpload?: (file: File) => Promise<string>;
83
91
  }
84
92
  interface IDauthProviderProps {
@@ -97,4 +105,4 @@ declare function DauthProfileModal({ open, onClose, onAvatarUpload, }: DauthProf
97
105
  declare const DauthProvider: React$1.FC<IDauthProviderProps>;
98
106
  declare const useDauth: () => IDauthState;
99
107
 
100
- export { DauthProfileModal, type DauthProfileModalProps, DauthProvider, type IDauthAuthMethods, type IDauthProviderProps, type IFormField, type IModalTheme, type IPasskeyCredential, useDauth };
108
+ export { DauthProfileModal, type DauthProfileModalProps, DauthProvider, type ICustomField, type IDauthAuthMethods, type IDauthProviderProps, type IFormField, type IModalTheme, type IPasskeyCredential, useDauth };
package/dist/index.js CHANGED
@@ -43,7 +43,8 @@ var initialDauthState = {
43
43
  deleteAccount: () => Promise.resolve(false),
44
44
  getPasskeyCredentials: () => Promise.resolve([]),
45
45
  registerPasskey: () => Promise.resolve(null),
46
- deletePasskeyCredential: () => Promise.resolve(false)
46
+ deletePasskeyCredential: () => Promise.resolve(false),
47
+ uploadAvatar: () => Promise.resolve(false)
47
48
  };
48
49
  var initialDauthState_default = initialDauthState;
49
50
 
@@ -205,6 +206,18 @@ async function deletePasskeyCredentialAPI(basePath, credentialId) {
205
206
  const data = await response.json();
206
207
  return { response, data };
207
208
  }
209
+ async function uploadAvatarAPI(basePath, file) {
210
+ const formData = new FormData();
211
+ formData.append("avatar", file);
212
+ const response = await fetch(`${basePath}/avatar`, {
213
+ method: "POST",
214
+ headers: { "X-CSRF-Token": getCsrfToken() },
215
+ credentials: "include",
216
+ body: formData
217
+ });
218
+ const data = await response.json();
219
+ return { response, data };
220
+ }
208
221
 
209
222
  // src/webauthn.ts
210
223
  function base64urlToBuffer(base64url) {
@@ -460,6 +473,30 @@ async function deletePasskeyCredentialAction(ctx, credentialId) {
460
473
  return false;
461
474
  }
462
475
  }
476
+ async function uploadAvatarAction(ctx, file) {
477
+ const { dispatch, authProxyPath, onError } = ctx;
478
+ try {
479
+ const result = await uploadAvatarAPI(authProxyPath, file);
480
+ if (result.response.status === 200) {
481
+ dispatch({
482
+ type: UPDATE_USER,
483
+ payload: result.data.user
484
+ });
485
+ return true;
486
+ }
487
+ onError(
488
+ new Error(
489
+ "Avatar upload error: " + result.data.message
490
+ )
491
+ );
492
+ return false;
493
+ } catch (error) {
494
+ onError(
495
+ error instanceof Error ? error : new Error("Avatar upload error")
496
+ );
497
+ return false;
498
+ }
499
+ }
463
500
  var resetUser = (dispatch) => {
464
501
  return dispatch({
465
502
  type: LOGIN,
@@ -763,7 +800,8 @@ function DauthProfileModal({
763
800
  logout,
764
801
  getPasskeyCredentials,
765
802
  registerPasskey,
766
- deletePasskeyCredential
803
+ deletePasskeyCredential,
804
+ uploadAvatar
767
805
  } = useDauth();
768
806
  const isDesktop = useMediaQuery("(min-width: 641px)");
769
807
  const phase = useModalAnimation(open);
@@ -775,6 +813,10 @@ function DauthProfileModal({
775
813
  const [lastname, setLastname] = (0, import_react.useState)("");
776
814
  const [nickname, setNickname] = (0, import_react.useState)("");
777
815
  const [country, setCountry] = (0, import_react.useState)("");
816
+ const [telPrefix, setTelPrefix] = (0, import_react.useState)("");
817
+ const [telSuffix, setTelSuffix] = (0, import_react.useState)("");
818
+ const [birthDate, setBirthDate] = (0, import_react.useState)("");
819
+ const [customFieldValues, setCustomFieldValues] = (0, import_react.useState)({});
778
820
  const [populated, setPopulated] = (0, import_react.useState)(false);
779
821
  const [saving, setSaving] = (0, import_react.useState)(false);
780
822
  const [status, setStatus] = (0, import_react.useState)(null);
@@ -794,6 +836,16 @@ function DauthProfileModal({
794
836
  setLastname(user.lastname || "");
795
837
  setNickname(user.nickname || "");
796
838
  setCountry(user.country || "");
839
+ setTelPrefix(user.telPrefix || "");
840
+ setTelSuffix(user.telSuffix || "");
841
+ setBirthDate(
842
+ user.birthDate ? new Date(user.birthDate).toISOString().split("T")[0] : ""
843
+ );
844
+ const cf = {};
845
+ for (const f of domain.customFields ?? []) {
846
+ cf[f.key] = user.customFields?.[f.key] ?? "";
847
+ }
848
+ setCustomFieldValues(cf);
797
849
  setPopulated(true);
798
850
  }
799
851
  if (!open) {
@@ -843,8 +895,23 @@ function DauthProfileModal({
843
895
  );
844
896
  const hasChanges = (0, import_react.useMemo)(() => {
845
897
  if (!user?._id) return false;
846
- return name !== (user.name || "") || lastname !== (user.lastname || "") || nickname !== (user.nickname || "") || country !== (user.country || "");
847
- }, [name, lastname, nickname, country, user]);
898
+ const origBirthDate = user.birthDate ? new Date(user.birthDate).toISOString().split("T")[0] : "";
899
+ const cfChanged = (domain.customFields ?? []).some(
900
+ (f) => (customFieldValues[f.key] ?? "") !== (user.customFields?.[f.key] ?? "")
901
+ );
902
+ return name !== (user.name || "") || lastname !== (user.lastname || "") || nickname !== (user.nickname || "") || country !== (user.country || "") || telPrefix !== (user.telPrefix || "") || telSuffix !== (user.telSuffix || "") || birthDate !== origBirthDate || cfChanged;
903
+ }, [
904
+ name,
905
+ lastname,
906
+ nickname,
907
+ country,
908
+ telPrefix,
909
+ telSuffix,
910
+ birthDate,
911
+ customFieldValues,
912
+ user,
913
+ domain.customFields
914
+ ]);
848
915
  const canSave = name.trim().length > 0 && hasChanges && !saving;
849
916
  const handleSave = (0, import_react.useCallback)(async () => {
850
917
  setSaving(true);
@@ -853,6 +920,14 @@ function DauthProfileModal({
853
920
  if (hasField("lastname")) fields.lastname = lastname;
854
921
  if (hasField("nickname")) fields.nickname = nickname;
855
922
  if (hasField("country")) fields.country = country;
923
+ if (hasField("tel_prefix"))
924
+ fields.telPrefix = telPrefix;
925
+ if (hasField("tel_suffix"))
926
+ fields.telSuffix = telSuffix;
927
+ if (hasField("birth_date") && birthDate)
928
+ fields.birthDate = birthDate;
929
+ if ((domain.customFields ?? []).length > 0)
930
+ fields.customFields = customFieldValues;
856
931
  const ok = await updateUser(fields);
857
932
  setSaving(false);
858
933
  if (ok) {
@@ -866,7 +941,19 @@ function DauthProfileModal({
866
941
  message: "Something went wrong. Please try again."
867
942
  });
868
943
  }
869
- }, [name, lastname, nickname, country, hasField, updateUser]);
944
+ }, [
945
+ name,
946
+ lastname,
947
+ nickname,
948
+ country,
949
+ telPrefix,
950
+ telSuffix,
951
+ birthDate,
952
+ customFieldValues,
953
+ hasField,
954
+ updateUser,
955
+ domain.customFields
956
+ ]);
870
957
  const handleDelete = (0, import_react.useCallback)(async () => {
871
958
  setDeleting(true);
872
959
  const ok = await deleteAccount();
@@ -922,19 +1009,21 @@ function DauthProfileModal({
922
1009
  [deletePasskeyCredential]
923
1010
  );
924
1011
  const handleAvatarClick = (0, import_react.useCallback)(() => {
925
- if (onAvatarUpload) {
926
- avatarInputRef.current?.click();
927
- }
928
- }, [onAvatarUpload]);
1012
+ avatarInputRef.current?.click();
1013
+ }, []);
929
1014
  const handleAvatarChange = (0, import_react.useCallback)(
930
1015
  async (e) => {
931
1016
  const file = e.target.files?.[0];
932
- if (!file || !onAvatarUpload) return;
1017
+ if (!file) return;
933
1018
  setUploadingAvatar(true);
934
1019
  try {
935
- const url = await onAvatarUpload(file);
936
- if (url) {
937
- await updateUser({ avatar: url });
1020
+ if (onAvatarUpload) {
1021
+ const url = await onAvatarUpload(file);
1022
+ if (url) {
1023
+ await updateUser({ avatar: url });
1024
+ }
1025
+ } else {
1026
+ await uploadAvatar(file);
938
1027
  }
939
1028
  } catch {
940
1029
  }
@@ -943,7 +1032,7 @@ function DauthProfileModal({
943
1032
  avatarInputRef.current.value = "";
944
1033
  }
945
1034
  },
946
- [onAvatarUpload, updateUser]
1035
+ [onAvatarUpload, updateUser, uploadAvatar]
947
1036
  );
948
1037
  const handleSignOut = (0, import_react.useCallback)(() => {
949
1038
  logout();
@@ -1085,7 +1174,7 @@ function DauthProfileModal({
1085
1174
  {
1086
1175
  style: {
1087
1176
  ...avatarCircle,
1088
- cursor: onAvatarUpload ? "pointer" : "default",
1177
+ cursor: "pointer",
1089
1178
  position: "relative"
1090
1179
  },
1091
1180
  onClick: handleAvatarClick,
@@ -1102,12 +1191,12 @@ function DauthProfileModal({
1102
1191
  }
1103
1192
  }
1104
1193
  ) : avatarInitial,
1105
- onAvatarUpload && !uploadingAvatar && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: avatarOverlay, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconCamera, {}) })
1194
+ !uploadingAvatar && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: avatarOverlay, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconCamera, {}) })
1106
1195
  ]
1107
1196
  }
1108
1197
  ),
1109
1198
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: emailText, children: user.email }),
1110
- onAvatarUpload && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1199
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1111
1200
  "input",
1112
1201
  {
1113
1202
  ref: avatarInputRef,
@@ -1232,6 +1321,127 @@ function DauthProfileModal({
1232
1321
  onBlur: inputBlurHandler
1233
1322
  }
1234
1323
  )
1324
+ ] }),
1325
+ (hasField("tel_prefix") || hasField("tel_suffix")) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: fieldGroup, children: [
1326
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: label, children: [
1327
+ "Phone",
1328
+ isRequired("tel_prefix") || isRequired("tel_suffix") ? " *" : ""
1329
+ ] }),
1330
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1331
+ "div",
1332
+ {
1333
+ style: {
1334
+ display: "flex",
1335
+ gap: 8
1336
+ },
1337
+ children: [
1338
+ hasField("tel_prefix") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1339
+ "input",
1340
+ {
1341
+ id: "dauth-tel-prefix",
1342
+ type: "text",
1343
+ value: telPrefix,
1344
+ onChange: (e) => setTelPrefix(e.target.value),
1345
+ placeholder: "+34",
1346
+ disabled: saving,
1347
+ style: {
1348
+ ...input,
1349
+ width: 80,
1350
+ flexShrink: 0
1351
+ },
1352
+ onFocus: inputFocusHandler,
1353
+ onBlur: inputBlurHandler,
1354
+ "aria-label": "Phone prefix"
1355
+ }
1356
+ ),
1357
+ hasField("tel_suffix") && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1358
+ "input",
1359
+ {
1360
+ id: "dauth-tel-suffix",
1361
+ type: "tel",
1362
+ value: telSuffix,
1363
+ onChange: (e) => setTelSuffix(e.target.value),
1364
+ placeholder: "612 345 678",
1365
+ disabled: saving,
1366
+ style: {
1367
+ ...input,
1368
+ flex: 1
1369
+ },
1370
+ onFocus: inputFocusHandler,
1371
+ onBlur: inputBlurHandler,
1372
+ "aria-label": "Phone number"
1373
+ }
1374
+ )
1375
+ ]
1376
+ }
1377
+ )
1378
+ ] }),
1379
+ hasField("birth_date") && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: fieldGroup, children: [
1380
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1381
+ "label",
1382
+ {
1383
+ htmlFor: "dauth-birthdate",
1384
+ style: label,
1385
+ children: [
1386
+ "Birth date",
1387
+ isRequired("birth_date") ? " *" : ""
1388
+ ]
1389
+ }
1390
+ ),
1391
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1392
+ "input",
1393
+ {
1394
+ id: "dauth-birthdate",
1395
+ type: "date",
1396
+ value: birthDate,
1397
+ onChange: (e) => setBirthDate(e.target.value),
1398
+ disabled: saving,
1399
+ style: input,
1400
+ onFocus: inputFocusHandler,
1401
+ onBlur: inputBlurHandler
1402
+ }
1403
+ )
1404
+ ] }),
1405
+ (domain.customFields ?? []).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1406
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("hr", { style: separator }),
1407
+ domain.customFields.map((cf) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1408
+ "div",
1409
+ {
1410
+ style: fieldGroup,
1411
+ children: [
1412
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1413
+ "label",
1414
+ {
1415
+ htmlFor: `dauth-cf-${cf.key}`,
1416
+ style: label,
1417
+ children: [
1418
+ cf.label,
1419
+ cf.required ? " *" : ""
1420
+ ]
1421
+ }
1422
+ ),
1423
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1424
+ "input",
1425
+ {
1426
+ id: `dauth-cf-${cf.key}`,
1427
+ type: "text",
1428
+ value: customFieldValues[cf.key] ?? "",
1429
+ onChange: (e) => setCustomFieldValues(
1430
+ (prev) => ({
1431
+ ...prev,
1432
+ [cf.key]: e.target.value
1433
+ })
1434
+ ),
1435
+ disabled: saving,
1436
+ style: input,
1437
+ onFocus: inputFocusHandler,
1438
+ onBlur: inputBlurHandler
1439
+ }
1440
+ )
1441
+ ]
1442
+ },
1443
+ cf.key
1444
+ ))
1235
1445
  ] })
1236
1446
  ] }),
1237
1447
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("hr", { style: separator }),
@@ -2056,6 +2266,10 @@ var DauthProvider = (props) => {
2056
2266
  (credentialId) => deletePasskeyCredentialAction(ctx, credentialId),
2057
2267
  [ctx]
2058
2268
  );
2269
+ const uploadAvatar = (0, import_react2.useCallback)(
2270
+ (file) => uploadAvatarAction(ctx, file),
2271
+ [ctx]
2272
+ );
2059
2273
  const memoProvider = (0, import_react2.useMemo)(
2060
2274
  () => ({
2061
2275
  ...dauthState,
@@ -2065,7 +2279,8 @@ var DauthProvider = (props) => {
2065
2279
  deleteAccount,
2066
2280
  getPasskeyCredentials,
2067
2281
  registerPasskey,
2068
- deletePasskeyCredential
2282
+ deletePasskeyCredential,
2283
+ uploadAvatar
2069
2284
  }),
2070
2285
  [
2071
2286
  dauthState,
@@ -2075,7 +2290,8 @@ var DauthProvider = (props) => {
2075
2290
  deleteAccount,
2076
2291
  getPasskeyCredentials,
2077
2292
  registerPasskey,
2078
- deletePasskeyCredential
2293
+ deletePasskeyCredential,
2294
+ uploadAvatar
2079
2295
  ]
2080
2296
  );
2081
2297
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DauthContext.Provider, { value: memoProvider, children });