map votes progress

This commit is contained in:
Sendou
2020-03-13 02:16:22 +02:00
parent 7791638004
commit 8b6ade5d97
5 changed files with 340 additions and 1 deletions

View File

@@ -0,0 +1,214 @@
import React, { useContext } from "react"
import { Grid, Box, Avatar, Flex } from "@chakra-ui/core"
import { mapIcons } from "../../assets/imageImports"
import MyThemeContext from "../../themeContext"
interface MapVoteGridProps {
votes: {
name: string
sz: -1 | 0 | 1
tc: -1 | 0 | 1
rm: -1 | 0 | 1
cb: -1 | 0 | 1
}[]
setVotes: React.Dispatch<
React.SetStateAction<
{
name: string
sz: 0 | 1 | -1
tc: 0 | 1 | -1
rm: 0 | 1 | -1
cb: 0 | 1 | -1
}[]
>
>
}
const getBg = (value: number) => {
if (value === 0) {
return "yellow.500"
} else if (value === 1) {
return "green.500"
}
return "red.500"
}
interface VotingButtonProps {
value: 1 | 0 | -1
handleClick: (oldValue: number) => void
}
const VotingButton: React.FC<VotingButtonProps> = ({ value, handleClick }) => {
return (
<Flex alignItems="center">
<Flex
onClick={() => handleClick(value)}
alignItems="center"
justifyContent="center"
borderRadius="50%"
w="50px"
h="50px"
fontWeight="bolder"
border="4px solid"
borderColor={getBg(value)}
fontSize="24px"
cursor="pointer"
userSelect="none"
>
{value > 0 ? "+" : ""}
{value}
</Flex>
</Flex>
)
}
const MapVoteGrid: React.FC<MapVoteGridProps> = ({ votes, setVotes }) => {
const { grayWithShade } = useContext(MyThemeContext)
const handleClick = (
stageIndex: number,
mode: "sz" | "tc" | "rm" | "cb",
oldValue: number
) => {
let newValue = oldValue + 1
if (newValue > 1) {
newValue = -1
}
const copyOfVotes = [...votes]
copyOfVotes[stageIndex][mode] = newValue as -1 | 0 | 1
setVotes(copyOfVotes)
}
return (
<Grid
gridTemplateColumns="repeat(5, 1fr)"
gridGap="2em"
maxW="500px"
mt="2em"
>
<Box />
<Flex
justifyContent="center"
color={grayWithShade}
fontWeight="semibold"
letterSpacing="wide"
fontSize="lg"
>
SZ
</Flex>
<Flex
justifyContent="center"
color={grayWithShade}
fontWeight="semibold"
letterSpacing="wide"
fontSize="lg"
>
TC
</Flex>
<Flex
justifyContent="center"
color={grayWithShade}
fontWeight="semibold"
letterSpacing="wide"
fontSize="lg"
>
RM
</Flex>
<Flex
justifyContent="center"
color={grayWithShade}
fontWeight="semibold"
letterSpacing="wide"
fontSize="lg"
>
CB
</Flex>
{votes.map((stage, index) => {
return (
<React.Fragment key={stage.name}>
<Flex alignItems="center">
<Avatar
src={mapIcons[stage.name]}
size="lg"
my="5px"
mr="0.5em"
/>
<Box
color={grayWithShade}
fontWeight="semibold"
letterSpacing="wide"
mx="0.5em"
>
{stage.name}
</Box>
</Flex>
<VotingButton
value={stage.sz}
handleClick={(oldValue: number) =>
handleClick(index, "sz", oldValue)
}
/>
<VotingButton
value={stage.tc}
handleClick={(oldValue: number) =>
handleClick(index, "tc", oldValue)
}
/>
<VotingButton
value={stage.rm}
handleClick={(oldValue: number) =>
handleClick(index, "rm", oldValue)
}
/>
<VotingButton
value={stage.cb}
handleClick={(oldValue: number) =>
handleClick(index, "cb", oldValue)
}
/>
</React.Fragment>
)
})}
<Box />
<Flex
justifyContent="center"
color={grayWithShade}
fontWeight="semibold"
letterSpacing="wide"
fontSize="lg"
>
SZ
</Flex>
<Flex
justifyContent="center"
color={grayWithShade}
fontWeight="semibold"
letterSpacing="wide"
fontSize="lg"
>
TC
</Flex>
<Flex
justifyContent="center"
color={grayWithShade}
fontWeight="semibold"
letterSpacing="wide"
fontSize="lg"
>
RM
</Flex>
<Flex
justifyContent="center"
color={grayWithShade}
fontWeight="semibold"
letterSpacing="wide"
fontSize="lg"
>
CB
</Flex>
</Grid>
)
}
export default MapVoteGrid

View File

@@ -0,0 +1,51 @@
import React, { useState, useEffect } from "react"
import { RouteComponentProps } from "@reach/router"
import { useQuery } from "@apollo/react-hooks"
import { MAP_VOTES, MapVotesData } from "../../graphql/queries/mapVotes"
import Error from "../common/Error"
import Loading from "../common/Loading"
import { Helmet } from "react-helmet-async"
import PageHeader from "../common/PageHeader"
import MapVoteGrid from "./MapVoteGrid"
import { Box } from "@chakra-ui/core"
import Button from "../elements/Button"
import { FaEnvelope } from "react-icons/fa"
const MapVoting: React.FC<RouteComponentProps> = ({}) => {
const { data, error, loading } = useQuery<MapVotesData>(MAP_VOTES)
const [votes, setVotes] = useState<
{
name: string
sz: -1 | 0 | 1
tc: -1 | 0 | 1
rm: -1 | 0 | 1
cb: -1 | 0 | 1
}[]
>([])
useEffect(() => {
if (loading || error || !data) {
return
}
setVotes(data.mapVotes)
}, [data])
if (error) return <Error errorMessage={error.message} />
if (loading) return <Loading />
return (
<>
<Helmet>
<title>Plus Server Map Voting | sendou.ink</title>
</Helmet>
<PageHeader title="Map Voting" />
<MapVoteGrid votes={votes} setVotes={setVotes} />
<Box>
<Button icon={FaEnvelope}>Submit</Button>
</Box>
</>
)
}
export default MapVoting

View File

@@ -18,6 +18,7 @@ const TeamPage = lazy(() => import("../team/TeamPage"))
const XSearch = lazy(() => import("../xsearch/Top500BrowserPage"))
const PlusPage = lazy(() => import("../plus/PlusPage"))
const VotingHistoryPage = lazy(() => import("../plus/VotingHistoryPage"))
const MapVoting = lazy(() => import("../plus/MapVoting"))
const About = lazy(() => import("./About"))
const Links = lazy(() => import("./Links"))
@@ -40,6 +41,7 @@ const Routes: React.FC = () => {
<Links path="/links" />
<PlusPage path="/plus" />
<VotingHistoryPage path="/plus/history" />
<MapVoting path="/plus/mapvoting" />
<NotFound default />
</ScrollToTop>
</Router>

View File

@@ -0,0 +1,23 @@
import { gql, DocumentNode } from "apollo-boost"
export interface MapVotesData {
mapVotes: {
name: string
sz: -1 | 0 | 1
tc: -1 | 0 | 1
rm: -1 | 0 | 1
cb: -1 | 0 | 1
}[]
}
export const MAP_VOTES: DocumentNode = gql`
{
mapVotes {
name
sz
tc
rm
cb
}
}
`

View File

@@ -1,4 +1,8 @@
const { UserInputError, gql } = require("apollo-server-express")
const {
UserInputError,
AuthenticationError,
gql,
} = require("apollo-server-express")
const Maplist = require("../mongoose-models/maplist")
const MapBallot = require("../mongoose-models/mapballot")
const maps = require("../utils/maps")
@@ -9,6 +13,10 @@ const typeDef = gql`
mapVotes: [MapVote!]
}
extend type Mutation {
addMapVotes(votes: [MapVote!]!): boolean
}
type Maplist {
name: String!
sz: [String!]!
@@ -58,6 +66,47 @@ const resolvers = {
return mapBallot.maps
},
},
Mutation: {
addMapVotes: (root, args, ctx) => {
if (!ctx.user || !ctx.user.plus || !ctx.user.plus.membership_status) {
throw new AuthenticationError("Insufficient access")
}
const legitVotes = [-1, 0, 1]
args.votes.forEach((vote, index) => {
if (vote.name !== maps[index]) {
throw new UserInputError(
`Invalid map or position: ${vote.name} on the index ${index}`
)
}
if (legitVotes.indexOf(vote.sz) === -1) {
throw new UserInputError(`Invalid vote for sz: ${vote.sz}`)
}
if (legitVotes.indexOf(vote.tc) === -1) {
throw new UserInputError(`Invalid vote for sz: ${vote.tc}`)
}
if (legitVotes.indexOf(vote.rm) === -1) {
throw new UserInputError(`Invalid vote for sz: ${vote.rm}`)
}
if (legitVotes.indexOf(vote.cb) === -1) {
throw new UserInputError(`Invalid vote for sz: ${vote.cb}`)
}
const discord_id = ctx.user.discord_id
MapBallot.findOneAndUpdate(
{ discord_id },
{ discord_id, maps: args.votes },
{ upsert: true }
)
})
},
},
}
module.exports = {