Add viewer-range categories, nomination tracking, dynamic showact form, session timeout, share URLs, and workflow-per-season
Features: - Category viewer ranges + subcategory templates (admin group modal, tree workspace) - Nomination enrichment via TwitchTracker API (NominationEnrichmentService, TwitchTrackerViewerStatsProvider) with admin tracking rules editor - Nomination group tracker: CategoryGroupName as primary identifier, CategoryId stays as nullable legacy field; StreamerIdentity table - Dynamic showact application form builder (AdminShowactFormBuilder, ShowactApplicationSchedule) - Session idle timeout setting (AdminSessionTimeoutCard) - Share URLs for X and Discord (SiteSettings, public extras) - Workflow rules now stored per season (falls back to global SiteSettings) - New admin routes: settings/access, settings/workflows, tracking-rules - New admin review workspace with subcategory tabs - AdminCategoriesView rebuilt with group/subcategory modals Migrations (all additive): - AddShareUrls, AddShowactDynamicForm, AddCategoryViewerRanges, AddSessionIdleTimeoutSettings, AddSeasonSubcategoryTemplates, AddNominationGroupTrackerIdentity, AddShowactApplicationSchedule, AddSeasonWorkflowRulesJson Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@ import { computed, type ComputedRef, type Ref } from 'vue'
|
||||
import { buildClipEmbed } from '../../lib/clipEmbeds'
|
||||
import { useAuthStore } from '../../stores/auth'
|
||||
import { useAwardsStore } from '../../stores/awards'
|
||||
import type { HomeDisplayCategory, HomeInteractionModalKind } from './homeLandingTypes'
|
||||
import type { HomeDisplayCategory, HomeInteractionModalKind, HomeLandingCategory } from './homeLandingTypes'
|
||||
|
||||
type AwardsStore = ReturnType<typeof useAwardsStore>
|
||||
type AuthStore = ReturnType<typeof useAuthStore>
|
||||
@@ -31,11 +31,52 @@ export function useHomeLandingOverviewPresentation(store: AwardsStore, authStore
|
||||
const displayCategories = computed<HomeDisplayCategory[]>(() =>
|
||||
store.categories.categories.map((category, index) => ({
|
||||
id: String(category.id),
|
||||
groupName: category.groupName,
|
||||
name: category.name,
|
||||
icon: CATEGORY_ICONS[index % CATEGORY_ICONS.length] ?? '✦',
|
||||
maxNomineesPerUser: category.maxNomineesPerUser,
|
||||
candidates: category.candidates,
|
||||
})),
|
||||
)
|
||||
const landingCategories = computed<HomeLandingCategory[]>(() => {
|
||||
if (store.overview.featuredCategories.length > 0) {
|
||||
return store.overview.featuredCategories.map((category, index) => {
|
||||
const matchingSubcategories = displayCategories.value.filter((item) => item.groupName === category.groupName)
|
||||
const candidateCount = matchingSubcategories.reduce((sum, item) => sum + item.candidates.length, 0)
|
||||
return {
|
||||
id: String(category.id),
|
||||
groupName: category.groupName,
|
||||
name: category.groupName,
|
||||
icon: CATEGORY_ICONS[index % CATEGORY_ICONS.length] ?? '✦',
|
||||
description: category.description,
|
||||
maxNomineesPerUser: category.maxNomineesPerUser,
|
||||
candidateCount,
|
||||
subcategoryCount: matchingSubcategories.length,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const seenGroups = new Set<string>()
|
||||
return displayCategories.value.flatMap((category, index) => {
|
||||
const groupKey = category.groupName.trim().toLowerCase()
|
||||
if (seenGroups.has(groupKey)) {
|
||||
return []
|
||||
}
|
||||
|
||||
seenGroups.add(groupKey)
|
||||
const matchingSubcategories = displayCategories.value.filter((item) => item.groupName.trim().toLowerCase() === groupKey)
|
||||
return [{
|
||||
id: category.id,
|
||||
groupName: category.groupName,
|
||||
name: category.groupName,
|
||||
icon: CATEGORY_ICONS[index % CATEGORY_ICONS.length] ?? '✦',
|
||||
description: '',
|
||||
maxNomineesPerUser: category.maxNomineesPerUser,
|
||||
candidateCount: matchingSubcategories.reduce((sum, item) => sum + item.candidates.length, 0),
|
||||
subcategoryCount: matchingSubcategories.length,
|
||||
}]
|
||||
})
|
||||
})
|
||||
const candidateCount = computed(() =>
|
||||
displayCategories.value.reduce((sum, category) => sum + category.candidates.length, 0),
|
||||
)
|
||||
@@ -82,6 +123,7 @@ export function useHomeLandingOverviewPresentation(store: AwardsStore, authStore
|
||||
showStartsAt,
|
||||
currentYear,
|
||||
displayCategories,
|
||||
landingCategories,
|
||||
candidateCount,
|
||||
bootstrapArchiveYears,
|
||||
formatRange,
|
||||
@@ -194,9 +236,12 @@ function buildCatList(
|
||||
const done = votes.value[category.id] != null
|
||||
return {
|
||||
name: category.name,
|
||||
groupName: category.groupName,
|
||||
displayName: `${category.groupName} · ${category.name}`,
|
||||
icon: category.icon,
|
||||
idx: index,
|
||||
done,
|
||||
candidateCount: category.candidates.length,
|
||||
onClick: () => setCat(index),
|
||||
rowStyle: baseRow + (active ? 'background:#f1ecfb;border-color:#d8c9f2;color:#5f44ad;' : 'border-color:transparent;color:#6f6685;'),
|
||||
iconStyle: "flex:none;display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:8px;font-size:14px;" + (active ? 'background:linear-gradient(135deg,#8b6cdb,#7355c8);color:#fff;' : 'background:#efe7fb;color:#9a7fce;'),
|
||||
|
||||
Reference in New Issue
Block a user