diff --git a/client/.prettierrc b/client/.prettierrc new file mode 100644 index 0000000..72c6444 --- /dev/null +++ b/client/.prettierrc @@ -0,0 +1,4 @@ +{ + "tabWidth": 2, + "useTabs": true +} \ No newline at end of file diff --git a/client/package-lock.json b/client/package-lock.json index 95af73b..b18e87e 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -8,6 +8,7 @@ "name": "client", "version": "0.1.0", "dependencies": { + "@chakra-ui/icons": "^2.1.1", "@chakra-ui/next-js": "^2.1.5", "@chakra-ui/react": "^2.8.1", "@emotion/react": "^11.11.1", @@ -528,6 +529,18 @@ "react": ">=18" } }, + "node_modules/@chakra-ui/icons": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/icons/-/icons-2.1.1.tgz", + "integrity": "sha512-3p30hdo4LlRZTT5CwoAJq3G9fHI0wDc0pBaMHj4SUn0yomO+RcDRlzhdXqdr5cVnzax44sqXJVnf3oQG0eI+4g==", + "dependencies": { + "@chakra-ui/icon": "3.2.0" + }, + "peerDependencies": { + "@chakra-ui/system": ">=2.0.0", + "react": ">=18" + } + }, "node_modules/@chakra-ui/image": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-2.1.0.tgz", @@ -6067,6 +6080,14 @@ "@chakra-ui/shared-utils": "2.0.5" } }, + "@chakra-ui/icons": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@chakra-ui/icons/-/icons-2.1.1.tgz", + "integrity": "sha512-3p30hdo4LlRZTT5CwoAJq3G9fHI0wDc0pBaMHj4SUn0yomO+RcDRlzhdXqdr5cVnzax44sqXJVnf3oQG0eI+4g==", + "requires": { + "@chakra-ui/icon": "3.2.0" + } + }, "@chakra-ui/image": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-2.1.0.tgz", diff --git a/client/package.json b/client/package.json index af8b020..808e279 100644 --- a/client/package.json +++ b/client/package.json @@ -9,6 +9,7 @@ "lint": "next lint" }, "dependencies": { + "@chakra-ui/icons": "^2.1.1", "@chakra-ui/next-js": "^2.1.5", "@chakra-ui/react": "^2.8.1", "@emotion/react": "^11.11.1", diff --git a/client/pages/Admin/LeaderboardConfig.module.css b/client/pages/Admin/LeaderboardConfig.module.css index f0aeea8..1dc9a3b 100644 --- a/client/pages/Admin/LeaderboardConfig.module.css +++ b/client/pages/Admin/LeaderboardConfig.module.css @@ -38,3 +38,25 @@ overflow-y: auto; padding: 0 1em; } + +.listItem{ + display: flex; + flex-direction: row; + justify-content: space-between; + gap: 2em; + align-items: center; + padding: 0.5em 1em; + border-radius: 0.5em; + background-color: rgb(45, 44, 44); + margin-bottom:1em ; +} + +.deleteBtn{ + display: flex; + justify-content: center; + align-items: center; + padding: 0.8em !important; + height: 1.2em !important; + font-size: 1.5em; + cursor: pointer; +} \ No newline at end of file diff --git a/client/pages/Admin/LeaderboardConfig.tsx b/client/pages/Admin/LeaderboardConfig.tsx index a22bd5a..3701d35 100644 --- a/client/pages/Admin/LeaderboardConfig.tsx +++ b/client/pages/Admin/LeaderboardConfig.tsx @@ -1,104 +1,128 @@ import { fetcher, myToast } from "@/util/functions"; +import { DeleteIcon } from "@chakra-ui/icons"; import RootLayout from "../Layout"; import styles from "./LeaderboardConfig.module.css"; import useSWR from "swr"; function useContests() { - const { data, error, isLoading, mutate } = useSWR( - `/api/admin/contestConfig`, - fetcher - ); + const { data, error, isLoading, mutate } = useSWR( + `/api/admin/contestConfig`, + fetcher + ); - return { - contests: data, - isLoading, - isError: error, - mutate, - }; + return { + contests: data, + isLoading, + isError: error, + mutate, + }; +} + +function verfiyContest(contest: string) { + return contest.length > 0; } async function postContest( - contest: string, - setIsLoading: React.Dispatch>, - setIsError: React.Dispatch>, - mutate: () => void + contest: string, + setIsLoading: React.Dispatch>, + setIsError: React.Dispatch>, + mutate: () => void ) { - setIsLoading(true); - const res = await fetch(`/api/admin/contestConfig`, { - method: "POST", - body: JSON.stringify({ contest }), - headers: { - "Content-Type": "application/json", - }, - }); - setIsLoading(false); - mutate(); - if (!res.ok) { - setIsError(true); - } + setIsLoading(true); + + if (!verfiyContest(contest)) { + setIsLoading(false); + setIsError(true); + return; + } + + const res = await fetch(`/api/admin/contestConfig`, { + method: "POST", + body: JSON.stringify({ contest }), + headers: { + "Content-Type": "application/json", + }, + }); + + setIsLoading(false); + mutate(); + if (!res.ok) { + setIsError(true); + } } import toast, { Toaster } from "react-hot-toast"; -import { Box, Button, Input } from "@chakra-ui/react"; +import { Box, Button, IconButton, Input } from "@chakra-ui/react"; import React from "react"; +function listComp({ contest }: { contest: string }) { + return ( +
+
{contest}
+ } + // onClick={} + /> +
+ ); +} + export default function LeaderboardConfig() { - const { contests, isLoading, isError, mutate } = useContests(); - const [contest, setContest] = React.useState(""); - const [isLoadingPost, setIsLoadingPost] = React.useState(false); - const [isErrorPost, setIsErrorPost] = React.useState(false); + const { contests, isLoading, isError, mutate } = useContests(); + const [contest, setContest] = React.useState(""); + const [isLoadingPost, setIsLoadingPost] = React.useState(false); + const [isErrorPost, setIsErrorPost] = React.useState(false); - let listOfContests; + let listOfContests; - if (isLoading) { - listOfContests =
Loading...
; - } else if (isError) { - listOfContests =
Error...
; - } else { - console.log(contests); - listOfContests = contests.contests.map((contest: string) => { - return
{contest}
; - }); - } + if (isLoading) { + listOfContests =
Loading...
; + } else if (isError) { + listOfContests =
Error...
; + } else { + console.log(contests); + listOfContests = contests.contests.map((contest: string) => { + return listComp({ contest }); + }); + } - return ( - -
-

Contests on Leaderboard

-
- setContest(e.target.value)} - /> + return ( + +
+

Contests on Leaderboard

+
+ setContest(e.target.value)} + /> - -
+ +
- -
{listOfContests}
-
-
- ); + +
{listOfContests}
+ + + ); } diff --git a/client/pages/api/admin/contestConfig.ts b/client/pages/api/admin/contestConfig.ts index d5b6a75..2b88d91 100644 --- a/client/pages/api/admin/contestConfig.ts +++ b/client/pages/api/admin/contestConfig.ts @@ -1,31 +1,38 @@ import type { NextApiRequest, NextApiResponse } from "next"; import redisClient from "@/util/redis"; type ResponseData = { - message: string; + message: string; }; export default async function handler( - req: NextApiRequest, - res: NextApiResponse + req: NextApiRequest, + res: NextApiResponse ) { - if (req.method === "GET") { - return GET(req, res); - } else if (req.method === "POST") { - return POST(req, res); - } else { - return res.status(400).send(`Method ${req.method} is not supported!`); - } + if (req.method === "GET") { + return GET(req, res); + } else if (req.method === "POST") { + return POST(req, res); + } else { + return res.status(400).send(`Method ${req.method} is not supported!`); + } } async function GET(req: NextApiRequest, res: NextApiResponse) { - const contests = await redisClient.lRange("Contests", 0, -1); + const contests = await redisClient.lRange("Contests", 0, -1); - return res.json({ contests }); + return res.json({ contests }); } async function POST(req: NextApiRequest, res: NextApiResponse) { - const { contest } = req.body; - await redisClient.lPush("Contests", contest); + const { contest } = req.body; + await redisClient.lPush("Contests", contest); - return res.send("success"); + return res.send("success"); +} + +async function DELETE(req: NextApiRequest, res: NextApiResponse) { + const { contest } = req.body; + await redisClient.lRem("Contests", 0, contest); + + return res.send("success"); } diff --git a/client/pages/components/NavBar/Navbar.module.css b/client/pages/components/NavBar/Navbar.module.css index 5ae2da2..9f2be27 100644 --- a/client/pages/components/NavBar/Navbar.module.css +++ b/client/pages/components/NavBar/Navbar.module.css @@ -35,7 +35,6 @@ color: white; display: flex; flex-direction: column; - align-items: center; justify-content: center; gap: 0.5em; } diff --git a/client/pages/components/ScoreBoard/ScoreBoard.module.css b/client/pages/components/ScoreBoard/ScoreBoard.module.css index f8b45f9..0d7745b 100644 --- a/client/pages/components/ScoreBoard/ScoreBoard.module.css +++ b/client/pages/components/ScoreBoard/ScoreBoard.module.css @@ -1,6 +1,35 @@ .title { font-size: 1.5rem; font-weight: bold; - margin-bottom: 1rem; text-align: center; } + +/*TODO grid with 5 cols: +- first col === 2 cols +- 2nd col === 1 col +-3rd col === 2 cols*/ + +.tableHeader { + display: grid; + grid-template-columns: 2fr 1fr 2fr 2fr; + width: 100%; + padding: 0 1.5rem; + gap: 2.5rem; + width: 100%; + text-align: center; +} + +.tableEntry { + display: grid; + grid-template-columns: 2fr 1fr 2fr 2fr; + width: 100%; + justify-content: space-around; + padding: 0 1rem; + text-align: center; +} + +.tableEntry > div:nth-child(1), +.tableHeader > div:nth-child(1) { + text-align: left; +} + diff --git a/client/pages/components/ScoreBoard/ScoreBoard.tsx b/client/pages/components/ScoreBoard/ScoreBoard.tsx index 638d848..125cb18 100644 --- a/client/pages/components/ScoreBoard/ScoreBoard.tsx +++ b/client/pages/components/ScoreBoard/ScoreBoard.tsx @@ -1,9 +1,105 @@ -import style from "./ScoreBoard.module.css"; +import { Divider } from "@chakra-ui/react"; +import styles from "./ScoreBoard.module.css"; function ScoreBoard() { + const data = { + players: [ + { + Name: "John Doe", + Rank: 1, + "Points Scored": 95, + }, + { + Name: "Alice Smith", + Rank: 2, + "Points Scored": 89, + }, + { + Name: "Bob Johnson", + Rank: 3, + "Points Scored": 87, + }, + { + Name: "Sarah Williams", + Rank: 4, + "Points Scored": 84, + }, + { + Name: "Michael Brown", + Rank: 5, + "Points Scored": 80, + }, + { + Name: "Emily Davis", + Rank: 6, + "Points Scored": 78, + }, + { + Name: "Daniel Wilson", + Rank: 7, + "Points Scored": 75, + }, + { + Name: "Olivia Harris", + Rank: 8, + "Points Scored": 72, + }, + { + Name: "James Lee", + Rank: 9, + "Points Scored": 68, + }, + { + Name: "Grace Anderson", + Rank: 10, + "Points Scored": 65, + }, + ], + }; + + // function of eligibility: + // if rank <= 4 => leader + // if rank <= 15 => coleader + // if rank <= 30 => elder + // if rank <= 60 => member + + function eligibility(rank: number) { + if (rank <= 4) { + return "leader"; + } else if (rank <= 15) { + return "coleader"; + } else if (rank <= 30) { + return "elder"; + } else if (rank <= 60) { + return "member"; + } else { + return "not eligible"; + } + } + return (
-

ScoreBoard

+

ScoreBoard

+ + {/* table here: */} +
+
Name
+
#
+
Points
+
Eligibility
+
+ + + + {/* table entries: */} + {data.players.map((item, index) => ( +
+
{item.Name}
+
{item.Rank}
+
{item["Points Scored"]}
+
{eligibility(item.Rank)}
+
+ ))}
); } diff --git a/client/pages/components/ScoreCard/ScoreCard.module.css b/client/pages/components/ScoreCard/ScoreCard.module.css index 37a3918..10512f0 100644 --- a/client/pages/components/ScoreCard/ScoreCard.module.css +++ b/client/pages/components/ScoreCard/ScoreCard.module.css @@ -1,10 +1,15 @@ +.title { + font-size: 1.5rem; + font-weight: bold; + text-align: center; +} + .tableHeader { -display: flex; + display: flex; flex-direction: row; - align-items: center; - padding: 0 1rem; - /* margin-bottom: 1rem; */ - gap:20px; + padding: 0 0.5rem; + width: 100%; + gap: 2.5rem; } .tableEntry{ @@ -12,10 +17,8 @@ display: flex; flex-direction: row; width: 100%; justify-content: space-between; - align-items: center; - padding: 0 1rem; - /* margin-bottom: 1rem; */ } + .tableWrapper{ display: flex; flex-direction: column; diff --git a/client/pages/components/ScoreCard/ScoreCard.tsx b/client/pages/components/ScoreCard/ScoreCard.tsx index ab70052..3eb97ac 100644 --- a/client/pages/components/ScoreCard/ScoreCard.tsx +++ b/client/pages/components/ScoreCard/ScoreCard.tsx @@ -1,5 +1,6 @@ import React from "react"; import styles from "./ScoreCard.module.css"; +import { Divider } from "@chakra-ui/react"; // typedefination for data object: type ScoreCardData = { @@ -41,6 +42,8 @@ function ScoreCard() { return ( // table here:
+

ScoreCard

+

Team A

@@ -56,6 +59,8 @@ function ScoreCard() {
+ + {/* table entries: */} {data.map((item, index) => (
diff --git a/client/pages/index.tsx b/client/pages/index.tsx index 236547c..4d5a5f6 100644 --- a/client/pages/index.tsx +++ b/client/pages/index.tsx @@ -1,12 +1,41 @@ import styles from "./styles/index.module.css"; import RootLayout from "./Layout"; import ScoreCard from "./components/ScoreCard/ScoreCard"; +import { useState } from "react"; +import { Button, ButtonGroup } from "@chakra-ui/react"; +import ScoreBoard from "./components/ScoreBoard/ScoreBoard"; export default function Home() { + const [showScoreCard, setShowScoreCard] = useState(false); + const [showScoreBoard, setShowScoreBoard] = useState(false); + return (
- +
+ {/* button to show ScoreCard component */} + + + {/* button to show ScoreBoard component */} + +
+ + {showScoreCard && } + {showScoreBoard && }
); diff --git a/client/pages/styles/Layout.module.css b/client/pages/styles/Layout.module.css index abbce81..97154dc 100644 --- a/client/pages/styles/Layout.module.css +++ b/client/pages/styles/Layout.module.css @@ -2,4 +2,5 @@ position: sticky; top: 0; width: 100%; + z-index: 100; } diff --git a/client/pages/styles/index.module.css b/client/pages/styles/index.module.css index 6b180bb..65a36ec 100644 --- a/client/pages/styles/index.module.css +++ b/client/pages/styles/index.module.css @@ -14,3 +14,16 @@ flex-direction: column; gap: 2rem; } + +.btnGrp{ + display: flex; + flex-direction: row; + gap: 1rem; + margin-bottom: 1em; +} + +@media (max-width: 768px) { + .btnGrp{ + flex-direction: column; + } +} \ No newline at end of file