Commit 05fea1a8 authored by Dimitrios Gogos's avatar Dimitrios Gogos
Browse files

feat: refactor role guard and user components for improved role handling and display

parent 710ca5b6
Loading
Loading
Loading
Loading
Loading
+1 −1
Changes for portal-gui/src/app/guards/role-guard.tsx: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -10,7 +10,7 @@ export function RoleGuard({ allowedRoles, children }: RoleGuardProps) {
    return <Navigate to={ROUTES.LOGIN} replace />;
  }

  if (!allowedRoles.includes(user.role)) {
  if (!allowedRoles.some((role) => user.roles.includes(role))) {
    return <Navigate to={ROUTES.FORBIDDEN} replace />;
  }

+1 −1
Changes for portal-gui/src/app/router.tsx: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -55,7 +55,7 @@ export const router = createBrowserRouter([
          {
            path: ROUTES.SETTINGS,
            element: (
              <RoleGuard allowedRoles={['admin']}>
              <RoleGuard allowedRoles={['platform-admin']}>
                <SettingsPage />
              </RoleGuard>
            ),
+6 −4
Changes for portal-gui/src/components/common/nav-user.tsx: 6 added lines, 4 removed lines.
Original line number Diff line number Diff line
@@ -36,6 +36,8 @@ export function NavUser() {
    return null;
  }

  const displayName = user.username ?? user.email ?? user.subject;

  return (
    <SidebarMenu>
      <SidebarMenuItem>
@@ -46,12 +48,12 @@ export function NavUser() {
              className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
            >
              <Avatar className="h-8 w-8 rounded-lg grayscale">
                <AvatarImage alt={user.name} />
                <AvatarImage alt={displayName} />
                <AvatarFallback className="rounded-lg">CN</AvatarFallback>
              </Avatar>
              <div className="grid flex-1 text-left text-sm leading-tight">
                <span className="truncate font-medium font-semibold">
                  {user.name}
                  {displayName}
                </span>
              </div>
              <EllipsisVerticalIcon className="ml-auto size-4" />
@@ -66,11 +68,11 @@ export function NavUser() {
            <DropdownMenuLabel className="p-0 font-normal">
              <div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
                <Avatar className="h-8 w-8 rounded-lg">
                  <AvatarImage alt={user.name} />
                  <AvatarImage alt={displayName} />
                  <AvatarFallback className="rounded-lg">CN</AvatarFallback>
                </Avatar>
                <div className="grid flex-1 text-left text-sm leading-tight">
                  <span className="truncate font-medium">{user.name}</span>
                  <span className="truncate font-medium">{displayName}</span>
                  <span className="truncate text-xs text-muted-foreground">
                    {user.email}
                  </span>
+3 −37
Changes for portal-gui/src/features/auth/auth-provider.tsx: 3 added lines, 37 removed lines.
Original line number Diff line number Diff line
import { useState } from 'react';
import { AuthContext } from './auth-context';
import type { SessionUser } from '@/types/authentication';

import { mockUser, mockCredentials } from '@/lib/constants';

function readStoredUser(): SessionUser | null {
  const stored = localStorage.getItem('user');

  if (!stored) {
    return null;
  }

  try {
    return JSON.parse(stored) as SessionUser;
  } catch {
    // Malformed JSON — clear the bad value and start unauthenticated.
    localStorage.removeItem('user');

    return null;
  }
}
import type { CurrentUser } from '@/types/authentication';

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<SessionUser | null>(readStoredUser);
  const login = async (email: string, password: string) => {
    if (email === mockUser.email && password === mockCredentials.password) {
      const sessionUser = {
        name: mockUser.name,
        email: mockUser.email,
        role: mockUser.role,
      };
      setUser(sessionUser);
      localStorage.setItem('user', JSON.stringify(sessionUser));

      return true;
    }

    return false;
  };
  const [user, setUser] = useState<CurrentUser | null>(null);
  const login = async () => false;

  const logout = () => {
    setUser(null);
    localStorage.removeItem('user');
  };

  return (
+27 −12
Changes for portal-gui/src/features/settings/settings.tsx: 27 added lines, 12 removed lines.
Original line number Diff line number Diff line
@@ -9,14 +9,19 @@ import {
} from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { mockUser } from '@/lib/constants';
import type { User } from '@/types/authentication';
import { useAuth } from '@/features/auth/auth-context';
import { isRole } from '@/types/authentication';

export function SettingsPage() {
  const { user } = useAuth();
  const [isEditing, setIsEditing] = useState(false);

  const [formValues, setFormValues] = useState<User>(mockUser);
  const updateField = (field: 'name' | 'email', value: string) => {
  const savedValues = {
    username: user?.username ?? '',
    email: user?.email ?? '',
  };
  const [formValues, setFormValues] = useState(savedValues);
  const updateField = (field: 'username' | 'email', value: string) => {
    setFormValues((previousValues) => ({
      ...previousValues,
      [field]: value,
@@ -26,7 +31,7 @@ export function SettingsPage() {
    setIsEditing(false);
  };
  const handleCancel = () => {
    setFormValues(mockUser);
    setFormValues(savedValues);
    setIsEditing(false);
  };

@@ -49,12 +54,14 @@ export function SettingsPage() {

          <CardContent className="space-y-4">
            <div className="grid gap-2">
              <Label htmlFor="name">Name</Label>
              <Label htmlFor="username">Username</Label>
              <Input
                id="name"
                value={formValues.name}
                id="username"
                value={formValues.username}
                readOnly={!isEditing}
                onChange={(event) => updateField('name', event.target.value)}
                onChange={(event) =>
                  updateField('username', event.target.value)
                }
              />
            </div>

@@ -70,13 +77,21 @@ export function SettingsPage() {
            </div>

            <div className="grid gap-2">
              <Label htmlFor="role">Role</Label>
              <Input id="role" value={mockUser.role} readOnly />
              <Label htmlFor="roles">Roles</Label>
              <Input
                id="roles"
                value={user?.roles.filter(isRole).join(', ') ?? ''}
                readOnly
              />
            </div>

            <div className="grid gap-2">
              <Label htmlFor="organization">Organization</Label>
              <Input id="organization" value={mockUser.organization} readOnly />
              <Input
                id="organization"
                value={user?.organization ?? ''}
                readOnly
              />
            </div>

            {isEditing ? (
Loading