Skip to content

Update permission system and permission wall to the new system #289

Draft
georgelgeback wants to merge 1 commit into
mainfrom
update-permission-system
Draft

Update permission system and permission wall to the new system #289
georgelgeback wants to merge 1 commit into
mainfrom
update-permission-system

Conversation

@georgelgeback

@georgelgeback georgelgeback commented Apr 11, 2026

Copy link
Copy Markdown
Contributor

Fetches the current user's (or non-user's) permissions from the backend instead of using the JWT. The PR compiles without errors when the backend changes are used.

This PR needs to be pushed to prod right after the corresponding backend fixes:

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the frontend permission model to use backend-fetched permissions (instead of decoding them from the JWT), and adapts permission-gated UI (permission wall + admin navigation) to handle loading/error states accordingly.

Changes:

  • Add a React Query–backed usePermissionsState/usePermissions API to fetch/build a PermissionMap from the backend.
  • Update PermissionWall to wait for permissions to load and show an error state on fetch failure.
  • Update admin UI (sidebar + member pages) to consume the new permissions hooks; reformat locale JSON and adjust permission-wall contact strings.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/lib/auth.ts Introduces permission-fetching hooks and builds PermissionMap from backend data; removes JWT-derived permissions.
src/components/PermissionWall.tsx Uses backend permissions hook, adds loading/error handling, and adjusts permission-denied markup.
src/app/admin/AdminSidebar.tsx Filters admin nav entries using backend permissions; adds loading skeleton and fails closed on error.
src/app/admin/members/page.tsx Switches to new usePermissions() API for permission checks.
src/app/admin/members/MemberEditForm.tsx Switches to new usePermissions() API for permission checks.
src/locales/en/main.json Reformat + adjust permission-wall contact string punctuation.
src/locales/sv/main.json Reformat + adjust permission-wall contact string punctuation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/lib/auth.ts
Comment on lines +51 to 62
function buildPermissionMap(
permissions: [string, string][] | null | undefined,
): PermissionMap {
const map = new PermissionMap();
if (!permissions) return map;

function buildPermissionMap(token: BearerResponse | null): PermissionMap {
const map = new PermissionMap();
if (!token) return map;
try {
const payload = JSON.parse(
Buffer.from(token.access_token.split(".")[1], "base64").toString(),
) as { permissions: string[] };
for (const [actionStr, targetStr] of permissions) {
const actionEnum = actionStr as ActionEnum;
const targetEnum = targetStr as TargetEnum;

for (const entry of payload.permissions) {
const parts = entry.split(":");
if (parts.length !== 2) continue;
if (!actionEnum || !targetEnum) continue;

Copilot AI Apr 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In buildPermissionMap, the as ActionEnum / as TargetEnum casts don’t validate the values coming from the backend, so the if (!actionEnum || !targetEnum) guard won’t actually filter out unexpected strings. Consider validating against Object.values(ActionEnum) / Object.values(TargetEnum) (or typing permissions as [ActionEnum, TargetEnum][] if that’s what the API returns) so invalid server data can’t silently populate the map with unknown targets/actions.

Copilot uses AI. Check for mistakes.
Comment thread src/lib/auth.ts
Comment on lines +11 to +16
export type UsePermissionsState = {
permissions: PermissionMap;
isLoading: boolean;
isError: boolean;
error: unknown;
};

Copilot AI Apr 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UsePermissionsState.error is typed as unknown, which loses the (default) Error type from TanStack Query and forces extra runtime checks downstream. Consider typing this as Error | null (or DefaultError | null) to keep type-safety and simplify consumers.

Copilot uses AI. Check for mistakes.
Comment on lines 16 to +24
<div className="flex items-center justify-center min-h-screen bg-background text-foreground">
<section className="text-center">
<h1 className="mb-4 text-7xl font-extrabold tracking-tight lg:text-9xl text-destructive">
<div className="mb-4 text-7xl font-extrabold tracking-tight lg:text-9xl text-destructive">
{t("permission-wall.stop")}
</h1>
<p className="mb-4 text-3xl font-bold md:text-4xl">
</div>
<div className="mb-4 text-3xl font-bold md:text-4xl">
{t("permission-wall.subtitle")}
</p>
<p className="mb-4 text-lg font-light text-muted-foreground">
</div>
<div className="mb-4 text-lg font-light text-muted-foreground">

Copilot AI Apr 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The permission denied UI replaced semantic heading/paragraph elements (h1/p) with plain divs. This is an accessibility regression (screen readers and document outline lose structure). Consider keeping appropriate heading levels (e.g. h1 for the main “Stop” heading, p for body text).

Copilot uses AI. Check for mistakes.
Comment on lines +24 to 30
<div className="mb-4 text-lg font-light text-muted-foreground">
{t("permission-wall.message")}
<Obfuscate email={"spindelman@fsektionen.se"}>
<p className="inline-flex text-forange hover:bg-primary hover:text-white">
<div className="inline-flex text-forange hover:bg-primary hover:text-white">
{t("permission-wall.contact")}
</p>
</div>
</Obfuscate>

Copilot AI Apr 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inside the sentence text, the email link is wrapped in a div (inline-flex). Using a block element in the middle of a sentence can create layout/whitespace issues and is semantically odd; a span (or letting Obfuscate render an anchor) would fit better here.

Copilot uses AI. Check for mistakes.
Comment on lines +69 to +72
if (isError) {
const errorMessage =
error instanceof Error ? error : "Failed to load permissions";
return <LoadingErrorCard error={errorMessage} isLoading={false} />;

Copilot AI Apr 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback error string "Failed to load permissions" is hard-coded in English. Since this UI uses i18n elsewhere, consider using a translated string (or passing through the original error so LoadingErrorCard can render a localized fallback).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants