Skip to content

Commit 872521b

Browse files
committed
chore(react): implement updateMeProfile function and integrate with BaseUserProfile for user profile updates
1 parent 29a69a1 commit 872521b

File tree

6 files changed

+124
-15
lines changed

6 files changed

+124
-15
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/**
2+
* Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com).
3+
*
4+
* WSO2 LLC. licenses this file to you under the Apache License,
5+
* Version 2.0 (the "License"); you may not use this file except
6+
* in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing,
12+
* software distributed under the License is distributed on an
13+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
* KIND, either express or implied. See the License for the
15+
* specific language governing permissions and limitations
16+
* under the License.
17+
*/
18+
19+
import {User, AsgardeoAPIError, HttpInstance, AsgardeoSPAClient, HttpRequestConfig} from '@asgardeo/browser';
20+
21+
const httpClient: HttpInstance = AsgardeoSPAClient.getInstance().httpRequest.bind(AsgardeoSPAClient.getInstance());
22+
23+
/**
24+
* Updates the user profile information at the specified SCIM2 Me endpoint.
25+
*
26+
* @param url - The SCIM2 Me endpoint URL.
27+
* @param value - The value object to patch (SCIM2 PATCH value).
28+
* @param requestConfig - Additional request config if needed.
29+
* @returns A promise that resolves with the updated user profile information.
30+
* @example
31+
* ```typescript
32+
* await updateMeProfile({
33+
* url: "https://api.asgardeo.io/t/<ORG>/scim2/Me",
34+
* value: { "urn:scim:wso2:schema": { mobileNumbers: ["0777933830"] } }
35+
* });
36+
* ```
37+
*/
38+
const updateMeProfile = async ({
39+
url,
40+
payload,
41+
...requestConfig
42+
}: {url: string; payload: any} & Partial<Request>): Promise<User> => {
43+
try {
44+
new URL(url);
45+
} catch (error) {
46+
throw new AsgardeoAPIError(
47+
'Invalid endpoint URL provided',
48+
'updateMeProfile-ValidationError-001',
49+
'javascript',
50+
400,
51+
'Invalid Request',
52+
);
53+
}
54+
55+
const data = {
56+
Operations: [
57+
{
58+
op: 'replace',
59+
value: payload,
60+
},
61+
],
62+
schemas: ['urn:ietf:params:scim:api:messages:2.0:PatchOp'],
63+
};
64+
65+
const response: any = await httpClient({
66+
url,
67+
method: 'PATCH',
68+
headers: {
69+
'Content-Type': 'application/scim+json',
70+
Accept: 'application/json',
71+
},
72+
data,
73+
...requestConfig,
74+
} as HttpRequestConfig);
75+
76+
if (!response.data) {
77+
const errorText = await response.text();
78+
79+
throw new AsgardeoAPIError(
80+
`Failed to update user profile: ${errorText}`,
81+
'updateMeProfile-ResponseError-001',
82+
'javascript',
83+
response.status,
84+
response.statusText,
85+
);
86+
}
87+
88+
return response.data;
89+
};
90+
91+
export default updateMeProfile;

packages/react/src/components/presentation/UserProfile/BaseUserProfile.tsx

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import clsx from 'clsx';
2828
import getMappedUserProfileValue from '../../../utils/getMappedUserProfileValue';
2929

3030
interface Schema {
31+
id?: string;
3132
caseExact?: boolean;
3233
description?: string;
3334
displayName?: string;
@@ -62,6 +63,7 @@ export interface BaseUserProfileProps {
6263
onSubmit?: (data: any) => void;
6364
saveButtonText?: string;
6465
cancelButtonText?: string;
66+
onUpdate: (payload: any) => Promise<void>;
6567
}
6668

6769
const BaseUserProfile: FC<BaseUserProfileProps> = ({
@@ -76,6 +78,7 @@ const BaseUserProfile: FC<BaseUserProfileProps> = ({
7678
editable = true,
7779
onChange,
7880
onSubmit,
81+
onUpdate,
7982
saveButtonText = 'Save Changes',
8083
cancelButtonText = 'Cancel',
8184
}): ReactElement => {
@@ -107,14 +110,22 @@ const BaseUserProfile: FC<BaseUserProfileProps> = ({
107110
}, []);
108111

109112
const handleFieldSave = useCallback(
110-
(fieldName: string, value: any) => {
111-
setEditedUser(prev => ({
112-
...prev,
113-
[fieldName]: value,
114-
}));
115-
onChange?.(fieldName, value);
116-
toggleFieldEdit(fieldName);
117-
onSubmit?.({...editedUser, [fieldName]: value});
113+
(schema: Schema) => {
114+
let payload = {};
115+
116+
if (schema.multiValued) {
117+
payload = {
118+
[schema.id]: {
119+
[schema.name]: schema.value,
120+
},
121+
};
122+
} else {
123+
payload = {
124+
[schema.name]: schema.value,
125+
};
126+
}
127+
128+
onUpdate(payload);
118129
},
119130
[onChange, onSubmit, editedUser, toggleFieldEdit],
120131
);
@@ -303,10 +314,7 @@ const BaseUserProfile: FC<BaseUserProfileProps> = ({
303314
>
304315
{isFieldEditing ? (
305316
<>
306-
<button
307-
style={{...actionButtonStyle, ...saveButtonStyle}}
308-
onClick={() => handleFieldSave(schema.name!, editedUser[schema.name!])}
309-
>
317+
<button style={{...actionButtonStyle, ...saveButtonStyle}} onClick={() => handleFieldSave(schema)}>
310318
Save
311319
</button>
312320
<button

packages/react/src/components/presentation/UserProfile/UserProfile.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import {FC, ReactElement} from 'react';
2020
import useAsgardeo from '../../../hooks/useAsgardeo';
2121
import BaseUserProfile, {BaseUserProfileProps} from './BaseUserProfile';
22+
import updateMeProfile from 'packages/react/src/api/scim2/updateMeProfile';
2223

2324
/**
2425
* Props for the UserProfile component.
@@ -50,9 +51,13 @@ export type UserProfileProps = Omit<BaseUserProfileProps, 'user'>;
5051
* ```
5152
*/
5253
const UserProfile: FC<UserProfileProps> = ({...rest}: UserProfileProps): ReactElement => {
53-
const {user} = useAsgardeo();
54+
const {user, baseUrl} = useAsgardeo();
5455

55-
return <BaseUserProfile user={user} {...rest} />;
56+
const handleProfileUpdate = async (payload: any): Promise<void> => {
57+
updateMeProfile({url: `${baseUrl}/scim2/Me`, payload});
58+
};
59+
60+
return <BaseUserProfile user={user} onUpdate={handleProfileUpdate} {...rest} />;
5661
};
5762

5863
export default UserProfile;

packages/react/src/contexts/AsgardeoContext.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export type AsgardeoContextProps = {
5050
*/
5151
signUp: any;
5252
user: any;
53+
baseUrl: string;
5354
};
5455

5556
/**
@@ -61,7 +62,8 @@ const AsgardeoContext: Context<AsgardeoContextProps | null> = createContext<null
6162
signIn: null,
6263
signOut: null,
6364
signUp: null,
64-
user: null
65+
user: null,
66+
baseUrl: ''
6567
});
6668

6769
AsgardeoContext.displayName = 'AsgardeoContext';

packages/react/src/providers/AsgardeoProvider.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ const AsgardeoProvider: FC<PropsWithChildren<AsgardeoProviderProps>> = ({
170170
throw new Error('Sign up functionality not implemented yet');
171171
},
172172
user,
173+
baseUrl
173174
}}
174175
>
175176
<ThemeProvider theme={preferences?.theme?.overrides} defaultColorScheme={isDarkMode ? 'dark' : 'light'}>

packages/react/src/utils/getUserProfile.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ const getUserProfile = async ({baseUrl}): Promise<any> => {
7979
for (const subAttr of subAttributes) {
8080
if (complexValue[subAttr.name] !== undefined) {
8181
result.push({
82+
id: schemaId,
8283
...subAttr,
8384
value: complexValue[subAttr.name],
8485
});
@@ -89,6 +90,7 @@ const getUserProfile = async ({baseUrl}): Promise<any> => {
8990
// Only include if value exists
9091
if (value !== undefined) {
9192
result.push({
93+
id: schemaId,
9294
...attr,
9395
value,
9496
});

0 commit comments

Comments
 (0)