Files
Hoard/GUI/src/routes/Forbidden.vue
T
Jonas 7e2ca4c9e2 Rename hoard- to ui- and add UI components
Mass rename of CSS classes, tokens and animations from the hoard- namespace to ui- (classes, variables like --ui-*, and keyframes). Introduces new UI components: EmptyState, StatusPill, and UserAvatar and updates admin views to import and use them. Updates many route/layout components and global.css to use the new ui- patterns and responsive variables. Also updates Impressum contact emails and adds .claude/settings.local.json to allow running npm scripts in the Claude local settings.
2026-04-28 21:52:22 +02:00

174 lines
4.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { fetchCurrentUser } from '@/services/authSession'
const router = useRouter()
const isLoading = ref(true)
const isAuthenticated = ref(false)
const primaryActionLabel = computed(() => (isAuthenticated.value ? 'Zum Dashboard' : 'Zum Login'))
const primaryActionIcon = computed(() =>
isAuthenticated.value ? 'mdi-view-dashboard-outline' : 'mdi-login',
)
async function resolveAuthState() {
try {
const user = await fetchCurrentUser({ force: true })
isAuthenticated.value = user !== null
} catch {
isAuthenticated.value = false
} finally {
isLoading.value = false
}
}
async function navigatePrimaryAction() {
if (isAuthenticated.value) {
await router.replace({ name: 'Dashboard' })
return
}
await router.replace({ name: 'Login' })
}
async function navigateBack() {
if (window.history.length > 1) {
router.back()
return
}
await router.replace({ name: isAuthenticated.value ? 'Dashboard' : 'Home' })
}
onMounted(() => {
void resolveAuthState()
})
</script>
<template>
<v-container fluid class="forbidden-page ui-page ui-page--centered">
<section class="forbidden-shell ui-panel ui-panel-gradient ui-spotlight">
<div class="forbidden-icon">
<span class="forbidden-icon__halo" aria-hidden="true" />
<v-icon icon="mdi-shield-alert-outline" size="40" />
</div>
<header class="forbidden-head">
<p class="ui-kicker ui-kicker--wide">Fehlende Berechtigung</p>
<h1>Kein Zugriff.</h1>
<p>
Dein Konto hat aktuell keine ausreichende Rolle, um diese Seite zu sehen. Falls das ein Fehler ist,
wende dich an einen Admin oder wechsle zurück in deinen freigegebenen Bereich.
</p>
</header>
<div class="forbidden-actions ui-action-row">
<v-btn
variant="elevated"
:prepend-icon="primaryActionIcon"
:loading="isLoading"
:disabled="isLoading"
@click="navigatePrimaryAction"
>
{{ primaryActionLabel }}
</v-btn>
<v-btn variant="outlined" prepend-icon="mdi-arrow-left" @click="navigateBack">
Zurück
</v-btn>
</div>
</section>
</v-container>
</template>
<style scoped>
.forbidden-page {
--ui-centered-offset: 200px;
}
.forbidden-shell {
--ui-gradient-angle: 130deg;
--ui-gradient-start: color-mix(in srgb, var(--color-warning) 14%, var(--color-surface) 86%);
--ui-gradient-end: var(--color-surface);
--ui-gradient-end-stop: 65%;
display: flex;
flex-direction: column;
gap: var(--space-5);
align-items: center;
text-align: center;
width: min(640px, 100%);
padding: var(--space-10) var(--space-7);
border-radius: var(--radius-xl);
}
.forbidden-icon {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
width: 80px;
height: 80px;
border-radius: var(--radius-full);
background:
linear-gradient(135deg, var(--color-warning), color-mix(in srgb, var(--color-warning) 60%, var(--color-danger) 40%));
color: #fff;
box-shadow:
var(--shadow-md),
inset 0 0 0 2px color-mix(in srgb, var(--color-accent-lime) 18%, transparent);
}
.forbidden-icon__halo {
position: absolute;
inset: -10px;
border-radius: var(--radius-full);
background:
radial-gradient(
closest-side,
color-mix(in srgb, var(--color-warning) 38%, transparent),
transparent 70%
);
filter: blur(12px);
opacity: 0.6;
}
.forbidden-head h1 {
margin: 0 0 var(--space-2);
font-size: clamp(1.8rem, 1.4rem + 1vw, 2.4rem);
font-weight: 700;
letter-spacing: -0.02em;
}
.forbidden-head p {
margin: 0 auto;
max-width: 50ch;
color: var(--color-text-secondary);
line-height: 1.6;
}
.forbidden-actions {
justify-content: center;
}
@media (prefers-reduced-motion: no-preference) {
.forbidden-shell {
animation: ui-soft-enter 280ms both;
}
}
@media (width <= 600px) {
.forbidden-shell {
padding: var(--space-7) var(--space-5);
}
.forbidden-actions {
width: 100%;
}
:deep(.forbidden-actions .v-btn) {
width: 100%;
min-height: 44px;
}
}
</style>