feat: SAC 0.7.3 custom error pages 401/403/404/429

Centered logo pages with event explanation and allowed actions; router
and apiFetch redirect to them on auth, permission, not-found, and rate-limit errors.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
PTah
2026-06-01 11:06:21 +10:00
parent d3a337992c
commit 141fc44099
9 changed files with 241 additions and 16 deletions
+124
View File
@@ -0,0 +1,124 @@
<template>
<div class="error-page">
<div class="error-card card">
<img src="/sac-icon.png" alt="" class="error-logo" width="96" height="96" />
<p class="error-code">{{ config.code }}</p>
<h1 class="error-title">{{ config.title }}</h1>
<p class="error-lead">{{ config.lead }}</p>
<p class="error-hint">{{ config.hint }}</p>
<div class="error-actions">
<RouterLink :to="config.primaryAction.to" class="error-btn error-btn-primary">
{{ config.primaryAction.label }}
</RouterLink>
<RouterLink
v-if="showSecondary"
:to="config.secondaryAction!.to"
class="error-btn error-btn-secondary"
>
{{ config.secondaryAction!.label }}
</RouterLink>
</div>
<p class="error-brand muted">Security Alert Center</p>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from "vue";
import { getErrorPageConfig } from "../errorPages";
const props = defineProps<{
code: number;
}>();
const config = computed(() => getErrorPageConfig(props.code));
const showSecondary = computed(() => Boolean(config.value.secondaryAction));
</script>
<style scoped>
.error-page {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 1.5rem;
}
.error-card {
max-width: 520px;
width: 100%;
margin: 0;
text-align: center;
padding: 2rem 1.75rem;
}
.error-logo {
border-radius: 16px;
margin-bottom: 1rem;
}
.error-code {
margin: 0;
font-size: 3rem;
font-weight: 800;
line-height: 1;
color: #7eb8ff;
letter-spacing: 0.04em;
}
.error-title {
margin: 0.75rem 0 0;
font-size: 1.35rem;
}
.error-lead {
margin: 1rem 0 0;
color: #e8eaed;
}
.error-hint {
margin: 1rem 0 0;
padding: 0.85rem 1rem;
background: #0f1419;
border: 1px solid #2a3441;
border-radius: 8px;
color: #9aa4b2;
font-size: 0.92rem;
text-align: left;
line-height: 1.55;
}
.error-actions {
display: flex;
flex-wrap: wrap;
gap: 0.65rem;
justify-content: center;
margin-top: 1.5rem;
}
.error-btn {
display: inline-block;
padding: 0.55rem 1.1rem;
border-radius: 6px;
text-decoration: none;
font: inherit;
}
.error-btn-primary {
background: #2563eb;
border: 1px solid #2563eb;
color: #fff;
}
.error-btn-secondary {
background: transparent;
border: 1px solid #3d4f63;
color: #e8eaed;
}
.error-brand {
margin: 1.5rem 0 0;
font-size: 0.85rem;
}
</style>