Get user management page working, add permission checking in more places

This commit is contained in:
2025-03-22 22:27:50 -04:00
parent 03dc119a15
commit f421154c1d
9 changed files with 414 additions and 147 deletions

View File

@@ -1,4 +1,19 @@
const pool = global.pool; // Get pool from global or create a new one if not available
let pool;
if (typeof global.pool !== 'undefined') {
pool = global.pool;
} else {
// If global pool is not available, create a new connection
const { Pool } = require('pg');
pool = new Pool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
port: process.env.DB_PORT,
});
console.log('Created new database pool in permissions.js');
}
/** /**
* Check if a user has a specific permission * Check if a user has a specific permission

View File

@@ -1,10 +1,26 @@
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const pool = global.pool;
const bcrypt = require('bcrypt'); const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const { requirePermission, getUserPermissions } = require('./permissions'); const { requirePermission, getUserPermissions } = require('./permissions');
// Get pool from global or create a new one if not available
let pool;
if (typeof global.pool !== 'undefined') {
pool = global.pool;
} else {
// If global pool is not available, create a new connection
const { Pool } = require('pg');
pool = new Pool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
port: process.env.DB_PORT,
});
console.log('Created new database pool in routes.js');
}
// Authentication middleware // Authentication middleware
const authenticate = async (req, res, next) => { const authenticate = async (req, res, next) => {
try { try {

View File

@@ -1,4 +1,4 @@
import { Routes, Route, useNavigate, Navigate } from 'react-router-dom'; import { Routes, Route, useNavigate, Navigate, useLocation } from 'react-router-dom';
import { MainLayout } from './components/layout/MainLayout'; import { MainLayout } from './components/layout/MainLayout';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Products } from './pages/Products'; import { Products } from './pages/Products';
@@ -16,41 +16,59 @@ import { Vendors } from '@/pages/Vendors';
import { Categories } from '@/pages/Categories'; import { Categories } from '@/pages/Categories';
import { Import } from '@/pages/Import'; import { Import } from '@/pages/Import';
import { AiValidationDebug } from "@/pages/AiValidationDebug" import { AiValidationDebug } from "@/pages/AiValidationDebug"
import { AuthProvider } from './contexts/AuthContext';
const queryClient = new QueryClient(); const queryClient = new QueryClient();
function App() { function App() {
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation();
useEffect(() => { useEffect(() => {
const checkAuth = async () => { const checkAuth = async () => {
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
if (token) { const isLoggedIn = sessionStorage.getItem('isLoggedIn') === 'true';
// If we have a token but aren't logged in yet, verify the token
if (token && !isLoggedIn) {
try { try {
const response = await fetch(`${config.authUrl}/me`, { const response = await fetch(`${config.authUrl}/me`, {
headers: { headers: {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}, },
}); });
if (!response.ok) { if (!response.ok) {
localStorage.removeItem('token'); localStorage.removeItem('token');
sessionStorage.removeItem('isLoggedIn'); sessionStorage.removeItem('isLoggedIn');
navigate('/login');
// Only navigate to login if we're not already there
if (!location.pathname.includes('/login')) {
navigate(`/login?redirect=${encodeURIComponent(location.pathname + location.search)}`);
}
} else {
// If token is valid, set the login flag
sessionStorage.setItem('isLoggedIn', 'true');
} }
} catch (error) { } catch (error) {
console.error('Token verification failed:', error); console.error('Token verification failed:', error);
localStorage.removeItem('token'); localStorage.removeItem('token');
sessionStorage.removeItem('isLoggedIn'); sessionStorage.removeItem('isLoggedIn');
navigate('/login');
// Only navigate to login if we're not already there
if (!location.pathname.includes('/login')) {
navigate(`/login?redirect=${encodeURIComponent(location.pathname + location.search)}`);
}
} }
} }
}; };
checkAuth(); checkAuth();
}, [navigate]); }, [navigate, location.pathname, location.search]);
return ( return (
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<AuthProvider>
<Toaster richColors position="top-center" /> <Toaster richColors position="top-center" />
<Routes> <Routes>
<Route path="/login" element={<Login />} /> <Route path="/login" element={<Login />} />
@@ -72,6 +90,7 @@ function App() {
<Route path="*" element={<Navigate to="/" replace />} /> <Route path="*" element={<Navigate to="/" replace />} />
</Route> </Route>
</Routes> </Routes>
</AuthProvider>
</QueryClientProvider> </QueryClientProvider>
); );
} }

View File

@@ -1,9 +1,22 @@
import { Navigate, useLocation } from "react-router-dom" import { Navigate, useLocation } from "react-router-dom"
import { useContext, useEffect } from "react"
import { AuthContext } from "@/contexts/AuthContext"
export function RequireAuth({ children }: { children: React.ReactNode }) { export function RequireAuth({ children }: { children: React.ReactNode }) {
const isLoggedIn = sessionStorage.getItem("isLoggedIn") === "true" const isLoggedIn = sessionStorage.getItem("isLoggedIn") === "true"
const { token, fetchCurrentUser } = useContext(AuthContext)
const location = useLocation() const location = useLocation()
// Check if token exists but we're not logged in
useEffect(() => {
if (token && !isLoggedIn) {
// Verify the token and fetch user data
fetchCurrentUser().catch(() => {
// Do nothing - the AuthContext will handle errors
})
}
}, [token, isLoggedIn, fetchCurrentUser])
if (!isLoggedIn) { if (!isLoggedIn) {
// Redirect to login with the current path in the redirect parameter // Redirect to login with the current path in the redirect parameter
return <Navigate return <Navigate

View File

@@ -24,47 +24,56 @@ import {
SidebarSeparator, SidebarSeparator,
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
import { useLocation, useNavigate, Link } from "react-router-dom"; import { useLocation, useNavigate, Link } from "react-router-dom";
import { PermissionGuard } from "@/components/common/PermissionGuard";
const items = [ const items = [
{ {
title: "Overview", title: "Overview",
icon: Home, icon: Home,
url: "/", url: "/",
permission: "access:dashboard"
}, },
{ {
title: "Products", title: "Products",
icon: Package, icon: Package,
url: "/products", url: "/products",
permission: "access:products"
}, },
{ {
title: "Import", title: "Import",
icon: FileSpreadsheet, icon: FileSpreadsheet,
url: "/import", url: "/import",
permission: "access:import"
}, },
{ {
title: "Forecasting", title: "Forecasting",
icon: IconCrystalBall, icon: IconCrystalBall,
url: "/forecasting", url: "/forecasting",
permission: "access:forecasting"
}, },
{ {
title: "Categories", title: "Categories",
icon: Tags, icon: Tags,
url: "/categories", url: "/categories",
permission: "access:categories"
}, },
{ {
title: "Vendors", title: "Vendors",
icon: Users, icon: Users,
url: "/vendors", url: "/vendors",
permission: "access:vendors"
}, },
{ {
title: "Purchase Orders", title: "Purchase Orders",
icon: ClipboardList, icon: ClipboardList,
url: "/purchase-orders", url: "/purchase-orders",
permission: "access:purchase-orders"
}, },
{ {
title: "Analytics", title: "Analytics",
icon: BarChart2, icon: BarChart2,
url: "/analytics", url: "/analytics",
permission: "access:analytics"
}, },
]; ];
@@ -73,8 +82,8 @@ export function AppSidebar() {
const navigate = useNavigate(); const navigate = useNavigate();
const handleLogout = () => { const handleLogout = () => {
localStorage.removeItem('token');
sessionStorage.removeItem('isLoggedIn'); sessionStorage.removeItem('isLoggedIn');
sessionStorage.removeItem('token');
navigate('/login'); navigate('/login');
}; };
@@ -98,7 +107,12 @@ export function AppSidebar() {
location.pathname === item.url || location.pathname === item.url ||
(item.url !== "/" && location.pathname.startsWith(item.url)); (item.url !== "/" && location.pathname.startsWith(item.url));
return ( return (
<SidebarMenuItem key={item.title}> <PermissionGuard
key={item.title}
permission={item.permission}
fallback={null}
>
<SidebarMenuItem>
<SidebarMenuButton <SidebarMenuButton
asChild asChild
tooltip={item.title} tooltip={item.title}
@@ -112,6 +126,7 @@ export function AppSidebar() {
</Link> </Link>
</SidebarMenuButton> </SidebarMenuButton>
</SidebarMenuItem> </SidebarMenuItem>
</PermissionGuard>
); );
})} })}
</SidebarMenu> </SidebarMenu>
@@ -122,6 +137,10 @@ export function AppSidebar() {
<SidebarGroup> <SidebarGroup>
<SidebarGroupContent> <SidebarGroupContent>
<SidebarMenu> <SidebarMenu>
<PermissionGuard
permission="access:settings"
fallback={null}
>
<SidebarMenuItem> <SidebarMenuItem>
<SidebarMenuButton <SidebarMenuButton
asChild asChild
@@ -136,6 +155,7 @@ export function AppSidebar() {
</Link> </Link>
</SidebarMenuButton> </SidebarMenuButton>
</SidebarMenuItem> </SidebarMenuItem>
</PermissionGuard>
</SidebarMenu> </SidebarMenu>
</SidebarGroupContent> </SidebarGroupContent>
</SidebarGroup> </SidebarGroup>

View File

@@ -1,9 +1,13 @@
import { useState, useEffect } from "react"; import { useState, useEffect, useContext } from "react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Alert, AlertDescription } from "@/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { UserList } from "./UserList"; import { UserList } from "./UserList";
import { UserForm } from "./UserForm"; import { UserForm } from "./UserForm";
import config from "@/config";
import { AuthContext } from "@/contexts/AuthContext";
import { usePermissions } from "@/hooks/usePermissions";
import { ShieldAlert } from "lucide-react";
interface User { interface User {
id: number; id: number;
@@ -29,6 +33,8 @@ interface PermissionCategory {
} }
export function UserManagement() { export function UserManagement() {
const { token, fetchCurrentUser } = useContext(AuthContext);
const { hasPermission } = usePermissions();
const [users, setUsers] = useState<User[]>([]); const [users, setUsers] = useState<User[]>([]);
const [selectedUser, setSelectedUser] = useState<User | null>(null); const [selectedUser, setSelectedUser] = useState<User | null>(null);
const [isAddingUser, setIsAddingUser] = useState(false); const [isAddingUser, setIsAddingUser] = useState(false);
@@ -37,32 +43,67 @@ export function UserManagement() {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
// Fetch users and permissions // Fetch users and permissions
useEffect(() => {
const fetchData = async () => { const fetchData = async () => {
if (!token) {
setError("Authentication required. Please log in again.");
setLoading(false);
return;
}
// The PermissionGuard component already handles permission checks,
// so we don't need to duplicate that logic here
try { try {
setLoading(true);
setError(null);
// Fetch users // Fetch users
const usersResponse = await fetch('/auth-inv/users', { const usersResponse = await fetch(`${config.authUrl}/users`, {
headers: { headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}` 'Authorization': `Bearer ${token}`
} }
}); });
if (!usersResponse.ok) { if (!usersResponse.ok) {
throw new Error('Failed to fetch users'); if (usersResponse.status === 401) {
throw new Error('Authentication failed. Please log in again.');
} else if (usersResponse.status === 403) {
throw new Error('You don\'t have permission to access the user list.');
} else {
// Try to get more detailed error message from response
try {
const errorData = await usersResponse.json();
throw new Error(errorData.error || `Failed to fetch users (${usersResponse.status})`);
} catch (e) {
throw new Error(`Failed to fetch users (${usersResponse.status})`);
}
}
} }
const usersData = await usersResponse.json(); const usersData = await usersResponse.json();
setUsers(usersData); setUsers(usersData);
// Fetch permissions // Fetch permissions
const permissionsResponse = await fetch('/auth-inv/permissions/categories', { const permissionsResponse = await fetch(`${config.authUrl}/permissions/categories`, {
headers: { headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}` 'Authorization': `Bearer ${token}`
} }
}); });
if (!permissionsResponse.ok) { if (!permissionsResponse.ok) {
throw new Error('Failed to fetch permissions'); if (permissionsResponse.status === 401) {
throw new Error('Authentication failed. Please log in again.');
} else if (permissionsResponse.status === 403) {
throw new Error('You don\'t have permission to access permissions.');
} else {
// Try to get more detailed error message from response
try {
const errorData = await permissionsResponse.json();
throw new Error(errorData.error || `Failed to fetch permissions (${permissionsResponse.status})`);
} catch (e) {
throw new Error(`Failed to fetch permissions (${permissionsResponse.status})`);
}
}
} }
const permissionsData = await permissionsResponse.json(); const permissionsData = await permissionsResponse.json();
@@ -73,17 +114,25 @@ export function UserManagement() {
const errorMessage = err instanceof Error ? err.message : 'An error occurred'; const errorMessage = err instanceof Error ? err.message : 'An error occurred';
setError(errorMessage); setError(errorMessage);
setLoading(false); setLoading(false);
// If authentication error, refresh the token
if (err instanceof Error && err.message.includes('Authentication failed')) {
fetchCurrentUser().catch(() => {
// Handle failed token refresh
});
}
} }
}; };
useEffect(() => {
fetchData(); fetchData();
}, []); }, [token]);
const handleEditUser = async (userId: number) => { const handleEditUser = async (userId: number) => {
try { try {
const response = await fetch(`/auth-inv/users/${userId}`, { const response = await fetch(`${config.authUrl}/users/${userId}`, {
headers: { headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}` 'Authorization': `Bearer ${token}`
} }
}); });
@@ -110,15 +159,15 @@ export function UserManagement() {
setLoading(true); setLoading(true);
const endpoint = userData.id const endpoint = userData.id
? `/auth-inv/users/${userData.id}` ? `${config.authUrl}/users/${userData.id}`
: '/auth-inv/users'; : `${config.authUrl}/users`;
const method = userData.id ? 'PUT' : 'POST'; const method = userData.id ? 'PUT' : 'POST';
const response = await fetch(endpoint, { const response = await fetch(endpoint, {
method, method,
headers: { headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`, 'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify(userData) body: JSON.stringify(userData)
@@ -129,20 +178,12 @@ export function UserManagement() {
throw new Error(errorData.error || 'Failed to save user'); throw new Error(errorData.error || 'Failed to save user');
} }
// Refresh user list // Refresh user list after a successful save
const usersResponse = await fetch('/auth-inv/users', { await fetchData();
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
const updatedUsers = await usersResponse.json();
setUsers(updatedUsers);
// Reset form state // Reset form state
setSelectedUser(null); setSelectedUser(null);
setIsAddingUser(false); setIsAddingUser(false);
setLoading(false);
} catch (err) { } catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Failed to save user'; const errorMessage = err instanceof Error ? err.message : 'Failed to save user';
setError(errorMessage); setError(errorMessage);
@@ -158,10 +199,10 @@ export function UserManagement() {
try { try {
setLoading(true); setLoading(true);
const response = await fetch(`/auth-inv/users/${userId}`, { const response = await fetch(`${config.authUrl}/users/${userId}`, {
method: 'DELETE', method: 'DELETE',
headers: { headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}` 'Authorization': `Bearer ${token}`
} }
}); });
@@ -169,16 +210,8 @@ export function UserManagement() {
throw new Error('Failed to delete user'); throw new Error('Failed to delete user');
} }
// Refresh user list // Refresh user list after a successful delete
const usersResponse = await fetch('/auth-inv/users', { await fetchData();
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
}
});
const updatedUsers = await usersResponse.json();
setUsers(updatedUsers);
setLoading(false);
} catch (err) { } catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Failed to delete user'; const errorMessage = err instanceof Error ? err.message : 'Failed to delete user';
setError(errorMessage); setError(errorMessage);
@@ -207,9 +240,14 @@ export function UserManagement() {
return ( return (
<Card> <Card>
<CardContent className="pt-6"> <CardContent className="pt-6">
<Alert variant="destructive"> <Alert variant="destructive" className="mb-4">
<ShieldAlert className="h-4 w-4" />
<AlertTitle>Permission Error</AlertTitle>
<AlertDescription>{error}</AlertDescription> <AlertDescription>{error}</AlertDescription>
</Alert> </Alert>
<div className="flex justify-center">
<Button onClick={fetchData}>Retry</Button>
</div>
</CardContent> </CardContent>
</Card> </Card>
); );

View File

@@ -1,4 +1,5 @@
import { createContext, useState, useEffect, ReactNode } from 'react'; import { createContext, useState, useEffect, ReactNode, useCallback } from 'react';
import config from '@/config';
export interface Permission { export interface Permission {
id: number; id: number;
@@ -44,52 +45,61 @@ export function AuthProvider({ children }: { children: ReactNode }) {
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
// Load token from localStorage on init const fetchCurrentUser = useCallback(async () => {
useEffect(() => {
const storedToken = localStorage.getItem('token');
if (storedToken) {
setToken(storedToken);
fetchCurrentUser();
}
}, []);
const fetchCurrentUser = async () => {
if (!token) return; if (!token) return;
try { try {
setIsLoading(true); setIsLoading(true);
setError(null); setError(null);
const response = await fetch('/auth-inv/me', { const response = await fetch(`${config.authUrl}/me`, {
headers: { headers: {
'Authorization': `Bearer ${token}` 'Authorization': `Bearer ${token}`
} }
}); });
if (!response.ok) { if (!response.ok) {
throw new Error('Failed to fetch user data'); const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.error || 'Failed to fetch user data');
} }
const userData = await response.json(); const userData = await response.json();
setUser(userData); setUser(userData);
// Ensure we have the sessionStorage isLoggedIn flag set
sessionStorage.setItem('isLoggedIn', 'true');
} catch (err) { } catch (err) {
const errorMessage = err instanceof Error ? err.message : 'An unknown error occurred'; const errorMessage = err instanceof Error ? err.message : 'An unknown error occurred';
setError(errorMessage); setError(errorMessage);
console.error('Auth error:', errorMessage);
// Clear token if authentication failed // Clear token if authentication failed
if (err instanceof Error && err.message.includes('authentication')) { if (err instanceof Error &&
(err.message.includes('authentication') ||
err.message.includes('token') ||
err.message.includes('401'))) {
logout(); logout();
} }
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
}; }, [token]);
// Load token and fetch user data on init
useEffect(() => {
if (token) {
fetchCurrentUser();
} else {
// Clear sessionStorage if no token exists
sessionStorage.removeItem('isLoggedIn');
}
}, [token, fetchCurrentUser]);
const login = async (username: string, password: string) => { const login = async (username: string, password: string) => {
try { try {
setIsLoading(true); setIsLoading(true);
setError(null); setError(null);
const response = await fetch('/auth-inv/login', { const response = await fetch(`${config.authUrl}/login`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@@ -98,18 +108,20 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}); });
if (!response.ok) { if (!response.ok) {
const errorData = await response.json(); const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.error || 'Login failed'); throw new Error(errorData.error || 'Login failed');
} }
const data = await response.json(); const data = await response.json();
localStorage.setItem('token', data.token); localStorage.setItem('token', data.token);
sessionStorage.setItem('isLoggedIn', 'true');
setToken(data.token); setToken(data.token);
setUser(data.user); setUser(data.user);
} catch (err) { } catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Login failed'; const errorMessage = err instanceof Error ? err.message : 'Login failed';
setError(errorMessage); setError(errorMessage);
console.error('Login error:', errorMessage);
throw err; throw err;
} finally { } finally {
setIsLoading(false); setIsLoading(false);
@@ -118,6 +130,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
const logout = () => { const logout = () => {
localStorage.removeItem('token'); localStorage.removeItem('token');
sessionStorage.removeItem('isLoggedIn');
setToken(null); setToken(null);
setUser(null); setUser(null);
}; };

View File

@@ -0,0 +1,101 @@
import { usePermissions } from './usePermissions';
import { useNavigate } from 'react-router-dom';
import { useEffect, useState } from 'react';
interface PagePermissionConfig {
// The permission required to access the specific page
permission?: string;
// Array of permissions where ANY must be present
anyPermissions?: string[];
// Array of permissions where ALL must be present
allPermissions?: string[];
// Whether this page is admin-only
adminOnly?: boolean;
// Page identifier for page-specific access check
page?: string;
// Redirect path if permission check fails
redirectTo?: string;
}
/**
* Hook to check if a user has permission to access a specific page
* Will automatically redirect if permission is denied
*/
export function usePagePermission(config: PagePermissionConfig) {
const {
permission,
anyPermissions,
allPermissions,
adminOnly = false,
page,
redirectTo = '/'
} = config;
const navigate = useNavigate();
const {
hasPermission,
hasPageAccess,
hasAnyPermission,
hasAllPermissions,
isAdmin
} = usePermissions();
const [hasAccess, setHasAccess] = useState<boolean | null>(null);
useEffect(() => {
// Check permissions
let permitted = true;
// Admin check
if (adminOnly && !isAdmin) {
permitted = false;
}
// Page access check
if (page && !hasPageAccess(page)) {
permitted = false;
}
// Single permission check
if (permission && !hasPermission(permission)) {
permitted = false;
}
// Any permissions check
if (anyPermissions && !hasAnyPermission(anyPermissions)) {
permitted = false;
}
// All permissions check
if (allPermissions && !hasAllPermissions(allPermissions)) {
permitted = false;
}
setHasAccess(permitted);
// Redirect if no permission
if (permitted === false) {
navigate(redirectTo);
}
}, [
permission,
anyPermissions,
allPermissions,
adminOnly,
page,
redirectTo,
hasPermission,
hasPageAccess,
hasAnyPermission,
hasAllPermissions,
isAdmin,
navigate
]);
return hasAccess;
}

View File

@@ -6,8 +6,24 @@ import { CalculationSettings } from "@/components/settings/CalculationSettings";
import { TemplateManagement } from "@/components/settings/TemplateManagement"; import { TemplateManagement } from "@/components/settings/TemplateManagement";
import { UserManagement } from "@/components/settings/UserManagement"; import { UserManagement } from "@/components/settings/UserManagement";
import { motion } from 'motion/react'; import { motion } from 'motion/react';
import { PermissionGuard } from "@/components/common/PermissionGuard";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { usePagePermission } from "@/hooks/usePagePermission";
import { useEffect } from "react";
export function Settings() { export function Settings() {
// Check if the user has permission to access the Settings page
// This will automatically redirect if permission is denied
const hasAccess = usePagePermission({
page: 'settings',
redirectTo: '/'
});
// Prevent flash of content before redirect
if (hasAccess === false) {
return null;
}
return ( return (
<motion.div layout className="container mx-auto py-6"> <motion.div layout className="container mx-auto py-6">
<div className="mb-6"> <div className="mb-6">
@@ -27,9 +43,14 @@ export function Settings() {
<TabsTrigger value="templates"> <TabsTrigger value="templates">
Template Management Template Management
</TabsTrigger> </TabsTrigger>
<PermissionGuard
permission="manage:users"
fallback={null}
>
<TabsTrigger value="user-management"> <TabsTrigger value="user-management">
User Management User Management
</TabsTrigger> </TabsTrigger>
</PermissionGuard>
</TabsList> </TabsList>
<TabsContent value="data-management"> <TabsContent value="data-management">
@@ -53,7 +74,18 @@ export function Settings() {
</TabsContent> </TabsContent>
<TabsContent value="user-management"> <TabsContent value="user-management">
<PermissionGuard
permission="manage:users"
fallback={
<Alert>
<AlertDescription>
You don't have permission to access User Management.
</AlertDescription>
</Alert>
}
>
<UserManagement /> <UserManagement />
</PermissionGuard>
</TabsContent> </TabsContent>
</Tabs> </Tabs>
</motion.div> </motion.div>