-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add API for entering individual attempts (#195)
- Loading branch information
1 parent
4101973
commit 5413b4f
Showing
24 changed files
with
969 additions
and
61 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 36 additions & 0 deletions
36
client/src/components/ScoretakingTokens/ScoretakingTokenDialog.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { | ||
Grid, | ||
Typography, | ||
Dialog, | ||
DialogContent, | ||
DialogActions, | ||
Button, | ||
} from "@mui/material"; | ||
|
||
function ScoretakingTokenDialog({ token, open, onClose }) { | ||
return ( | ||
<Dialog open={open} onClose={onClose}> | ||
<DialogContent> | ||
<Grid container direction="column" spacing={2} alignItems="center"> | ||
<Grid item> | ||
<Typography color="textSecondary"> | ||
Copy and save the token, you won't be able to see it again. | ||
</Typography> | ||
</Grid> | ||
<Grid item> | ||
<Typography variant="subtitle1" align="center"> | ||
{token} | ||
</Typography> | ||
</Grid> | ||
</Grid> | ||
</DialogContent> | ||
<DialogActions> | ||
<Button color="primary" onClick={() => onClose()}> | ||
Close | ||
</Button> | ||
</DialogActions> | ||
</Dialog> | ||
); | ||
} | ||
|
||
export default ScoretakingTokenDialog; |
128 changes: 128 additions & 0 deletions
128
client/src/components/ScoretakingTokens/ScoretakingTokens.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import { useState } from "react"; | ||
import { gql, useMutation, useQuery } from "@apollo/client"; | ||
import { Button, Grid, Link, Typography } from "@mui/material"; | ||
|
||
import useApolloErrorHandler from "../../hooks/useApolloErrorHandler"; | ||
import CompetitionSearch from "../CompetitionSearch/CompetitionSearch"; | ||
import Loading from "../Loading/Loading"; | ||
import Error from "../Error/Error"; | ||
import ScoretakingTokensTable from "../ScoretakingTokensTable/ScoretakingTokensTable"; | ||
import ScoretakingTokenDialog from "./ScoretakingTokenDialog"; | ||
|
||
const ACTIVE_SCORETAKING_TOKENS_QUERY = gql` | ||
query ActiveScoretakingTokensQuery { | ||
activeScoretakingTokens { | ||
id | ||
insertedAt | ||
competition { | ||
id | ||
name | ||
} | ||
} | ||
} | ||
`; | ||
|
||
const GENERATE_SCORETAKING_TOKEN = gql` | ||
mutation GenerateScoretakingToken($input: GenerateScoretakingTokenInput!) { | ||
generateScoretakingToken(input: $input) { | ||
token | ||
} | ||
} | ||
`; | ||
|
||
function ScoretakingTokens() { | ||
const apolloErrorHandler = useApolloErrorHandler(); | ||
|
||
const [competition, setCompetition] = useState(null); | ||
const [dialogOpen, setDialogOpen] = useState(false); | ||
|
||
const { data, loading, error } = useQuery(ACTIVE_SCORETAKING_TOKENS_QUERY); | ||
|
||
const [ | ||
generateScoretakingToken, | ||
{ data: generatedTokenData, loading: mutationLoading }, | ||
] = useMutation(GENERATE_SCORETAKING_TOKEN, { | ||
variables: { | ||
input: { | ||
competitionId: competition && competition.id, | ||
}, | ||
}, | ||
onError: apolloErrorHandler, | ||
onCompleted: () => { | ||
setCompetition(null); | ||
setDialogOpen(true); | ||
}, | ||
refetchQueries: [ACTIVE_SCORETAKING_TOKENS_QUERY], | ||
}); | ||
|
||
if (loading && !data) return <Loading />; | ||
if (error) return <Error error={error} />; | ||
|
||
const { activeScoretakingTokens } = data; | ||
|
||
const token = generatedTokenData | ||
? generatedTokenData.generateScoretakingToken.token | ||
: null; | ||
|
||
return ( | ||
<> | ||
<Grid container direction="column" spacing={2}> | ||
<Grid item> | ||
<Typography variant="h5" gutterBottom> | ||
Scoretaking tokens | ||
</Typography> | ||
<Typography color="textSecondary"> | ||
Generate a personal token for a specific competition. You can use | ||
this token programmatically enter results from an external software | ||
(such as a dedicated scoretaking device). For API specifics check | ||
out{" "} | ||
<Link | ||
href="https://github.com/thewca/wca-live/wiki/Entering-attempts-with-external-devices" | ||
underline="hover" | ||
> | ||
this page | ||
</Link> | ||
. | ||
</Typography> | ||
</Grid> | ||
<Grid item> | ||
<CompetitionSearch | ||
onChange={(competition) => setCompetition(competition)} | ||
value={competition} | ||
TextFieldProps={{ | ||
placeholder: "Competition", | ||
size: "small", | ||
style: { width: 350 }, | ||
}} | ||
/> | ||
</Grid> | ||
<Grid item> | ||
<Button | ||
variant="contained" | ||
color="primary" | ||
disabled={mutationLoading || !competition} | ||
onClick={() => generateScoretakingToken()} | ||
> | ||
Generate | ||
</Button> | ||
</Grid> | ||
<Grid item> | ||
<Typography variant="subtitle2" mb={0.5}> | ||
Active tokens | ||
</Typography> | ||
<ScoretakingTokensTable | ||
scoretakingTokens={activeScoretakingTokens} | ||
activeScoretakingTokensQuery={ACTIVE_SCORETAKING_TOKENS_QUERY} | ||
/> | ||
</Grid> | ||
</Grid> | ||
<ScoretakingTokenDialog | ||
token={token} | ||
open={dialogOpen} | ||
onClose={() => setDialogOpen(false)} | ||
/> | ||
</> | ||
); | ||
} | ||
|
||
export default ScoretakingTokens; |
97 changes: 97 additions & 0 deletions
97
client/src/components/ScoretakingTokensTable/ScoretakingTokensTable.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import { gql, useMutation } from "@apollo/client"; | ||
import { | ||
Table, | ||
TableBody, | ||
TableCell, | ||
TableHead, | ||
TableRow, | ||
TableContainer, | ||
Paper, | ||
IconButton, | ||
} from "@mui/material"; | ||
import DeleteIcon from "@mui/icons-material/Delete"; | ||
import TimeAgo from "react-timeago"; | ||
import { parseISO } from "date-fns"; | ||
import { useConfirm } from "material-ui-confirm"; | ||
|
||
import { orderBy } from "../../lib/utils"; | ||
import useApolloErrorHandler from "../../hooks/useApolloErrorHandler"; | ||
|
||
const DELETE_SCORETAKING_TOKEN_MUTATION = gql` | ||
mutation DeleteScoretakingToken($input: DeleteScoretakingTokenInput!) { | ||
deleteScoretakingToken(input: $input) { | ||
scoretakingToken { | ||
id | ||
} | ||
} | ||
} | ||
`; | ||
|
||
function ScoretakingTokensTable({ | ||
scoretakingTokens, | ||
activeScoretakingTokensQuery, | ||
}) { | ||
const confirm = useConfirm(); | ||
const apolloErrorHandler = useApolloErrorHandler(); | ||
|
||
const [deleteScoretakingToken, { loading }] = useMutation( | ||
DELETE_SCORETAKING_TOKEN_MUTATION, | ||
{ | ||
onError: apolloErrorHandler, | ||
refetchQueries: [activeScoretakingTokensQuery], | ||
} | ||
); | ||
|
||
function handleDelete(scoretakingToken) { | ||
confirm({ | ||
description: ` | ||
This will remove the scoretaking token for ${scoretakingToken.competition.name}. | ||
`, | ||
}).then(() => { | ||
deleteScoretakingToken({ | ||
variables: { input: { id: scoretakingToken.id } }, | ||
}); | ||
}); | ||
} | ||
|
||
const sortedScoretakingTokens = orderBy( | ||
scoretakingTokens, | ||
(token) => [token.insertedAt], | ||
["desc"] | ||
); | ||
|
||
return ( | ||
<TableContainer component={Paper}> | ||
<Table size="small"> | ||
<TableHead> | ||
<TableRow> | ||
<TableCell>Competition</TableCell> | ||
<TableCell>Created</TableCell> | ||
<TableCell></TableCell> | ||
</TableRow> | ||
</TableHead> | ||
<TableBody> | ||
{sortedScoretakingTokens.map((scoretakingToken) => ( | ||
<TableRow key={scoretakingToken.id}> | ||
<TableCell>{scoretakingToken.competition.name}</TableCell> | ||
<TableCell> | ||
<TimeAgo date={parseISO(scoretakingToken.insertedAt)} /> | ||
</TableCell> | ||
<TableCell align="right"> | ||
<IconButton | ||
onClick={() => handleDelete(scoretakingToken)} | ||
size="small" | ||
disabled={loading} | ||
> | ||
<DeleteIcon /> | ||
</IconButton> | ||
</TableCell> | ||
</TableRow> | ||
))} | ||
</TableBody> | ||
</Table> | ||
</TableContainer> | ||
); | ||
} | ||
|
||
export default ScoretakingTokensTable; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.