From 4feac9a1fc3906409ae4b7c6b80cc39082461309 Mon Sep 17 00:00:00 2001 From: Heidi Date: Sat, 16 May 2026 11:58:24 +0100 Subject: [PATCH] fix --- server.cjs | 17 +++++++++++++++++ src/App.jsx | 33 ++++++++++++++++++++++++++++----- vite.config.js | 14 ++++++++++++++ 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/server.cjs b/server.cjs index 7ab12d3..0195fac 100644 --- a/server.cjs +++ b/server.cjs @@ -1533,6 +1533,23 @@ function allowedApiTarget(req) { return url } + if (pathname === '/api/tss/teams/search') { + const keys = [...params.keys()] + const query = params.get('q') || params.get('name') || '' + const limit = Number(params.get('limit') || 10) + if ( + keys.some((key) => !['q', 'name', 'limit'].includes(key)) || + query.length < 2 || + query.length > MAX_TEAM_NAME_LENGTH || + !Number.isInteger(limit) || + limit < 1 || + limit > 20 + ) { + return null + } + return url + } + const teamMatch = pathname.match(/^\/api\/tss\/teams\/([^/]+)(?:\/(history|games))?$/) if (!teamMatch || [...params.keys()].length) return null diff --git a/src/App.jsx b/src/App.jsx index 697ae75..9043fb6 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -19,6 +19,7 @@ const apiEndpoints = { teams: '/api/tss/leaderboard/teams?limit=100', teamsHealth: '/api/tss/leaderboard/teams?limit=1', resolve: (name) => `/api/tss/teams/resolve?name=${encodeURIComponent(name)}`, + searchTeams: (name) => `/api/tss/teams/search?q=${encodeURIComponent(name)}&limit=10`, detail: (name) => `/api/tss/teams/${encodeURIComponent(name)}`, history: (name) => `/api/tss/teams/${encodeURIComponent(name)}/history`, games: (name) => `/api/tss/teams/${encodeURIComponent(name)}/games`, @@ -547,6 +548,7 @@ function AppContent() { const [showFloatingNav, setShowFloatingNav] = useState(() => window.scrollY > 40) const [teamQuery, setTeamQuery] = useState('') const [searchHint, setSearchHint] = useState({ status: 'idle', name: '' }) + const [teamSearchResults, setTeamSearchResults] = useState([]) const [profile, setProfile] = useState({ teamName: '', detail: { status: 'idle', data: null, error: null }, @@ -733,20 +735,40 @@ function AppContent() { const query = teamQuery.trim() if (query.length < 2) { setSearchHint({ status: 'idle', name: '' }) + setTeamSearchResults([]) return } const controller = new AbortController() const timer = window.setTimeout(() => { setSearchHint({ status: 'loading', name: '' }) - fetchJson(apiEndpoints.resolve(query), controller.signal) + fetchJson(apiEndpoints.searchTeams(query), controller.signal) .then((data) => { - const name = data.tag_name || data.short_name || data.long_name || query - setSearchHint({ status: 'ready', name }) + const results = (data.teams || data.results || []) + .map((team) => ({ + name: bestTeamName(team), + detail: team.long_name || team.short_name || '', + aliases: [team.tag_name, team.short_name, team.long_name].filter(Boolean), + })) + .filter((team) => team.name) + .slice(0, 10) + setTeamSearchResults(results) + setSearchHint(results.length ? { status: 'ready', name: results[0].name } : { status: 'error', name: '' }) }) .catch(() => { if (!controller.signal.aborted) { - setSearchHint({ status: 'error', name: '' }) + fetchJson(apiEndpoints.resolve(query), controller.signal) + .then((data) => { + const name = data.tag_name || data.short_name || data.long_name || '' + setTeamSearchResults(name ? [{ name, detail: data.long_name || data.short_name || '', aliases: [name] }] : []) + setSearchHint(name ? { status: 'ready', name } : { status: 'error', name: '' }) + }) + .catch(() => { + if (!controller.signal.aborted) { + setTeamSearchResults([]) + setSearchHint({ status: 'error', name: '' }) + } + }) } }) }, 350) @@ -1060,7 +1082,7 @@ function AppContent() { }, [route.page, route.teamName]) const topTeamName = bestTeamName(teams[0]) - const teamSuggestions = useMemo(() => { + const localTeamSuggestions = useMemo(() => { const query = teamQuery.trim().toLowerCase() const seen = new Set() @@ -1078,6 +1100,7 @@ function AppContent() { }) .slice(0, 10) }, [teamQuery, teams]) + const teamSuggestions = teamSearchResults.length ? teamSearchResults : localTeamSuggestions const searchPlaceholder = searchHint.status === 'ready' ? `Found ${searchHint.name}` diff --git a/vite.config.js b/vite.config.js index a03d37e..d4082b3 100644 --- a/vite.config.js +++ b/vite.config.js @@ -92,6 +92,20 @@ function isAllowedApiUrl(req) { return keys.every((key) => key === 'name') && name.length >= 2 && name.length <= MAX_TEAM_NAME_LENGTH } + if (url.pathname === '/api/tss/teams/search') { + const keys = [...params.keys()] + const query = params.get('q') || params.get('name') || '' + const limit = Number(params.get('limit') || 10) + return ( + keys.every((key) => ['q', 'name', 'limit'].includes(key)) && + query.length >= 2 && + query.length <= MAX_TEAM_NAME_LENGTH && + Number.isInteger(limit) && + limit >= 1 && + limit <= 20 + ) + } + if ([...params.keys()].length) return false try {