refactor: modify route guard pattern
This commit is contained in:
16
src/app/(modules)/clients/layout.tsx
Normal file
16
src/app/(modules)/clients/layout.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
'use client';
|
||||
|
||||
import { PERMISSIONS } from '@/constants/permissions';
|
||||
import { PermissionRouteGuard } from '@/guards';
|
||||
|
||||
export default function ClientsLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<PermissionRouteGuard permission={PERMISSIONS.CLIENT.READ}>
|
||||
{children}
|
||||
</PermissionRouteGuard>
|
||||
);
|
||||
}
|
||||
16
src/app/(modules)/package/layout.tsx
Normal file
16
src/app/(modules)/package/layout.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
'use client';
|
||||
|
||||
import { PERMISSIONS } from '@/constants/permissions';
|
||||
import { PermissionRouteGuard } from '@/guards';
|
||||
|
||||
export default function PackageLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<PermissionRouteGuard permission={PERMISSIONS.PACKAGE.READ}>
|
||||
{children}
|
||||
</PermissionRouteGuard>
|
||||
);
|
||||
}
|
||||
16
src/app/(modules)/plans/layout.tsx
Normal file
16
src/app/(modules)/plans/layout.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
'use client';
|
||||
|
||||
import { PERMISSIONS } from '@/constants/permissions';
|
||||
import { PermissionRouteGuard } from '@/guards';
|
||||
|
||||
export default function PlansLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<PermissionRouteGuard permission={PERMISSIONS.PLAN.READ}>
|
||||
{children}
|
||||
</PermissionRouteGuard>
|
||||
);
|
||||
}
|
||||
16
src/app/(modules)/project/layout.tsx
Normal file
16
src/app/(modules)/project/layout.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
'use client';
|
||||
|
||||
import { PERMISSIONS } from '@/constants/permissions';
|
||||
import { PermissionRouteGuard } from '@/guards';
|
||||
|
||||
export default function ProjectLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<PermissionRouteGuard permission={PERMISSIONS.PROJECT.READ}>
|
||||
{children}
|
||||
</PermissionRouteGuard>
|
||||
);
|
||||
}
|
||||
16
src/app/(modules)/roles/layout.tsx
Normal file
16
src/app/(modules)/roles/layout.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
'use client';
|
||||
|
||||
import { PERMISSIONS } from '@/constants/permissions';
|
||||
import { PermissionRouteGuard } from '@/guards';
|
||||
|
||||
export default function RolesLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<PermissionRouteGuard permission={PERMISSIONS.ROLE.READ}>
|
||||
{children}
|
||||
</PermissionRouteGuard>
|
||||
);
|
||||
}
|
||||
16
src/app/(modules)/segment/layout.tsx
Normal file
16
src/app/(modules)/segment/layout.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
'use client';
|
||||
|
||||
import { PERMISSIONS } from '@/constants/permissions';
|
||||
import { PermissionRouteGuard } from '@/guards';
|
||||
|
||||
export default function SegmentLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<PermissionRouteGuard permission={PERMISSIONS.SEGMENT.READ}>
|
||||
{children}
|
||||
</PermissionRouteGuard>
|
||||
);
|
||||
}
|
||||
16
src/app/(modules)/tenants/layout.tsx
Normal file
16
src/app/(modules)/tenants/layout.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
'use client';
|
||||
|
||||
import { PERMISSIONS } from '@/constants/permissions';
|
||||
import { PermissionRouteGuard } from '@/guards';
|
||||
|
||||
export default function TenantsLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<PermissionRouteGuard permission={PERMISSIONS.TENANT.READ}>
|
||||
{children}
|
||||
</PermissionRouteGuard>
|
||||
);
|
||||
}
|
||||
16
src/app/(modules)/ticket/layout.tsx
Normal file
16
src/app/(modules)/ticket/layout.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
'use client';
|
||||
|
||||
import { PERMISSIONS } from '@/constants/permissions';
|
||||
import { PermissionRouteGuard } from '@/guards';
|
||||
|
||||
export default function TicketLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<PermissionRouteGuard permission={PERMISSIONS.TICKET.READ}>
|
||||
{children}
|
||||
</PermissionRouteGuard>
|
||||
);
|
||||
}
|
||||
16
src/app/(modules)/upload/layout.tsx
Normal file
16
src/app/(modules)/upload/layout.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
'use client';
|
||||
|
||||
import { PERMISSIONS } from '@/constants/permissions';
|
||||
import { PermissionRouteGuard } from '@/guards';
|
||||
|
||||
export default function UploadLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<PermissionRouteGuard permission={PERMISSIONS.VIDEO.UPLOAD}>
|
||||
{children}
|
||||
</PermissionRouteGuard>
|
||||
);
|
||||
}
|
||||
16
src/app/(modules)/users/layout.tsx
Normal file
16
src/app/(modules)/users/layout.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
'use client';
|
||||
|
||||
import { PERMISSIONS } from '@/constants/permissions';
|
||||
import { PermissionRouteGuard } from '@/guards';
|
||||
|
||||
export default function UsersLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<PermissionRouteGuard permission={PERMISSIONS.USER.READ}>
|
||||
{children}
|
||||
</PermissionRouteGuard>
|
||||
);
|
||||
}
|
||||
@@ -286,12 +286,12 @@ function DataTableContent<TData, TValue>({
|
||||
<TableRow
|
||||
key={row.id}
|
||||
data-state={row.getIsSelected() && 'selected'}
|
||||
onClick={() => onRowClick?.(row.original)}
|
||||
className={cn(onRowClick && 'cursor-pointer')}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => {
|
||||
const truncate = shouldTruncateCell(cell.column);
|
||||
const pinned = cell.column.getIsPinned();
|
||||
const isActionsCell = cell.column.id === 'actions';
|
||||
const isRowClickable = Boolean(onRowClick && !isActionsCell);
|
||||
const content = flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext(),
|
||||
@@ -301,9 +301,16 @@ function DataTableContent<TData, TValue>({
|
||||
<TableCell
|
||||
key={cell.id}
|
||||
style={getPinnedColumnStyles(cell.column)}
|
||||
onClick={
|
||||
isRowClickable
|
||||
? () => onRowClick?.(row.original)
|
||||
: undefined
|
||||
}
|
||||
className={cn(
|
||||
'max-w-0 overflow-hidden border-b border-border',
|
||||
pinned && 'bg-card',
|
||||
isRowClickable && 'cursor-pointer',
|
||||
isActionsCell && 'cursor-default',
|
||||
)}
|
||||
>
|
||||
{truncate ? (
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { PERMISSIONS } from '@/constants/permissions';
|
||||
import { ROUTES } from '@/utils/routes';
|
||||
|
||||
export type AppRoute = {
|
||||
path: string;
|
||||
permission?: string;
|
||||
match?: 'exact' | 'prefix';
|
||||
};
|
||||
|
||||
export const appRoutes: AppRoute[] = [
|
||||
{
|
||||
path: ROUTES.ROLES,
|
||||
permission: PERMISSIONS.ROLE.READ,
|
||||
},
|
||||
{
|
||||
path: ROUTES.USERS,
|
||||
permission: PERMISSIONS.USER.READ,
|
||||
},
|
||||
{
|
||||
path: ROUTES.CLIENTS,
|
||||
permission: PERMISSIONS.CLIENT.READ,
|
||||
},
|
||||
{
|
||||
path: ROUTES.PLANS,
|
||||
permission: PERMISSIONS.PLAN.READ,
|
||||
},
|
||||
{
|
||||
path: ROUTES.UPLOAD,
|
||||
permission: PERMISSIONS.VIDEO.UPLOAD,
|
||||
},
|
||||
{
|
||||
path: ROUTES.TICKET,
|
||||
permission: PERMISSIONS.TICKET.READ,
|
||||
match: 'prefix',
|
||||
},
|
||||
];
|
||||
|
||||
export function getRoutePermission(pathname: string) {
|
||||
return appRoutes.find((route) =>
|
||||
route.match === 'prefix'
|
||||
? pathname === route.path || pathname.startsWith(`${route.path}/`)
|
||||
: route.path === pathname,
|
||||
)?.permission;
|
||||
}
|
||||
@@ -47,16 +47,19 @@ export const menuItems: MenuItem[] = [
|
||||
title: 'Project',
|
||||
path: ROUTES.PROJECT,
|
||||
icon: Layers,
|
||||
permission: PERMISSIONS.PROJECT.READ,
|
||||
},
|
||||
{
|
||||
title: 'Package',
|
||||
path: ROUTES.PACKAGE,
|
||||
icon: Package,
|
||||
permission: PERMISSIONS.PACKAGE.READ,
|
||||
},
|
||||
{
|
||||
title: 'Segment',
|
||||
path: ROUTES.CHAINAGE,
|
||||
icon: Milestone,
|
||||
permission: PERMISSIONS.SEGMENT.READ,
|
||||
},
|
||||
{
|
||||
title: 'Administration',
|
||||
|
||||
@@ -29,6 +29,24 @@ export const PERMISSIONS = {
|
||||
UPDATE: 'administration.tenants.update',
|
||||
DELETE: 'administration.tenants.delete',
|
||||
},
|
||||
PROJECT: {
|
||||
CREATE: 'management.project.create',
|
||||
READ: 'management.project.read',
|
||||
UPDATE: 'management.project.update',
|
||||
DELETE: 'management.project.delete',
|
||||
},
|
||||
PACKAGE: {
|
||||
CREATE: 'management.package.create',
|
||||
READ: 'management.package.read',
|
||||
UPDATE: 'management.package.update',
|
||||
DELETE: 'management.package.delete',
|
||||
},
|
||||
SEGMENT: {
|
||||
CREATE: 'management.segment.create',
|
||||
READ: 'management.segment.read',
|
||||
UPDATE: 'management.segment.update',
|
||||
DELETE: 'management.segment.delete',
|
||||
},
|
||||
TICKET: {
|
||||
ASSIGN: 'ticket.assign',
|
||||
WORK: 'ticket.work',
|
||||
|
||||
@@ -3,22 +3,16 @@
|
||||
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 { usePathname, useRouter } from 'next/navigation';
|
||||
import { 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;
|
||||
@@ -28,17 +22,11 @@ export function AuthGuard({ children }: { children: ReactNode }) {
|
||||
}
|
||||
}, [accessToken, isInitialized, router]);
|
||||
|
||||
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) {
|
||||
if (!accessToken || !user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
31
src/guards/PermissionRouteGuard.tsx
Normal file
31
src/guards/PermissionRouteGuard.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
'use client';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, type ReactNode } from 'react';
|
||||
|
||||
import { usePermissions, type PermissionInput } from '@/hooks/usePermissions';
|
||||
import { ROUTES } from '@/utils/routes';
|
||||
|
||||
interface PermissionRouteGuardProps {
|
||||
children: ReactNode;
|
||||
permission: PermissionInput;
|
||||
}
|
||||
|
||||
export function PermissionRouteGuard({
|
||||
children,
|
||||
permission,
|
||||
}: PermissionRouteGuardProps) {
|
||||
const router = useRouter();
|
||||
const { hasPermission } = usePermissions();
|
||||
const allowed = hasPermission(permission);
|
||||
|
||||
useEffect(() => {
|
||||
if (!allowed) {
|
||||
router.replace(ROUTES.ACCESS);
|
||||
}
|
||||
}, [allowed, router]);
|
||||
|
||||
if (!allowed) return null;
|
||||
|
||||
return children;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './AuthGuard';
|
||||
export * from './GuestGuard';
|
||||
export * from './PermissionGuard';
|
||||
export * from './PermissionRouteGuard';
|
||||
|
||||
Reference in New Issue
Block a user