diff --git a/src/client/.env b/src/client/.env index 02a4337..fafeb61 100644 --- a/src/client/.env +++ b/src/client/.env @@ -1,2 +1,3 @@ REACT_APP_API_URL=https://asura.feal.no/api +REACT_APP_LOGIN_URL=http://localhost:3001/auth/google BROWSER=none diff --git a/src/client/package-lock.json b/src/client/package-lock.json index dd036ab..bf11bdc 100644 --- a/src/client/package-lock.json +++ b/src/client/package-lock.json @@ -4869,6 +4869,7 @@ "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", + "fsevents": "~2.3.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", @@ -6116,7 +6117,8 @@ "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2", - "optionator": "^0.8.1" + "optionator": "^0.8.1", + "source-map": "~0.6.1" }, "bin": { "escodegen": "bin/escodegen.js", @@ -8724,6 +8726,7 @@ "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", "graceful-fs": "^4.2.9", "jest-regex-util": "^27.5.1", "jest-serializer": "^27.5.1", @@ -10029,9 +10032,14 @@ "integrity": "sha512-EoQp/Et7OSOVu0aJknJOtlXZsnr8XE8KwuzTHOLeVSEx8pVWUICc8Q0VYRHgzyjX78nMEyC/oztWFbgyhtNfDA==", "dependencies": { "copy-anything": "^2.0.1", + "errno": "^0.1.1", "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", "mime": "^1.4.1", + "needle": "^2.5.2", "parse-node-version": "^1.0.1", + "source-map": "~0.6.0", "tslib": "^2.3.0" }, "bin": { @@ -12407,6 +12415,7 @@ "eslint-webpack-plugin": "^3.1.1", "file-loader": "^6.2.0", "fs-extra": "^10.0.0", + "fsevents": "^2.3.2", "html-webpack-plugin": "^5.5.0", "identity-obj-proxy": "^3.0.0", "jest": "^27.4.3", @@ -12826,6 +12835,9 @@ "version": "2.70.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.70.1.tgz", "integrity": "sha512-CRYsI5EuzLbXdxC6RnYhOuRdtz4bhejPMSWjsFLfVM/7w/85n2szZv6yExqUXsBdz5KT8eoubeyDUDjhLHEslA==", + "dependencies": { + "fsevents": "~2.3.2" + }, "bin": { "rollup": "dist/bin/rollup" }, diff --git a/src/client/public/btn_google_signing_dark.png b/src/client/public/btn_google_signing_dark.png new file mode 100644 index 0000000..f27bb24 Binary files /dev/null and b/src/client/public/btn_google_signing_dark.png differ diff --git a/src/client/src/AdminsOverview.js b/src/client/src/AdminsOverview.js new file mode 100644 index 0000000..f04db42 --- /dev/null +++ b/src/client/src/AdminsOverview.js @@ -0,0 +1,134 @@ +import * as React from "react"; +import { BrowserRouter as Router, Link, Route, Routes, useParams } from "react-router-dom"; +import Appbar from "./components/AsuraBar"; +import ErrorSnackbar from "./components/ErrorSnackbar"; + +import {Button, Box, TextField, Stack, InputLabel, Paper, TableContainer, Table, TableBody, TableHead, TableCell, TableRow, Typography} from '@mui/material'; +import AddCircleIcon from '@mui/icons-material/AddCircle'; +import DeleteIcon from '@mui/icons-material/Delete'; +import EditIcon from '@mui/icons-material/Edit'; + +function showError(error) { + alert("Something went wrong. \n" + error); + console.error(error); +} + +function AdminCreator(props){ + function postCreate(){ + let adminEmail = document.getElementById("adminEmailInput").value; + if (!adminEmail) { + showError("Admin email is required"); + return; + } + + + let formData = new FormData(); + formData.append("email", adminEmail) + let body = new URLSearchParams(formData) + + fetch(process.env.REACT_APP_API_URL + `/admins/create`, { + method: "POST", + body: body + }) + .then(res => res.json()) + .then(data => { + if (data.status !== "OK") { + showError(data.data); + return; + } + document.getElementById("adminEmailInput").value = ""; + props.onAdminCreated(); + } + ) + } + + return ( + +
+ + {/* */} + +
+
+ ) +} + +function AdminList(props){ + const deleteAdmin = adminId => { + fetch(process.env.REACT_APP_API_URL + `/admins/${adminId}`, {method: "DELETE"}) + .then(res => res.json()) + .then(data => { + if(data.status !== "OK"){ + showError(data.data); + return; + } + props.setAdmins(props.admins.filter(admin => admin.id !== adminId)); + }) + .catch(error => showError(error)); + } + + return( + +
+ {/* TODO: scroll denne menyen, eventuelt søkefelt */} + + + + Admin + Actions + + + + {props.admins.map((admin) => ( + + + {admin.name} + + {/* {team.members} */} + + {/* */} + + + + ))} + +
+
+
+ ) +} + +export default function Admins(props) { + const [admins, setAdmins] = React.useState([]); + const { adminId } = useParams(); + + function getAdmins() { + fetch(process.env.REACT_APP_API_URL + `/admins/getAdmins`) + .then((res) => res.json()) + .then((data) =>{ + if(data.status !== "OK") { + showError(data.data); + } + setAdmins(data.data); + }) + .catch((err) => showError(err)); + } + React.useEffect(() => { + getAdmins() + }, []); + + return ( + <> + +
+ + +
+ + ); +} \ No newline at end of file diff --git a/src/client/src/FrontPage.js b/src/client/src/FrontPage.js index ebe1da4..3cfc07d 100644 --- a/src/client/src/FrontPage.js +++ b/src/client/src/FrontPage.js @@ -3,9 +3,9 @@ import { BrowserRouter as Router, Link, Route, Routes } from "react-router-dom"; import TournamentCreator from "./TournamentCreator.js"; import TournamentOverview from "./TournamentOverview.js"; import TournamentManager from "./TournamentManager.js"; -import TournamentAnnouncement from "./TournamentAnnouncement"; import TournamentHistory from "./TournamentHistory"; import TournamentTeams from "./TournamentTeams"; +import LoginPage from "./LoginPage"; import AppBar from './components/AsuraBar'; import { Button, Container, Typography, Box, Stack, Card, CardContent, CardMedia, Paper, Grid, Icon } from "@mui/material"; import AddCircleIcon from '@mui/icons-material/AddCircle'; @@ -56,6 +56,34 @@ function TournamentListItem(props) { ; } } + + function Countdown() { + const [remainingTime, setremainingTime] = React.useState(Math.abs(props.tournament.startTime - new Date())) + React.useEffect(() => { + const interval = setInterval(() => + setremainingTime(Math.abs(props.tournament.startTime - new Date())) + , 1000); + return () => { + clearInterval(interval); + }; + }, []); + + let remainingDays = Math.floor(remainingTime / (1000 * 60 * 60 * 24)); + let remainingHours = Math.floor(remainingTime / (1000 * 60 * 60)) - remainingDays * 24 + let remainingMins = Math.floor(remainingTime / (1000 * 60)) - (remainingDays * 24 * 60 + remainingHours * 60) + let remainingSecs = Math.floor(remainingTime / 1000) - (remainingDays * 24 * 60 * 60 + remainingHours * 60 * 60 + remainingMins * 60) + if (props.tournament.startTime < new Date()) { + return ( + Started! + ) + } else { + return( + Starts in: {remainingDays} Days, {remainingHours} Hours, {remainingMins} Minutes and {remainingSecs} Seconds + + ) + } + } + return ( @@ -73,7 +101,7 @@ function TournamentListItem(props) { End: {props.tournament.endTime.toLocaleString()} - Players {props.tournament.teamCount} / {props.tournament.teamLimit} + Players: {props.tournament.teamCount} / {props.tournament.teamLimit} @@ -92,6 +120,8 @@ function TournamentListItem(props) { + + @@ -144,6 +174,9 @@ function Home() { + + Finished tournaments are moved to the history-page + ); @@ -160,10 +193,7 @@ export default function App() { } /> } /> } /> - } - /> + } /> diff --git a/src/client/src/LoginPage.js b/src/client/src/LoginPage.js new file mode 100644 index 0000000..f723b63 --- /dev/null +++ b/src/client/src/LoginPage.js @@ -0,0 +1,19 @@ +import * as React from "react"; +import { BrowserRouter as Router, Link, Route, Routes } from "react-router-dom"; +import AppBar from "./components/AsuraBar"; +import ErrorSnackbar from "./components/ErrorSnackbar"; + +import {Button, Textfield, Stack, InputLabel, Paper, Typography} from '@mui/material'; + + +export default function LoginPage() { + return ( + <> + +

Sign in with google

+ + Sign in with google + + + ); +} \ No newline at end of file diff --git a/src/client/src/TournamentAnnouncement.js b/src/client/src/TournamentAnnouncement.js deleted file mode 100644 index af10100..0000000 --- a/src/client/src/TournamentAnnouncement.js +++ /dev/null @@ -1,34 +0,0 @@ -import * as React from "react"; -import { BrowserRouter as Router, Link, Route, Routes } from "react-router-dom"; -import Appbar from './components/AsuraBar'; - -function Announcement() { - return ( -
- - -
- - -
- - - - -
- ); -} - -export default function TournamentAnnouncement() { - return ( - <> - - - - ); -} diff --git a/src/client/src/TournamentCreator.js b/src/client/src/TournamentCreator.js index 00a6a8b..a589981 100644 --- a/src/client/src/TournamentCreator.js +++ b/src/client/src/TournamentCreator.js @@ -7,6 +7,7 @@ import { Button, TextField, Stack, InputLabel, Select, Container, Slider, Paper, import DateTimePicker from '@mui/lab/DateTimePicker'; import AdapterDateFns from '@mui/lab/AdapterDateFns'; import LocalizationProvider from '@mui/lab/LocalizationProvider'; +import { setDate } from "date-fns"; function postTournament(showError, tournamentName, tournamentDescription, tournamentStartDate, tournamentEndDate, tournamentMaxTeams) { if (!tournamentName || tournamentName === "") { @@ -81,8 +82,8 @@ function TournamentForm(props) { function submitTournament(event) { event.preventDefault(); let maxTeams = Math.pow(2, maxTeamsExponent); - let tournamentStart = new Date(startTime).valueOf() - new Date().getTimezoneOffset() * 60000; - let tournamentEnd = new Date(endTime).valueOf() - new Date().getTimezoneOffset() * 60000; + let tournamentStart = new Date(startTime.setSeconds(0, 0, 0)).valueOf() - new Date().getTimezoneOffset() * 60*1000; + let tournamentEnd = new Date(endTime.setSeconds(0, 0, 0)).valueOf() - new Date().getTimezoneOffset() * 60*1000; postTournament( props.showError, document.getElementById("nameInput").value, diff --git a/src/client/src/TournamentHistory.js b/src/client/src/TournamentHistory.js index e874523..5845096 100644 --- a/src/client/src/TournamentHistory.js +++ b/src/client/src/TournamentHistory.js @@ -51,7 +51,7 @@ function shorten(description, maxLength) { End: {props.tournament.endTime.toLocaleString()} - Players {props.tournament.teamCount} / {props.tournament.teamLimit} + Players: {props.tournament.teamCount} / {props.tournament.teamLimit} diff --git a/src/client/src/TournamentMatches.js b/src/client/src/TournamentMatches.js index 43afb8a..44772c8 100644 --- a/src/client/src/TournamentMatches.js +++ b/src/client/src/TournamentMatches.js @@ -5,7 +5,7 @@ import Appbar from './components/Appbar'; function MatchHistory() { return( - `Hei` + `Hei på deg din gamle sei` ); } diff --git a/src/client/src/TournamentOverview.js b/src/client/src/TournamentOverview.js index 732b02a..59bec17 100644 --- a/src/client/src/TournamentOverview.js +++ b/src/client/src/TournamentOverview.js @@ -169,16 +169,48 @@ function BracketViewer(props){ .catch(err => showError(err)); }, []); return ( - (matches && teams) ? - //
-
- {matches.map(tier => { - let tierNum = tier[0].tier; - return - })} -
- : -); + (matches && teams) ? + //
+
+ {matches.map(tier => { + let tierNum = tier[0].tier; + return + })} +
+ : + ); +} + +function RemovableBar(props) { + const [endTime, setendTime] = React.useState(null); + + React.useEffect(() => { + fetch(process.env.REACT_APP_API_URL + `/tournament/${props.tournamentId}`) + .then(res => res.json()) + .then(data => { + if (data.status !== "OK") { + // Do your error thing + console.error(data); + return; + } + let endTime = data.data.endTime; + setendTime(endTime); + }) + .catch(err => showError(err)); + }) + let today = new Date() + let yesterday = today.setDate(today.getDate() - 1) + let isComplete = new Date(endTime) < yesterday + if (isComplete) { + return (null) + } else { + return () + } +} + +function showError(error) { + alert("Something went wrong. \n" + error); + console.error(error); } export default function TournamentOverview(props) { @@ -187,7 +219,7 @@ export default function TournamentOverview(props) { return ( <> - + ); diff --git a/src/client/src/TournamentTeams.js b/src/client/src/TournamentTeams.js index e063834..3d24409 100644 --- a/src/client/src/TournamentTeams.js +++ b/src/client/src/TournamentTeams.js @@ -43,15 +43,18 @@ function TeamCreator(props) { return (
+
{/* */} - +
+
) } @@ -92,7 +95,7 @@ function TeamList(props) { {/* {team.members} */} - + @@ -146,6 +149,10 @@ function TeamEditor(props) { setTeam(newTeam); } + function handleFocus(event) { + event.currentTarget.select() + } + function saveTeam() { let formData = new FormData(); formData.append("name", team.name); @@ -172,9 +179,9 @@ function TeamEditor(props) {

Edit Team:

- + {/* */} - +
diff --git a/src/client/src/components/TournamentBar.js b/src/client/src/components/TournamentBar.js index 153eb2c..b196488 100644 --- a/src/client/src/components/TournamentBar.js +++ b/src/client/src/components/TournamentBar.js @@ -1,7 +1,32 @@ import * as React from "react"; import { useParams } from "react-router-dom"; import { BrowserRouter as Router, Link, Route, Routes, History } from "react-router-dom"; -import { Stack, Paper, Typography, Box, Button, Grid } from "@mui/material" +import { Stack, Paper, Typography, Box, Button, Grid, Snackbar, IconButton } from "@mui/material" +import CloseIcon from '@mui/icons-material/Close'; + +function ClipboardButton(props) { + const [open, setOpen] = React.useState(false); + function copyString() { + navigator.clipboard.writeText(props.clipboardContent || ""); + setOpen(true); + } + const handleClose = (event, reason) => { + if (reason === 'clickaway') { return } + setOpen(false); + }; + const closeAction = <> + + + + + + return ( + <> + + + + ); +} function ButtonLink(props) { return ( @@ -14,10 +39,17 @@ function ButtonLink(props) { export default function TournamentBar(props) { const { tournamentId } = useParams(); return ( - + + + + + + + + ) } \ No newline at end of file