diff --git a/site/src/pages/TemplatePage/TemplateRedirectController.test.tsx b/site/src/pages/TemplatePage/TemplateRedirectController.test.tsx new file mode 100644 index 0000000000000..459e94fe911f0 --- /dev/null +++ b/site/src/pages/TemplatePage/TemplateRedirectController.test.tsx @@ -0,0 +1,40 @@ +import { waitFor } from "@testing-library/react"; +import { API } from "api/api"; +import * as M from "testHelpers/entities"; +import { renderWithAuth } from "testHelpers/renderHelpers"; +import { TemplateRedirectController } from "./TemplateRedirectController"; + +const renderTemplateRedirectController = (route: string) => { + return renderWithAuth(, { + route, + path: "/templates/:organization?/:template", + }); +}; + +it("redirects from multi-org to single-org", async () => { + const { router } = renderTemplateRedirectController( + `/templates/${M.MockTemplate.organization_name}/${M.MockTemplate.name}`, + ); + + await waitFor(() => + expect(router.state.location.pathname).toEqual( + `/templates/${M.MockTemplate.name}`, + ), + ); +}); + +it("redirects from single-org to multi-org", async () => { + jest + .spyOn(API, "getOrganizations") + .mockResolvedValueOnce([M.MockDefaultOrganization, M.MockOrganization2]); + + const { router } = renderTemplateRedirectController( + `/templates/${M.MockTemplate.name}`, + ); + + await waitFor(() => + expect(router.state.location.pathname).toEqual( + `/templates/${M.MockDefaultOrganization.name}/${M.MockTemplate.name}`, + ), + ); +}); diff --git a/site/src/pages/TemplatePage/TemplateRedirectController.tsx b/site/src/pages/TemplatePage/TemplateRedirectController.tsx new file mode 100644 index 0000000000000..66da3b6ea0bab --- /dev/null +++ b/site/src/pages/TemplatePage/TemplateRedirectController.tsx @@ -0,0 +1,53 @@ +import type { FC } from "react"; +import { Navigate, Outlet, useLocation, useParams } from "react-router-dom"; +import type { Organization } from "api/typesGenerated"; +import { useDashboard } from "modules/dashboard/useDashboard"; + +export const TemplateRedirectController: FC = () => { + const { organizations, showOrganizations } = useDashboard(); + const { organization, template } = useParams() as { + organization?: string; + template: string; + }; + const location = useLocation(); + + // We redirect templates without an organization to the default organization, + // as that's likely what any links floating around expect. + if (showOrganizations && !organization) { + const extraPath = removePrefix(location.pathname, `/templates/${template}`); + + return ( + + ); + } + + // `showOrganizations` can only be false when there is a single organization, + // so it's safe to throw away the organization name. + if (!showOrganizations && organization) { + const extraPath = removePrefix( + location.pathname, + `/templates/${organization}/${template}`, + ); + + return ( + + ); + } + + return ; +}; + +const getOrganizationNameByDefault = (organizations: Organization[]) => + organizations.find((org) => org.is_default)?.name; + +// I really hate doing it this way, but React Router does not provide a better way. +const removePrefix = (self: string, prefix: string) => + self.startsWith(prefix) ? self.slice(prefix.length) : self; diff --git a/site/src/router.tsx b/site/src/router.tsx index 152c458b83fb4..75091a311cb3a 100644 --- a/site/src/router.tsx +++ b/site/src/router.tsx @@ -1,4 +1,4 @@ -import { Suspense, lazy } from "react"; +import { lazy, Suspense } from "react"; import { createBrowserRouter, createRoutesFromChildren, @@ -6,6 +6,7 @@ import { Outlet, Route, } from "react-router-dom"; +import { TemplateRedirectController } from "pages/TemplatePage/TemplateRedirectController"; import { Loader } from "./components/Loader/Loader"; import { RequireAuth } from "./contexts/auth/RequireAuth"; import { DashboardLayout } from "./modules/dashboard/DashboardLayout"; @@ -289,27 +290,29 @@ const RoutesWithSuspense = () => { const templateRouter = () => { return ( - }> - } /> - } /> - } /> - } /> - } /> - } /> - + }> + }> + } /> + } /> + } /> + } /> + } /> + } /> + - } /> + } /> - }> - } /> - } /> - } /> - } /> - + }> + } /> + } /> + } /> + } /> + - - - } /> + + + } /> +