From df8bb84b351711938da61c24d2cb9a034b53013b Mon Sep 17 00:00:00 2001 From: Shreyansh Kumar Date: Wed, 18 Sep 2024 14:18:49 +0530 Subject: [PATCH] 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 --- .../modal/components/ConfirmationModal.tsx | 23 ++++++-- .../SettingsDevelopersApiKeyDetail.tsx | 58 +++++++++++++------ 2 files changed, 58 insertions(+), 23 deletions(-) diff --git a/packages/twenty-front/src/modules/ui/layout/modal/components/ConfirmationModal.tsx b/packages/twenty-front/src/modules/ui/layout/modal/components/ConfirmationModal.tsx index cd35ec347..04a80a1c8 100644 --- a/packages/twenty-front/src/modules/ui/layout/modal/components/ConfirmationModal.tsx +++ b/packages/twenty-front/src/modules/ui/layout/modal/components/ConfirmationModal.tsx @@ -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 ( @@ -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 /> { - await onConfirmClick(); - setIsOpen(false); - }} + onClick={handleConfirmClick} variant="secondary" accent={confirmButtonAccent} title={deleteButtonText} - disabled={!isValidValue} + disabled={!isValidValue || loading} fullWidth dataTestId="confirmation-modal-confirm-button" /> diff --git a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx index fa1c93c17..107ce698d 100644 --- a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx +++ b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx @@ -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} /> { } onConfirmClick={regenerateApiKey} deleteButtonText="Regenerate key" + loading={isLoading} /> );