authorizer/dashboard/src/components/UpdateWebhookModal.tsx

597 lines
16 KiB
TypeScript
Raw Normal View History

2022-07-16 09:54:50 +00:00
import React, { useEffect, useState } from 'react';
2022-07-14 18:11:44 +00:00
import {
Button,
Center,
Flex,
Input,
InputGroup,
InputRightElement,
2022-07-16 09:54:50 +00:00
MenuItem,
2022-07-14 18:11:44 +00:00
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
2022-07-17 09:12:46 +00:00
Select,
2022-07-14 18:11:44 +00:00
Switch,
Text,
useDisclosure,
useToast,
} from '@chakra-ui/react';
import { FaMinusCircle, FaPlus } from 'react-icons/fa';
import { useClient } from 'urql';
2022-07-15 16:42:08 +00:00
import {
2022-07-17 09:12:46 +00:00
webhookEventNames,
2022-07-15 16:42:08 +00:00
ArrayInputOperations,
WebhookInputDataFields,
WebhookInputHeaderFields,
2022-07-16 09:54:50 +00:00
UpdateWebhookModalViews,
2022-07-17 10:33:07 +00:00
webhookVerifiedStatus,
2022-07-15 16:42:08 +00:00
} from '../constants';
2022-07-17 09:18:20 +00:00
import { capitalizeFirstLetter, validateURI } from '../utils';
2022-07-17 11:20:58 +00:00
import { AddWebhook, EditWebhook, TestEndpoint } from '../graphql/mutation';
2022-07-17 09:12:46 +00:00
import { rest } from 'lodash';
2022-07-17 10:33:07 +00:00
import { BiCheckCircle, BiError, BiErrorCircle } from 'react-icons/bi';
2022-07-14 18:11:44 +00:00
interface headersDataType {
2022-07-15 16:42:08 +00:00
[WebhookInputHeaderFields.KEY]: string;
[WebhookInputHeaderFields.VALUE]: string;
2022-07-14 18:11:44 +00:00
}
interface headersValidatorDataType {
2022-07-15 16:42:08 +00:00
[WebhookInputHeaderFields.KEY]: boolean;
[WebhookInputHeaderFields.VALUE]: boolean;
2022-07-14 18:11:44 +00:00
}
2022-07-16 09:54:50 +00:00
interface selecetdWebhookDataTypes {
[WebhookInputDataFields.ID]: string;
[WebhookInputDataFields.EVENT_NAME]: string;
[WebhookInputDataFields.ENDPOINT]: string;
[WebhookInputDataFields.ENABLED]: boolean;
[WebhookInputDataFields.HEADERS]?: Record<string, string>;
}
interface UpdateWebhookModalInputPropTypes {
view: UpdateWebhookModalViews;
selectedWebhook?: selecetdWebhookDataTypes;
fetchWebookData: Function;
}
2022-07-14 18:11:44 +00:00
const initHeadersData: headersDataType = {
2022-07-15 16:42:08 +00:00
[WebhookInputHeaderFields.KEY]: '',
[WebhookInputHeaderFields.VALUE]: '',
2022-07-14 18:11:44 +00:00
};
const initHeadersValidatorData: headersValidatorDataType = {
2022-07-15 16:42:08 +00:00
[WebhookInputHeaderFields.KEY]: true,
[WebhookInputHeaderFields.VALUE]: true,
2022-07-14 18:11:44 +00:00
};
interface webhookDataType {
2022-07-15 16:42:08 +00:00
[WebhookInputDataFields.EVENT_NAME]: string;
[WebhookInputDataFields.ENDPOINT]: string;
[WebhookInputDataFields.ENABLED]: boolean;
[WebhookInputDataFields.HEADERS]: headersDataType[];
2022-07-14 18:11:44 +00:00
}
interface validatorDataType {
2022-07-15 16:42:08 +00:00
[WebhookInputDataFields.ENDPOINT]: boolean;
[WebhookInputDataFields.HEADERS]: headersValidatorDataType[];
2022-07-14 18:11:44 +00:00
}
const initWebhookData: webhookDataType = {
2022-07-17 09:12:46 +00:00
[WebhookInputDataFields.EVENT_NAME]: webhookEventNames.USER_LOGIN,
2022-07-15 16:42:08 +00:00
[WebhookInputDataFields.ENDPOINT]: '',
[WebhookInputDataFields.ENABLED]: false,
[WebhookInputDataFields.HEADERS]: [{ ...initHeadersData }],
2022-07-14 18:11:44 +00:00
};
const initWebhookValidatorData: validatorDataType = {
2022-07-15 16:42:08 +00:00
[WebhookInputDataFields.ENDPOINT]: true,
[WebhookInputDataFields.HEADERS]: [{ ...initHeadersValidatorData }],
2022-07-14 18:11:44 +00:00
};
2022-07-16 09:54:50 +00:00
const UpdateWebhookModal = ({
view,
selectedWebhook,
fetchWebookData,
}: UpdateWebhookModalInputPropTypes) => {
2022-07-14 18:11:44 +00:00
const client = useClient();
const toast = useToast();
const { isOpen, onOpen, onClose } = useDisclosure();
const [loading, setLoading] = useState<boolean>(false);
2022-07-17 10:33:07 +00:00
const [verifyingEndpoint, setVerifyingEndpoint] = useState<boolean>(false);
2022-07-14 18:11:44 +00:00
const [webhook, setWebhook] = useState<webhookDataType>({
...initWebhookData,
});
const [validator, setValidator] = useState<validatorDataType>({
...initWebhookValidatorData,
});
2022-07-17 10:33:07 +00:00
const [verifiedStatus, setVerifiedStatus] = useState<webhookVerifiedStatus>(
webhookVerifiedStatus.PENDING
);
2022-07-14 18:11:44 +00:00
const inputChangehandler = (
inputType: string,
value: any,
2022-07-15 16:42:08 +00:00
headerInputType: string = WebhookInputHeaderFields.KEY,
2022-07-14 18:11:44 +00:00
headerIndex: number = 0
) => {
2022-07-17 11:20:58 +00:00
if (
verifiedStatus !== webhookVerifiedStatus.PENDING &&
inputType !== WebhookInputDataFields.ENABLED
) {
setVerifiedStatus(webhookVerifiedStatus.PENDING);
}
2022-07-14 18:11:44 +00:00
switch (inputType) {
2022-07-15 16:42:08 +00:00
case WebhookInputDataFields.EVENT_NAME:
2022-07-14 18:11:44 +00:00
setWebhook({ ...webhook, [inputType]: value });
break;
2022-07-15 16:42:08 +00:00
case WebhookInputDataFields.ENDPOINT:
2022-07-14 18:11:44 +00:00
setWebhook({ ...webhook, [inputType]: value });
setValidator({
...validator,
2022-07-15 16:42:08 +00:00
[WebhookInputDataFields.ENDPOINT]: validateURI(value),
2022-07-14 18:11:44 +00:00
});
break;
2022-07-15 16:42:08 +00:00
case WebhookInputDataFields.ENABLED:
2022-07-14 18:11:44 +00:00
setWebhook({ ...webhook, [inputType]: value });
break;
2022-07-15 16:42:08 +00:00
case WebhookInputDataFields.HEADERS:
const updatedHeaders: any = [
...webhook[WebhookInputDataFields.HEADERS],
];
2022-07-14 18:11:44 +00:00
const updatedHeadersValidatorData: any = [
2022-07-15 16:42:08 +00:00
...validator[WebhookInputDataFields.HEADERS],
2022-07-14 18:11:44 +00:00
];
const otherHeaderInputType =
2022-07-15 16:42:08 +00:00
headerInputType === WebhookInputHeaderFields.KEY
? WebhookInputHeaderFields.VALUE
: WebhookInputHeaderFields.KEY;
2022-07-14 18:11:44 +00:00
updatedHeaders[headerIndex][headerInputType] = value;
updatedHeadersValidatorData[headerIndex][headerInputType] =
value.length > 0
? updatedHeaders[headerIndex][otherHeaderInputType].length > 0
: updatedHeaders[headerIndex][otherHeaderInputType].length === 0;
updatedHeadersValidatorData[headerIndex][otherHeaderInputType] =
value.length > 0
? updatedHeaders[headerIndex][otherHeaderInputType].length > 0
: updatedHeaders[headerIndex][otherHeaderInputType].length === 0;
setWebhook({ ...webhook, [inputType]: updatedHeaders });
setValidator({
...validator,
[inputType]: updatedHeadersValidatorData,
});
break;
default:
break;
}
};
2022-07-15 06:50:51 +00:00
const updateHeaders = (operation: string, index: number = 0) => {
2022-07-17 11:20:58 +00:00
if (verifiedStatus !== webhookVerifiedStatus.PENDING) {
setVerifiedStatus(webhookVerifiedStatus.PENDING);
}
2022-07-14 18:11:44 +00:00
switch (operation) {
case ArrayInputOperations.APPEND:
setWebhook({
...webhook,
2022-07-15 16:42:08 +00:00
[WebhookInputDataFields.HEADERS]: [
...(webhook?.[WebhookInputDataFields.HEADERS] || []),
2022-07-14 18:11:44 +00:00
{ ...initHeadersData },
],
});
setValidator({
...validator,
2022-07-15 16:42:08 +00:00
[WebhookInputDataFields.HEADERS]: [
...(validator?.[WebhookInputDataFields.HEADERS] || []),
2022-07-14 18:11:44 +00:00
{ ...initHeadersValidatorData },
],
});
break;
case ArrayInputOperations.REMOVE:
2022-07-15 16:42:08 +00:00
if (webhook?.[WebhookInputDataFields.HEADERS]?.length) {
const updatedHeaders = [...webhook[WebhookInputDataFields.HEADERS]];
2022-07-14 18:11:44 +00:00
updatedHeaders.splice(index, 1);
setWebhook({
...webhook,
2022-07-15 16:42:08 +00:00
[WebhookInputDataFields.HEADERS]: updatedHeaders,
2022-07-14 18:11:44 +00:00
});
}
2022-07-15 16:42:08 +00:00
if (validator?.[WebhookInputDataFields.HEADERS]?.length) {
const updatedHeadersData = [
...validator[WebhookInputDataFields.HEADERS],
];
2022-07-14 18:11:44 +00:00
updatedHeadersData.splice(index, 1);
setValidator({
...validator,
2022-07-15 16:42:08 +00:00
[WebhookInputDataFields.HEADERS]: updatedHeadersData,
2022-07-14 18:11:44 +00:00
});
}
break;
default:
break;
}
};
2022-07-15 06:50:51 +00:00
const validateData = () => {
return (
!loading &&
2022-07-17 10:33:07 +00:00
!verifyingEndpoint &&
2022-07-15 16:42:08 +00:00
webhook[WebhookInputDataFields.EVENT_NAME].length > 0 &&
webhook[WebhookInputDataFields.ENDPOINT].length > 0 &&
validator[WebhookInputDataFields.ENDPOINT] &&
!validator[WebhookInputDataFields.HEADERS].some(
2022-07-15 06:50:51 +00:00
(headerData: headersValidatorDataType) =>
!headerData.key || !headerData.value
)
);
};
2022-07-17 11:20:58 +00:00
const getParams = () => {
2022-07-16 09:54:50 +00:00
let params: any = {
[WebhookInputDataFields.EVENT_NAME]:
webhook[WebhookInputDataFields.EVENT_NAME],
[WebhookInputDataFields.ENDPOINT]:
webhook[WebhookInputDataFields.ENDPOINT],
[WebhookInputDataFields.ENABLED]: webhook[WebhookInputDataFields.ENABLED],
2022-07-17 08:08:18 +00:00
[WebhookInputDataFields.HEADERS]: {},
2022-07-16 09:54:50 +00:00
};
2022-07-15 16:42:08 +00:00
if (webhook[WebhookInputDataFields.HEADERS].length) {
const headers = webhook[WebhookInputDataFields.HEADERS].reduce(
(acc, data) => {
return data.key ? { ...acc, [data.key]: data.value } : acc;
},
{}
);
if (Object.keys(headers).length) {
params[WebhookInputDataFields.HEADERS] = headers;
}
2022-07-15 06:50:51 +00:00
}
2022-07-17 11:20:58 +00:00
return params;
};
const saveData = async () => {
if (!validateData()) return;
setLoading(true);
const params = getParams();
2022-07-16 09:54:50 +00:00
let res: any = {};
if (
view === UpdateWebhookModalViews.Edit &&
selectedWebhook?.[WebhookInputDataFields.ID]
) {
res = await client
.mutation(EditWebhook, {
params: {
...params,
id: selectedWebhook[WebhookInputDataFields.ID],
},
})
.toPromise();
} else {
res = await client.mutation(AddWebhook, { params }).toPromise();
}
2022-07-17 09:12:46 +00:00
setLoading(false);
2022-07-15 06:50:51 +00:00
if (res.error) {
toast({
title: capitalizeFirstLetter(res.error.message),
isClosable: true,
status: 'error',
position: 'bottom-right',
});
2022-07-16 09:54:50 +00:00
} else if (res.data?._add_webhook || res.data?._update_webhook) {
2022-07-15 06:50:51 +00:00
toast({
2022-07-16 09:54:50 +00:00
title: capitalizeFirstLetter(
res.data?._add_webhook?.message || res.data?._update_webhook?.message
),
2022-07-15 06:50:51 +00:00
isClosable: true,
status: 'success',
position: 'bottom-right',
});
2022-07-15 07:34:32 +00:00
setWebhook({
...initWebhookData,
2022-07-15 16:42:08 +00:00
[WebhookInputDataFields.HEADERS]: [{ ...initHeadersData }],
2022-07-15 07:34:32 +00:00
});
setValidator({ ...initWebhookValidatorData });
2022-07-16 09:54:50 +00:00
fetchWebookData();
2022-07-15 06:50:51 +00:00
}
2022-07-17 09:12:46 +00:00
view === UpdateWebhookModalViews.ADD && onClose();
2022-07-15 06:50:51 +00:00
};
2022-07-16 09:54:50 +00:00
useEffect(() => {
if (
2022-07-17 08:22:31 +00:00
isOpen &&
2022-07-16 09:54:50 +00:00
view === UpdateWebhookModalViews.Edit &&
selectedWebhook &&
Object.keys(selectedWebhook || {}).length
) {
const { headers, ...rest } = selectedWebhook;
const headerItems = Object.entries(headers || {});
if (headerItems.length) {
let formattedHeadersData = headerItems.map((headerData) => {
return {
[WebhookInputHeaderFields.KEY]: headerData[0],
[WebhookInputHeaderFields.VALUE]: headerData[1],
};
});
setWebhook({
...rest,
[WebhookInputDataFields.HEADERS]: formattedHeadersData,
});
setValidator({
...validator,
[WebhookInputDataFields.HEADERS]: new Array(
formattedHeadersData.length
)
.fill({})
.map(() => ({ ...initHeadersValidatorData })),
});
} else {
2022-07-17 08:08:18 +00:00
setWebhook({
...rest,
[WebhookInputDataFields.HEADERS]: [{ ...initHeadersData }],
});
2022-07-16 09:54:50 +00:00
}
}
2022-07-17 08:22:31 +00:00
}, [isOpen]);
2022-07-17 11:20:58 +00:00
const verifyEndpoint = async () => {
if (!validateData()) return;
setVerifyingEndpoint(true);
const { [WebhookInputDataFields.ENABLED]: _, ...params } = getParams();
const res = await client.mutation(TestEndpoint, { params }).toPromise();
if (res.data?._test_endpoint?.response?.success) {
setVerifiedStatus(webhookVerifiedStatus.VERIFIED);
} else {
setVerifiedStatus(webhookVerifiedStatus.NOT_VERIFIED);
}
setVerifyingEndpoint(false);
};
2022-07-14 18:11:44 +00:00
return (
<>
2022-07-16 09:54:50 +00:00
{view === UpdateWebhookModalViews.ADD ? (
<Button
leftIcon={<FaPlus />}
colorScheme="blue"
variant="solid"
onClick={onOpen}
isDisabled={false}
size="sm"
>
<Center h="100%">Add Webhook</Center>{' '}
</Button>
) : (
<MenuItem onClick={onOpen}>Edit</MenuItem>
)}
2022-07-14 18:11:44 +00:00
<Modal isOpen={isOpen} onClose={onClose} size="3xl">
<ModalOverlay />
<ModalContent>
2022-07-16 09:54:50 +00:00
<ModalHeader>
{view === UpdateWebhookModalViews.ADD
? 'Add New Webhook'
: 'Edit Webhook'}
</ModalHeader>
2022-07-14 18:11:44 +00:00
<ModalCloseButton />
<ModalBody>
<Flex
flexDirection="column"
border="1px"
borderRadius="md"
borderColor="gray.200"
p="5"
>
<Flex
width="100%"
justifyContent="space-between"
alignItems="center"
marginBottom="2%"
>
<Flex flex="1">Event Name</Flex>
<Flex flex="3">
2022-07-17 09:12:46 +00:00
<Select
size="md"
value={webhook[WebhookInputDataFields.EVENT_NAME]}
onChange={(e) =>
inputChangehandler(
WebhookInputDataFields.EVENT_NAME,
e.currentTarget.value
)
}
>
{Object.entries(webhookEventNames).map(
([key, value]: any) => (
<option value={value} key={key}>
{key}
</option>
)
)}
</Select>
2022-07-14 18:11:44 +00:00
</Flex>
</Flex>
<Flex
width="100%"
justifyContent="start"
alignItems="center"
marginBottom="5%"
>
<Flex flex="1">Endpoint</Flex>
<Flex flex="3">
<InputGroup size="md">
<Input
pr="4.5rem"
type="text"
placeholder="https://domain.com/webhook"
2022-07-15 16:42:08 +00:00
value={webhook[WebhookInputDataFields.ENDPOINT]}
isInvalid={!validator[WebhookInputDataFields.ENDPOINT]}
2022-07-14 18:11:44 +00:00
onChange={(e) =>
inputChangehandler(
2022-07-15 16:42:08 +00:00
WebhookInputDataFields.ENDPOINT,
2022-07-14 18:11:44 +00:00
e.currentTarget.value
)
}
/>
</InputGroup>
</Flex>
</Flex>
<Flex
width="100%"
justifyContent="space-between"
alignItems="center"
marginBottom="5%"
>
<Flex flex="1">Enabled</Flex>
<Flex w="25%" justifyContent="space-between">
<Text h="75%" fontWeight="bold" marginRight="2">
Off
</Text>
<Switch
size="md"
2022-07-15 16:42:08 +00:00
isChecked={webhook[WebhookInputDataFields.ENABLED]}
2022-07-14 18:11:44 +00:00
onChange={() =>
inputChangehandler(
2022-07-15 16:42:08 +00:00
WebhookInputDataFields.ENABLED,
!webhook[WebhookInputDataFields.ENABLED]
2022-07-14 18:11:44 +00:00
)
}
/>
<Text h="75%" fontWeight="bold" marginLeft="2">
On
</Text>
</Flex>
</Flex>
<Flex
width="100%"
justifyContent="space-between"
alignItems="center"
marginBottom="2%"
>
<Flex>Headers</Flex>
<Flex>
<Button
leftIcon={<FaPlus />}
colorScheme="blue"
h="1.75rem"
size="sm"
variant="ghost"
paddingRight="0"
2022-07-15 06:50:51 +00:00
onClick={() => updateHeaders(ArrayInputOperations.APPEND)}
2022-07-14 18:11:44 +00:00
>
Add more Headers
</Button>
</Flex>
</Flex>
<Flex flexDirection="column" maxH={220} overflowY="scroll">
2022-07-15 16:42:08 +00:00
{webhook[WebhookInputDataFields.HEADERS]?.map(
(headerData, index) => (
<Flex
key={`header-data-${index}`}
justifyContent="center"
alignItems="center"
>
<InputGroup size="md" marginBottom="2.5%">
<Input
type="text"
placeholder="key"
value={headerData[WebhookInputHeaderFields.KEY]}
isInvalid={
!validator[WebhookInputDataFields.HEADERS][index]?.[
WebhookInputHeaderFields.KEY
]
}
onChange={(e) =>
inputChangehandler(
WebhookInputDataFields.HEADERS,
e.target.value,
WebhookInputHeaderFields.KEY,
index
)
}
width="30%"
marginRight="2%"
/>
<Center marginRight="2%">
<Text fontWeight="bold">:</Text>
</Center>
<Input
type="text"
placeholder="value"
value={headerData[WebhookInputHeaderFields.VALUE]}
isInvalid={
!validator[WebhookInputDataFields.HEADERS][index]?.[
WebhookInputHeaderFields.VALUE
]
}
onChange={(e) =>
inputChangehandler(
WebhookInputDataFields.HEADERS,
e.target.value,
WebhookInputHeaderFields.VALUE,
index
)
2022-07-14 18:11:44 +00:00
}
2022-07-15 16:42:08 +00:00
width="65%"
/>
<InputRightElement width="3rem">
<Button
width="6rem"
colorScheme="blackAlpha"
variant="ghost"
padding="0"
onClick={() =>
updateHeaders(ArrayInputOperations.REMOVE, index)
}
>
<FaMinusCircle />
</Button>
</InputRightElement>
</InputGroup>
</Flex>
)
)}
2022-07-14 18:11:44 +00:00
</Flex>
</Flex>
</ModalBody>
<ModalFooter>
2022-07-17 10:33:07 +00:00
<Button
colorScheme={
verifiedStatus === webhookVerifiedStatus.VERIFIED
? 'green'
: verifiedStatus === webhookVerifiedStatus.PENDING
? 'yellow'
: 'red'
}
variant="outline"
2022-07-17 11:20:58 +00:00
onClick={verifyEndpoint}
2022-07-17 10:33:07 +00:00
isLoading={verifyingEndpoint}
isDisabled={!validateData()}
marginRight="5"
leftIcon={
verifiedStatus === webhookVerifiedStatus.VERIFIED ? (
<BiCheckCircle />
) : verifiedStatus === webhookVerifiedStatus.PENDING ? (
<BiErrorCircle />
) : (
<BiError />
)
}
>
2022-07-17 11:20:58 +00:00
{verifiedStatus === webhookVerifiedStatus.VERIFIED
? 'Endpoint Verified'
: verifiedStatus === webhookVerifiedStatus.PENDING
? 'Test Endpoint'
: 'Endpoint Not Verified'}
2022-07-17 10:33:07 +00:00
</Button>
2022-07-14 18:11:44 +00:00
<Button
colorScheme="blue"
variant="solid"
2022-07-15 06:50:51 +00:00
onClick={saveData}
isDisabled={!validateData()}
2022-07-14 18:11:44 +00:00
>
<Center h="100%" pt="5%">
Save
</Center>
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
);
};
2022-07-16 09:54:50 +00:00
export default UpdateWebhookModal;