From 664d80809ab0cf8011c515c388128d03daf0a4de Mon Sep 17 00:00:00 2001 From: "santasri.pachhal" Date: Fri, 19 Jun 2026 18:36:05 +0530 Subject: [PATCH] refactor: modify route guard pattern --- src/app/(modules)/clients/layout.tsx | 16 ++++++++++ src/app/(modules)/package/layout.tsx | 16 ++++++++++ src/app/(modules)/plans/layout.tsx | 16 ++++++++++ src/app/(modules)/project/layout.tsx | 16 ++++++++++ src/app/(modules)/roles/layout.tsx | 16 ++++++++++ src/app/(modules)/segment/layout.tsx | 16 ++++++++++ src/app/(modules)/tenants/layout.tsx | 16 ++++++++++ src/app/(modules)/ticket/layout.tsx | 16 ++++++++++ src/app/(modules)/upload/layout.tsx | 16 ++++++++++ src/app/(modules)/users/layout.tsx | 16 ++++++++++ src/components/data-table/index.tsx | 11 +++++-- src/config/app.routes.ts | 44 ---------------------------- src/config/menu.config.ts | 3 ++ src/constants/permissions.ts | 18 ++++++++++++ src/guards/AuthGuard.tsx | 16 ++-------- src/guards/PermissionRouteGuard.tsx | 31 ++++++++++++++++++++ src/guards/index.ts | 1 + 17 files changed, 224 insertions(+), 60 deletions(-) create mode 100644 src/app/(modules)/clients/layout.tsx create mode 100644 src/app/(modules)/package/layout.tsx create mode 100644 src/app/(modules)/plans/layout.tsx create mode 100644 src/app/(modules)/project/layout.tsx create mode 100644 src/app/(modules)/roles/layout.tsx create mode 100644 src/app/(modules)/segment/layout.tsx create mode 100644 src/app/(modules)/tenants/layout.tsx create mode 100644 src/app/(modules)/ticket/layout.tsx create mode 100644 src/app/(modules)/upload/layout.tsx create mode 100644 src/app/(modules)/users/layout.tsx delete mode 100644 src/config/app.routes.ts create mode 100644 src/guards/PermissionRouteGuard.tsx diff --git a/src/app/(modules)/clients/layout.tsx b/src/app/(modules)/clients/layout.tsx new file mode 100644 index 0000000..a637066 --- /dev/null +++ b/src/app/(modules)/clients/layout.tsx @@ -0,0 +1,16 @@ +'use client'; + +import { PERMISSIONS } from '@/constants/permissions'; +import { PermissionRouteGuard } from '@/guards'; + +export default function ClientsLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/src/app/(modules)/package/layout.tsx b/src/app/(modules)/package/layout.tsx new file mode 100644 index 0000000..e5b9bed --- /dev/null +++ b/src/app/(modules)/package/layout.tsx @@ -0,0 +1,16 @@ +'use client'; + +import { PERMISSIONS } from '@/constants/permissions'; +import { PermissionRouteGuard } from '@/guards'; + +export default function PackageLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/src/app/(modules)/plans/layout.tsx b/src/app/(modules)/plans/layout.tsx new file mode 100644 index 0000000..9e4c316 --- /dev/null +++ b/src/app/(modules)/plans/layout.tsx @@ -0,0 +1,16 @@ +'use client'; + +import { PERMISSIONS } from '@/constants/permissions'; +import { PermissionRouteGuard } from '@/guards'; + +export default function PlansLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/src/app/(modules)/project/layout.tsx b/src/app/(modules)/project/layout.tsx new file mode 100644 index 0000000..f6f32a3 --- /dev/null +++ b/src/app/(modules)/project/layout.tsx @@ -0,0 +1,16 @@ +'use client'; + +import { PERMISSIONS } from '@/constants/permissions'; +import { PermissionRouteGuard } from '@/guards'; + +export default function ProjectLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/src/app/(modules)/roles/layout.tsx b/src/app/(modules)/roles/layout.tsx new file mode 100644 index 0000000..22542de --- /dev/null +++ b/src/app/(modules)/roles/layout.tsx @@ -0,0 +1,16 @@ +'use client'; + +import { PERMISSIONS } from '@/constants/permissions'; +import { PermissionRouteGuard } from '@/guards'; + +export default function RolesLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/src/app/(modules)/segment/layout.tsx b/src/app/(modules)/segment/layout.tsx new file mode 100644 index 0000000..f654ef5 --- /dev/null +++ b/src/app/(modules)/segment/layout.tsx @@ -0,0 +1,16 @@ +'use client'; + +import { PERMISSIONS } from '@/constants/permissions'; +import { PermissionRouteGuard } from '@/guards'; + +export default function SegmentLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/src/app/(modules)/tenants/layout.tsx b/src/app/(modules)/tenants/layout.tsx new file mode 100644 index 0000000..fea1c34 --- /dev/null +++ b/src/app/(modules)/tenants/layout.tsx @@ -0,0 +1,16 @@ +'use client'; + +import { PERMISSIONS } from '@/constants/permissions'; +import { PermissionRouteGuard } from '@/guards'; + +export default function TenantsLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/src/app/(modules)/ticket/layout.tsx b/src/app/(modules)/ticket/layout.tsx new file mode 100644 index 0000000..edb51c5 --- /dev/null +++ b/src/app/(modules)/ticket/layout.tsx @@ -0,0 +1,16 @@ +'use client'; + +import { PERMISSIONS } from '@/constants/permissions'; +import { PermissionRouteGuard } from '@/guards'; + +export default function TicketLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/src/app/(modules)/upload/layout.tsx b/src/app/(modules)/upload/layout.tsx new file mode 100644 index 0000000..22bfcad --- /dev/null +++ b/src/app/(modules)/upload/layout.tsx @@ -0,0 +1,16 @@ +'use client'; + +import { PERMISSIONS } from '@/constants/permissions'; +import { PermissionRouteGuard } from '@/guards'; + +export default function UploadLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/src/app/(modules)/users/layout.tsx b/src/app/(modules)/users/layout.tsx new file mode 100644 index 0000000..a13a872 --- /dev/null +++ b/src/app/(modules)/users/layout.tsx @@ -0,0 +1,16 @@ +'use client'; + +import { PERMISSIONS } from '@/constants/permissions'; +import { PermissionRouteGuard } from '@/guards'; + +export default function UsersLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/src/components/data-table/index.tsx b/src/components/data-table/index.tsx index 591a002..c67044b 100644 --- a/src/components/data-table/index.tsx +++ b/src/components/data-table/index.tsx @@ -286,12 +286,12 @@ function DataTableContent({ 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({ 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 ? ( diff --git a/src/config/app.routes.ts b/src/config/app.routes.ts deleted file mode 100644 index 97713ec..0000000 --- a/src/config/app.routes.ts +++ /dev/null @@ -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; -} diff --git a/src/config/menu.config.ts b/src/config/menu.config.ts index f9bf081..913f731 100644 --- a/src/config/menu.config.ts +++ b/src/config/menu.config.ts @@ -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', diff --git a/src/constants/permissions.ts b/src/constants/permissions.ts index 202c3c4..9a95cf9 100644 --- a/src/constants/permissions.ts +++ b/src/constants/permissions.ts @@ -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', diff --git a/src/guards/AuthGuard.tsx b/src/guards/AuthGuard.tsx index 3c0da28..1a55ac3 100644 --- a/src/guards/AuthGuard.tsx +++ b/src/guards/AuthGuard.tsx @@ -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 ; } - if (!accessToken || !user || !canAccessRoute) { + if (!accessToken || !user) { return null; } diff --git a/src/guards/PermissionRouteGuard.tsx b/src/guards/PermissionRouteGuard.tsx new file mode 100644 index 0000000..7057e24 --- /dev/null +++ b/src/guards/PermissionRouteGuard.tsx @@ -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; +} diff --git a/src/guards/index.ts b/src/guards/index.ts index 0caa0c0..e96cb41 100644 --- a/src/guards/index.ts +++ b/src/guards/index.ts @@ -1,3 +1,4 @@ export * from './AuthGuard'; export * from './GuestGuard'; export * from './PermissionGuard'; +export * from './PermissionRouteGuard';