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';