Skip to content

Commit 1d168cd

Browse files
committed
refactor: improve error handling & address PR feedback
1 parent 07fa234 commit 1d168cd

File tree

5 files changed

+95
-49
lines changed

5 files changed

+95
-49
lines changed

src/authz-module/data/api.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ export type PermissionsByRole = {
1313
permissions: string[];
1414
userCount: number;
1515
};
16-
export interface PutTeamMembersResponse {
16+
export interface PutAssignTeamMembersRoleResponse {
1717
completed: { user: string; status: string }[];
18-
errors: { user: string; error: string }[];
18+
errors: { userIdentifier: string; error: string }[];
1919
}
2020

21-
export interface AddTeamMembersRequest {
21+
export interface AssignTeamMembersRoleRequest {
2222
users: string[];
2323
role: string;
2424
scope: string;
@@ -30,10 +30,10 @@ export const getTeamMembers = async (object: string): Promise<TeamMember[]> => {
3030
return camelCaseObject(data.results);
3131
};
3232

33-
export const addTeamMembers = async (
34-
data: AddTeamMembersRequest,
35-
): Promise<PutTeamMembersResponse> => {
36-
const res = await getAuthenticatedHttpClient().put(getApiUrl('/api/authz/v1/roles/users'), data);
33+
export const assignTeamMembersRole = async (
34+
data: AssignTeamMembersRoleRequest,
35+
): Promise<PutAssignTeamMembersRoleResponse> => {
36+
const res = await getAuthenticatedHttpClient().put(getApiUrl('/api/authz/v1/roles/users/'), data);
3737
return camelCaseObject(res.data);
3838
};
3939

src/authz-module/data/hooks.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import {
44
import { appId } from '@src/constants';
55
import { LibraryMetadata, TeamMember } from '@src/types';
66
import {
7-
addTeamMembers, AddTeamMembersRequest, getLibrary, getPermissionsByRole, getTeamMembers, PermissionsByRole,
7+
assignTeamMembersRole,
8+
AssignTeamMembersRoleRequest,
9+
getLibrary, getPermissionsByRole, getTeamMembers, PermissionsByRole,
810
} from './api';
911

1012
const authzQueryKeys = {
@@ -68,17 +70,15 @@ export const useLibrary = (libraryId: string) => useSuspenseQuery<LibraryMetadat
6870
* It provides a mutation function to add users with specified roles to the library's team.
6971
*
7072
* @example
71-
* ```tsx
72-
* const { mutate: addTeamMember, isPending } = useAddTeamMember();
73-
* addTeamMember({ data: { libraryId: 'lib:123', users: ['jdoe'], role: 'editor' } });
74-
* ```
73+
* const { mutate: assignTeamMembersRole } = useAssignTeamMembersRole();
74+
* assignTeamMembersRole({ data: { libraryId: 'lib:123', users: ['jdoe'], role: 'editor' } });
7575
*/
76-
export const useAddTeamMember = () => {
76+
export const useAssignTeamMembersRole = () => {
7777
const queryClient = useQueryClient();
7878
return useMutation({
7979
mutationFn: async ({ data }: {
80-
data: AddTeamMembersRequest
81-
}) => addTeamMembers(data),
80+
data: AssignTeamMembersRoleRequest
81+
}) => assignTeamMembersRole(data),
8282
onSettled: (_data, _error, { data: { scope } }) => {
8383
queryClient.invalidateQueries({ queryKey: authzQueryKeys.teamMembers(scope) });
8484
},

src/authz-module/libraries-manager/components/AddNewTeamMemberModal.tsx

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import { FC } from 'react';
22
import { useIntl } from '@edx/frontend-platform/i18n';
33
import {
4-
ActionRow, Button, Form, ModalDialog,
4+
ActionRow, Form, Icon, ModalDialog,
55
Stack,
6+
StatefulButton,
67
} from '@openedx/paragon';
78
import { useLibraryAuthZ } from '@src/authz-module/libraries-manager/context';
9+
import { SpinnerSimple } from '@openedx/paragon/icons';
810
import messages from './messages';
911

1012
interface AddNewTeamMemberModalProps {
1113
isOpen: boolean;
14+
isError: boolean;
1215
isLoading: boolean;
1316
formValues: {
1417
users: string;
@@ -20,7 +23,7 @@ interface AddNewTeamMemberModalProps {
2023
}
2124

2225
const AddNewTeamMemberModal: FC<AddNewTeamMemberModalProps> = ({
23-
isOpen, isLoading, formValues, close, onSave, handleChangeForm,
26+
isOpen, isError, isLoading, formValues, close, onSave, handleChangeForm,
2427
}) => {
2528
const intl = useIntl();
2629
const { roles } = useLibraryAuthZ();
@@ -50,19 +53,23 @@ const AddNewTeamMemberModal: FC<AddNewTeamMemberModalProps> = ({
5053
<Form.Group controlId="users_list">
5154
<Form.Label>{intl.formatMessage(messages['libraries.authz.manage.add.member.users.label'])}</Form.Label>
5255
<Form.Control
56+
isInvalid={isError}
5357
as="textarea"
5458
name="users"
5559
rows="3"
5660
value={formValues.users}
5761
onChange={(e) => handleChangeForm(e)}
62+
style={{ color: isError && 'var(--pgn-color-form-feedback-invalid)' }}
5863
/>
5964
</Form.Group>
6065

6166
<Form.Group controlId="role_options">
6267
<Form.Label>{intl.formatMessage(messages['libraries.authz.manage.add.member.roles.label'])}</Form.Label>
6368
<Form.Control as="select" name="role" value={formValues.role} onChange={(e) => handleChangeForm(e)}>
64-
<option value="" disabled>Select a role</option>
65-
{roles.map(({ role }) => <option key={role}>{role}</option>)}
69+
<option value="" disabled>
70+
{intl.formatMessage(messages['libraries.authz.manage.add.member.select.default'])}
71+
</option>
72+
{roles.map((role) => <option key={role.role} value={role.role}>{role.name}</option>)}
6673
</Form.Control>
6774
</Form.Group>
6875
</Stack>
@@ -71,17 +78,22 @@ const AddNewTeamMemberModal: FC<AddNewTeamMemberModalProps> = ({
7178
<ModalDialog.Footer>
7279
<ActionRow>
7380
<ModalDialog.CloseButton variant="tertiary" disabled={isLoading}>
74-
{intl.formatMessage(messages['libraries.authz.manage.add.member.cancel.button'])}
81+
{intl.formatMessage(messages['libraries.authz.manage.cancel.button'])}
7582
</ModalDialog.CloseButton>
76-
<Button
83+
<StatefulButton
7784
className="px-4"
85+
labels={{
86+
default: intl.formatMessage(messages['libraries.authz.manage.save.button']),
87+
pending: intl.formatMessage(messages['libraries.authz.manage.saving.button']),
88+
}}
89+
icons={{
90+
pending: <Icon src={SpinnerSimple} />,
91+
}}
92+
state={isLoading ? 'pending' : 'default'}
7893
onClick={() => onSave()}
79-
disabled={!formValues.users || !formValues.role || isLoading}
80-
>
81-
{isLoading
82-
? intl.formatMessage(messages['libraries.authz.manage.add.member.saving.button'])
83-
: intl.formatMessage(messages['libraries.authz.manage.add.member.save.button'])}
84-
</Button>
94+
disabledStates={['pending']}
95+
disabled={!formValues.users || !formValues.role}
96+
/>
8597
</ActionRow>
8698
</ModalDialog.Footer>
8799
</ModalDialog>

src/authz-module/libraries-manager/components/AddNewTeamMemberTrigger.tsx

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { useIntl } from '@edx/frontend-platform/i18n';
33
import { Button, Toast, useToggle } from '@openedx/paragon';
44
import { Plus } from '@openedx/paragon/icons';
55

6-
import { useAddTeamMember } from '@src/authz-module/data/hooks';
6+
import { useAssignTeamMembersRole } from '@src/authz-module/data/hooks';
7+
import { PutAssignTeamMembersRoleResponse } from 'authz-module/data/api';
78
import AddNewTeamMemberModal from './AddNewTeamMemberModal';
89
import messages from './messages';
910

@@ -23,8 +24,9 @@ const AddNewTeamMemberTrigger: FC<AddNewTeamMemberTriggerProps> = ({
2324
const [isOpen, open, close] = useToggle(false);
2425
const [additionMessage, setAdditionMessage] = useState<string | null>(null);
2526
const [formValues, setFormValues] = useState(DEFAULT_FORM_VALUES);
27+
const [isError, setIsError] = useState(false);
2628

27-
const { mutate: addTeamMember, isPending: isAddingNewTeamMember } = useAddTeamMember();
29+
const { mutate: assignTeamMembersRole, isPending: isAssignTeamMembersRolePending } = useAssignTeamMembersRole();
2830

2931
const handleChangeForm = (e: React.ChangeEvent<HTMLTextAreaElement | HTMLSelectElement>) => {
3032
const { name, value } = e.target;
@@ -34,15 +36,40 @@ const AddNewTeamMemberTrigger: FC<AddNewTeamMemberTriggerProps> = ({
3436
}));
3537
};
3638

39+
const handleErrors = (errors: PutAssignTeamMembersRoleResponse['errors']) => {
40+
setIsError(false);
41+
const notFoundUsers = errors.filter(err => err.error === 'user_not_found').map(err => err.userIdentifier);
42+
43+
if (notFoundUsers.length) {
44+
setIsError(true);
45+
setFormValues((prev) => ({
46+
...prev,
47+
users: prev.users
48+
.split(',')
49+
.map(user => user.trim())
50+
.filter(user => notFoundUsers.includes(user))
51+
.join(', '),
52+
}));
53+
setAdditionMessage((prevMessage) => (
54+
`${prevMessage ? `${prevMessage} ` : ''}${intl.formatMessage(
55+
messages['libraries.authz.manage.add.member.failure'],
56+
{ count: notFoundUsers.length },
57+
)}`
58+
));
59+
}
60+
};
61+
3762
const handleAddTeamMember = () => {
3863
const data = {
3964
users: formValues.users.split(',').map(user => user.trim()),
4065
role: formValues.role,
4166
scope: libraryId,
4267
};
4368

44-
addTeamMember({ data }, {
69+
assignTeamMembersRole({ data }, {
4570
onSuccess: (successData) => {
71+
setAdditionMessage(null);
72+
4673
if (successData.completed.length) {
4774
setAdditionMessage(
4875
intl.formatMessage(
@@ -53,13 +80,9 @@ const AddNewTeamMemberTrigger: FC<AddNewTeamMemberTriggerProps> = ({
5380
}
5481

5582
if (successData.errors.length) {
56-
setAdditionMessage((prevMessage) => (
57-
`${prevMessage ? `${prevMessage} ` : ''}${intl.formatMessage(
58-
messages['libraries.authz.manage.add.member.failure'],
59-
{ count: successData.errors.length },
60-
)}`
61-
));
83+
handleErrors(successData.errors);
6284
} else {
85+
setIsError(false);
6386
close();
6487
setFormValues(DEFAULT_FORM_VALUES);
6588
}
@@ -80,9 +103,10 @@ const AddNewTeamMemberTrigger: FC<AddNewTeamMemberTriggerProps> = ({
80103
{isOpen && (
81104
<AddNewTeamMemberModal
82105
isOpen={isOpen}
106+
isError={isError}
83107
close={close}
84108
onSave={handleAddTeamMember}
85-
isLoading={isAddingNewTeamMember}
109+
isLoading={isAssignTeamMembersRolePending}
86110
formValues={formValues}
87111
handleChangeForm={handleChangeForm}
88112
/>

src/authz-module/libraries-manager/components/messages.ts

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,30 @@ const messages = defineMessages({
4646
defaultMessage: 'Roles',
4747
description: 'Label for the roles select field in the add new team member modal',
4848
},
49-
'libraries.authz.manage.add.member.cancel.button': {
50-
id: 'libraries.authz.manage.add.member.cancel.button',
51-
defaultMessage: 'Cancel',
52-
description: 'Label for the cancel button in the add new team member modal',
49+
'libraries.authz.manage.add.member.invalid.users': {
50+
id: 'libraries.authz.manage.add.member.invalid.users',
51+
defaultMessage: 'The following users could not be found:',
52+
description: 'Error message for invalid users in the add new team member modal',
5353
},
54-
'libraries.authz.manage.add.member.save.button': {
55-
id: 'libraries.authz.manage.add.member.save.button',
56-
defaultMessage: 'Save',
57-
description: 'Label for the save button in the add new team member modal',
54+
'libraries.authz.manage.add.member.select.default': {
55+
id: 'libraries.authz.manage.add.member.select.default',
56+
defaultMessage: 'Select a role',
57+
description: 'Default option for the roles select field in the add new team member modal',
58+
},
59+
'libraries.authz.manage.cancel.button': {
60+
id: 'libraries.authz.manage.cancel.button',
61+
defaultMessage: 'Cancel',
62+
description: 'Libraries AuthZ cancel button title',
5863
},
59-
'libraries.authz.manage.add.member.saving.button': {
60-
id: 'libraries.authz.manage.add.member.saving.button',
64+
'libraries.authz.manage.saving.button': {
65+
id: 'libraries.authz.manage.saving.button',
6166
defaultMessage: 'Saving...',
62-
description: 'Label for the save button in the add new team member modal when saving',
67+
description: 'Libraries AuthZ saving button title',
68+
},
69+
'libraries.authz.manage.save.button': {
70+
id: 'libraries.authz.manage.save.button',
71+
defaultMessage: 'Save',
72+
description: 'Libraries AuthZ save button title',
6373
},
6474
'libraries.authz.manage.add.member.description': {
6575
id: 'libraries.authz.manage.add.member.description',
@@ -73,7 +83,7 @@ const messages = defineMessages({
7383
},
7484
'libraries.authz.manage.add.member.failure': {
7585
id: 'libraries.authz.manage.add.member.failure',
76-
defaultMessage: 'We couldn\'t find a user for {count, plural, one {# email address or username.} other {# email addresses or usernames.}} Please check the email and try again, or invite them to join your organization first.',
86+
defaultMessage: 'We couldn\'t find a user for {count, plural, one {# email address or username.} other {# email addresses or usernames.}} Please check the values and try again, or invite them to join your organization first.',
7787
description: 'Error message when adding new team members',
7888
},
7989
});

0 commit comments

Comments
 (0)