diff --git a/packages/insomnia/src/ui/components/dropdowns/git-project-sync-dropdown.tsx b/packages/insomnia/src/ui/components/dropdowns/git-project-sync-dropdown.tsx index 9153a026d5..70e0a4836a 100644 --- a/packages/insomnia/src/ui/components/dropdowns/git-project-sync-dropdown.tsx +++ b/packages/insomnia/src/ui/components/dropdowns/git-project-sync-dropdown.tsx @@ -37,7 +37,11 @@ import { showModal } from '../modals'; import { GitProjectBranchesModal } from '../modals/git-project-branches-modal'; import { GitProjectLogModal } from '../modals/git-project-log-modal'; import { GitProjectMigrationModal } from '../modals/git-project-migration-modal'; -import { GitProjectStagingModal, type StagingModalMode, StagingModalModes } from '../modals/git-project-staging-modal'; +import { + GitProjectStagingModal, + type GitProjectStagingModalCallbackProps, + StagingModalModes, +} from '../modals/git-project-staging-modal'; import { GitPullRequiredModal } from '../modals/git-pull-required-modal'; import { SyncMergeModal } from '../modals/sync-merge-modal'; import { showToast } from '../toast-notification'; @@ -54,9 +58,7 @@ export const GitProjectSyncDropdown: FC = ({ gitRepository, activeProject const [isGitBranchesModalOpen, setIsGitBranchesModalOpen] = useState(false); const [isGitLogModalOpen, setIsGitLogModalOpen] = useState(false); - const [isGitStagingModalOpen, setIsGitStagingModalOpen] = useState(false); const [isGitPullRequiredModalOpen, setIsGitPullRequiredModalOpen] = useState(false); - const [stagingMode, setStagingMode] = useState(StagingModalModes.default); const [isMigrationModalOpen, setIsMigrationModalOpen] = useState(false); const prevHadPullError = useRef(false); @@ -257,6 +259,29 @@ export const GitProjectSyncDropdown: FC = ({ gitRepository, activeProject ? gitRepoDataFetcher.data : { branches: [], branch: '' }; + const closeGitProjectStagingModalRef = useRef<(() => void) | null>(null); + + const gitProjectStagingModalCallbackPropsRef = useRef(null!); + gitProjectStagingModalCallbackPropsRef.current = { + onPullAfterCommit: async () => { + await handlePull(); + fetchStatus(); + }, + onPushAfterPull: async () => { + setIsGitPullRequiredModalOpen(false); + const pullResult = await handlePull(); + if (pullResult && pullResult.success) { + handlePush({ force: false }); + } + prevHadPullError.current = true; + fetchStatus(); + }, + onClose: () => { + prevHadPullError.current = false; + fetchStatus(); + }, + }; + const handlePull = async () => { try { setIsPulling(true); @@ -274,8 +299,10 @@ export const GitProjectSyncDropdown: FC = ({ gitRepository, activeProject pullResult.errors.includes(GitVCSOperationErrors.UncommittedChangesError) ) { setIsPulling(false); - setStagingMode(StagingModalModes.commitAndPull); - setIsGitStagingModalOpen(true); + closeGitProjectStagingModalRef.current = showModal(GitProjectStagingModal, { + mode: StagingModalModes.commitAndPull, + callbackRef: gitProjectStagingModalCallbackPropsRef, + }); } else if ('errors' in pullResult && pullResult.errors) { showToast({ icon, @@ -333,7 +360,7 @@ export const GitProjectSyncDropdown: FC = ({ gitRepository, activeProject }); }, onCancelUnresolved: () => { - setIsGitStagingModalOpen(false); + closeGitProjectStagingModalRef.current?.(); setIsPulling(false); showToast({ icon, @@ -394,7 +421,12 @@ export const GitProjectSyncDropdown: FC = ({ gitRepository, activeProject icon: 'check', isDisabled: status?.localChanges === 0, label: 'Commit', - action: () => setIsGitStagingModalOpen(true), + action: () => { + closeGitProjectStagingModalRef.current = showModal(GitProjectStagingModal, { + mode: StagingModalModes.default, + callbackRef: gitProjectStagingModalCallbackPropsRef, + }); + }, }, { id: 'pull', @@ -658,36 +690,7 @@ export const GitProjectSyncDropdown: FC = ({ gitRepository, activeProject /> )} {isGitLogModalOpen && gitRepository && setIsGitLogModalOpen(false)} />} - {isGitStagingModalOpen && gitRepository && ( - { - setIsGitStagingModalOpen(false); - setStagingMode(StagingModalModes.default); - await handlePull(); - fetchStatus(); - }} - onPushAfterPull={async () => { - setIsGitPullRequiredModalOpen(false); - const pullResult = await handlePull(); - - if (pullResult && pullResult.success) { - handlePush({ force: false }); - } - - prevHadPullError.current = true; - fetchStatus(); - }} - onClose={() => { - prevHadPullError.current = false; - - setIsGitStagingModalOpen(false); - setStagingMode(StagingModalModes.default); - fetchStatus(); - }} - /> - )} {isMigrationModalOpen && gitRepository && legacyInsomniaWorkspace && ( = ({ ); }; -export const GitProjectStagingModal: FC<{ - mode?: StagingModalMode; +export interface GitProjectStagingModalCallbackProps { onClose: () => void; onPullAfterCommit: () => void; onPushAfterPull: () => void; -}> = ({ mode = StagingModalModes.default, onClose, onPullAfterCommit, onPushAfterPull }) => { +} + +export interface GitProjectStagingModalOptions { + mode?: StagingModalMode; + /* Why is callbackRef a ref object? + * The callbacks passed to the modal (onClose, onPullAfterCommit, onPushAfterPull) may change after the show function is called. + * If we were to pass the callbacks directly, the modal would capture the initial callbacks and not reflect any updates to them. + * By using a ref object, we can ensure that the modal always has access to the latest version of the callbacks, even if they change after the modal is shown. + */ + callbackRef: React.MutableRefObject; +} + +export interface GitProjectStagingModalHandle { + show: (options: GitProjectStagingModalOptions) => void; + hide: () => void; +} + +export const GitProjectStagingModal = forwardRef((_, ref) => { + const [isOpen, setIsOpen] = useState(false); + const [modalOptions, setModalOptions] = useState(null); + + const hide = useCallback(() => { + setIsOpen(false); + setModalOptions(null); + }, []); + + useImperativeHandle(ref, () => ({ + show: ({ mode: newMode = StagingModalModes.default, callbackRef }) => { + setModalOptions({ mode: newMode, callbackRef }); + setIsOpen(true); + }, + hide, + })); + + const onClose = useCallback(() => { + modalOptions?.callbackRef.current.onClose(); + hide(); + }, [hide, modalOptions]); + + const onPullAfterCommit = useCallback(() => { + modalOptions?.callbackRef.current.onPullAfterCommit(); + hide(); + }, [hide, modalOptions]); + + const onPushAfterPull = useCallback(() => { + modalOptions?.callbackRef.current.onPushAfterPull(); + }, [modalOptions]); + + return ( + isOpen && ( + + ) + ); +}); +GitProjectStagingModal.displayName = 'GitProjectStagingModal'; + +const OriginalGitProjectStagingModal: FC< + { + mode?: StagingModalMode; + } & GitProjectStagingModalCallbackProps +> = ({ mode = StagingModalModes.default, onClose, onPullAfterCommit, onPushAfterPull }) => { const { projectId } = useParams() as { projectId: string }; const [commitGenerationKey, setCommitGenerationKey] = useState(0); diff --git a/packages/insomnia/src/ui/modals.tsx b/packages/insomnia/src/ui/modals.tsx index c99a8580ee..9cd8648711 100644 --- a/packages/insomnia/src/ui/modals.tsx +++ b/packages/insomnia/src/ui/modals.tsx @@ -10,6 +10,7 @@ import { AskModal } from './components/modals/ask-modal'; import { CodePromptModal } from './components/modals/code-prompt-modal'; import { ErrorModal } from './components/modals/error-modal'; import { GenerateCodeModal } from './components/modals/generate-code-modal'; +import { GitProjectStagingModal } from './components/modals/git-project-staging-modal'; import { LogoutModal } from './components/modals/logout-modal'; import { NunjucksModal } from './components/modals/nunjucks-modal'; import { PromptModal } from './components/modals/prompt-modal'; @@ -58,6 +59,8 @@ const Modals = () => { registerModal(instance, 'SyncMergeModal')} /> + registerModal(instance, 'GitProjectStagingModal')} /> + registerModal(instance, 'UpgradeModal')} /> registerModal(instance, 'LogoutModal')} />