diff --git a/dashboard/src/components/EditUserModal.tsx b/dashboard/src/components/EditUserModal.tsx new file mode 100644 index 0000000..96494f9 --- /dev/null +++ b/dashboard/src/components/EditUserModal.tsx @@ -0,0 +1,250 @@ +import React from 'react'; +import { + Button, + Center, + Flex, + MenuItem, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + Stack, + useDisclosure, + Text, + useToast, +} from '@chakra-ui/react'; +import { useClient } from 'urql'; +import { FaSave } from 'react-icons/fa'; +import InputField from './InputField'; +import { + ArrayInputType, + DateInputType, + SelectInputType, + TextInputType, +} from '../constants'; +import { getObjectDiff } from '../utils'; +import { UpdateUser } from '../graphql/mutation'; + +const GenderTypes = { + Undisclosed: null, + Male: 'Male', + Female: 'Female', +}; + +interface userDataTypes { + id: string; + email: string; + given_name: string; + family_name: string; + middle_name: string; + nickname: string; + gender: string; + birthdate: string; + phone_number: string; + picture: string; + roles: [string] | []; +} + +const EditUserModal = ({ + user, + updateUserList, +}: { + user: userDataTypes; + updateUserList: Function; +}) => { + const client = useClient(); + const toast = useToast(); + const { isOpen, onOpen, onClose } = useDisclosure(); + const [userData, setUserData] = React.useState({ + id: '', + email: '', + given_name: '', + family_name: '', + middle_name: '', + nickname: '', + gender: '', + birthdate: '', + phone_number: '', + picture: '', + roles: [], + }); + React.useEffect(() => { + setUserData(user); + }, []); + const saveHandler = async () => { + const diff = getObjectDiff(user, userData); + const updatedUserData = diff.reduce( + (acc: any, property: string) => ({ + ...acc, + // @ts-ignore + [property]: userData[property], + }), + {} + ); + const res = await client + .mutation(UpdateUser, { params: updatedUserData }) + .toPromise(); + if (res.error) { + toast({ + title: 'User data update failed', + isClosable: true, + status: 'error', + position: 'bottom-right', + }); + } else if (res.data?._update_user?.id) { + toast({ + title: 'User data update successful', + isClosable: true, + status: 'success', + position: 'bottom-right', + }); + } + onClose(); + updateUserList(); + }; + return ( + <> + Edit User Details + + + + Edit User Details + + + + + + Given Name: + +
+ +
+
+ + + Middle Name: + +
+ +
+
+ + + Family Name: + +
+ +
+
+ + + Birth Date: + +
+ +
+
+ + + Nickname: + +
+ +
+
+ + + Gender: + +
+ +
+
+ + + Phone Number: + +
+ +
+
+ + + Picture: + +
+ +
+
+ + + Roles: + +
+ +
+
+
+
+ + + + +
+
+ + ); +}; + +export default EditUserModal; diff --git a/dashboard/src/components/InputField.tsx b/dashboard/src/components/InputField.tsx index 854fc83..e4a5bcf 100644 --- a/dashboard/src/components/InputField.tsx +++ b/dashboard/src/components/InputField.tsx @@ -29,13 +29,14 @@ import { TextInputType, TextAreaInputType, SwitchInputType, + DateInputType, } from '../constants'; import { copyTextToClipboard } from '../utils'; const InputField = ({ inputType, - envVariables, - setEnvVariables, + variables, + setVariables, fieldVisibility, setFieldVisibility, ...downshiftProps @@ -51,12 +52,14 @@ const InputField = ({ DEFAULT_ROLES: false, PROTECTED_ROLES: false, ALLOWED_ORIGINS: false, + roles: false, }); const [inputData, setInputData] = React.useState>({ ROLES: '', DEFAULT_ROLES: '', PROTECTED_ROLES: '', ALLOWED_ORIGINS: '', + roles: '', }); const updateInputHandler = ( type: string, @@ -65,20 +68,20 @@ const InputField = ({ ) => { if (operation === ArrayInputOperations.APPEND) { if (inputData[type] !== '') { - setEnvVariables({ - ...envVariables, - [type]: [...envVariables[type], inputData[type]], + setVariables({ + ...variables, + [type]: [...variables[type], inputData[type]], }); setInputData({ ...inputData, [type]: '' }); } setInputFieldVisibility({ ...inputFieldVisibility, [type]: false }); } if (operation === ArrayInputOperations.REMOVE) { - let updatedEnvVars = envVariables[type].filter( + let updatedEnvVars = variables[type].filter( (item: string) => item !== role ); - setEnvVariables({ - ...envVariables, + setVariables({ + ...variables, [type]: updatedEnvVars, }); } @@ -88,14 +91,14 @@ const InputField = ({ - setEnvVariables({ - ...envVariables, + setVariables({ + ...variables, [inputType]: event.target.value, }) } @@ -103,7 +106,7 @@ const InputField = ({ } cursor="pointer" - onClick={() => copyTextToClipboard(envVariables[inputType])} + onClick={() => copyTextToClipboard(variables[inputType])} /> ); @@ -113,14 +116,14 @@ const InputField = ({ - setEnvVariables({ - ...envVariables, + setVariables({ + ...variables, [inputType]: event.target.value, }) } @@ -163,7 +166,7 @@ const InputField = ({ w="25px" margin="0 1.5%" cursor="pointer" - onClick={() => copyTextToClipboard(envVariables[inputType])} + onClick={() => copyTextToClipboard(variables[inputType])} > @@ -183,7 +186,7 @@ const InputField = ({ overflow="scroll" padding="1%" > - {envVariables[inputType].map((role: string, index: number) => ( + {variables[inputType].map((role: string, index: number) => ( + {[variables[inputType]].map((value: string) => ( + + ))} + + ); + } + const { options, ...rest } = props; return ( - + setVariables({ ...variables, [inputType]: e.target.value }) + } + > + {Object.entries(options).map(([key, value]: any) => ( + ))} @@ -282,11 +304,11 @@ const InputField = ({ Off { - setEnvVariables({ - ...envVariables, - [inputType]: !envVariables[inputType], + setVariables({ + ...variables, + [inputType]: !variables[inputType], }); }} /> @@ -294,6 +316,20 @@ const InputField = ({ ); } + if (Object.values(DateInputType).includes(inputType)) { + return ( + + + setVariables({ ...variables, [inputType]: e.target.value }) + } + /> + + ); + } return null; }; diff --git a/dashboard/src/constants.ts b/dashboard/src/constants.ts index 58e0581..8f08765 100644 --- a/dashboard/src/constants.ts +++ b/dashboard/src/constants.ts @@ -16,6 +16,12 @@ export const TextInputType = { DATABASE_NAME: 'DATABASE_NAME', DATABASE_TYPE: 'DATABASE_TYPE', DATABASE_URL: 'DATABASE_URL', + GIVEN_NAME: 'given_name', + MIDDLE_NAME: 'middle_name', + FAMILY_NAME: 'family_name', + NICKNAME: 'nickname', + PHONE_NUMBER: 'phone_number', + PICTURE: 'picture', }; export const HiddenInputType = { @@ -33,10 +39,12 @@ export const ArrayInputType = { DEFAULT_ROLES: 'DEFAULT_ROLES', PROTECTED_ROLES: 'PROTECTED_ROLES', ALLOWED_ORIGINS: 'ALLOWED_ORIGINS', + USER_ROLES: 'roles', }; export const SelectInputType = { JWT_TYPE: 'JWT_TYPE', + GENDER: 'gender', }; export const TextAreaInputType = { @@ -50,6 +58,10 @@ export const SwitchInputType = { DISABLE_BASIC_AUTHENTICATION: 'DISABLE_BASIC_AUTHENTICATION', }; +export const DateInputType = { + BIRTHDATE: 'birthdate', +}; + export const ArrayInputOperations = { APPEND: 'APPEND', REMOVE: 'REMOVE', diff --git a/dashboard/src/pages/Environment.tsx b/dashboard/src/pages/Environment.tsx index 682a529..9aa1986 100644 --- a/dashboard/src/pages/Environment.tsx +++ b/dashboard/src/pages/Environment.tsx @@ -249,16 +249,16 @@ export default function Environment() {
@@ -346,8 +346,8 @@ export default function Environment() {
@@ -358,8 +358,8 @@ export default function Environment() {
@@ -378,8 +378,8 @@ export default function Environment() {
@@ -431,8 +431,8 @@ export default function Environment() {
@@ -449,8 +449,8 @@ export default function Environment() {
@@ -461,8 +461,8 @@ export default function Environment() {
@@ -473,8 +473,8 @@ export default function Environment() {
@@ -485,8 +485,8 @@ export default function Environment() {
@@ -517,8 +517,8 @@ export default function Environment() {
@@ -535,8 +535,8 @@ export default function Environment() {
@@ -547,8 +547,8 @@ export default function Environment() {
@@ -562,8 +562,8 @@ export default function Environment() {
@@ -594,8 +594,8 @@ export default function Environment() { @@ -606,8 +606,8 @@ export default function Environment() { @@ -618,8 +618,8 @@ export default function Environment() { @@ -647,8 +647,8 @@ export default function Environment() {
@@ -660,8 +660,8 @@ export default function Environment() {
@@ -673,8 +673,8 @@ export default function Environment() {
@@ -743,8 +743,8 @@ export default function Environment() {
{ export default function Users() { const client = useClient(); + const toast = useToast(); const [paginationProps, setPaginationProps] = React.useState({ limit: 5, @@ -94,7 +97,7 @@ export default function Users() { maxPages: 1, }); const [userList, setUserList] = React.useState([]); - const updateData = async () => { + const updateUserList = async () => { const { data } = await client .query(UserDetailsQuery, { params: { @@ -110,15 +113,13 @@ export default function Users() { const maxPages = getMaxPages(pagination); setPaginationProps({ ...paginationProps, ...pagination, maxPages }); setUserList(users); - console.log('users ==>> ', users); - console.log('pagination ==>> ', { ...pagination, maxPages }); } }; React.useEffect(() => { - updateData(); + updateUserList(); }, []); React.useEffect(() => { - updateData(); + updateUserList(); }, [paginationProps.page, paginationProps.limit]); const paginationHandler = (value: Record) => { @@ -127,7 +128,7 @@ export default function Users() { const userVerificationHandler = async (user: userDataTypes) => { const { id, email } = user; - await client + const res = await client .mutation(UpdateUser, { params: { id, @@ -136,9 +137,23 @@ export default function Users() { }, }) .toPromise(); - updateData(); + if (res.error) { + toast({ + title: 'User verification failed', + isClosable: true, + status: 'error', + position: 'bottom-right', + }); + } else if (res.data?._update_user?.id) { + toast({ + title: 'User verification successful', + isClosable: true, + status: 'success', + position: 'bottom-right', + }); + } + updateUserList(); }; - return ( @@ -157,44 +172,52 @@ export default function Users() { - {userList.map((user: userDataTypes) => ( - - {user.email} - {dayjs(user.created_at).format('MMM DD, YYYY')} - - - {user.email_verified.toString()} - - - - - - - - Menu - - - - - - Update User Details - {!user.email_verified && ( - userVerificationHandler(user)} + {userList.map((user: userDataTypes) => { + const { email_verified, created_at, ...rest }: any = user; + return ( + + {user.email} + {dayjs(user.created_at).format('MMM DD, YYYY')} + + + {user.email_verified.toString()} + + + + + + - Verify User - - )} - - - - - ))} + + Menu + + + + + + + {!user.email_verified && ( + userVerificationHandler(user)} + > + Verify User + + )} + + + + + ); + })} {paginationProps.maxPages > 1 && ( diff --git a/dashboard/src/utils/index.ts b/dashboard/src/utils/index.ts index 9383d6f..3e398de 100644 --- a/dashboard/src/utils/index.ts +++ b/dashboard/src/utils/index.ts @@ -48,7 +48,11 @@ export const getObjectDiff = (obj1: any, obj2: any) => { const diff = Object.keys(obj1).reduce((result, key) => { if (!obj2.hasOwnProperty(key)) { result.push(key); - } else if (_.isEqual(obj1[key], obj2[key])) { + } else if ( + _.isEqual(obj1[key], obj2[key]) || + (obj1[key] === null && obj2[key] === '') || + (obj1[key] === [] && obj2[key] === null) + ) { const resultKeyIndex = result.indexOf(key); result.splice(resultKeyIndex, 1); }