import classnames from 'classnames'; import moment from 'moment'; import { Dispatch, MouseEvent as ReactMouseEvent, SetStateAction, useRef, useState } from 'react'; import { BsThreeDotsVertical } from 'react-icons/bs'; import { FaPencilAlt, FaTrashAlt } from 'react-icons/fa'; import { IoMdCloseCircle } from 'react-icons/io'; import API from '../../../../../api'; import useOutsideClick from '../../../../../hooks/useOutsideClick'; import { APIKey, ErrorKind } from '../../../../../types'; import alertDispatcher from '../../../../../utils/alertDispatcher'; import ButtonCopyToClipboard from '../../../../common/ButtonCopyToClipboard'; import Modal from '../../../../common/Modal'; import styles from './Card.module.css'; interface ModalStatus { open: boolean; apiKey?: APIKey; } interface Props { apiKey: APIKey; setModalStatus: Dispatch<SetStateAction<ModalStatus>>; onSuccess: () => void; onAuthError: () => void; } const APIKeyCard = (props: Props) => { const [isDeleting, setIsDeleting] = useState(false); const [dropdownMenuStatus, setDropdownMenuStatus] = useState<boolean>(false); const dropdownMenu = useRef(null); const [deletionModalStatus, setDeletionModalStatus] = useState<boolean>(false); const closeDropdown = () => { setDropdownMenuStatus(false); }; useOutsideClick([dropdownMenu], dropdownMenuStatus, closeDropdown); async function deleteAPIKey() { try { setIsDeleting(true); await API.deleteAPIKey(props.apiKey.apiKeyId!); setIsDeleting(false); props.onSuccess(); } catch (err: any) { setIsDeleting(false); if (err.kind === ErrorKind.Unauthorized) { props.onAuthError(); } else { alertDispatcher.postAlert({ type: 'danger', message: 'An error occurred deleting the API key, please try again later.', }); } } } return ( <div className="col-12 col-xxl-6 py-sm-3 py-2 px-0 px-xxl-3" data-testid="APIKeyCard"> <div className="card h-100"> <div className="card-body d-flex flex-column h-100"> <div className="d-flex flex-row w-100 justify-content-between"> <div className={`h5 mb-1 me-2 text-break ${styles.titleCard}`}>{props.apiKey.name}</div> {deletionModalStatus && ( <Modal className={`d-inline-block ${styles.modal}`} closeButton={ <> <button className="btn btn-sm btn-outline-secondary text-uppercase" onClick={() => setDeletionModalStatus(false)} aria-label="Cancel" > <div className="d-flex flex-row align-items-center"> <IoMdCloseCircle className="me-2" /> <span>Cancel</span> </div> </button> <button className="btn btn-sm btn-danger ms-3" onClick={(e) => { e.preventDefault(); closeDropdown(); deleteAPIKey(); }} disabled={isDeleting} aria-label="Delete API key" > <div className="d-flex flex-row align-items-center text-uppercase"> {isDeleting ? ( <> <span className="spinner-grow spinner-grow-sm" role="status" aria-hidden="true" /> <span className="ms-2">Deleting...</span> </> ) : ( <> <FaTrashAlt className={`me-2 ${styles.btnDeleteIcon}`} /> <span>Delete</span> </> )} </div> </button> </> } header={<div className={`h3 m-2 flex-grow-1 ${styles.title}`}>Delete API key</div>} onClose={() => setDeletionModalStatus(false)} open > <div className="mt-3 mw-100 text-center"> <p>Are you sure you want to remove this API key?</p> </div> </Modal> )} <div className="ms-auto"> <div ref={dropdownMenu} className={classnames('dropdown-menu dropdown-menu-end p-0', styles.dropdownMenu, { show: dropdownMenuStatus, })} > <div className={`dropdown-arrow ${styles.arrow}`} /> <button className="dropdown-item btn btn-sm rounded-0 text-dark" onClick={(e: ReactMouseEvent<HTMLButtonElement>) => { e.preventDefault(); closeDropdown(); props.setModalStatus({ open: true, apiKey: props.apiKey, }); }} aria-label="Open API key modal" > <div className="d-flex flex-row align-items-center"> <FaPencilAlt className={`me-2 ${styles.btnIcon}`} /> <span>Edit</span> </div> </button> <button className="dropdown-item btn btn-sm rounded-0 text-dark" onClick={(e: ReactMouseEvent<HTMLButtonElement>) => { e.preventDefault(); closeDropdown(); setDeletionModalStatus(true); }} aria-label="Open deletion modal" > <div className="d-flex flex-row align-items-center"> <FaTrashAlt className={`me-2 ${styles.btnIcon}`} /> <span>Delete</span> </div> </button> </div> <button className={`btn btn-outline-secondary rounded-circle p-0 text-center ${styles.btnDropdown}`} onClick={() => setDropdownMenuStatus(true)} aria-label="Open menu" aria-expanded={dropdownMenuStatus} > <BsThreeDotsVertical /> </button> </div> </div> <div className="mt-2 d-flex flex-row align-items-baseline"> <div className="text-truncate"> <small className="text-muted text-uppercase me-1">API-KEY-ID: </small> <small>{props.apiKey.apiKeyId}</small> </div> <div className={`ms-1 ${styles.copyBtn}`}> <div className={`position-absolute ${styles.copyBtnWrapper}`}> <ButtonCopyToClipboard text={props.apiKey.apiKeyId!} className="btn-link border-0 text-dark fw-bold" label="Copy API key ID to clipboard" /> </div> </div> </div> <div className="text-truncate"> <small className="text-muted text-uppercase me-1">Created at: </small> <small>{moment.unix(props.apiKey.createdAt!).format('YYYY/MM/DD HH:mm:ss (Z)')}</small> </div> </div> </div> </div> ); }; export default APIKeyCard;