Skip to content

Commit 4a286bb

Browse files
authored
Merge pull request #65 from acmucsd/comp-team-page-main
Competitions team page
2 parents 42f5195 + b1188c6 commit 4a286bb

File tree

8 files changed

+323
-0
lines changed

8 files changed

+323
-0
lines changed

process.env

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
REACT_APP_API="http://localhost:9000"
2+
REACT_APP_BOT_PASSWORDS="xcpBv249u1u2u03ZqMa2DPlasd"

src/App.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import nnRanksPage from './pages/Competitions/NNRankPage';
3535
import requestreset from './pages/Auth/RequestReset';
3636
import CompetitionLandingPage from './pages/Competitions/CompetitionLandingPage';
3737
import CompetitionUploadPage from './pages/Competitions/CompetitionUploadPage';
38+
import CompetitionSpecificTeamPage from './pages/Competitions/CompetitionTeamPages/SpecificTeamPage';
39+
import CompetitionAllTeamsPage from './pages/Competitions/CompetitionTeamPages/AllTeamsPage';
3840
import CompetitionLeaderboardPage from './pages/Competitions/CompetitionLeaderboardPage';
3941

4042
import ProjectPage from './pages/ProjectsPage/index'
@@ -100,6 +102,8 @@ function App() {
100102
<Route path="/competitions/:id" exact component={CompetitionLandingPage} />
101103
<Route path="/competitions/:id/leaderboard" exact component={CompetitionLeaderboardPage} />
102104
<Route path="/competitions/:id/upload" exact component={CompetitionUploadPage} />
105+
<Route path="/competitions/:competitionName/teams" exact component={CompetitionAllTeamsPage} />
106+
<Route path="/competitions/:competitionName/teams/:teamName" exact component={CompetitionSpecificTeamPage} />
103107
{/* <Route path="/competitions/nn/upload" exact component={nnUpload} /> */}
104108
<Route
105109
exact

src/actions/DELETE.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { message } from 'antd';
2+
import axios, { AxiosResponse } from 'axios';
3+
import { COOKIE_NAME } from '../configs';
4+
import { getToken } from '../utils/token';
5+
6+
const API =
7+
'http://localhost:9000/v1/teams/TEST';
8+
9+
export const getTeams = async (competitionid: string): Promise<AxiosResponse> => {
10+
return new Promise((resolve, reject) => {
11+
axios
12+
.get(API)
13+
.then((res: AxiosResponse) => {
14+
resolve(res);
15+
})
16+
.catch((error) => {
17+
message.error('teams Failed lol');
18+
console.log(error);
19+
reject(error);
20+
});
21+
});
22+
};
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
@import '../../../../styles/base.less';
2+
3+
.AllTeamsPage {
4+
.noContainer();
5+
@import '../../../../styles/impactful.less';
6+
7+
h3, h4 {
8+
margin: 0;
9+
padding: 0;
10+
}
11+
12+
p, .subheader {
13+
color: @b2;
14+
}
15+
16+
.AllTeamsContent {
17+
padding: 0 2rem;
18+
}
19+
20+
.teamBlock {
21+
margin: 2rem 0;
22+
padding: 1rem;
23+
border: 1.2px solid @gray;
24+
border-radius: 0.6rem;
25+
// background-color: @gray;
26+
}
27+
28+
.teamBlock:hover {
29+
background-color: @gray;
30+
.teamBlockHeader {
31+
background-color: @white;
32+
}
33+
}
34+
35+
.teamBlockSection {
36+
margin: 0.4rem 0;
37+
}
38+
39+
.teamBlockHeader {
40+
margin-right: 0.5rem;
41+
padding: 0.2rem 0.5rem;
42+
background-color: @gray;
43+
border-radius: 0.3rem;
44+
}
45+
46+
.errorMessage {
47+
margin: 2rem;
48+
}
49+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import React, { useEffect, useState, useContext } from 'react';
2+
import DefaultLayout from '../../../../components/layouts/default';
3+
import './index.less';
4+
import { useParams, Link } from 'react-router-dom';
5+
import { getRegisteredState, getTeams } from '../utils';
6+
import UserContext from '../../../../UserContext';
7+
8+
// Block for each team
9+
const Team = (team: any) => {
10+
return (
11+
<div className='teamBlock'>
12+
{/* Note: change to Link to={team.teamName} if the routing doesn't work; idk why this happens*/}
13+
<Link to={'teams/' + team.teamName}>
14+
<h3><span className='subheader'>{team.teamName}</span></h3>
15+
<p className="teamBlockSection"><span className="teamBlockHeader">Best Score</span> <span>{team.bestScore}</span></p>
16+
<p className="teamBlockSection"><span className="teamBlockHeader">Members</span> <span className="teamMembers">{team.teamMembers.join(", ")}</span></p>
17+
<p className="teamBlockSection"><span className="teamBlockHeader">About</span><span>{team.teamDescription}</span></p>
18+
</Link>
19+
</div>
20+
)
21+
}
22+
23+
const CompetitionAllTeamsPage = () => {
24+
25+
const { user } = useContext(UserContext);
26+
const [isRegistered, setIsRegistered] = useState<any>(false);
27+
const [teams, setTeams] = useState<any>([]);
28+
let { competitionName } = useParams<{ competitionName: string }>();
29+
30+
useEffect(() => {
31+
if (user.loggedIn) {
32+
// TODO: it's hardcoded to "testinguser1"; change it to user.username
33+
getRegisteredState(competitionName, "testinguser1").then((res) => {
34+
setIsRegistered(res.data.registered);
35+
})
36+
}
37+
38+
getTeams(competitionName).then((res) => {
39+
setTeams(res.data);
40+
});
41+
}, []);
42+
43+
return (
44+
<DefaultLayout>
45+
<div className='AllTeamsPage'>
46+
<div className="hero">
47+
<h1 id="title">{competitionName}</h1>
48+
</div>
49+
{isRegistered ? (
50+
<div>
51+
<div className='main-section'>
52+
<h2 className='statement'>Teams</h2>
53+
{teams.map((team: any) => {
54+
return (Team(team));
55+
})}
56+
</div>
57+
</div>
58+
):(
59+
<p className='errorMessage'>You must be logged in and registered in this competition to view this page.</p>
60+
)}
61+
</div>
62+
</DefaultLayout>
63+
)
64+
}
65+
export default CompetitionAllTeamsPage;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
@import '../../../../styles/base.less';
2+
.CompetitionSpecificTeamPage {
3+
.noContainer();
4+
@import '../../../../styles/impactful.less';
5+
6+
li {
7+
list-style-type: none;
8+
}
9+
10+
.subheader {
11+
color: @b2;
12+
}
13+
14+
.block {
15+
margin: 2rem 0;
16+
background-color: @gray;
17+
padding: 1rem 1.2rem;
18+
border-radius: 0.5rem;
19+
20+
h4 {
21+
margin: 0;
22+
}
23+
}
24+
25+
.errorMessage {
26+
margin: 2rem;
27+
}
28+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import React, { useEffect, useState, useContext } from 'react';
2+
import DefaultLayout from '../../../../components/layouts/default';
3+
import './index.less';
4+
import { useParams } from 'react-router-dom';
5+
import { getRegisteredState, getTeamInfo } from '../utils';
6+
import UserContext from '../../../../UserContext';
7+
8+
const CompetitionSpecificTeamPage = () => {
9+
10+
const { user } = useContext(UserContext);
11+
const [isRegistered, setIsRegistered] = useState<any>(false);
12+
const [team, setTeam] = useState<any>({});
13+
const [teamMembers, setTeamMembers] = useState<any>([]);
14+
15+
let { competitionName, teamName } = useParams<{ competitionName: string, teamName: string }>();
16+
17+
useEffect(() => {
18+
if (user.loggedIn) {
19+
// TODO: it's hardcoded to "testinguser1"; change it to user.username
20+
getRegisteredState(competitionName, "testinguser1").then((res) => {
21+
setIsRegistered(res.data.registered);
22+
})
23+
}
24+
25+
getTeamInfo(competitionName, teamName).then((res) => {
26+
setTeam(res.data);
27+
setTeamMembers(res.data.teamMembers);
28+
});
29+
}, []);
30+
31+
return (
32+
<DefaultLayout>
33+
<div className="CompetitionSpecificTeamPage">
34+
<div className="hero">
35+
<h1 id="title">{competitionName}</h1>
36+
</div>
37+
<div></div>
38+
{isRegistered ? (
39+
<div className='main-section'>
40+
41+
<h2 className='statement'>Team {team.teamName}</h2>
42+
{/* TODO: Change testinguser1 to user.username */}
43+
{teamMembers.includes("testinguser1") &&
44+
<div className='block'>
45+
<p>Invite your friends to join this team!</p>
46+
<h4>Team Join Code: {team.joinCode}</h4>
47+
</div>
48+
}
49+
<h3><span className="subheader">Best Score: {team.bestScore}</span></h3>
50+
51+
<h3><span className="subheader">Members</span></h3>
52+
<div>
53+
{teamMembers.map((member: string) => {
54+
return <li key={member}>{member}</li>
55+
})}
56+
</div>
57+
58+
<h3><span className="subheader">About</span></h3>
59+
<p>{team.teamDescription}</p>
60+
</div>
61+
):(
62+
<p className='errorMessage'>
63+
You need to be logged in and registered in this competition to view this page.
64+
</p>
65+
)}
66+
</div>
67+
</DefaultLayout>
68+
)
69+
}
70+
export default CompetitionSpecificTeamPage;
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { message } from 'antd';
2+
import axios, { AxiosResponse } from 'axios';
3+
import { COOKIE_NAME } from '../../../configs';
4+
import { getToken } from '../../../utils/token';
5+
6+
// Checks if user is registered in competition
7+
export const getRegisteredState = (
8+
competitionName: string,
9+
username: string
10+
): Promise<AxiosResponse> => {
11+
return new Promise((resolve, reject) => {
12+
axios
13+
.get(
14+
// TODO: remove this hardcoded thing
15+
// `http://localhost:9000/v1/competitions/${competitionName}/${username}`
16+
process.env.REACT_APP_API + `/v1/competitions/${competitionName}/${username}`
17+
)
18+
.then((res: AxiosResponse) => {
19+
resolve(res);
20+
})
21+
.catch((error) => {
22+
message.error(error.response.data.error.message);
23+
reject(error);
24+
});
25+
});
26+
};
27+
28+
// Get all teams of a competition
29+
export const getTeams = async (
30+
competitionName: string
31+
): Promise<AxiosResponse> => {
32+
let token = getToken(COOKIE_NAME);
33+
return new Promise((resolve, reject) => {
34+
axios
35+
.get(
36+
// TODO: remove hardcoded thing
37+
// `http://localhost:9000/v1/competitions/teams/${competitionName}`,
38+
process.env.REACT_APP_API + `/v1/competitions/teams/${competitionName}`,
39+
{
40+
headers: {
41+
Authorization: `Bearer ${token}`,
42+
'Content-Type': 'multipart/form-data',
43+
},
44+
}
45+
)
46+
.then((res: AxiosResponse) => {
47+
resolve(res);
48+
})
49+
.catch((error) => {
50+
message.error(error.response.data.error.message);
51+
reject(error);
52+
});
53+
});
54+
};
55+
56+
// Get team info of a specific team in a competition
57+
export const getTeamInfo = async (
58+
competitionName: string,
59+
teamName: string
60+
): Promise<AxiosResponse> => {
61+
let token = getToken(COOKIE_NAME);
62+
return new Promise((resolve, reject) => {
63+
axios
64+
.get(
65+
// TODO: remove hardcoded thing
66+
// `http://localhost:9000/v1/competitions/teams/${competitionName}/${teamName}`,
67+
process.env.REACT_APP_API + `/v1/competitions/teams/${competitionName}/${teamName}`,
68+
{
69+
headers: {
70+
Authorization: `Bearer ${token}`,
71+
'Content-Type': 'multipart/form-data',
72+
},
73+
}
74+
)
75+
.then((res: AxiosResponse) => {
76+
resolve(res);
77+
})
78+
.catch((error) => {
79+
message.error(error.response.data.error.message);
80+
reject(error);
81+
});
82+
});
83+
};

0 commit comments

Comments
 (0)