aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--frontend/src/App.tsx2
-rw-r--r--frontend/src/api/Api.tsx8
-rw-r--r--frontend/src/components/RankingEntry.tsx22
-rw-r--r--frontend/src/css/Rankings.css107
-rw-r--r--frontend/src/pages/Rankings.tsx92
-rw-r--r--frontend/src/types/Ranking.tsx13
6 files changed, 244 insertions, 0 deletions
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index fdf1077..d45cd97 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -11,6 +11,7 @@ import Maps from './pages/Maps';
11import User from './pages/User'; 11import User from './pages/User';
12import Homepage from './pages/Homepage'; 12import Homepage from './pages/Homepage';
13import Maplist from './pages/Maplist'; 13import Maplist from './pages/Maplist';
14import Rankings from './pages/Rankings';
14 15
15const App: React.FC = () => { 16const App: React.FC = () => {
16 const [token, setToken] = React.useState<string | undefined>(undefined); 17 const [token, setToken] = React.useState<string | undefined>(undefined);
@@ -33,6 +34,7 @@ const App: React.FC = () => {
33 <Route path="/games" element={<Games />} /> 34 <Route path="/games" element={<Games />} />
34 <Route path="/maps/*" element={<Maps isModerator={isModerator} />} /> 35 <Route path="/maps/*" element={<Maps isModerator={isModerator} />} />
35 <Route path='/games/:id' element={<Maplist></Maplist>}></Route> 36 <Route path='/games/:id' element={<Maplist></Maplist>}></Route>
37 <Route path='/rankings' element={<Rankings></Rankings>}></Route>
36 <Route path="*" element={"404"} /> 38 <Route path="*" element={"404"} />
37 </Routes> 39 </Routes>
38 </> 40 </>
diff --git a/frontend/src/api/Api.tsx b/frontend/src/api/Api.tsx
index 326052f..e62bb22 100644
--- a/frontend/src/api/Api.tsx
+++ b/frontend/src/api/Api.tsx
@@ -6,6 +6,7 @@ import { MapDiscussion, MapDiscussions, MapLeaderboard, MapSummary, Map } from '
6import { MapDiscussionCommentContent, MapDiscussionContent, ModMenuContent } from '../types/Content'; 6import { MapDiscussionCommentContent, MapDiscussionContent, ModMenuContent } from '../types/Content';
7import { Search } from '../types/Search'; 7import { Search } from '../types/Search';
8import { UserProfile } from '../types/Profile'; 8import { UserProfile } from '../types/Profile';
9import { Ranking } from '../types/Ranking';
9 10
10// add new api call function entries here 11// add new api call function entries here
11// example usage: API.get_games(); 12// example usage: API.get_games();
@@ -17,6 +18,7 @@ export const API = {
17 get_chapters: (chapter_id: string) => get_chapters(chapter_id), 18 get_chapters: (chapter_id: string) => get_chapters(chapter_id),
18 get_games_chapters: (game_id: string) => get_games_chapters(game_id), 19 get_games_chapters: (game_id: string) => get_games_chapters(game_id),
19 get_games_maps: (game_id: string) => get_games_maps(game_id), 20 get_games_maps: (game_id: string) => get_games_maps(game_id),
21 get_rankings: () => get_rankings(),
20 get_search: (q: string) => get_search(q), 22 get_search: (q: string) => get_search(q),
21 get_map_summary: (map_id: string) => get_map_summary(map_id), 23 get_map_summary: (map_id: string) => get_map_summary(map_id),
22 get_map_leaderboard: (map_id: string) => get_map_leaderboard(map_id), 24 get_map_leaderboard: (map_id: string) => get_map_leaderboard(map_id),
@@ -74,6 +76,12 @@ const get_games_maps = async (game_id: string): Promise<Map> => {
74 return response.data.data; 76 return response.data.data;
75} 77}
76 78
79// RANKINGS
80const get_rankings = async (): Promise<Ranking> => {
81 const response = await axios.get(url(`rankings`));
82 return response.data.data;
83}
84
77// SEARCH 85// SEARCH
78 86
79const get_search = async (q: string): Promise<Search> => { 87const get_search = async (q: string): Promise<Search> => {
diff --git a/frontend/src/components/RankingEntry.tsx b/frontend/src/components/RankingEntry.tsx
new file mode 100644
index 0000000..b77bb3d
--- /dev/null
+++ b/frontend/src/components/RankingEntry.tsx
@@ -0,0 +1,22 @@
1import React from 'react';
2import { Link } from "react-router-dom";
3import { RankingType } from '../types/Ranking';
4
5interface RankingEntryProps {
6 curRankingData: RankingType;
7};
8
9const RankingEntry: React.FC<RankingEntryProps> = (curRankingData) => {
10 return (
11 <div className='leaderboard-entry'>
12 <span>{curRankingData.curRankingData.placement}</span>
13 <div>
14 <img src={curRankingData.curRankingData.user.avatar_link}></img>
15 <span>{curRankingData.curRankingData.user.user_name}</span>
16 </div>
17 <span>{curRankingData.curRankingData.total_score}</span>
18 </div>
19 )
20}
21
22export default RankingEntry;
diff --git a/frontend/src/css/Rankings.css b/frontend/src/css/Rankings.css
new file mode 100644
index 0000000..8e49ef9
--- /dev/null
+++ b/frontend/src/css/Rankings.css
@@ -0,0 +1,107 @@
1.nav-container {
2 justify-content: center;
3 display: flex;
4}
5
6.nav-container div {
7 display: flex;
8 width: 100%;
9 background-color: #202232;
10 margin-top: 20px;
11 border-radius: 2000px;
12 overflow: hidden;
13 gap: 3px;
14}
15
16.nav-container button {
17 background-color: #2B2E46;
18 color: inherit;
19 border: none;
20 font-family: inherit;
21 cursor: pointer;
22 display: flex;
23 width: 100%;
24 justify-content: center;
25 font-size: 26px;
26 padding: 10px 0px;
27 transition: all 0.1s;
28}
29
30.nav-container button:hover, .nav-container button.selected {
31 background-color: #202232;
32}
33
34.nav-1 div {
35 width: 65%;
36}
37
38.nav-2 div {
39 width: 80%;
40}
41
42.rankings-leaderboard {
43 width: 100%;
44 display: flex;
45 justify-content: center;
46 font-size: 20px;
47 align-items: center;
48 margin-top: 20px;
49}
50
51.ranks-container {
52 display: flex;
53 width: calc(60% - 20px);
54 padding: 8px 8px;
55 background-color: #202232;
56 border-radius: 32px;
57 flex-direction: column;
58 gap: 7px;
59}
60
61.leaderboard-entry {
62 display: grid;
63 grid-template-columns: 20% 40% 40%;
64 text-align: center;
65 align-items: center;
66 width: 100%;
67 background-color: #2B2E46;
68 border-radius: 2000px;
69 padding: 6px 0px;
70}
71
72.leaderboard-entry div:nth-child(2) {
73 text-align: left;
74}
75
76.leaderboard-entry div {
77 display: flex;
78 align-items: center;
79}
80
81.leaderboard-entry div span {
82 margin-left: 5px;
83}
84
85.leaderboard-entry img {
86 height: 34px;
87 border-radius: 2000px;
88}
89
90.leaderboard-entry.header {
91 background-color: rgba(0, 0, 0, 0);
92 font-family: "BarlowSemiCondensed-SemiBold";
93 padding: 2px 0px;
94}
95
96.leaderboard-entry.header span:nth-child(2) {
97 text-align: left;
98}
99
100.ranks-container .splitter {
101 width: calc(100% - 20px);
102 display: flex;
103 height: 0.13em;
104 background-color: #b7b9c6;
105 border-radius: 200px;
106 transform: translateX(10px);
107}
diff --git a/frontend/src/pages/Rankings.tsx b/frontend/src/pages/Rankings.tsx
new file mode 100644
index 0000000..377222f
--- /dev/null
+++ b/frontend/src/pages/Rankings.tsx
@@ -0,0 +1,92 @@
1import React, { useEffect } from "react";
2
3import RankingEntry from "../components/RankingEntry";
4import { Ranking, RankingType } from "../types/Ranking";
5import { API } from "../api/Api";
6
7import "../css/Rankings.css";
8
9const Rankings: React.FC = () => {
10 const [leaderboardData, setLeaderboardData] = React.useState<Ranking>();
11 const [currentLeaderboardCat, setCurrentLeaderboardCat] = React.useState<RankingCategories>();
12 const [currentLeaderboard, setCurrentLeaderboard] = React.useState<RankingType[]>();
13 const [load, setLoad] = React.useState<boolean>(false);
14
15 enum RankingCategories {
16 rankings_overall,
17 rankings_multiplayer,
18 rankings_singleplayer
19 }
20
21 const _fetch_rankings = async () => {
22 const rankings = await API.get_rankings();
23 setLeaderboardData(rankings);
24 setLoad(true);
25 }
26
27 const _set_current_leaderboard = (ranking_cat: RankingCategories) => {
28 if (ranking_cat == RankingCategories.rankings_singleplayer) {
29 setCurrentLeaderboard(leaderboardData!.rankings_singleplayer);
30 } else if (ranking_cat == RankingCategories.rankings_multiplayer) {
31 setCurrentLeaderboard(leaderboardData!.rankings_multiplayer);
32 } else {
33 setCurrentLeaderboard(leaderboardData!.rankings_overall);
34 }
35 }
36
37 useEffect(() => {
38 _fetch_rankings();
39 if (load) {
40 _set_current_leaderboard(RankingCategories.rankings_singleplayer);
41 }
42 }, [load])
43
44 return (
45 <main>
46 <section className="nav-container nav-1">
47 <div>
48 <button className="nav-1-btn">
49 <span>Official (LPHUB)</span>
50 </button>
51 <button className="nav-1-btn">
52 <span>Unofficial (Steam)</span>
53 </button>
54 </div>
55 </section>
56 <section className="nav-container nav-2">
57 <div>
58 <button onClick={() => _set_current_leaderboard(RankingCategories.rankings_singleplayer)} className="nav-2-btn">
59 <span>Singleplayer</span>
60 </button>
61 <button onClick={() => _set_current_leaderboard(RankingCategories.rankings_multiplayer)} className="nav-2-btn">
62 <span>Cooperative</span>
63 </button>
64 <button onClick={() => _set_current_leaderboard(RankingCategories.rankings_overall)} className="nav-2-btn">
65 <span>Overall</span>
66 </button>
67 </div>
68 </section>
69
70 {load ?
71 <section className="rankings-leaderboard">
72 <div className="ranks-container">
73 <div className="leaderboard-entry header">
74 <span>Rank</span>
75 <span>Player</span>
76 <span>Portals</span>
77 </div>
78
79 <div className="splitter"></div>
80
81 {currentLeaderboard?.map((curRankingData, i) => {
82 return <RankingEntry curRankingData={curRankingData} key={i}></RankingEntry>
83 })
84 }
85 </div>
86 </section>
87 : null}
88 </main>
89 )
90}
91
92export default Rankings;
diff --git a/frontend/src/types/Ranking.tsx b/frontend/src/types/Ranking.tsx
new file mode 100644
index 0000000..ad4d8ae
--- /dev/null
+++ b/frontend/src/types/Ranking.tsx
@@ -0,0 +1,13 @@
1import { UserShort } from "./Profile";
2
3export interface RankingType {
4 placement: number;
5 user: UserShort;
6 total_score: number;
7}
8
9export interface Ranking {
10 rankings_overall: RankingType[];
11 rankings_singleplayer: RankingType[];
12 rankings_multiplayer: RankingType[];
13} \ No newline at end of file