feat: add permission-gated navigation and admin access

This commit is contained in:
2026-06-16 16:16:03 +05:30
parent 893b49c701
commit 964b177ae1
18 changed files with 401 additions and 112 deletions

View File

@@ -1,17 +1,24 @@
'use client';
import { AppLoader } from '@/components/AppLoader';
import { useAppStore } from '@/store/app.store';
import { useAuthStore } from '@/store/auth.store';
import { getRoutePermission } from '@/config/app.routes';
import { usePermissions } from '@/hooks/usePermissions';
import { ROUTES } from '@/utils/routes';
import { useRouter } from 'next/navigation';
import { usePathname, useRouter } from 'next/navigation';
import type { ReactNode } from 'react';
import { useEffect } from 'react';
export function AuthGuard({ children }: { children: ReactNode }) {
const router = useRouter();
const pathname = usePathname();
const accessToken = useAuthStore((state) => state.accessToken);
const isInitialized = useAppStore((state) => state.isInitialized);
const user = useAppStore((state) => state.user);
const { hasPermission } = usePermissions();
const requiredPermission = getRoutePermission(pathname);
const canAccessRoute = hasPermission(requiredPermission);
useEffect(() => {
if (!isInitialized) return;
@@ -21,7 +28,17 @@ export function AuthGuard({ children }: { children: ReactNode }) {
}
}, [accessToken, isInitialized, router]);
if (!isInitialized || !accessToken || !user) {
useEffect(() => {
if (!isInitialized || !accessToken || !user || canAccessRoute) return;
router.replace(ROUTES.ACCESS);
}, [accessToken, canAccessRoute, isInitialized, router, user]);
if (!isInitialized || (accessToken && !user)) {
return <AppLoader label="Preparing workspace" />;
}
if (!accessToken || !user || !canAccessRoute) {
return null;
}

View File

@@ -1,5 +1,6 @@
'use client';
import { AppLoader } from '@/components/AppLoader';
import { useAppStore } from '@/store/app.store';
import { useAuthStore } from '@/store/auth.store';
import { ROUTES } from '@/utils/routes';
@@ -19,7 +20,11 @@ export function GuestGuard({ children }: { children: ReactNode }) {
}
}, [accessToken, isInitialized, router, user]);
if (!isInitialized || (accessToken && user)) return null;
if (!isInitialized) {
return <AppLoader label={accessToken ? 'Preparing workspace' : 'Loading'} />;
}
if (isInitialized && accessToken && user) return <AppLoader label="Opening dashboard" />;
return children;
}

View File

@@ -1,29 +1,18 @@
'use client';
import { useAppStore } from '@/store/app.store';
import { usePermissions, type PermissionInput } from '@/hooks/usePermissions';
import type { ReactNode } from 'react';
type PermissionGuardProps = {
children: ReactNode;
permissions: string[];
mode?: 'all' | 'any';
permissions?: PermissionInput;
fallback?: ReactNode;
};
export function PermissionGuard({
children,
permissions,
mode = 'all',
fallback = null,
}: PermissionGuardProps) {
const grantedPermissions = useAppStore((state) => state.permissions);
const normalizedPermissions = grantedPermissions.map((permission) => permission.toLowerCase());
const hasPermission =
mode === 'all'
? permissions.every((permission) => normalizedPermissions.includes(permission.toLowerCase()))
: permissions.some((permission) => normalizedPermissions.includes(permission.toLowerCase()));
export function PermissionGuard({ children, permissions, fallback = null }: PermissionGuardProps) {
const { hasPermission } = usePermissions();
if (!hasPermission) return fallback;
if (!hasPermission(permissions)) return fallback;
return children;
}