diff options
| author | Arda Serdar Pektezol <1669855+pektezol@users.noreply.github.com> | 2024-10-09 16:34:12 +0300 |
|---|---|---|
| committer | Arda Serdar Pektezol <1669855+pektezol@users.noreply.github.com> | 2024-10-09 16:34:12 +0300 |
| commit | a7c282ca348c1e8e60559e5c064caee28ba11eec (patch) | |
| tree | 43bd7bdb2bbc80b92b96a14b36c33f0b7df622c9 /frontend/src/pages | |
| parent | Rankings page (diff) | |
| download | lphub-a7c282ca348c1e8e60559e5c064caee28ba11eec.tar.gz lphub-a7c282ca348c1e8e60559e5c064caee28ba11eec.tar.bz2 lphub-a7c282ca348c1e8e60559e5c064caee28ba11eec.zip | |
refactor: so much shit
Diffstat (limited to 'frontend/src/pages')
| -rw-r--r-- | frontend/src/pages/Homepage.tsx | 2 | ||||
| -rw-r--r-- | frontend/src/pages/Maplist.tsx | 5 | ||||
| -rw-r--r-- | frontend/src/pages/Maps.tsx | 14 | ||||
| -rw-r--r-- | frontend/src/pages/Profile.tsx | 131 | ||||
| -rw-r--r-- | frontend/src/pages/Rankings.tsx | 65 | ||||
| -rw-r--r-- | frontend/src/pages/Rules.tsx | 4 | ||||
| -rw-r--r-- | frontend/src/pages/User.tsx | 146 |
7 files changed, 153 insertions, 214 deletions
diff --git a/frontend/src/pages/Homepage.tsx b/frontend/src/pages/Homepage.tsx index 8e5bb4b..8c1cd48 100644 --- a/frontend/src/pages/Homepage.tsx +++ b/frontend/src/pages/Homepage.tsx | |||
| @@ -7,7 +7,7 @@ const Homepage: React.FC = () => { | |||
| 7 | <main> | 7 | <main> |
| 8 | <section> | 8 | <section> |
| 9 | <p/> | 9 | <p/> |
| 10 | <h1><img src={PortalIcon} alt="lphub"/>Welcome to Least Portals Hub!</h1> | 10 | <h1>Welcome to Least Portals Hub!</h1> |
| 11 | <p>At the moment, LPHUB is in beta state. This means that the site has only the core functionalities enabled for providing both collaborative information and competitive leaderboards.</p> | 11 | <p>At the moment, LPHUB is in beta state. This means that the site has only the core functionalities enabled for providing both collaborative information and competitive leaderboards.</p> |
| 12 | <p>The website should feel intuitive to navigate around. For any type of feedback, reach us at LPHUB Discord server.</p> | 12 | <p>The website should feel intuitive to navigate around. For any type of feedback, reach us at LPHUB Discord server.</p> |
| 13 | <p>By using LPHUB, you agree that you have read the 'Leaderboard Rules' and the 'About LPHUB' pages.</p> | 13 | <p>By using LPHUB, you agree that you have read the 'Leaderboard Rules' and the 'About LPHUB' pages.</p> |
diff --git a/frontend/src/pages/Maplist.tsx b/frontend/src/pages/Maplist.tsx index 5d0c852..9526d18 100644 --- a/frontend/src/pages/Maplist.tsx +++ b/frontend/src/pages/Maplist.tsx | |||
| @@ -64,6 +64,7 @@ const Maplist: React.FC = () => { | |||
| 64 | // console.log(foundGame) | 64 | // console.log(foundGame) |
| 65 | if (foundGame) { | 65 | if (foundGame) { |
| 66 | setGame(foundGame); | 66 | setGame(foundGame); |
| 67 | setLoad(false); | ||
| 67 | } | 68 | } |
| 68 | }; | 69 | }; |
| 69 | 70 | ||
| @@ -73,9 +74,9 @@ const Maplist: React.FC = () => { | |||
| 73 | setNumChapters(games_chapters.chapters.length); | 74 | setNumChapters(games_chapters.chapters.length); |
| 74 | } | 75 | } |
| 75 | 76 | ||
| 77 | setLoad(true); | ||
| 76 | _fetch_game(); | 78 | _fetch_game(); |
| 77 | _fetch_game_chapters(); | 79 | _fetch_game_chapters(); |
| 78 | setLoad(true); | ||
| 79 | }, []); | 80 | }, []); |
| 80 | 81 | ||
| 81 | useEffect(() => { | 82 | useEffect(() => { |
| @@ -96,7 +97,7 @@ const Maplist: React.FC = () => { | |||
| 96 | </button> | 97 | </button> |
| 97 | </Link> | 98 | </Link> |
| 98 | </section> | 99 | </section> |
| 99 | {!load ? ( | 100 | {load ? ( |
| 100 | <div></div> | 101 | <div></div> |
| 101 | ) : ( | 102 | ) : ( |
| 102 | <section> | 103 | <section> |
diff --git a/frontend/src/pages/Maps.tsx b/frontend/src/pages/Maps.tsx index 62fc3cc..13ee4d9 100644 --- a/frontend/src/pages/Maps.tsx +++ b/frontend/src/pages/Maps.tsx | |||
| @@ -7,18 +7,16 @@ import Leaderboards from '../components/Leaderboards'; | |||
| 7 | import Discussions from '../components/Discussions'; | 7 | import Discussions from '../components/Discussions'; |
| 8 | import ModMenu from '../components/ModMenu'; | 8 | import ModMenu from '../components/ModMenu'; |
| 9 | import { MapDiscussions, MapLeaderboard, MapSummary } from '../types/Map'; | 9 | import { MapDiscussions, MapLeaderboard, MapSummary } from '../types/Map'; |
| 10 | import { UserProfile } from '../types/Profile'; | ||
| 11 | import { API } from '../api/Api'; | 10 | import { API } from '../api/Api'; |
| 12 | import "../css/Maps.css"; | 11 | import "../css/Maps.css"; |
| 13 | import Loading from '../components/Loading'; | 12 | import Loading from '../components/Loading'; |
| 14 | 13 | ||
| 15 | interface MapProps { | 14 | interface MapProps { |
| 16 | profile?: UserProfile; | 15 | token?: string; |
| 17 | isModerator: boolean; | 16 | isModerator: boolean; |
| 18 | onUploadRun: (mapID: number) => void; | ||
| 19 | }; | 17 | }; |
| 20 | 18 | ||
| 21 | const Maps: React.FC<MapProps> = ({ profile, isModerator, onUploadRun }) => { | 19 | const Maps: React.FC<MapProps> = ({ token, isModerator }) => { |
| 22 | 20 | ||
| 23 | const [selectedRun, setSelectedRun] = React.useState<number>(0); | 21 | const [selectedRun, setSelectedRun] = React.useState<number>(0); |
| 24 | 22 | ||
| @@ -39,7 +37,8 @@ const Maps: React.FC<MapProps> = ({ profile, isModerator, onUploadRun }) => { | |||
| 39 | 37 | ||
| 40 | const _fetch_map_leaderboards = async () => { | 38 | const _fetch_map_leaderboards = async () => { |
| 41 | const mapLeaderboards = await API.get_map_leaderboard(mapID); | 39 | const mapLeaderboards = await API.get_map_leaderboard(mapID); |
| 42 | console.log(mapLeaderboards?.records[0]); | 40 | console.log("lbs:") |
| 41 | console.log(mapLeaderboards); | ||
| 43 | setMapLeaderboardData(mapLeaderboards); | 42 | setMapLeaderboardData(mapLeaderboards); |
| 44 | }; | 43 | }; |
| 45 | 44 | ||
| @@ -77,7 +76,7 @@ const Maps: React.FC<MapProps> = ({ profile, isModerator, onUploadRun }) => { | |||
| 77 | 76 | ||
| 78 | return ( | 77 | return ( |
| 79 | <> | 78 | <> |
| 80 | {isModerator && <ModMenu data={mapSummaryData} selectedRun={selectedRun} mapID={mapID} />} | 79 | {isModerator && <ModMenu token={token} data={mapSummaryData} selectedRun={selectedRun} mapID={mapID} />} |
| 81 | 80 | ||
| 82 | <div id='background-image'> | 81 | <div id='background-image'> |
| 83 | <img src={mapSummaryData.map.image} alt="" /> | 82 | <img src={mapSummaryData.map.image} alt="" /> |
| @@ -88,7 +87,6 @@ const Maps: React.FC<MapProps> = ({ profile, isModerator, onUploadRun }) => { | |||
| 88 | <Link to="/games"><button className='nav-button' style={{ borderRadius: "20px 0px 0px 20px" }}><i className='triangle'></i><span>Games List</span></button></Link> | 87 | <Link to="/games"><button className='nav-button' style={{ borderRadius: "20px 0px 0px 20px" }}><i className='triangle'></i><span>Games List</span></button></Link> |
| 89 | <Link to={`/games/${!mapSummaryData.map.is_coop ? "1" : "2"}?chapter=${mapSummaryData.map.chapter_name.split(" ")[1]}`}><button className='nav-button' style={{ borderRadius: "0px 20px 20px 0px", marginLeft: "2px" }}><i className='triangle'></i><span>{mapSummaryData.map.chapter_name}</span></button></Link> | 88 | <Link to={`/games/${!mapSummaryData.map.is_coop ? "1" : "2"}?chapter=${mapSummaryData.map.chapter_name.split(" ")[1]}`}><button className='nav-button' style={{ borderRadius: "0px 20px 20px 0px", marginLeft: "2px" }}><i className='triangle'></i><span>{mapSummaryData.map.chapter_name}</span></button></Link> |
| 90 | <br /><span><b>{mapSummaryData.map.map_name}</b></span> | 89 | <br /><span><b>{mapSummaryData.map.map_name}</b></span> |
| 91 | {profile && <button onClick={() => onUploadRun(mapSummaryData.map.id)}>Submit a Run</button>} | ||
| 92 | </div> | 90 | </div> |
| 93 | </section> | 91 | </section> |
| 94 | 92 | ||
| @@ -100,7 +98,7 @@ const Maps: React.FC<MapProps> = ({ profile, isModerator, onUploadRun }) => { | |||
| 100 | 98 | ||
| 101 | {navState === 0 && <Summary selectedRun={selectedRun} setSelectedRun={setSelectedRun} data={mapSummaryData} />} | 99 | {navState === 0 && <Summary selectedRun={selectedRun} setSelectedRun={setSelectedRun} data={mapSummaryData} />} |
| 102 | {navState === 1 && <Leaderboards data={mapLeaderboardData} />} | 100 | {navState === 1 && <Leaderboards data={mapLeaderboardData} />} |
| 103 | {navState === 2 && <Discussions data={mapDiscussionsData} isModerator={isModerator} mapID={mapID} onRefresh={() => _fetch_map_discussions()} />} | 101 | {navState === 2 && <Discussions data={mapDiscussionsData} token={token} isModerator={isModerator} mapID={mapID} onRefresh={() => _fetch_map_discussions()} />} |
| 104 | </main> | 102 | </main> |
| 105 | </> | 103 | </> |
| 106 | ); | 104 | ); |
diff --git a/frontend/src/pages/Profile.tsx b/frontend/src/pages/Profile.tsx index 8eec23c..e20d930 100644 --- a/frontend/src/pages/Profile.tsx +++ b/frontend/src/pages/Profile.tsx | |||
| @@ -1,5 +1,5 @@ | |||
| 1 | import React from 'react'; | 1 | import React from 'react'; |
| 2 | import { useLocation, useNavigate } from 'react-router-dom'; | 2 | import { Link, useLocation, useNavigate } from 'react-router-dom'; |
| 3 | 3 | ||
| 4 | import { SteamIcon, TwitchIcon, YouTubeIcon, PortalIcon, FlagIcon, StatisticsIcon, SortIcon, ThreedotIcon, DownloadIcon, HistoryIcon } from '../images/Images'; | 4 | import { SteamIcon, TwitchIcon, YouTubeIcon, PortalIcon, FlagIcon, StatisticsIcon, SortIcon, ThreedotIcon, DownloadIcon, HistoryIcon } from '../images/Images'; |
| 5 | import { UserProfile } from '../types/Profile'; | 5 | import { UserProfile } from '../types/Profile'; |
| @@ -7,104 +7,74 @@ import { Game, GameChapters } from '../types/Game'; | |||
| 7 | import { Map } from '../types/Map'; | 7 | import { Map } from '../types/Map'; |
| 8 | import { ticks_to_time } from '../utils/Time'; | 8 | import { ticks_to_time } from '../utils/Time'; |
| 9 | import "../css/Profile.css"; | 9 | import "../css/Profile.css"; |
| 10 | import { API } from '../api/Api'; | ||
| 10 | 11 | ||
| 11 | interface ProfileProps { | 12 | interface ProfileProps { |
| 12 | profile?: UserProfile; | 13 | profile?: UserProfile; |
| 14 | token?: string; | ||
| 15 | gameData: Game[]; | ||
| 13 | } | 16 | } |
| 14 | 17 | ||
| 15 | const Profile: React.FC<ProfileProps> = ({ profile }) => { | 18 | const Profile: React.FC<ProfileProps> = ({ profile, token, gameData }) => { |
| 16 | |||
| 17 | |||
| 18 | const location = useLocation(); | ||
| 19 | const navigate = useNavigate(); | ||
| 20 | |||
| 21 | React.useEffect(() => { | ||
| 22 | if (!profile) { | ||
| 23 | navigate("/"); | ||
| 24 | }; | ||
| 25 | }, [profile]); | ||
| 26 | 19 | ||
| 27 | const [navState, setNavState] = React.useState(0); | 20 | const [navState, setNavState] = React.useState(0); |
| 28 | const [pageNumber, setPageNumber] = React.useState(1); | 21 | const [pageNumber, setPageNumber] = React.useState(1); |
| 29 | const [pageMax, setPageMax] = React.useState(0); | 22 | const [pageMax, setPageMax] = React.useState(0); |
| 30 | 23 | ||
| 31 | const [game, setGame] = React.useState("0") | 24 | const [game, setGame] = React.useState("0") |
| 32 | const [gameData, setGameData] = React.useState<Game[]>([]); | ||
| 33 | const [chapter, setChapter] = React.useState("0") | 25 | const [chapter, setChapter] = React.useState("0") |
| 34 | const [chapterData, setChapterData] = React.useState<GameChapters | null>(null); | 26 | const [chapterData, setChapterData] = React.useState<GameChapters | null>(null); |
| 35 | const [maps, setMaps] = React.useState<Map[]>([]); | 27 | const [maps, setMaps] = React.useState<Map[]>([]); |
| 36 | 28 | ||
| 37 | function NavClick() { | 29 | const navigate = useNavigate(); |
| 38 | if (profile) { | ||
| 39 | const btn = document.querySelectorAll("#section2 button"); | ||
| 40 | btn.forEach((e) => { (e as HTMLElement).style.backgroundColor = "#2b2e46" }); | ||
| 41 | (btn[navState] as HTMLElement).style.backgroundColor = "#202232"; | ||
| 42 | 30 | ||
| 43 | document.querySelectorAll("section").forEach((e, i) => i >= 2 ? e.style.display = "none" : "") | 31 | const _update_profile = () => { |
| 44 | if (navState === 0) { document.querySelectorAll(".profile1").forEach((e) => { (e as HTMLElement).style.display = "block" }); } | 32 | if (token) { |
| 45 | if (navState === 1) { document.querySelectorAll(".profile2").forEach((e) => { (e as HTMLElement).style.display = "block" }); } | 33 | API.post_profile(token).then(() => navigate(0)); |
| 46 | } | 34 | } |
| 47 | } | 35 | }; |
| 48 | 36 | ||
| 49 | function UpdateProfile() { | 37 | const _get_game_chapters = async () => { |
| 50 | fetch(`https://lp.ardapektezol.com/api/v1/profile`, { | 38 | if (game && game !== "0") { |
| 51 | method: 'POST', | 39 | const gameChapters = await API.get_games_chapters(game); |
| 52 | headers: { Authorization: "" } | 40 | setChapterData(gameChapters); |
| 53 | }).then(r => r.json()) | 41 | } else if (game && game === "0") { |
| 54 | .then(d => d.success ? window.alert("profile updated") : window.alert(`Error: ${d.message}`)) | 42 | setPageMax(Math.ceil(profile!.records.length / 20)); |
| 55 | } | 43 | setPageNumber(1); |
| 44 | } | ||
| 45 | }; | ||
| 56 | 46 | ||
| 57 | React.useEffect(() => { | 47 | const _get_game_maps = async () => { |
| 58 | if (profile) { | 48 | if (chapter === "0") { |
| 59 | fetch("https://lp.ardapektezol.com/api/v1/games") | 49 | const gameMaps = await API.get_game_maps(game); |
| 60 | .then(r => r.json()) | 50 | setMaps(gameMaps); |
| 61 | .then(d => { | 51 | setPageMax(Math.ceil(gameMaps.length / 20)); |
| 62 | setGameData(d.data) | 52 | setPageNumber(1); |
| 63 | setGame("0") | 53 | } else { |
| 64 | }) | 54 | const gameChapters = await API.get_chapters(chapter); |
| 55 | setMaps(gameChapters.maps); | ||
| 56 | setPageMax(Math.ceil(gameChapters.maps.length / 20)); | ||
| 57 | setPageNumber(1); | ||
| 65 | } | 58 | } |
| 66 | }, [profile, location]); | 59 | }; |
| 60 | |||
| 61 | React.useEffect(() => { | ||
| 62 | if (!profile) { | ||
| 63 | navigate("/"); | ||
| 64 | }; | ||
| 65 | }, [profile]); | ||
| 67 | 66 | ||
| 68 | React.useEffect(() => { | 67 | React.useEffect(() => { |
| 69 | if (profile) { | 68 | if (profile) { |
| 70 | if (game && game !== "0") { | 69 | _get_game_chapters(); |
| 71 | fetch(`https://lp.ardapektezol.com/api/v1/games/${game}`) | ||
| 72 | .then(r => r.json()) | ||
| 73 | .then(d => { | ||
| 74 | setChapterData(d.data) | ||
| 75 | setChapter("0"); | ||
| 76 | // (document.querySelector('#select-chapter') as HTMLInputElement).value = "0" | ||
| 77 | }) | ||
| 78 | |||
| 79 | } else if (game && game === "0") { | ||
| 80 | setPageMax(Math.ceil(profile.records.length / 20)) | ||
| 81 | setPageNumber(1) | ||
| 82 | } | ||
| 83 | } | 70 | } |
| 84 | }, [profile, game, location]); | 71 | }, [profile, game]); |
| 85 | 72 | ||
| 86 | React.useEffect(() => { | 73 | React.useEffect(() => { |
| 87 | if (game !== "0") { | 74 | if (profile && game !== "0") { |
| 88 | if (chapter === "0") { | 75 | _get_game_maps(); |
| 89 | fetch(`https://lp.ardapektezol.com/api/v1/games/${game}/maps`) | ||
| 90 | .then(r => r.json()) | ||
| 91 | .then(d => { | ||
| 92 | setMaps(d.data.maps); | ||
| 93 | setPageMax(Math.ceil(d.data.maps.length / 20)) | ||
| 94 | setPageNumber(1) | ||
| 95 | }) | ||
| 96 | } else { | ||
| 97 | fetch(`https://lp.ardapektezol.com/api/v1/chapters/${chapter}`) | ||
| 98 | .then(r => r.json()) | ||
| 99 | .then(d => { | ||
| 100 | setMaps(d.data.maps); | ||
| 101 | setPageMax(Math.ceil(d.data.maps.length / 20)) | ||
| 102 | setPageNumber(1) | ||
| 103 | }) | ||
| 104 | |||
| 105 | } | ||
| 106 | } | 76 | } |
| 107 | }, [game, chapter, chapterData]) | 77 | }, [profile, game, chapter, chapterData]) |
| 108 | 78 | ||
| 109 | if (!profile) { | 79 | if (!profile) { |
| 110 | return ( | 80 | return ( |
| @@ -118,7 +88,7 @@ const Profile: React.FC<ProfileProps> = ({ profile }) => { | |||
| 118 | 88 | ||
| 119 | {profile.profile | 89 | {profile.profile |
| 120 | ? ( | 90 | ? ( |
| 121 | <div id='profile-image' onClick={() => UpdateProfile()}> | 91 | <div id='profile-image' onClick={_update_profile}> |
| 122 | <img src={profile.avatar_link} alt="profile-image"></img> | 92 | <img src={profile.avatar_link} alt="profile-image"></img> |
| 123 | <span>Refresh</span> | 93 | <span>Refresh</span> |
| 124 | </div> | 94 | </div> |
| @@ -187,7 +157,14 @@ const Profile: React.FC<ProfileProps> = ({ profile }) => { | |||
| 187 | {gameData === null ? <select>error</select> : | 157 | {gameData === null ? <select>error</select> : |
| 188 | 158 | ||
| 189 | <select id='select-game' | 159 | <select id='select-game' |
| 190 | onChange={() => setGame((document.querySelector('#select-game') as HTMLInputElement).value)}> | 160 | onChange={() => { |
| 161 | setGame((document.querySelector('#select-game') as HTMLInputElement).value); | ||
| 162 | setChapter("0"); | ||
| 163 | const chapterSelect = document.querySelector('#select-chapter') as HTMLSelectElement; | ||
| 164 | if (chapterSelect) { | ||
| 165 | chapterSelect.value = "0"; | ||
| 166 | } | ||
| 167 | }}> | ||
| 191 | <option value={0} key={0}>All Scores</option> | 168 | <option value={0} key={0}>All Scores</option> |
| 192 | {gameData.map((e, i) => ( | 169 | {gameData.map((e, i) => ( |
| 193 | <option value={e.id} key={i + 1}>{e.name}</option> | 170 | <option value={e.id} key={i + 1}>{e.name}</option> |
| @@ -240,7 +217,7 @@ const Profile: React.FC<ProfileProps> = ({ profile }) => { | |||
| 240 | {r.scores.map((e, i) => (<> | 217 | {r.scores.map((e, i) => (<> |
| 241 | {i !== 0 ? <hr style={{ gridColumn: "1 / span 8" }} /> : ""} | 218 | {i !== 0 ? <hr style={{ gridColumn: "1 / span 8" }} /> : ""} |
| 242 | 219 | ||
| 243 | <span>{r.map_name}</span> | 220 | <Link to={`/maps/${r.map_id}`}><span>{r.map_name}</span></Link> |
| 244 | 221 | ||
| 245 | <span style={{ display: "grid" }}>{e.score_count}</span> | 222 | <span style={{ display: "grid" }}>{e.score_count}</span> |
| 246 | 223 | ||
| @@ -252,7 +229,7 @@ const Profile: React.FC<ProfileProps> = ({ profile }) => { | |||
| 252 | <span style={{ flexDirection: "row-reverse" }}> | 229 | <span style={{ flexDirection: "row-reverse" }}> |
| 253 | 230 | ||
| 254 | <button onClick={() => { window.alert(`Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button> | 231 | <button onClick={() => { window.alert(`Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button> |
| 255 | <button onClick={() => window.location.href = `https://lp.ardapektezol.com/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button> | 232 | <button onClick={() => window.location.href = `/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button> |
| 256 | {i === 0 && r.scores.length > 1 ? <button onClick={() => { | 233 | {i === 0 && r.scores.length > 1 ? <button onClick={() => { |
| 257 | (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" || | 234 | (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" || |
| 258 | (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "" ? | 235 | (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "" ? |
| @@ -297,7 +274,7 @@ const Profile: React.FC<ProfileProps> = ({ profile }) => { | |||
| 297 | <span style={{ flexDirection: "row-reverse" }}> | 274 | <span style={{ flexDirection: "row-reverse" }}> |
| 298 | 275 | ||
| 299 | <button onClick={() => { window.alert(`Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button> | 276 | <button onClick={() => { window.alert(`Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button> |
| 300 | <button onClick={() => window.location.href = `https://lp.ardapektezol.com/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button> | 277 | <button onClick={() => window.location.href = `/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button> |
| 301 | {i === 0 && record!.scores.length > 1 ? <button onClick={() => { | 278 | {i === 0 && record!.scores.length > 1 ? <button onClick={() => { |
| 302 | (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" || | 279 | (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" || |
| 303 | (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "" ? | 280 | (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "" ? |
diff --git a/frontend/src/pages/Rankings.tsx b/frontend/src/pages/Rankings.tsx index 6dbf3d3..9280b02 100644 --- a/frontend/src/pages/Rankings.tsx +++ b/frontend/src/pages/Rankings.tsx | |||
| @@ -22,14 +22,8 @@ const Rankings: React.FC = () => { | |||
| 22 | const [currentLeaderboardType, setCurrentLeaderboardType] = React.useState<RankingCategories>(RankingCategories.rankings_singleplayer); | 22 | const [currentLeaderboardType, setCurrentLeaderboardType] = React.useState<RankingCategories>(RankingCategories.rankings_singleplayer); |
| 23 | const [load, setLoad] = React.useState<boolean>(false); | 23 | const [load, setLoad] = React.useState<boolean>(false); |
| 24 | 24 | ||
| 25 | interface ResponseSTUPID { | ||
| 26 | success: boolean; | ||
| 27 | message: string; | ||
| 28 | data: SteamRanking; | ||
| 29 | } | ||
| 30 | |||
| 31 | const _fetch_rankings = async () => { | 25 | const _fetch_rankings = async () => { |
| 32 | const rankings = await API.get_rankings(); | 26 | const rankings = await API.get_official_rankings(); |
| 33 | setLeaderboardData(rankings); | 27 | setLeaderboardData(rankings); |
| 34 | if (currentLeaderboardType == RankingCategories.rankings_singleplayer) { | 28 | if (currentLeaderboardType == RankingCategories.rankings_singleplayer) { |
| 35 | setCurrentLeaderboard(rankings.rankings_singleplayer) | 29 | setCurrentLeaderboard(rankings.rankings_singleplayer) |
| @@ -43,20 +37,15 @@ const Rankings: React.FC = () => { | |||
| 43 | 37 | ||
| 44 | const __dev_fetch_unofficial_rankings = async () => { | 38 | const __dev_fetch_unofficial_rankings = async () => { |
| 45 | try { | 39 | try { |
| 46 | const response = await fetch("/response.json"); | 40 | const rankings = await API.get_unofficial_rankings(); |
| 47 | const result: ResponseSTUPID = await response.json(); | 41 | setLeaderboardData(rankings); |
| 48 | 42 | if (currentLeaderboardType == RankingCategories.rankings_singleplayer) { | |
| 49 | if (result.success) { | 43 | // console.log(_sort_rankings_steam(unofficialRanking.rankings_singleplayer)) |
| 50 | const unofficialRanking: SteamRanking = result.data; | 44 | setCurrentLeaderboard(rankings.rankings_singleplayer) |
| 51 | setLeaderboardData(unofficialRanking); | 45 | } else if (currentLeaderboardType == RankingCategories.rankings_multiplayer) { |
| 52 | if (currentLeaderboardType == RankingCategories.rankings_singleplayer) { | 46 | setCurrentLeaderboard(rankings.rankings_multiplayer) |
| 53 | // console.log(_sort_rankings_steam(unofficialRanking.rankings_singleplayer)) | 47 | } else { |
| 54 | setCurrentLeaderboard(unofficialRanking.rankings_singleplayer) | 48 | setCurrentLeaderboard(rankings.rankings_overall) |
| 55 | } else if (currentLeaderboardType == RankingCategories.rankings_multiplayer) { | ||
| 56 | setCurrentLeaderboard(unofficialRanking.rankings_multiplayer) | ||
| 57 | } else { | ||
| 58 | setCurrentLeaderboard(unofficialRanking.rankings_overall) | ||
| 59 | } | ||
| 60 | } | 49 | } |
| 61 | } catch (e) { | 50 | } catch (e) { |
| 62 | console.log(e) | 51 | console.log(e) |
| @@ -79,7 +68,7 @@ const Rankings: React.FC = () => { | |||
| 79 | if (leaderboard_type == LeaderboardTypes.official) { | 68 | if (leaderboard_type == LeaderboardTypes.official) { |
| 80 | _fetch_rankings(); | 69 | _fetch_rankings(); |
| 81 | } else { | 70 | } else { |
| 82 | 71 | ||
| 83 | } | 72 | } |
| 84 | } | 73 | } |
| 85 | 74 | ||
| @@ -117,23 +106,23 @@ const Rankings: React.FC = () => { | |||
| 117 | </section> | 106 | </section> |
| 118 | 107 | ||
| 119 | {load ? | 108 | {load ? |
| 120 | <section className="rankings-leaderboard"> | 109 | <section className="rankings-leaderboard"> |
| 121 | <div className="ranks-container"> | 110 | <div className="ranks-container"> |
| 122 | <div className="leaderboard-entry header"> | 111 | <div className="leaderboard-entry header"> |
| 123 | <span>Rank</span> | 112 | <span>Rank</span> |
| 124 | <span>Player</span> | 113 | <span>Player</span> |
| 125 | <span>Portals</span> | 114 | <span>Portals</span> |
| 115 | </div> | ||
| 116 | |||
| 117 | <div className="splitter"></div> | ||
| 118 | |||
| 119 | {currentLeaderboard?.map((curRankingData, i) => { | ||
| 120 | return <RankingEntry currentLeaderboardType={currentLeaderboardType} curRankingData={curRankingData} key={i}></RankingEntry> | ||
| 121 | }) | ||
| 122 | } | ||
| 126 | </div> | 123 | </div> |
| 127 | 124 | </section> | |
| 128 | <div className="splitter"></div> | 125 | : null} |
| 129 | |||
| 130 | {currentLeaderboard?.map((curRankingData, i) => { | ||
| 131 | return <RankingEntry currentLeaderboardType={currentLeaderboardType} curRankingData={curRankingData} key={i}></RankingEntry> | ||
| 132 | }) | ||
| 133 | } | ||
| 134 | </div> | ||
| 135 | </section> | ||
| 136 | : null} | ||
| 137 | </main> | 126 | </main> |
| 138 | ) | 127 | ) |
| 139 | } | 128 | } |
diff --git a/frontend/src/pages/Rules.tsx b/frontend/src/pages/Rules.tsx index 516b73c..99fff1f 100644 --- a/frontend/src/pages/Rules.tsx +++ b/frontend/src/pages/Rules.tsx | |||
| @@ -11,7 +11,7 @@ const Rules: React.FC = () => { | |||
| 11 | const fetchRules = async () => { | 11 | const fetchRules = async () => { |
| 12 | try { | 12 | try { |
| 13 | const response = await fetch( | 13 | const response = await fetch( |
| 14 | 'https://raw.githubusercontent.com/pektezol/leastportalshub/main/README.md' | 14 | 'https://raw.githubusercontent.com/pektezol/leastportalshub/typescript/RULES.md' |
| 15 | ); | 15 | ); |
| 16 | if (!response.ok) { | 16 | if (!response.ok) { |
| 17 | throw new Error('Failed to fetch README'); | 17 | throw new Error('Failed to fetch README'); |
| @@ -21,7 +21,7 @@ const Rules: React.FC = () => { | |||
| 21 | } catch (error) { | 21 | } catch (error) { |
| 22 | console.error('Error fetching Rules:', error); | 22 | console.error('Error fetching Rules:', error); |
| 23 | } | 23 | } |
| 24 | setRulesText(rulesText) | 24 | // setRulesText(rulesText) |
| 25 | }; | 25 | }; |
| 26 | fetchRules(); | 26 | fetchRules(); |
| 27 | }, []); | 27 | }, []); |
diff --git a/frontend/src/pages/User.tsx b/frontend/src/pages/User.tsx index 1605ada..e3781a3 100644 --- a/frontend/src/pages/User.tsx +++ b/frontend/src/pages/User.tsx | |||
| @@ -1,5 +1,5 @@ | |||
| 1 | import React from 'react'; | 1 | import React from 'react'; |
| 2 | import { useLocation } from 'react-router-dom'; | 2 | import { Link, useLocation, useNavigate } from 'react-router-dom'; |
| 3 | 3 | ||
| 4 | import { SteamIcon, TwitchIcon, YouTubeIcon, PortalIcon, FlagIcon, StatisticsIcon, SortIcon, ThreedotIcon, DownloadIcon, HistoryIcon } from '../images/Images'; | 4 | import { SteamIcon, TwitchIcon, YouTubeIcon, PortalIcon, FlagIcon, StatisticsIcon, SortIcon, ThreedotIcon, DownloadIcon, HistoryIcon } from '../images/Images'; |
| 5 | import { UserProfile } from '../types/Profile'; | 5 | import { UserProfile } from '../types/Profile'; |
| @@ -9,8 +9,13 @@ import { API } from '../api/Api'; | |||
| 9 | import { ticks_to_time } from '../utils/Time'; | 9 | import { ticks_to_time } from '../utils/Time'; |
| 10 | import "../css/Profile.css"; | 10 | import "../css/Profile.css"; |
| 11 | 11 | ||
| 12 | const User: React.FC = () => { | 12 | interface UserProps { |
| 13 | const location = useLocation(); | 13 | profile?: UserProfile; |
| 14 | token?: string; | ||
| 15 | gameData: Game[]; | ||
| 16 | } | ||
| 17 | |||
| 18 | const User: React.FC<UserProps> = ({ token, profile, gameData }) => { | ||
| 14 | 19 | ||
| 15 | const [user, setUser] = React.useState<UserProfile | undefined>(undefined); | 20 | const [user, setUser] = React.useState<UserProfile | undefined>(undefined); |
| 16 | 21 | ||
| @@ -18,91 +23,63 @@ const User: React.FC = () => { | |||
| 18 | const [pageNumber, setPageNumber] = React.useState(1); | 23 | const [pageNumber, setPageNumber] = React.useState(1); |
| 19 | const [pageMax, setPageMax] = React.useState(0); | 24 | const [pageMax, setPageMax] = React.useState(0); |
| 20 | 25 | ||
| 21 | const [game, setGame] = React.useState("0") | 26 | const [game, setGame] = React.useState("0"); |
| 22 | const [gameData, setGameData] = React.useState<Game[]>([]); | 27 | const [chapter, setChapter] = React.useState("0"); |
| 23 | const [chapter, setChapter] = React.useState("0") | ||
| 24 | const [chapterData, setChapterData] = React.useState<GameChapters | null>(null); | 28 | const [chapterData, setChapterData] = React.useState<GameChapters | null>(null); |
| 25 | const [maps, setMaps] = React.useState<Map[]>([]); | 29 | const [maps, setMaps] = React.useState<Map[]>([]); |
| 26 | 30 | ||
| 27 | function NavClick() { | 31 | const location = useLocation(); |
| 28 | if (user) { | 32 | const navigate = useNavigate(); |
| 29 | const btn = document.querySelectorAll("#section2 button"); | ||
| 30 | btn.forEach((e) => { (e as HTMLElement).style.backgroundColor = "#2b2e46" }); | ||
| 31 | (btn[navState] as HTMLElement).style.backgroundColor = "#202232"; | ||
| 32 | 33 | ||
| 33 | document.querySelectorAll("section").forEach((e, i) => i >= 2 ? e.style.display = "none" : "") | 34 | const _fetch_user = async () => { |
| 34 | if (navState === 0) { document.querySelectorAll(".profile1").forEach((e) => { (e as HTMLElement).style.display = "block" }); } | 35 | const userID = location.pathname.split("/")[2]; |
| 35 | if (navState === 1) { document.querySelectorAll(".profile2").forEach((e) => { (e as HTMLElement).style.display = "block" }); } | 36 | if (token && profile && profile.profile && profile.steam_id === userID) { |
| 37 | navigate("/profile"); | ||
| 38 | return; | ||
| 36 | } | 39 | } |
| 37 | } | 40 | const userData = await API.get_user(userID); |
| 41 | setUser(userData); | ||
| 42 | }; | ||
| 38 | 43 | ||
| 39 | function UpdateProfile() { | 44 | const _get_game_chapters = async () => { |
| 40 | fetch(`https://lp.ardapektezol.com/api/v1/profile`, { | 45 | if (game !== "0") { |
| 41 | method: 'POST', | 46 | const gameChapters = await API.get_games_chapters(game); |
| 42 | headers: { Authorization: "" } | 47 | setChapterData(gameChapters); |
| 43 | }).then(r => r.json()) | 48 | } else { |
| 44 | .then(d => d.success ? window.alert("profile updated") : window.alert(`Error: ${d.message}`)) | 49 | setPageMax(Math.ceil(user!.records.length / 20)); |
| 45 | } | 50 | setPageNumber(1); |
| 51 | } | ||
| 52 | }; | ||
| 46 | 53 | ||
| 47 | const _fetch_user = async () => { | 54 | const _get_game_maps = async () => { |
| 48 | const userData = await API.get_user(location.pathname.split("/")[2]); | 55 | if (chapter === "0") { |
| 49 | setUser(userData); | 56 | const gameMaps = await API.get_game_maps(game); |
| 57 | setMaps(gameMaps); | ||
| 58 | setPageMax(Math.ceil(gameMaps.length / 20)); | ||
| 59 | setPageNumber(1); | ||
| 60 | } else { | ||
| 61 | const gameChapters = await API.get_chapters(chapter); | ||
| 62 | setMaps(gameChapters.maps); | ||
| 63 | setPageMax(Math.ceil(gameChapters.maps.length / 20)); | ||
| 64 | setPageNumber(1); | ||
| 65 | } | ||
| 50 | }; | 66 | }; |
| 51 | 67 | ||
| 52 | React.useEffect(() => { | 68 | React.useEffect(() => { |
| 53 | fetch("https://lp.ardapektezol.com/api/v1/games") | 69 | _fetch_user(); |
| 54 | .then(r => r.json()) | ||
| 55 | .then(d => { | ||
| 56 | setGameData(d.data) | ||
| 57 | setGame("0") | ||
| 58 | }) | ||
| 59 | |||
| 60 | }, [location]); | 70 | }, [location]); |
| 61 | 71 | ||
| 62 | React.useEffect(() => { | 72 | React.useEffect(() => { |
| 63 | if (user) { | 73 | if (user) { |
| 64 | if (game && game !== "0") { | 74 | _get_game_chapters(); |
| 65 | fetch(`https://lp.ardapektezol.com/api/v1/games/${game}`) | ||
| 66 | .then(r => r.json()) | ||
| 67 | .then(d => { | ||
| 68 | setChapterData(d.data) | ||
| 69 | setChapter("0"); | ||
| 70 | // (document.querySelector('#select-chapter') as HTMLInputElement).value = "0" | ||
| 71 | }) | ||
| 72 | |||
| 73 | } else if (game && game === "0") { | ||
| 74 | setPageMax(Math.ceil(user.records.length / 20)) | ||
| 75 | setPageNumber(1) | ||
| 76 | } | ||
| 77 | } | 75 | } |
| 78 | }, [user, game, location]); | 76 | }, [user, game, location]); |
| 79 | 77 | ||
| 80 | React.useEffect(() => { | 78 | React.useEffect(() => { |
| 81 | _fetch_user(); | 79 | if (user && game !== "0") { |
| 82 | }, [user]); | 80 | _get_game_maps(); |
| 83 | |||
| 84 | React.useEffect(() => { | ||
| 85 | if (game !== "0") { | ||
| 86 | if (chapter === "0") { | ||
| 87 | fetch(`https://lp.ardapektezol.com/api/v1/games/${game}/maps`) | ||
| 88 | .then(r => r.json()) | ||
| 89 | .then(d => { | ||
| 90 | setMaps(d.data.maps); | ||
| 91 | setPageMax(Math.ceil(d.data.maps.length / 20)) | ||
| 92 | setPageNumber(1) | ||
| 93 | }) | ||
| 94 | } else { | ||
| 95 | fetch(`https://lp.ardapektezol.com/api/v1/chapters/${chapter}`) | ||
| 96 | .then(r => r.json()) | ||
| 97 | .then(d => { | ||
| 98 | setMaps(d.data.maps); | ||
| 99 | setPageMax(Math.ceil(d.data.maps.length / 20)) | ||
| 100 | setPageNumber(1) | ||
| 101 | }) | ||
| 102 | |||
| 103 | } | ||
| 104 | } | 81 | } |
| 105 | }, [game, chapter, chapterData]) | 82 | }, [user, game, chapter, location]) |
| 106 | 83 | ||
| 107 | if (!user) { | 84 | if (!user) { |
| 108 | return ( | 85 | return ( |
| @@ -113,19 +90,9 @@ const User: React.FC = () => { | |||
| 113 | return ( | 90 | return ( |
| 114 | <main> | 91 | <main> |
| 115 | <section id='section1' className='profile'> | 92 | <section id='section1' className='profile'> |
| 116 | 93 | <div> | |
| 117 | {user.profile | 94 | <img src={user.avatar_link} alt="profile-image"></img> |
| 118 | ? ( | 95 | </div> |
| 119 | <div id='profile-image' onClick={() => UpdateProfile()}> | ||
| 120 | <img src={user.avatar_link} alt="profile-image"></img> | ||
| 121 | <span>Refresh</span> | ||
| 122 | </div> | ||
| 123 | ) : ( | ||
| 124 | <div> | ||
| 125 | <img src={user.avatar_link} alt="profile-image"></img> | ||
| 126 | </div> | ||
| 127 | )} | ||
| 128 | |||
| 129 | <div id='profile-top'> | 96 | <div id='profile-top'> |
| 130 | <div> | 97 | <div> |
| 131 | <div>{user.user_name}</div> | 98 | <div>{user.user_name}</div> |
| @@ -185,7 +152,14 @@ const User: React.FC = () => { | |||
| 185 | {gameData === null ? <select>error</select> : | 152 | {gameData === null ? <select>error</select> : |
| 186 | 153 | ||
| 187 | <select id='select-game' | 154 | <select id='select-game' |
| 188 | onChange={() => setGame((document.querySelector('#select-game') as HTMLInputElement).value)}> | 155 | onChange={() => { |
| 156 | setGame((document.querySelector('#select-game') as HTMLInputElement).value); | ||
| 157 | setChapter("0"); | ||
| 158 | const chapterSelect = document.querySelector('#select-chapter') as HTMLSelectElement; | ||
| 159 | if (chapterSelect) { | ||
| 160 | chapterSelect.value = "0"; | ||
| 161 | } | ||
| 162 | }}> | ||
| 189 | <option value={0} key={0}>All Scores</option> | 163 | <option value={0} key={0}>All Scores</option> |
| 190 | {gameData.map((e, i) => ( | 164 | {gameData.map((e, i) => ( |
| 191 | <option value={e.id} key={i + 1}>{e.name}</option> | 165 | <option value={e.id} key={i + 1}>{e.name}</option> |
| @@ -238,7 +212,7 @@ const User: React.FC = () => { | |||
| 238 | {r.scores.map((e, i) => (<> | 212 | {r.scores.map((e, i) => (<> |
| 239 | {i !== 0 ? <hr style={{ gridColumn: "1 / span 8" }} /> : ""} | 213 | {i !== 0 ? <hr style={{ gridColumn: "1 / span 8" }} /> : ""} |
| 240 | 214 | ||
| 241 | <span>{r.map_name}</span> | 215 | <Link to={`/maps/${r.map_id}`}><span>{r.map_name}</span></Link> |
| 242 | 216 | ||
| 243 | <span style={{ display: "grid" }}>{e.score_count}</span> | 217 | <span style={{ display: "grid" }}>{e.score_count}</span> |
| 244 | 218 | ||
| @@ -250,7 +224,7 @@ const User: React.FC = () => { | |||
| 250 | <span style={{ flexDirection: "row-reverse" }}> | 224 | <span style={{ flexDirection: "row-reverse" }}> |
| 251 | 225 | ||
| 252 | <button onClick={() => { window.alert(`Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button> | 226 | <button onClick={() => { window.alert(`Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button> |
| 253 | <button onClick={() => window.location.href = `https://lp.ardapektezol.com/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button> | 227 | <button onClick={() => window.location.href = `/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button> |
| 254 | {i === 0 && r.scores.length > 1 ? <button onClick={() => { | 228 | {i === 0 && r.scores.length > 1 ? <button onClick={() => { |
| 255 | (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" || | 229 | (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" || |
| 256 | (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "" ? | 230 | (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "" ? |
| @@ -295,7 +269,7 @@ const User: React.FC = () => { | |||
| 295 | <span style={{ flexDirection: "row-reverse" }}> | 269 | <span style={{ flexDirection: "row-reverse" }}> |
| 296 | 270 | ||
| 297 | <button onClick={() => { window.alert(`Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button> | 271 | <button onClick={() => { window.alert(`Demo ID: ${e.demo_id}`) }}><img src={ThreedotIcon} alt="demo_id" /></button> |
| 298 | <button onClick={() => window.location.href = `https://lp.ardapektezol.com/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button> | 272 | <button onClick={() => window.location.href = `/api/v1/demos?uuid=${e.demo_id}`}><img src={DownloadIcon} alt="download" /></button> |
| 299 | {i === 0 && record!.scores.length > 1 ? <button onClick={() => { | 273 | {i === 0 && record!.scores.length > 1 ? <button onClick={() => { |
| 300 | (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" || | 274 | (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "44px" || |
| 301 | (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "" ? | 275 | (document.querySelectorAll(".profileboard-record")[index % 20] as HTMLInputElement).style.height === "" ? |