From a65d6d9127c3fa7f6a8ecaec5d1ffd1f47c2bc98 Mon Sep 17 00:00:00 2001 From: Arda Serdar Pektezol <1669855+pektezol@users.noreply.github.com> Date: Tue, 3 Sep 2024 00:08:53 +0300 Subject: refactor: port to typescript --- frontend/src/pages/Games.tsx | 51 +++++++ frontend/src/pages/Maps.tsx | 91 ++++++++++++ frontend/src/pages/Profile.tsx | 326 +++++++++++++++++++++++++++++++++++++++++ frontend/src/pages/User.tsx | 320 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 788 insertions(+) create mode 100644 frontend/src/pages/Games.tsx create mode 100644 frontend/src/pages/Maps.tsx create mode 100644 frontend/src/pages/Profile.tsx create mode 100644 frontend/src/pages/User.tsx (limited to 'frontend/src/pages') diff --git a/frontend/src/pages/Games.tsx b/frontend/src/pages/Games.tsx new file mode 100644 index 0000000..e4b33e5 --- /dev/null +++ b/frontend/src/pages/Games.tsx @@ -0,0 +1,51 @@ +import React from 'react'; + +import GameEntry from '../components/GameEntry'; +import { Game } from '../types/Game'; +import { API } from '../api/Api'; +import "../css/Maps.css" + +const Games: React.FC = () => { + const [games, setGames] = React.useState([]); + + const _fetch_games = async () => { + const games = await API.get_games(); + setGames(games); + }; + + const _page_load = () => { + const loaders = document.querySelectorAll(".loader"); + loaders.forEach((loader) => { + (loader as HTMLElement).style.display = "none"; + }); + } + + React.useEffect(() => { + document.querySelectorAll(".games-page-item-body").forEach((game, index) => { + game.innerHTML = ""; + }); + + _fetch_games(); + _page_load(); + }, []); + + return ( +
+
+ Games list +
+ +
+
+
+ {games.map((game, index) => ( + + ))} +
+
+
+
+ ); +}; + +export default Games; diff --git a/frontend/src/pages/Maps.tsx b/frontend/src/pages/Maps.tsx new file mode 100644 index 0000000..707d865 --- /dev/null +++ b/frontend/src/pages/Maps.tsx @@ -0,0 +1,91 @@ +import React from 'react'; +import { Link, useLocation } from 'react-router-dom'; + +import { PortalIcon, FlagIcon, ChatIcon } from '../images/Images'; +import Summary from '../components/Summary'; +import Leaderboards from '../components/Leaderboards'; +import Discussions from '../components/Discussions'; +import ModMenu from '../components/ModMenu'; +import { MapDiscussions, MapLeaderboard, MapSummary } from '../types/Map'; +import { API } from '../api/Api'; +import "../css/Maps.css"; + +interface MapProps { + isModerator: boolean; +}; + +const Maps: React.FC = ({ isModerator }) => { + + const [selectedRun, setSelectedRun] = React.useState(0); + + const [mapSummaryData, setMapSummaryData] = React.useState(undefined); + const [mapLeaderboardData, setMapLeaderboardData] = React.useState(undefined); + const [mapDiscussionsData, setMapDiscussionsData] = React.useState(undefined) + + + const [navState, setNavState] = React.useState(0); + + const location = useLocation(); + + const mapID = location.pathname.split("/")[2]; + + const _fetch_map_summary = async () => { + const mapSummary = await API.get_map_summary(mapID); + setMapSummaryData(mapSummary); + }; + + const _fetch_map_leaderboards = async () => { + const mapLeaderboards = await API.get_map_leaderboard(mapID); + setMapLeaderboardData(mapLeaderboards); + }; + + const _fetch_map_discussions = async () => { + const mapDiscussions = await API.get_map_discussions(mapID); + setMapDiscussionsData(mapDiscussions); + }; + + React.useEffect(() => { + _fetch_map_summary(); + _fetch_map_leaderboards(); + _fetch_map_discussions(); + }, []); + + if (!mapSummaryData) { + return ( + <> + ); + } + + return ( + <> + {isModerator && } + +
+ +
+
+
+
+ + +
{mapSummaryData.map.map_name} +
+ + +
+ +
+ + + +
+ + {navState === 0 && } + {navState === 1 && } + {navState === 2 && _fetch_map_discussions()} />} +
+ + ); +}; + +export default Maps; diff --git a/frontend/src/pages/Profile.tsx b/frontend/src/pages/Profile.tsx new file mode 100644 index 0000000..8654a03 --- /dev/null +++ b/frontend/src/pages/Profile.tsx @@ -0,0 +1,326 @@ +import React from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; + +import { SteamIcon, TwitchIcon, YouTubeIcon, PortalIcon, FlagIcon, StatisticsIcon, SortIcon, ThreedotIcon, DownloadIcon, HistoryIcon } from '../images/Images'; +import { UserProfile } from '../types/Profile'; +import { Game, GameChapters } from '../types/Game'; +import { Map } from '../types/Map'; +import "../css/Profile.css"; + +interface ProfileProps { + profile: UserProfile; +} + +const Profile: React.FC = ({ profile }) => { + + + const location = useLocation(); + const navigate = useNavigate(); + + React.useEffect(() => { + if (!profile) { + navigate("/"); + }; + }, [profile]); + + const [navState, setNavState] = React.useState(0); + const [pageNumber, setPageNumber] = React.useState(1); + const [pageMax, setPageMax] = React.useState(0); + + const [game, setGame] = React.useState("0") + const [gameData, setGameData] = React.useState([]); + const [chapter, setChapter] = React.useState("0") + const [chapterData, setChapterData] = React.useState(null); + const [maps, setMaps] = React.useState([]); + + function NavClick() { + if (profile) { + const btn = document.querySelectorAll("#section2 button"); + btn.forEach((e) => { (e as HTMLElement).style.backgroundColor = "#2b2e46" }); + (btn[navState] as HTMLElement).style.backgroundColor = "#202232"; + + document.querySelectorAll("section").forEach((e, i) => i >= 2 ? e.style.display = "none" : "") + if (navState === 0) { document.querySelectorAll(".profile1").forEach((e) => { (e as HTMLElement).style.display = "block" }); } + if (navState === 1) { document.querySelectorAll(".profile2").forEach((e) => { (e as HTMLElement).style.display = "block" }); } + } + } + + function UpdateProfile() { + fetch(`https://lp.ardapektezol.com/api/v1/profile`, { + method: 'POST', + headers: { Authorization: "" } + }).then(r => r.json()) + .then(d => d.success ? window.alert("profile updated") : window.alert(`Error: ${d.message}`)) + } + + function TicksToTime(ticks: number) { + + let seconds = Math.floor(ticks / 60) + let minutes = Math.floor(seconds / 60) + let hours = Math.floor(minutes / 60) + + let milliseconds = Math.floor((ticks % 60) * 1000 / 60) + seconds = seconds % 60; + minutes = minutes % 60; + + return `${hours === 0 ? "" : hours + ":"}${minutes === 0 ? "" : hours > 0 ? minutes.toString().padStart(2, '0') + ":" : (minutes + ":")}${minutes > 0 ? seconds.toString().padStart(2, '0') : seconds}.${milliseconds.toString().padStart(3, '0')} (${ticks})`; + } + + React.useEffect(() => { + fetch("https://lp.ardapektezol.com/api/v1/games") + .then(r => r.json()) + .then(d => { + setGameData(d.data) + setGame("0") + }) + + }, [location]); + + React.useEffect(() => { + if (game && game !== "0") { + fetch(`https://lp.ardapektezol.com/api/v1/games/${game}`) + .then(r => r.json()) + .then(d => { + setChapterData(d.data) + setChapter("0"); + // (document.querySelector('#select-chapter') as HTMLInputElement).value = "0" + }) + + } else if (game && game === "0") { + setPageMax(Math.ceil(profile.records.length / 20)) + setPageNumber(1) + } + + }, [game, location]); + + React.useEffect(() => { + if (game !== "0") { + if (chapter === "0") { + fetch(`https://lp.ardapektezol.com/api/v1/games/${game}/maps`) + .then(r => r.json()) + .then(d => { + setMaps(d.data.maps); + setPageMax(Math.ceil(d.data.maps.length / 20)) + setPageNumber(1) + }) + } else { + fetch(`https://lp.ardapektezol.com/api/v1/chapters/${chapter}`) + .then(r => r.json()) + .then(d => { + setMaps(d.data.maps); + setPageMax(Math.ceil(d.data.maps.length / 20)) + setPageNumber(1) + }) + + } + } + }, [game, chapter, chapterData]) + + return ( +
+
+ + {profile.profile + ? ( +
UpdateProfile()}> + profile-image + Refresh +
+ ) : ( +
+ profile-image +
+ )} + +
+
+
{profile.user_name}
+
+ {profile.country_code === "XX" ? "" : {profile.country_code}} +
+
+ {profile.titles.map(e => ( + + {e.name} + + ))} +
+
+
+ {profile.links.steam === "-" ? "" : Steam} + {profile.links.twitch === "-" ? "" : Twitch} + {profile.links.youtube === "-" ? "" : Youtube} + {profile.links.p2sr === "-" ? "" : P2SR} +
+ +
+
+
+ Overall + {profile.rankings.overall.rank === 0 ? "N/A " : "#" + profile.rankings.overall.rank + " "} + ({profile.rankings.overall.completion_count}/{profile.rankings.overall.completion_total}) + +
+
+ Singleplayer + {profile.rankings.singleplayer.rank === 0 ? "N/A " : "#" + profile.rankings.singleplayer.rank + " "} + ({profile.rankings.singleplayer.completion_count}/{profile.rankings.singleplayer.completion_total}) + +
+
+ Cooperative + {profile.rankings.cooperative.rank === 0 ? "N/A " : "#" + profile.rankings.cooperative.rank + " "} + ({profile.rankings.cooperative.completion_count}/{profile.rankings.cooperative.completion_total}) + +
+
+
+ + +
+ + +
+ + + + + +
+
+ {gameData === null ? : + + + } + + {game === "0" ? + + : chapterData === null ? : + + + } +
+
+ Map Name + Portals + WRΔ + Time + + Rank + Date +
+
+ + {pageNumber}/{pageMax} + +
+
+
+
+
+ + {game === "0" + ? ( + + profile.records.sort((a, b) => a.map_id - b.map_id) + .map((r, index) => ( + + Math.ceil((index + 1) / 20) === pageNumber ? ( + + + {i === 0 && r.scores.length > 1 ? : ""} + + + ))} + + + ) : "" + ))) : maps ? + + maps.filter(e => e.is_disabled === false).sort((a, b) => a.id - b.id) + .map((r, index) => { + if (Math.ceil((index + 1) / 20) === pageNumber) { + let record = profile.records.find((e) => e.map_id === r.id); + return record === undefined ? ( + + ) : ( + + + {i === 0 && record!.scores.length > 1 ? : ""} + + + ))} + + + ) + } else { return null } + }) : (<>{console.warn(maps)})} +
+
+
+ ); +}; + +export default Profile; diff --git a/frontend/src/pages/User.tsx b/frontend/src/pages/User.tsx new file mode 100644 index 0000000..1f6d8d0 --- /dev/null +++ b/frontend/src/pages/User.tsx @@ -0,0 +1,320 @@ +import React from 'react'; +import { useLocation } from 'react-router-dom'; + +import { SteamIcon, TwitchIcon, YouTubeIcon, PortalIcon, FlagIcon, StatisticsIcon, SortIcon, ThreedotIcon, DownloadIcon, HistoryIcon } from '../images/Images'; +import { UserProfile } from '../types/Profile'; +import { Game, GameChapters } from '../types/Game'; +import { Map } from '../types/Map'; +import { API } from '../api/Api'; +import { ticks_to_time } from '../utils/Time'; +import "../css/Profile.css"; + +const User: React.FC = () => { + const location = useLocation(); + + const [user, setUser] = React.useState(undefined); + + const [navState, setNavState] = React.useState(0); + const [pageNumber, setPageNumber] = React.useState(1); + const [pageMax, setPageMax] = React.useState(0); + + const [game, setGame] = React.useState("0") + const [gameData, setGameData] = React.useState([]); + const [chapter, setChapter] = React.useState("0") + const [chapterData, setChapterData] = React.useState(null); + const [maps, setMaps] = React.useState([]); + + function NavClick() { + if (user) { + const btn = document.querySelectorAll("#section2 button"); + btn.forEach((e) => { (e as HTMLElement).style.backgroundColor = "#2b2e46" }); + (btn[navState] as HTMLElement).style.backgroundColor = "#202232"; + + document.querySelectorAll("section").forEach((e, i) => i >= 2 ? e.style.display = "none" : "") + if (navState === 0) { document.querySelectorAll(".profile1").forEach((e) => { (e as HTMLElement).style.display = "block" }); } + if (navState === 1) { document.querySelectorAll(".profile2").forEach((e) => { (e as HTMLElement).style.display = "block" }); } + } + } + + function UpdateProfile() { + fetch(`https://lp.ardapektezol.com/api/v1/profile`, { + method: 'POST', + headers: { Authorization: "" } + }).then(r => r.json()) + .then(d => d.success ? window.alert("profile updated") : window.alert(`Error: ${d.message}`)) + } + + const _fetch_user = async () => { + const userData = await API.get_user(location.pathname.split("/")[2]); + setUser(userData); + }; + + React.useEffect(() => { + fetch("https://lp.ardapektezol.com/api/v1/games") + .then(r => r.json()) + .then(d => { + setGameData(d.data) + setGame("0") + }) + + }, [location]); + + React.useEffect(() => { + if (user) { + if (game && game !== "0") { + fetch(`https://lp.ardapektezol.com/api/v1/games/${game}`) + .then(r => r.json()) + .then(d => { + setChapterData(d.data) + setChapter("0"); + // (document.querySelector('#select-chapter') as HTMLInputElement).value = "0" + }) + + } else if (game && game === "0") { + setPageMax(Math.ceil(user.records.length / 20)) + setPageNumber(1) + } + } + }, [user, game, location]); + + React.useEffect(() => { + _fetch_user(); + }, []); + + React.useEffect(() => { + if (game !== "0") { + if (chapter === "0") { + fetch(`https://lp.ardapektezol.com/api/v1/games/${game}/maps`) + .then(r => r.json()) + .then(d => { + setMaps(d.data.maps); + setPageMax(Math.ceil(d.data.maps.length / 20)) + setPageNumber(1) + }) + } else { + fetch(`https://lp.ardapektezol.com/api/v1/chapters/${chapter}`) + .then(r => r.json()) + .then(d => { + setMaps(d.data.maps); + setPageMax(Math.ceil(d.data.maps.length / 20)) + setPageNumber(1) + }) + + } + } + }, [game, chapter, chapterData]) + + if (!user) { + return ( + <> + ); + }; + + return ( +
+
+ + {user.profile + ? ( +
UpdateProfile()}> + profile-image + Refresh +
+ ) : ( +
+ profile-image +
+ )} + +
+
+
{user.user_name}
+
+ {user.country_code === "XX" ? "" : {user.country_code}} +
+
+ {user.titles.map(e => ( + + {e.name} + + ))} +
+
+
+ {user.links.steam === "-" ? "" : Steam} + {user.links.twitch === "-" ? "" : Twitch} + {user.links.youtube === "-" ? "" : Youtube} + {user.links.p2sr === "-" ? "" : P2SR} +
+ +
+
+
+ Overall + {user.rankings.overall.rank === 0 ? "N/A " : "#" + user.rankings.overall.rank + " "} + ({user.rankings.overall.completion_count}/{user.rankings.overall.completion_total}) + +
+
+ Singleplayer + {user.rankings.singleplayer.rank === 0 ? "N/A " : "#" + user.rankings.singleplayer.rank + " "} + ({user.rankings.singleplayer.completion_count}/{user.rankings.singleplayer.completion_total}) + +
+
+ Cooperative + {user.rankings.cooperative.rank === 0 ? "N/A " : "#" + user.rankings.cooperative.rank + " "} + ({user.rankings.cooperative.completion_count}/{user.rankings.cooperative.completion_total}) + +
+
+
+ + +
+ + +
+ + + + + +
+
+ {gameData === null ? : + + + } + + {game === "0" ? + + : chapterData === null ? : + + + } +
+
+ Map Name + Portals + WRΔ + Time + + Rank + Date +
+
+ + {pageNumber}/{pageMax} + +
+
+
+
+
+ + {game === "0" + ? ( + + user.records.sort((a, b) => a.map_id - b.map_id) + .map((r, index) => ( + + Math.ceil((index + 1) / 20) === pageNumber ? ( + + + {i === 0 && r.scores.length > 1 ? : ""} + + + ))} + + + ) : "" + ))) : maps ? + + maps.filter(e => e.is_disabled === false).sort((a, b) => a.id - b.id) + .map((r, index) => { + if (Math.ceil((index + 1) / 20) === pageNumber) { + let record = user.records.find((e) => e.map_id === r.id); + return record === undefined ? ( + + ) : ( + + + {i === 0 && record!.scores.length > 1 ? : ""} + + + ))} + + + ) + } else { return null } + }) : (<>{console.warn(maps)})} +
+
+
+ ); +}; + +export default User; -- cgit v1.2.3