Fix admin demo review and landing regressions
This commit is contained in:
@@ -76,21 +76,7 @@ public static partial class AdminSeasonManagementEndpoints
|
|||||||
ViewerRangeMin = category.ViewerRangeMin,
|
ViewerRangeMin = category.ViewerRangeMin,
|
||||||
ViewerRangeMax = category.ViewerRangeMax,
|
ViewerRangeMax = category.ViewerRangeMax,
|
||||||
}));
|
}));
|
||||||
var visibleCategoryRows = categoryRows
|
var categories = 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
|
|
||||||
.Select(category => new AdminCategoryItemDto(
|
.Select(category => new AdminCategoryItemDto(
|
||||||
category.Id,
|
category.Id,
|
||||||
category.GroupName,
|
category.GroupName,
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ public partial class SeedLiveDemoAwardData : Migration
|
|||||||
award_groups.group_name,
|
award_groups.group_name,
|
||||||
tiers.tier_name,
|
tiers.tier_name,
|
||||||
award_groups.group_slug || '-' || tiers.tier_slug,
|
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),
|
(award_groups.ord * 100) + (tiers.ord * 10),
|
||||||
3,
|
3,
|
||||||
tiers.min_viewers,
|
tiers.min_viewers,
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ public partial class SeedCurrentSeasonDemoVotingData : Migration
|
|||||||
|
|
||||||
WITH award_groups(ord, group_name, group_slug, description, name_prefix) AS (
|
WITH award_groups(ord, group_name, group_slug, description, name_prefix) AS (
|
||||||
VALUES
|
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'),
|
(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'),
|
(3, 'Creative', 'creative', 'Model, Design, Rigging, Art und visuelle Praesentation im Stream.', 'Velvet'),
|
||||||
(4, 'Performance', 'performance', 'Gesang, Musik, Buehnenpraesenz und besondere Live-Momente.', 'Melo'),
|
(4, 'Performance', 'performance', 'Gesang, Musik, Buehnenpraesenz und besondere Live-Momente.', 'Melo'),
|
||||||
@@ -76,7 +76,7 @@ public partial class SeedCurrentSeasonDemoVotingData : Migration
|
|||||||
award_groups.group_name,
|
award_groups.group_name,
|
||||||
tiers.tier_name,
|
tiers.tier_name,
|
||||||
award_groups.group_slug || '-' || tiers.tier_slug,
|
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),
|
(award_groups.ord * 100) + (tiers.ord * 10),
|
||||||
3,
|
3,
|
||||||
tiers.min_viewers,
|
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(() =>
|
const categoryOptions = computed(() =>
|
||||||
seasonDetail.value.categories.map((category) => ({ label: `${category.groupName} · ${category.name}`, value: category.id })),
|
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 categoryFilterOptions = computed(() => [{ label: 'Alle Kategorien', value: null }, ...categoryOptions.value])
|
||||||
const categoryLabelMap = computed<Record<number, string>>(() =>
|
const categoryLabelMap = computed<Record<number, string>>(() =>
|
||||||
Object.fromEntries(seasonDetail.value.categories.map((category) => [category.id, `${category.groupName} · ${category.name}`])),
|
Object.fromEntries(seasonDetail.value.categories.map((category) => [category.id, `${category.groupName} · ${category.name}`])),
|
||||||
@@ -307,7 +316,7 @@ export function useAdminCandidateManager() {
|
|||||||
adminMessage.value = ''
|
adminMessage.value = ''
|
||||||
adminError.value = ''
|
adminError.value = ''
|
||||||
editingId.value = 'new'
|
editingId.value = 'new'
|
||||||
form.categoryId = categoryFilter.value ?? seasonDetail.value.categories[0]?.id ?? 0
|
form.categoryId = preferredCreateCategoryId.value
|
||||||
form.displayName = ''
|
form.displayName = ''
|
||||||
form.channelSlug = ''
|
form.channelSlug = ''
|
||||||
form.platform = 'Twitch'
|
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_TOAST_KEY = 'admin-api-disconnected'
|
||||||
const ADMIN_API_DISCONNECT_MESSAGE =
|
const ADMIN_API_DISCONNECT_MESSAGE =
|
||||||
'Backend/API nicht erreichbar. Der Admin-Bereich bleibt eingeschränkt, bis die Verbindung wieder da ist.'
|
'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
|
const RECONNECT_PROBE_INTERVAL_MS = 5000
|
||||||
|
|
||||||
export function useAdminApiDisconnectToast(isAdminRoute: ComputedRef<boolean>) {
|
export function useAdminApiDisconnectToast(isAdminRoute: ComputedRef<boolean>) {
|
||||||
@@ -13,6 +14,7 @@ export function useAdminApiDisconnectToast(isAdminRoute: ComputedRef<boolean>) {
|
|||||||
const { dismissAdminToast, showAdminToast } = useAdminToast()
|
const { dismissAdminToast, showAdminToast } = useAdminToast()
|
||||||
|
|
||||||
let reconnectTimer: number | undefined
|
let reconnectTimer: number | undefined
|
||||||
|
let disconnectToastTimer: number | undefined
|
||||||
let reconnectProbeRunning = false
|
let reconnectProbeRunning = false
|
||||||
|
|
||||||
function clearReconnectTimer() {
|
function clearReconnectTimer() {
|
||||||
@@ -21,6 +23,12 @@ export function useAdminApiDisconnectToast(isAdminRoute: ComputedRef<boolean>) {
|
|||||||
reconnectTimer = undefined
|
reconnectTimer = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearDisconnectToastTimer() {
|
||||||
|
if (disconnectToastTimer === undefined || typeof window === 'undefined') return
|
||||||
|
window.clearTimeout(disconnectToastTimer)
|
||||||
|
disconnectToastTimer = undefined
|
||||||
|
}
|
||||||
|
|
||||||
function scheduleReconnectProbe() {
|
function scheduleReconnectProbe() {
|
||||||
if (reconnectTimer !== undefined || typeof window === 'undefined') return
|
if (reconnectTimer !== undefined || typeof window === 'undefined') return
|
||||||
|
|
||||||
@@ -42,18 +50,30 @@ export function useAdminApiDisconnectToast(isAdminRoute: ComputedRef<boolean>) {
|
|||||||
}, RECONNECT_PROBE_INTERVAL_MS)
|
}, RECONNECT_PROBE_INTERVAL_MS)
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
function scheduleDisconnectToast() {
|
||||||
() => isAdminRoute.value && awardsStore.apiMode === 'fallback',
|
if (disconnectToastTimer !== undefined || typeof window === 'undefined') return
|
||||||
(shouldShowDisconnectToast) => {
|
|
||||||
if (shouldShowDisconnectToast) {
|
disconnectToastTimer = window.setTimeout(() => {
|
||||||
|
disconnectToastTimer = undefined
|
||||||
|
if (!isAdminRoute.value || awardsStore.apiMode !== 'fallback') return
|
||||||
|
|
||||||
showAdminToast(ADMIN_API_DISCONNECT_MESSAGE, 'error', {
|
showAdminToast(ADMIN_API_DISCONNECT_MESSAGE, 'error', {
|
||||||
key: ADMIN_API_DISCONNECT_TOAST_KEY,
|
key: ADMIN_API_DISCONNECT_TOAST_KEY,
|
||||||
persistent: true,
|
persistent: true,
|
||||||
})
|
})
|
||||||
|
}, ADMIN_API_DISCONNECT_TOAST_DELAY_MS)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => isAdminRoute.value && awardsStore.apiMode === 'fallback',
|
||||||
|
(shouldShowDisconnectToast) => {
|
||||||
|
if (shouldShowDisconnectToast) {
|
||||||
|
scheduleDisconnectToast()
|
||||||
scheduleReconnectProbe()
|
scheduleReconnectProbe()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearDisconnectToastTimer()
|
||||||
clearReconnectTimer()
|
clearReconnectTimer()
|
||||||
dismissAdminToast(ADMIN_API_DISCONNECT_TOAST_KEY)
|
dismissAdminToast(ADMIN_API_DISCONNECT_TOAST_KEY)
|
||||||
},
|
},
|
||||||
@@ -61,6 +81,7 @@ export function useAdminApiDisconnectToast(isAdminRoute: ComputedRef<boolean>) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
clearDisconnectToastTimer()
|
||||||
clearReconnectTimer()
|
clearReconnectTimer()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ const loginFormElement = ref<HTMLFormElement | null>(null)
|
|||||||
const passwordChangeFormElement = ref<HTMLFormElement | null>(null)
|
const passwordChangeFormElement = ref<HTMLFormElement | null>(null)
|
||||||
|
|
||||||
const passwordChangeRequired = computed(() => Boolean(authStore.session?.mustChangePassword))
|
const passwordChangeRequired = computed(() => Boolean(authStore.session?.mustChangePassword))
|
||||||
|
const successMessageClass = computed(() =>
|
||||||
|
passwordChangeRequired.value ? 'login-form__warning' : 'login-form__success',
|
||||||
|
)
|
||||||
|
|
||||||
const redirectTarget = computed(() => {
|
const redirectTarget = computed(() => {
|
||||||
const redirect = Array.isArray(route.query.redirect)
|
const redirect = Array.isArray(route.query.redirect)
|
||||||
@@ -211,7 +214,7 @@ async function submitPasswordChange() {
|
|||||||
{{ errorMessage }}
|
{{ errorMessage }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p v-if="successMessage" class="login-form__success" role="status">
|
<p v-if="successMessage" :class="successMessageClass" role="status">
|
||||||
{{ successMessage }}
|
{{ successMessage }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -231,7 +234,7 @@ async function submitPasswordChange() {
|
|||||||
{{ errorMessage }}
|
{{ errorMessage }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p v-if="successMessage" class="login-form__success" role="status">
|
<p v-if="successMessage" :class="successMessageClass" role="status">
|
||||||
{{ successMessage }}
|
{{ successMessage }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -284,7 +287,7 @@ async function submitPasswordChange() {
|
|||||||
{{ errorMessage }}
|
{{ errorMessage }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p v-if="successMessage" class="login-form__success" role="status">
|
<p v-if="successMessage" :class="successMessageClass" role="status">
|
||||||
{{ successMessage }}
|
{{ successMessage }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -535,6 +538,16 @@ async function submitPasswordChange() {
|
|||||||
font-weight: 800;
|
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 {
|
.login-form__submit {
|
||||||
min-height: 58px;
|
min-height: 58px;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
|||||||
@@ -260,13 +260,13 @@ watch(
|
|||||||
</Teleport>
|
</Teleport>
|
||||||
|
|
||||||
<div class="admin-panel pb-10 pt-4 xl:pt-0">
|
<div class="admin-panel pb-10 pt-4 xl:pt-0">
|
||||||
<div class="grid gap-6 xl:grid-cols-[292px_minmax(0,1fr)]">
|
<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">
|
<main class="order-1 min-w-0 xl:order-2 xl:min-h-0 xl:overflow-y-auto xl:pr-2">
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- Desktop sidebar (hidden on mobile) -->
|
<!-- 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">
|
<Card class="p-3">
|
||||||
<nav class="space-y-4">
|
<nav class="space-y-4">
|
||||||
<section v-for="group in navGroups" :key="group.label" class="space-y-1.5">
|
<section v-for="group in navGroups" :key="group.label" class="space-y-1.5">
|
||||||
|
|||||||
Reference in New Issue
Block a user