Modal API Refactoring (#12062)
# Modal API Refactoring This PR refactors the modal system to use an imperative approach for setting hotkey scopes, addressing race conditions that occurred with the previous effect-based implementation. Fixes #11986 Closes #12087 ## Key Changes: - **New Modal API**: Introduced a `useModal` hook with `openModal`, `closeModal`, and `toggleModal` functions, similar to the existing dropdown API - **Modal Identification**: Added a `modalId` prop to uniquely identify modals - **State Management**: Introduced `isModalOpenedComponentState` and removed individual boolean state atoms (like `isRemoveSortingModalOpenState`) - **Modal Constants**: Added consistent modal ID constants (e.g., `FavoriteFolderDeleteModalId`, `RecordIndexRemoveSortingModalId`) for better maintainability - **Mount Effects**: Created mount effect components (like `AuthModalMountEffect`) to handle initial modal opening where needed ## Implementation Details: - Modified `Modal` and `ConfirmationModal` components to accept the new `modalId` prop - Added a component-state-based approach using `ModalComponentInstanceContext` to track modal state - Introduced imperative modal handlers that properly manage hotkey scopes - Components like `ActionModal` and `AttachmentList` now use the new `useModal` hook for better control over modal state ## Benefits: - **Race Condition Prevention**: Hotkey scopes are now set imperatively, eliminating race conditions - **Consistent API**: Modal and dropdown now share similar patterns, improving developer experience ## Tests to do before merging: 1. Action Modals (Modal triggered by an action, for example the delete action) 2. Auth Modal (`AuthModal.tsx` and `AuthModalMountEffect.tsx`) - Test that auth modal opens automatically on mount - Verify authentication flow works properly 3. Email Verification Sent Modal (in `SignInUp.tsx`) - Verify this modal displays correctly 4. Attachment Preview Modal (in `AttachmentList.tsx`) - Test opening preview modal by clicking on attachments - Verify close, download functionality works - Test modal navigation and interactions 5. Favorite Folder Delete Modal (`CurrentWorkspaceMemberFavorites.tsx`) - Test deletion confirmation flow - Check that modal opens when attempting to delete folders with favorites 6. Record Board Remove Sorting Modal (`RecordBoard.tsx` using `RecordIndexRemoveSortingModalId`) - Test that modal appears when trying to drag records with sorting enabled - Verify sorting removal works correctly 7. Record Group Reorder Confirmation Modal (`RecordGroupReorderConfirmationModal.tsx`) - Test group reordering with sorting enabled - Verify confirmation modal properly handles sorting removal 8. Confirmation Modal (base component used by several modals) - Test all variants with different confirmation options For each modal, verify: - Opening/closing behavior - Hotkey support (Esc to close, Enter to confirm where applicable) - Click outside behavior - Proper z-index stacking - Any modal-specific functionality
This commit is contained in:
@ -9,12 +9,12 @@ 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 { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@ -28,6 +28,9 @@ import { SettingsCustomDomain } from '~/pages/settings/workspace/SettingsCustomD
|
||||
import { SettingsSubdomain } from '~/pages/settings/workspace/SettingsSubdomain';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
|
||||
export const SUBDOMAIN_CHANGE_CONFIRMATION_MODAL_ID =
|
||||
'subdomain-change-confirmation-modal';
|
||||
|
||||
export const SettingsDomain = () => {
|
||||
const navigate = useNavigateSettings();
|
||||
const { t } = useLingui();
|
||||
@ -73,10 +76,7 @@ export const SettingsDomain = () => {
|
||||
currentWorkspaceState,
|
||||
);
|
||||
|
||||
const [
|
||||
isSubdomainChangeConfirmationModalOpen,
|
||||
setIsSubdomainChangeConfirmationModalOpen,
|
||||
] = useState(false);
|
||||
const { openModal } = useModal();
|
||||
|
||||
const form = useForm<{
|
||||
subdomain: string;
|
||||
@ -201,7 +201,7 @@ export const SettingsDomain = () => {
|
||||
isDefined(values.subdomain) &&
|
||||
values.subdomain !== currentWorkspace.subdomain
|
||||
) {
|
||||
setIsSubdomainChangeConfirmationModalOpen(true);
|
||||
openModal(SUBDOMAIN_CHANGE_CONFIRMATION_MODAL_ID);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -240,10 +240,9 @@ export const SettingsDomain = () => {
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
<ConfirmationModal
|
||||
isOpen={isSubdomainChangeConfirmationModalOpen}
|
||||
modalId={SUBDOMAIN_CHANGE_CONFIRMATION_MODAL_ID}
|
||||
title={t`Change subdomain?`}
|
||||
subtitle={t`You're about to change your workspace subdomain. This action will log out all users.`}
|
||||
setIsOpen={setIsSubdomainChangeConfirmationModalOpen}
|
||||
onConfirmClick={() => {
|
||||
const values = form.getValues();
|
||||
currentWorkspace &&
|
||||
|
||||
Reference in New Issue
Block a user