@@ -15,12 +15,13 @@ import {
1515 type RoflAppSecrets ,
1616} from '../../../nexus/api' ;
1717import { useNetwork } from '../../../hooks/useNetwork' ;
18- import { useParams } from 'react-router-dom' ;
18+ import { useParams , useBlocker } from 'react-router-dom' ;
1919import { Skeleton } from '@oasisprotocol/ui-library/src/components/ui/skeleton' ;
2020import { trimLongString } from '../../../utils/trimLongString' ;
2121import { type ViewMetadataState , type ViewSecretsState } from './types' ;
2222import { DiscardChanges } from './DiscardButton' ;
2323import { ApplyChanges } from './ApplyChanges' ;
24+ import { cn } from '@oasisprotocol/ui-library/src/lib/utils' ;
2425
2526function setDefaultMetadataViewState (
2627 metadata : RoflAppMetadata | undefined = { }
@@ -62,6 +63,12 @@ export const AppDetails: FC = () => {
6263 const { data, isLoading, isFetched } = roflAppQuery ;
6364 const roflApp = data ?. data ;
6465
66+ const blocker = useBlocker (
67+ ( { currentLocation, nextLocation } ) =>
68+ ( viewMetadataState . isDirty || viewSecretsState . isDirty ) &&
69+ currentLocation . pathname !== nextLocation . pathname
70+ ) ;
71+
6572 useEffect ( ( ) => {
6673 if ( roflApp ) {
6774 setViewMetadataState ( {
@@ -83,72 +90,82 @@ export const AppDetails: FC = () => {
8390 </ div >
8491 ) }
8592 { isFetched && roflApp && (
86- < div >
87- < Tabs defaultValue = "details" >
88- < div className = "flex flex-col md:flex-row md:items-center justify-between gap-4 border-b py-5 mb-5" >
89- < div className = "flex items-center gap-2" >
90- < h1 className = "text-2xl font-bold" >
91- < >
92- { viewMetadataState . metadata . name ||
93- trimLongString ( roflApp . id ) }
94- </ >
95- </ h1 >
96- < AppStatusIcon hasActiveInstances removed = { false } />
93+ < >
94+ < div
95+ className = { cn (
96+ 'flex items-center gap-2 px-4 py-2 rounded-md bg-card absolute right-6 bottom-16' ,
97+ ! viewMetadataState . isDirty &&
98+ ! viewSecretsState . isDirty &&
99+ 'hidden' ,
100+ blocker . state === 'blocked' && 'animate-bounce'
101+ ) }
102+ >
103+ < span className = "text-sm font-semibold pr-6" > Unsaved Changes</ span >
104+ < DiscardChanges
105+ disabled = { ! viewMetadataState . isDirty && ! viewSecretsState . isDirty }
106+ onConfirm = { ( ) => {
107+ setViewMetadataState ( {
108+ ...setDefaultMetadataViewState ( roflApp . metadata ) ,
109+ } ) ;
110+ setViewSecretsState ( {
111+ ...setDefaultSecretsViewState ( roflApp . secrets ) ,
112+ } ) ;
113+ blocker . reset ( ) ;
114+ } }
115+ />
116+ < ApplyChanges
117+ disabled = { ! viewMetadataState . isDirty && ! viewSecretsState . isDirty }
118+ onConfirm = { ( ) => {
119+ setViewMetadataState ( ( prev ) => ( {
120+ ...prev ,
121+ isDirty : false ,
122+ } ) ) ;
123+ setViewSecretsState ( ( prev ) => ( {
124+ ...prev ,
125+ isDirty : false ,
126+ } ) ) ;
127+ blocker . reset ( ) ;
128+ roflAppQuery . refetch ( ) ;
129+ } }
130+ />
131+ </ div >
132+ < div >
133+ < Tabs defaultValue = "details" >
134+ < div className = "flex flex-col md:flex-row md:items-center justify-between gap-4 border-b py-5 mb-5" >
135+ < div className = "flex items-center gap-2" >
136+ < h1 className = "text-2xl font-bold" >
137+ < >
138+ { viewMetadataState . metadata . name ||
139+ trimLongString ( roflApp . id ) }
140+ </ >
141+ </ h1 >
142+ < AppStatusIcon hasActiveInstances removed = { false } />
143+ </ div >
144+ < div className = "flex flex-wrap gap-3" >
145+ < TabsList className = "w-full md:w-auto" >
146+ < TabsTrigger value = "details" > Details</ TabsTrigger >
147+ < TabsTrigger value = "secrets" > Secrets</ TabsTrigger >
148+ < TabsTrigger value = "compose" > Compose</ TabsTrigger >
149+ </ TabsList >
150+ </ div >
97151 </ div >
98- < div className = "flex flex-wrap gap-3" >
99- < DiscardChanges
100- disabled = {
101- ! viewMetadataState . isDirty && ! viewSecretsState . isDirty
102- }
103- onConfirm = { ( ) => {
104- setViewMetadataState ( {
105- ...setDefaultMetadataViewState ( roflApp . metadata ) ,
106- } ) ;
107- setViewSecretsState ( {
108- ...setDefaultSecretsViewState ( roflApp . secrets ) ,
109- } ) ;
110- } }
152+ < TabsContent value = "details" >
153+ < AppMetadata
154+ id = { roflApp . id }
155+ editableState = { viewMetadataState . metadata }
156+ policy = { roflApp . policy }
157+ setViewMetadataState = { setViewMetadataState }
111158 />
112- < ApplyChanges
113- disabled = {
114- ! viewMetadataState . isDirty && ! viewSecretsState . isDirty
115- }
116- onConfirm = { ( ) => {
117- setViewMetadataState ( ( prev ) => ( {
118- ...prev ,
119- isDirty : false ,
120- } ) ) ;
121- setViewSecretsState ( ( prev ) => ( {
122- ...prev ,
123- isDirty : false ,
124- } ) ) ;
125- roflAppQuery . refetch ( ) ;
126- } }
159+ </ TabsContent >
160+ < TabsContent value = "secrets" >
161+ < AppSecrets
162+ secrets = { viewSecretsState . secrets }
163+ setViewSecretsState = { setViewSecretsState }
127164 />
128- < TabsList className = "w-full md:w-auto" >
129- < TabsTrigger value = "details" > Details</ TabsTrigger >
130- < TabsTrigger value = "secrets" > Secrets</ TabsTrigger >
131- < TabsTrigger value = "compose" > Compose</ TabsTrigger >
132- </ TabsList >
133- </ div >
134- </ div >
135- < TabsContent value = "details" >
136- < AppMetadata
137- id = { roflApp . id }
138- editableState = { viewMetadataState . metadata }
139- policy = { roflApp . policy }
140- setViewMetadataState = { setViewMetadataState }
141- />
142- </ TabsContent >
143- < TabsContent value = "secrets" >
144- < AppSecrets
145- secrets = { viewSecretsState . secrets }
146- setViewSecretsState = { setViewSecretsState }
147- />
148- </ TabsContent >
149- < TabsContent value = "compose" >
150- < YamlCode
151- data = { `
165+ </ TabsContent >
166+ < TabsContent value = "compose" >
167+ < YamlCode
168+ data = { `
152169 services:
153170 ollama:
154171 image: "docker.io/ollama/ollama"
@@ -200,10 +217,11 @@ export const AppDetails: FC = () => {
200217 condition: service_completed_successfully
201218
202219 ` }
203- />
204- </ TabsContent >
205- </ Tabs >
206- </ div >
220+ />
221+ </ TabsContent >
222+ </ Tabs >
223+ </ div >
224+ </ >
207225 ) }
208226 </ >
209227 ) ;
0 commit comments