Redesign public site, add phase gating and clip submission
- Rebuild landing page and Nominations/Voting/Winners views in the Jayuhime brand style (purple/gold/pastel-rainbow, stars, PageHero banner) - Gate participation by season phase (nominate/clip share the nomination window, vote in the voting window); hide closed/locked pages from nav - Add login-gated clip submission flow (link-based) + voting clip links - Make candidate admin scalable: searchable, filterable, paginated table with modal create/edit and confirm-delete; add delete API/store actions - New reusable Modal and PageHero components, usePhases composable - Segmented top nav and full-size header on admin routes - vite: honor PORT env for preview Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ import type {
|
||||
AdminSeasonDetailResponse,
|
||||
AdminSeasonListItem,
|
||||
AuthSession,
|
||||
CreateClipPayload,
|
||||
CreateNominationPayload,
|
||||
CreateVotePayload,
|
||||
LoginPayload,
|
||||
@@ -40,6 +41,23 @@ async function getJson<T>(path: string): Promise<T> {
|
||||
return response.json() as Promise<T>
|
||||
}
|
||||
|
||||
async function sendDelete<TResponse>(path: string): Promise<TResponse> {
|
||||
const token = getAuthToken()
|
||||
const response = await fetch(`${API_URL}${path}`, {
|
||||
method: 'DELETE',
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : undefined,
|
||||
}).catch(() => {
|
||||
throw new Error(`API nicht erreichbar (${API_URL}). Bitte Backend starten.`)
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text()
|
||||
throw new Error(error || `API request failed for ${path}`)
|
||||
}
|
||||
|
||||
return response.json() as Promise<TResponse>
|
||||
}
|
||||
|
||||
async function sendJson<TResponse>(path: string, method: 'POST' | 'PUT', body: unknown): Promise<TResponse> {
|
||||
const token = getAuthToken()
|
||||
const response = await fetch(`${API_URL}${path}`, {
|
||||
@@ -78,6 +96,8 @@ export const api = {
|
||||
sendJson<{ saved: number; category: string }>('/api/public/nominations', 'POST', payload),
|
||||
submitVote: (payload: CreateVotePayload) =>
|
||||
sendJson<{ ballotId: number; entries: number }>('/api/public/votes', 'POST', payload),
|
||||
submitClip: (payload: CreateClipPayload) =>
|
||||
sendJson<{ saved: boolean; clipId: number }>('/api/public/clips', 'POST', payload),
|
||||
updateAdminSeason: (seasonId: number, payload: UpdateSeasonPayload) =>
|
||||
sendJson<{ saved: boolean; seasonId: number }>(`/api/admin/seasons/${seasonId}`, 'PUT', payload),
|
||||
createAdminCategory: (seasonId: number, payload: UpsertCategoryPayload) =>
|
||||
@@ -88,6 +108,10 @@ export const api = {
|
||||
sendJson<{ saved: boolean; candidateId: number }>(`/api/admin/seasons/${seasonId}/candidates`, 'POST', payload),
|
||||
updateAdminCandidate: (candidateId: number, payload: UpsertCandidatePayload) =>
|
||||
sendJson<{ saved: boolean; candidateId: number }>(`/api/admin/candidates/${candidateId}`, 'PUT', payload),
|
||||
deleteAdminCandidate: (candidateId: number) =>
|
||||
sendDelete<{ deleted: boolean; candidateId: number }>(`/api/admin/candidates/${candidateId}`),
|
||||
deleteAdminCategory: (categoryId: number) =>
|
||||
sendDelete<{ deleted: boolean; categoryId: number }>(`/api/admin/categories/${categoryId}`),
|
||||
approveAdminNomination: (nominationId: number, payload: ApproveNominationPayload) =>
|
||||
sendJson<{ saved: boolean; nominationId: number; candidateId: number; created: boolean }>(
|
||||
`/api/admin/nominations/${nominationId}/approve`,
|
||||
|
||||
Reference in New Issue
Block a user