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 = {
isOpen: boolean;
title: string;
loading?: boolean;
subtitle: ReactNode;
setIsOpen: (val: boolean) => void;
onConfirmClick: () => void;
@@ -59,6 +60,7 @@ export const StyledConfirmationButton = styled(StyledCenteredButton)`
export const ConfirmationModal = ({
isOpen = false,
title,
loading,
subtitle,
setIsOpen,
onConfirmClick,
@@ -83,6 +85,18 @@ export const ConfirmationModal = ({
250,
);
const handleConfirmClick = () => {
onConfirmClick();
setIsOpen(false);
};
const handleEnter = () => {
if (isValidValue) {
handleConfirmClick();
}
};
return (
<AnimatePresence mode="wait">
<LayoutGroup>
@@ -93,7 +107,7 @@ export const ConfirmationModal = ({
setIsOpen(false);
}
}}
onEnter={!isValidValue ? undefined : onConfirmClick}
onEnter={handleEnter}
isClosable={true}
padding="large"
>
@@ -125,14 +139,11 @@ export const ConfirmationModal = ({
fullWidth
/>
<StyledCenteredButton
onClick={async () => {
await onConfirmClick();
setIsOpen(false);
}}
onClick={handleConfirmClick}
variant="secondary"
accent={confirmButtonAccent}
title={deleteButtonText}
disabled={!isValidValue}
disabled={!isValidValue || loading}
fullWidth
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 { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
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 { TextInput } from '@/ui/input/components/TextInput';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
@@ -41,9 +43,11 @@ const StyledInputContainer = styled.div`
`;
export const SettingsDevelopersApiKeyDetail = () => {
const { enqueueSnackBar } = useSnackBar();
const [isRegenerateKeyModalOpen, setIsRegenerateKeyModalOpen] =
useState(false);
const [isDeleteApiKeyModalOpen, setIsDeleteApiKeyModalOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
const { apiKeyId = '' } = useParams();
@@ -69,12 +73,22 @@ export const SettingsDevelopersApiKeyDetail = () => {
const developerPath = getSettingsPagePath(SettingsPath.Developers);
const deleteIntegration = async (redirect = true) => {
await updateApiKey?.({
idToUpdate: apiKeyId,
updateOneRecordInput: { revokedAt: DateTime.now().toString() },
});
if (redirect) {
navigate(developerPath);
setIsLoading(true);
try {
await updateApiKey?.({
idToUpdate: apiKeyId,
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,
};
};
const regenerateApiKey = async () => {
if (isNonEmptyString(apiKeyData?.name)) {
const newExpiresAt = computeNewExpirationDate(
apiKeyData?.expiresAt,
apiKeyData?.createdAt,
);
const apiKey = await createIntegration(apiKeyData?.name, newExpiresAt);
await deleteIntegration(false);
setIsLoading(true);
try {
if (isNonEmptyString(apiKeyData?.name)) {
const newExpiresAt = computeNewExpirationDate(
apiKeyData?.expiresAt,
apiKeyData?.createdAt,
);
const apiKey = await createIntegration(apiKeyData?.name, newExpiresAt);
await deleteIntegration(false);
if (isNonEmptyString(apiKey?.token)) {
setApiKeyToken(apiKey.token);
navigate(`/settings/developers/api-keys/${apiKey.id}`);
if (isNonEmptyString(apiKey?.token)) {
setApiKeyToken(apiKey.token);
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}
deleteButtonText="Delete"
loading={isLoading}
/>
<ConfirmationModal
confirmationPlaceholder="yes"
@@ -241,6 +264,7 @@ export const SettingsDevelopersApiKeyDetail = () => {
}
onConfirmClick={regenerateApiKey}
deleteButtonText="Regenerate key"
loading={isLoading}
/>
</>
);