Fix admin demo review and landing regressions
CI - Build & Verify / Build, Typecheck & Hygiene (push) Successful in 59s
CI - Build & Verify / Deploy to award.noveria.net (push) Successful in 1m34s

This commit is contained in:
AzuTear
2026-06-30 08:18:14 +02:00
parent 0803a0124b
commit 76ac60d3fd
9 changed files with 1726 additions and 29 deletions
@@ -86,6 +86,15 @@ export function useAdminCandidateManager() {
const categoryOptions = computed(() =>
seasonDetail.value.categories.map((category) => ({ label: `${category.groupName} · ${category.name}`, value: category.id })),
)
const preferredCreateCategoryId = computed(() => {
if (categoryFilter.value) {
return categoryFilter.value
}
return seasonDetail.value.categories.find((category) =>
category.viewerRangeMin !== null || category.viewerRangeMax !== null,
)?.id ?? seasonDetail.value.categories[0]?.id ?? 0
})
const categoryFilterOptions = computed(() => [{ label: 'Alle Kategorien', value: null }, ...categoryOptions.value])
const categoryLabelMap = computed<Record<number, string>>(() =>
Object.fromEntries(seasonDetail.value.categories.map((category) => [category.id, `${category.groupName} · ${category.name}`])),
@@ -307,7 +316,7 @@ export function useAdminCandidateManager() {
adminMessage.value = ''
adminError.value = ''
editingId.value = 'new'
form.categoryId = categoryFilter.value ?? seasonDetail.value.categories[0]?.id ?? 0
form.categoryId = preferredCreateCategoryId.value
form.displayName = ''
form.channelSlug = ''
form.platform = 'Twitch'
@@ -6,6 +6,7 @@ import { useAwardsStore } from '../stores/awards'
const ADMIN_API_DISCONNECT_TOAST_KEY = 'admin-api-disconnected'
const ADMIN_API_DISCONNECT_MESSAGE =
'Backend/API nicht erreichbar. Der Admin-Bereich bleibt eingeschränkt, bis die Verbindung wieder da ist.'
const ADMIN_API_DISCONNECT_TOAST_DELAY_MS = 1200
const RECONNECT_PROBE_INTERVAL_MS = 5000
export function useAdminApiDisconnectToast(isAdminRoute: ComputedRef<boolean>) {
@@ -13,6 +14,7 @@ export function useAdminApiDisconnectToast(isAdminRoute: ComputedRef<boolean>) {
const { dismissAdminToast, showAdminToast } = useAdminToast()
let reconnectTimer: number | undefined
let disconnectToastTimer: number | undefined
let reconnectProbeRunning = false
function clearReconnectTimer() {
@@ -21,6 +23,12 @@ export function useAdminApiDisconnectToast(isAdminRoute: ComputedRef<boolean>) {
reconnectTimer = undefined
}
function clearDisconnectToastTimer() {
if (disconnectToastTimer === undefined || typeof window === 'undefined') return
window.clearTimeout(disconnectToastTimer)
disconnectToastTimer = undefined
}
function scheduleReconnectProbe() {
if (reconnectTimer !== undefined || typeof window === 'undefined') return
@@ -42,18 +50,30 @@ export function useAdminApiDisconnectToast(isAdminRoute: ComputedRef<boolean>) {
}, RECONNECT_PROBE_INTERVAL_MS)
}
function scheduleDisconnectToast() {
if (disconnectToastTimer !== undefined || typeof window === 'undefined') return
disconnectToastTimer = window.setTimeout(() => {
disconnectToastTimer = undefined
if (!isAdminRoute.value || awardsStore.apiMode !== 'fallback') return
showAdminToast(ADMIN_API_DISCONNECT_MESSAGE, 'error', {
key: ADMIN_API_DISCONNECT_TOAST_KEY,
persistent: true,
})
}, ADMIN_API_DISCONNECT_TOAST_DELAY_MS)
}
watch(
() => isAdminRoute.value && awardsStore.apiMode === 'fallback',
(shouldShowDisconnectToast) => {
if (shouldShowDisconnectToast) {
showAdminToast(ADMIN_API_DISCONNECT_MESSAGE, 'error', {
key: ADMIN_API_DISCONNECT_TOAST_KEY,
persistent: true,
})
scheduleDisconnectToast()
scheduleReconnectProbe()
return
}
clearDisconnectToastTimer()
clearReconnectTimer()
dismissAdminToast(ADMIN_API_DISCONNECT_TOAST_KEY)
},
@@ -61,6 +81,7 @@ export function useAdminApiDisconnectToast(isAdminRoute: ComputedRef<boolean>) {
)
onBeforeUnmount(() => {
clearDisconnectToastTimer()
clearReconnectTimer()
})
}
+16 -3
View File
@@ -28,6 +28,9 @@ const loginFormElement = ref<HTMLFormElement | null>(null)
const passwordChangeFormElement = ref<HTMLFormElement | null>(null)
const passwordChangeRequired = computed(() => Boolean(authStore.session?.mustChangePassword))
const successMessageClass = computed(() =>
passwordChangeRequired.value ? 'login-form__warning' : 'login-form__success',
)
const redirectTarget = computed(() => {
const redirect = Array.isArray(route.query.redirect)
@@ -211,7 +214,7 @@ async function submitPasswordChange() {
{{ errorMessage }}
</p>
<p v-if="successMessage" class="login-form__success" role="status">
<p v-if="successMessage" :class="successMessageClass" role="status">
{{ successMessage }}
</p>
@@ -231,7 +234,7 @@ async function submitPasswordChange() {
{{ errorMessage }}
</p>
<p v-if="successMessage" class="login-form__success" role="status">
<p v-if="successMessage" :class="successMessageClass" role="status">
{{ successMessage }}
</p>
@@ -284,7 +287,7 @@ async function submitPasswordChange() {
{{ errorMessage }}
</p>
<p v-if="successMessage" class="login-form__success" role="status">
<p v-if="successMessage" :class="successMessageClass" role="status">
{{ successMessage }}
</p>
@@ -535,6 +538,16 @@ async function submitPasswordChange() {
font-weight: 800;
}
.login-form__warning {
padding: 13px 15px;
border: 1px solid rgba(217, 119, 6, 0.24);
border-radius: 18px;
background: rgba(255, 251, 235, 0.96);
color: #b45309 !important;
font-size: 14px !important;
font-weight: 800;
}
.login-form__submit {
min-height: 58px;
border: 0;
+3 -3
View File
@@ -260,13 +260,13 @@ watch(
</Teleport>
<div class="admin-panel pb-10 pt-4 xl:pt-0">
<div class="grid gap-6 xl:grid-cols-[292px_minmax(0,1fr)]">
<main class="order-1 min-w-0 xl:order-2">
<div class="grid gap-6 xl:min-h-[calc(100vh-1rem)] xl:grid-cols-[292px_minmax(0,1fr)]">
<main class="order-1 min-w-0 xl:order-2 xl:min-h-0 xl:overflow-y-auto xl:pr-2">
<RouterView />
</main>
<!-- Desktop sidebar (hidden on mobile) -->
<aside class="order-2 hidden space-y-3 xl:order-1 xl:block xl:sticky xl:top-4 xl:h-fit">
<aside class="order-2 hidden space-y-3 xl:order-1 xl:block xl:max-h-[calc(100vh-1rem)] xl:overflow-y-auto xl:pr-1">
<Card class="p-3">
<nav class="space-y-4">
<section v-for="group in navGroups" :key="group.label" class="space-y-1.5">