RoleAndPermissionAPI.jsx
3 months ago
TrashUserRoleModel.jsx
3 months ago
UserDisplayModal.jsx
3 months ago
UserRoleTable.jsx
2 months ago
UserDisplayModal.jsx
321 lines
| 1 | import { AddIcon } from '@chakra-ui/icons'; |
| 2 | import { |
| 3 | Alert, |
| 4 | Button, |
| 5 | Flex, |
| 6 | FormControl, |
| 7 | FormLabel, |
| 8 | Modal, |
| 9 | ModalBody, |
| 10 | ModalCloseButton, |
| 11 | ModalContent, |
| 12 | ModalHeader, |
| 13 | ModalOverlay, |
| 14 | Stack, |
| 15 | Text, |
| 16 | useDisclosure, |
| 17 | useToast, |
| 18 | } from '@chakra-ui/react'; |
| 19 | import { useInfiniteQuery, useQueryClient } from '@tanstack/react-query'; |
| 20 | import { __ } from '@wordpress/i18n'; |
| 21 | import { Select } from 'chakra-react-select'; |
| 22 | import { debounce } from 'lodash'; |
| 23 | import { useEffect, useMemo, useRef, useState } from 'react'; |
| 24 | import { addManagerRole, getWPUsers } from './RoleAndPermissionAPI'; |
| 25 | |
| 26 | const selectChakraStyles = { |
| 27 | dropdownIndicator: (provided) => ({ ...provided, bg: 'transparent' }), |
| 28 | indicatorSeparator: (provided) => ({ ...provided, display: 'none' }), |
| 29 | option: (provided) => ({ ...provided, fontSize: '13px' }), |
| 30 | input: (base) => ({ |
| 31 | ...base, |
| 32 | border: 'none', |
| 33 | outline: 'none', |
| 34 | boxShadow: 'none', |
| 35 | padding: 0, |
| 36 | margin: 0, |
| 37 | background: 'transparent', |
| 38 | height: 'auto', |
| 39 | minHeight: 0, |
| 40 | width: 0, |
| 41 | }), |
| 42 | }; |
| 43 | |
| 44 | const UserDisplayModal = ({ wp_roles, context = '', value = {} }) => { |
| 45 | const { isOpen, onOpen, onClose } = useDisclosure(); |
| 46 | const queryClient = useQueryClient(); |
| 47 | const [selectedUser, setSelectedUser] = useState(null); |
| 48 | const [permissions, setPermissions] = useState([]); |
| 49 | const [errors, setErrors] = useState([]); |
| 50 | const [isSubmitting, setIsSubmitting] = useState(false); |
| 51 | const [searchTerm, setSearchTerm] = useState(''); |
| 52 | const toast = useToast(); |
| 53 | |
| 54 | const debouncedSetSearch = useRef( |
| 55 | debounce((val) => setSearchTerm(val), 400), |
| 56 | ).current; |
| 57 | |
| 58 | useEffect(() => () => debouncedSetSearch.cancel(), [debouncedSetSearch]); |
| 59 | |
| 60 | useEffect(() => { |
| 61 | if (!isOpen) return; |
| 62 | if (context === 'edit') { |
| 63 | setSelectedUser( |
| 64 | value.email ? { value: value.email, label: value.email } : null, |
| 65 | ); |
| 66 | setPermissions( |
| 67 | Array.isArray(value.permission) ? [...value.permission] : [], |
| 68 | ); |
| 69 | } else { |
| 70 | setSelectedUser(null); |
| 71 | setPermissions([]); |
| 72 | setSearchTerm(''); |
| 73 | } |
| 74 | setErrors([]); |
| 75 | }, [isOpen]); // eslint-disable-line react-hooks/exhaustive-deps |
| 76 | |
| 77 | const { |
| 78 | data: usersData, |
| 79 | fetchNextPage, |
| 80 | hasNextPage, |
| 81 | isFetchingNextPage, |
| 82 | isLoading: isUsersLoading, |
| 83 | } = useInfiniteQuery({ |
| 84 | queryKey: ['wp-users', searchTerm], |
| 85 | queryFn: ({ pageParam = 1 }) => |
| 86 | getWPUsers({ page: pageParam, search: searchTerm }), |
| 87 | getNextPageParam: (lastPage, allPages) => |
| 88 | lastPage.has_more ? allPages.length + 1 : undefined, |
| 89 | enabled: isOpen && context !== 'edit', |
| 90 | staleTime: 30 * 1000, |
| 91 | keepPreviousData: true, |
| 92 | }); |
| 93 | |
| 94 | const userOptions = useMemo( |
| 95 | () => usersData?.pages.flatMap((page) => page.users) ?? [], |
| 96 | [usersData], |
| 97 | ); |
| 98 | |
| 99 | const handleInputChange = (val) => { |
| 100 | debouncedSetSearch(val); |
| 101 | }; |
| 102 | |
| 103 | const allPermissionOptions = useMemo( |
| 104 | () => |
| 105 | Object.entries(wp_roles).map(([key, label]) => ({ |
| 106 | label, |
| 107 | value: key, |
| 108 | })), |
| 109 | [wp_roles], |
| 110 | ); |
| 111 | |
| 112 | const handleMultiplePermission = (selectedOptions) => { |
| 113 | setPermissions(selectedOptions ? selectedOptions.map((o) => o.value) : []); |
| 114 | }; |
| 115 | |
| 116 | const handleAddManager = () => { |
| 117 | const email = selectedUser?.value; |
| 118 | setIsSubmitting(true); |
| 119 | addManagerRole(email, permissions) |
| 120 | .then((res) => { |
| 121 | setErrors([]); |
| 122 | if (!res.success) { |
| 123 | const errorList = Object.entries(res.message).map( |
| 124 | ([key, message]) => ({ key, message }), |
| 125 | ); |
| 126 | setErrors(errorList); |
| 127 | } else { |
| 128 | onClose(); |
| 129 | toast({ |
| 130 | title: res.message, |
| 131 | status: 'success', |
| 132 | duration: 3000, |
| 133 | isClosable: true, |
| 134 | }); |
| 135 | |
| 136 | if (context === 'edit') { |
| 137 | queryClient.setQueriesData( |
| 138 | { queryKey: ['managers'] }, |
| 139 | (oldData) => { |
| 140 | if (!oldData?.managers) return oldData; |
| 141 | return { |
| 142 | ...oldData, |
| 143 | managers: oldData.managers.map((m) => |
| 144 | m.email === email |
| 145 | ? { ...m, permissions: [...permissions] } |
| 146 | : m, |
| 147 | ), |
| 148 | }; |
| 149 | }, |
| 150 | ); |
| 151 | } |
| 152 | |
| 153 | queryClient.invalidateQueries({ queryKey: ['managers'] }); |
| 154 | } |
| 155 | }) |
| 156 | .finally(() => setIsSubmitting(false)); |
| 157 | }; |
| 158 | |
| 159 | const addButtonStyles = { |
| 160 | width: '113px', |
| 161 | height: '41px', |
| 162 | backgroundColor: '#7545BB', |
| 163 | padding: '10px 16px', |
| 164 | gap: '6px', |
| 165 | fontWeight: '500', |
| 166 | lineHeight: '21px', |
| 167 | fontSize: '14px', |
| 168 | color: '#FFFFFF', |
| 169 | }; |
| 170 | |
| 171 | return ( |
| 172 | <> |
| 173 | {context === 'edit' ? ( |
| 174 | <Button |
| 175 | variant="link" |
| 176 | color="gray.500" |
| 177 | fontWeight="400" |
| 178 | fontSize="13px" |
| 179 | minW="auto" |
| 180 | height="auto" |
| 181 | padding={0} |
| 182 | _hover={{ color: 'primary.400', textDecoration: 'none' }} |
| 183 | onClick={onOpen} |
| 184 | > |
| 185 | {__('Edit', 'everest-forms')} |
| 186 | </Button> |
| 187 | ) : ( |
| 188 | <Button style={addButtonStyles} onClick={onOpen}> |
| 189 | <AddIcon |
| 190 | height="9.95px" |
| 191 | width="9.9px" |
| 192 | fontWeight="500" |
| 193 | color="#FFFFFF" |
| 194 | />{' '} |
| 195 | {__('Add User', 'everest-forms')} |
| 196 | </Button> |
| 197 | )} |
| 198 | |
| 199 | <Modal isOpen={isOpen} onClose={onClose} isCentered size="lg"> |
| 200 | <ModalOverlay /> |
| 201 | <ModalContent p={2}> |
| 202 | <ModalHeader> |
| 203 | {context === 'edit' |
| 204 | ? __('Edit User', 'everest-forms') |
| 205 | : __('Add User', 'everest-forms')} |
| 206 | <Text mt={1} fontSize="sm" fontWeight="400" color="gray.500"> |
| 207 | {__( |
| 208 | 'View and manage the list of current managers, their assigned roles, and permissions.', |
| 209 | 'everest-forms', |
| 210 | )} |
| 211 | </Text> |
| 212 | </ModalHeader> |
| 213 | <ModalCloseButton /> |
| 214 | |
| 215 | <ModalBody paddingTop="0"> |
| 216 | <FormControl> |
| 217 | <Stack gap="28px"> |
| 218 | <Stack> |
| 219 | <FormLabel display="flex" alignItems="center" fontSize="14px"> |
| 220 | {__('User Email', 'everest-forms')} |
| 221 | </FormLabel> |
| 222 | |
| 223 | <Select |
| 224 | placeholder={__('Select a user', 'everest-forms')} |
| 225 | options={context === 'edit' ? [] : userOptions} |
| 226 | value={selectedUser} |
| 227 | onChange={setSelectedUser} |
| 228 | onInputChange={ |
| 229 | context === 'edit' ? undefined : handleInputChange |
| 230 | } |
| 231 | filterOption={() => true} |
| 232 | isLoading={ |
| 233 | context !== 'edit' && |
| 234 | (isUsersLoading || isFetchingNextPage) |
| 235 | } |
| 236 | isDisabled={context === 'edit'} |
| 237 | isClearable={context !== 'edit'} |
| 238 | isSearchable={false} |
| 239 | menuIsOpen={context === 'edit' ? false : undefined} |
| 240 | onMenuScrollToBottom={() => { |
| 241 | if (hasNextPage && !isFetchingNextPage) { |
| 242 | fetchNextPage(); |
| 243 | } |
| 244 | }} |
| 245 | noOptionsMessage={() => |
| 246 | isUsersLoading |
| 247 | ? __('Loading…', 'everest-forms') |
| 248 | : __('No users found', 'everest-forms') |
| 249 | } |
| 250 | loadingMessage={() => __('Loading…', 'everest-forms')} |
| 251 | chakraStyles={selectChakraStyles} |
| 252 | /> |
| 253 | |
| 254 | {errors.map((error, index) => |
| 255 | error.key === 'user_email' ? ( |
| 256 | <Alert borderRadius="4px" key={index} status="error"> |
| 257 | {error.message} |
| 258 | </Alert> |
| 259 | ) : null, |
| 260 | )} |
| 261 | </Stack> |
| 262 | |
| 263 | <Stack> |
| 264 | <FormLabel display="flex" alignItems="center" fontSize="14px"> |
| 265 | {__('User Permission', 'everest-forms')} |
| 266 | </FormLabel> |
| 267 | |
| 268 | <Select |
| 269 | isMulti |
| 270 | size="md" |
| 271 | placeholder={__('Select user permission', 'everest-forms')} |
| 272 | options={allPermissionOptions} |
| 273 | value={permissions.map((val) => ({ |
| 274 | value: val, |
| 275 | label: wp_roles[val] || val, |
| 276 | }))} |
| 277 | onChange={handleMultiplePermission} |
| 278 | isClearable |
| 279 | isSearchable={false} |
| 280 | chakraStyles={selectChakraStyles} |
| 281 | /> |
| 282 | |
| 283 | {errors.map((error, index) => |
| 284 | error.key === 'assigned_permission' ? ( |
| 285 | <Alert borderRadius="4px" key={index} status="error"> |
| 286 | {error.message} |
| 287 | </Alert> |
| 288 | ) : null, |
| 289 | )} |
| 290 | </Stack> |
| 291 | </Stack> |
| 292 | |
| 293 | <Flex justifyContent="flex-end" mt="6" gap={3}> |
| 294 | <Button onClick={onClose} variant="outline"> |
| 295 | {__('Back', 'everest-forms')} |
| 296 | </Button> |
| 297 | <Button |
| 298 | color="#FFFFFF" |
| 299 | backgroundColor="#7545BB" |
| 300 | padding="10px 16px" |
| 301 | borderRadius="4px" |
| 302 | border="1px solid #7545BB" |
| 303 | width="94px" |
| 304 | height="39px" |
| 305 | _hover={{ backgroundColor: '#7545BB' }} |
| 306 | onClick={handleAddManager} |
| 307 | isLoading={isSubmitting} |
| 308 | > |
| 309 | {__('Confirm', 'everest-forms')} |
| 310 | </Button> |
| 311 | </Flex> |
| 312 | </FormControl> |
| 313 | </ModalBody> |
| 314 | </ModalContent> |
| 315 | </Modal> |
| 316 | </> |
| 317 | ); |
| 318 | }; |
| 319 | |
| 320 | export default UserDisplayModal; |
| 321 |