Verification popup can be activated multiple times (#6938)

Fixes https://github.com/twentyhq/twenty/issues/6912

By clicking Enter key over and over, user can repeat action Expected:
When 'yes' is typed in popup and user clicks Enter key once, popup
should disappear and correlated action should be performed only once

Implementation:
- Added loading state for buttons onClick and onEnter to disable the
button when the "Delete Api Key" and "Regenerate Api Key" function is
called.
- Added a new function to handle modal close and logic handling on
clicking enter key.

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
Shreyansh Kumar
2024-09-18 14:18:49 +05:30
committed by GitHub
parent 72ab6bcf35
commit df8bb84b35
2 changed files with 58 additions and 23 deletions

View File

@@ -17,6 +17,7 @@ import {
export type ConfirmationModalProps = { export type ConfirmationModalProps = {
isOpen: boolean; isOpen: boolean;
title: string; title: string;
loading?: boolean;
subtitle: ReactNode; subtitle: ReactNode;
setIsOpen: (val: boolean) => void; setIsOpen: (val: boolean) => void;
onConfirmClick: () => void; onConfirmClick: () => void;
@@ -59,6 +60,7 @@ export const StyledConfirmationButton = styled(StyledCenteredButton)`
export const ConfirmationModal = ({ export const ConfirmationModal = ({
isOpen = false, isOpen = false,
title, title,
loading,
subtitle, subtitle,
setIsOpen, setIsOpen,
onConfirmClick, onConfirmClick,
@@ -83,6 +85,18 @@ export const ConfirmationModal = ({
250, 250,
); );
const handleConfirmClick = () => {
onConfirmClick();
setIsOpen(false);
};
const handleEnter = () => {
if (isValidValue) {
handleConfirmClick();
}
};
return ( return (
<AnimatePresence mode="wait"> <AnimatePresence mode="wait">
<LayoutGroup> <LayoutGroup>
@@ -93,7 +107,7 @@ export const ConfirmationModal = ({
setIsOpen(false); setIsOpen(false);
} }
}} }}
onEnter={!isValidValue ? undefined : onConfirmClick} onEnter={handleEnter}
isClosable={true} isClosable={true}
padding="large" padding="large"
> >
@@ -125,14 +139,11 @@ export const ConfirmationModal = ({
fullWidth fullWidth
/> />
<StyledCenteredButton <StyledCenteredButton
onClick={async () => { onClick={handleConfirmClick}
await onConfirmClick();
setIsOpen(false);
}}
variant="secondary" variant="secondary"
accent={confirmButtonAccent} accent={confirmButtonAccent}
title={deleteButtonText} title={deleteButtonText}
disabled={!isValidValue} disabled={!isValidValue || loading}
fullWidth fullWidth
dataTestId="confirmation-modal-confirm-button" dataTestId="confirmation-modal-confirm-button"
/> />

View File

@@ -19,6 +19,8 @@ import { computeNewExpirationDate } from '@/settings/developers/utils/compute-ne
import { formatExpiration } from '@/settings/developers/utils/format-expiration'; import { formatExpiration } from '@/settings/developers/utils/format-expiration';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath'; import { SettingsPath } from '@/types/SettingsPath';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { Button } from '@/ui/input/button/components/Button'; import { Button } from '@/ui/input/button/components/Button';
import { TextInput } from '@/ui/input/components/TextInput'; import { TextInput } from '@/ui/input/components/TextInput';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
@@ -41,9 +43,11 @@ const StyledInputContainer = styled.div`
`; `;
export const SettingsDevelopersApiKeyDetail = () => { export const SettingsDevelopersApiKeyDetail = () => {
const { enqueueSnackBar } = useSnackBar();
const [isRegenerateKeyModalOpen, setIsRegenerateKeyModalOpen] = const [isRegenerateKeyModalOpen, setIsRegenerateKeyModalOpen] =
useState(false); useState(false);
const [isDeleteApiKeyModalOpen, setIsDeleteApiKeyModalOpen] = useState(false); const [isDeleteApiKeyModalOpen, setIsDeleteApiKeyModalOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
const { apiKeyId = '' } = useParams(); const { apiKeyId = '' } = useParams();
@@ -69,12 +73,22 @@ export const SettingsDevelopersApiKeyDetail = () => {
const developerPath = getSettingsPagePath(SettingsPath.Developers); const developerPath = getSettingsPagePath(SettingsPath.Developers);
const deleteIntegration = async (redirect = true) => { const deleteIntegration = async (redirect = true) => {
await updateApiKey?.({ setIsLoading(true);
idToUpdate: apiKeyId,
updateOneRecordInput: { revokedAt: DateTime.now().toString() }, try {
}); await updateApiKey?.({
if (redirect) { idToUpdate: apiKeyId,
navigate(developerPath); updateOneRecordInput: { revokedAt: DateTime.now().toString() },
});
if (redirect) {
navigate(developerPath);
}
} catch (err) {
enqueueSnackBar(`Error deleting api key: ${err}`, {
variant: SnackBarVariant.Error,
});
} finally {
setIsLoading(false);
} }
}; };
@@ -102,20 +116,28 @@ export const SettingsDevelopersApiKeyDetail = () => {
token: tokenData.data?.generateApiKeyToken.token, token: tokenData.data?.generateApiKeyToken.token,
}; };
}; };
const regenerateApiKey = async () => { const regenerateApiKey = async () => {
if (isNonEmptyString(apiKeyData?.name)) { setIsLoading(true);
const newExpiresAt = computeNewExpirationDate( try {
apiKeyData?.expiresAt, if (isNonEmptyString(apiKeyData?.name)) {
apiKeyData?.createdAt, const newExpiresAt = computeNewExpirationDate(
); apiKeyData?.expiresAt,
const apiKey = await createIntegration(apiKeyData?.name, newExpiresAt); apiKeyData?.createdAt,
await deleteIntegration(false); );
const apiKey = await createIntegration(apiKeyData?.name, newExpiresAt);
await deleteIntegration(false);
if (isNonEmptyString(apiKey?.token)) { if (isNonEmptyString(apiKey?.token)) {
setApiKeyToken(apiKey.token); setApiKeyToken(apiKey.token);
navigate(`/settings/developers/api-keys/${apiKey.id}`); navigate(`/settings/developers/api-keys/${apiKey.id}`);
}
} }
} catch (err) {
enqueueSnackBar(`Error regenerating api key: ${err}`, {
variant: SnackBarVariant.Error,
});
} finally {
setIsLoading(false);
} }
}; };
@@ -225,6 +247,7 @@ export const SettingsDevelopersApiKeyDetail = () => {
} }
onConfirmClick={deleteIntegration} onConfirmClick={deleteIntegration}
deleteButtonText="Delete" deleteButtonText="Delete"
loading={isLoading}
/> />
<ConfirmationModal <ConfirmationModal
confirmationPlaceholder="yes" confirmationPlaceholder="yes"
@@ -241,6 +264,7 @@ export const SettingsDevelopersApiKeyDetail = () => {
} }
onConfirmClick={regenerateApiKey} onConfirmClick={regenerateApiKey}
deleteButtonText="Regenerate key" deleteButtonText="Regenerate key"
loading={isLoading}
/> />
</> </>
); );