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

- 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:
2025-10-06 22:43:28 +00:00
parent 560fd22023
commit 4e19b992df
37 changed files with 6767 additions and 675 deletions

View 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>
);
}