dauth-context-react 6.3.0 → 6.5.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 +6 -3
- package/dist/index.d.ts +6 -3
- package/dist/index.js +70 -19
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +70 -19
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/DauthProfileModal.tsx +21 -22
- package/src/api/dauth.api.ts +17 -0
- package/src/api/interfaces/dauth.api.responses.ts +9 -0
- package/src/index.tsx +13 -0
- package/src/initialDauthState.ts +2 -0
- package/src/interfaces.ts +6 -2
- package/src/reducer/dauth.actions.ts +31 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dauth-context-react",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.5.0",
|
|
4
4
|
"description": "React provider and hook for passwordless authentication via the Dauth service (BFF pattern with httpOnly cookies)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "David T. Pizarro Frick",
|
|
@@ -299,6 +299,7 @@ export function DauthProfileModal({
|
|
|
299
299
|
getPasskeyCredentials,
|
|
300
300
|
registerPasskey,
|
|
301
301
|
deletePasskeyCredential,
|
|
302
|
+
uploadAvatar,
|
|
302
303
|
} = useDauth();
|
|
303
304
|
const isDesktop = useMediaQuery('(min-width: 641px)');
|
|
304
305
|
const phase = useModalAnimation(open);
|
|
@@ -570,20 +571,22 @@ export function DauthProfileModal({
|
|
|
570
571
|
);
|
|
571
572
|
|
|
572
573
|
const handleAvatarClick = useCallback(() => {
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
}
|
|
576
|
-
}, [onAvatarUpload]);
|
|
574
|
+
avatarInputRef.current?.click();
|
|
575
|
+
}, []);
|
|
577
576
|
|
|
578
577
|
const handleAvatarChange = useCallback(
|
|
579
578
|
async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
580
579
|
const file = e.target.files?.[0];
|
|
581
|
-
if (!file
|
|
580
|
+
if (!file) return;
|
|
582
581
|
setUploadingAvatar(true);
|
|
583
582
|
try {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
583
|
+
if (onAvatarUpload) {
|
|
584
|
+
const url = await onAvatarUpload(file);
|
|
585
|
+
if (url) {
|
|
586
|
+
await updateUser({ avatar: url } as any);
|
|
587
|
+
}
|
|
588
|
+
} else {
|
|
589
|
+
await uploadAvatar(file);
|
|
587
590
|
}
|
|
588
591
|
} catch {
|
|
589
592
|
// Error handled by onError callback
|
|
@@ -593,7 +596,7 @@ export function DauthProfileModal({
|
|
|
593
596
|
avatarInputRef.current.value = '';
|
|
594
597
|
}
|
|
595
598
|
},
|
|
596
|
-
[onAvatarUpload, updateUser]
|
|
599
|
+
[onAvatarUpload, updateUser, uploadAvatar]
|
|
597
600
|
);
|
|
598
601
|
|
|
599
602
|
const handleSignOut = useCallback(() => {
|
|
@@ -776,9 +779,7 @@ export function DauthProfileModal({
|
|
|
776
779
|
<div
|
|
777
780
|
style={{
|
|
778
781
|
...avatarCircle,
|
|
779
|
-
cursor:
|
|
780
|
-
? 'pointer'
|
|
781
|
-
: 'default',
|
|
782
|
+
cursor: 'pointer',
|
|
782
783
|
position: 'relative' as const,
|
|
783
784
|
}}
|
|
784
785
|
onClick={handleAvatarClick}
|
|
@@ -798,22 +799,20 @@ export function DauthProfileModal({
|
|
|
798
799
|
) : (
|
|
799
800
|
avatarInitial
|
|
800
801
|
)}
|
|
801
|
-
{
|
|
802
|
+
{!uploadingAvatar && (
|
|
802
803
|
<div style={avatarOverlay}>
|
|
803
804
|
<IconCamera />
|
|
804
805
|
</div>
|
|
805
806
|
)}
|
|
806
807
|
</div>
|
|
807
808
|
<div style={emailText}>{user.email}</div>
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
/>
|
|
816
|
-
)}
|
|
809
|
+
<input
|
|
810
|
+
ref={avatarInputRef}
|
|
811
|
+
type="file"
|
|
812
|
+
accept="image/*"
|
|
813
|
+
style={{ display: 'none' }}
|
|
814
|
+
onChange={handleAvatarChange}
|
|
815
|
+
/>
|
|
817
816
|
</div>
|
|
818
817
|
|
|
819
818
|
{/* Status */}
|
package/src/api/dauth.api.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
IPasskeyRegistrationStartResponse,
|
|
9
9
|
IPasskeyRegistrationFinishResponse,
|
|
10
10
|
IDeletePasskeyResponse,
|
|
11
|
+
IUploadAvatarResponse,
|
|
11
12
|
} from './interfaces/dauth.api.responses';
|
|
12
13
|
|
|
13
14
|
function getCsrfToken(): string {
|
|
@@ -158,3 +159,19 @@ export async function deletePasskeyCredentialAPI(
|
|
|
158
159
|
const data = await response.json();
|
|
159
160
|
return { response, data };
|
|
160
161
|
}
|
|
162
|
+
|
|
163
|
+
export async function uploadAvatarAPI(
|
|
164
|
+
basePath: string,
|
|
165
|
+
file: File
|
|
166
|
+
): Promise<IUploadAvatarResponse> {
|
|
167
|
+
const formData = new FormData();
|
|
168
|
+
formData.append('avatar', file);
|
|
169
|
+
const response = await fetch(`${basePath}/avatar`, {
|
|
170
|
+
method: 'POST',
|
|
171
|
+
headers: { 'X-CSRF-Token': getCsrfToken() },
|
|
172
|
+
credentials: 'include',
|
|
173
|
+
body: formData,
|
|
174
|
+
});
|
|
175
|
+
const data = await response.json();
|
|
176
|
+
return { response, data };
|
|
177
|
+
}
|
package/src/index.tsx
CHANGED
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
IModalTheme,
|
|
22
22
|
IPasskeyCredential,
|
|
23
23
|
DauthProfileModalProps,
|
|
24
|
+
ThemeMode,
|
|
24
25
|
} from './interfaces';
|
|
25
26
|
|
|
26
27
|
export { DauthProfileModal } from './DauthProfileModal';
|
|
@@ -32,6 +33,7 @@ export type {
|
|
|
32
33
|
IModalTheme,
|
|
33
34
|
IPasskeyCredential,
|
|
34
35
|
DauthProfileModalProps,
|
|
36
|
+
ThemeMode,
|
|
35
37
|
};
|
|
36
38
|
|
|
37
39
|
const defaultOnError = (error: Error) => console.error(error);
|
|
@@ -92,10 +94,12 @@ export const DauthProvider: React.FC<IDauthProviderProps> = (
|
|
|
92
94
|
telPrefix,
|
|
93
95
|
telSuffix,
|
|
94
96
|
language,
|
|
97
|
+
theme,
|
|
95
98
|
avatar,
|
|
96
99
|
birthDate,
|
|
97
100
|
country,
|
|
98
101
|
metadata,
|
|
102
|
+
customFields,
|
|
99
103
|
} = fields;
|
|
100
104
|
const user = {
|
|
101
105
|
name,
|
|
@@ -104,10 +108,12 @@ export const DauthProvider: React.FC<IDauthProviderProps> = (
|
|
|
104
108
|
telPrefix,
|
|
105
109
|
telSuffix,
|
|
106
110
|
language,
|
|
111
|
+
theme,
|
|
107
112
|
avatar,
|
|
108
113
|
birthDate,
|
|
109
114
|
country,
|
|
110
115
|
metadata,
|
|
116
|
+
customFields,
|
|
111
117
|
} as Partial<IDauthUser>;
|
|
112
118
|
return action.updateUserAction(ctx, user);
|
|
113
119
|
},
|
|
@@ -135,6 +141,11 @@ export const DauthProvider: React.FC<IDauthProviderProps> = (
|
|
|
135
141
|
[ctx]
|
|
136
142
|
);
|
|
137
143
|
|
|
144
|
+
const uploadAvatar = useCallback(
|
|
145
|
+
(file: File) => action.uploadAvatarAction(ctx, file),
|
|
146
|
+
[ctx]
|
|
147
|
+
);
|
|
148
|
+
|
|
138
149
|
const memoProvider = useMemo(
|
|
139
150
|
() => ({
|
|
140
151
|
...dauthState,
|
|
@@ -145,6 +156,7 @@ export const DauthProvider: React.FC<IDauthProviderProps> = (
|
|
|
145
156
|
getPasskeyCredentials,
|
|
146
157
|
registerPasskey,
|
|
147
158
|
deletePasskeyCredential,
|
|
159
|
+
uploadAvatar,
|
|
148
160
|
}),
|
|
149
161
|
[
|
|
150
162
|
dauthState,
|
|
@@ -155,6 +167,7 @@ export const DauthProvider: React.FC<IDauthProviderProps> = (
|
|
|
155
167
|
getPasskeyCredentials,
|
|
156
168
|
registerPasskey,
|
|
157
169
|
deletePasskeyCredential,
|
|
170
|
+
uploadAvatar,
|
|
158
171
|
]
|
|
159
172
|
);
|
|
160
173
|
|
package/src/initialDauthState.ts
CHANGED
|
@@ -6,6 +6,7 @@ const initialDauthState: IDauthState = {
|
|
|
6
6
|
(typeof window !== 'undefined'
|
|
7
7
|
? window.document.documentElement.getAttribute('lang')
|
|
8
8
|
: null) || 'es',
|
|
9
|
+
theme: 'system',
|
|
9
10
|
} as IDauthUser,
|
|
10
11
|
domain: {} as IDauthDomainState,
|
|
11
12
|
isLoading: true,
|
|
@@ -17,6 +18,7 @@ const initialDauthState: IDauthState = {
|
|
|
17
18
|
getPasskeyCredentials: () => Promise.resolve([]),
|
|
18
19
|
registerPasskey: () => Promise.resolve(null),
|
|
19
20
|
deletePasskeyCredential: () => Promise.resolve(false),
|
|
21
|
+
uploadAvatar: () => Promise.resolve(false),
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
export default initialDauthState;
|
package/src/interfaces.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export type ThemeMode = 'dark' | 'light' | 'system';
|
|
2
|
+
|
|
1
3
|
export interface IDauthUser {
|
|
2
4
|
_id: string;
|
|
3
5
|
name: string;
|
|
@@ -6,6 +8,7 @@ export interface IDauthUser {
|
|
|
6
8
|
email: string;
|
|
7
9
|
isVerified: boolean;
|
|
8
10
|
language: string;
|
|
11
|
+
theme: ThemeMode;
|
|
9
12
|
avatar: {
|
|
10
13
|
id: string;
|
|
11
14
|
url: string;
|
|
@@ -89,14 +92,15 @@ export interface IDauthState {
|
|
|
89
92
|
deletePasskeyCredential: (
|
|
90
93
|
credentialId: string
|
|
91
94
|
) => Promise<boolean>;
|
|
95
|
+
uploadAvatar: (file: File) => Promise<boolean>;
|
|
92
96
|
}
|
|
93
97
|
|
|
94
98
|
export interface DauthProfileModalProps {
|
|
95
99
|
open: boolean;
|
|
96
100
|
onClose: () => void;
|
|
97
|
-
/** Optional
|
|
101
|
+
/** Optional override for avatar upload.
|
|
98
102
|
* Receives a File, should return the URL string.
|
|
99
|
-
* If not provided, the
|
|
103
|
+
* If not provided, uses the built-in upload via the auth proxy. */
|
|
100
104
|
onAvatarUpload?: (file: File) => Promise<string>;
|
|
101
105
|
}
|
|
102
106
|
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
startPasskeyRegistrationAPI,
|
|
9
9
|
finishPasskeyRegistrationAPI,
|
|
10
10
|
deletePasskeyCredentialAPI,
|
|
11
|
+
uploadAvatarAPI,
|
|
11
12
|
} from '../api/dauth.api';
|
|
12
13
|
import type { IPasskeyCredential } from '../api/interfaces/dauth.api.responses';
|
|
13
14
|
import { createPasskeyCredential } from '../webauthn';
|
|
@@ -239,6 +240,36 @@ export async function deletePasskeyCredentialAction(
|
|
|
239
240
|
}
|
|
240
241
|
}
|
|
241
242
|
|
|
243
|
+
export async function uploadAvatarAction(
|
|
244
|
+
ctx: ActionContext,
|
|
245
|
+
file: File
|
|
246
|
+
): Promise<boolean> {
|
|
247
|
+
const { dispatch, authProxyPath, onError } = ctx;
|
|
248
|
+
try {
|
|
249
|
+
const result = await uploadAvatarAPI(authProxyPath, file);
|
|
250
|
+
if (result.response.status === 200) {
|
|
251
|
+
dispatch({
|
|
252
|
+
type: DauthTypes.UPDATE_USER,
|
|
253
|
+
payload: result.data.user,
|
|
254
|
+
});
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
onError(
|
|
258
|
+
new Error(
|
|
259
|
+
'Avatar upload error: ' + result.data.message
|
|
260
|
+
)
|
|
261
|
+
);
|
|
262
|
+
return false;
|
|
263
|
+
} catch (error) {
|
|
264
|
+
onError(
|
|
265
|
+
error instanceof Error
|
|
266
|
+
? error
|
|
267
|
+
: new Error('Avatar upload error')
|
|
268
|
+
);
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
242
273
|
export const resetUser = (dispatch: React.Dispatch<any>) => {
|
|
243
274
|
return dispatch({
|
|
244
275
|
type: DauthTypes.LOGIN,
|