fix(cropper): professional permission-denied page
Centered card with navbar, lock icon, sans-serif stack (error pages previously rendered in the browser default serif), and action-aware copy: permission denial offers only 'Back to Tools' instead of a pointless 'Try again'; auth failures keep Try again + Back to Tools.
This commit is contained in:
+25
-6
@@ -58,7 +58,10 @@ function readProfile(raw) {
|
||||
}
|
||||
}
|
||||
|
||||
function errorPage(res, status, title, message) {
|
||||
function errorPage(res, status, title, message, actions = {}) {
|
||||
const primary = actions.primary || { href: '/auth/login', label: 'Try again' };
|
||||
const secondary = actions.secondary === null ? null : actions.secondary || { href: '/', label: 'Back to Tools' };
|
||||
const icon = actions.icon || 'alert';
|
||||
res
|
||||
.status(status)
|
||||
.type('html')
|
||||
@@ -71,13 +74,26 @@ function errorPage(res, status, title, message) {
|
||||
<link rel="stylesheet" href="/assets/style.css">
|
||||
</head>
|
||||
<body class="app">
|
||||
<nav class="nav">
|
||||
<div class="container">
|
||||
<a class="brand" href="/">
|
||||
<span class="brand-mark">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 2a8 8 0 0 0-8 8c0 3.5 1 5.5 2 7"/><path d="M16 21a10 10 0 0 0 4-13 8 8 0 0 0-15.7-1"/><path d="M8 21a8 8 0 0 1-1-11 5 5 0 0 1 9.7 1.4A6 6 0 0 1 12 17"/><path d="M12 21a4 4 0 0 0 4-4 4 4 0 0 0-1.2-2.9"/></svg>
|
||||
</span>
|
||||
YOUreka <small>Tools</small>
|
||||
</a>
|
||||
<div class="nav-right">
|
||||
<a class="linklike" href="/auth/logout">Sign out</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<main class="container narrow center-v">
|
||||
<div class="card message-card">
|
||||
<div class="message-icon error">${ICONS.alert}</div>
|
||||
<div class="message-icon error">${ICONS[icon] || ICONS.alert}</div>
|
||||
<h1>${title}</h1>
|
||||
<p class="muted">${message}</p>
|
||||
<a class="btn btn-primary" href="/auth/login">Try again</a>
|
||||
<a class="btn btn-ghost" href="/">Back to Tools</a>
|
||||
<a class="btn btn-primary" href="${primary.href}">${primary.label}</a>
|
||||
${secondary ? `<a class="btn btn-ghost" href="${secondary.href}">${secondary.label}</a>` : ''}
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
@@ -87,6 +103,8 @@ function errorPage(res, status, title, message) {
|
||||
const ICONS = {
|
||||
alert:
|
||||
'<svg viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',
|
||||
lock:
|
||||
'<svg viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>',
|
||||
};
|
||||
|
||||
// ── app factory ──────────────────────────────────────────────────────────────
|
||||
@@ -304,8 +322,9 @@ export function createApp(overrides = {}) {
|
||||
return errorPage(
|
||||
res,
|
||||
403,
|
||||
'Not permitted',
|
||||
'You are not permitted to crop images. Contact your administrator if you believe this is a mistake.'
|
||||
'No access',
|
||||
'Your account doesn\u2019t have permission to crop images. Ask a domain administrator to grant you access.',
|
||||
{ icon: 'lock', primary: { href: '/', label: 'Back to Tools' }, secondary: null }
|
||||
);
|
||||
}
|
||||
next();
|
||||
|
||||
Reference in New Issue
Block a user