mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 22:58:17 +00:00
feat: ( permission ) add permission guard
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useAuthStore } from '@/lib/auth-store';
|
||||
|
||||
interface Props {
|
||||
permission?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a page to enforce auth + optional permission check.
|
||||
* - Not logged in → redirect to /login
|
||||
* - Missing permission → redirect to /dashboard
|
||||
*/
|
||||
export function PermissionGuard({ permission, children }: Props) {
|
||||
const router = useRouter();
|
||||
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
|
||||
const hasPermission = useAuthStore((s) => s.hasPermission);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAuthenticated) {
|
||||
router.replace('/login');
|
||||
return;
|
||||
}
|
||||
if (permission && !hasPermission(permission)) {
|
||||
router.replace('/dashboard');
|
||||
}
|
||||
}, [isAuthenticated, permission, hasPermission, router]);
|
||||
|
||||
if (!isAuthenticated) return null;
|
||||
if (permission && !hasPermission(permission)) return null;
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -39,49 +39,65 @@ import {
|
||||
import { useAuthStore } from '@/lib/auth-store';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useTheme } from '@/lib/theme-store';
|
||||
import { PERMS } from '@/lib/permissions';
|
||||
|
||||
const navigationSections = [
|
||||
interface NavItem {
|
||||
name: string;
|
||||
href: string;
|
||||
icon: React.ComponentType<{ className?: string }>;
|
||||
permission?: string;
|
||||
}
|
||||
|
||||
const navigationSections: { title: string; items: NavItem[] }[] = [
|
||||
{
|
||||
title: 'Overview',
|
||||
items: [
|
||||
{ name: 'Dashboard', href: '/dashboard', icon: LayoutDashboard },
|
||||
{ name: 'Dashboard', href: '/dashboard', icon: LayoutDashboard, permission: PERMS.dashboard },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Operations',
|
||||
items: [
|
||||
{ name: 'Bookings', href: '/bookings', icon: Ticket },
|
||||
{ name: 'Passengers', href: '/passengers', icon: Users },
|
||||
{ name: 'Tickets', href: '/tickets', icon: FileText },
|
||||
{ name: 'Lugagges', href: '/excess-baggage', icon: Banknote },
|
||||
{ name: 'Bookings', href: '/bookings', icon: Ticket, permission: PERMS.bookings.view },
|
||||
{ name: 'Passengers', href: '/passengers', icon: Users, permission: PERMS.passengers.view },
|
||||
{ name: 'Tickets', href: '/tickets', icon: FileText, permission: PERMS.tickets.view },
|
||||
{ name: 'Luggage', href: '/excess-baggage', icon: Banknote, permission: PERMS.bookings.view },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Tourism',
|
||||
items: [
|
||||
{ name: 'Packages', href: '/packages', icon: Package },
|
||||
{ name: 'Inquiries', href: '/package-inquiries', icon: MessageSquare },
|
||||
{ name: 'Packages', href: '/packages', icon: Package, permission: PERMS.admin },
|
||||
{ name: 'Inquiries', href: '/package-inquiries', icon: MessageSquare, permission: PERMS.admin },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Master Data',
|
||||
items: [
|
||||
{ name: 'Stations', href: '/stations', icon: MapPin },
|
||||
{ name: 'Trains', href: '/trains', icon: Train },
|
||||
{ name: 'Coaches', href: '/coaches', icon: Grid3x3 },
|
||||
{ name: 'Seats', href: '/seats', icon: Armchair },
|
||||
{ name: 'Classes', href: '/classes', icon: Settings },
|
||||
{ name: 'Routes', href: '/routes', icon: Route },
|
||||
{ name: 'Schedules', href: '/schedules', icon: Calendar },
|
||||
{ name: 'Stations', href: '/stations', icon: MapPin, permission: PERMS.admin },
|
||||
{ name: 'Trains', href: '/trains', icon: Train, permission: PERMS.admin },
|
||||
{ name: 'Coaches', href: '/coaches', icon: Grid3x3, permission: PERMS.admin },
|
||||
{ name: 'Seats', href: '/seats', icon: Armchair, permission: PERMS.admin },
|
||||
{ name: 'Classes', href: '/classes', icon: Settings, permission: PERMS.admin },
|
||||
{ name: 'Routes', href: '/routes', icon: Route, permission: PERMS.admin },
|
||||
{ name: 'Schedules', href: '/schedules', icon: Calendar, permission: PERMS.admin },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Financial',
|
||||
items: [
|
||||
{ name: 'Fares', href: '/pricing', icon: DollarSign },
|
||||
{ name: 'Currencies', href: '/currencies', icon: Banknote },
|
||||
{ name: 'Payments', href: '/payments', icon: CreditCard },
|
||||
{ name: 'Promos', href: '/promos', icon: Gift },
|
||||
{ name: 'Pricing & Fares', href: '/pricing', icon: DollarSign, permission: PERMS.admin },
|
||||
{ name: 'Currencies', href: '/currencies', icon: Banknote, permission: PERMS.currencies.manage },
|
||||
{ name: 'Payments', href: '/payments', icon: CreditCard, permission: PERMS.payments.view },
|
||||
{ name: 'Promo Codes', href: '/promos', icon: Gift, permission: PERMS.admin },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Customer Services',
|
||||
items: [
|
||||
{ name: 'Loyalty Program', href: '/loyalty', icon: Gift, permission: PERMS.passengers.view },
|
||||
{ name: 'Support Center', href: '/support', icon: MessageSquare, permission: PERMS.bookings.view },
|
||||
{ name: 'Notifications', href: '/notifications', icon: Bell, permission: PERMS.notifications.send },
|
||||
]
|
||||
},
|
||||
// {
|
||||
@@ -95,32 +111,32 @@ const navigationSections = [
|
||||
{
|
||||
title: 'Security & Compliance',
|
||||
items: [
|
||||
{ name: 'Logs', href: '/audit', icon: AlertTriangle },
|
||||
{ name: 'Fraud', href: '/fraud', icon: Shield },
|
||||
{ name: 'Verifayda', href: '/verifayda', icon: UserCheck },
|
||||
{ name: 'Audit Logs', href: '/audit', icon: AlertTriangle, permission: PERMS.audit.view },
|
||||
{ name: 'Fraud Detection', href: '/fraud', icon: Shield, permission: PERMS.fraud.view },
|
||||
{ name: 'Verifayda Integration', href: '/verifayda', icon: UserCheck, permission: PERMS.admin },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Analytics & Reports',
|
||||
items: [
|
||||
{ name: 'Reports', href: '/reports', icon: BarChart3 },
|
||||
{ name: 'Operational', href: '/operational-reports', icon: FileText },
|
||||
{ name: 'Reports', href: '/reports', icon: BarChart3, permission: PERMS.reports.view },
|
||||
{ name: 'Operational Reports', href: '/operational-reports', icon: FileText, permission: PERMS.reports.view },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'System',
|
||||
items: [
|
||||
{ name: 'Agents', href: '/agents', icon: Briefcase },
|
||||
{ name: 'Users', href: '/settings/users', icon: Users },
|
||||
{ name: 'Settings', href: '/settings', icon: Settings },
|
||||
{ name: 'Health', href: '/health', icon: Activity },
|
||||
{ name: 'Agent Operations', href: '/agents', icon: Briefcase, permission: PERMS.agents.view },
|
||||
{ name: 'User Management', href: '/settings/users', icon: Users, permission: PERMS.admin },
|
||||
{ name: 'Settings', href: '/settings', icon: Settings, permission: PERMS.admin },
|
||||
{ name: 'Health', href: '/health', icon: Activity, permission: PERMS.admin },
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export default function Sidebar() {
|
||||
const pathname = usePathname();
|
||||
const { user, logout } = useAuthStore();
|
||||
const { user, logout, hasPermission } = useAuthStore();
|
||||
const { isDark, toggleTheme } = useTheme();
|
||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||
|
||||
@@ -154,7 +170,12 @@ export default function Sidebar() {
|
||||
|
||||
{/* Navigation */}
|
||||
<nav className="flex-1 overflow-y-auto px-3 py-4 space-y-6">
|
||||
{navigationSections.map((section) => (
|
||||
{navigationSections.map((section) => {
|
||||
const visibleItems = section.items.filter(
|
||||
(item) => !item.permission || hasPermission(item.permission)
|
||||
);
|
||||
if (visibleItems.length === 0) return null;
|
||||
return (
|
||||
<div key={section.title}>
|
||||
{!isCollapsed && (
|
||||
<h3 className="mb-2 px-3 text-xs font-semibold uppercase tracking-wider text-white/60 dark:text-slate-400">
|
||||
@@ -162,7 +183,7 @@ export default function Sidebar() {
|
||||
</h3>
|
||||
)}
|
||||
<div className="space-y-1">
|
||||
{section.items.map((item) => {
|
||||
{visibleItems.map((item) => {
|
||||
// Special handling for Settings to avoid conflict with User Management
|
||||
let isActive;
|
||||
if (item.href === '/settings') {
|
||||
@@ -194,7 +215,8 @@ export default function Sidebar() {
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user