Dev session

This commit is contained in:
Felix Albrigtsen 2022-04-25 13:20:54 +02:00
parent 020ab4de84
commit d4a2190a5a
8 changed files with 239 additions and 108 deletions

View File

@ -3,10 +3,12 @@ import { BrowserRouter as Router, Link, Route, Routes, useParams } from "react-r
import Appbar from "./components/AsuraBar"; import Appbar from "./components/AsuraBar";
import ErrorSnackbar from "./components/ErrorSnackbar"; import ErrorSnackbar from "./components/ErrorSnackbar";
import LoginPage from "./LoginPage"; import LoginPage from "./LoginPage";
import {Button, Box, TextField, Stack, InputLabel, Paper, TableContainer, Table, TableBody, TableHead, TableCell, TableRow, Typography, Select, MenuItem, FormControl} from '@mui/material'; import { Button, Box, TextField, Stack, InputLabel, Paper, TableContainer, Table, TableBody, TableHead, TableCell, TableRow, Typography, Select, MenuItem, FormControl } from '@mui/material';
import { Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle } from "@mui/material";
import AddCircleIcon from '@mui/icons-material/AddCircle'; import AddCircleIcon from '@mui/icons-material/AddCircle';
import DeleteIcon from '@mui/icons-material/Delete'; import DeleteIcon from '@mui/icons-material/Delete';
import EditIcon from '@mui/icons-material/Edit'; import EditIcon from '@mui/icons-material/Edit';
import PropTypes from 'prop-types'
function AdminCreator(props){ function AdminCreator(props){
function postCreate(){ function postCreate(){
@ -55,17 +57,20 @@ function AdminCreator(props){
} }
function UserList(props){ function UserList(props){
const deleteUsers = (userId) => { const deleteUser = (userId) => {
openConfirmDialog(function() {
fetch(process.env.REACT_APP_API_URL + `/users/${userId}`, {method: "DELETE"}) fetch(process.env.REACT_APP_API_URL + `/users/${userId}`, {method: "DELETE"})
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
if(data.status !== "OK"){ if(data.status !== "OK"){
showError(data.data); showError(data.data);
console.log("UWU")
return; return;
} }
props.setUsers(props.users.filter(user => user.id !== userId)); props.onUserUpdated();
}) })
.catch(error => showError(error)); .catch(error => showError(error));
});
} }
let updateRank = (asuraId) => event => { let updateRank = (asuraId) => event => {
@ -92,52 +97,99 @@ function UserList(props){
return( return(
<Paper sx={{minHeight: "30vh", width:"90vw", margin:"10px auto"}} component={Stack} direction="column" justifycontent="center"> <Paper sx={{minHeight: "30vh", width:"90vw", margin:"10px auto"}} component={Stack} direction="column" justifycontent="center">
<div align="center"> <div align="center">
{/* TODO: scroll denne menyen, eventuelt søkefelt */} <Table aria-label="simple table">
<Table aria-label="simple table"> <TableHead>
<TableHead> <TableRow>
<TableRow> <TableCell>Name</TableCell>
<TableCell>Name</TableCell> <TableCell>Email</TableCell>
<TableCell>Email</TableCell> <TableCell>Rank</TableCell>
<TableCell>Rank</TableCell> <TableCell align="center">Actions</TableCell>
<TableCell align="center">Actions</TableCell> </TableRow>
</TableHead>
<TableBody>
{props.users.map((user) => (
<TableRow key={user.asuraId}>
<TableCell component="th" scope="row">
<b>
{user.name}
</b>
</TableCell>
<TableCell>{user.email}</TableCell>
{/* TODO Drop down menu for selecting rank */}
<TableCell>
<FormControl variant="standard">
<Select onChange={updateRank(user.asuraId)} value={user.isManager ? "manager" : "admin"} label="rank" labelId="rankSelect" id="rankSelect">
<MenuItem value={"manager"}>Manager</MenuItem>
<MenuItem value={"admin"}>Admin</MenuItem>
</Select>
</FormControl>
</TableCell>
{/* <TableCell align="right">{team.members}</TableCell> */}
<TableCell align="center">
{/* <Button variant="contained" sx={{margin: "auto 5px"}} color="primary" onClick={() => props.setSelectedTeamId(team.id)} endIcon={<EditIcon />}>Edit</Button> */}
<Button variant="contained" sx={{margin: "auto 5px"}} color="error" onClick={() => {deleteUser(user.asuraId)}} endIcon={<DeleteIcon />}>Delete</Button>
</TableCell>
</TableRow> </TableRow>
</TableHead> ))}
<TableBody> </TableBody>
{props.users.map((user) => ( </Table>
<TableRow key={user.asuraId}>
<TableCell component="th" scope="row">
<b>
{user.name}
</b>
</TableCell>
<TableCell>{user.email}</TableCell>
{/* TODO Drop down menu for selecting rank */}
<TableCell>
<FormControl variant="standard">
<Select onChange={updateRank(user.asuraId)} value={user.isManager ? "manager" : "admin"} label="rank" labelId="rankSelect" id="rankSelect">
<MenuItem value={"manager"}>Manager</MenuItem>
<MenuItem value={"admin"}>Admin</MenuItem>
</Select>
</FormControl>
</TableCell>
{/* <TableCell align="right">{team.members}</TableCell> */}
<TableCell align="center">
{/* <Button variant="contained" sx={{margin: "auto 5px"}} color="primary" onClick={() => props.setSelectedTeamId(team.id)} endIcon={<EditIcon />}>Edit</Button> */}
<Button variant="contained" sx={{margin: "auto 5px"}} color="error" onClick={() => {deleteUsers(user.asuraId)}} endIcon={<DeleteIcon />}>Delete</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div> </div>
</Paper> </Paper>
) )
} }
function ConfirmationDialogRaw(props) {
const { userId } = useParams();
const { onClose, value: valueProp, open, ...other } = props;
const [value, setValue] = React.useState(valueProp);
React.useEffect(() => {
if (!open) {
setValue(valueProp);
}
}, [valueProp, open]);
const handleCancel = () => {
onClose();
};
const handleConfirm = () => {
onClose();
props.handleconfirm();
};
return (
<Dialog
sx={{ '& .MuiDialog-paper': { width: '80%', maxHeight: 435 } }}
maxWidth="xs"
open={open}
keepMounted
>
<DialogTitle>Delete administrator?</DialogTitle>
<DialogContent>
Are you sure you want to delete the administrator? This action is not reversible!
</DialogContent>
<DialogActions>
<Button autoFocus onClick={handleCancel}>
Cancel
</Button>
<Button onClick={handleConfirm}>Confirm</Button>
</DialogActions>
</Dialog>
);
}
ConfirmationDialogRaw.propTypes = {
onClose: PropTypes.func.isRequired,
open: PropTypes.bool.isRequired,
};
// Confirmation window for "Delete user"
function openConfirmDialog(callback) {g_setDialogCallback(callback); g_setDialogOpen(true);}
let g_setDialogOpen = () => {};
let g_setDialogCallback = (callback) => {};
let showError = (message) => {}; let showError = (message) => {};
export default function Users(props) { export default function Users(props) {
const [openError, setOpenError] = React.useState(false); const [openError, setOpenError] = React.useState(false);
@ -149,6 +201,12 @@ export default function Users(props) {
} }
const [users, setUsers] = React.useState([]); const [users, setUsers] = React.useState([]);
const [dialogOpen, setDialogOpen] = React.useState(false);
const handleDialogClose = () => { setDialogOpen(false); };
const [dialogOnConfirm, setDialogOnConfirm] = React.useState(() => {return function(){}});
g_setDialogCallback = (callback) => { setDialogOnConfirm(()=>{ return callback })};
g_setDialogOpen = (value) => { setDialogOpen(value); };
function getUsers() { function getUsers() {
fetch(process.env.REACT_APP_API_URL + `/users/getUsers`) fetch(process.env.REACT_APP_API_URL + `/users/getUsers`)
.then((res) => res.json()) .then((res) => res.json())
@ -186,6 +244,13 @@ export default function Users(props) {
</div> </div>
<ErrorSnackbar message={errorMessage} open={openError} setOpen={setOpenError} /> <ErrorSnackbar message={errorMessage} open={openError} setOpen={setOpenError} />
<ConfirmationDialogRaw
id="confirmation-dialog"
keepMounted
open={dialogOpen}
onClose={handleDialogClose}
handleconfirm={dialogOnConfirm}
/>
</> </>
); );
} }

View File

@ -182,7 +182,7 @@ function Home(props) {
<> <>
<Appbar user={props.user} pageTitle="Asura Tournaments" /> <Appbar user={props.user} pageTitle="Asura Tournaments" />
<Container sx={{minHeight: "30vh", width: "90vw", padding: "20px 20px"}} component={Container} direction="column" align="center"> <Container sx={{minHeight: "30vh", width: "90vw", padding: "20px 20px"}} component={Container} direction="column" align="center">
<Box component={Stack} direction="row" align="center" justifyContent="space-between" alignItems="center" sx={{flexGrow: 1}}> <Box component={Stack} direction={['column','row']}sx={{align:'center', justifyContent:'space-between', flexGrow:1}}>
<Typography sx={{fontSize:['1.5rem','2rem','2rem']}}>Tournaments</Typography> <Typography sx={{fontSize:['1.5rem','2rem','2rem']}}>Tournaments</Typography>
{ props.user.isLoggedIn ? { props.user.isLoggedIn ?
<CreateButton /> : null <CreateButton /> : null

View File

@ -219,8 +219,13 @@ export default function TournamentManager(props) {
const handleDialogClickListItem = () => { setDialogOpen(true); }; const handleDialogClickListItem = () => { setDialogOpen(true); };
const handleDialogClose = () => { setDialogOpen(false); }; const handleDialogClose = () => { setDialogOpen(false); };
showError = props.showError; const [openError, setOpenError] = React.useState(false);
showSuccess = props.showSuccess; const [errorMessage, setErrorMessage] = React.useState("");
showError = (message) => {
setOpenError(false);
setErrorMessage(message);
setOpenError(true);
}
if (!props.user.isLoggedIn) { return <LoginPage user={props.user} />; } if (!props.user.isLoggedIn) { return <LoginPage user={props.user} />; }
@ -229,7 +234,7 @@ export default function TournamentManager(props) {
<Appbar user={props.user} pageTitle="Edit Tournament" /> <Appbar user={props.user} pageTitle="Edit Tournament" />
<TournamentBar pageTitle="Edit Tournament"/> <TournamentBar pageTitle="Edit Tournament"/>
<Paper sx={{minHeight: "30vh", width: "90vw", margin: "20px auto", padding: "20px 0"}} component={Container} direction="column" align="center"> <Paper sx={{minHeight: "30vh", width: "90vw", margin: "20px auto", padding: "20px 0"}} component={Container} direction="column" align="center">
<ManageTournament tournamentId={tournamentId} showError={showError} /> <ManageTournament tournamentId={tournamentId} />
{/* <AnnounceButton /> */} {/* <AnnounceButton /> */}
<Box sx={{width: "100%"}}> <Box sx={{width: "100%"}}>
<Button variant="contained" color="error" onClick={handleDialogClickListItem} sx={{margin: "auto 5px"}} endIcon={<DeleteIcon />}> <Button variant="contained" color="error" onClick={handleDialogClickListItem} sx={{margin: "auto 5px"}} endIcon={<DeleteIcon />}>

View File

@ -1,19 +0,0 @@
import * as React from "react";
import { BrowserRouter as Router, Link, Route, Routes } from "react-router-dom";
import Appbar from './components/AsuraBar';
function MatchHistory() {
return(
`Hei på deg din gamle sei`
);
}
export default function TournamentMatches(props) {
return (
<>
<Appbar user={props.user} />
<MatchHistory />
</>
);
}

View File

@ -16,7 +16,7 @@ function TournamentTier(props){
let roundTypes = ["finals", "semifinals", "quarterfinals", "eighthfinals", "sixteenthfinals", "thirtysecondfinals"]; let roundTypes = ["finals", "semifinals", "quarterfinals", "eighthfinals", "sixteenthfinals", "thirtysecondfinals"];
let matches = []; let matches = [];
for (let i = 0; i < props.matches.length; i++) { for (let i = 0; i < props.matches.length; i++) {
matches.push(<Match tournament={props.tournament} user={props.user} tier={props.tier} roundTypes={roundTypes} teams={props.teams} match={props.matches[i]} key={i} />); matches.push(<Match tournament={props.tournament} user={props.user} tier={props.tier} roundTypes={roundTypes} teams={props.teams} match={props.matches[i]} key={i} onwinnerchange={props.onwinnerchange} />);
} }
return( return(
<> <>
@ -40,7 +40,6 @@ function Match(props){
let setWinner = curryTeamId => event => { let setWinner = curryTeamId => event => {
let teamId = curryTeamId; let teamId = curryTeamId;
// console.log(teamId)
if (!teamId || teamId == null) { if (!teamId || teamId == null) {
showError("No team selected"); showError("No team selected");
return; return;
@ -56,7 +55,7 @@ function Match(props){
.then(data => { .then(data => {
if (data.status === "OK") { if (data.status === "OK") {
//Refresh when winner is set successfully //Refresh when winner is set successfully
window.location.reload(); props.onwinnerchange();
} else { } else {
showError(data.data) showError(data.data)
} }
@ -65,11 +64,9 @@ function Match(props){
}; };
let curryUnsetContestant = teamId => (e) => { let curryUnsetContestant = teamId => (e) => {
// console.log("wack")
let formData = new FormData(); let formData = new FormData();
formData.append("teamId", teamId); formData.append("teamId", teamId);
let body = new URLSearchParams(formData); let body = new URLSearchParams(formData);
// console.log(props.match)
fetch(process.env.REACT_APP_API_URL + `/match/${props.match.id}/unsetContestant`, { fetch(process.env.REACT_APP_API_URL + `/match/${props.match.id}/unsetContestant`, {
method: "POST", method: "POST",
body: body body: body
@ -77,8 +74,9 @@ function Match(props){
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.status === "OK") { if (data.status === "OK") {
// console.log("wacky smacky"); props.onwinnerchange()
window.location.reload(); } else {
showError(data.data);
} }
}) })
.catch(error => showError(error)); .catch(error => showError(error));
@ -126,12 +124,53 @@ function Match(props){
); );
} }
function WinnerDisplay(props) {
let unsetWinner = event => {
let formData = new FormData();
formData.append("winnerId","null");
let body = new URLSearchParams(formData);
fetch(process.env.REACT_APP_API_URL + `/match/${props.finalMatch.id}/setWinner`, {
method: "POST",
body: body
})
.then(response => response.json())
.then(data => {
if (data.status !== "OK") { showError(data.data); return;}
props.onwinnerchange();
})
.catch(error => showError(error));
};
if (!props.team) {
// Winner is not yet chosen
return <div className="winnerDisplay">
<Typography variant="h5" component="h2">
Winner is not chosen.<br /> Will it be you?
</Typography>
</div>;
}
return (
<div className="winnerDisplay winner">
<Typography variant="h4" component="h2" align="center">
{props.user.isLoggedIn && <IconButton color="error" aria-label="remove winner" component="span" onClick={unsetWinner}><BackspaceIcon /></IconButton>}
Winner:
</Typography>
<Typography variant="h4" component="h2">
{props.team.name}<EmojiEventsIcon alt="A trohpy" />
</Typography>
</div>
)
}
function BracketViewer(props){ function BracketViewer(props){
const [matches, setMatches] = React.useState(null); const [matches, setMatches] = React.useState(null);
const [teams, setTeams] = React.useState(null); const [teams, setTeams] = React.useState(null);
React.useEffect(() => { let getMatches = () => {
fetch(process.env.REACT_APP_API_URL + `/tournament/${props.tournamentId}/getMatches`) fetch(process.env.REACT_APP_API_URL + `/tournament/${props.tournamentId}/getMatches`)
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
@ -140,20 +179,18 @@ function BracketViewer(props){
console.error(data); console.error(data);
return; return;
} }
let matches = data.data; let allMatches = data.data;
// Group all matches by their round/tier // Group all matches by their round/tier
let tiers = matches.reduce((tiers, match) => { let tiers = allMatches.reduce((tiers, match) => {
if (!tiers[match.tier]) { if (!tiers[match.tier]) {
tiers[match.tier] = []; tiers[match.tier] = [];
} }
tiers[match.tier].push(match); tiers[match.tier].push(match);
console.log(tiers)
return tiers; return tiers;
}, {}); }, {});
tiers = Object.values(tiers); tiers = Object.values(tiers);
tiers = tiers.reverse(); tiers = tiers.reverse();
setMatches(tiers); setMatches(tiers);
}) })
.catch(err => showError(err)); .catch(err => showError(err));
@ -169,19 +206,35 @@ function BracketViewer(props){
setTeams(teams); setTeams(teams);
}) })
.catch(err => showError(err)); .catch(err => showError(err));
}
React.useEffect(() => {
getMatches();
}, []); }, []);
let getFinalMatch = (tierMatches) => {
let finalMatch = tierMatches[tierMatches.length - 1][0];
return finalMatch;
};
let getWinnerTeam = (tierMatches) => {
let finalMatch = getFinalMatch(tierMatches);
if (finalMatch.winnerId === null) { return null;}
let winnerTeam = teams.find(team => team.id === finalMatch.winnerId);
return winnerTeam;
};
return ( return (
(props.tournament && matches && teams) ? (props.tournament && matches && teams) ?
// <div sx={{width: "100vw", height: "80vh", overflow: "scroll"}} className="bracket"> // <div sx={{width: "100vw", height: "80vh", overflow: "scroll"}} className="bracket">
<> <>
<div className="bracket"> <div className="bracket">
{matches.map(tier => { {matches.map(tierMatches => {
let tierNum = tier[0].tier; let tierNum = tierMatches[0].tier;
return <TournamentTier user={props.user} tournament={props.tournament} key={tierNum} tier={tierNum} matches={tier} teams={teams} /> return <TournamentTier user={props.user} tournament={props.tournament} key={tierNum} tier={tierNum} matches={tierMatches} teams={teams} onwinnerchange={getMatches} />
})} })}
</div>
<WinnerDisplay team={getWinnerTeam(matches)} user={props.user} finalMatch={getFinalMatch(matches)} onwinnerchange={getMatches} />
</div>
</> </>
: <Box sx={{display:'flex', justifyContent:'center', alignItems:'center', position:'relative', marginTop:'5%'}}><CircularProgress size={"20vw"}/></Box> : <Box sx={{display:'flex', justifyContent:'center', alignItems:'center', position:'relative', marginTop:'5%'}}><CircularProgress size={"20vw"}/></Box>
); );

View File

@ -31,10 +31,10 @@ function LoggedInMenu(props) {
<Menu anchorEl={anchorEl} open={open} onClose={handleClose} MenuListProps={{'aria-labelledby': 'basic-button',}} sx={{position:"absolute"}}> <Menu anchorEl={anchorEl} open={open} onClose={handleClose} MenuListProps={{'aria-labelledby': 'basic-button',}} sx={{position:"absolute"}}>
<Link to="/profile" style={{color:"black"}}><MenuItem onClick={handleClose}><Button startIcon={<AccountCircleIcon />}>{props.user.name}</Button></MenuItem></Link> <Link to="/profile" style={{color:"black"}}><MenuItem onClick={handleClose}><Button startIcon={<AccountCircleIcon />}>{props.user.name}</Button></MenuItem></Link>
<Link to="/history" style={{color:"black"}}><MenuItem onClick={handleClose}><Button startIcon={<HistoryIcon />}>History</Button></MenuItem></Link> <Link to="/history" style={{color:"black"}}><MenuItem onClick={handleClose}><Button startIcon={<HistoryIcon />}>History</Button></MenuItem></Link>
<Link to="/api/logout" style={{color:"black"}}><MenuItem onClick={logout}><Button startIcon={<LogoutIcon />} >Logout</Button></MenuItem></Link>
{ props.user.isManager && { props.user.isManager &&
<Link to="/admins" style={{color:"black"}}><MenuItem onClick={handleClose}><Button startIcon={<EditIcon />} >Admins</Button></MenuItem></Link> <Link to="/admins" style={{color:"black"}}><MenuItem onClick={handleClose}><Button startIcon={<EditIcon />} >Admins</Button></MenuItem></Link>
} }
<a href={`${process.env.REACT_APP_API_URL}/users/logout`} style={{color:"black"}}><MenuItem onClick={logout}><Button startIcon={<LogoutIcon />} >Logout</Button></MenuItem></a>
</Menu> </Menu>
</> </>
); );

View File

@ -3,6 +3,11 @@ import { useParams } from "react-router-dom";
import { BrowserRouter as Router, Link, Route, Routes, History } from "react-router-dom"; import { BrowserRouter as Router, Link, Route, Routes, History } from "react-router-dom";
import { Stack, Paper, Typography, Box, Button, Grid, Snackbar, IconButton } from "@mui/material" import { Stack, Paper, Typography, Box, Button, Grid, Snackbar, IconButton } from "@mui/material"
import CloseIcon from '@mui/icons-material/Close'; import CloseIcon from '@mui/icons-material/Close';
import MuiAlert from '@mui/material/Alert';
const Alert = React.forwardRef(function Alert(props, ref) {
return <MuiAlert elevation={6} ref={ref} variant="filled" {...props} />;
});
function ClipboardButton(props) { function ClipboardButton(props) {
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
@ -23,7 +28,11 @@ function ClipboardButton(props) {
return ( return (
<> <>
<Button onClick={copyString} variant="outlined" color="primary" sx={{margin: "auto 5px"}} >Copy {props.name}</Button> <Button onClick={copyString} variant="outlined" color="primary" sx={{margin: "auto 5px"}} >Copy {props.name}</Button>
<Snackbar open={open} autoHideDuration={1500} onClose={handleClose} message={props.name + " copied to clipboard"} action={closeAction} /> <Snackbar open={open} autoHideDuration={1500} onClose={handleClose} action={closeAction}>
<Alert onClose={handleClose} severity="info" sx={{ width: '100%' }}>
{props.name + " copied to clipboard"}
</Alert>
</Snackbar>
</> </>
); );
} }

View File

@ -6,7 +6,7 @@
flex-direction:row; flex-direction:row;
justify-content: center; justify-content: center;
} }
.round{ .round{
display:flex; display:flex;
flex-direction:column; flex-direction:column;
justify-content:center; justify-content:center;
@ -14,13 +14,13 @@
list-style:none; list-style:none;
padding:0; padding:0;
/* font-size: 1.5rem; */ /* font-size: 1.5rem; */
} }
.round .spacer{ flex-grow:1;} .round .spacer{ flex-grow:1;}
.round .spacer:first-child, .round .spacer:first-child,
.round .spacer:last-child{ flex-grow:.5; } .round .spacer:last-child{ flex-grow:.5; }
.round .game-spacer{ .round .game-spacer{
flex-grow:1; flex-grow:1;
} }
/* /*
* General Styles * General Styles
@ -32,26 +32,44 @@
line-height:1.4em; line-height:1.4em;
} */ } */
li.game{ li.game{
padding-left:20px; padding-left:20px;
} }
.winner{ .winner{
color:green; color:green;
font-weight: bold; font-weight: bold;
} }
.loser{ .loser{
color:grey; color:grey;
} }
li.game-top{ border-bottom:1px solid #aaa; } li.game-top{ border-bottom:1px solid #aaa; }
li.game-spacer{ li.game-spacer{
border-right:1px solid #aaa; border-right:1px solid #aaa;
min-height:10vh; min-height:10vh;
} }
li.game-bottom{ li.game-bottom{
border-top:1px solid #aaa; border-top:1px solid #aaa;
} }
.winnerDisplay {
display:flex;
flex-direction:row;
align-items: center;
border: 2px solid gray;
border-radius: 15px;
min-height: 10vh;
max-height: 40vh;
margin: auto 5px;
padding: 10px;
}
.winnerDisplay.winner {
border: 2px solid green;
}
.winnerDisplay > h2 {
margin-right: 10px;
}