Skip to content

Commit 872d923

Browse files
edisileCopilot
andauthored
Merge publisher layouts (#5400)
* Merge publisher layouts (pt1): Portal helpers and move Brand store snaps (#5398) * feature: add portal helpers * chore: move brand store snaps page to Publisher layout * fix: remove redundant fragments * fix: better error check in useMergeRefs * Merge publisher layouts (pt2): account details page (#5401) * chore: move account details page to Publisher layout * fix: typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * chore: move brand store members page to Publisher layout (#5408) * chore: move brand store settings page to Publisher layout (#5409) * chore: move signing keys page to Publisher layout (#5410) * chore: move signing keys page to Publisher layout * fix: test * chore: move models page to Publisher layout (#5412) * chore: move Model details page to Publisher layout (#5413) * chore: move Policy page to Publisher layout (#5415) * chore: move Policy page to Publisher layout * fix: tests * fix: Delete policy modal button * chore: cleanup store not found logic (#5418) * chore: cleanup store not found logic * fix: route for redirect when visiting /admin/:id -> /admin/:id/snaps * chore: rename portal componen and linting --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent c99ce5d commit 872d923

18 files changed

Lines changed: 1485 additions & 1482 deletions

File tree

static/js/publisher/layouts/BrandStoreLayout.tsx renamed to static/js/publisher/components/BrandStoreRoute/BrandStoreRoute.tsx

Lines changed: 8 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,14 @@
1-
import { applyTheme, Icon, loadTheme } from "@canonical/react-components";
1+
import { Spinner } from "@canonical/react-components";
22
import { useSetAtom } from "jotai";
33
import { useEffect } from "react";
44
import { Outlet, useLocation, useNavigate, useParams } from "react-router-dom";
55

6-
import Navigation from "../components/Navigation";
7-
import { useBrandStores } from "../hooks";
8-
import StoreNotFound from "../pages/StoreNotFound";
9-
import { brandStoresState } from "../state/brandStoreState";
10-
import { Store } from "../types/shared";
11-
import useSideNavigationData from "../hooks/useSideNavigationData";
12-
13-
// TODO: get rid of this file and create a common layout for all the pages in the app
14-
15-
function BrandStoreLoader() {
16-
return (
17-
<div className="l-application">
18-
<Navigation />
19-
20-
<main className="l-main">
21-
<div className="p-panel--loading">
22-
<div className="p-panel__content">
23-
<div className="u-fixed-width">
24-
<Icon name="spinner" className="u-animation--spin" />
25-
&nbsp;Loading...
26-
</div>
27-
</div>
28-
</div>
29-
</main>
30-
</div>
31-
);
32-
}
33-
34-
function BrandStoreLayout() {
35-
useSideNavigationData();
6+
import { useBrandStores } from "../../hooks";
7+
import StoreNotFound from "../../pages/StoreNotFound";
8+
import { brandStoresState } from "../../state/brandStoreState";
9+
import { Store } from "../../types/shared";
3610

11+
function BrandStoreRoute() {
3712
const location = useLocation();
3813
const navigate = useNavigate();
3914
const { data: brandStoresList, isLoading } = useBrandStores();
@@ -46,11 +21,6 @@ function BrandStoreLayout() {
4621
(store: Store) => store.id === storeId,
4722
);
4823

49-
useEffect(() => {
50-
const theme = loadTheme();
51-
applyTheme(theme);
52-
}, []);
53-
5424
const setBrandStores = useSetAtom(brandStoresState);
5525

5626
useEffect(() => {
@@ -77,8 +47,8 @@ function BrandStoreLayout() {
7747
<StoreNotFound />
7848
)
7949
) : (
80-
<BrandStoreLoader />
50+
<Spinner text="Loading..." />
8151
);
8252
}
8353

84-
export default BrandStoreLayout;
54+
export default BrandStoreRoute;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { useCallback } from "react";
2+
3+
export default function useMergeRefs<T>(
4+
...refs: (React.Ref<T> | undefined)[]
5+
): React.Ref<T> | React.RefCallback<T> {
6+
const mergedRefs = useCallback<React.RefCallback<T>>(
7+
(element) => {
8+
for (const ref of refs) {
9+
if (!ref) continue;
10+
11+
if (typeof ref === "function") {
12+
ref(element);
13+
} else if (Object.hasOwn(ref, "current")) {
14+
(ref as React.RefObject<T | null>).current = element;
15+
} else {
16+
console.error(
17+
`useMergeRefs: can't merge object "${JSON.stringify(ref) || ref?.toString()}" because it's not a ref`,
18+
);
19+
}
20+
}
21+
},
22+
[...refs],
23+
);
24+
25+
return mergedRefs;
26+
}

static/js/publisher/index.tsx

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1+
import * as Sentry from "@sentry/react";
12
import { Provider as JotaiProvider } from "jotai";
23
import { createRoot } from "react-dom/client";
34
import { QueryClient, QueryClientProvider } from "react-query";
4-
import { BrowserRouter, Route, Routes } from "react-router-dom";
5-
import * as Sentry from "@sentry/react";
5+
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
66

7-
import BrandStoreLayout from "./layouts/BrandStoreLayout";
7+
import BrandStoreRoute from "./components/BrandStoreRoute/BrandStoreRoute";
88
import PublisherLayout from "./layouts/PublisherLayout";
99
import AccountDetails from "./pages/AccountDetails";
1010
import AccountSnaps from "./pages/AccountSnaps";
@@ -52,7 +52,6 @@ root.render(
5252
<QueryClientProvider client={queryClient}>
5353
<BrowserRouter>
5454
<Routes>
55-
{/* START publisher routes */}
5655
<Route element={<PublisherLayout />}>
5756
<Route path=":snapId">
5857
<Route path="publicise" element={<Publicise />} />
@@ -88,31 +87,27 @@ root.render(
8887
path="validation-sets/:validationSetId"
8988
element={<ValidationSet />}
9089
/>
91-
</Route>
92-
{/* END publisher routes */}
9390

94-
{/* START brand store routes */}
95-
<Route path="admin" element={<BrandStoreLayout />}>
96-
<Route path="account" element={<AccountDetails />} />
97-
<Route path=":id">
98-
<Route index element={<Snaps />} />
91+
<Route path="admin/account" element={<AccountDetails />} />
92+
93+
<Route path="admin/:id" element={<BrandStoreRoute />}>
94+
<Route index element={<Navigate to="snaps" />} />
9995
<Route path="snaps" element={<Snaps />} />
10096
<Route path="members" element={<Members />} />
10197
<Route path="settings" element={<BrandStoreSettings />} />
102-
<Route path="models" element={<Models />} />
103-
<Route path="models/create" element={<Models />} />
104-
<Route path="models/:model_id" element={<Model />} />
105-
<Route path="models/:model_id/policies" element={<Policies />} />
106-
<Route
107-
path="models/:model_id/policies/create"
108-
element={<Policies />}
109-
/>
11098
<Route path="signing-keys" element={<SigningKeys />} />
11199
<Route path="signing-keys/create" element={<SigningKeys />} />
100+
<Route path="models">
101+
<Route index element={<Models />} />
102+
<Route path="create" element={<Models />} />
103+
<Route path=":model_id">
104+
<Route index element={<Model />} />
105+
<Route path="policies" element={<Policies />} />
106+
<Route path="policies/create" element={<Policies />} />
107+
</Route>
108+
</Route>
112109
</Route>
113110
</Route>
114-
{/* END brand store routes */}
115-
{/* TODO: merge the two layouts */}
116111
</Routes>
117112
</BrowserRouter>
118113
</QueryClientProvider>

static/js/publisher/layouts/PublisherLayout.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,25 @@ import { Outlet } from "react-router-dom";
1212

1313
import Navigation from "../components/Navigation";
1414
import useSideNavigationData from "../hooks/useSideNavigationData";
15+
import { usePortalExit } from "../pages/Portals/Portals";
16+
import useMergeRefs from "../hooks/useMergeRefs";
1517

1618
function PublisherLayout(): React.JSX.Element {
1719
useSideNavigationData();
20+
// merge two ref callbacks into one
21+
const mergedPortalsRef = useMergeRefs<HTMLDivElement>(
22+
usePortalExit("aside"),
23+
usePortalExit("modal"),
24+
);
25+
const notificationPortalRef = usePortalExit("notification");
1826

1927
useEffect(() => {
2028
const theme = loadTheme();
2129
applyTheme(theme);
2230
}, []);
2331

2432
return (
25-
<Application>
33+
<Application ref={mergedPortalsRef}>
2634
<Navigation />
2735
<AppMain>
2836
<Panel>
@@ -33,6 +41,8 @@ function PublisherLayout(): React.JSX.Element {
3341
</Row>
3442
</Panel>
3543
</AppMain>
44+
45+
<div className="p-notification-center" ref={notificationPortalRef}></div>
3646
</Application>
3747
);
3848
}

0 commit comments

Comments
 (0)