import React from "react"; import { Helmet } from "react-helmet"; import { XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Area, AreaChart } from "recharts"; import { API } from "../api/Api"; import { PortalCountData, ScoreLog } from "../api/Stats"; import "../css/Homepage.css"; import { Link } from "react-router-dom"; const Homepage: React.FC = () => { const [portalCountDataSingleplayer, setPortalCountDataSingleplayer] = React.useState([]); const [portalCountDataMultiplayer, setPortalCountDataMultiplayer] = React.useState([]); const [recentScores, setRecentScores] = React.useState([]); const [isLoading, setIsLoading] = React.useState(true); const [isLoadingScores, setIsLoadingScores] = React.useState(true); const [selectedMode, setSelectedMode] = React.useState<"singleplayer" | "multiplayer">("singleplayer"); const processTimelineData = (data: PortalCountData[]): PortalCountData[] => { if (data.length === 0) { return []; }; const startDate = new Date(data[0].date); const today = new Date(); const result: PortalCountData[] = []; let currentDate = new Date(startDate.getFullYear(), 0, 1); let dataIndex = 0; let currentCount = data[0].count; while (currentDate <= today) { while (dataIndex < data.length && new Date(data[dataIndex].date) <= currentDate) { currentCount = data[dataIndex].count; dataIndex++; } result.push({ date: currentDate.toISOString(), count: currentCount }); const nextDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate() + 7); if (nextDate.getMonth() !== currentDate.getMonth()) { currentDate = new Date(nextDate.getFullYear(), nextDate.getMonth(), 1); } else { currentDate = nextDate; } } return result; }; const processedDataSingleplayer = React.useMemo( () => processTimelineData(portalCountDataSingleplayer), [portalCountDataSingleplayer] ); const processedDataMultiplayer = React.useMemo( () => processTimelineData(portalCountDataMultiplayer), [portalCountDataMultiplayer] ); const getYearlyTicks = (data: PortalCountData[]): string[] => { if (data.length === 0) { return []; } const seenYears = new Set(); const ticks: string[] = []; for (const point of data) { const year = new Date(point.date).getFullYear(); if (!seenYears.has(year)) { seenYears.add(year); ticks.push(point.date); } } return ticks; }; const yearlyTicksSingleplayer = React.useMemo( () => getYearlyTicks(processedDataSingleplayer), [processedDataSingleplayer] ); const yearlyTicksMultiplayer = React.useMemo( () => getYearlyTicks(processedDataMultiplayer), [processedDataMultiplayer] ); const fetchPortalCountData = async () => { setIsLoading(true); const data = await API.get_portal_count_history(); setPortalCountDataSingleplayer(data?.timeline_singleplayer || []); setPortalCountDataMultiplayer(data?.timeline_multiplayer || []); setIsLoading(false); }; const fetchRecentScores = async () => { setIsLoadingScores(true); const scores = await API.get_recent_scores(); setRecentScores(scores); setIsLoadingScores(false); }; React.useEffect(() => { fetchPortalCountData(); fetchRecentScores(); }, []); const CustomTooltip = ({ active, payload }: any) => { if (active && payload && payload.length) { return (

{new Date(payload[0].payload.date).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" })}

{`Portal Count: ${payload[0].value}`}

); } return null; }; return (
LPHUB | Homepage

Welcome to Least Portals Hub!

Your ultimate destination for Portal 2 Least Portals speedrunning.

Least Portals World Record Timeline

{isLoading ? (
) : (selectedMode === "singleplayer" ? processedDataSingleplayer : processedDataMultiplayer).length > 0 ? ( <>
{ const d = new Date(date); return d.getFullYear().toString(); }} /> } />
) : (

No data available yet.

)}

Recent Scores

{isLoadingScores ? (
) : recentScores.length > 0 ? (
{recentScores.map((score, index) => (
{score.user.user_name}
{score.map.name}
{score.score_count} portals ยท {new Date(score.date).toISOString().split("T")[0]}
))}
) : (

No Recent Scores.

)}
); }; export default Homepage;