diff --git a/frontend-react/src/components/plus/MapVoteGrid.tsx b/frontend-react/src/components/plus/MapVoteGrid.tsx new file mode 100644 index 000000000..31d7aba77 --- /dev/null +++ b/frontend-react/src/components/plus/MapVoteGrid.tsx @@ -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 = ({ value, handleClick }) => { + return ( + + 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} + + + ) +} + +const MapVoteGrid: React.FC = ({ 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 ( + + + + SZ + + + TC + + + RM + + + CB + + {votes.map((stage, index) => { + return ( + + + + + {stage.name} + + + + handleClick(index, "sz", oldValue) + } + /> + + handleClick(index, "tc", oldValue) + } + /> + + handleClick(index, "rm", oldValue) + } + /> + + handleClick(index, "cb", oldValue) + } + /> + + ) + })} + + + SZ + + + TC + + + RM + + + CB + + + ) +} + +export default MapVoteGrid diff --git a/frontend-react/src/components/plus/MapVoting.tsx b/frontend-react/src/components/plus/MapVoting.tsx new file mode 100644 index 000000000..27b0eadee --- /dev/null +++ b/frontend-react/src/components/plus/MapVoting.tsx @@ -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 = ({}) => { + const { data, error, loading } = useQuery(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 + if (loading) return + + return ( + <> + + Plus Server Map Voting | sendou.ink + + + + + + + + ) +} + +export default MapVoting diff --git a/frontend-react/src/components/root/Routes.tsx b/frontend-react/src/components/root/Routes.tsx index 5f2d3e9ad..472090ed1 100644 --- a/frontend-react/src/components/root/Routes.tsx +++ b/frontend-react/src/components/root/Routes.tsx @@ -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 = () => { + diff --git a/frontend-react/src/graphql/queries/mapVotes.ts b/frontend-react/src/graphql/queries/mapVotes.ts new file mode 100644 index 000000000..222c473ef --- /dev/null +++ b/frontend-react/src/graphql/queries/mapVotes.ts @@ -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 + } + } +` diff --git a/graphql-schemas/maps.js b/graphql-schemas/maps.js index 29cd2a39f..ecd76dd25 100644 --- a/graphql-schemas/maps.js +++ b/graphql-schemas/maps.js @@ -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 = {