dauth-context-react 6.3.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 +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +62 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +62 -16
- 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 +7 -0
- package/src/initialDauthState.ts +1 -0
- package/src/interfaces.ts +3 -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.4.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
|
@@ -135,6 +135,11 @@ export const DauthProvider: React.FC<IDauthProviderProps> = (
|
|
|
135
135
|
[ctx]
|
|
136
136
|
);
|
|
137
137
|
|
|
138
|
+
const uploadAvatar = useCallback(
|
|
139
|
+
(file: File) => action.uploadAvatarAction(ctx, file),
|
|
140
|
+
[ctx]
|
|
141
|
+
);
|
|
142
|
+
|
|
138
143
|
const memoProvider = useMemo(
|
|
139
144
|
() => ({
|
|
140
145
|
...dauthState,
|
|
@@ -145,6 +150,7 @@ export const DauthProvider: React.FC<IDauthProviderProps> = (
|
|
|
145
150
|
getPasskeyCredentials,
|
|
146
151
|
registerPasskey,
|
|
147
152
|
deletePasskeyCredential,
|
|
153
|
+
uploadAvatar,
|
|
148
154
|
}),
|
|
149
155
|
[
|
|
150
156
|
dauthState,
|
|
@@ -155,6 +161,7 @@ export const DauthProvider: React.FC<IDauthProviderProps> = (
|
|
|
155
161
|
getPasskeyCredentials,
|
|
156
162
|
registerPasskey,
|
|
157
163
|
deletePasskeyCredential,
|
|
164
|
+
uploadAvatar,
|
|
158
165
|
]
|
|
159
166
|
);
|
|
160
167
|
|
package/src/initialDauthState.ts
CHANGED
|
@@ -17,6 +17,7 @@ const initialDauthState: IDauthState = {
|
|
|
17
17
|
getPasskeyCredentials: () => Promise.resolve([]),
|
|
18
18
|
registerPasskey: () => Promise.resolve(null),
|
|
19
19
|
deletePasskeyCredential: () => Promise.resolve(false),
|
|
20
|
+
uploadAvatar: () => Promise.resolve(false),
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
export default initialDauthState;
|
package/src/interfaces.ts
CHANGED
|
@@ -89,14 +89,15 @@ export interface IDauthState {
|
|
|
89
89
|
deletePasskeyCredential: (
|
|
90
90
|
credentialId: string
|
|
91
91
|
) => Promise<boolean>;
|
|
92
|
+
uploadAvatar: (file: File) => Promise<boolean>;
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
export interface DauthProfileModalProps {
|
|
95
96
|
open: boolean;
|
|
96
97
|
onClose: () => void;
|
|
97
|
-
/** Optional
|
|
98
|
+
/** Optional override for avatar upload.
|
|
98
99
|
* Receives a File, should return the URL string.
|
|
99
|
-
* If not provided, the
|
|
100
|
+
* If not provided, uses the built-in upload via the auth proxy. */
|
|
100
101
|
onAvatarUpload?: (file: File) => Promise<string>;
|
|
101
102
|
}
|
|
102
103
|
|
|
@@ -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,
|