Conversation
This commit introduces a new admin dashboard with the following features: - User management (view, update role, delete) - Public chart management (view, delete) - File management (view, delete) - Analytics overview The backend has been updated with new admin-only API endpoints, and the frontend includes a new /admin route protected by an AdminRoute component.
❌ Deploy Preview for excel-data-analytics failed. Why did it fail? →
|
There was a problem hiding this comment.
Pull Request Overview
This PR implements a comprehensive admin dashboard feature that provides administrators with centralized management capabilities for users, public charts, files, and analytics. The implementation includes both frontend components for the admin interface and backend API endpoints with proper authentication and authorization.
- Adds admin role-based access control with protected routes and middleware
- Creates a tabbed admin dashboard interface for managing users, charts, files, and viewing analytics
- Implements secure backend admin API endpoints with JWT authentication and role verification
Reviewed Changes
Copilot reviewed 14 out of 15 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/pages/AdminDashboard.jsx | Main admin dashboard component with tabbed interface |
| src/components/auth/AuthContext.jsx | Enhanced to fetch user profile with role information |
| src/components/auth/AdminRoute.jsx | Route protection component for admin-only pages |
| src/components/admin/UserManagement.jsx | User management interface with role updates and deletion |
| src/components/admin/FileManagement.jsx | File management interface for viewing and deleting files |
| src/components/admin/ChartManagement.jsx | Chart management interface for public chart operations |
| src/components/admin/Analytics.jsx | Analytics overview displaying system metrics |
| src/App.jsx | Adds admin route configuration |
| server/routes/userProfile.js | Adds authentication and authorization to existing endpoints |
| server/routes/admin.js | New admin API endpoints with role-based access control |
| server/models/userProfile.model.js | Adds role field to user profile schema |
| server/middleware/auth.js | JWT authentication middleware |
| server/index.js | Registers admin router |
| package.json | Adds express-oauth2-jwt-bearer dependency |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
|
|
||
| useEffect(() => { | ||
| fetchUsers(); | ||
| }, []); |
There was a problem hiding this comment.
The fetchUsers function should be included in the useEffect dependency array to follow React hooks best practices and prevent potential stale closure issues.
|
|
||
| useEffect(() => { | ||
| fetchFiles(); | ||
| }, []); |
There was a problem hiding this comment.
The fetchFiles function should be included in the useEffect dependency array to follow React hooks best practices and prevent potential stale closure issues.
|
|
||
| useEffect(() => { | ||
| fetchCharts(); | ||
| }, []); |
There was a problem hiding this comment.
The fetchCharts function should be included in the useEffect dependency array to follow React hooks best practices and prevent potential stale closure issues.
| }; | ||
|
|
||
| fetchAnalytics(); | ||
| }, []); |
There was a problem hiding this comment.
The fetchAnalytics function should be included in the useEffect dependency array to follow React hooks best practices and prevent potential stale closure issues.
| }, []); | |
| }, [getAccessTokenSilently]); |
| try { | ||
| const charts = await SavedChart.find({ isPublic: true }); | ||
| res.json(charts); | ||
| } catch (error) { | ||
| res.status(500).json({ message: 'Error fetching public charts' }); | ||
| } | ||
| }); | ||
|
|
||
| router.delete('/charts/:chartId', async (req, res) => { | ||
| try { | ||
| const deletedChart = await SavedChart.findByIdAndDelete(req.params.chartId); | ||
| if (!deletedChart) { | ||
| return res.status(404).json({ message: 'Chart not found' }); | ||
| } | ||
| res.json({ message: 'Chart deleted successfully' }); | ||
| } catch (error) { | ||
| res.status(500).json({ message: 'Error deleting chart' }); | ||
| } | ||
| }); | ||
|
|
||
| // File Management | ||
| router.get('/files', async (req, res) => { | ||
| try { | ||
| const files = await FileHistory.find(); | ||
| res.json(files); | ||
| } catch (error) { | ||
| res.status(500).json({ message: 'Error fetching files' }); | ||
| } | ||
| }); | ||
|
|
||
| router.delete('/files/:fileId', async (req, res) => { | ||
| try { | ||
| const deletedFile = await FileHistory.findByIdAndDelete(req.params.fileId); | ||
| if (!deletedFile) { | ||
| return res.status(404).json({ message: 'File not found' }); | ||
| } | ||
| res.json({ message: 'File deleted successfully' }); | ||
| } catch (error) { | ||
| res.status(500).json({ message: 'Error deleting file' }); | ||
| } |
There was a problem hiding this comment.
Inconsistent indentation using spaces instead of the established 2-space pattern used throughout the rest of the file.
| try { | |
| const charts = await SavedChart.find({ isPublic: true }); | |
| res.json(charts); | |
| } catch (error) { | |
| res.status(500).json({ message: 'Error fetching public charts' }); | |
| } | |
| }); | |
| router.delete('/charts/:chartId', async (req, res) => { | |
| try { | |
| const deletedChart = await SavedChart.findByIdAndDelete(req.params.chartId); | |
| if (!deletedChart) { | |
| return res.status(404).json({ message: 'Chart not found' }); | |
| } | |
| res.json({ message: 'Chart deleted successfully' }); | |
| } catch (error) { | |
| res.status(500).json({ message: 'Error deleting chart' }); | |
| } | |
| }); | |
| // File Management | |
| router.get('/files', async (req, res) => { | |
| try { | |
| const files = await FileHistory.find(); | |
| res.json(files); | |
| } catch (error) { | |
| res.status(500).json({ message: 'Error fetching files' }); | |
| } | |
| }); | |
| router.delete('/files/:fileId', async (req, res) => { | |
| try { | |
| const deletedFile = await FileHistory.findByIdAndDelete(req.params.fileId); | |
| if (!deletedFile) { | |
| return res.status(404).json({ message: 'File not found' }); | |
| } | |
| res.json({ message: 'File deleted successfully' }); | |
| } catch (error) { | |
| res.status(500).json({ message: 'Error deleting file' }); | |
| } | |
| try { | |
| const charts = await SavedChart.find({ isPublic: true }); | |
| res.json(charts); | |
| } catch (error) { | |
| res.status(500).json({ message: 'Error fetching public charts' }); | |
| } | |
| }); | |
| router.delete('/charts/:chartId', async (req, res) => { | |
| try { | |
| const deletedChart = await SavedChart.findByIdAndDelete(req.params.chartId); | |
| if (!deletedChart) { | |
| return res.status(404).json({ message: 'Chart not found' }); | |
| } | |
| res.json({ message: 'Chart deleted successfully' }); | |
| } catch (error) { | |
| res.status(500).json({ message: 'Error deleting chart' }); | |
| } | |
| }); | |
| // File Management | |
| router.get('/files', async (req, res) => { | |
| try { | |
| const files = await FileHistory.find(); | |
| res.json(files); | |
| } catch (error) { | |
| res.status(500).json({ message: 'Error fetching files' }); | |
| } | |
| }); | |
| router.delete('/files/:fileId', async (req, res) => { | |
| try { | |
| const deletedFile = await FileHistory.findByIdAndDelete(req.params.fileId); | |
| if (!deletedFile) { | |
| return res.status(404).json({ message: 'File not found' }); | |
| } | |
| res.json({ message: 'File deleted successfully' }); | |
| } catch (error) { | |
| res.status(500).json({ message: 'Error deleting file' }); | |
| } |
| try { | ||
| const charts = await SavedChart.find({ isPublic: true }); | ||
| res.json(charts); | ||
| } catch (error) { | ||
| res.status(500).json({ message: 'Error fetching public charts' }); | ||
| } | ||
| }); | ||
|
|
||
| router.delete('/charts/:chartId', async (req, res) => { | ||
| try { | ||
| const deletedChart = await SavedChart.findByIdAndDelete(req.params.chartId); | ||
| if (!deletedChart) { | ||
| return res.status(404).json({ message: 'Chart not found' }); | ||
| } | ||
| res.json({ message: 'Chart deleted successfully' }); | ||
| } catch (error) { | ||
| res.status(500).json({ message: 'Error deleting chart' }); | ||
| } |
There was a problem hiding this comment.
Inconsistent indentation using spaces instead of the established 2-space pattern used throughout the rest of the file.
| try { | |
| const charts = await SavedChart.find({ isPublic: true }); | |
| res.json(charts); | |
| } catch (error) { | |
| res.status(500).json({ message: 'Error fetching public charts' }); | |
| } | |
| }); | |
| router.delete('/charts/:chartId', async (req, res) => { | |
| try { | |
| const deletedChart = await SavedChart.findByIdAndDelete(req.params.chartId); | |
| if (!deletedChart) { | |
| return res.status(404).json({ message: 'Chart not found' }); | |
| } | |
| res.json({ message: 'Chart deleted successfully' }); | |
| } catch (error) { | |
| res.status(500).json({ message: 'Error deleting chart' }); | |
| } | |
| try { | |
| const charts = await SavedChart.find({ isPublic: true }); | |
| res.json(charts); | |
| } catch (error) { | |
| res.status(500).json({ message: 'Error fetching public charts' }); | |
| } | |
| }); | |
| router.delete('/charts/:chartId', async (req, res) => { | |
| try { | |
| const deletedChart = await SavedChart.findByIdAndDelete(req.params.chartId); | |
| if (!deletedChart) { | |
| return res.status(404).json({ message: 'Chart not found' }); | |
| } | |
| res.json({ message: 'Chart deleted successfully' }); | |
| } catch (error) { | |
| res.status(500).json({ message: 'Error deleting chart' }); | |
| } |
| try { | ||
| const charts = await SavedChart.find({ isPublic: true }); | ||
| res.json(charts); | ||
| } catch (error) { | ||
| res.status(500).json({ message: 'Error fetching public charts' }); | ||
| } | ||
| }); | ||
|
|
||
| router.delete('/charts/:chartId', async (req, res) => { | ||
| try { | ||
| const deletedChart = await SavedChart.findByIdAndDelete(req.params.chartId); | ||
| if (!deletedChart) { | ||
| return res.status(404).json({ message: 'Chart not found' }); | ||
| } | ||
| res.json({ message: 'Chart deleted successfully' }); | ||
| } catch (error) { | ||
| res.status(500).json({ message: 'Error deleting chart' }); | ||
| } | ||
| }); | ||
|
|
||
| // File Management | ||
| router.get('/files', async (req, res) => { | ||
| try { | ||
| const files = await FileHistory.find(); | ||
| res.json(files); | ||
| } catch (error) { | ||
| res.status(500).json({ message: 'Error fetching files' }); | ||
| } | ||
| }); | ||
|
|
||
| router.delete('/files/:fileId', async (req, res) => { | ||
| try { | ||
| const deletedFile = await FileHistory.findByIdAndDelete(req.params.fileId); | ||
| if (!deletedFile) { | ||
| return res.status(404).json({ message: 'File not found' }); | ||
| } | ||
| res.json({ message: 'File deleted successfully' }); | ||
| } catch (error) { | ||
| res.status(500).json({ message: 'Error deleting file' }); | ||
| } |
There was a problem hiding this comment.
Inconsistent indentation using spaces instead of the established 2-space pattern used throughout the rest of the file.
| try { | |
| const charts = await SavedChart.find({ isPublic: true }); | |
| res.json(charts); | |
| } catch (error) { | |
| res.status(500).json({ message: 'Error fetching public charts' }); | |
| } | |
| }); | |
| router.delete('/charts/:chartId', async (req, res) => { | |
| try { | |
| const deletedChart = await SavedChart.findByIdAndDelete(req.params.chartId); | |
| if (!deletedChart) { | |
| return res.status(404).json({ message: 'Chart not found' }); | |
| } | |
| res.json({ message: 'Chart deleted successfully' }); | |
| } catch (error) { | |
| res.status(500).json({ message: 'Error deleting chart' }); | |
| } | |
| }); | |
| // File Management | |
| router.get('/files', async (req, res) => { | |
| try { | |
| const files = await FileHistory.find(); | |
| res.json(files); | |
| } catch (error) { | |
| res.status(500).json({ message: 'Error fetching files' }); | |
| } | |
| }); | |
| router.delete('/files/:fileId', async (req, res) => { | |
| try { | |
| const deletedFile = await FileHistory.findByIdAndDelete(req.params.fileId); | |
| if (!deletedFile) { | |
| return res.status(404).json({ message: 'File not found' }); | |
| } | |
| res.json({ message: 'File deleted successfully' }); | |
| } catch (error) { | |
| res.status(500).json({ message: 'Error deleting file' }); | |
| } | |
| try { | |
| const charts = await SavedChart.find({ isPublic: true }); | |
| res.json(charts); | |
| } catch (error) { | |
| res.status(500).json({ message: 'Error fetching public charts' }); | |
| } | |
| }); | |
| router.delete('/charts/:chartId', async (req, res) => { | |
| try { | |
| const deletedChart = await SavedChart.findByIdAndDelete(req.params.chartId); | |
| if (!deletedChart) { | |
| return res.status(404).json({ message: 'Chart not found' }); | |
| } | |
| res.json({ message: 'Chart deleted successfully' }); | |
| } catch (error) { | |
| res.status(500).json({ message: 'Error deleting chart' }); | |
| } | |
| }); | |
| // File Management | |
| router.get('/files', async (req, res) => { | |
| try { | |
| const files = await FileHistory.find(); | |
| res.json(files); | |
| } catch (error) { | |
| res.status(500).json({ message: 'Error fetching files' }); | |
| } | |
| }); | |
| router.delete('/files/:fileId', async (req, res) => { | |
| try { | |
| const deletedFile = await FileHistory.findByIdAndDelete(req.params.fileId); | |
| if (!deletedFile) { | |
| return res.status(404).json({ message: 'File not found' }); | |
| } | |
| res.json({ message: 'File deleted successfully' }); | |
| } catch (error) { | |
| res.status(500).json({ message: 'Error deleting file' }); | |
| } |
| router.delete('/files/:fileId', async (req, res) => { | ||
| try { | ||
| const deletedFile = await FileHistory.findByIdAndDelete(req.params.fileId); | ||
| if (!deletedFile) { | ||
| return res.status(404).json({ message: 'File not found' }); | ||
| } | ||
| res.json({ message: 'File deleted successfully' }); | ||
| } catch (error) { | ||
| res.status(500).json({ message: 'Error deleting file' }); | ||
| } | ||
| }); |
There was a problem hiding this comment.
Inconsistent indentation using spaces instead of the established 2-space pattern used throughout the rest of the file.
This commit introduces a new admin dashboard with the following features:
The backend has been updated with new admin-only API endpoints, and the frontend includes a new /admin route protected by an AdminRoute component.