mirror of
				https://github.com/lingble/twenty.git
				synced 2025-10-30 20:27:55 +00:00 
			
		
		
		
	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:
		| @@ -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" | ||||||
|             /> |             /> | ||||||
|   | |||||||
| @@ -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} | ||||||
|       /> |       /> | ||||||
|     </> |     </> | ||||||
|   ); |   ); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Shreyansh Kumar
					Shreyansh Kumar