Role based home page rendering
This commit is contained in:
Binary file not shown.
@@ -8,6 +8,7 @@ import Login from "~/routes/login";
|
||||
import Management from "~/routes/management";
|
||||
import NotFound from "~/routes/[...404]";
|
||||
import Toasts from "~/components/Toasts";
|
||||
import { APP_ROLES, hasAnyRole } from "~/auth/roles";
|
||||
import { initializeLanguage, useLanguage } from "~/i18n";
|
||||
import { sessionAtom } from "~/state/appState";
|
||||
import "./app.css";
|
||||
@@ -52,7 +53,7 @@ function AppShell() {
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route
|
||||
path="/management"
|
||||
element={session?.roles.includes("admin") ? <Management /> : <Navigate to="/" replace />}
|
||||
element={hasAnyRole(session, [APP_ROLES.admin]) ? <Management /> : <Navigate to="/" replace />}
|
||||
/>
|
||||
<Route path="*" element={<NotFound />} />
|
||||
<Route path="/index.html" element={<Navigate to="/" replace />} />
|
||||
|
||||
24
ui/src/auth/roles.ts
Normal file
24
ui/src/auth/roles.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { Session } from "~/state/appState";
|
||||
|
||||
export const APP_ROLES = {
|
||||
admin: "admin",
|
||||
lok: "lok",
|
||||
} as const;
|
||||
|
||||
export type AppRole = (typeof APP_ROLES)[keyof typeof APP_ROLES];
|
||||
|
||||
export const hasAnyRole = (session: Session | null, roles: readonly AppRole[]): boolean => {
|
||||
if (!session || roles.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return roles.some((role) => session.roles.includes(role));
|
||||
};
|
||||
|
||||
export const hasAllRoles = (session: Session | null, roles: readonly AppRole[]): boolean => {
|
||||
if (!session || roles.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return roles.every((role) => session.roles.includes(role));
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import { useRecoilState } from "recoil";
|
||||
import { APP_ROLES, hasAnyRole } from "~/auth/roles";
|
||||
import { sessionAtom } from "~/state/appState";
|
||||
import { useLanguage, useT } from "~/i18n";
|
||||
|
||||
@@ -32,7 +33,7 @@ export default function Nav() {
|
||||
<div className="order-2 sm:order-1 flex items-center justify-center sm:justify-start">
|
||||
<Link to="/" className={linkClass("/")}>{t("nav.home")}</Link>
|
||||
<Link to="/about" className={linkClass("/about")}>{t("nav.about")}</Link>
|
||||
{session?.roles.includes("admin") ? (
|
||||
{hasAnyRole(session, [APP_ROLES.admin]) ? (
|
||||
<Link to="/management" className={linkClass("/management")}>{t("nav.management")}</Link>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
27
ui/src/components/RoleGuard.tsx
Normal file
27
ui/src/components/RoleGuard.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { useRecoilValue } from "recoil";
|
||||
import { sessionAtom } from "~/state/appState";
|
||||
import type { AppRole } from "~/auth/roles";
|
||||
import { hasAllRoles, hasAnyRole } from "~/auth/roles";
|
||||
|
||||
type RoleGuardProps = {
|
||||
roles: readonly AppRole[];
|
||||
match?: "any" | "all";
|
||||
fallback?: ReactNode;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export default function RoleGuard({
|
||||
roles,
|
||||
match = "any",
|
||||
fallback = null,
|
||||
children,
|
||||
}: RoleGuardProps) {
|
||||
const session = useRecoilValue(sessionAtom);
|
||||
|
||||
const isAllowed = match === "all"
|
||||
? hasAllRoles(session, roles)
|
||||
: hasAnyRole(session, roles);
|
||||
|
||||
return isAllowed ? <>{children}</> : <>{fallback}</>;
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useEffect } from "react";
|
||||
import OpenHoursForm from "~/components/OpenHoursForm";
|
||||
import RoleGuard from "~/components/RoleGuard";
|
||||
import { APP_ROLES } from "~/auth/roles";
|
||||
import { useT } from "~/i18n";
|
||||
|
||||
export default function Home() {
|
||||
@@ -14,7 +16,9 @@ export default function Home() {
|
||||
<h1 className="text-center">{t("home.heading")}</h1>
|
||||
<h2 className="text-center">{t("home.subheading")}</h2>
|
||||
<img src="/favicon.svg" alt={t("home.logoAlt")} className="w-28" />
|
||||
<OpenHoursForm />
|
||||
<RoleGuard roles={[APP_ROLES.lok]}>
|
||||
<OpenHoursForm />
|
||||
</RoleGuard>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user