Merge branch 'main' of https://github.com/authorizerdev/authorizer
This commit is contained in:
commit
21fef67c7d
71
dashboard/package-lock.json
generated
71
dashboard/package-lock.json
generated
|
@ -22,6 +22,7 @@
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
"react-dropzone": "^12.0.4",
|
||||||
"react-icons": "^4.3.1",
|
"react-icons": "^4.3.1",
|
||||||
"react-router-dom": "^6.2.1",
|
"react-router-dom": "^6.2.1",
|
||||||
"typescript": "^4.5.4",
|
"typescript": "^4.5.4",
|
||||||
|
@ -1251,6 +1252,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/attr-accept": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
|
||||||
|
"integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/babel-plugin-macros": {
|
"node_modules/babel-plugin-macros": {
|
||||||
"version": "2.8.0",
|
"version": "2.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz",
|
||||||
|
@ -1631,6 +1640,17 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/file-selector": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-iACCiXeMYOvZqlF1kTiYINzgepRBymz1wwjiuup9u9nayhb6g4fSwiyJ/6adli+EPwrWtpgQAh2PoS7HukEGEg==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/find-root": {
|
"node_modules/find-root": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
|
||||||
|
@ -1914,9 +1934,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prop-types": {
|
"node_modules/prop-types": {
|
||||||
"version": "15.8.0",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
"integrity": "sha512-fDGekdaHh65eI3lMi5OnErU6a8Ighg2KjcjQxO7m8VHyWjcPyj5kiOgV1LQDOOOgVy3+5FgjXvdSSX7B8/5/4g==",
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.4.0",
|
"loose-envify": "^1.4.0",
|
||||||
"object-assign": "^4.1.1",
|
"object-assign": "^4.1.1",
|
||||||
|
@ -1959,6 +1979,22 @@
|
||||||
"react": "17.0.2"
|
"react": "17.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-dropzone": {
|
||||||
|
"version": "12.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-12.0.4.tgz",
|
||||||
|
"integrity": "sha512-fcqHEYe1MzAghU6/Hz86lHDlBNsA+lO48nAcm7/wA+kIzwS6uuJbUG33tBZjksj7GAZ1iUQ6NHwjUURPmSGang==",
|
||||||
|
"dependencies": {
|
||||||
|
"attr-accept": "^2.2.2",
|
||||||
|
"file-selector": "^0.4.0",
|
||||||
|
"prop-types": "^15.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.13"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-fast-compare": {
|
"node_modules/react-fast-compare": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
|
||||||
|
@ -3226,6 +3262,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"attr-accept": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
|
||||||
|
"integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg=="
|
||||||
|
},
|
||||||
"babel-plugin-macros": {
|
"babel-plugin-macros": {
|
||||||
"version": "2.8.0",
|
"version": "2.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz",
|
||||||
|
@ -3478,6 +3519,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
|
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
|
||||||
},
|
},
|
||||||
|
"file-selector": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-iACCiXeMYOvZqlF1kTiYINzgepRBymz1wwjiuup9u9nayhb6g4fSwiyJ/6adli+EPwrWtpgQAh2PoS7HukEGEg==",
|
||||||
|
"requires": {
|
||||||
|
"tslib": "^2.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"find-root": {
|
"find-root": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
|
||||||
|
@ -3707,9 +3756,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"prop-types": {
|
"prop-types": {
|
||||||
"version": "15.8.0",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
"integrity": "sha512-fDGekdaHh65eI3lMi5OnErU6a8Ighg2KjcjQxO7m8VHyWjcPyj5kiOgV1LQDOOOgVy3+5FgjXvdSSX7B8/5/4g==",
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"loose-envify": "^1.4.0",
|
"loose-envify": "^1.4.0",
|
||||||
"object-assign": "^4.1.1",
|
"object-assign": "^4.1.1",
|
||||||
|
@ -3743,6 +3792,16 @@
|
||||||
"scheduler": "^0.20.2"
|
"scheduler": "^0.20.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-dropzone": {
|
||||||
|
"version": "12.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-12.0.4.tgz",
|
||||||
|
"integrity": "sha512-fcqHEYe1MzAghU6/Hz86lHDlBNsA+lO48nAcm7/wA+kIzwS6uuJbUG33tBZjksj7GAZ1iUQ6NHwjUURPmSGang==",
|
||||||
|
"requires": {
|
||||||
|
"attr-accept": "^2.2.2",
|
||||||
|
"file-selector": "^0.4.0",
|
||||||
|
"prop-types": "^15.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-fast-compare": {
|
"react-fast-compare": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
"react-dropzone": "^12.0.4",
|
||||||
"react-icons": "^4.3.1",
|
"react-icons": "^4.3.1",
|
||||||
"react-router-dom": "^6.2.1",
|
"react-router-dom": "^6.2.1",
|
||||||
"typescript": "^4.5.4",
|
"typescript": "^4.5.4",
|
||||||
|
|
372
dashboard/src/components/InviteMembersModal.tsx
Normal file
372
dashboard/src/components/InviteMembersModal.tsx
Normal file
|
@ -0,0 +1,372 @@
|
||||||
|
import React, { useState, useCallback, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Flex,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
ModalOverlay,
|
||||||
|
useDisclosure,
|
||||||
|
useToast,
|
||||||
|
Tabs,
|
||||||
|
TabList,
|
||||||
|
Tab,
|
||||||
|
TabPanels,
|
||||||
|
TabPanel,
|
||||||
|
InputGroup,
|
||||||
|
Input,
|
||||||
|
InputRightElement,
|
||||||
|
Text,
|
||||||
|
Link,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { useClient } from 'urql';
|
||||||
|
import { FaUserPlus, FaMinusCircle, FaPlus, FaUpload } from 'react-icons/fa';
|
||||||
|
import { useDropzone } from 'react-dropzone';
|
||||||
|
import { escape } from 'lodash';
|
||||||
|
import { validateEmail, validateURI } from '../utils';
|
||||||
|
import { InviteMembers } from '../graphql/mutation';
|
||||||
|
import { ArrayInputOperations, csvDemoData } from '../constants';
|
||||||
|
import parseCSV from '../utils/parseCSV';
|
||||||
|
|
||||||
|
interface stateDataTypes {
|
||||||
|
value: string;
|
||||||
|
isInvalid: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface requestParamTypes {
|
||||||
|
emails: string[];
|
||||||
|
redirect_uri?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initData: stateDataTypes = {
|
||||||
|
value: '',
|
||||||
|
isInvalid: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const InviteMembersModal = ({
|
||||||
|
updateUserList,
|
||||||
|
disabled = true,
|
||||||
|
}: {
|
||||||
|
updateUserList: Function;
|
||||||
|
disabled: boolean;
|
||||||
|
}) => {
|
||||||
|
const client = useClient();
|
||||||
|
const toast = useToast();
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const [tabIndex, setTabIndex] = useState<number>(0);
|
||||||
|
const [redirectURI, setRedirectURI] = useState<stateDataTypes>({
|
||||||
|
...initData,
|
||||||
|
});
|
||||||
|
const [emails, setEmails] = useState<stateDataTypes[]>([{ ...initData }]);
|
||||||
|
const [disableSendButton, setDisableSendButton] = useState<boolean>(false);
|
||||||
|
const [loading, setLoading] = React.useState<boolean>(false);
|
||||||
|
useEffect(() => {
|
||||||
|
if (redirectURI.isInvalid) {
|
||||||
|
setDisableSendButton(true);
|
||||||
|
} else if (emails.some((emailData) => emailData.isInvalid)) {
|
||||||
|
setDisableSendButton(true);
|
||||||
|
} else {
|
||||||
|
setDisableSendButton(false);
|
||||||
|
}
|
||||||
|
}, [redirectURI, emails]);
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
setRedirectURI({ ...initData });
|
||||||
|
setEmails([{ ...initData }]);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
const sendInviteHandler = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const emailList = emails
|
||||||
|
.filter((emailData) => !emailData.isInvalid)
|
||||||
|
.map((emailData) => emailData.value);
|
||||||
|
const params: requestParamTypes = {
|
||||||
|
emails: emailList,
|
||||||
|
};
|
||||||
|
if (redirectURI.value !== '' && !redirectURI.isInvalid) {
|
||||||
|
params.redirect_uri = redirectURI.value;
|
||||||
|
}
|
||||||
|
if (emailList.length > 0) {
|
||||||
|
const res = await client
|
||||||
|
.mutation(InviteMembers, {
|
||||||
|
params,
|
||||||
|
})
|
||||||
|
.toPromise();
|
||||||
|
if (res.error) {
|
||||||
|
throw new Error('Internal server error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toast({
|
||||||
|
title: 'Invites sent successfully!',
|
||||||
|
isClosable: true,
|
||||||
|
status: 'success',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
setLoading(false);
|
||||||
|
updateUserList();
|
||||||
|
} else {
|
||||||
|
throw new Error('Please add emails');
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
toast({
|
||||||
|
title: error?.message || 'Error occurred, try again!',
|
||||||
|
isClosable: true,
|
||||||
|
status: 'error',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
closeModalHandler();
|
||||||
|
};
|
||||||
|
const updateEmailListHandler = (operation: string, index: number = 0) => {
|
||||||
|
switch (operation) {
|
||||||
|
case ArrayInputOperations.APPEND:
|
||||||
|
setEmails([...emails, { ...initData }]);
|
||||||
|
break;
|
||||||
|
case ArrayInputOperations.REMOVE:
|
||||||
|
const updatedEmailList = [...emails];
|
||||||
|
updatedEmailList.splice(index, 1);
|
||||||
|
setEmails(updatedEmailList);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const inputChangeHandler = (value: string, index: number) => {
|
||||||
|
const updatedEmailList = [...emails];
|
||||||
|
updatedEmailList[index].value = value;
|
||||||
|
updatedEmailList[index].isInvalid = !validateEmail(value);
|
||||||
|
setEmails(updatedEmailList);
|
||||||
|
};
|
||||||
|
const changeTabsHandler = (index: number) => {
|
||||||
|
setTabIndex(index);
|
||||||
|
};
|
||||||
|
const onDrop = useCallback(async (acceptedFiles) => {
|
||||||
|
const result = await parseCSV(acceptedFiles[0], ',');
|
||||||
|
setEmails(result);
|
||||||
|
changeTabsHandler(0);
|
||||||
|
}, []);
|
||||||
|
const setRedirectURIHandler = (value: string) => {
|
||||||
|
const updatedRedirectURI: stateDataTypes = {
|
||||||
|
value: '',
|
||||||
|
isInvalid: false,
|
||||||
|
};
|
||||||
|
updatedRedirectURI.value = value;
|
||||||
|
updatedRedirectURI.isInvalid = !validateURI(value);
|
||||||
|
setRedirectURI(updatedRedirectURI);
|
||||||
|
};
|
||||||
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||||
|
onDrop,
|
||||||
|
accept: 'text/csv',
|
||||||
|
});
|
||||||
|
const closeModalHandler = () => {
|
||||||
|
setRedirectURI({
|
||||||
|
value: '',
|
||||||
|
isInvalid: false,
|
||||||
|
});
|
||||||
|
setEmails([
|
||||||
|
{
|
||||||
|
value: '',
|
||||||
|
isInvalid: false,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
leftIcon={<FaUserPlus />}
|
||||||
|
colorScheme="blue"
|
||||||
|
variant="solid"
|
||||||
|
onClick={onOpen}
|
||||||
|
isDisabled={disabled}
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
<Center h="100%">Invite Members</Center>
|
||||||
|
</Button>
|
||||||
|
<Modal isOpen={isOpen} onClose={closeModalHandler} size="xl">
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>Invite Members</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody>
|
||||||
|
<Tabs
|
||||||
|
isFitted
|
||||||
|
variant="enclosed"
|
||||||
|
index={tabIndex}
|
||||||
|
onChange={changeTabsHandler}
|
||||||
|
>
|
||||||
|
<TabList>
|
||||||
|
<Tab>Enter emails</Tab>
|
||||||
|
<Tab>Upload CSV</Tab>
|
||||||
|
</TabList>
|
||||||
|
<TabPanels
|
||||||
|
border="1px"
|
||||||
|
borderTop="0"
|
||||||
|
borderBottomRadius="5px"
|
||||||
|
borderColor="inherit"
|
||||||
|
>
|
||||||
|
<TabPanel>
|
||||||
|
<Flex flexDirection="column">
|
||||||
|
<Flex
|
||||||
|
width="100%"
|
||||||
|
justifyContent="start"
|
||||||
|
alignItems="center"
|
||||||
|
marginBottom="2%"
|
||||||
|
>
|
||||||
|
<Flex marginLeft="2.5%">Redirect URI</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
width="100%"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
marginBottom="2%"
|
||||||
|
>
|
||||||
|
<InputGroup size="md" marginBottom="2.5%">
|
||||||
|
<Input
|
||||||
|
pr="4.5rem"
|
||||||
|
type="text"
|
||||||
|
placeholder="https://domain.com/sign-up"
|
||||||
|
value={redirectURI.value}
|
||||||
|
isInvalid={redirectURI.isInvalid}
|
||||||
|
onChange={(e) =>
|
||||||
|
setRedirectURIHandler(e.currentTarget.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
width="100%"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
marginBottom="2%"
|
||||||
|
>
|
||||||
|
<Flex marginLeft="2.5%">Emails</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Button
|
||||||
|
leftIcon={<FaPlus />}
|
||||||
|
colorScheme="blue"
|
||||||
|
h="1.75rem"
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() =>
|
||||||
|
updateEmailListHandler(ArrayInputOperations.APPEND)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Add more emails
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Flex flexDirection="column" maxH={250} overflowY="scroll">
|
||||||
|
{emails.map((emailData, index) => (
|
||||||
|
<Flex
|
||||||
|
key={`email-data-${index}`}
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<InputGroup size="md" marginBottom="2.5%">
|
||||||
|
<Input
|
||||||
|
pr="4.5rem"
|
||||||
|
type="text"
|
||||||
|
placeholder="name@domain.com"
|
||||||
|
value={emailData.value}
|
||||||
|
isInvalid={emailData.isInvalid}
|
||||||
|
onChange={(e) =>
|
||||||
|
inputChangeHandler(e.currentTarget.value, index)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<InputRightElement width="3rem">
|
||||||
|
<Button
|
||||||
|
h="1.75rem"
|
||||||
|
size="sm"
|
||||||
|
colorScheme="blackAlpha"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() =>
|
||||||
|
updateEmailListHandler(
|
||||||
|
ArrayInputOperations.REMOVE,
|
||||||
|
index
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FaMinusCircle />
|
||||||
|
</Button>
|
||||||
|
</InputRightElement>
|
||||||
|
</InputGroup>
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel>
|
||||||
|
<Flex
|
||||||
|
justify="center"
|
||||||
|
align="center"
|
||||||
|
textAlign="center"
|
||||||
|
bg="#f0f0f0"
|
||||||
|
h={230}
|
||||||
|
p={50}
|
||||||
|
m={2}
|
||||||
|
borderRadius={5}
|
||||||
|
{...getRootProps()}
|
||||||
|
>
|
||||||
|
<input {...getInputProps()} />
|
||||||
|
{isDragActive ? (
|
||||||
|
<Text>Drop the files here...</Text>
|
||||||
|
) : (
|
||||||
|
<Flex
|
||||||
|
flexDirection="column"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Center boxSize="20" color="blackAlpha.500">
|
||||||
|
<FaUpload fontSize="40" />
|
||||||
|
</Center>
|
||||||
|
<Text>
|
||||||
|
Drag 'n' drop the csv file here, or click to select.
|
||||||
|
</Text>
|
||||||
|
<Text size="xs">
|
||||||
|
Download{' '}
|
||||||
|
<Link
|
||||||
|
href={`data:text/csv;charset=utf-8,${escape(
|
||||||
|
csvDemoData
|
||||||
|
)}`}
|
||||||
|
download="sample.csv"
|
||||||
|
color="blue.600"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
{' '}
|
||||||
|
sample.csv
|
||||||
|
</Link>{' '}
|
||||||
|
and modify it.{' '}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</TabPanel>
|
||||||
|
</TabPanels>
|
||||||
|
</Tabs>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
colorScheme="blue"
|
||||||
|
variant="solid"
|
||||||
|
onClick={sendInviteHandler}
|
||||||
|
isDisabled={disableSendButton || loading}
|
||||||
|
>
|
||||||
|
<Center h="100%" pt="5%">
|
||||||
|
Send
|
||||||
|
</Center>
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InviteMembersModal;
|
|
@ -88,3 +88,9 @@ export const ECDSAEncryptionType = {
|
||||||
ES384: 'ES384',
|
ES384: 'ES384',
|
||||||
ES512: 'ES512',
|
ES512: 'ES512',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const csvDemoData = `lakhan.demo@contentment.org
|
||||||
|
john@gmail.com
|
||||||
|
anik@contentment.org
|
||||||
|
harry@potter.com
|
||||||
|
anikgh89@gmail.com`;
|
||||||
|
|
|
@ -45,3 +45,11 @@ export const DeleteUser = `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const InviteMembers = `
|
||||||
|
mutation inviteMembers($params: InviteMemberInput!) {
|
||||||
|
_invite_members(params: $params) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
|
@ -84,3 +84,11 @@ export const UserDetailsQuery = `
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const EmailVerificationQuery = `
|
||||||
|
query {
|
||||||
|
_env{
|
||||||
|
DISABLE_EMAIL_VERIFICATION
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
|
@ -38,10 +38,11 @@ import {
|
||||||
FaExclamationCircle,
|
FaExclamationCircle,
|
||||||
FaAngleDown,
|
FaAngleDown,
|
||||||
} from 'react-icons/fa';
|
} from 'react-icons/fa';
|
||||||
import { UserDetailsQuery } from '../graphql/queries';
|
import { EmailVerificationQuery, UserDetailsQuery } from '../graphql/queries';
|
||||||
import { UpdateUser } from '../graphql/mutation';
|
import { UpdateUser } from '../graphql/mutation';
|
||||||
import EditUserModal from '../components/EditUserModal';
|
import EditUserModal from '../components/EditUserModal';
|
||||||
import DeleteUserModal from '../components/DeleteUserModal';
|
import DeleteUserModal from '../components/DeleteUserModal';
|
||||||
|
import InviteMembersModal from '../components/InviteMembersModal';
|
||||||
|
|
||||||
interface paginationPropTypes {
|
interface paginationPropTypes {
|
||||||
limit: number;
|
limit: number;
|
||||||
|
@ -101,6 +102,8 @@ export default function Users() {
|
||||||
});
|
});
|
||||||
const [userList, setUserList] = React.useState<userDataTypes[]>([]);
|
const [userList, setUserList] = React.useState<userDataTypes[]>([]);
|
||||||
const [loading, setLoading] = React.useState<boolean>(false);
|
const [loading, setLoading] = React.useState<boolean>(false);
|
||||||
|
const [disableInviteMembers, setDisableInviteMembers] =
|
||||||
|
React.useState<boolean>(true);
|
||||||
const updateUserList = async () => {
|
const updateUserList = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const { data } = await client
|
const { data } = await client
|
||||||
|
@ -132,8 +135,18 @@ export default function Users() {
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
const checkEmailVerification = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const { data } = await client.query(EmailVerificationQuery).toPromise();
|
||||||
|
if (data?._env) {
|
||||||
|
const { DISABLE_EMAIL_VERIFICATION } = data._env;
|
||||||
|
setDisableInviteMembers(DISABLE_EMAIL_VERIFICATION);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
updateUserList();
|
updateUserList();
|
||||||
|
checkEmailVerification();
|
||||||
}, []);
|
}, []);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
updateUserList();
|
updateUserList();
|
||||||
|
@ -177,6 +190,10 @@ export default function Users() {
|
||||||
<Text fontSize="md" fontWeight="bold">
|
<Text fontSize="md" fontWeight="bold">
|
||||||
Users
|
Users
|
||||||
</Text>
|
</Text>
|
||||||
|
<InviteMembersModal
|
||||||
|
disabled={disableInviteMembers}
|
||||||
|
updateUserList={updateUserList}
|
||||||
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
{!loading ? (
|
{!loading ? (
|
||||||
userList.length > 0 ? (
|
userList.length > 0 ? (
|
||||||
|
|
|
@ -64,3 +64,25 @@ export const getObjectDiff = (obj1: any, obj2: any) => {
|
||||||
|
|
||||||
return diff;
|
return diff;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const validateEmail = (email: string) => {
|
||||||
|
if (!email || email === '') return true;
|
||||||
|
return email
|
||||||
|
.toLowerCase()
|
||||||
|
.match(
|
||||||
|
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||||
|
)
|
||||||
|
? true
|
||||||
|
: false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateURI = (uri: string) => {
|
||||||
|
if (!uri || uri === '') return true;
|
||||||
|
return uri
|
||||||
|
.toLowerCase()
|
||||||
|
.match(
|
||||||
|
/(?:^|\s)((https?:\/\/)?(?:localhost|[\w-]+(?:\.[\w-]+)+)(:\d+)?(\/\S*)?)/
|
||||||
|
)
|
||||||
|
? true
|
||||||
|
: false;
|
||||||
|
};
|
||||||
|
|
39
dashboard/src/utils/parseCSV.ts
Normal file
39
dashboard/src/utils/parseCSV.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import _flatten from 'lodash/flatten';
|
||||||
|
import { validateEmail } from '.';
|
||||||
|
|
||||||
|
interface dataTypes {
|
||||||
|
value: string;
|
||||||
|
isInvalid: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseCSV = (file: File, delimiter: string): Promise<dataTypes[]> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
// When the FileReader has loaded the file...
|
||||||
|
reader.onload = (e: any) => {
|
||||||
|
// Split the result to an array of lines
|
||||||
|
const lines = e.target.result.split('\n');
|
||||||
|
// Split the lines themselves by the specified
|
||||||
|
// delimiter, such as a comma
|
||||||
|
let result = lines.map((line: string) => line.split(delimiter));
|
||||||
|
// As the FileReader reads asynchronously,
|
||||||
|
// we can't just return the result; instead,
|
||||||
|
// we're passing it to a callback function
|
||||||
|
result = _flatten(result);
|
||||||
|
resolve(
|
||||||
|
result.map((email: string) => {
|
||||||
|
return {
|
||||||
|
value: email.trim(),
|
||||||
|
isInvalid: !validateEmail(email.trim()),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Read the file content as a single string
|
||||||
|
reader.readAsText(file);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default parseCSV;
|
Loading…
Reference in New Issue
Block a user