Skip to content

Commit 0843232

Browse files
authored
Merge pull request #33 from oasisprotocol/mz/blockNav
Block navigation and move unsaved changes action to floating element
2 parents 1d7670c + 16b1c58 commit 0843232

File tree

4 files changed

+139
-98
lines changed

4 files changed

+139
-98
lines changed

src/main.tsx

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { StrictMode } from 'react';
22
import { createRoot } from 'react-dom/client';
3-
import { BrowserRouter, Route, Routes } from 'react-router-dom';
3+
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
44
import { WagmiProvider } from 'wagmi';
55
import { CreateContextProvider } from './pages/CreateApp/CreateContextProvider';
66
import { Landing } from './pages/Landing';
@@ -33,6 +33,53 @@ const rainbowKitTheme: Theme = {
3333
},
3434
};
3535

36+
const router = createBrowserRouter([
37+
{
38+
path: '/',
39+
element: <Landing />,
40+
},
41+
{
42+
path: '/dashboard',
43+
element: <MainLayout />,
44+
children: [
45+
{
46+
index: true,
47+
element: <Dashboard />,
48+
},
49+
{
50+
path: 'apps',
51+
element: <MyApps />,
52+
},
53+
{
54+
path: 'apps/:id',
55+
element: <AppDetails />,
56+
},
57+
{
58+
path: 'machines',
59+
element: <Machines />,
60+
},
61+
{
62+
path: 'machines/:provider/instances/:id',
63+
element: <MachinesDetails />,
64+
},
65+
],
66+
},
67+
{
68+
path: '/create',
69+
element: <Create />,
70+
},
71+
{
72+
path: '/explore',
73+
element: <MainLayout />,
74+
children: [
75+
{
76+
index: true,
77+
element: <Explore />,
78+
},
79+
],
80+
},
81+
]);
82+
3683
createRoot(document.getElementById('root')!).render(
3784
<StrictMode>
3885
<WagmiProvider config={wagmiConfig}>
@@ -47,27 +94,9 @@ createRoot(document.getElementById('root')!).render(
4794
)}
4895
>
4996
<RoflAppBackendAuthProvider>
50-
<BrowserRouter>
51-
<CreateContextProvider>
52-
<Routes>
53-
<Route path="/" element={<Landing />} />
54-
<Route path="/dashboard" element={<MainLayout />}>
55-
<Route index element={<Dashboard />} />
56-
<Route path="apps" element={<MyApps />} />
57-
<Route path="apps/:id" element={<AppDetails />} />
58-
<Route path="machines" element={<Machines />} />
59-
<Route
60-
path="machines/:provider/instances/:id"
61-
element={<MachinesDetails />}
62-
/>
63-
</Route>
64-
<Route path="/create" element={<Create />}></Route>
65-
<Route path="/explore" element={<MainLayout />}>
66-
<Route index element={<Explore />} />
67-
</Route>
68-
</Routes>
69-
</CreateContextProvider>
70-
</BrowserRouter>
97+
<CreateContextProvider>
98+
<RouterProvider router={router} />
99+
</CreateContextProvider>
71100
</RoflAppBackendAuthProvider>
72101
</RainbowKitProvider>
73102
</QueryClientProvider>

src/pages/Dashboard/AppDetails/ApplyChanges.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ export const ApplyChanges: FC<ApplyChangesProps> = ({
2323
return (
2424
<Dialog>
2525
<DialogTrigger asChild>
26-
<Button disabled={disabled} className="w-full md:w-auto md:mr-8">
27-
Apply
28-
</Button>
26+
<Button disabled={disabled}>Apply</Button>
2927
</DialogTrigger>
3028
<DialogContent className="sm:max-w-[425px]">
3129
<DialogHeader>

src/pages/Dashboard/AppDetails/DiscardButton.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,7 @@ export const DiscardChanges: FC<DiscardChangesProps> = ({
2323
return (
2424
<Dialog>
2525
<DialogTrigger asChild>
26-
<Button
27-
disabled={disabled}
28-
variant="destructive"
29-
className="w-full md:w-auto md:ml-8"
30-
>
26+
<Button disabled={disabled} variant="destructive">
3127
Discard
3228
</Button>
3329
</DialogTrigger>

src/pages/Dashboard/AppDetails/index.tsx

Lines changed: 86 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@ import {
1515
type RoflAppSecrets,
1616
} from '../../../nexus/api';
1717
import { useNetwork } from '../../../hooks/useNetwork';
18-
import { useParams } from 'react-router-dom';
18+
import { useParams, useBlocker } from 'react-router-dom';
1919
import { Skeleton } from '@oasisprotocol/ui-library/src/components/ui/skeleton';
2020
import { trimLongString } from '../../../utils/trimLongString';
2121
import { type ViewMetadataState, type ViewSecretsState } from './types';
2222
import { DiscardChanges } from './DiscardButton';
2323
import { ApplyChanges } from './ApplyChanges';
24+
import { cn } from '@oasisprotocol/ui-library/src/lib/utils';
2425

2526
function 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

Comments
 (0)