mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-10-01 07:37:55 -05:00
map votes progress
This commit is contained in:
214
frontend-react/src/components/plus/MapVoteGrid.tsx
Normal file
214
frontend-react/src/components/plus/MapVoteGrid.tsx
Normal 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
|
||||
51
frontend-react/src/components/plus/MapVoting.tsx
Normal file
51
frontend-react/src/components/plus/MapVoting.tsx
Normal 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
|
||||
@@ -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>
|
||||
|
||||
23
frontend-react/src/graphql/queries/mapVotes.ts
Normal file
23
frontend-react/src/graphql/queries/mapVotes.ts
Normal 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
|
||||
}
|
||||
}
|
||||
`
|
||||
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user