mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-10-01 15:48:23 -05:00
plus page basically working
This commit is contained in:
12
frontend-react/package-lock.json
generated
12
frontend-react/package-lock.json
generated
@@ -1090,9 +1090,9 @@
|
||||
}
|
||||
},
|
||||
"@emotion/core": {
|
||||
"version": "10.0.27",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.0.27.tgz",
|
||||
"integrity": "sha512-XbD5R36pVbohQMnKfajHv43g8EbN4NHdF6Zh9zg/C0nr0jqwOw3gYnC07Xj3yG43OYSRyrGsoQ5qPwc8ycvLZw==",
|
||||
"version": "10.0.28",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.0.28.tgz",
|
||||
"integrity": "sha512-pH8UueKYO5jgg0Iq+AmCLxBsvuGtvlmiDCOuv8fGNYn3cowFpLN98L8zO56U0H1PjDIyAlXymgL3Wu7u7v6hbA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"@emotion/cache": "^10.0.27",
|
||||
@@ -1451,9 +1451,9 @@
|
||||
"integrity": "sha512-a1USH7L3bEfDdPN4iNZGvMEFuBfkdG+QNybeyDv8RloVFgZYRoM+KGXyy2KOfEnTUM8QWDRSROwaL3+ts5Angg=="
|
||||
},
|
||||
"@reach/router": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@reach/router/-/router-1.3.1.tgz",
|
||||
"integrity": "sha512-Ov1j1J+pSgXliJHFL7XWhjyREwc6GxeWfgBTa5MMH5eRmYtHbPhaovba4xKo7aTVCg8fxkt2yDMNSpvwfUP+pA==",
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@reach/router/-/router-1.3.3.tgz",
|
||||
"integrity": "sha512-gOIAiFhWdiVGSVjukKeNKkCRBLmnORoTPyBihI/jLunICPgxdP30DroAvPQuf1eVfQbfGJQDJkwhJXsNPMnVWw==",
|
||||
"requires": {
|
||||
"create-react-context": "0.3.0",
|
||||
"invariant": "^2.2.3",
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
"dependencies": {
|
||||
"@apollo/react-hooks": "^3.1.3",
|
||||
"@chakra-ui/core": "^0.6.1",
|
||||
"@emotion/core": "^10.0.27",
|
||||
"@emotion/core": "^10.0.28",
|
||||
"@emotion/styled": "^10.0.27",
|
||||
"@reach/router": "^1.3.1",
|
||||
"@reach/router": "^1.3.3",
|
||||
"@rehooks/local-storage": "^2.2.2",
|
||||
"@sendou/react-sketch": "^0.5.2",
|
||||
"@testing-library/jest-dom": "^5.1.1",
|
||||
|
||||
49
frontend-react/src/components/common/UserSelector.tsx
Normal file
49
frontend-react/src/components/common/UserSelector.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import React from "react"
|
||||
import { useQuery } from "@apollo/react-hooks"
|
||||
import { USERS } from "../../graphql/queries/users"
|
||||
import Error from "./Error"
|
||||
import Select from "../elements/Select"
|
||||
|
||||
interface UsersData {
|
||||
users: {
|
||||
discord_id: string
|
||||
discriminator: string
|
||||
twitter_name?: string
|
||||
username: string
|
||||
}[]
|
||||
}
|
||||
|
||||
interface UserSelectorProps {
|
||||
id?: string
|
||||
setValue?: (value: string) => void
|
||||
}
|
||||
|
||||
const UserSelector: React.FC<UserSelectorProps> = ({ setValue }) => {
|
||||
const { data, error, loading } = useQuery<UsersData>(USERS)
|
||||
|
||||
if (error) return <Error errorMessage={error.message} />
|
||||
return (
|
||||
<>
|
||||
<Select
|
||||
isSearchable
|
||||
options={
|
||||
!loading && data
|
||||
? data.users.map(user => ({
|
||||
label: `${user.username}#${user.discriminator}`,
|
||||
value: user.discord_id,
|
||||
}))
|
||||
: undefined
|
||||
}
|
||||
setValue={setValue}
|
||||
clearable
|
||||
hideMenuBeforeTyping
|
||||
isLoading={loading}
|
||||
isDisabled={loading}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export default UserSelector
|
||||
@@ -6,12 +6,11 @@ import { IconType } from "react-icons/lib/cjs"
|
||||
|
||||
interface ButtonProps {
|
||||
children: string | string[]
|
||||
onClick: () => void
|
||||
onClick?: () => void
|
||||
size?: "xs" | "sm" | "lg" | "md"
|
||||
icon?: IconType
|
||||
outlined?: boolean
|
||||
disabled?: boolean
|
||||
negative?: boolean
|
||||
}
|
||||
|
||||
const Button: React.FC<ButtonProps> = ({
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import React, { useContext } from "react"
|
||||
import React, { useContext, useState } from "react"
|
||||
import ReactSelect, { OptionsType, GroupedOptionsType } from "react-select"
|
||||
import MyThemeContext from "../../themeContext"
|
||||
import { SelectComponents } from "react-select/src/components"
|
||||
import Box from "./Box"
|
||||
import Label from "./Label"
|
||||
import { Flex } from "@chakra-ui/core"
|
||||
import Button from "./Button"
|
||||
|
||||
interface SelectProps {
|
||||
options:
|
||||
options?:
|
||||
| OptionsType<{
|
||||
label: string
|
||||
value: string
|
||||
@@ -16,7 +18,7 @@ interface SelectProps {
|
||||
value: string
|
||||
}>
|
||||
width?: string
|
||||
label: string
|
||||
label?: string
|
||||
required?: boolean
|
||||
value?:
|
||||
| {
|
||||
@@ -26,7 +28,7 @@ interface SelectProps {
|
||||
| string
|
||||
| string[]
|
||||
| null
|
||||
setValue: (value: any) => void
|
||||
setValue?: (value: any) => void
|
||||
autoFocus?: boolean
|
||||
components?: Partial<
|
||||
SelectComponents<{
|
||||
@@ -36,7 +38,10 @@ interface SelectProps {
|
||||
>
|
||||
clearable?: boolean
|
||||
isMulti?: boolean
|
||||
isLoading?: boolean
|
||||
isDisabled?: boolean
|
||||
isSearchable?: boolean
|
||||
hideMenuBeforeTyping?: boolean
|
||||
}
|
||||
|
||||
const Select: React.FC<SelectProps> = ({
|
||||
@@ -49,7 +54,10 @@ const Select: React.FC<SelectProps> = ({
|
||||
label,
|
||||
required,
|
||||
isMulti,
|
||||
isLoading,
|
||||
isDisabled,
|
||||
isSearchable,
|
||||
hideMenuBeforeTyping,
|
||||
width = "290px",
|
||||
}) => {
|
||||
const {
|
||||
@@ -58,8 +66,10 @@ const Select: React.FC<SelectProps> = ({
|
||||
themeColorHex,
|
||||
themeColorHexLighter,
|
||||
} = useContext(MyThemeContext)
|
||||
const [inputValue, setInputValue] = useState("")
|
||||
|
||||
const handleChange = (selectedOption: any) => {
|
||||
if (!setValue || !selectedOption) return
|
||||
if (Array.isArray(selectedOption)) {
|
||||
setValue(selectedOption.map(obj => obj.value))
|
||||
} else {
|
||||
@@ -84,16 +94,31 @@ const Select: React.FC<SelectProps> = ({
|
||||
className="basic-single"
|
||||
classNamePrefix="select"
|
||||
value={getValue()}
|
||||
inputValue={inputValue}
|
||||
onInputChange={newValue => setInputValue(newValue)}
|
||||
menuIsOpen={
|
||||
hideMenuBeforeTyping ? !!(inputValue.length >= 3) : undefined
|
||||
}
|
||||
onChange={handleChange}
|
||||
placeholder={null}
|
||||
isSearchable={!!isSearchable}
|
||||
isMulti={!!isMulti}
|
||||
isLoading={isLoading}
|
||||
isDisabled={isDisabled}
|
||||
isClearable={isMulti ? false : clearable}
|
||||
options={options}
|
||||
components={{
|
||||
IndicatorSeparator: () => null,
|
||||
...components,
|
||||
}}
|
||||
components={
|
||||
hideMenuBeforeTyping
|
||||
? {
|
||||
IndicatorSeparator: () => null,
|
||||
DropdownIndicator: () => null,
|
||||
...components,
|
||||
}
|
||||
: {
|
||||
IndicatorSeparator: () => null,
|
||||
...components,
|
||||
}
|
||||
}
|
||||
theme={theme => ({
|
||||
...theme,
|
||||
borderRadius: 5,
|
||||
@@ -102,6 +127,7 @@ const Select: React.FC<SelectProps> = ({
|
||||
primary25: `${themeColorHexLighter}`,
|
||||
primary: `${themeColorHex}`,
|
||||
neutral0: darkerBgColor,
|
||||
neutral5: darkerBgColor,
|
||||
},
|
||||
})}
|
||||
autoFocus={autoFocus}
|
||||
|
||||
@@ -7,9 +7,10 @@ import MyThemeContext from "../../themeContext"
|
||||
interface TextAreaProps {
|
||||
value?: string
|
||||
setValue: (value: string) => void
|
||||
label: string
|
||||
label?: string
|
||||
limit?: number
|
||||
required?: boolean
|
||||
id?: string
|
||||
}
|
||||
|
||||
const TextArea: React.FC<TextAreaProps> = ({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react"
|
||||
import React from "react"
|
||||
import { useQuery } from "@apollo/react-hooks"
|
||||
|
||||
import Suggestions from "./Suggestions"
|
||||
@@ -7,8 +7,11 @@ import Error from "../common/Error"
|
||||
import { PLUS_INFO } from "../../graphql/queries/plusInfo"
|
||||
import { USER } from "../../graphql/queries/user"
|
||||
//import Voting from "./Voting"
|
||||
import { Redirect, RouteComponentProps } from "@reach/router"
|
||||
import { Redirect, RouteComponentProps, Link } from "@reach/router"
|
||||
import PageHeader from "../common/PageHeader"
|
||||
import { FaHistory } from "react-icons/fa"
|
||||
import Button from "../elements/Button"
|
||||
import { Helmet } from "react-helmet-async"
|
||||
|
||||
interface PlusInfoData {
|
||||
plusInfo: {
|
||||
@@ -19,8 +22,6 @@ interface PlusInfoData {
|
||||
}
|
||||
|
||||
const PlusPage: React.FC<RouteComponentProps> = () => {
|
||||
const [showSuggestionForm, setShowSuggestionForm] = useState(false)
|
||||
|
||||
const { data, error, loading } = useQuery<PlusInfoData>(PLUS_INFO)
|
||||
const {
|
||||
data: userData,
|
||||
@@ -36,8 +37,10 @@ const PlusPage: React.FC<RouteComponentProps> = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Plus Server Home | sendou.ink</title>
|
||||
</Helmet>
|
||||
<PageHeader title="Plus Server" />
|
||||
{/*Inv link here*/}
|
||||
{/*data.plusInfo.voting_ends && userData.user.plus?.membership_status ? (
|
||||
<Voting
|
||||
user={userData.user}
|
||||
@@ -61,6 +64,11 @@ const PlusPage: React.FC<RouteComponentProps> = () => {
|
||||
handleError={handleError}
|
||||
/>
|
||||
)*/}
|
||||
<Link to="/plus/history">
|
||||
<Button outlined icon={FaHistory}>
|
||||
Show voting history
|
||||
</Button>
|
||||
</Link>
|
||||
<Suggestions user={userData.user} />
|
||||
</>
|
||||
)
|
||||
|
||||
257
frontend-react/src/components/plus/SuggestionVouchModal.tsx
Normal file
257
frontend-react/src/components/plus/SuggestionVouchModal.tsx
Normal file
@@ -0,0 +1,257 @@
|
||||
import React, { useState, useContext } from "react"
|
||||
import Modal from "../elements/Modal"
|
||||
import UserSelector from "../common/UserSelector"
|
||||
import { ADD_SUGGESTION } from "../../graphql/mutations/addSuggestion"
|
||||
import { ADD_VOUCH } from "../../graphql/mutations/addVouch"
|
||||
import { useMutation } from "@apollo/react-hooks"
|
||||
import {
|
||||
useToast,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
FormHelperText,
|
||||
RadioGroup,
|
||||
Radio,
|
||||
Flex,
|
||||
Box,
|
||||
FormErrorMessage,
|
||||
} from "@chakra-ui/core"
|
||||
import MyThemeContext from "../../themeContext"
|
||||
import TextArea from "../elements/TextArea"
|
||||
import Button from "../elements/Button"
|
||||
import { SUGGESTIONS } from "../../graphql/queries/suggestions"
|
||||
import { VOUCHES } from "../../graphql/queries/vouches"
|
||||
import { USER } from "../../graphql/queries/user"
|
||||
|
||||
interface AddSuggestionVars {
|
||||
discord_id: string
|
||||
region: string
|
||||
server: string
|
||||
description: string
|
||||
}
|
||||
|
||||
interface AddVouchVars {
|
||||
discord_id: string
|
||||
region: string
|
||||
server: string
|
||||
}
|
||||
|
||||
interface SuggestionVouchModalProps {
|
||||
closeModal: () => void
|
||||
canSuggest: boolean
|
||||
canVouch: boolean
|
||||
plusServer: "ONE" | "TWO"
|
||||
}
|
||||
|
||||
const SuggestionVouchModal: React.FC<SuggestionVouchModalProps> = ({
|
||||
closeModal,
|
||||
canSuggest,
|
||||
canVouch,
|
||||
plusServer,
|
||||
}) => {
|
||||
const [form, setForm] = useState<Partial<AddSuggestionVars>>({})
|
||||
const [actionType, setActionType] = useState<string>(
|
||||
!canSuggest ? "VOUCH" : "SUGGEST"
|
||||
)
|
||||
const [showErrors, setShowErrors] = useState(false)
|
||||
const toast = useToast()
|
||||
const { themeColor } = useContext(MyThemeContext)
|
||||
|
||||
const [addSuggestion] = useMutation<boolean, AddSuggestionVars>(
|
||||
ADD_SUGGESTION,
|
||||
{
|
||||
variables: { ...(form as AddSuggestionVars) },
|
||||
onCompleted: data => {
|
||||
closeModal()
|
||||
toast({
|
||||
description: `Suggestion added`,
|
||||
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: SUGGESTIONS }],
|
||||
}
|
||||
)
|
||||
|
||||
const [addVouch] = useMutation<boolean, AddVouchVars>(ADD_VOUCH, {
|
||||
variables: { ...(form as AddVouchVars) },
|
||||
onCompleted: data => {
|
||||
closeModal()
|
||||
toast({
|
||||
description: `Player vouched`,
|
||||
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: VOUCHES }, { query: USER }],
|
||||
})
|
||||
|
||||
const handleChange = (newValueObject: Partial<AddSuggestionVars>) => {
|
||||
setForm({ ...form, ...newValueObject })
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (!form.discord_id || !form.region || !form.server) {
|
||||
setShowErrors(true)
|
||||
return
|
||||
}
|
||||
if (
|
||||
(!form.description ||
|
||||
(form.description && form.description.length > 1000)) &&
|
||||
actionType === "SUGGEST"
|
||||
) {
|
||||
setShowErrors(true)
|
||||
return
|
||||
}
|
||||
|
||||
if (actionType === "SUGGEST") addSuggestion()
|
||||
if (actionType === "VOUCH") addVouch()
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal title={"Suggesting or vouching a player"} closeModal={closeModal}>
|
||||
<FormControl isRequired isInvalid={showErrors && !form.discord_id}>
|
||||
<FormLabel htmlFor="user">Discord username</FormLabel>
|
||||
<UserSelector
|
||||
id="user"
|
||||
setValue={value => handleChange({ discord_id: value })}
|
||||
/>
|
||||
<FormErrorMessage>Required field</FormErrorMessage>
|
||||
<FormHelperText>
|
||||
Only people who have logged in to sendou.ink at least once shown
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
<FormControl isRequired mt="1em">
|
||||
<FormLabel htmlFor="server">Action</FormLabel>
|
||||
<RadioGroup
|
||||
spacing={5}
|
||||
isInline
|
||||
onChange={(e, value) => setActionType(value as string)}
|
||||
value={actionType}
|
||||
>
|
||||
<Radio
|
||||
variantColor={themeColor}
|
||||
value="SUGGEST"
|
||||
isDisabled={!canSuggest}
|
||||
>
|
||||
Suggest
|
||||
</Radio>
|
||||
<Radio variantColor={themeColor} value="VOUCH" isDisabled={!canVouch}>
|
||||
Vouch
|
||||
</Radio>
|
||||
</RadioGroup>
|
||||
<FormHelperText>
|
||||
Vouching only possible if you got high enough ratio in the last voting
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
<FormControl isRequired isInvalid={showErrors && !form.server} mt="1em">
|
||||
<FormLabel htmlFor="server">Server</FormLabel>
|
||||
<RadioGroup
|
||||
id="server"
|
||||
spacing={5}
|
||||
isInline
|
||||
onChange={(e, value) => handleChange({ server: value as string })}
|
||||
value={form.server}
|
||||
>
|
||||
<Radio
|
||||
variantColor={themeColor}
|
||||
value="ONE"
|
||||
isDisabled={plusServer !== "ONE"}
|
||||
>
|
||||
+1
|
||||
</Radio>
|
||||
<Radio variantColor={themeColor} value="TWO">
|
||||
+2
|
||||
</Radio>
|
||||
<FormErrorMessage>Required field</FormErrorMessage>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
|
||||
<FormControl isRequired isInvalid={showErrors && !form.region} mt="1em">
|
||||
<FormLabel htmlFor="region">Region</FormLabel>
|
||||
<RadioGroup
|
||||
id="region"
|
||||
spacing={5}
|
||||
isInline
|
||||
onChange={(e, value) => handleChange({ region: value as string })}
|
||||
value={form.region}
|
||||
>
|
||||
<Radio
|
||||
variantColor={themeColor}
|
||||
value="EU"
|
||||
isDisabled={plusServer !== "ONE"}
|
||||
>
|
||||
Europe
|
||||
</Radio>
|
||||
<Radio variantColor={themeColor} value="NA">
|
||||
The Americas
|
||||
</Radio>
|
||||
</RadioGroup>
|
||||
<FormErrorMessage>Required field</FormErrorMessage>
|
||||
<FormHelperText>
|
||||
If the player doesn't live in either Europe or The Americas you can
|
||||
choose the region based on who they are playing more often with
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
{actionType === "SUGGEST" && (
|
||||
<FormControl
|
||||
isRequired
|
||||
isInvalid={
|
||||
!!(
|
||||
(showErrors && !form.description) ||
|
||||
(form.description && form.description.length > 1000)
|
||||
)
|
||||
}
|
||||
mt="1em"
|
||||
>
|
||||
<FormLabel htmlFor="description">Description</FormLabel>
|
||||
<TextArea
|
||||
id="description"
|
||||
setValue={(value: string) =>
|
||||
handleChange({ description: value as string })
|
||||
}
|
||||
value={form.description}
|
||||
limit={1000}
|
||||
/>
|
||||
<FormErrorMessage>
|
||||
{!form.description
|
||||
? "Required field"
|
||||
: "Description can't be more than 1000 characters"}
|
||||
</FormErrorMessage>
|
||||
</FormControl>
|
||||
)}
|
||||
<Flex flexWrap="wrap" mt="1em">
|
||||
<Box mr="1em">
|
||||
<Button onClick={() => handleSubmit()}>Submit</Button>
|
||||
</Box>
|
||||
<Button outlined onClick={() => closeModal()}>
|
||||
Cancel
|
||||
</Button>
|
||||
</Flex>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default SuggestionVouchModal
|
||||
@@ -1,7 +1,5 @@
|
||||
import React, { useContext } from "react"
|
||||
import React, { useContext, useState } from "react"
|
||||
import { useQuery } from "@apollo/react-hooks"
|
||||
|
||||
//import SuggestionForm from "./SuggestionForm"
|
||||
import { SUGGESTIONS } from "../../graphql/queries/suggestions"
|
||||
import Loading from "../common/Loading"
|
||||
import Error from "../common/Error"
|
||||
@@ -23,57 +21,8 @@ import {
|
||||
import { Link } from "@reach/router"
|
||||
import MyThemeContext from "../../themeContext"
|
||||
import { IoIosChatbubbles } from "react-icons/io"
|
||||
|
||||
/*const SuggestionList = ({ suggestionsArray }) => {
|
||||
if (suggestionsArray.length === 0) return null
|
||||
|
||||
const suggestionLists = suggestionsArray.map(suggestion => (
|
||||
<React.Fragment key={suggestion.suggester_discord_user.discord_id}>
|
||||
<List.Item>
|
||||
<List.Content>
|
||||
{suggestion.discord_user.twitter_name && (
|
||||
<Image
|
||||
avatar
|
||||
size="mini"
|
||||
src={`https://avatars.io/twitter/${suggestion.discord_user.twitter_name}`}
|
||||
/>
|
||||
)}
|
||||
<List.Header as="a" href={`/u/${suggestion.discord_user.discord_id}`}>
|
||||
{suggestion.discord_user.username}#
|
||||
{suggestion.discord_user.discriminator}
|
||||
</List.Header>
|
||||
<List.Description>
|
||||
<div style={{ marginTop: "0.5em" }}>
|
||||
<b>
|
||||
{suggestion.plus_region} | Suggested to join{" "}
|
||||
{suggestion.plus_server === "ONE" ? "+1 " : "+2 "} by{" "}
|
||||
<a href={`/u/${suggestion.suggester_discord_user.discord_id}`}>
|
||||
{suggestion.suggester_discord_user.username}#
|
||||
{suggestion.suggester_discord_user.discriminator}
|
||||
</a>
|
||||
</b>
|
||||
</div>
|
||||
</List.Description>
|
||||
<List.Description style={{ whiteSpace: "pre-wrap" }}>
|
||||
{suggestion.description}
|
||||
</List.Description>
|
||||
</List.Content>
|
||||
</List.Item>
|
||||
<Divider />
|
||||
</React.Fragment>
|
||||
))
|
||||
|
||||
return (
|
||||
<>
|
||||
{suggestionLists.length > 0 && (
|
||||
<>
|
||||
<h2>Suggested</h2>
|
||||
{suggestionLists}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}*/
|
||||
import Button from "../elements/Button"
|
||||
import SuggestionVouchModal from "./SuggestionVouchModal"
|
||||
|
||||
interface SuggestionsProps {
|
||||
user: UserLean
|
||||
@@ -120,14 +69,11 @@ interface VouchesData {
|
||||
vouches: VouchUser[]
|
||||
}
|
||||
|
||||
const Suggestions: React.FC<SuggestionsProps> = ({
|
||||
user,
|
||||
//showSuggestionForm,
|
||||
//setShowSuggestionForm,
|
||||
}) => {
|
||||
const Suggestions: React.FC<SuggestionsProps> = ({ user }) => {
|
||||
const { grayWithShade, themeColor, darkerBgColor } = useContext(
|
||||
MyThemeContext
|
||||
)
|
||||
const [showSuggestionForm, setShowSuggestionForm] = useState(false)
|
||||
const { data, error, loading } = useQuery<SuggestionsData>(SUGGESTIONS)
|
||||
const {
|
||||
data: vouchesData,
|
||||
@@ -244,6 +190,10 @@ const Suggestions: React.FC<SuggestionsProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
const buttonText = getButtonText()
|
||||
|
||||
console.log("data", data)
|
||||
|
||||
return (
|
||||
<>
|
||||
{/*!showSuggestionForm && (canSuggest || canVouch) && (
|
||||
@@ -264,34 +214,52 @@ const Suggestions: React.FC<SuggestionsProps> = ({
|
||||
canVouchFor={user.plus.can_vouch}
|
||||
/>
|
||||
)*/}
|
||||
{vouchesData.vouches.length > 0 && (
|
||||
<>
|
||||
<Flex flexWrap="wrap">
|
||||
<Box mr="2em" mb="1em">
|
||||
{plusOneVouches.length > 0 && (
|
||||
{showSuggestionForm && (
|
||||
<SuggestionVouchModal
|
||||
closeModal={() => setShowSuggestionForm(false)}
|
||||
canSuggest={canSuggest}
|
||||
canVouch={canVouch}
|
||||
plusServer={user.plus.membership_status!}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Box mt="1em">
|
||||
{buttonText && (
|
||||
<Button onClick={() => setShowSuggestionForm(true)}>
|
||||
{buttonText}
|
||||
</Button>
|
||||
)}
|
||||
{vouchesData.vouches.length > 0 && (
|
||||
<Flex flexWrap="wrap" mt="1em">
|
||||
{plusOneVouches.length > 0 && (
|
||||
<Box mr="2em" mb="1em">
|
||||
<Heading size="md">Vouched players to +1</Heading>
|
||||
)}
|
||||
<Grid
|
||||
gridRowGap="0.5em"
|
||||
gridTemplateColumns="min-content 1fr"
|
||||
mt="1em"
|
||||
>
|
||||
{plusOneVouches.map(vouchMap)}
|
||||
</Grid>
|
||||
</Box>
|
||||
<Box>
|
||||
{plusOneVouches.length > 0 && (
|
||||
|
||||
<Grid
|
||||
gridRowGap="0.5em"
|
||||
gridTemplateColumns="min-content 1fr"
|
||||
mt="1em"
|
||||
>
|
||||
{plusOneVouches.map(vouchMap)}
|
||||
</Grid>
|
||||
</Box>
|
||||
)}
|
||||
{plusTwoVouches.length > 0 && (
|
||||
<Box>
|
||||
<Heading size="md">Vouched players to +2</Heading>
|
||||
)}
|
||||
<Grid
|
||||
gridRowGap="0.5em"
|
||||
gridTemplateColumns="min-content 1fr"
|
||||
mt="1em"
|
||||
>
|
||||
{plusTwoVouches.map(vouchMap)}
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
<Grid
|
||||
gridRowGap="0.5em"
|
||||
gridTemplateColumns="min-content 1fr"
|
||||
mt="1em"
|
||||
>
|
||||
{plusTwoVouches.map(vouchMap)}
|
||||
</Grid>
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
{data.suggestions.length > 0 && (
|
||||
<Flex flexWrap="wrap" mt="2em">
|
||||
<Box mr="2em" mb="1em">
|
||||
{plusOneSuggested.length > 0 && (
|
||||
@@ -318,8 +286,8 @@ const Suggestions: React.FC<SuggestionsProps> = ({
|
||||
</Grid>
|
||||
</Box>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
import React, { useContext } from "react"
|
||||
import UserAvatar from "../common/UserAvatar"
|
||||
import { Summary } from "./VotingHistoryPage"
|
||||
import { Heading, Box, Grid, Flex } from "@chakra-ui/core"
|
||||
import {
|
||||
Heading,
|
||||
Box,
|
||||
Grid,
|
||||
Flex,
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
IconButton,
|
||||
PopoverContent,
|
||||
PopoverArrow,
|
||||
PopoverBody,
|
||||
} from "@chakra-ui/core"
|
||||
import { Link } from "@reach/router"
|
||||
import MyThemeContext from "../../themeContext"
|
||||
import { FaBolt } from "react-icons/fa"
|
||||
|
||||
const getColor = (score: number) =>
|
||||
score < 50 ? { color: "red" } : { color: "green" }
|
||||
@@ -50,7 +62,9 @@ interface SummariesProps {
|
||||
}
|
||||
|
||||
const Summaries: React.FC<SummariesProps> = ({ summaries }) => {
|
||||
const { grayWithShade } = useContext(MyThemeContext)
|
||||
const { grayWithShade, themeColor, darkerBgColor } = useContext(
|
||||
MyThemeContext
|
||||
)
|
||||
const members: Summary[] = []
|
||||
const suggested: Summary[] = []
|
||||
|
||||
@@ -88,12 +102,30 @@ const Summaries: React.FC<SummariesProps> = ({ summaries }) => {
|
||||
<Count count={score.na_count} />)
|
||||
</>
|
||||
)}
|
||||
{/*summary.vouched && (
|
||||
<Popup
|
||||
content="User was vouched to the server last month"
|
||||
trigger={<Icon name="bolt" color="teal" size="large" />}
|
||||
/>
|
||||
)*/}
|
||||
{summary.vouched && (
|
||||
<Popover placement="top">
|
||||
<PopoverTrigger>
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
isRound
|
||||
variantColor={themeColor}
|
||||
aria-label="Show description"
|
||||
fontSize="20px"
|
||||
icon={FaBolt}
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
zIndex={4}
|
||||
width="220px"
|
||||
backgroundColor={darkerBgColor}
|
||||
>
|
||||
<PopoverArrow />
|
||||
<PopoverBody textAlign="center">
|
||||
User was voted to the server this month
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</React.Fragment>
|
||||
|
||||
@@ -6,11 +6,15 @@ import { USER } from "../../graphql/queries/user"
|
||||
import Error from "../common/Error"
|
||||
import { months } from "../../utils/lists"
|
||||
//import SummaryLists from "./SummaryLists"
|
||||
import { Redirect, RouteComponentProps } from "@reach/router"
|
||||
import { Redirect, RouteComponentProps, Link } from "@reach/router"
|
||||
import { UserLean, UserData } from "../../types"
|
||||
import { Flex, Box } from "@chakra-ui/core"
|
||||
import Select from "../elements/Select"
|
||||
import Summaries from "./Summaries"
|
||||
import PageHeader from "../common/PageHeader"
|
||||
import Button from "../elements/Button"
|
||||
import { FaHistory } from "react-icons/fa"
|
||||
import { Helmet } from "react-helmet-async"
|
||||
|
||||
export interface Summary {
|
||||
discord_user: {
|
||||
@@ -109,6 +113,10 @@ const VotingHistoryPage: React.FC<RouteComponentProps> = () => {
|
||||
const year = parseInt(parts[1])
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Plus Server Voting History | sendou.ink</title>
|
||||
</Helmet>
|
||||
<PageHeader title="Voting History" />
|
||||
<Flex flexWrap="wrap" justifyContent="center">
|
||||
{userData.user.plus.membership_status === "ONE" && (
|
||||
<Box m="0.5em">
|
||||
|
||||
@@ -24,13 +24,11 @@ const UserItem: React.FC<{ data?: UserData }> = ({ data }) => {
|
||||
if (!data || !data.user) {
|
||||
// prolly need to do href here
|
||||
return (
|
||||
<List>
|
||||
<NavItem
|
||||
to="auth/discord"
|
||||
Icon={FaSignInAlt}
|
||||
title="Log in via Discord"
|
||||
/>
|
||||
</List>
|
||||
<NavItem
|
||||
to="auth/discord"
|
||||
Icon={FaSignInAlt}
|
||||
title="Log in via Discord"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -77,7 +75,7 @@ export const SideNavContent: React.FC<SideNavProps> = ({ showLogo = true }) => {
|
||||
</Flex>
|
||||
)}
|
||||
<Flex>
|
||||
<List spacing={4} mt="2em">
|
||||
<List mt="2em">
|
||||
<NavItem to="plans" Icon={FaMap} title="Map Planner" />
|
||||
<NavItem to="calendar" Icon={FaCalendarAlt} title="Calendar" />
|
||||
<NavItem to="builds" Icon={FaTshirt} title="Builds" />
|
||||
|
||||
@@ -79,7 +79,7 @@ const BuildFormModal: React.FC<BuildFormModalProps> = ({
|
||||
title: "An error occurred",
|
||||
description: error.message,
|
||||
position: "top-right",
|
||||
status: "success",
|
||||
status: "error",
|
||||
duration: 10000,
|
||||
})
|
||||
},
|
||||
@@ -104,7 +104,7 @@ const BuildFormModal: React.FC<BuildFormModalProps> = ({
|
||||
title: "An error occurred",
|
||||
description: error.message,
|
||||
position: "top-right",
|
||||
status: "success",
|
||||
status: "error",
|
||||
duration: 10000,
|
||||
})
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { gql } from "apollo-boost"
|
||||
import { gql, DocumentNode } from "apollo-boost"
|
||||
|
||||
export const addSuggestion = gql`
|
||||
export const ADD_SUGGESTION: DocumentNode = gql`
|
||||
mutation addSuggestion(
|
||||
$discord_id: String!
|
||||
$region: String!
|
||||
@@ -1,6 +1,6 @@
|
||||
import { gql } from "apollo-boost"
|
||||
import { gql, DocumentNode } from "apollo-boost"
|
||||
|
||||
export const addVouch = gql`
|
||||
export const ADD_VOUCH: DocumentNode = gql`
|
||||
mutation addVouch($discord_id: String!, $server: String!, $region: String!) {
|
||||
addVouch(discord_id: $discord_id, server: $server, region: $region)
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
import { gql } from "apollo-boost"
|
||||
import { gql, DocumentNode } from "apollo-boost"
|
||||
|
||||
export const users = gql`
|
||||
export const USERS: DocumentNode = gql`
|
||||
{
|
||||
users {
|
||||
username
|
||||
discriminator
|
||||
twitter_name
|
||||
discord_id
|
||||
twitter_name
|
||||
}
|
||||
}
|
||||
`
|
||||
Reference in New Issue
Block a user