Fix admin demo review and landing regressions
This commit is contained in:
@@ -76,21 +76,7 @@ public static partial class AdminSeasonManagementEndpoints
|
||||
ViewerRangeMin = category.ViewerRangeMin,
|
||||
ViewerRangeMax = category.ViewerRangeMax,
|
||||
}));
|
||||
var visibleCategoryRows = categoryRows
|
||||
.Where(category => SeasonSubcategoryTemplateSettings.MatchesTemplate(
|
||||
new Backend.Domain.Category
|
||||
{
|
||||
GroupName = category.GroupName,
|
||||
Name = category.Name,
|
||||
Slug = category.Slug,
|
||||
SortOrder = category.SortOrder,
|
||||
ViewerRangeMin = category.ViewerRangeMin,
|
||||
ViewerRangeMax = category.ViewerRangeMax,
|
||||
},
|
||||
subcategoryTemplateSettings))
|
||||
.ToArray();
|
||||
|
||||
var categories = visibleCategoryRows
|
||||
var categories = categoryRows
|
||||
.Select(category => new AdminCategoryItemDto(
|
||||
category.Id,
|
||||
category.GroupName,
|
||||
|
||||
@@ -128,7 +128,7 @@ public partial class SeedLiveDemoAwardData : Migration
|
||||
award_groups.group_name,
|
||||
tiers.tier_name,
|
||||
award_groups.group_slug || '-' || tiers.tier_slug,
|
||||
award_groups.description || ' Demo-Tier: ' || tiers.tier_name || '.',
|
||||
award_groups.description,
|
||||
(award_groups.ord * 100) + (tiers.ord * 10),
|
||||
3,
|
||||
tiers.min_viewers,
|
||||
|
||||
@@ -45,7 +45,7 @@ public partial class SeedCurrentSeasonDemoVotingData : Migration
|
||||
|
||||
WITH award_groups(ord, group_name, group_slug, description, name_prefix) AS (
|
||||
VALUES
|
||||
(1, 'Main Awards', 'main-awards', 'Die groessten Allround-Auszeichnungen der aktuellen Demo-Season.', 'Astra'),
|
||||
(1, 'Main Awards', 'main-awards', 'Die groessten Allround-Auszeichnungen der aktuellen Season.', 'Astra'),
|
||||
(2, 'Discovery', 'discovery', 'Neue Stimmen, Debuets und Creator, die in dieser Season besonders sichtbar geworden sind.', 'Nova'),
|
||||
(3, 'Creative', 'creative', 'Model, Design, Rigging, Art und visuelle Praesentation im Stream.', 'Velvet'),
|
||||
(4, 'Performance', 'performance', 'Gesang, Musik, Buehnenpraesenz und besondere Live-Momente.', 'Melo'),
|
||||
@@ -76,7 +76,7 @@ public partial class SeedCurrentSeasonDemoVotingData : Migration
|
||||
award_groups.group_name,
|
||||
tiers.tier_name,
|
||||
award_groups.group_slug || '-' || tiers.tier_slug,
|
||||
award_groups.description || ' Demo-Tier: ' || tiers.tier_name || '.',
|
||||
award_groups.description,
|
||||
(award_groups.ord * 100) + (tiers.ord * 10),
|
||||
3,
|
||||
tiers.min_viewers,
|
||||
|
||||
+1620
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,48 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Backend.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class SanitizeDemoCategoryDescriptions : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(
|
||||
"""
|
||||
UPDATE "Categories"
|
||||
SET "Description" = trim(
|
||||
regexp_replace(
|
||||
replace("Description", 'aktuellen Demo-Season', 'aktuellen Season'),
|
||||
'\s*Demo-Tier:\s*[^.]+\.',
|
||||
'',
|
||||
'g'
|
||||
)
|
||||
)
|
||||
WHERE "Description" LIKE '%Demo-Tier:%'
|
||||
OR "Description" LIKE '%aktuellen Demo-Season%';
|
||||
"""
|
||||
);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(
|
||||
"""
|
||||
UPDATE "Categories"
|
||||
SET "Description" = "Description" || ' Demo-Tier: ' || "Name" || '.'
|
||||
WHERE "ViewerRangeMin" IS NOT NULL
|
||||
AND "Description" NOT LIKE '%Demo-Tier:%';
|
||||
|
||||
UPDATE "Categories"
|
||||
SET "Description" = replace("Description", 'aktuellen Season', 'aktuellen Demo-Season')
|
||||
WHERE "Slug" LIKE 'main-awards-%'
|
||||
AND "Description" LIKE '%aktuellen Season%';
|
||||
"""
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => isAdminRoute.value && awardsStore.apiMode === 'fallback',
|
||||
(shouldShowDisconnectToast) => {
|
||||
if (shouldShowDisconnectToast) {
|
||||
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) {
|
||||
scheduleDisconnectToast()
|
||||
scheduleReconnectProbe()
|
||||
return
|
||||
}
|
||||
|
||||
clearDisconnectToastTimer()
|
||||
clearReconnectTimer()
|
||||
dismissAdminToast(ADMIN_API_DISCONNECT_TOAST_KEY)
|
||||
},
|
||||
@@ -61,6 +81,7 @@ export function useAdminApiDisconnectToast(isAdminRoute: ComputedRef<boolean>) {
|
||||
)
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearDisconnectToastTimer()
|
||||
clearReconnectTimer()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user