Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat/ add wildcard in header to prevent CORS error #144

Merged
merged 9 commits into from
Feb 1, 2024
1 change: 1 addition & 0 deletions apps/challenges/src/controllers/questionaire.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const getQuestion = asyncHandler(async (req: Request, res: Response) => {
}
});


// @desc Set question
// @route POST /api/question
// @access Private
Expand Down
7 changes: 6 additions & 1 deletion apps/challenges/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ const port = process.env.PORT || 3000;

// Middleware
app.use(express.json());

app.use(function(req, res, next) {
// TODO: change when deploying to production
res.header("Access-Control-Allow-Origin", "*"); // TODO: configure properly before deployment
res.header("Access-Control-Allow-Headers", "*");
next();
});
// Routes
app.get("/ping", (req: Request, res: Response) => {
res.status(200).json({ message: "pong" });
Expand Down
6 changes: 2 additions & 4 deletions apps/web/features/challenges/components/LeaderboardEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ interface LeaderboardEntryProps {
points: number
}

const LeaderboardEntry = ({ index, name, points }: LeaderboardEntryProps) => {
export const LeaderboardEntry = ({ index, name, points }: LeaderboardEntryProps) => {
return (<Flex justify="space-evenly" py={1} >
<Flex justify="start" align="center" alignItems="center" w="80%">
<Text w="10%" textAlign="center">{index}</Text>
Expand All @@ -18,6 +18,4 @@ const LeaderboardEntry = ({ index, name, points }: LeaderboardEntryProps) => {
</Flex>
)

}

export default LeaderboardEntry
}
13 changes: 13 additions & 0 deletions apps/web/features/challenges/components/PaginationButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Button } from "@chakra-ui/react"

interface PaginationButtonProps {
onClick: () => void
variant?: string
text: string
}

export const PaginationButton = ({ onClick, variant = 'primary-blue', text }: PaginationButtonProps) => {
return <Button onClick={onClick} variant={variant} size={['sm', 'md']} _hover={{ bg: variant }} mx={4}>{text}</Button>
}

export default PaginationButton
2 changes: 2 additions & 0 deletions apps/web/features/challenges/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { LeaderboardEntry } from './LeaderboardEntry'
export { PaginationButton } from './PaginationButton'
167 changes: 99 additions & 68 deletions apps/web/pages/challenges/leaderboard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,98 +1,129 @@
import LeaderboardEntry from "@/features/challenges/components/LeaderboardEntry"
import { Box, Button, Flex, Select, Spacer, Text } from "@chakra-ui/react"
import { useState } from "react"
import { Pagination } from "react-bootstrap"

// placeholder for API Response
let currentSeasonData: LeaderboardData[] = [
{ uuid: 1000, userId: "alex200", points: 100 },
{ uuid: 1001, userId: "johndoe010", points: 200 },
{ uuid: 1002, userId: "fwqdqw", points: 350 },
{ uuid: 1003, userId: "cwcqw", points: 350 },
{ uuid: 1004, userId: "dqwdq", points: 350 },
{ uuid: 1005, userId: "wqcqw", points: 350 },
{ uuid: 1006, userId: "cwqc", points: 350 },
]
/*
TODO
Pagination
-----------------
[x] Fetch season data when dropdown is selected
[x] Get next page when PaginationButton is clicked
[ ] Fetch active season
[ ] Display correct ranking
*/

interface LeaderboardData {
uuid: number,
userId: string,
points: number
}

function paginateData(data: LeaderboardData[], numItemsPerPage: number) : LeaderboardData[][] {
const data2D : LeaderboardData[][] = []
for (var i = 0; i < data.length; i += numItemsPerPage) {
data2D.push(data.slice(i, i + numItemsPerPage))
}
return data2D
}
import { LeaderboardEntry, PaginationButton } from "@/features/challenges/components"
import { Box, Flex, Select } from "@chakra-ui/react"
import { useEffect, useState } from "react"

interface PaginationButtonProps {
onClick : () => void
variant?: string
text: string
const numOfItemsPerPage = 1

interface Season {
_id: string,
title: string,
start_date: Date,
end_date: Date
}
const PaginationButton = ({onClick, variant = 'primary-blue', text} : PaginationButtonProps) => {

return <Button onClick={onClick} variant={variant} size={['sm','md']} _hover={{bg: variant}} mx={4}>{text}</Button>

interface RankingResponse {
_id: string,
seasonID: string,
userID: string,
username: string,
__v: number,
createdAt: string,
points: number,
updatedAt: string
}

const numOfItemsPerPage = 3
interface LeaderboardData {
uuid: string,
userId: string,
username: string,
points: number
}

const numOfPages = Math.ceil(currentSeasonData.length / numOfItemsPerPage)
const paginatedCurrentSeasonData = paginateData(currentSeasonData, numOfItemsPerPage)
function findSeason(seasonId: string, seasons: Season[]) {
return seasons.find((ele) => ele._id == seasonId)
}

const Leaderboard = () => {
const [currentDisplayedData, setCurrentDisplayedData] = useState<LeaderboardData[]>()
const [currentPage, setCurrentPage] = useState(0)
const [numOfPages, setNumOfPages] = useState(0)
const [seasons, setSeasons] = useState<Season[]>()
const [currentSeason, setCurrentSeason] = useState<Season>()

// get different season / page ranking
function updateSeasonRanking(seasonID: string, index: number) {
const url = `http://localhost:3000/api/seasons/${seasonID}/rankings?page=${index}&limit=${numOfItemsPerPage}`
fetch(url)
.then((res: Response) => {
return res.json()
})
.then((res: any) => {
console.log(res)
const currentSeasonData: LeaderboardData[] = res.rankings.map((ele: RankingResponse) => {
return { "uuid": ele._id, "userId": ele.userID, "username": ele.username, "points": ele.points }
})
setNumOfPages(res._metaData.pageCount)
setCurrentDisplayedData(currentSeasonData)
})
}

const [currentDisplayedData, setCurrentDisplayedData] = useState(paginatedCurrentSeasonData[currentPage])
function handleDataChange(event: React.ChangeEvent<HTMLSelectElement>) {
// fetch and update leaderboard
function handleDropdownChange(event: React.ChangeEvent<HTMLSelectElement>) {
// reset to first page when changing seasons
setCurrentPage(0)
switch (event.target.value) {
case 'season-id-1':
setCurrentDisplayedData(paginatedCurrentSeasonData[currentPage])
break
if (seasons != undefined) {
setCurrentSeason(findSeason(event.target.value, seasons))
updateSeasonRanking(event.target.value, 0)
}

}

function goToPage(index: number) {
let toSet = index
if (index < 0) toSet = 0
else if (index > numOfPages-1) toSet = numOfPages - 1
console.log(`toSet: ${toSet}`)
setCurrentPage(toSet)
setCurrentDisplayedData(paginatedCurrentSeasonData[toSet])
}

function handlePaginationButtonClick(index: number) {
if (currentSeason != undefined && index >= 0 && index <= (numOfPages - 1)) {
updateSeasonRanking(currentSeason._id, index)
setCurrentPage(index)
}
}

useEffect(() => {
fetch("http://localhost:3000/api/seasons/")
.then((res: Response) => {
return res.json()
})
.then((res: any) => {
let seasons: Season[] = res.seasons
setSeasons(seasons)
setCurrentSeason(seasons[0])
// replace with active season
updateSeasonRanking(seasons[0]._id, 0)
})

}, [])

return (<Flex
minH="100vh"
pt={24}
flexDirection="column"
justifyContent="center"
alignItems="center">
<Select bg="gray.300" borderColor="gray.300" w="80vw" my={4} onChange={handleDataChange}>
<option value='season-id-1'>Current Season</option>
<option value='season-id-2'>Season 2</option>
</Select>
<Select bg="gray.300" borderColor="gray.300" w="80vw" my={4} onChange={handleDropdownChange}>
{seasons?.map((season) => { return <option key={season._id} value={season._id}>{season.title}</option> })}
</Select>

<Box bg="gray.300" w="80vw" px={8} py={4} minHeight="70vh" borderRadius="8">
{currentDisplayedData.sort().reverse().map((item, index) => {
return <LeaderboardEntry key={item.uuid} index={index+1} name={item.userId} points={item.points}/>
{currentDisplayedData && currentDisplayedData.sort().reverse().map((item, index) => {
return <LeaderboardEntry key={item.uuid} index={index + 1} name={item.username} points={item.points} />
})}
</Box>

<Flex justifyContent='center' w="60vw" align="center" py={8}>
<PaginationButton onClick={() =>{goToPage(currentPage - 1)}} text="<"/>

{Array.from(Array(numOfPages).keys()).map((index) => <PaginationButton onClick={() => {goToPage(index)}} variant={currentPage == index ? 'primary-black' : 'primary-blue'} text={(index+1).toString()}/>)}

<PaginationButton onClick={() =>{goToPage(currentPage + 1)}} text=">"/>

<PaginationButton onClick={() => { handlePaginationButtonClick(currentPage - 1) }} text="<" />

{Array.from(Array(numOfPages).keys()).map((index) => <PaginationButton key={index} onClick={() => { handlePaginationButtonClick(index) }} variant={currentPage == index ? 'primary-black' : 'primary-blue'} text={(index + 1).toString()} />)}

<PaginationButton onClick={() => { handlePaginationButtonClick(currentPage + 1) }} text=">" />
</Flex>

</Flex>)
}



export default Leaderboard
Loading