From c0227ef231826346440089a9d8c63a988814a566 Mon Sep 17 00:00:00 2001 From: Michiel Date: Wed, 21 Jan 2026 19:37:38 +0100 Subject: [PATCH 1/5] Make links and nav buttons proper links --- frontend/src/Link.tsx | 15 ++-- frontend/src/components/EditButton.tsx | 7 +- .../admin/OrganizationsOverview.tsx | 6 +- frontend/src/hooks/useRemails.ts | 9 ++- frontend/src/layout/Breadcrumbs.tsx | 14 ++-- frontend/src/layout/NavBar.tsx | 69 ++++++++++++------- frontend/src/layout/Tabs.tsx | 11 ++- frontend/src/router.ts | 6 +- 8 files changed, 97 insertions(+), 40 deletions(-) diff --git a/frontend/src/Link.tsx b/frontend/src/Link.tsx index 030422a0..8a1ae304 100644 --- a/frontend/src/Link.tsx +++ b/frontend/src/Link.tsx @@ -1,4 +1,4 @@ -import { Anchor } from "@mantine/core"; +import { Anchor, AnchorProps } from "@mantine/core"; import { useRemails } from "./hooks/useRemails.ts"; import { RouteParams } from "./router.ts"; import { RouteName } from "./routes.ts"; @@ -8,18 +8,25 @@ interface LinkProps { params?: RouteParams; underline?: "always" | "hover" | "never"; children: React.ReactNode; + style?: AnchorProps; } -export function Link({ to, params, underline, children }: LinkProps) { - const { navigate } = useRemails(); +export function Link({ to, params, underline, children, style }: LinkProps) { + const { navigate, getRoute } = useRemails(); const onClick = (e: React.MouseEvent) => { + if (e.defaultPrevented || e.ctrlKey || e.metaKey) { + return; + } + e.preventDefault(); navigate(to, params); }; + const route = getRoute(to ?? "default", params); + return ( - + {children} ); diff --git a/frontend/src/components/EditButton.tsx b/frontend/src/components/EditButton.tsx index 6f775dcb..772ce242 100644 --- a/frontend/src/components/EditButton.tsx +++ b/frontend/src/components/EditButton.tsx @@ -5,12 +5,15 @@ import { RouteName } from "../routes.ts"; import { RouteParams } from "../router.ts"; export default function EditButton({ route, params }: { route: RouteName; params: RouteParams }) { - const { navigate } = useRemails(); + const { navigate, getRoute } = useRemails(); return ( + ); } diff --git a/frontend/src/layout/NavBar.tsx b/frontend/src/layout/NavBar.tsx index b10b67c7..e8391e35 100644 --- a/frontend/src/layout/NavBar.tsx +++ b/frontend/src/layout/NavBar.tsx @@ -1,9 +1,42 @@ -import { NavLink } from "@mantine/core"; +import { BoxProps, NavLink as MantineNavLink } from "@mantine/core"; import { IconChartBar, IconGavel, IconServer, IconSettings, IconWorldWww } from "@tabler/icons-react"; import { useRemails } from "../hooks/useRemails.ts"; import { useDisclosure } from "@mantine/hooks"; import { NewOrganization } from "../components/organizations/NewOrganization.tsx"; import OrgDropDown from "./OrgDropDown.tsx"; +import { RouteName } from "../routes.ts"; + +interface NavLinkProps { + label: string; + route: RouteName; + active: boolean; + close: () => void; + leftSection?: React.ReactNode; + style?: BoxProps; +} + +function NavLink({ label, route, active, close, leftSection, style }: NavLinkProps) { + const { navigate, getRoute } = useRemails(); + + return ( + { + if (e.defaultPrevented || e.ctrlKey || e.metaKey) { + return; + } + + e.preventDefault(); + navigate(route); + close(); + }} + {...style} + /> + ); +} export function NavBar({ close }: { close: () => void }) { const { @@ -20,14 +53,12 @@ export function NavBar({ close }: { close: () => void }) { <> {user.global_role === "admin" && ( } - onClick={() => { - navigate("admin"); - close(); - }} + style={{ mb: "md" }} /> )} @@ -42,41 +73,33 @@ export function NavBar({ close }: { close: () => void }) { } - onClick={() => { - navigate("projects"); - close(); - }} + style={{ mt: "md" }} /> } - onClick={() => { - navigate("domains"); - close(); - }} /> } - onClick={() => { - navigate("statistics"); - close(); - }} /> } - onClick={() => { - navigate("settings"); - close(); - }} /> ); diff --git a/frontend/src/layout/Tabs.tsx b/frontend/src/layout/Tabs.tsx index d2f83114..a1b30cca 100644 --- a/frontend/src/layout/Tabs.tsx +++ b/frontend/src/layout/Tabs.tsx @@ -16,6 +16,7 @@ export default function Tabs({ tabs, keepMounted }: { tabs: Tab[]; keepMounted?: const { state: { routerState }, navigate, + getRoute, } = useRemails(); const default_route = tabs[0].route; @@ -30,7 +31,15 @@ export default function Tabs({ tabs, keepMounted }: { tabs: Tab[]; keepMounted?: {tabs.map((t) => ( - + e.preventDefault()} + size="lg" + value={t.route} + leftSection={t.icon} + key={t.route} + {...{ href: getRoute(t.route).fullPath }} + > {t.name} ))} diff --git a/frontend/src/router.ts b/frontend/src/router.ts index 1609d361..ceda0432 100644 --- a/frontend/src/router.ts +++ b/frontend/src/router.ts @@ -151,7 +151,7 @@ export class Router { return null; } - navigate(name: RouteName, params: RouteParams): FullRouterState { + navigate(name: RouteName, params: RouteParams = {}, resetParamCache = true): FullRouterState { const route = this.routes.find((route) => route.name === name); if (!route) { throw new Error(`Route with name ${name} not found`); @@ -162,7 +162,9 @@ export class Router { const query = Object.fromEntries(Object.entries(params).filter(([, v]) => v !== undefined)); const pathParams = { ...this.pathParamCache, ...query }; - this.pathParamCache = {}; + if (resetParamCache) { + this.pathParamCache = {}; + } path = path.replace(/{(\w+)}/g, (_match, key) => { const value = pathParams[key]; From 1772392f669801c9539d19645a4ed5a021c95966 Mon Sep 17 00:00:00 2001 From: Michiel Date: Fri, 23 Jan 2026 11:51:10 +0100 Subject: [PATCH 2/5] Refactor login page --- frontend/src/Login.tsx | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/frontend/src/Login.tsx b/frontend/src/Login.tsx index 49301527..e5074601 100644 --- a/frontend/src/Login.tsx +++ b/frontend/src/Login.tsx @@ -23,6 +23,14 @@ interface LoginProps { setUser: (user: User | null) => void; } +type LoginType = "login" | "register" | "reset_password"; + +const BUTTON_NAMES: { [type in LoginType]: string } = { + login: "Login", + register: "Register", + reset_password: "Reset password", +}; + export default function Login({ setUser }: LoginProps) { const { state: { routerState }, @@ -30,24 +38,11 @@ export default function Login({ setUser }: LoginProps) { redirect, } = useRemails(); - let type: "login" | "register" | "reset_password" = "login"; - let buttonName = "Login"; - - switch (routerState.params.type) { - case "register": - type = "register"; - buttonName = "Register"; - break; - case "reset_password": - type = "reset_password"; - buttonName = "Reset password"; - break; - } + const type = routerState.params.type in BUTTON_NAMES ? (routerState.params.type as LoginType) : "login"; + const buttonName = BUTTON_NAMES[type]; const [globalError, setGlobalError] = useState(null); - const xIcon = ; - const form = useForm({ initialValues: { email: "", @@ -193,7 +188,7 @@ export default function Login({ setUser }: LoginProps) { /> )} - {globalError && {globalError}} + {globalError && }>{globalError}} From c4f49317312f33ef7016e38d2503948c1b79f99e Mon Sep 17 00:00:00 2001 From: Michiel Date: Fri, 23 Jan 2026 11:52:05 +0100 Subject: [PATCH 3/5] Catch route to path errors --- frontend/src/Link.tsx | 6 ++---- frontend/src/components/EditButton.tsx | 4 ++-- .../components/admin/OrganizationsOverview.tsx | 4 ++-- frontend/src/hooks/useRemails.ts | 15 ++++++++++++--- frontend/src/layout/NavBar.tsx | 4 ++-- frontend/src/layout/Tabs.tsx | 4 ++-- 6 files changed, 22 insertions(+), 15 deletions(-) diff --git a/frontend/src/Link.tsx b/frontend/src/Link.tsx index 8a1ae304..00a36c1e 100644 --- a/frontend/src/Link.tsx +++ b/frontend/src/Link.tsx @@ -12,7 +12,7 @@ interface LinkProps { } export function Link({ to, params, underline, children, style }: LinkProps) { - const { navigate, getRoute } = useRemails(); + const { navigate, routeToPath } = useRemails(); const onClick = (e: React.MouseEvent) => { if (e.defaultPrevented || e.ctrlKey || e.metaKey) { @@ -23,10 +23,8 @@ export function Link({ to, params, underline, children, style }: LinkProps) { navigate(to, params); }; - const route = getRoute(to ?? "default", params); - return ( - + {children} ); diff --git a/frontend/src/components/EditButton.tsx b/frontend/src/components/EditButton.tsx index 772ce242..90dbcd96 100644 --- a/frontend/src/components/EditButton.tsx +++ b/frontend/src/components/EditButton.tsx @@ -5,13 +5,13 @@ import { RouteName } from "../routes.ts"; import { RouteParams } from "../router.ts"; export default function EditButton({ route, params }: { route: RouteName; params: RouteParams }) { - const { navigate, getRoute } = useRemails(); + const { navigate, routeToPath } = useRemails(); return (