@@ -207,14 +207,20 @@ export const fetchTeamsByTournament = async (tournamentId: string): Promise<UITe
207207 short : String ( team . short_name ?? team . team_code ?? "" ) ,
208208 players : Number ( team . players_count ?? team . player_count ?? 0 ) ,
209209 } ) ) ;
210- // Always refresh player counts from the dedicated API per requirement
210+ // Refresh counts using the per-team-per-tournament players endpoint (authoritative per requirement)
211211 const withCounts = await Promise . all (
212212 base . map ( async ( t ) => {
213213 try {
214- const count = await getTeamPlayerCount ( t . id ) ;
215- return { ...t , players : Number ( count || 0 ) } ;
214+ const list = await getTeamPlayersByTournament ( t . id , tournamentId ) ;
215+ return { ...t , players : Array . isArray ( list ) ? list . length : 0 } ;
216216 } catch {
217- return t ; // fall back to existing value if count API fails
217+ // Fallback to generic count endpoint if specific endpoint fails
218+ try {
219+ const count = await getTeamPlayerCount ( t . id ) ;
220+ return { ...t , players : Number ( count || 0 ) } ;
221+ } catch {
222+ return t ;
223+ }
218224 }
219225 } )
220226 ) ;
@@ -396,10 +402,15 @@ export const API_PATHS = {
396402} as const ;
397403
398404type JsonInit = Omit < RequestInit , "body" > & { body ?: unknown } ;
399- const ACCESS_TOKEN_KEY = "cpl_access_token" ;
405+ const ACCESS_TOKEN_KEY_PRIMARY = "cpl_access_token" ;
406+ const ACCESS_TOKEN_KEY_FALLBACK = "auth_token" ; // used by Admin.tsx
400407export function getAuthToken ( ) : string | null {
401408 try {
402- return localStorage . getItem ( ACCESS_TOKEN_KEY ) ;
409+ // Prefer the primary key, but fall back to Admin's key if present
410+ return (
411+ localStorage . getItem ( ACCESS_TOKEN_KEY_PRIMARY ) ||
412+ localStorage . getItem ( ACCESS_TOKEN_KEY_FALLBACK )
413+ ) ;
403414 } catch {
404415 return null ;
405416 }
@@ -519,19 +530,92 @@ export function backgroundImageUrl(filename: string): string {
519530}
520531
521532// Tournament images
522- export async function uploadTournamentImage ( file : File ) : Promise < unknown > {
533+ export async function uploadTournamentImage ( file : File , tournamentId ?: string | number ) : Promise < unknown > {
534+ // Backend expects: POST /api/v1/upload/tounament/image?tounament_id={id}
535+ // We'll be generous and also include a form field for broader compatibility.
523536 const fd = new FormData ( ) ;
524537 fd . set ( "file" , file ) ;
538+ if ( tournamentId !== undefined ) {
539+ // Some backends read form-data, others read query param (with the misspelled key)
540+ fd . set ( "tournament_id" , String ( tournamentId ) ) ;
541+ }
525542 const token = getAuthToken ( ) ;
526- const res = await fetch ( buildUrl ( API_PATHS . uploadTournamentImage ) , { method : "POST" , headers : token ? { Authorization : `Bearer ${ token } ` } : undefined , body : fd } ) ;
543+ const url = tournamentId !== undefined
544+ ? `${ buildUrl ( API_PATHS . uploadTournamentImage ) } ?tounament_id=${ encodeURIComponent ( String ( tournamentId ) ) } `
545+ : buildUrl ( API_PATHS . uploadTournamentImage ) ;
546+ const res = await fetch ( url , { method : "POST" , headers : token ? { Authorization : `Bearer ${ token } ` } : undefined , body : fd } ) ;
527547 if ( ! res . ok ) throw new Error ( `${ res . status } ${ res . statusText } ` ) ;
528548 return await res . json ( ) ;
529549}
530- export async function listTournamentImageFiles ( ) : Promise < unknown > {
531- return apiFetchJson ( API_PATHS . listTournamentImages ) ;
550+ export async function listTournamentImageFiles ( tournamentId ?: string ) : Promise < unknown > {
551+ const path = tournamentId
552+ ? `${ API_PATHS . listTournamentImages } ?tounament_id=${ encodeURIComponent ( tournamentId ) } `
553+ : API_PATHS . listTournamentImages ;
554+ return apiFetchJson ( path ) ;
532555}
533556export function tournamentImageUrl ( filename : string ) : string {
534- return buildUrl ( API_PATHS . getTournamentImage ( filename ) ) ;
557+ const safe = encodeURIComponent ( filename ) ;
558+ return buildUrl ( API_PATHS . getTournamentImage ( safe ) ) ;
559+ }
560+
561+ // Gallery: tournament images
562+ export type TournamentImageFile = { filename : string ; url : string ; id ?: string ; tournament_id ?: string ; year ?: string } ;
563+ export async function fetchTournamentImages ( tournamentId ?: string ) : Promise < TournamentImageFile [ ] > {
564+ try {
565+ let raw : unknown ;
566+ try {
567+ raw = await listTournamentImageFiles ( tournamentId ) ;
568+ } catch ( err ) {
569+ // If the API strictly requires tounament_id and rejects other shapes, or vice versa,
570+ // retry without the param to get a superset and then filter locally.
571+ try {
572+ raw = await listTournamentImageFiles ( undefined ) ;
573+ } catch {
574+ raw = [ ] ;
575+ }
576+ }
577+ type RawItem = { filename ?: string ; name ?: string ; file ?: string ; photo_url ?: string ; id ?: string | number ; tournament_id ?: string | number ; tournamentId ?: string | number ; tournament ?: string | number ; year ?: string | number } | string ;
578+ // Accept a variety of server payload shapes
579+ const extractArray = ( val : unknown ) : RawItem [ ] => {
580+ if ( Array . isArray ( val ) ) return val as RawItem [ ] ;
581+ if ( val && typeof val === 'object' ) {
582+ const obj = val as Record < string , unknown > ;
583+ const keys = [ "data" , "files" , "filenames" , "results" , "items" , "response" ] ;
584+ for ( const k of keys ) {
585+ const v = obj [ k ] ;
586+ if ( Array . isArray ( v ) ) return v as RawItem [ ] ;
587+ }
588+ }
589+ return [ ] ;
590+ } ;
591+ const arr : RawItem [ ] = extractArray ( raw ) ;
592+ const norm : TournamentImageFile [ ] = arr
593+ . map ( ( it : RawItem ) => {
594+ const obj : { filename ?: string ; name ?: string ; file ?: string ; photo_url ?: string ; id ?: string | number ; tournament_id ?: string | number ; tournamentId ?: string | number ; tournament ?: string | number ; year ?: string | number } = typeof it === 'string' ? { filename : it } : ( it as Exclude < RawItem , string > ) ;
595+ const fileFromObj = obj ?. filename ?? obj ?. name ?? obj ?. file ;
596+ const filenameRaw = String ( fileFromObj ?? ( typeof it === 'string' ? it : '' ) ?? "" ) ;
597+ const fromPhotoUrl = obj ?. photo_url ? extractFilename ( String ( obj . photo_url ) ) : undefined ;
598+ const filename = String ( fromPhotoUrl ?? filenameRaw ) ;
599+ const tidRaw = obj ?. tournament_id ?? obj ?. tournamentId ?? obj ?. tournament ;
600+ const tid = tidRaw !== undefined && tidRaw !== null && String ( tidRaw ) !== "" ? String ( tidRaw ) : undefined ;
601+ const yr = obj ?. year !== undefined ? String ( obj . year ) : undefined ;
602+ const id = obj ?. id !== undefined ? String ( obj . id ) : filename ;
603+ // Backend serves images via /api/v1/tounament/image/{filename}
604+ const url = tournamentImageUrl ( filename ) ;
605+ return { filename, url, id, tournament_id : tid , year : yr } as TournamentImageFile ;
606+ } )
607+ . filter ( ( x ) => x . filename ) ;
608+ if ( ! tournamentId ) return norm ;
609+ const key = String ( tournamentId ) ;
610+ // Filter by explicit t.tournament_id if present; otherwise try loose match by filename containing id or year
611+ const byId = norm . filter ( ( x ) => x . tournament_id && String ( x . tournament_id ) === key ) ;
612+ if ( byId . length > 0 ) return byId ;
613+ const loose = norm . filter ( ( x ) => x . filename . includes ( key ) || ( x . year && x . year === key ) ) ;
614+ // If no match at all, return everything so users at least see uploads
615+ return loose . length > 0 ? loose : norm ;
616+ } catch {
617+ return [ ] ;
618+ }
535619}
536620
537621// Admin tournaments
0 commit comments