Add proper 404 handling and custom error page
Only serve index.html for GET /; all other unknown paths return HTTP 404 with a custom branded error page (stethoscope SVG illustration, matches app design, links back to /).
This commit is contained in:
parent
6633763833
commit
a3277f352e
2 changed files with 181 additions and 1 deletions
175
public/404.html
Normal file
175
public/404.html
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>404 – Page Not Found | Pediatric AI Scribe</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--blue: #2563eb;
|
||||
--blue2: #1d4ed8;
|
||||
--indigo: #6366f1;
|
||||
--g50: #f9fafb;
|
||||
--g100: #f3f4f6;
|
||||
--g300: #d1d5db;
|
||||
--g500: #6b7280;
|
||||
--g700: #374151;
|
||||
--g900: #111827;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
font-family: 'Inter', system-ui, sans-serif;
|
||||
background: var(--g50);
|
||||
color: var(--g900);
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 24px;
|
||||
box-shadow: 0 4px 6px -1px rgba(0,0,0,.07), 0 20px 60px -10px rgba(37,99,235,.12);
|
||||
padding: 56px 48px 48px;
|
||||
max-width: 520px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ── Illustration ── */
|
||||
.illustration { margin-bottom: 32px; }
|
||||
.illustration svg { width: 200px; height: 200px; }
|
||||
|
||||
/* ── 404 badge ── */
|
||||
.badge-404 {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: linear-gradient(135deg, #eff6ff, #eef2ff);
|
||||
border: 1.5px solid #c7d2fe;
|
||||
border-radius: 100px;
|
||||
padding: 6px 18px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: var(--indigo);
|
||||
letter-spacing: 1px;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
font-weight: 800;
|
||||
color: var(--g900);
|
||||
margin-bottom: 12px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 15px;
|
||||
color: var(--g500);
|
||||
line-height: 1.6;
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: linear-gradient(135deg, var(--blue), var(--indigo));
|
||||
color: white;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
padding: 13px 32px;
|
||||
border-radius: 12px;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: transform .15s, box-shadow .15s;
|
||||
box-shadow: 0 4px 14px rgba(37,99,235,.35);
|
||||
}
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(37,99,235,.45);
|
||||
}
|
||||
.btn svg { width: 18px; height: 18px; }
|
||||
|
||||
.footer-note {
|
||||
margin-top: 28px;
|
||||
font-size: 13px;
|
||||
color: var(--g300);
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.card { padding: 40px 24px 32px; }
|
||||
h1 { font-size: 22px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
|
||||
<!-- Illustration: stethoscope with a question mark -->
|
||||
<div class="illustration">
|
||||
<svg viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Background circle -->
|
||||
<circle cx="100" cy="100" r="96" fill="#eff6ff" stroke="#c7d2fe" stroke-width="1.5"/>
|
||||
|
||||
<!-- Stethoscope body -->
|
||||
<path d="M62 58 C62 58 58 58 58 64 L58 96 C58 118 76 132 100 132 C124 132 142 118 142 96 L142 88"
|
||||
stroke="#2563eb" stroke-width="7" stroke-linecap="round" fill="none"/>
|
||||
|
||||
<!-- Stethoscope tube going up-right -->
|
||||
<path d="M142 88 C142 72 148 66 158 66 C168 66 172 72 172 80 C172 88 166 92 158 92"
|
||||
stroke="#2563eb" stroke-width="7" stroke-linecap="round" fill="none"/>
|
||||
|
||||
<!-- Ear tips -->
|
||||
<circle cx="158" cy="93" r="7" fill="#2563eb"/>
|
||||
<circle cx="58" cy="57" r="7" fill="#2563eb"/>
|
||||
|
||||
<!-- Chest piece (circle at bottom) -->
|
||||
<circle cx="100" cy="143" r="16" fill="white" stroke="#2563eb" stroke-width="5"/>
|
||||
<circle cx="100" cy="143" r="7" fill="#6366f1"/>
|
||||
|
||||
<!-- Question mark inside chest piece area — floating above -->
|
||||
<text x="100" y="106" text-anchor="middle" font-family="Inter,sans-serif"
|
||||
font-size="28" font-weight="800" fill="#6366f1">?</text>
|
||||
|
||||
<!-- Small dots decoration -->
|
||||
<circle cx="38" cy="100" r="4" fill="#c7d2fe"/>
|
||||
<circle cx="28" cy="118" r="3" fill="#ddd6fe"/>
|
||||
<circle cx="162" cy="130" r="4" fill="#c7d2fe"/>
|
||||
<circle cx="172" cy="112" r="3" fill="#ddd6fe"/>
|
||||
<circle cx="50" cy="148" r="3" fill="#bfdbfe"/>
|
||||
<circle cx="150" cy="56" r="4" fill="#bfdbfe"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="badge-404">
|
||||
<svg viewBox="0 0 16 16" fill="currentColor"><path d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1zm0 11a.75.75 0 1 1 0-1.5A.75.75 0 0 1 8 12zm.75-3.5a.75.75 0 0 1-1.5 0V5.75a.75.75 0 0 1 1.5 0V8.5z"/></svg>
|
||||
Error 404
|
||||
</div>
|
||||
|
||||
<h1>This page doesn't exist</h1>
|
||||
<p>The URL you visited isn't part of Pediatric AI Scribe.<br>It may have been mistyped or the link is outdated.</p>
|
||||
|
||||
<a href="/" class="btn">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor"><path d="M10.707 2.293a1 1 0 0 0-1.414 0l-7 7a1 1 0 0 0 1.414 1.414L4 10.414V17a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1v-3h2v3a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1v-6.586l.293.293a1 1 0 0 0 1.414-1.414l-7-7z"/></svg>
|
||||
Back to the app
|
||||
</a>
|
||||
|
||||
<div class="footer-note">Pediatric AI Scribe — Clinical Documentation Platform</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -155,13 +155,18 @@ app.use('/api', require('./src/routes/memories'));
|
|||
app.use('/api', require('./src/routes/wellVisit'));
|
||||
app.use('/api', require('./src/routes/sickVisit'));
|
||||
|
||||
app.get('*', (req, res) => {
|
||||
app.get('/', (req, res) => {
|
||||
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
||||
res.setHeader('Pragma', 'no-cache');
|
||||
res.setHeader('Expires', '0');
|
||||
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
||||
});
|
||||
|
||||
// 404 handler — must be last
|
||||
app.use((req, res) => {
|
||||
res.status(404).sendFile(path.join(__dirname, 'public', '404.html'));
|
||||
});
|
||||
|
||||
// Load prompt DB overrides after DB is ready (3s grace period)
|
||||
const PROMPTS = require('./src/utils/prompts');
|
||||
const db = require('./src/db/database');
|
||||
|
|
|
|||
Loading…
Reference in a new issue