result form

This commit is contained in:
Sendou
2020-02-14 19:31:04 +02:00
parent 57cb5c3330
commit cf5b43b1b9
13 changed files with 127 additions and 48 deletions

View File

@@ -1207,9 +1207,9 @@
"integrity": "sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA=="
},
"@hapi/hoek": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.0.tgz",
"integrity": "sha512-7XYT10CZfPsH7j9F1Jmg1+d0ezOux2oM2GfArAzLwWe4mE2Dr3hVjsAL6+TFY49RRJlCdJDMw3nJsLFroTc8Kw=="
"version": "8.5.1",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz",
"integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow=="
},
"@hapi/joi": {
"version": "15.1.1",
@@ -1969,9 +1969,9 @@
}
},
"@types/history": {
"version": "4.7.4",
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.4.tgz",
"integrity": "sha512-+o2igcuZA3xtOoFH56s+MCZVidwlJNcJID57DSCyawS2i910yG9vkwehCjJNZ6ImhCR5S9DbvIJKyYHcMyOfMw=="
"version": "4.7.5",
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.5.tgz",
"integrity": "sha512-wLD/Aq2VggCJXSjxEwrMafIP51Z+13H78nXIX0ABEuIGhmB5sNGbR113MOKo+yfw+RDo1ZU3DM6yfnnRF/+ouw=="
},
"@types/istanbul-lib-coverage": {
"version": "2.0.1",
@@ -2112,9 +2112,9 @@
"integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA=="
},
"@types/node": {
"version": "13.7.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.7.0.tgz",
"integrity": "sha512-GnZbirvmqZUzMgkFn70c74OQpTTUcCzlhQliTzYjQMqg+hVKcDnxdL19Ne3UdYzdMA/+W3eb646FWn/ZaT1NfQ=="
"version": "13.7.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.7.1.tgz",
"integrity": "sha512-Zq8gcQGmn4txQEJeiXo/KiLpon8TzAl0kmKH4zdWctPj05nWwp1ClMdAVEloqrQKfaC48PNLdgN/aVaLqUrluA=="
},
"@types/parse-json": {
"version": "4.0.0",
@@ -2132,9 +2132,9 @@
"integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw=="
},
"@types/reach__router": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@types/reach__router/-/reach__router-1.2.6.tgz",
"integrity": "sha512-Oh5DAVr/L2svBvubw6QEFpXGu295Y406BPs4i9t1n2pp7M+q3pmCmhzb9oZV5wncR41KCD3NHl1Yhi7uKnTPsA==",
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@types/reach__router/-/reach__router-1.3.0.tgz",
"integrity": "sha512-0aL79bFPJzJOJOOMZm2301ErQVaveBdpW88uuavXymUlcYIAOCmI1ujJ2XLH6Mzn76O94eQCHIl1FDzNNKJCYA==",
"requires": {
"@types/history": "*",
"@types/react": "*"
@@ -11786,9 +11786,9 @@
}
},
"react-datepicker": {
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-2.12.0.tgz",
"integrity": "sha512-LlFK36MyQXLSMGB+DiZvPmVw+P1IeCvn2eX3bU1P32T6i8o7+b8LrUVYV9o7slXZMhqOjgTq2vYiXvdRpN/dlQ==",
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-2.12.1.tgz",
"integrity": "sha512-Y5xIim/tzHZ0M+bo6EAZZX5KZ7ov+N6VceSM981bS9dNDoD+ezUAT1/DpeSRXxmFBrNnd5dW4BhlgE36ZzkfLQ==",
"requires": {
"classnames": "^2.2.6",
"date-fns": "^2.0.1",

View File

@@ -13,8 +13,8 @@
"@testing-library/react": "^9.4.0",
"@testing-library/user-event": "^8.1.0",
"@types/jest": "^25.1.2",
"@types/node": "^13.7.0",
"@types/reach__router": "^1.2.6",
"@types/node": "^13.7.1",
"@types/reach__router": "^1.3.0",
"@types/react": "^16.9.19",
"@types/react-datepicker": "^2.11.0",
"@types/react-dom": "^16.9.5",
@@ -26,7 +26,7 @@
"jstz": "^2.1.1",
"node-sass": "^4.13.1",
"react": "^16.12.0",
"react-datepicker": "^2.12.0",
"react-datepicker": "^2.12.1",
"react-dom": "^16.12.0",
"react-draggable": "^4.2.0",
"react-helmet-async": "^1.0.4",

View File

@@ -1,4 +1,4 @@
import React from "react"
import React, { forwardRef } from "react"
import HackerOneDatePicker from "react-datepicker"
import Button from "./Button"
import "react-datepicker/src/stylesheets/datepicker.scss"
@@ -13,15 +13,20 @@ interface CustomInputProps {
onClick?: () => void
}
const CustomInput: React.FC<CustomInputProps> = ({ value, onClick }) => (
<Button onClick={onClick as () => void}>{value as string}</Button>
)
const DatePicker: React.FC<DatePickerProps> = ({ date, setDate }) => {
const CustomInput: React.FC<CustomInputProps> = forwardRef(
({ onClick, value }, ref) => (
<Button onClick={onClick as () => void}>{value as string}</Button>
)
)
return (
<HackerOneDatePicker
selected={date}
onChange={date => setDate(date)}
onChange={date => {
date?.setUTCHours(0, 0, 0, 0)
setDate(date)
}}
customInput={<CustomInput />}
/>
)

View File

@@ -1,4 +1,4 @@
import React, { useState } from "react"
import React, { useState, useEffect } from "react"
import Modal from "../elements/Modal"
import Box from "../elements/Box"
import { TournamentResult } from "../../types"
@@ -7,22 +7,27 @@ import { useMutation } from "@apollo/react-hooks"
import { useToast } from "@chakra-ui/core"
import { ADD_RESULT } from "../../graphql/mutations/addResult"
import TweetEmbed from "react-tweet-embed"
import { useContext } from "react"
import MyThemeContext from "../../themeContext"
import DatePicker from "../elements/DatePicker"
import Label from "../elements/Label"
import PlacementInput from "./PlacementInput"
import Button from "../elements/Button"
interface AddResultModalProps {
closeModal: () => void
}
const AddResultModal: React.FC<AddResultModalProps> = ({ closeModal }) => {
const { colorMode } = useContext(MyThemeContext)
const [result, setResult] = useState<Partial<TournamentResult>>({})
const [result, setResult] = useState<Partial<TournamentResult>>({
placement: 1,
date: (new Date() as unknown) as string,
})
const toast = useToast()
const [addResult] = useMutation<boolean, TournamentResult>(ADD_RESULT, {
variables: result as TournamentResult,
variables: {
...(result as TournamentResult),
date: result.date?.toString() as string,
},
onCompleted: () => {
closeModal()
toast({
@@ -37,15 +42,30 @@ const AddResultModal: React.FC<AddResultModalProps> = ({ closeModal }) => {
title: "An error occurred",
description: error.message,
position: "top-right",
status: "success",
status: "error",
duration: 10000,
})
},
refetchQueries: ["searchForUser"],
refetchQueries: ["searchForTeam"],
})
const [error, setError] = useState<string | null>(null)
useEffect(() => {
if (!result.tournament_name) {
setError("Tournament name is a required field")
} else if (result.tournament_name.length > 100) {
setError("Tournament name has to be 100 or less characters")
} else if (result.tweet_id && isNaN(result.tweet_id as any)) {
setError("Invalid Tweet ID")
} else if (result.placement && result.placement > 500) {
setError("Placement has to be between 1 and 500.")
} else {
setError(null)
}
}, [result])
const handleChange = (newValueObject: Partial<TournamentResult>) => {
console.log("newV", newValueObject)
setResult({ ...result, ...newValueObject })
}
@@ -83,6 +103,27 @@ const AddResultModal: React.FC<AddResultModalProps> = ({ closeModal }) => {
</Box>
)}
</Box>
<Box mt="1em">
<PlacementInput
value={result.placement}
onChange={value => handleChange({ placement: value })}
/>
</Box>
<Box mt="1em">
<Button disabled={!!error} onClick={() => addResult()}>
Submit
</Button>
<Box as="span" ml="0.5em">
<Button outlined onClick={() => closeModal()}>
Cancel
</Button>
</Box>
</Box>
{error && (
<Box mt="0.5em" color="red.500">
{error}
</Box>
)}
</Modal>
)
}

View File

@@ -0,0 +1,33 @@
import React, { useContext } from "react"
import Label from "../elements/Label"
import {
NumberInput,
NumberInputField,
NumberInputStepper,
NumberIncrementStepper,
NumberDecrementStepper,
} from "@chakra-ui/core"
import MyThemeContext from "../../themeContext"
interface PlacementInputProps {
value?: number
onChange: (value: number) => void
}
const PlacementInput: React.FC<PlacementInputProps> = ({ value, onChange }) => {
const { themeColorHex } = useContext(MyThemeContext)
return (
<>
<Label>Placement</Label>
<NumberInput size="lg" defaultValue={1} min={1} max={500}>
<NumberInputField focusBorderColor={themeColorHex} />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
</>
)
}
export default PlacementInput

View File

@@ -128,7 +128,7 @@ const BuildFormModal: React.FC<BuildFormModalProps> = ({
title: "An error occurred",
description: error.message,
position: "top-right",
status: "success",
status: "error",
duration: 10000,
})
},

View File

@@ -79,7 +79,7 @@ const ProfileModal: React.FC<ProfileModalProps> = ({
title: "An error occurred",
description: error.message,
position: "top-right",
status: "success",
status: "error",
duration: 10000,
})
},

View File

@@ -7,7 +7,7 @@ export const ADD_RESULT: DocumentNode = gql`
$tournament_name: String!
$placement: Int!
) {
updateUser(
addResult(
date: $date
tweet_id: $tweet_id
tournament_name: $tournament_name

View File

@@ -87,12 +87,11 @@ const resolvers = {
throw new UserInputError("Must be logged in")
}
if (user.team.captain_discord_id !== user.discord_id) {
//??
throw new UserInputError("Must be a captain to add a result")
}
const team = await Team.findById(user.team)
if (user.team.tournament_results.length >= 100) {
if (!team) throw new UserInputError("Not a team captain")
if (team.tournament_results.length > 100) {
throw new UserInputError("Can't have more than 100 tournament results")
}
@@ -100,7 +99,7 @@ const resolvers = {
throw new UserInputError("Invalid date")
}
if (args.tweet_id && !isNaN(args.tweet_id)) {
if (args.tweet_id && isNaN(args.tweet_id)) {
throw new UserInputError("Tweet ID can only contain numbers")
}
@@ -117,8 +116,7 @@ const resolvers = {
throw new UserInputError("Placement has to be between 1 and 500")
}
const team = await Team.findById(user.team)
team.push({
team.tournament_results.push({
date: args.date,
tournament_name: args.tournament_name,
placement: args.placement,

View File

@@ -80,6 +80,7 @@ const resolvers = {
if (process.env.LOGGED_IN) {
return {
id: "5cee8f73d1120d4315c55011",
team: "5e3ecc0ea7dd3830303eaaa7",
discord_id: "79237403620945920",
__v: 0,
avatar: "2e292c1b5d1366c24a9e4b6c1cffc700",

View File

@@ -91,6 +91,7 @@ const server = new ApolloServer({
return {
user: {
_id: "5cee8f73d1120d4315c55011",
team: "5e3ecc0ea7dd3830303eaaa7",
discord_id: "79237403620945920",
__v: 0,
avatar: "2e292c1b5d1366c24a9e4b6c1cffc700",

6
package-lock.json generated
View File

@@ -1656,9 +1656,9 @@
}
},
"mongoose": {
"version": "5.8.11",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.8.11.tgz",
"integrity": "sha512-Yz0leNEJsAtNtMTxTDEadacLWt58gaVeBVL3c1Z3vaBoc159aJqlf+T8jaL9mAdBxKndF5YWhh6Q719xac7cjA==",
"version": "5.8.12",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.8.12.tgz",
"integrity": "sha512-PM6Ks4kIcsLiHcqskVaLj5vDE9l2PoW5foecIuUb5vIG22Gf2yCps31+U3I7Rw8UPnrnbmZvvrRgkHmZR2/Kbw==",
"requires": {
"bson": "~1.1.1",
"kareem": "2.3.1",

View File

@@ -26,7 +26,7 @@
"express-session": "^1.17.0",
"graphql": "^14.6.0",
"lodash": "^4.17.15",
"mongoose": "^5.8.11",
"mongoose": "^5.8.12",
"mongoose-unique-validator": "^2.0.3",
"node-fetch": "^2.6.0",
"passport": "^0.4.1",