feat: Complete production deployment pipeline with admin dashboard
Some checks failed
ParentFlow CI/CD Pipeline / Backend Tests (push) Has been cancelled
ParentFlow CI/CD Pipeline / Frontend Tests (push) Has been cancelled
ParentFlow CI/CD Pipeline / Security Scanning (push) Has been cancelled
ParentFlow CI/CD Pipeline / Build Docker Images (map[context:maternal-app/maternal-app-backend dockerfile:Dockerfile.production name:backend]) (push) Has been cancelled
ParentFlow CI/CD Pipeline / Build Docker Images (map[context:maternal-web dockerfile:Dockerfile.production name:frontend]) (push) Has been cancelled
ParentFlow CI/CD Pipeline / Deploy to Development (push) Has been cancelled
ParentFlow CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / Lint and Test (push) Has been cancelled
CI/CD Pipeline / E2E Tests (push) Has been cancelled
CI/CD Pipeline / Build Application (push) Has been cancelled
Some checks failed
ParentFlow CI/CD Pipeline / Backend Tests (push) Has been cancelled
ParentFlow CI/CD Pipeline / Frontend Tests (push) Has been cancelled
ParentFlow CI/CD Pipeline / Security Scanning (push) Has been cancelled
ParentFlow CI/CD Pipeline / Build Docker Images (map[context:maternal-app/maternal-app-backend dockerfile:Dockerfile.production name:backend]) (push) Has been cancelled
ParentFlow CI/CD Pipeline / Build Docker Images (map[context:maternal-web dockerfile:Dockerfile.production name:frontend]) (push) Has been cancelled
ParentFlow CI/CD Pipeline / Deploy to Development (push) Has been cancelled
ParentFlow CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / Lint and Test (push) Has been cancelled
CI/CD Pipeline / E2E Tests (push) Has been cancelled
CI/CD Pipeline / Build Application (push) Has been cancelled
- Add unified deployment script with Node.js 22 installation - Create comprehensive database migration script (28 migrations + admin tables) - Add production start/stop scripts for all services - Integrate admin dashboard (parentflow-admin) into PM2 ecosystem - Configure all services: Backend (3020), Frontend (3030), Admin (3335) - Update ecosystem.config.js with admin dashboard configuration - Add invite codes module for user registration management
This commit is contained in:
196
parentflow-admin/src/components/AdminLayout.tsx
Normal file
196
parentflow-admin/src/components/AdminLayout.tsx
Normal file
@@ -0,0 +1,196 @@
|
||||
'use client';
|
||||
|
||||
import { useState, ReactNode } from 'react';
|
||||
import { useRouter, usePathname } from 'next/navigation';
|
||||
import {
|
||||
Box,
|
||||
Drawer,
|
||||
AppBar,
|
||||
Toolbar,
|
||||
List,
|
||||
Typography,
|
||||
IconButton,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Avatar,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Divider,
|
||||
Chip,
|
||||
} from '@mui/material';
|
||||
import {
|
||||
Menu as MenuIcon,
|
||||
Dashboard,
|
||||
People,
|
||||
ConfirmationNumber,
|
||||
Analytics,
|
||||
Settings,
|
||||
Logout,
|
||||
FamilyRestroom,
|
||||
HealthAndSafety,
|
||||
} from '@mui/icons-material';
|
||||
import apiClient from '@/lib/api-client';
|
||||
|
||||
const drawerWidth = 240;
|
||||
|
||||
interface AdminLayoutProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export default function AdminLayout({ children }: AdminLayoutProps) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const [mobileOpen, setMobileOpen] = useState(false);
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
|
||||
const handleDrawerToggle = () => {
|
||||
setMobileOpen(!mobileOpen);
|
||||
};
|
||||
|
||||
const handleLogout = async () => {
|
||||
await apiClient.logout();
|
||||
router.push('/login');
|
||||
};
|
||||
|
||||
const menuItems = [
|
||||
{ text: 'Dashboard', icon: <Dashboard />, path: '/' },
|
||||
{ text: 'Users', icon: <People />, path: '/users' },
|
||||
{ text: 'Families', icon: <FamilyRestroom />, path: '/families' },
|
||||
{ text: 'Invite Codes', icon: <ConfirmationNumber />, path: '/invite-codes' },
|
||||
{ text: 'Analytics', icon: <Analytics />, path: '/analytics' },
|
||||
{ text: 'System Health', icon: <HealthAndSafety />, path: '/health' },
|
||||
{ text: 'Settings', icon: <Settings />, path: '/settings' },
|
||||
];
|
||||
|
||||
const drawer = (
|
||||
<Box>
|
||||
<Toolbar sx={{ justifyContent: 'center', py: 2 }}>
|
||||
<Typography variant="h6" noWrap sx={{ fontWeight: 600, color: 'primary.main' }}>
|
||||
ParentFlow Admin
|
||||
</Typography>
|
||||
</Toolbar>
|
||||
<Divider />
|
||||
<List>
|
||||
{menuItems.map((item) => (
|
||||
<ListItem key={item.text} disablePadding>
|
||||
<ListItemButton
|
||||
onClick={() => router.push(item.path)}
|
||||
selected={pathname === item.path}
|
||||
sx={{
|
||||
'&.Mui-selected': {
|
||||
backgroundColor: 'primary.light',
|
||||
'&:hover': {
|
||||
backgroundColor: 'primary.light',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ListItemIcon sx={{ color: pathname === item.path ? 'primary.main' : 'inherit' }}>
|
||||
{item.icon}
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={item.text} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex' }}>
|
||||
<AppBar
|
||||
position="fixed"
|
||||
sx={{
|
||||
width: { sm: `calc(100% - ${drawerWidth}px)` },
|
||||
ml: { sm: `${drawerWidth}px` },
|
||||
backgroundColor: 'white',
|
||||
color: 'text.primary',
|
||||
}}
|
||||
elevation={1}
|
||||
>
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
aria-label="open drawer"
|
||||
edge="start"
|
||||
onClick={handleDrawerToggle}
|
||||
sx={{ mr: 2, display: { sm: 'none' } }}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<Typography variant="h6" noWrap component="div">
|
||||
{menuItems.find(item => item.path === pathname)?.text || 'Dashboard'}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Chip label="Admin" color="primary" size="small" sx={{ mr: 2 }} />
|
||||
|
||||
<IconButton onClick={(e) => setAnchorEl(e.currentTarget)}>
|
||||
<Avatar sx={{ bgcolor: 'primary.main', width: 32, height: 32 }}>A</Avatar>
|
||||
</IconButton>
|
||||
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={() => setAnchorEl(null)}
|
||||
>
|
||||
<MenuItem onClick={handleLogout}>
|
||||
<ListItemIcon>
|
||||
<Logout fontSize="small" />
|
||||
</ListItemIcon>
|
||||
Logout
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
||||
<Box
|
||||
component="nav"
|
||||
sx={{ width: { sm: drawerWidth }, flexShrink: { sm: 0 } }}
|
||||
>
|
||||
<Drawer
|
||||
variant="temporary"
|
||||
open={mobileOpen}
|
||||
onClose={handleDrawerToggle}
|
||||
ModalProps={{
|
||||
keepMounted: true, // Better open performance on mobile
|
||||
}}
|
||||
sx={{
|
||||
display: { xs: 'block', sm: 'none' },
|
||||
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth },
|
||||
}}
|
||||
>
|
||||
{drawer}
|
||||
</Drawer>
|
||||
<Drawer
|
||||
variant="permanent"
|
||||
sx={{
|
||||
display: { xs: 'none', sm: 'block' },
|
||||
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth },
|
||||
}}
|
||||
open
|
||||
>
|
||||
{drawer}
|
||||
</Drawer>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
component="main"
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
p: 3,
|
||||
width: { sm: `calc(100% - ${drawerWidth}px)` },
|
||||
mt: 8,
|
||||
backgroundColor: 'background.default',
|
||||
minHeight: '100vh',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user