feat: Fix invite-codes page with AdminGuard and UI improvements
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
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
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

Backend changes:
- Added AdminGuard to InviteCodesController for authentication
- Added PATCH endpoint alongside PUT for invite code updates
- Secured all admin invite code endpoints

Frontend changes:
- Removed deprecated MUI Grid v1, replaced with CSS Grid
- Added thousand separators to all numbers (total codes, uses, etc.)
- Removed Grid import (no longer used)
- Applied .toLocaleString() to stats cards and DataGrid uses column
- Fixed responsive layout with CSS Grid breakpoints
This commit is contained in:
Andrei
2025-10-08 09:08:57 +00:00
parent 7d0d199e64
commit 45150860ce
2 changed files with 45 additions and 46 deletions

View File

@@ -9,11 +9,14 @@ import {
Query, Query,
UseGuards, UseGuards,
Request, Request,
Patch,
} from '@nestjs/common'; } from '@nestjs/common';
import { InviteCodesService } from './invite-codes.service'; import { InviteCodesService } from './invite-codes.service';
import { CreateInviteCodeDto, UpdateInviteCodeDto } from './invite-codes.dto'; import { CreateInviteCodeDto, UpdateInviteCodeDto } from './invite-codes.dto';
import { AdminGuard } from '../../common/guards/admin.guard';
@Controller('api/v1/admin/invite-codes') @Controller('api/v1/admin/invite-codes')
@UseGuards(AdminGuard)
export class InviteCodesController { export class InviteCodesController {
constructor(private readonly inviteCodesService: InviteCodesService) {} constructor(private readonly inviteCodesService: InviteCodesService) {}
@@ -57,6 +60,11 @@ export class InviteCodesController {
return this.inviteCodesService.update(id, dto); return this.inviteCodesService.update(id, dto);
} }
@Patch(':id')
async patch(@Param('id') id: string, @Body() dto: UpdateInviteCodeDto) {
return this.inviteCodesService.update(id, dto);
}
@Delete(':id') @Delete(':id')
async remove(@Param('id') id: string) { async remove(@Param('id') id: string) {
await this.inviteCodesService.delete(id); await this.inviteCodesService.delete(id);

View File

@@ -17,7 +17,6 @@ import {
Switch, Switch,
Alert, Alert,
Tooltip, Tooltip,
Grid,
} from '@mui/material'; } from '@mui/material';
import { DataGrid, GridColDef } from '@mui/x-data-grid'; import { DataGrid, GridColDef } from '@mui/x-data-grid';
import { import {
@@ -178,7 +177,7 @@ export default function InviteCodesPage() {
width: 100, width: 100,
renderCell: (params) => ( renderCell: (params) => (
<Typography variant="body2"> <Typography variant="body2">
{params.value} / {params.row.maxUses || '∞'} {params.value.toLocaleString()} / {params.row.maxUses?.toLocaleString() || '∞'}
</Typography> </Typography>
), ),
}, },
@@ -255,50 +254,42 @@ export default function InviteCodesPage() {
</Box> </Box>
</Box> </Box>
<Grid container spacing={3} sx={{ mb: 3 }}> <Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', gap: 3, mb: 3 }}>
<Grid item xs={12} sm={6} md={3}> <Paper sx={{ p: 2 }}>
<Paper sx={{ p: 2 }}> <Typography variant="body2" color="text.secondary" gutterBottom>
<Typography variant="body2" color="text.secondary" gutterBottom> Total Codes
Total Codes </Typography>
</Typography> <Typography variant="h4">{inviteCodes.length.toLocaleString()}</Typography>
<Typography variant="h4">{inviteCodes.length}</Typography> </Paper>
</Paper> <Paper sx={{ p: 2 }}>
</Grid> <Typography variant="body2" color="text.secondary" gutterBottom>
<Grid item xs={12} sm={6} md={3}> Active Codes
<Paper sx={{ p: 2 }}> </Typography>
<Typography variant="body2" color="text.secondary" gutterBottom> <Typography variant="h4">
Active Codes {inviteCodes.filter(c => c.isActive).length.toLocaleString()}
</Typography> </Typography>
<Typography variant="h4"> </Paper>
{inviteCodes.filter(c => c.isActive).length} <Paper sx={{ p: 2 }}>
</Typography> <Typography variant="body2" color="text.secondary" gutterBottom>
</Paper> Total Uses
</Grid> </Typography>
<Grid item xs={12} sm={6} md={3}> <Typography variant="h4">
<Paper sx={{ p: 2 }}> {inviteCodes.reduce((sum, c) => sum + c.uses, 0).toLocaleString()}
<Typography variant="body2" color="text.secondary" gutterBottom> </Typography>
Total Uses </Paper>
</Typography> <Paper sx={{ p: 2 }}>
<Typography variant="h4"> <Typography variant="body2" color="text.secondary" gutterBottom>
{inviteCodes.reduce((sum, c) => sum + c.uses, 0)} Available
</Typography> </Typography>
</Paper> <Typography variant="h4">
</Grid> {inviteCodes.filter(c => {
<Grid item xs={12} sm={6} md={3}> const isExpired = c.expiresAt && new Date(c.expiresAt) < new Date();
<Paper sx={{ p: 2 }}> const isMaxedOut = c.maxUses && c.uses >= c.maxUses;
<Typography variant="body2" color="text.secondary" gutterBottom> return c.isActive && !isExpired && !isMaxedOut;
Available }).length.toLocaleString()}
</Typography> </Typography>
<Typography variant="h4"> </Paper>
{inviteCodes.filter(c => { </Box>
const isExpired = c.expiresAt && new Date(c.expiresAt) < new Date();
const isMaxedOut = c.maxUses && c.uses >= c.maxUses;
return c.isActive && !isExpired && !isMaxedOut;
}).length}
</Typography>
</Paper>
</Grid>
</Grid>
<Paper sx={{ p: 2 }}> <Paper sx={{ p: 2 }}>
<DataGrid <DataGrid