Improve admin candidate modal UX and add clip menu visibility toggle
- Widen AdminCandidateEditorModal to size lg for better readability - Rename "Clip-Compilation" section to "Clip / Compilation", update copy to reflect single clips too, drop upload hint and Clip-Plattform field, rename label to "Link" - Fix NativeSelect dropdown clipping inside overflow-y-auto modals by teleporting the menu to body with fixed positioning, flip-up logic, and dynamic maxHeight capped to viewport - Add ClipAdminMenuVisible setting (backend domain, contracts, endpoint, migration) with matching frontend types, defaults, form wiring, and toggle in the Clip-Workflow modal — hides the Clips nav item from the admin sidebar when disabled Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,16 +7,21 @@ import {
|
||||
createEmptyAdminDashboard,
|
||||
createEmptyAdminRiskFlagsResponse,
|
||||
createEmptyAdminSeasonDetail,
|
||||
createEmptyAdminOptionalFeatureSettings,
|
||||
createEmptyAdminSiteSettings,
|
||||
createEmptyAdminWorkflowRulesResponse,
|
||||
createEmptyArchive,
|
||||
createEmptyDatabaseHealth,
|
||||
createEmptyPublicSponsors,
|
||||
normalizeSeasonDetail,
|
||||
} from './awards/defaults'
|
||||
import type {
|
||||
ApproveNominationPayload,
|
||||
AddNominationLinkBlacklistEntryPayload,
|
||||
CreateSeasonPayload,
|
||||
CreateClipPayload,
|
||||
CreateNominationPayload,
|
||||
CreateShowactApplicationPayload,
|
||||
CreateVotePayload,
|
||||
RejectNominationPayload,
|
||||
ResolveRiskFlagPayload,
|
||||
@@ -25,9 +30,14 @@ import type {
|
||||
AdminRiskQueryOptions,
|
||||
UpdateSeasonPayload,
|
||||
UpdateClipStatusPayload,
|
||||
UpdateNominationLinkBlacklistPayload,
|
||||
UpdateOptionalFeatureSettingsPayload,
|
||||
UpdateShowactStatusPayload,
|
||||
UpdateSiteSettingsPayload,
|
||||
UpdateWorkflowRulesPayload,
|
||||
UpsertCandidatePayload,
|
||||
UpsertCategoryPayload,
|
||||
UpsertSponsorPayload,
|
||||
} from '../types/awards'
|
||||
|
||||
interface AdminSeasonRefreshOptions {
|
||||
@@ -45,6 +55,7 @@ export const useAwardsStore = defineStore('awards', {
|
||||
try {
|
||||
this.overview = await api.getOverview()
|
||||
this.categories = await api.getSeasonCategories(this.overview.year)
|
||||
await this.loadPublicSponsors(this.overview.year)
|
||||
const overviewArchiveYears = this.overview.archiveYears ?? []
|
||||
const initialArchiveYear = overviewArchiveYears[0]?.year
|
||||
?? this.overview.winnersPreview[0]?.year
|
||||
@@ -70,18 +81,31 @@ export const useAwardsStore = defineStore('awards', {
|
||||
this.archive = createEmptyArchive(year)
|
||||
}
|
||||
},
|
||||
async loadPublicSponsors(year: number) {
|
||||
try {
|
||||
this.publicSponsors = await api.getPublicSponsors(year)
|
||||
return this.publicSponsors
|
||||
} catch {
|
||||
this.publicSponsors = createEmptyPublicSponsors(year)
|
||||
return this.publicSponsors
|
||||
}
|
||||
},
|
||||
async loadAdmin() {
|
||||
try {
|
||||
const [admin, adminSeasons, adminSiteSettings, databaseHealth] = await Promise.all([
|
||||
const [admin, adminSeasons, adminSiteSettings, adminOptionalFeatureSettings, adminWorkflowRules, databaseHealth] = await Promise.all([
|
||||
api.getAdminDashboard(),
|
||||
api.getAdminSeasons(),
|
||||
api.getAdminSiteSettings(),
|
||||
api.getAdminOptionalFeatureSettings(),
|
||||
api.getAdminWorkflowRules(),
|
||||
api.getDatabaseHealth(),
|
||||
])
|
||||
|
||||
this.admin = admin
|
||||
this.adminSeasons = adminSeasons
|
||||
this.adminSiteSettings = adminSiteSettings
|
||||
this.adminOptionalFeatureSettings = adminOptionalFeatureSettings
|
||||
this.adminWorkflowRules = adminWorkflowRules
|
||||
this.databaseHealth = databaseHealth
|
||||
|
||||
if (!this.adminSelectedSeasonId || !this.adminSeasons.some((season) => season.id === this.adminSelectedSeasonId)) {
|
||||
@@ -90,8 +114,16 @@ export const useAwardsStore = defineStore('awards', {
|
||||
|
||||
if (this.adminSelectedSeasonId) {
|
||||
this.adminSeasonDetail = normalizeSeasonDetail(await api.getAdminSeasonDetail(this.adminSelectedSeasonId))
|
||||
const [sponsors, showacts] = await Promise.all([
|
||||
api.getAdminSponsors(this.adminSelectedSeasonId),
|
||||
api.getAdminShowactApplications(this.adminSelectedSeasonId),
|
||||
])
|
||||
this.adminSponsors = sponsors
|
||||
this.adminShowactApplications = showacts
|
||||
} else {
|
||||
this.adminSeasonDetail = createEmptyAdminSeasonDetail()
|
||||
this.adminSponsors = []
|
||||
this.adminShowactApplications = []
|
||||
}
|
||||
this.apiMode = 'api'
|
||||
} catch {
|
||||
@@ -99,25 +131,32 @@ export const useAwardsStore = defineStore('awards', {
|
||||
this.adminSeasons = []
|
||||
this.adminSeasonDetail = createEmptyAdminSeasonDetail()
|
||||
this.adminSiteSettings = createEmptyAdminSiteSettings()
|
||||
this.adminOptionalFeatureSettings = createEmptyAdminOptionalFeatureSettings()
|
||||
this.adminWorkflowRules = createEmptyAdminWorkflowRulesResponse()
|
||||
this.adminRiskHistory = []
|
||||
this.adminRiskFlagsPage = createEmptyAdminRiskFlagsResponse()
|
||||
this.adminRiskHistoryPage = createEmptyAdminRiskFlagsResponse()
|
||||
this.adminSponsors = []
|
||||
this.adminShowactApplications = []
|
||||
this.databaseHealth = createEmptyDatabaseHealth()
|
||||
this.adminSelectedSeasonId = null
|
||||
}
|
||||
},
|
||||
async loadAdminContentWorkspace() {
|
||||
try {
|
||||
const [adminSiteSettings, databaseHealth] = await Promise.all([
|
||||
const [adminSiteSettings, adminOptionalFeatureSettings, databaseHealth] = await Promise.all([
|
||||
api.getAdminSiteSettings(),
|
||||
api.getAdminOptionalFeatureSettings(),
|
||||
api.getDatabaseHealth(),
|
||||
])
|
||||
|
||||
this.adminSiteSettings = adminSiteSettings
|
||||
this.adminOptionalFeatureSettings = adminOptionalFeatureSettings
|
||||
this.databaseHealth = databaseHealth
|
||||
this.apiMode = 'api'
|
||||
} catch {
|
||||
this.adminSiteSettings = createEmptyAdminSiteSettings()
|
||||
this.adminOptionalFeatureSettings = createEmptyAdminOptionalFeatureSettings()
|
||||
this.databaseHealth = createEmptyDatabaseHealth()
|
||||
}
|
||||
},
|
||||
@@ -129,9 +168,17 @@ export const useAwardsStore = defineStore('awards', {
|
||||
try {
|
||||
this.adminSelectedSeasonId = seasonId
|
||||
this.adminSeasonDetail = normalizeSeasonDetail(await api.getAdminSeasonDetail(seasonId))
|
||||
const [sponsors, showacts] = await Promise.all([
|
||||
api.getAdminSponsors(seasonId),
|
||||
api.getAdminShowactApplications(seasonId),
|
||||
])
|
||||
this.adminSponsors = sponsors
|
||||
this.adminShowactApplications = showacts
|
||||
this.apiMode = 'api'
|
||||
} catch {
|
||||
this.adminSeasonDetail = createEmptyAdminSeasonDetail()
|
||||
this.adminSponsors = []
|
||||
this.adminShowactApplications = []
|
||||
}
|
||||
},
|
||||
async initializeAdminWorkspace() {
|
||||
@@ -209,6 +256,50 @@ export const useAwardsStore = defineStore('awards', {
|
||||
submitClip(payload: CreateClipPayload) {
|
||||
return api.submitClip(payload)
|
||||
},
|
||||
submitShowactApplication(payload: CreateShowactApplicationPayload) {
|
||||
return api.submitShowactApplication(payload)
|
||||
},
|
||||
async loadAdminExtras(seasonId?: number | null) {
|
||||
const resolvedSeasonId = seasonId ?? this.adminSelectedSeasonId
|
||||
if (!resolvedSeasonId) {
|
||||
this.adminSponsors = []
|
||||
this.adminShowactApplications = []
|
||||
return { sponsors: this.adminSponsors, showacts: this.adminShowactApplications }
|
||||
}
|
||||
|
||||
const [sponsors, showacts] = await Promise.all([
|
||||
api.getAdminSponsors(resolvedSeasonId),
|
||||
api.getAdminShowactApplications(resolvedSeasonId),
|
||||
])
|
||||
this.adminSponsors = sponsors
|
||||
this.adminShowactApplications = showacts
|
||||
return { sponsors, showacts }
|
||||
},
|
||||
async createAdminSponsor(seasonId: number, payload: UpsertSponsorPayload) {
|
||||
const result = await api.createAdminSponsor(seasonId, payload)
|
||||
await Promise.all([this.loadAdminExtras(seasonId), this.loadPublicSponsors(this.overview.year)])
|
||||
return result
|
||||
},
|
||||
async updateAdminSponsor(sponsorId: number, seasonId: number, payload: UpsertSponsorPayload) {
|
||||
const result = await api.updateAdminSponsor(sponsorId, payload)
|
||||
await Promise.all([this.loadAdminExtras(seasonId), this.loadPublicSponsors(this.overview.year)])
|
||||
return result
|
||||
},
|
||||
async deleteAdminSponsor(sponsorId: number, seasonId: number) {
|
||||
const result = await api.deleteAdminSponsor(sponsorId)
|
||||
await Promise.all([this.loadAdminExtras(seasonId), this.loadPublicSponsors(this.overview.year)])
|
||||
return result
|
||||
},
|
||||
async updateAdminShowactStatus(applicationId: number, seasonId: number, payload: UpdateShowactStatusPayload) {
|
||||
const result = await api.updateAdminShowactStatus(applicationId, payload)
|
||||
await this.loadAdminExtras(seasonId)
|
||||
return result
|
||||
},
|
||||
async deleteAdminShowactApplication(applicationId: number, seasonId: number) {
|
||||
const result = await api.deleteAdminShowactApplication(applicationId)
|
||||
await this.loadAdminExtras(seasonId)
|
||||
return result
|
||||
},
|
||||
async updateAdminSeason(seasonId: number, payload: UpdateSeasonPayload) {
|
||||
const result = await api.updateAdminSeason(seasonId, payload)
|
||||
await this.refreshAdminSeasonWorkspace(seasonId, { reloadAdmin: true, reloadHome: true })
|
||||
@@ -292,6 +383,15 @@ export const useAwardsStore = defineStore('awards', {
|
||||
await Promise.allSettled(nominationIds.map((id) => api.rejectAdminNomination(id, { reviewNote })))
|
||||
await this.refreshAdminSeasonWorkspace(seasonId, { reloadAdmin: true })
|
||||
},
|
||||
loadAdminNominationLinkBlacklist() {
|
||||
return api.getAdminNominationLinkBlacklist()
|
||||
},
|
||||
updateAdminNominationLinkBlacklist(payload: UpdateNominationLinkBlacklistPayload) {
|
||||
return api.updateAdminNominationLinkBlacklist(payload)
|
||||
},
|
||||
addAdminNominationLinkBlacklistEntry(payload: AddNominationLinkBlacklistEntryPayload) {
|
||||
return api.addAdminNominationLinkBlacklistEntry(payload)
|
||||
},
|
||||
async resolveRiskFlag(riskFlagId: number, payload: ResolveRiskFlagPayload) {
|
||||
const result = await api.resolveRiskFlag(riskFlagId, payload)
|
||||
await Promise.all([
|
||||
@@ -328,6 +428,23 @@ export const useAwardsStore = defineStore('awards', {
|
||||
async updateAdminRiskRules(payload: import('../types/awards').UpdateRiskRulesPayload) {
|
||||
return api.updateAdminRiskRules(payload)
|
||||
},
|
||||
async loadAdminOptionalFeatureSettings() {
|
||||
this.adminOptionalFeatureSettings = await api.getAdminOptionalFeatureSettings()
|
||||
return this.adminOptionalFeatureSettings
|
||||
},
|
||||
async updateAdminOptionalFeatureSettings(payload: UpdateOptionalFeatureSettingsPayload) {
|
||||
this.adminOptionalFeatureSettings = await api.updateAdminOptionalFeatureSettings(payload)
|
||||
await this.loadHomeData()
|
||||
return this.adminOptionalFeatureSettings
|
||||
},
|
||||
async loadAdminWorkflowRules() {
|
||||
this.adminWorkflowRules = await api.getAdminWorkflowRules()
|
||||
return this.adminWorkflowRules
|
||||
},
|
||||
async updateAdminWorkflowRules(payload: UpdateWorkflowRulesPayload) {
|
||||
this.adminWorkflowRules = await api.updateAdminWorkflowRules(payload)
|
||||
return this.adminWorkflowRules
|
||||
},
|
||||
async updateAdminSiteSettings(payload: UpdateSiteSettingsPayload) {
|
||||
const result = await api.updateAdminSiteSettings(payload)
|
||||
this.adminSiteSettings = await api.getAdminSiteSettings()
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import { ApiRequestError } from '../../lib/http'
|
||||
import type {
|
||||
AdminDashboardResponse,
|
||||
AdminOptionalFeatureSettingsResponse,
|
||||
AdminRiskFlag,
|
||||
AdminRiskFlagsResponse,
|
||||
AdminSeasonDetailResponse,
|
||||
AdminSeasonListItem,
|
||||
AdminShowactApplicationItem,
|
||||
AdminSiteSettingsResponse,
|
||||
AdminSponsorItem,
|
||||
AdminWorkflowRulesResponse,
|
||||
DatabaseHealthResponse,
|
||||
OverviewResponse,
|
||||
PublicSponsorsResponse,
|
||||
SeasonCategoriesResponse,
|
||||
WinnerArchiveResponse,
|
||||
} from '../../types/awards'
|
||||
@@ -39,10 +44,26 @@ export function createEmptyOverview(): OverviewResponse {
|
||||
socialLinks: [],
|
||||
footerLinks: [],
|
||||
},
|
||||
featureFlags: {
|
||||
clipSubmissionsEnabled: false,
|
||||
clipReviewEnabled: true,
|
||||
clipAdminMenuVisible: true,
|
||||
clipSubmissionDisabledMessage: 'Clip-Einreichungen sind aktuell geschlossen.',
|
||||
showactApplicationsEnabled: false,
|
||||
showactApplicationDisabledMessage: 'Showact-Bewerbungen sind aktuell geschlossen.',
|
||||
sponsorsVisible: true,
|
||||
},
|
||||
faq: [],
|
||||
}
|
||||
}
|
||||
|
||||
export function createEmptyPublicSponsors(year = new Date().getFullYear()): PublicSponsorsResponse {
|
||||
return {
|
||||
year,
|
||||
items: [],
|
||||
}
|
||||
}
|
||||
|
||||
export function createEmptyCategories(): SeasonCategoriesResponse {
|
||||
return {
|
||||
seasonId: 0,
|
||||
@@ -81,6 +102,12 @@ export function createEmptyAdminRiskFlagsResponse(): AdminRiskFlagsResponse {
|
||||
}
|
||||
}
|
||||
|
||||
export function createEmptyAdminWorkflowRulesResponse(): AdminWorkflowRulesResponse {
|
||||
return {
|
||||
rules: [],
|
||||
}
|
||||
}
|
||||
|
||||
export function createEmptyAdminSeasonDetail(): AdminSeasonDetailResponse {
|
||||
return {
|
||||
id: 0,
|
||||
@@ -122,11 +149,25 @@ export function createEmptyAdminSiteSettings(): AdminSiteSettingsResponse {
|
||||
contactContent: '',
|
||||
sponsorsUrl: '',
|
||||
sponsorsContent: '',
|
||||
showactsUrl: '',
|
||||
showactsContent: '',
|
||||
socialLinks: [],
|
||||
faq: [],
|
||||
}
|
||||
}
|
||||
|
||||
export function createEmptyAdminOptionalFeatureSettings(): AdminOptionalFeatureSettingsResponse {
|
||||
return {
|
||||
clipSubmissionsEnabled: false,
|
||||
clipReviewEnabled: true,
|
||||
clipAdminMenuVisible: true,
|
||||
clipSubmissionDisabledMessage: 'Clip-Einreichungen sind aktuell geschlossen.',
|
||||
showactApplicationsEnabled: false,
|
||||
showactApplicationDisabledMessage: 'Showact-Bewerbungen sind aktuell geschlossen.',
|
||||
sponsorsVisible: true,
|
||||
}
|
||||
}
|
||||
|
||||
export function createEmptyDatabaseHealth(): DatabaseHealthResponse {
|
||||
return {
|
||||
provider: 'postgres',
|
||||
@@ -143,10 +184,15 @@ export function createAwardsState() {
|
||||
overview: createEmptyOverview(),
|
||||
categories: createEmptyCategories(),
|
||||
archive: createEmptyArchive(),
|
||||
publicSponsors: createEmptyPublicSponsors(),
|
||||
admin: createEmptyAdminDashboard(),
|
||||
adminSeasons: [] as AdminSeasonListItem[],
|
||||
adminSeasonDetail: createEmptyAdminSeasonDetail(),
|
||||
adminSiteSettings: createEmptyAdminSiteSettings(),
|
||||
adminOptionalFeatureSettings: createEmptyAdminOptionalFeatureSettings(),
|
||||
adminWorkflowRules: createEmptyAdminWorkflowRulesResponse(),
|
||||
adminSponsors: [] as AdminSponsorItem[],
|
||||
adminShowactApplications: [] as AdminShowactApplicationItem[],
|
||||
adminRiskHistory: [] as AdminRiskFlag[],
|
||||
adminRiskFlagsPage: createEmptyAdminRiskFlagsResponse(),
|
||||
adminRiskHistoryPage: createEmptyAdminRiskFlagsResponse(),
|
||||
@@ -167,7 +213,15 @@ export function normalizeSeasonDetail(detail: AdminSeasonDetailResponse): AdminS
|
||||
return {
|
||||
...detail,
|
||||
categories: detail.categories ?? [],
|
||||
candidates: detail.candidates ?? [],
|
||||
candidates: (detail.candidates ?? []).map((candidate) => ({
|
||||
...candidate,
|
||||
acceptanceStatus: candidate.acceptanceStatus ?? 'open',
|
||||
acceptanceNote: candidate.acceptanceNote ?? null,
|
||||
clipCompilationUrl: candidate.clipCompilationUrl ?? null,
|
||||
clipCompilationTitle: candidate.clipCompilationTitle ?? null,
|
||||
clipCompilationPlatform: candidate.clipCompilationPlatform ?? null,
|
||||
clipEmbedStatus: candidate.clipEmbedStatus ?? 'unchecked',
|
||||
})),
|
||||
pendingNominations: detail.pendingNominations ?? [],
|
||||
reviewedNominations: detail.reviewedNominations ?? [],
|
||||
results: detail.results ?? [],
|
||||
|
||||
Reference in New Issue
Block a user