11import type { IconName , IconProp } from '@fortawesome/fontawesome-svg-core' ;
2- import React , { Fragment , useMemo , useRef , useState } from 'react' ;
2+ import React , { Fragment , useEffect , useMemo , useRef , useState } from 'react' ;
33import {
44 Button ,
55 Dialog ,
@@ -62,9 +62,11 @@ export const WorkspaceEnvironmentsEditModal = ({ onClose }: { onClose: () => voi
6262 const updateEnvironmentFetcher = useEnvironmentUpdateActionFetcher ( ) ;
6363 const duplicateEnvironmentFetcher = useEnvironmentDuplicateActionFetcher ( ) ;
6464 const { toggleEnvironmentType } = useToggleEnvironmentType ( ) ;
65+ const pendingUpdateRef = useRef ( false ) ;
6566
6667 const { baseEnvironment, activeEnvironment, subEnvironments, activeProject, activeWorkspaceMeta } = routeData ;
6768 const [ selectedEnvironmentId , setSelectedEnvironmentId ] = useState < string > ( activeEnvironment . _id ) ;
69+ const [ hasPendingUpdate , setHasPendingUpdate ] = useState ( false ) ;
6870 const isUsingInsomniaCloudSync = Boolean (
6971 models . project . isRemoteProject ( activeProject ) && ! activeWorkspaceMeta ?. gitRepositoryId ,
7072 ) ;
@@ -169,6 +171,8 @@ export const WorkspaceEnvironmentsEditModal = ({ onClose }: { onClose: () => voi
169171
170172 const handleEnvironmentChange = ( value : EnvironmentInfo ) => {
171173 if ( environmentEditorRef . current ?. isValid ( ) && selectedEnvironment ) {
174+ pendingUpdateRef . current = true ;
175+ setHasPendingUpdate ( true ) ;
172176 const { object, propertyOrder } = value ;
173177
174178 updateEnvironmentFetcher . submit ( {
@@ -186,6 +190,8 @@ export const WorkspaceEnvironmentsEditModal = ({ onClose }: { onClose: () => voi
186190
187191 const handleKVPairChange = ( kvPairData : EnvironmentKvPairData [ ] ) => {
188192 if ( selectedEnvironment ) {
193+ pendingUpdateRef . current = true ;
194+ setHasPendingUpdate ( true ) ;
189195 const environmentData = getDataFromKVPair ( kvPairData ) ;
190196 updateEnvironmentFetcher . submit ( {
191197 organizationId,
@@ -200,6 +206,24 @@ export const WorkspaceEnvironmentsEditModal = ({ onClose }: { onClose: () => voi
200206 } ) ;
201207 }
202208 } ;
209+ useEffect ( ( ) => {
210+ if ( pendingUpdateRef . current && updateEnvironmentFetcher . state === 'idle' ) {
211+ pendingUpdateRef . current = false ;
212+ setHasPendingUpdate ( false ) ;
213+ }
214+ } , [ updateEnvironmentFetcher . state ] ) ;
215+ const isEnvironmentMutationPending =
216+ hasPendingUpdate ||
217+ createEnvironmentFetcher . state !== 'idle' ||
218+ deleteEnvironmentFetcher . state !== 'idle' ||
219+ duplicateEnvironmentFetcher . state !== 'idle' ||
220+ updateEnvironmentFetcher . state !== 'idle' ;
221+ const requestClose = ( close : ( ) => void ) => {
222+ if ( pendingUpdateRef . current || isEnvironmentMutationPending ) {
223+ return ;
224+ }
225+ close ( ) ;
226+ } ;
203227 const environmentsDragAndDrop = useDragAndDrop ( {
204228 getItems : keys => [ ...keys ] . map ( key => ( { 'text/plain' : key . toString ( ) } ) ) ,
205229 onReorder ( e ) {
@@ -249,26 +273,36 @@ export const WorkspaceEnvironmentsEditModal = ({ onClose }: { onClose: () => voi
249273 < ModalOverlay
250274 isOpen
251275 onOpenChange = { isOpen => {
252- ! isOpen && onClose ( ) ;
276+ if ( ! isOpen && ! pendingUpdateRef . current && ! isEnvironmentMutationPending ) {
277+ onClose ( ) ;
278+ }
253279 } }
254280 className = "fixed top-0 left-0 z-10 flex h-(--visual-viewport-height) w-full items-center justify-center bg-black/30"
255281 >
256282 < Modal
257283 onOpenChange = { isOpen => {
258- ! isOpen && onClose ( ) ;
284+ if ( ! isOpen && ! pendingUpdateRef . current && ! isEnvironmentMutationPending ) {
285+ onClose ( ) ;
286+ }
259287 } }
260288 className = "flex h-[calc(100%-var(--padding-xl))] w-[calc(100%-var(--padding-xl))] flex-col rounded-md border border-solid border-(--hl-sm) bg-(--color-bg) p-(--padding-lg) text-(--color-font)"
261289 >
262- < Dialog className = "flex h-full flex-1 flex-col overflow-hidden outline-hidden" >
290+ < Dialog
291+ data-testid = "WorkspaceEnvironmentsDialog"
292+ data-save-state = { isEnvironmentMutationPending ? 'saving' : 'idle' }
293+ aria-busy = { isEnvironmentMutationPending }
294+ className = "flex h-full flex-1 flex-col overflow-hidden outline-hidden"
295+ >
263296 { ( { close } ) => (
264297 < div className = "flex h-full flex-1 flex-col gap-4 overflow-hidden" >
265298 < div className = "flex items-center justify-between gap-2" >
266299 < Heading slot = "title" className = "text-2xl" >
267300 Manage Environments
268301 </ Heading >
269302 < Button
303+ isDisabled = { isEnvironmentMutationPending }
270304 className = "flex aspect-square h-6 shrink-0 items-center justify-center rounded-xs text-sm text-(--color-font) ring-1 ring-transparent transition-all hover:bg-(--hl-xs) focus:ring-(--hl-md) focus:ring-inset aria-pressed:bg-(--hl-sm)"
271- onPress = { close }
305+ onPress = { ( ) => requestClose ( close ) }
272306 >
273307 < Icon icon = "x" />
274308 </ Button >
@@ -551,9 +585,15 @@ export const WorkspaceEnvironmentsEditModal = ({ onClose }: { onClose: () => voi
551585 * Environment data can be used for < a href = { docsTemplateTags } > Nunjucks Templating</ a > in your
552586 requests.
553587 </ p >
588+ { isEnvironmentMutationPending && (
589+ < p data-testid = "WorkspaceEnvironmentsSaveStatus" className = "text-sm italic" >
590+ Saving environments...
591+ </ p >
592+ ) }
554593 </ div >
555594 < Button
556- onPress = { close }
595+ isDisabled = { isEnvironmentMutationPending }
596+ onPress = { ( ) => requestClose ( close ) }
557597 className = "rounded-xs border border-solid border-(--hl-md) px-3 py-2 text-(--color-font) transition-colors hover:no-underline"
558598 >
559599 Close
0 commit comments