mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-10 21:26:08 -05:00
voting frontend
This commit is contained in:
32
frontend-react/src/components/common/SubHeader.css
Normal file
32
frontend-react/src/components/common/SubHeader.css
Normal file
@@ -0,0 +1,32 @@
|
||||
/*https://stackoverflow.com/a/23155413*/
|
||||
|
||||
:root {
|
||||
--sub-header-border: white;
|
||||
}
|
||||
|
||||
.decorated {
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
.decorated > span {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
.decorated > span:before,
|
||||
.decorated > span:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
border-bottom: 4px solid;
|
||||
border-color: var(--sub-header-border);
|
||||
width: 592px; /* half of limiter */
|
||||
margin: 0 20px;
|
||||
}
|
||||
.decorated > span:before {
|
||||
right: 100%;
|
||||
}
|
||||
.decorated > span:after {
|
||||
left: 100%;
|
||||
}
|
||||
25
frontend-react/src/components/common/SubHeader.tsx
Normal file
25
frontend-react/src/components/common/SubHeader.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import React, { useContext } from "react"
|
||||
import "./SubHeader.css"
|
||||
import MyThemeContext from "../../themeContext"
|
||||
import { Box, useTheme } from "@chakra-ui/core"
|
||||
|
||||
interface SubHeaderProps {
|
||||
children: string[] | string
|
||||
}
|
||||
|
||||
const SubHeader: React.FC<SubHeaderProps> = ({ children }) => {
|
||||
const { themeColorHex, themeColorHexLighter, colorMode } = useContext(
|
||||
MyThemeContext
|
||||
)
|
||||
const style = {
|
||||
"--sub-header-border":
|
||||
colorMode === "dark" ? themeColorHexLighter : themeColorHex,
|
||||
} as React.CSSProperties
|
||||
return (
|
||||
<h2 className="decorated">
|
||||
<span style={style}>{children}</span>
|
||||
</h2>
|
||||
)
|
||||
}
|
||||
|
||||
export default SubHeader
|
||||
151
frontend-react/src/components/plus/PersonForVoting.tsx
Normal file
151
frontend-react/src/components/plus/PersonForVoting.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
import React, { useContext } from "react"
|
||||
import { Link } from "@reach/router"
|
||||
import UserAvatar from "../common/UserAvatar"
|
||||
import { Flex, Avatar, Box, Grid } from "@chakra-ui/core"
|
||||
import MyThemeContext from "../../themeContext"
|
||||
|
||||
interface VotingButtonProps {
|
||||
value: 2 | 1 | -1 | -2
|
||||
handleClick: (oldValue: number) => void
|
||||
gridArea: string
|
||||
active: boolean
|
||||
}
|
||||
|
||||
const buttonBg = {
|
||||
"-2": "red.500",
|
||||
"-1": "red.500",
|
||||
"1": "green.500",
|
||||
"2": "green.500",
|
||||
} as const
|
||||
|
||||
const VotingButton: React.FC<VotingButtonProps> = ({
|
||||
value,
|
||||
handleClick,
|
||||
gridArea,
|
||||
active,
|
||||
}) => {
|
||||
return (
|
||||
<Flex
|
||||
flexDirection="column"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
gridArea={gridArea}
|
||||
>
|
||||
<Flex
|
||||
onClick={() => handleClick(value)}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
borderRadius="50%"
|
||||
w="50px"
|
||||
h="50px"
|
||||
fontWeight="bolder"
|
||||
border={active ? "4px solid" : undefined}
|
||||
borderColor={buttonBg[value]}
|
||||
fontSize="24px"
|
||||
cursor="pointer"
|
||||
userSelect="none"
|
||||
>
|
||||
{value > 0 ? "+" : ""}
|
||||
{value}
|
||||
</Flex>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
interface PersonForVotingProps {
|
||||
votes: Record<string, number>
|
||||
setVotes: React.Dispatch<React.SetStateAction<Record<string, number>>>
|
||||
user: {
|
||||
username: string
|
||||
discriminator: string
|
||||
twitter_name?: string | undefined
|
||||
discord_id: string
|
||||
}
|
||||
suggester?: {
|
||||
username: string
|
||||
discriminator: string
|
||||
}
|
||||
description?: string
|
||||
sameRegion?: boolean
|
||||
}
|
||||
|
||||
const PersonForVoting: React.FC<PersonForVotingProps> = ({
|
||||
votes,
|
||||
setVotes,
|
||||
user,
|
||||
suggester,
|
||||
description,
|
||||
sameRegion = true,
|
||||
}) => {
|
||||
const { grayWithShade } = useContext(MyThemeContext)
|
||||
|
||||
const handleClick = (value: number) => {
|
||||
setVotes({ ...votes, [user.discord_id]: value })
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid
|
||||
gridTemplateColumns="repeat(4, 1fr)"
|
||||
gridTemplateRows="repeat(2, 1fr)"
|
||||
my="1em"
|
||||
rounded="lg"
|
||||
overflow="hidden"
|
||||
boxShadow="0px 0px 16px 6px rgba(0,0,0,0.1)"
|
||||
p="12px"
|
||||
>
|
||||
<Flex
|
||||
gridArea="1 / 1 / 2 / 5"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
flexDirection="column"
|
||||
>
|
||||
<UserAvatar twitterName={user.twitter_name} name={user.username} />
|
||||
|
||||
<Link to={`/u/${user.discord_id}`}>
|
||||
<Box
|
||||
color={grayWithShade}
|
||||
fontWeight="semibold"
|
||||
letterSpacing="wide"
|
||||
mx="0.5em"
|
||||
>
|
||||
{user.username}#{user.discriminator}
|
||||
</Box>
|
||||
</Link>
|
||||
</Flex>
|
||||
{sameRegion ? (
|
||||
<VotingButton
|
||||
value={-2}
|
||||
handleClick={handleClick}
|
||||
gridArea="2 / 1 / 3 / 2"
|
||||
active={votes[user.discord_id] === -2}
|
||||
/>
|
||||
) : (
|
||||
<Box gridArea="2 / 2 / 3 / 3" />
|
||||
)}
|
||||
<VotingButton
|
||||
value={-1}
|
||||
handleClick={handleClick}
|
||||
gridArea="2 / 2 / 3 / 3"
|
||||
active={votes[user.discord_id] === -1}
|
||||
/>
|
||||
<VotingButton
|
||||
value={1}
|
||||
handleClick={handleClick}
|
||||
gridArea="2 / 3 / 3 / 4"
|
||||
active={votes[user.discord_id] === 1}
|
||||
/>
|
||||
{sameRegion ? (
|
||||
<VotingButton
|
||||
value={2}
|
||||
handleClick={handleClick}
|
||||
gridArea="2 / 4 / 3 / 5"
|
||||
active={votes[user.discord_id] === 2}
|
||||
/>
|
||||
) : (
|
||||
<Box gridArea="2 / 4 / 3 / 5" />
|
||||
)}
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
export default PersonForVoting
|
||||
@@ -4,7 +4,7 @@ import { useQuery } from "@apollo/react-hooks"
|
||||
import Suggestions from "./Suggestions"
|
||||
import Loading from "../common/Loading"
|
||||
import Error from "../common/Error"
|
||||
import { PLUS_INFO } from "../../graphql/queries/plusInfo"
|
||||
import { PLUS_INFO, PlusInfoData } from "../../graphql/queries/plusInfo"
|
||||
import { USER } from "../../graphql/queries/user"
|
||||
//import Voting from "./Voting"
|
||||
import { Redirect, RouteComponentProps, Link } from "@reach/router"
|
||||
@@ -18,14 +18,7 @@ import {
|
||||
PLUS_MAPLISTS,
|
||||
PlusMaplistsData,
|
||||
} from "../../graphql/queries/plusMaplists"
|
||||
|
||||
interface PlusInfoData {
|
||||
plusInfo: {
|
||||
voting_ends?: String
|
||||
voter_count: number
|
||||
eligible_voters: number
|
||||
}
|
||||
}
|
||||
import Voting from "./Voting"
|
||||
|
||||
const PlusPage: React.FC<RouteComponentProps> = () => {
|
||||
const { data, error, loading } = useQuery<PlusInfoData>(PLUS_INFO)
|
||||
@@ -49,6 +42,7 @@ const PlusPage: React.FC<RouteComponentProps> = () => {
|
||||
if (!data.plusInfo) return <Redirect to="/404" />
|
||||
|
||||
const maplist = maplistData.plusMaplists[0]
|
||||
const plusInfo = data.plusInfo
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -56,29 +50,6 @@ const PlusPage: React.FC<RouteComponentProps> = () => {
|
||||
<title>Plus Server Home | sendou.ink</title>
|
||||
</Helmet>
|
||||
<PageHeader title="Plus Server" />
|
||||
{/*data.plusInfo.voting_ends && userData.user.plus?.membership_status ? (
|
||||
<Voting
|
||||
user={userData.user}
|
||||
handleSuccess={handleSuccess}
|
||||
handleError={handleError}
|
||||
votedSoFar={data.plusInfo.voter_count}
|
||||
eligibleVoters={data.plusInfo.eligible_voters}
|
||||
votingEnds={
|
||||
data.plusInfo.voting_ends
|
||||
? parseInt(data.plusInfo.voting_ends)
|
||||
: null
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Suggestions
|
||||
user={userData.user}
|
||||
plusServer={userData.user?.plus?.membership_status}
|
||||
showSuggestionForm={showSuggestionForm}
|
||||
setShowSuggestionForm={setShowSuggestionForm}
|
||||
handleSuccess={handleSuccess}
|
||||
handleError={handleError}
|
||||
/>
|
||||
)*/}
|
||||
<Flex mb="1em" flexWrap="wrap">
|
||||
<Box mr="1em" mt="1em">
|
||||
<Link to="/plus/history">
|
||||
@@ -102,15 +73,26 @@ const PlusPage: React.FC<RouteComponentProps> = () => {
|
||||
</Link>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Maplist
|
||||
name={maplist.name}
|
||||
sz={maplist.sz}
|
||||
tc={maplist.tc}
|
||||
rm={maplist.rm}
|
||||
cb={maplist.cb}
|
||||
voterCount={maplist.plus.voter_count}
|
||||
/>
|
||||
<Suggestions user={userData.user} />
|
||||
{plusInfo.voting_ends ? (
|
||||
<Voting
|
||||
user={userData.user}
|
||||
votingEnds={parseInt(plusInfo.voting_ends)}
|
||||
votedSoFar={plusInfo.voter_count}
|
||||
eligibleVoters={plusInfo.eligible_voters}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Maplist
|
||||
name={maplist.name}
|
||||
sz={maplist.sz}
|
||||
tc={maplist.tc}
|
||||
rm={maplist.rm}
|
||||
cb={maplist.cb}
|
||||
voterCount={maplist.plus.voter_count}
|
||||
/>
|
||||
<Suggestions user={userData.user} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,215 +0,0 @@
|
||||
import React, { useState, useEffect } from "react"
|
||||
import { usersForVoting } from "../../graphql/queries/usersForVoting"
|
||||
import { useQuery, useMutation } from "@apollo/react-hooks"
|
||||
import { Grid, Message, Button, Progress } from "semantic-ui-react"
|
||||
import { Prompt } from "react-router-dom"
|
||||
|
||||
import Loading from "../common/Loading"
|
||||
import Error from "../common/Error"
|
||||
import VotingGridRow from "./VotingGridRow"
|
||||
import { addVotes } from "../../graphql/mutations/addVotes"
|
||||
|
||||
const Voting = ({
|
||||
user,
|
||||
handleSuccess,
|
||||
handleError,
|
||||
votingEnds,
|
||||
votedSoFar,
|
||||
eligibleVoters,
|
||||
}) => {
|
||||
const { data, loading, error } = useQuery(usersForVoting)
|
||||
const [votes, setVotes] = useState({})
|
||||
const [voteCount, setVoteCount] = useState(0)
|
||||
const [suggestedArrays, setSuggestedArrays] = useState(null)
|
||||
|
||||
const [addVotesMutation] = useMutation(addVotes, {
|
||||
onError: handleError,
|
||||
onCompleted: () => handleSuccess("Votes successfully recorded."),
|
||||
refetchQueries: [
|
||||
{
|
||||
query: usersForVoting,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await addVotesMutation({
|
||||
variables: {
|
||||
votes: Object.keys(votes).map(key => ({
|
||||
discord_id: key,
|
||||
score: votes[key],
|
||||
})),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (loading || error) return
|
||||
|
||||
const sameRegionSuggested = []
|
||||
const otherRegionSuggested = []
|
||||
|
||||
data.usersForVoting.suggested.forEach(suggested => {
|
||||
if (suggested.plus_region === user.plus.plus_region) {
|
||||
sameRegionSuggested.push(suggested)
|
||||
} else {
|
||||
otherRegionSuggested.push(suggested)
|
||||
}
|
||||
})
|
||||
|
||||
setSuggestedArrays({
|
||||
sameRegion: sameRegionSuggested,
|
||||
otherRegion: otherRegionSuggested,
|
||||
})
|
||||
|
||||
if (data.usersForVoting.votes) {
|
||||
const voteObj = {}
|
||||
data.usersForVoting.votes.forEach(
|
||||
vote => (voteObj[vote.discord_id] = vote.score)
|
||||
)
|
||||
setVotes(voteObj)
|
||||
setVoteCount(data.usersForVoting.votes.length)
|
||||
}
|
||||
}, [loading, error, data, user])
|
||||
|
||||
if (error) return <Error errorMessage={error.message} />
|
||||
if (loading || !suggestedArrays) return <Loading />
|
||||
const date = new Date()
|
||||
if (votingEnds < date.getTime())
|
||||
return (
|
||||
<Message>
|
||||
<Message.Header>Voting for the month is over</Message.Header>
|
||||
Results will be posted later
|
||||
</Message>
|
||||
)
|
||||
|
||||
const hoursLeft = Math.ceil((votingEnds - date.getTime()) / (1000 * 60 * 60))
|
||||
const alreadyVoted = data.usersForVoting.votes.length > 0
|
||||
|
||||
return (
|
||||
<>
|
||||
<Prompt
|
||||
when={
|
||||
voteCount > 0 &&
|
||||
voteCount <
|
||||
data.usersForVoting.users.length +
|
||||
data.usersForVoting.suggested.length
|
||||
}
|
||||
message="Are you sure you want to leave? Vote form won't be saved."
|
||||
/>
|
||||
<Message
|
||||
success={alreadyVoted}
|
||||
icon={alreadyVoted ? "check" : null}
|
||||
header={alreadyVoted ? "You have voted (editing possible)" : null}
|
||||
content={`Voting ends ${new Date(
|
||||
votingEnds
|
||||
).toLocaleString()} (${hoursLeft}~
|
||||
hours left)`}
|
||||
/>
|
||||
<Progress
|
||||
value={votedSoFar}
|
||||
total={eligibleVoters}
|
||||
progress="ratio"
|
||||
color="blue"
|
||||
>
|
||||
Voted so far
|
||||
</Progress>
|
||||
<h2 style={{ marginTop: "1em" }}>
|
||||
{user.plus.plus_region === "EU" ? "European" : "American"} players
|
||||
</h2>
|
||||
<Grid>
|
||||
{data.usersForVoting.users.map(userForVoting => {
|
||||
if (userForVoting.plus.plus_region !== user.plus.plus_region)
|
||||
return null
|
||||
return (
|
||||
<VotingGridRow
|
||||
key={userForVoting.discord_id}
|
||||
user={userForVoting}
|
||||
votes={votes}
|
||||
setVotes={setVotes}
|
||||
increaseCount={() => setVoteCount(voteCount + 1)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Grid>
|
||||
{suggestedArrays.sameRegion.length > 0 && (
|
||||
<>
|
||||
<h2 style={{ marginTop: "2em" }}>
|
||||
{user.plus.plus_region === "EU" ? "European" : "American"} players
|
||||
(suggested)
|
||||
</h2>
|
||||
<Grid>
|
||||
{suggestedArrays.sameRegion.map(suggestion => {
|
||||
return (
|
||||
<VotingGridRow
|
||||
key={suggestion.discord_user.discord_id}
|
||||
user={suggestion.discord_user}
|
||||
suggester={suggestion.suggester_discord_user}
|
||||
votes={votes}
|
||||
setVotes={setVotes}
|
||||
description={suggestion.description}
|
||||
increaseCount={() => setVoteCount(voteCount + 1)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
<h2 style={{ marginTop: "2em" }}>
|
||||
{user.plus.plus_region === "NA" ? "European" : "American"} players
|
||||
</h2>
|
||||
<Grid>
|
||||
{data.usersForVoting.users.map(userForVoting => {
|
||||
if (userForVoting.plus.plus_region === user.plus.plus_region)
|
||||
return null
|
||||
return (
|
||||
<VotingGridRow
|
||||
key={userForVoting.discord_id}
|
||||
user={userForVoting}
|
||||
votes={votes}
|
||||
setVotes={setVotes}
|
||||
sameRegion={false}
|
||||
increaseCount={() => setVoteCount(voteCount + 1)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Grid>
|
||||
{suggestedArrays.otherRegion.length > 0 && (
|
||||
<>
|
||||
<h2 style={{ marginTop: "2em" }}>
|
||||
{user.plus.plus_region === "NA" ? "European" : "American"} players
|
||||
(suggested)
|
||||
</h2>
|
||||
<Grid>
|
||||
{suggestedArrays.otherRegion.map(suggestion => {
|
||||
return (
|
||||
<VotingGridRow
|
||||
key={suggestion.discord_user.discord_id}
|
||||
user={suggestion.discord_user}
|
||||
votes={votes}
|
||||
setVotes={setVotes}
|
||||
sameRegion={false}
|
||||
increaseCount={() => setVoteCount(voteCount + 1)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
disabled={
|
||||
voteCount <
|
||||
data.usersForVoting.users.length +
|
||||
data.usersForVoting.suggested.length
|
||||
}
|
||||
positive
|
||||
style={{ marginTop: "2em" }}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Voting
|
||||
243
frontend-react/src/components/plus/Voting.tsx
Normal file
243
frontend-react/src/components/plus/Voting.tsx
Normal file
@@ -0,0 +1,243 @@
|
||||
import React, { useState, useEffect, useContext } from "react"
|
||||
import {
|
||||
USERS_FOR_VOTING,
|
||||
UsersForVotingData,
|
||||
VotingSuggested,
|
||||
} from "../../graphql/queries/usersForVoting"
|
||||
import { useQuery, useMutation } from "@apollo/react-hooks"
|
||||
|
||||
import Loading from "../common/Loading"
|
||||
import Error from "../common/Error"
|
||||
import { ADD_VOTES, AddVotesVars } from "../../graphql/mutations/addVotes"
|
||||
import { useToast, Progress, Box, Flex, Grid } from "@chakra-ui/core"
|
||||
import { UserLean } from "../../types"
|
||||
import Alert from "../elements/Alert"
|
||||
import MyThemeContext from "../../themeContext"
|
||||
import Button from "../elements/Button"
|
||||
import PersonForVoting from "./PersonForVoting"
|
||||
import SubHeader from "../common/SubHeader"
|
||||
|
||||
interface VotingProps {
|
||||
user: UserLean
|
||||
votingEnds: number
|
||||
votedSoFar: number
|
||||
eligibleVoters: number
|
||||
}
|
||||
|
||||
interface SuggestedArrays {
|
||||
sameRegion: VotingSuggested[]
|
||||
otherRegion: VotingSuggested[]
|
||||
}
|
||||
|
||||
const Voting: React.FC<VotingProps> = ({
|
||||
user,
|
||||
votingEnds,
|
||||
votedSoFar,
|
||||
eligibleVoters,
|
||||
}) => {
|
||||
const { themeColor, grayWithShade } = useContext(MyThemeContext)
|
||||
const { data, loading, error } = useQuery<UsersForVotingData>(
|
||||
USERS_FOR_VOTING
|
||||
)
|
||||
const [votes, setVotes] = useState<Record<string, number>>({})
|
||||
const [suggestedArrays, setSuggestedArrays] = useState<SuggestedArrays>({
|
||||
sameRegion: [],
|
||||
otherRegion: [],
|
||||
})
|
||||
const toast = useToast()
|
||||
|
||||
const [addVotesMutation, { loading: addVotesLoading }] = useMutation<
|
||||
boolean,
|
||||
AddVotesVars
|
||||
>(ADD_VOTES, {
|
||||
onCompleted: (data) => {
|
||||
window.scrollTo(0, 0)
|
||||
toast({
|
||||
description: `Votes submitted`,
|
||||
position: "top-right",
|
||||
status: "success",
|
||||
duration: 10000,
|
||||
})
|
||||
},
|
||||
onError: (error) => {
|
||||
toast({
|
||||
title: "An error occurred",
|
||||
description: error.message,
|
||||
position: "top-right",
|
||||
status: "error",
|
||||
duration: 10000,
|
||||
})
|
||||
},
|
||||
refetchQueries: [
|
||||
{
|
||||
query: USERS_FOR_VOTING,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await addVotesMutation({
|
||||
variables: {
|
||||
votes: Object.keys(votes).map((key) => ({
|
||||
discord_id: key,
|
||||
score: (votes as any)[key],
|
||||
})),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (loading || error) return
|
||||
|
||||
const sameRegionSuggested: VotingSuggested[] = []
|
||||
const otherRegionSuggested: VotingSuggested[] = []
|
||||
|
||||
data!.usersForVoting.suggested.forEach((suggested) => {
|
||||
if (suggested.plus_region === user.plus!.plus_region) {
|
||||
sameRegionSuggested.push(suggested)
|
||||
} else {
|
||||
otherRegionSuggested.push(suggested)
|
||||
}
|
||||
})
|
||||
|
||||
setSuggestedArrays({
|
||||
sameRegion: sameRegionSuggested,
|
||||
otherRegion: otherRegionSuggested,
|
||||
})
|
||||
|
||||
if (data!.usersForVoting.votes) {
|
||||
const voteObj: Record<string, number> = {}
|
||||
data!.usersForVoting.votes.forEach(
|
||||
(vote) => (voteObj[vote.discord_id] = vote.score)
|
||||
)
|
||||
setVotes(voteObj)
|
||||
}
|
||||
}, [loading, error, data, user])
|
||||
|
||||
if (loading) return <Loading />
|
||||
if (error) return <Error errorMessage={error.message} />
|
||||
|
||||
const date = new Date()
|
||||
if (votingEnds < date.getTime())
|
||||
return (
|
||||
<Alert status="info">
|
||||
Voting is over. Results will be posted a bit later.
|
||||
</Alert>
|
||||
)
|
||||
|
||||
const hoursLeft = Math.ceil((votingEnds - date.getTime()) / (1000 * 60 * 60))
|
||||
const alreadyVoted = data!.usersForVoting.votes.length > 0
|
||||
|
||||
const missingVotes =
|
||||
data!.usersForVoting.users.length +
|
||||
data!.usersForVoting.suggested.length -
|
||||
Object.keys(votes).length
|
||||
|
||||
return (
|
||||
<>
|
||||
<Alert status={alreadyVoted ? "success" : "info"}>{`${
|
||||
alreadyVoted ? "You have voted! " : ""
|
||||
}Voting ends ${new Date(votingEnds).toLocaleString()} (${hoursLeft}
|
||||
hours left)`}</Alert>
|
||||
<Box mt="1em" textAlign="center" color={grayWithShade}>
|
||||
<Progress
|
||||
value={(votedSoFar / eligibleVoters) * 100}
|
||||
color={themeColor}
|
||||
/>
|
||||
{votedSoFar}/{eligibleVoters} voted so far
|
||||
</Box>
|
||||
<Box mt="2em">
|
||||
<SubHeader>
|
||||
{user.plus!.plus_region === "EU" ? "European" : "American"} players
|
||||
</SubHeader>
|
||||
{data!.usersForVoting.users.map((userForVoting) => {
|
||||
if (userForVoting.plus.plus_region !== user.plus!.plus_region)
|
||||
return null
|
||||
return (
|
||||
<PersonForVoting
|
||||
key={userForVoting.discord_id}
|
||||
user={userForVoting}
|
||||
votes={votes}
|
||||
setVotes={setVotes}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Box>
|
||||
{suggestedArrays.sameRegion.length > 0 && (
|
||||
<>
|
||||
<SubHeader>
|
||||
{user.plus!.plus_region === "EU" ? "European" : "American"} players
|
||||
(suggested)
|
||||
</SubHeader>
|
||||
{suggestedArrays.sameRegion.map((suggestion) => {
|
||||
return (
|
||||
<PersonForVoting
|
||||
key={suggestion.discord_user.discord_id}
|
||||
user={suggestion.discord_user}
|
||||
suggester={suggestion.suggester_discord_user}
|
||||
votes={votes}
|
||||
setVotes={setVotes}
|
||||
description={suggestion.description}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
<SubHeader>
|
||||
{user.plus!.plus_region === "NA" ? "European" : "American"} players
|
||||
</SubHeader>
|
||||
{data!.usersForVoting.users.map((userForVoting) => {
|
||||
if (userForVoting.plus.plus_region === user.plus!.plus_region)
|
||||
return null
|
||||
return (
|
||||
<PersonForVoting
|
||||
key={userForVoting.discord_id}
|
||||
user={userForVoting}
|
||||
votes={votes}
|
||||
setVotes={setVotes}
|
||||
sameRegion={false}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
{suggestedArrays.otherRegion.length > 0 && (
|
||||
<>
|
||||
<SubHeader>
|
||||
{user.plus!.plus_region === "NA" ? "European" : "American"} players
|
||||
(suggested)
|
||||
</SubHeader>
|
||||
|
||||
{suggestedArrays.otherRegion.map((suggestion) => {
|
||||
return (
|
||||
<PersonForVoting
|
||||
key={suggestion.discord_user.discord_id}
|
||||
user={suggestion.discord_user}
|
||||
votes={votes}
|
||||
setVotes={setVotes}
|
||||
sameRegion={false}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
<Flex mt="2em" alignItems="center">
|
||||
<Box mr="1em">
|
||||
<Button
|
||||
disabled={missingVotes > 0}
|
||||
onClick={handleSubmit}
|
||||
loading={addVotesLoading}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</Box>
|
||||
{missingVotes > 0 && (
|
||||
<>
|
||||
You need to vote on {missingVotes} more player
|
||||
{missingVotes > 1 ? "s" : ""}
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Voting
|
||||
@@ -1,102 +0,0 @@
|
||||
import React from "react"
|
||||
import { Divider, Grid } from "semantic-ui-react"
|
||||
import { Link } from "react-router-dom"
|
||||
|
||||
import useWindowDimensions from "../../hooks/useWindowDimensions"
|
||||
import VotingNumber from "./VotingNumber"
|
||||
import UserAvatar from "../common/UserAvatar"
|
||||
|
||||
const VotingGridRow = ({
|
||||
votes,
|
||||
setVotes,
|
||||
user,
|
||||
suggester,
|
||||
description,
|
||||
increaseCount,
|
||||
sameRegion = true,
|
||||
}) => {
|
||||
const { isMobile } = useWindowDimensions()
|
||||
return (
|
||||
<>
|
||||
<Grid.Row columns={6}>
|
||||
<Grid.Column>
|
||||
<div>
|
||||
<Link
|
||||
to={`/u/${user.discord_id}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<UserAvatar twitterName={user.twitter_name} />
|
||||
{user.username}#{user.discriminator}
|
||||
</Link>
|
||||
</div>
|
||||
</Grid.Column>
|
||||
{isMobile && <Grid.Column />}
|
||||
<Grid.Column>
|
||||
{sameRegion && (
|
||||
<VotingNumber
|
||||
number={-2}
|
||||
selected={votes[user.discord_id] === -2}
|
||||
onClick={() => {
|
||||
if (isNaN(votes[user.discord_id])) increaseCount()
|
||||
setVotes({ ...votes, [user.discord_id]: -2 })
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Grid.Column>
|
||||
<Grid.Column>
|
||||
<VotingNumber
|
||||
number={-1}
|
||||
selected={votes[user.discord_id] === -1}
|
||||
onClick={() => {
|
||||
if (isNaN(votes[user.discord_id])) increaseCount()
|
||||
setVotes({ ...votes, [user.discord_id]: -1 })
|
||||
}}
|
||||
/>
|
||||
</Grid.Column>
|
||||
<Grid.Column>
|
||||
<VotingNumber
|
||||
number={1}
|
||||
selected={votes[user.discord_id] === 1}
|
||||
onClick={() => {
|
||||
if (isNaN(votes[user.discord_id])) increaseCount()
|
||||
setVotes({ ...votes, [user.discord_id]: 1 })
|
||||
}}
|
||||
/>
|
||||
</Grid.Column>
|
||||
<Grid.Column>
|
||||
{sameRegion && (
|
||||
<VotingNumber
|
||||
number={2}
|
||||
selected={votes[user.discord_id] === 2}
|
||||
onClick={() => {
|
||||
if (isNaN(votes[user.discord_id])) increaseCount()
|
||||
setVotes({ ...votes, [user.discord_id]: 2 })
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Grid.Column>
|
||||
{!isMobile && <Grid.Column />}
|
||||
</Grid.Row>
|
||||
{suggester ? (
|
||||
<div style={{ margin: "0.5em 0 0.5em 0" }}>
|
||||
<b>
|
||||
Suggested by{" "}
|
||||
<Link
|
||||
to={`/u/${suggester.discord_id}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{suggester.username}#{suggester.discriminator}
|
||||
</Link>
|
||||
</b>
|
||||
<div>{description}</div>
|
||||
</div>
|
||||
) : (
|
||||
<Divider />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default VotingGridRow
|
||||
@@ -1,25 +0,0 @@
|
||||
import React from "react"
|
||||
import { Button } from "semantic-ui-react"
|
||||
|
||||
const VotingNumber = ({ number, selected, onClick }) => {
|
||||
const color = () => {
|
||||
if (!selected) return "grey"
|
||||
if (number === 2) return "green"
|
||||
if (number === -2) return "red"
|
||||
|
||||
return null
|
||||
}
|
||||
const style = () => {
|
||||
if (!selected || number === 2 || number === -2) return {}
|
||||
else if (number === -1) return { background: "#FFA07A" }
|
||||
else if (number === 1) return { background: "#90EE90" }
|
||||
}
|
||||
return (
|
||||
<Button color={color()} style={style()} circular onClick={onClick} compact>
|
||||
{number > 0 && "+"}
|
||||
{number}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default VotingNumber
|
||||
@@ -1,7 +0,0 @@
|
||||
import { gql } from "apollo-boost"
|
||||
|
||||
export const addVotes = gql`
|
||||
mutation addVotes($votes: [VoteInput!]!) {
|
||||
addVotes(votes: $votes)
|
||||
}
|
||||
`
|
||||
14
frontend-react/src/graphql/mutations/addVotes.ts
Normal file
14
frontend-react/src/graphql/mutations/addVotes.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { gql, DocumentNode } from "apollo-boost"
|
||||
|
||||
export interface AddVotesVars {
|
||||
votes: {
|
||||
discord_id: string
|
||||
score: -2 | -1 | 1 | 2
|
||||
}[]
|
||||
}
|
||||
|
||||
export const ADD_VOTES: DocumentNode = gql`
|
||||
mutation addVotes($votes: [VoteInput!]!) {
|
||||
addVotes(votes: $votes)
|
||||
}
|
||||
`
|
||||
@@ -1,5 +1,13 @@
|
||||
import { gql, DocumentNode } from "apollo-boost"
|
||||
|
||||
export interface PlusInfoData {
|
||||
plusInfo?: {
|
||||
voting_ends?: string
|
||||
voter_count: number
|
||||
eligible_voters: number
|
||||
}
|
||||
}
|
||||
|
||||
export const PLUS_INFO: DocumentNode = gql`
|
||||
{
|
||||
plusInfo {
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import { gql } from "apollo-boost"
|
||||
|
||||
export const usersForVoting = gql`
|
||||
{
|
||||
usersForVoting {
|
||||
users {
|
||||
username
|
||||
discriminator
|
||||
twitter_name
|
||||
discord_id
|
||||
plus {
|
||||
membership_status
|
||||
vouch_status
|
||||
plus_region
|
||||
}
|
||||
}
|
||||
suggested {
|
||||
discord_user {
|
||||
discord_id
|
||||
username
|
||||
discriminator
|
||||
twitter_name
|
||||
}
|
||||
suggester_discord_user {
|
||||
username
|
||||
discriminator
|
||||
}
|
||||
plus_region
|
||||
description
|
||||
}
|
||||
votes {
|
||||
discord_id
|
||||
score
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
77
frontend-react/src/graphql/queries/usersForVoting.ts
Normal file
77
frontend-react/src/graphql/queries/usersForVoting.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { gql, DocumentNode } from "apollo-boost"
|
||||
|
||||
export interface VotingSuggested {
|
||||
discord_user: {
|
||||
discord_id: string
|
||||
username: string
|
||||
discriminator: string
|
||||
twitter_name?: string
|
||||
}
|
||||
suggester_discord_user: {
|
||||
username: string
|
||||
discriminator: string
|
||||
}
|
||||
plus_region: "EU" | "NA"
|
||||
description: string
|
||||
}
|
||||
|
||||
export interface UsersForVotingData {
|
||||
usersForVoting: {
|
||||
users: {
|
||||
username: string
|
||||
discriminator: string
|
||||
twitter_name?: string
|
||||
discord_id: string
|
||||
plus: {
|
||||
membership_status?: "ONE" | "TWO"
|
||||
vouch_status?: "ONE" | "TWO"
|
||||
plus_region: "EU" | "NA"
|
||||
}
|
||||
}[]
|
||||
suggested: VotingSuggested[]
|
||||
votes: {
|
||||
discord_id: string
|
||||
score: number
|
||||
month: number
|
||||
year: number
|
||||
}[]
|
||||
}
|
||||
}
|
||||
|
||||
export const USERS_FOR_VOTING: DocumentNode = gql`
|
||||
{
|
||||
usersForVoting {
|
||||
users {
|
||||
username
|
||||
discriminator
|
||||
twitter_name
|
||||
discord_id
|
||||
plus {
|
||||
membership_status
|
||||
vouch_status
|
||||
plus_region
|
||||
}
|
||||
}
|
||||
suggested {
|
||||
discord_user {
|
||||
discord_id
|
||||
username
|
||||
discriminator
|
||||
twitter_name
|
||||
}
|
||||
suggester_discord_user {
|
||||
username
|
||||
discriminator
|
||||
}
|
||||
plus_region
|
||||
description
|
||||
}
|
||||
votes {
|
||||
discord_id
|
||||
score
|
||||
month
|
||||
year
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
Reference in New Issue
Block a user