ai generated solutions to our ai generated problems
This commit is contained in:
+63
-20
@@ -15,6 +15,7 @@ const apiEndpoints = {
|
|||||||
viewerEvent: '/api/viewers/event',
|
viewerEvent: '/api/viewers/event',
|
||||||
viewerDelete: '/api/viewers/delete',
|
viewerDelete: '/api/viewers/delete',
|
||||||
teams: '/api/tss/leaderboard/teams?limit=100',
|
teams: '/api/tss/leaderboard/teams?limit=100',
|
||||||
|
homeTeams: '/api/tss/leaderboard/teams?limit=4',
|
||||||
teamsHealth: '/api/tss/leaderboard/teams?limit=1',
|
teamsHealth: '/api/tss/leaderboard/teams?limit=1',
|
||||||
recentGames: '/api/tss/games/recent?limit=50',
|
recentGames: '/api/tss/games/recent?limit=50',
|
||||||
game: (gameId) => `/api/tss/games/${encodeURIComponent(gameId)}`,
|
game: (gameId) => `/api/tss/games/${encodeURIComponent(gameId)}`,
|
||||||
@@ -910,6 +911,7 @@ function App() {
|
|||||||
function AppContent() {
|
function AppContent() {
|
||||||
const [route, setRoute] = useState(() => parseRoute())
|
const [route, setRoute] = useState(() => parseRoute())
|
||||||
const [leaderboard, setLeaderboard] = useState({ status: 'idle', data: null, error: null })
|
const [leaderboard, setLeaderboard] = useState({ status: 'idle', data: null, error: null })
|
||||||
|
const [homeTeams, setHomeTeams] = useState({ status: 'idle', data: null, error: null })
|
||||||
const [live, setLive] = useState({ status: 'idle', data: null, error: null, updatedAt: 0 })
|
const [live, setLive] = useState({ status: 'idle', data: null, error: null, updatedAt: 0 })
|
||||||
const [uptime, setUptime] = useState({ status: 'idle', checks: [], history: [], updatedAt: null })
|
const [uptime, setUptime] = useState({ status: 'idle', checks: [], history: [], updatedAt: null })
|
||||||
const [viewers, setViewers] = useState({ status: 'idle', data: null, error: null, updatedAt: null })
|
const [viewers, setViewers] = useState({ status: 'idle', data: null, error: null, updatedAt: null })
|
||||||
@@ -932,6 +934,10 @@ function AppContent() {
|
|||||||
() => leaderboard.data?.teams || leaderboard.data?.squadrons || [],
|
() => leaderboard.data?.teams || leaderboard.data?.squadrons || [],
|
||||||
[leaderboard.data],
|
[leaderboard.data],
|
||||||
)
|
)
|
||||||
|
const teamsToWatch = useMemo(
|
||||||
|
() => homeTeams.data?.teams || homeTeams.data?.squadrons || teams.slice(0, 4),
|
||||||
|
[homeTeams.data, teams],
|
||||||
|
)
|
||||||
const matches = live.data?.matches || []
|
const matches = live.data?.matches || []
|
||||||
const liveRef = useRef(live)
|
const liveRef = useRef(live)
|
||||||
const navPillRef = useRef(null)
|
const navPillRef = useRef(null)
|
||||||
@@ -1163,7 +1169,7 @@ function AppContent() {
|
|||||||
}, [teamQuery])
|
}, [teamQuery])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!['home', 'teams', 'team', 'battle-logs'].includes(route.page)) return
|
if (!['teams', 'team', 'battle-logs'].includes(route.page)) return
|
||||||
if (leaderboard.status === 'ready' || leaderboard.status === 'loading') return
|
if (leaderboard.status === 'ready' || leaderboard.status === 'loading') return
|
||||||
|
|
||||||
const controller = new AbortController()
|
const controller = new AbortController()
|
||||||
@@ -1181,7 +1187,25 @@ function AppContent() {
|
|||||||
}, [leaderboard.status, route.page])
|
}, [leaderboard.status, route.page])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!['home', 'teams', 'team', 'battle-logs'].includes(route.page)) return
|
if (route.page !== 'home') return
|
||||||
|
if (homeTeams.status === 'ready' || homeTeams.status === 'loading') return
|
||||||
|
|
||||||
|
const controller = new AbortController()
|
||||||
|
setHomeTeams({ status: 'loading', data: null, error: null })
|
||||||
|
|
||||||
|
fetchJson(apiEndpoints.homeTeams, controller.signal)
|
||||||
|
.then((data) => setHomeTeams({ status: 'ready', data, error: null }))
|
||||||
|
.catch((error) => {
|
||||||
|
if (!controller.signal.aborted) {
|
||||||
|
setHomeTeams({ status: 'error', data: null, error: error.message })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => controller.abort()
|
||||||
|
}, [homeTeams.status, route.page])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!['teams', 'team', 'battle-logs'].includes(route.page)) return
|
||||||
|
|
||||||
const controller = new AbortController()
|
const controller = new AbortController()
|
||||||
const timer = window.setInterval(() => {
|
const timer = window.setInterval(() => {
|
||||||
@@ -1617,6 +1641,7 @@ function AppContent() {
|
|||||||
setTeamQuery={setTeamQuery}
|
setTeamQuery={setTeamQuery}
|
||||||
teamSuggestions={teamSuggestions}
|
teamSuggestions={teamSuggestions}
|
||||||
teams={teams}
|
teams={teams}
|
||||||
|
teamsToWatch={teamsToWatch}
|
||||||
teamQuery={teamQuery}
|
teamQuery={teamQuery}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -2104,6 +2129,7 @@ function Landing({
|
|||||||
setTeamQuery,
|
setTeamQuery,
|
||||||
teamSuggestions,
|
teamSuggestions,
|
||||||
teams,
|
teams,
|
||||||
|
teamsToWatch,
|
||||||
teamQuery,
|
teamQuery,
|
||||||
}) {
|
}) {
|
||||||
const treeRef = useRef(null)
|
const treeRef = useRef(null)
|
||||||
@@ -2190,15 +2216,16 @@ function Landing({
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<LandingOverview teams={teams} matches={matches} navigate={navigate} />
|
<LandingOverview teams={teams} teamsToWatch={teamsToWatch} matches={matches} navigate={navigate} />
|
||||||
<RecentGamesSection live={live} matches={matches} navigate={navigate} />
|
<RecentGamesSection live={live} matches={matches} navigate={navigate} />
|
||||||
<LandingTrustSection navigate={navigate} />
|
<LandingTrustSection navigate={navigate} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function LandingOverview({ teams, matches, navigate }) {
|
function LandingOverview({ teams, teamsToWatch, matches, navigate }) {
|
||||||
const activeTeams = teams.slice(0, 4)
|
const activeTeams = teamsToWatch.slice(0, 4)
|
||||||
|
const visibleTeamCount = teams.length || teamsToWatch.length
|
||||||
const totalPlayers = matches.reduce((sum, match) => sum + Number(match.player_count || 0), 0)
|
const totalPlayers = matches.reduce((sum, match) => sum + Number(match.player_count || 0), 0)
|
||||||
const latestMatch = matches[0]
|
const latestMatch = matches[0]
|
||||||
|
|
||||||
@@ -2216,7 +2243,7 @@ function LandingOverview({ teams, matches, navigate }) {
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="mt-6 grid gap-3 sm:grid-cols-3">
|
<div className="mt-6 grid gap-3 sm:grid-cols-3">
|
||||||
<LandingMetric label="Teams indexed" value={formatNumber(teams.length)} />
|
<LandingMetric label="Teams indexed" value={formatNumber(visibleTeamCount)} />
|
||||||
<LandingMetric label="Recent games" value={formatNumber(matches.length)} />
|
<LandingMetric label="Recent games" value={formatNumber(matches.length)} />
|
||||||
<LandingMetric label="Players seen" value={formatNumber(totalPlayers)} />
|
<LandingMetric label="Players seen" value={formatNumber(totalPlayers)} />
|
||||||
</div>
|
</div>
|
||||||
@@ -2800,49 +2827,65 @@ function GamePage({ gameId, navigate }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div className="overflow-x-auto">
|
||||||
|
<div className="min-w-[760px]">
|
||||||
|
{participants.length ? (
|
||||||
|
<div className="grid grid-cols-[minmax(220px,1fr)_80px_repeat(5,88px)] gap-3 border-b border-surface px-5 py-3 text-xs font-semibold uppercase tracking-wide text-text-soft">
|
||||||
|
<p>Team / player</p>
|
||||||
|
<p className="text-right">Players</p>
|
||||||
|
<p className="text-right">Ground</p>
|
||||||
|
<p className="text-right">Air</p>
|
||||||
|
<p className="text-right">Assists</p>
|
||||||
|
<p className="text-right">Score</p>
|
||||||
|
<p className="text-right">Deaths</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{participants.map((participant) => {
|
{participants.map((participant) => {
|
||||||
const won = String(participant.result || '').toLowerCase() === 'win'
|
const won = String(participant.result || '').toLowerCase() === 'win'
|
||||||
return (
|
return (
|
||||||
<div className="border-b border-surface" key={participant.team_name}>
|
<div className="border-b border-surface" key={participant.team_name}>
|
||||||
<button
|
<button
|
||||||
className="grid w-full gap-4 px-5 py-4 text-left transition hover:bg-surface md:grid-cols-[1fr_repeat(5,auto)] md:items-center"
|
className="grid w-full grid-cols-[minmax(220px,1fr)_80px_repeat(5,88px)] gap-3 px-5 py-3 text-left transition hover:bg-surface"
|
||||||
onClick={() => navigate(teamPath(participant.team_name))}
|
onClick={() => navigate(teamPath(participant.team_name))}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<p className={`truncate font-semibold ${won ? 'text-fury-violet' : 'text-text-soft'}`}>
|
<p className={`truncate font-semibold ${won ? 'text-fury-violet' : 'text-text-soft'}`}>
|
||||||
{participant.team_name}
|
{participant.team_name}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm">{formatNumber(participant.player_count)} players</p>
|
<p className="text-right text-sm">{formatNumber(participant.player_count)}</p>
|
||||||
<p className="text-sm">{formatNumber(participant.stats?.ground_kills)} ground</p>
|
<p className="text-right text-sm">{formatNumber(participant.stats?.ground_kills)}</p>
|
||||||
<p className="text-sm">{formatNumber(participant.stats?.air_kills)} air</p>
|
<p className="text-right text-sm">{formatNumber(participant.stats?.air_kills)}</p>
|
||||||
<p className="text-sm">{formatNumber(participant.stats?.assists)} assists</p>
|
<p className="text-right text-sm">{formatNumber(participant.stats?.assists)}</p>
|
||||||
<p className="text-sm">{formatNumber(participant.stats?.deaths)} deaths</p>
|
<p className="text-right text-sm text-text-muted">-</p>
|
||||||
|
<p className="text-right text-sm">{formatNumber(participant.stats?.deaths)}</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="pb-3">
|
<div className="pb-3">
|
||||||
{(participant.players || []).map((player) => (
|
{(participant.players || []).map((player) => (
|
||||||
<button
|
<button
|
||||||
className="grid w-full gap-3 px-5 py-2 pl-10 text-left text-sm transition hover:bg-surface md:grid-cols-[1fr_repeat(5,auto)] md:items-center sm:pl-14"
|
className="grid w-full grid-cols-[minmax(220px,1fr)_80px_repeat(5,88px)] gap-3 px-5 py-2 text-left text-sm transition hover:bg-surface"
|
||||||
key={player.uid}
|
key={player.uid}
|
||||||
onClick={() => navigate(`/players/${encodeURIComponent(player.uid)}`)}
|
onClick={() => navigate(`/players/${encodeURIComponent(player.uid)}`)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<div className="min-w-0">
|
<div className="min-w-0 pl-8 sm:pl-12">
|
||||||
<p className="truncate font-semibold text-text">{player.nick || player.uid}</p>
|
<p className="truncate font-semibold text-text">{player.nick || player.uid}</p>
|
||||||
<p className="text-xs text-text-soft">{player.uid}</p>
|
<p className="text-xs text-text-soft">{player.uid}</p>
|
||||||
</div>
|
</div>
|
||||||
<p>{formatNumber(player.stats?.ground_kills)} ground</p>
|
<p className="text-right text-text-muted">-</p>
|
||||||
<p>{formatNumber(player.stats?.air_kills)} air</p>
|
<p className="text-right">{formatNumber(player.stats?.ground_kills)}</p>
|
||||||
<p>{formatNumber(player.stats?.assists)} assists</p>
|
<p className="text-right">{formatNumber(player.stats?.air_kills)}</p>
|
||||||
<p>{formatNumber(player.stats?.score)} score</p>
|
<p className="text-right">{formatNumber(player.stats?.assists)}</p>
|
||||||
<p>{formatNumber(player.stats?.deaths)} deaths</p>
|
<p className="text-right">{formatNumber(player.stats?.score)}</p>
|
||||||
|
<p className="text-right">{formatNumber(player.stats?.deaths)}</p>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
{!participants.length ? (
|
{!participants.length ? (
|
||||||
<p className="px-5 py-10 text-sm text-text-soft">
|
<p className="px-5 py-10 text-sm text-text-soft">
|
||||||
|
|||||||
Reference in New Issue
Block a user