import { SnackbarProvider } from 'notistack';
import { CssBaseline, ThemeProvider, createTheme } from '@mui/material';
import AppHeader from './app/components/AppHeader';
+import { AuthProvider } from './app/components/AuthContext';
const theme = createTheme({
palette: {
<ThemeProvider theme={theme}>
<CssBaseline />
<SnackbarProvider maxSnack={3}>
- <AppHeader />
- <AppRoutes />
+ <AuthProvider>
+ <AppHeader />
+ <AppRoutes />
+ </AuthProvider>
</SnackbarProvider>
</ThemeProvider>
);
* @file Reusable header component with navigation
*/
-import React, { useContext } from 'react';
+import React, { useContext, useMemo } from 'react';
import { AppBar, Toolbar, Typography, Button, Box } from '@mui/material';
import { useNavigate } from 'react-router-dom';
import { AuthContext } from './AuthContext';
const navigate = useNavigate();
const { user, logout } = useContext(AuthContext) || {};
- const navItems = [
+ const navItems = useMemo( () => user ? [
{ label: 'Dashboard', path: '/', roles: ['User', 'Admin'] },
{ label: 'Messages', path: '/messages', roles: ['User', 'Admin'] },
{ label: 'Message Groups', path: '/message-groups', roles: ['User', 'Admin'] },
{ label: 'Media', path: '/media', roles: ['User', 'Admin'] },
{ label: 'Posts', path: '/posts', roles: ['User', 'Admin'] },
{ label: 'Docker', path: '/docker', roles: ['Admin'] },
- ];
+ { label: 'VPN', path: '/vpn', roles: ['Admin'] },
+ { label: 'GIT', path: '/git', roles: ['Admin'] },
+ ].filter(item => item.roles.some( role => [...user.roles]?.map( r => r.name )?.includes( role ) ) ) : [], [user] );
+
+ /**
+ *
+ * @type {React.ReactNode[]}
+ */
+ const navButtons = useMemo( () => navItems.map((item) => (
+ (
+ <Button
+ key={item.label}
+ color="inherit"
+ onClick={() => navigate(item.path)}
+ className="mx-2"
+ >
+ {item.label}
+ </Button>
+ )
+ )), [navItems, navigate] );
return (
<AppBar position="static" className="bg-blue-600">
PHS Admin
</Typography>
<Box>
- {navItems.map((item) => (
- (!user || item.roles.some(role => user.roles?.includes(role))) && (
- <Button
- key={item.label}
- color="inherit"
- onClick={() => navigate(item.path)}
- className="mx-2"
- >
- {item.label}
- </Button>
- )
- ))}
+ {navButtons}
{user ? (
<Button color="inherit" onClick={logout}>
Logout
import axios from 'axios';
-const BASE_URL = 'http://localhost:3601';
+const BASE_URL = 'https://api.phasecustomsoft.com';
const getAuthHeaders = () => ({
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
* @file Dashboard view with navigation to main features
*/
-import React, { useContext } from 'react';
+import React, { useContext, useMemo } from 'react';
import { Container, Grid, Card, CardContent, Typography, Button } from '@mui/material';
import { useNavigate } from 'react-router-dom';
import { AuthContext } from '../../components/AuthContext';
-import AppHeader from '../../components/AppHeader';
const Dashboard = () => {
const navigate = useNavigate();
const { user } = useContext(AuthContext) || {};
- const features = [
+ console.log(user);
+
+ const features = useMemo( () => user ? [
{ title: 'Messages', path: '/messages', description: 'View and send messages', roles: ['User', 'Admin'] },
{ title: 'Message Groups', path: '/message-groups', description: 'Manage group chats', roles: ['User', 'Admin'] },
{ title: 'Media', path: '/media', description: 'Upload and view media', roles: ['User', 'Admin'] },
{ title: 'Posts', path: '/posts', description: 'Create and view posts', roles: ['User', 'Admin'] },
{ title: 'Docker', path: '/docker', description: 'Manage Docker containers', roles: ['Admin'] },
- ];
+ { title: 'VPN', path: '/vpn', description: 'Manage OpenVPN Connections and Clients', roles: ['Admin'] },
+ { title: 'GIT', path: '/git', description: 'Manage GIT repositories', roles: ['Admin'] },
+ ].filter(feature => feature.roles.some( role => [...user.roles]?.map( r => r.name )?.includes( role ) ) ) : [], [user] );
+
+ /**
+ *
+ * @type {unknown[]}
+ */
+ const featureCards = useMemo( () => features.map((feature) => (
+ (
+ <Grid item xs={12} sm= {6} md={4} key={feature.title}>
+ <Card className="shadow-md rounded-lg">
+ <CardContent>
+ <Typography variant="h6" className="font-semiold">
+ {feature.title}
+ </Typography>
+ <Typography variant="body2" className="text-gray-600 mb-4">
+ {feature.description}
+ </Typography>
+ <Button
+ variant="contained"
+ color="primary"
+ onClick={() => navigate(feature.path)}
+ >
+ Go to {feature.title}
+ </Button>
+ </CardContent>
+ </Card>
+ </Grid>
+ )
+ )), [features, navigate] );
return (
<div>
Welcome to PHS Admin{user ? `, ${user.first_name || 'User ' + user.id}` : ''}
</Typography>
<Grid container spacing={3}>
- {features.map((feature) => (
- (!user || feature.roles.some(role => user.roles?.includes(role))) && (
- <Grid item xs={12} sm= {6} md={4} key={feature.title}>
- <Card className="shadow-md rounded-lg">
- <CardContent>
- <Typography variant="h6" className="font-semibold">
- {feature.title}
- </Typography>
- <Typography variant="body2" className="text-gray-600 mb-4">
- {feature.description}
- </Typography>
- <Button
- variant="contained"
- color="primary"
- onClick={() => navigate(feature.path)}
- >
- Go to {feature.title}
- </Button>
- </CardContent>
- </Card>
- </Grid>
- )
- ))}
+ {featureCards}
</Grid>
</Container>
</div>
/**
- * @file Login view with authentication form
+ * @file Login view with an authentication form
*/
import React, { useContext } from 'react';
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';
import { useSnackbar } from 'notistack';
-import { useNavigate } from 'react-router-dom';
import { AuthContext } from '../../components/AuthContext';
-import AppHeader from '../../components/AppHeader';
const schema = yup.object({
email: yup.string().email('Invalid email').required('Email is required'),
const Login = () => {
const { enqueueSnackbar } = useSnackbar();
- const navigate = useNavigate();
const { login } = useContext(AuthContext) || {};
const { control, handleSubmit, formState: { errors } } = useForm({
resolver: yupResolver(schema),
return (
<div>
- <AppHeader />
<Container maxWidth="sm" className="container">
<Typography variant="h4" className="text-3xl font-bold mb-6 mt-4">
Login to PHS Admin