mirror of
https://github.com/Sea-Haven-Industries/proposal-system.git
synced 2026-10-07 16:18:57 +00:00
Makes the system's namesake feature real: marking a proposal Sent now emails the customer an expiring link to the branded PDF, and customers are managed (with contact emails) instead of hardcoded. v1 PR4. API: - Customer.ContactEmail + migration; Customer list/update endpoints. Search stays additive at GET /api/customers?query= (frozen-mobile + web compat); new paginated list at GET /api/customers/list (admin). - IEmailService (SesEmailService v2 / DevEmailService, dev-gated). MarkSentAsync resolves the customer's email, presigns the latest PDF (7d), and sends via SES. Email/presign failures are caught + audited and NEVER roll back the Sent transition. - Startup EF migration guarded by a Postgres advisory lock (concurrency-safe). Infra: - SES email identity (proposals@seahavenind.com); least-privilege ses:SendEmail/ SendRawEmail scoped to the identity ARN + ses:FromAddress condition; SES_FROM_ADDRESS env. SES starts in sandbox — production access needed for unverified recipients. Web: - Customer management page (/admin/customers): list / create / edit incl. contact email. - New-proposal form searches real customers (free-solo) instead of a hardcoded value. - Mark-as-Sent dialog notes the PDF will be emailed to the customer. GPT-4.1 cross-review (SES IAM): no BLOCK (ses:FromAddress condition applied). Verified: api build + 121 tests; web tsc + 26 tests; infra tsc; ruff clean.
85 lines
4 KiB
TypeScript
85 lines
4 KiB
TypeScript
import { Routes, Route, Navigate } from 'react-router-dom';
|
|
import { Box, Button, Card, CardContent, Toolbar, Typography } from '@mui/material';
|
|
import PeopleIcon from '@mui/icons-material/People';
|
|
import ProtectedRoute, { RoleGuard } from './components/ProtectedRoute';
|
|
import Topbar from './components/Topbar';
|
|
import Sidebar from './components/Sidebar';
|
|
import LoginPage from './pages/auth/LoginPage';
|
|
import AuthCallback from './pages/auth/AuthCallback';
|
|
import Dashboard from './pages/dashboard/Dashboard';
|
|
import ProposalFormPage from './pages/proposals/form/ProposalFormPage';
|
|
import ProposalDetailPage from './pages/proposals/detail/ProposalDetailPage';
|
|
import ProposalListPage from './pages/proposals/list/ProposalListPage';
|
|
import AdminDashboard from './pages/admin/dashboard/AdminDashboard';
|
|
import AdminWorkspace from './pages/admin/workspace/AdminWorkspace';
|
|
import CustomerManagementPage from './pages/admin/customers/CustomerManagementPage';
|
|
|
|
export default function App() {
|
|
return (
|
|
<Routes>
|
|
<Route path="/login" element={<LoginPage />} />
|
|
<Route path="/callback" element={<AuthCallback />} />
|
|
|
|
<Route
|
|
path="/*"
|
|
element={
|
|
<ProtectedRoute>
|
|
<Box sx={{ display: 'flex' }}>
|
|
<Topbar />
|
|
<Sidebar />
|
|
<Box
|
|
component="main"
|
|
sx={{
|
|
flexGrow: 1,
|
|
minWidth: 0,
|
|
p: 1.25,
|
|
minHeight: '100vh',
|
|
}}
|
|
>
|
|
<Toolbar sx={{ minHeight: 64 }} />
|
|
<Routes>
|
|
<Route path="/" element={<Dashboard />} />
|
|
<Route path="/proposals" element={<ProposalListPage />} />
|
|
<Route path="/proposals/new" element={<ProposalFormPage />} />
|
|
<Route path="/proposals/:id" element={<ProposalDetailPage />} />
|
|
|
|
{/* Admin Routes */}
|
|
<Route path="/admin" element={<RoleGuard roles={['Admin', 'SysAdmin']}><AdminDashboard defaultStatus="InReview" /></RoleGuard>} />
|
|
<Route path="/admin/proposals" element={<RoleGuard roles={['Admin', 'SysAdmin']}><AdminDashboard /></RoleGuard>} />
|
|
<Route path="/admin/proposals/:id" element={<RoleGuard roles={['Admin', 'SysAdmin']}><AdminWorkspace /></RoleGuard>} />
|
|
<Route path="/admin/customers" element={<RoleGuard roles={['Admin', 'SysAdmin']}><CustomerManagementPage /></RoleGuard>} />
|
|
<Route path="/admin/users" element={
|
|
<RoleGuard roles={['SysAdmin']}>
|
|
<Box sx={{ display: 'flex', justifyContent: 'center', pt: 6 }}>
|
|
<Card sx={{ maxWidth: 520, p: 4, textAlign: 'center' }}>
|
|
<CardContent>
|
|
<PeopleIcon sx={{ fontSize: 48, color: '#94A3B8', mb: 2 }} />
|
|
<Typography variant="h5" sx={{ mb: 1 }}>User Management</Typography>
|
|
<Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}>
|
|
Users and roles are currently managed in AWS Cognito. Contact the system administrator to update access.
|
|
</Typography>
|
|
<Button
|
|
variant="outlined"
|
|
size="large"
|
|
href="https://us-east-1.console.aws.amazon.com/cognito/v2/idp/user-pools?region=us-east-1"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
Manage in Cognito
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
</Box>
|
|
</RoleGuard>
|
|
} />
|
|
|
|
<Route path="*" element={<Navigate to="/" replace />} />
|
|
</Routes>
|
|
</Box>
|
|
</Box>
|
|
</ProtectedRoute>
|
|
}
|
|
/>
|
|
</Routes>
|
|
);
|
|
}
|