From b3157484c4e4ccd152712366f06b559122b67454 Mon Sep 17 00:00:00 2001 From: Nick Szaunig Date: Wed, 26 Aug 2026 23:14:47 +0200 Subject: [PATCH] feat: Unbekannte und nicht erlaubte Seiten leiten zum Dashboard um - src/app/not-found.tsx: jede nicht existierende Route landet auf /dashboard statt der Next.js-Standard-404-Seite (der bestehende Auth-Guard in (app)/layout.tsx schickt nicht angemeldete User von dort automatisch weiter zu /login). - settings/layout.tsx: fehlt die Berechtigung fuer den Einstellungen-Bereich, wird jetzt zu /dashboard umgeleitet statt nur einen Hinweistext anzuzeigen (gleiches Redirect-Muster wie der bestehende Login-Guard). Login-nach-Anmeldung-Redirect zu /dashboard und der Auth-Gate in (app)/layout.tsx bestanden bereits. --- .../src/app/(app)/settings/layout.spec.tsx | 46 +++++++++++++++++++ .../src/app/(app)/settings/layout.tsx | 18 ++++---- ApiCore-Frontend/src/app/not-found.tsx | 5 ++ 3 files changed, 61 insertions(+), 8 deletions(-) create mode 100644 ApiCore-Frontend/src/app/(app)/settings/layout.spec.tsx create mode 100644 ApiCore-Frontend/src/app/not-found.tsx diff --git a/ApiCore-Frontend/src/app/(app)/settings/layout.spec.tsx b/ApiCore-Frontend/src/app/(app)/settings/layout.spec.tsx new file mode 100644 index 0000000..2c426a9 --- /dev/null +++ b/ApiCore-Frontend/src/app/(app)/settings/layout.spec.tsx @@ -0,0 +1,46 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { render, screen } from "@testing-library/react"; +import SettingsLayout from "./layout"; + +const replaceMock = vi.fn(); +vi.mock("next/navigation", () => ({ + useRouter: () => ({ replace: replaceMock }), +})); + +let mockUser: { permissions: string[] } | null = null; +vi.mock("@/lib/auth/auth-context", () => ({ + useAuth: () => ({ user: mockUser }), +})); + +describe("SettingsLayout", () => { + beforeEach(() => { + replaceMock.mockReset(); + mockUser = null; + }); + + it("renders children when the user has a settings-area permission", () => { + mockUser = { permissions: ["users:read"] }; + + render( + +

Einstellungen-Inhalt

+
, + ); + + expect(screen.getByText("Einstellungen-Inhalt")).toBeInTheDocument(); + expect(replaceMock).not.toHaveBeenCalled(); + }); + + it("redirects to the dashboard when the user lacks every settings-area permission", () => { + mockUser = { permissions: ["colonies:read"] }; + + render( + +

Einstellungen-Inhalt

+
, + ); + + expect(screen.queryByText("Einstellungen-Inhalt")).not.toBeInTheDocument(); + expect(replaceMock).toHaveBeenCalledWith("/dashboard"); + }); +}); diff --git a/ApiCore-Frontend/src/app/(app)/settings/layout.tsx b/ApiCore-Frontend/src/app/(app)/settings/layout.tsx index 672e872..dce72f3 100644 --- a/ApiCore-Frontend/src/app/(app)/settings/layout.tsx +++ b/ApiCore-Frontend/src/app/(app)/settings/layout.tsx @@ -1,6 +1,7 @@ "use client"; -import { ReactNode } from "react"; +import { ReactNode, useEffect } from "react"; +import { useRouter } from "next/navigation"; import { useAuth } from "@/lib/auth/auth-context"; const SETTINGS_AREA_PERMISSIONS = [ @@ -14,19 +15,20 @@ const SETTINGS_AREA_PERMISSIONS = [ ]; export default function SettingsLayout({ children }: { children: ReactNode }) { + const router = useRouter(); const { user } = useAuth(); const hasSettingsAccess = SETTINGS_AREA_PERMISSIONS.some((permission) => user?.permissions.includes(permission), ); + useEffect(() => { + if (!hasSettingsAccess) { + router.replace("/dashboard"); + } + }, [hasSettingsAccess, router]); + if (!hasSettingsAccess) { - return ( -
-

- Diese Seite ist nur für Administratoren verfügbar. -

-
- ); + return null; } return <>{children}; diff --git a/ApiCore-Frontend/src/app/not-found.tsx b/ApiCore-Frontend/src/app/not-found.tsx new file mode 100644 index 0000000..aab4065 --- /dev/null +++ b/ApiCore-Frontend/src/app/not-found.tsx @@ -0,0 +1,5 @@ +import { redirect } from "next/navigation"; + +export default function NotFound() { + redirect("/dashboard"); +}