import { IconExternalLink, IconTrash } from '@tabler/icons-react'; import { fromUnixTime } from 'date-fns'; import Link from 'next/link'; import { useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; import { toast, ToastOptions } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; import { useFormStatus } from '~/hooks'; import { IntegrationTokenType, Optional, TokenPermissionEnum, TokenPermissionsType } from '~/types'; import classes from '../UserSettings.module.css'; //Components import CopyButton from '~/Components/UI/CopyButton/CopyButton'; import Error from '~/Components/UI/Error/Error'; import Info from '~/Components/UI/Info/Info'; import { useLoader } from '~/contexts/LoaderContext'; type IntegrationsDataForm = { tokenName: string; }; export default function Integrations() { const toastOptions: ToastOptions = { position: 'top-right', autoClose: 5000, hideProgressBar: false, closeOnClick: true, pauseOnHover: true, draggable: true, progress: undefined, }; const { register, handleSubmit, reset, formState: { errors, isSubmitting, isValid }, } = useForm({ mode: 'onChange' }); const { start, stop } = useLoader(); const { error, handleError, clearError, setIsLoading, isLoading } = useFormStatus(); const renderPermissionBadges = (permissions: TokenPermissionsType) => { return Object.entries(permissions) .filter(([, hasPermission]) => hasPermission) .map(([key]) => (
{key.charAt(0).toUpperCase() + key.slice(1)}
)); }; ////State const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [tokenList, setTokenList] = useState>(); const [lastGeneratedToken, setLastGeneratedToken] = useState>(); const [deletingToken, setDeletingToken] = useState>(undefined); const [permissions, setPermissions] = useState({ create: false, read: false, update: false, delete: false, }); const fetchTokenList = async () => { start(); try { const response = await fetch('/api/v1/integration/token-manager', { method: 'GET', headers: { 'Content-type': 'application/json', }, }); const data: Array = await response.json(); setTokenList(data); } catch (error) { handleError('Fetching token list failed.'); } finally { stop(); } }; ////LifeCycle useEffect(() => { fetchTokenList(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Permissions handler const hasNoPermissionSelected = () => { return !Object.values(permissions).some((value) => value); }; const togglePermission = (permissionType: TokenPermissionEnum) => { const updatedPermissions = { ...permissions, [permissionType]: !permissions[permissionType], }; setPermissions(updatedPermissions); }; const resetPermissions = () => { setPermissions({ create: false, read: false, update: false, delete: false, }); }; //Form submit handler to ADD a new token const formSubmitHandler = async (data: IntegrationsDataForm) => { start(); clearError(); setIsLoading(true); // Post API to send the new token integration try { const response = await fetch('/api/v1/integration/token-manager', { method: 'POST', headers: { 'Content-type': 'application/json', }, body: JSON.stringify({ name: data.tokenName, permissions: permissions, }), }); const result = await response.json(); setLastGeneratedToken({ name: data.tokenName, value: result.token }); if (!response.ok) { toast.error(result.message, toastOptions); } else { fetchTokenList(); toast.success('🔑 Token generated !', toastOptions); } } catch (error) { toast.error('Failed to generate a new token', toastOptions); } finally { setIsLoading(false); resetPermissions(); reset(); stop(); } }; //Delete token const deleteTokenHandler = async (tokenName: string) => { setIsDeleteLoading(true); try { const response = await fetch('/api/v1/integration/token-manager', { method: 'DELETE', headers: { 'Content-type': 'application/json', }, body: JSON.stringify({ name: tokenName, }), }); const result = await response.json(); if (!response.ok) { toast.error(result.message, toastOptions); setIsDeleteLoading(false); } else { fetchTokenList(); setIsDeleteLoading(false); toast.success('🗑️ Token deleted !', toastOptions); } } catch (error) { setIsDeleteLoading(false); toast.error('Failed to delete the token', toastOptions); } finally { setIsDeleteLoading(false); setDeletingToken(undefined); } }; return ( <>

Generate token

togglePermission(TokenPermissionEnum.CREATE)} > Create
togglePermission(TokenPermissionEnum.READ)} > Read
togglePermission(TokenPermissionEnum.UPDATE)} > Update
togglePermission(TokenPermissionEnum.DELETE)} > Delete
{errors.tokenName && errors.tokenName.type === 'maxLength' && ( 25 characters max. )} {errors.tokenName && errors.tokenName.type === 'pattern' && ( Only alphanumeric characters, dashes, and underscores are allowed (no spaces). )} {error && }
{tokenList && tokenList.length > 0 && (

API Tokens

{tokenList .slice() .sort((a, b) => b.creation - a.creation) .map((token, index) => (
{token.name}
Created at: {fromUnixTime(token.creation).toLocaleString()}
Permission:
{renderPermissionBadges(token.permissions)}
{lastGeneratedToken && lastGeneratedToken.name === token.name && ( <>
Token: {lastGeneratedToken.value}
)} {deletingToken && deletingToken.name === token.name && (
{!isDeleteLoading && ( )}
)}
setDeletingToken(token)} />
))}
)} ); }