mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-07-30 23:56:50 -05:00
free agent feature initial commit
This commit is contained in:
parent
287a62853a
commit
b29754191a
24
models/fapost.js
Normal file
24
models/fapost.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
const mongoose = require("mongoose")
|
||||
|
||||
const faPostSchema = new mongoose.Schema(
|
||||
{
|
||||
discord_id: { type: String, required: true },
|
||||
can_vc: { type: String, required: true },
|
||||
playstyles: { type: [String], required: true },
|
||||
activity: String,
|
||||
past_experience: String,
|
||||
looking_for: String,
|
||||
description: String,
|
||||
hidden: { type: Boolean, default: false },
|
||||
},
|
||||
{ timestamps: true }
|
||||
)
|
||||
|
||||
faPostSchema.virtual("discord_user", {
|
||||
ref: "User",
|
||||
localField: "discord_id",
|
||||
foreignField: "discord_id",
|
||||
justOne: true,
|
||||
})
|
||||
|
||||
module.exports = mongoose.model("FAPost", faPostSchema)
|
||||
|
|
@ -12,6 +12,7 @@ const userSchema = new mongoose.Schema({
|
|||
motion: { type: Number, min: -5, max: 5 },
|
||||
},
|
||||
weapons: [String],
|
||||
top500: Boolean,
|
||||
})
|
||||
|
||||
module.exports = mongoose.model("User", userSchema)
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
const mongoose = require('mongoose')
|
||||
|
||||
const videoSchema = new mongoose.Schema({
|
||||
youtube_id: {type: String, required: true},
|
||||
video_title: {type: String, required: true},
|
||||
match_begin_timestamp: {type: Number, required: true},
|
||||
upload_timestamp: {type: Number, required: true},
|
||||
map: {type: String, required: true},
|
||||
mode: {type: String, required: true},
|
||||
weapon: {type: String, required: false},
|
||||
unique_id: {type: String, required: false},
|
||||
status: {type: String, required: true},
|
||||
submitter_id: {type: String, required: true},
|
||||
match_type: {type: String, required: true},
|
||||
top500: {type: Boolean, required: false},
|
||||
spec_pov: {type: Boolean, default: false, required: false}
|
||||
})
|
||||
|
||||
module.exports = mongoose.model('Video', videoSchema)
|
||||
20
react-ui/src/components/common/TextAreaWithLimit.js
vendored
Normal file
20
react-ui/src/components/common/TextAreaWithLimit.js
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import React from "react"
|
||||
import { TextArea } from "semantic-ui-react"
|
||||
|
||||
const TextAreaWithLimit = ({ value, setValue, limit, style = {} }) => {
|
||||
return (
|
||||
<>
|
||||
<TextArea
|
||||
value={value}
|
||||
onChange={e =>
|
||||
e.target.value.length <= limit && setValue(e.target.value)
|
||||
}
|
||||
rows={5}
|
||||
style={style}
|
||||
/>
|
||||
{value.length}/{limit}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TextAreaWithLimit
|
||||
105
react-ui/src/components/freeagents/FATableRows.js
vendored
Normal file
105
react-ui/src/components/freeagents/FATableRows.js
vendored
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import React, { useState } from "react"
|
||||
import { Table, Image, Icon, Flag } from "semantic-ui-react"
|
||||
import { Link } from "react-router-dom"
|
||||
import { countries } from "../../utils/lists"
|
||||
import top500 from "../../assets/xleaderboardIcons/all.png"
|
||||
import WpnImage from "../common/WpnImage"
|
||||
import RoleIcons from "./RoleIcons"
|
||||
import VCIcon from "./VCIcon"
|
||||
import TextRows from "./TextRows"
|
||||
|
||||
const FATableRows = ({ freeAgent }) => {
|
||||
const [expanded, setExpanded] = useState(false)
|
||||
|
||||
const twitter = freeAgent.discord_user.twitter_name
|
||||
const playstyles = freeAgent.playstyles.reduce((acc, cur) => {
|
||||
acc[cur] = true
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table.Row>
|
||||
<Table.Cell rowSpan={2}>
|
||||
<Icon
|
||||
name={expanded ? "angle down" : "angle right"}
|
||||
size="big"
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
style={{ cursor: "pointer" }}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{twitter ? (
|
||||
<>
|
||||
<Image src={`https://avatars.io/twitter/${twitter}`} avatar />
|
||||
<span>
|
||||
<Link to={`/u/${freeAgent.discord_user.discord_id}`}>
|
||||
{freeAgent.discord_user.username}#
|
||||
{freeAgent.discord_user.discriminator}
|
||||
</Link>
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<Link to={`/u/${freeAgent.discord_user.discord_id}`}>
|
||||
{freeAgent.discord_user.username}#
|
||||
{freeAgent.discord_user.discriminator}
|
||||
</Link>
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{freeAgent.discord_user.country && (
|
||||
<>
|
||||
<Flag name={freeAgent.discord_user.country} />
|
||||
{countries.reduce(
|
||||
(acc, cur) =>
|
||||
cur.code === freeAgent.discord_user.country ? cur.name : acc,
|
||||
""
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Icon name="twitter" size="large" style={{ color: "#1da1f2" }} />
|
||||
<a href={`https://twitter.com/${twitter}`}>{twitter}</a>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<span style={{ color: "#999999" }}>
|
||||
{new Date(parseInt(freeAgent.createdAt)).toLocaleDateString()}
|
||||
</span>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell style={{ borderStyle: "hidden" }}>
|
||||
{freeAgent.discord_user.weapons.map(weapon => (
|
||||
<WpnImage
|
||||
key={weapon}
|
||||
weapon={weapon}
|
||||
size="small"
|
||||
style={{ float: "left" }}
|
||||
/>
|
||||
))}
|
||||
</Table.Cell>
|
||||
<Table.Cell style={{ borderStyle: "hidden" }}>
|
||||
<RoleIcons playstyles={playstyles} />
|
||||
</Table.Cell>
|
||||
<Table.Cell style={{ borderStyle: "hidden" }}>
|
||||
<VCIcon canVC={freeAgent.can_vc} />
|
||||
</Table.Cell>
|
||||
<Table.Cell style={{ borderStyle: "hidden" }}>
|
||||
{freeAgent.discord_user.top500 && (
|
||||
<Link to={`/u/${freeAgent.discord_user.discord_id}?tab=1`}>
|
||||
<img
|
||||
src={top500}
|
||||
style={{ width: "30%", height: "auto" }}
|
||||
alt="top 500"
|
||||
/>
|
||||
</Link>
|
||||
)}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{expanded && <TextRows freeAgent={freeAgent} />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default FATableRows
|
||||
57
react-ui/src/components/freeagents/FreeAgentBrowser.js
vendored
Normal file
57
react-ui/src/components/freeagents/FreeAgentBrowser.js
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import React, { useState } from "react"
|
||||
import NewFAPostForm from "./NewFAPostForm"
|
||||
import { Message, Button } from "semantic-ui-react"
|
||||
import { freeAgentPosts } from "../../graphql/queries/freeAgentPosts"
|
||||
import Loading from "../common/Loading"
|
||||
import Error from "../common/Error"
|
||||
import { useQuery } from "@apollo/react-hooks"
|
||||
import FreeAgentTable from "./FreeAgentTable"
|
||||
|
||||
const FreeAgentBrowser = () => {
|
||||
const [successMsg, setSuccessMsg] = useState(null)
|
||||
const [showForm, setShowForm] = useState(false)
|
||||
|
||||
const { data, error, loading } = useQuery(freeAgentPosts)
|
||||
|
||||
const handleSuccess = () => {
|
||||
setSuccessMsg(
|
||||
"New free agent post successfully created! Good luck finding a team 😎"
|
||||
)
|
||||
setShowForm(false)
|
||||
setTimeout(() => {
|
||||
setSuccessMsg(null)
|
||||
}, 10000)
|
||||
}
|
||||
|
||||
const showButton = () => {
|
||||
if (showForm) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if (loading) return <Loading />
|
||||
if (error) return <Error errorMessage={error.message} />
|
||||
console.log("data", data)
|
||||
|
||||
return (
|
||||
<>
|
||||
{successMsg && <Message success>{successMsg}</Message>}
|
||||
{showButton() && (
|
||||
<Button onClick={() => setShowForm(true)}>New free agent post</Button>
|
||||
)}
|
||||
{showForm && (
|
||||
<NewFAPostForm
|
||||
handleSuccess={handleSuccess}
|
||||
hideForm={() => setShowForm(false)}
|
||||
/>
|
||||
)}
|
||||
{data.freeAgentPosts.length > 0 ? (
|
||||
<FreeAgentTable FAArray={data.freeAgentPosts} />
|
||||
) : (
|
||||
<>No free agents at the moment</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default FreeAgentBrowser
|
||||
17
react-ui/src/components/freeagents/FreeAgentTable.js
vendored
Normal file
17
react-ui/src/components/freeagents/FreeAgentTable.js
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import React from "react"
|
||||
import { Table } from "semantic-ui-react"
|
||||
import FATableRows from "./FATableRows"
|
||||
|
||||
const FreeAgentTable = ({ FAArray }) => {
|
||||
return (
|
||||
<Table basic="very">
|
||||
<Table.Body>
|
||||
{FAArray.map(fa => {
|
||||
return <FATableRows key={fa.id} freeAgent={fa} />
|
||||
})}
|
||||
</Table.Body>
|
||||
</Table>
|
||||
)
|
||||
}
|
||||
|
||||
export default FreeAgentTable
|
||||
185
react-ui/src/components/freeagents/NewFAPostForm.js
vendored
Normal file
185
react-ui/src/components/freeagents/NewFAPostForm.js
vendored
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
import React, { useState } from "react"
|
||||
import {
|
||||
Form,
|
||||
Checkbox,
|
||||
Radio,
|
||||
Message,
|
||||
Button,
|
||||
Header,
|
||||
} from "semantic-ui-react"
|
||||
import { useMutation } from "@apollo/react-hooks"
|
||||
|
||||
import TextAreaWithLimit from "../common/TextAreaWithLimit"
|
||||
import { addFreeAgentPost } from "../../graphql/mutations/addFreeAgentPost"
|
||||
import { freeAgentPosts } from "../../graphql/queries/freeAgentPosts"
|
||||
|
||||
const NewFAPostForm = ({ handleSuccess, hideForm }) => {
|
||||
const [form, setForm] = useState({
|
||||
can_vc: "",
|
||||
playstyles: [],
|
||||
activity: "",
|
||||
past_experience: "",
|
||||
looking_for: "",
|
||||
description: "",
|
||||
})
|
||||
const [errorMsg, setErrorMsg] = useState(null)
|
||||
|
||||
const handleError = error => {
|
||||
setErrorMsg(error.message)
|
||||
setTimeout(() => {
|
||||
setErrorMsg(null)
|
||||
}, 10000)
|
||||
}
|
||||
|
||||
const [addFAPostMutation] = useMutation(addFreeAgentPost, {
|
||||
onError: handleError,
|
||||
onCompleted: handleSuccess,
|
||||
refetchQueries: [
|
||||
{
|
||||
query: freeAgentPosts,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const handlePlaystyleChange = (e, { value }) => {
|
||||
if (form.playstyles.indexOf(value) !== -1) {
|
||||
setForm({
|
||||
...form,
|
||||
playstyles: form.playstyles.filter(playstyle => playstyle !== value),
|
||||
})
|
||||
} else {
|
||||
setForm({ ...form, playstyles: [...form.playstyles, value] })
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async event => {
|
||||
event.preventDefault()
|
||||
const postToAdd = { ...form }
|
||||
//https://stackoverflow.com/a/38340730
|
||||
Object.keys(postToAdd).forEach(
|
||||
key => !postToAdd[key] && delete postToAdd[key]
|
||||
)
|
||||
|
||||
await addFAPostMutation({ variables: postToAdd })
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header>Make a new free agent post</Header>
|
||||
<Message>
|
||||
Discord name, Twitter user, weapon pool and Top 500 history are
|
||||
automatically synced up with your profile.
|
||||
</Message>
|
||||
{errorMsg && <Message error>{errorMsg}</Message>}
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<Form.Field required>
|
||||
<label>Playstyles</label>
|
||||
<Form.Field>
|
||||
<Checkbox
|
||||
label="Frontline/Slayer"
|
||||
value="FRONTLINE"
|
||||
checked={form.playstyles.indexOf("FRONTLINE") !== -1}
|
||||
onChange={handlePlaystyleChange}
|
||||
/>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Checkbox
|
||||
label="Midline/Support"
|
||||
value="MIDLINE"
|
||||
checked={form.playstyles.indexOf("MIDLINE") !== -1}
|
||||
onChange={handlePlaystyleChange}
|
||||
/>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Checkbox
|
||||
label="Backline/Anchor"
|
||||
value="BACKLINE"
|
||||
checked={form.playstyles.indexOf("BACKLINE") !== -1}
|
||||
onChange={handlePlaystyleChange}
|
||||
/>
|
||||
</Form.Field>
|
||||
</Form.Field>
|
||||
<Form.Field required>
|
||||
<label>Can you voice chat?</label>
|
||||
<Form.Field>
|
||||
<Radio
|
||||
label="Yes"
|
||||
value="YES"
|
||||
checked={form.can_vc === "YES"}
|
||||
onChange={() => setForm({ ...form, can_vc: "YES" })}
|
||||
/>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Radio
|
||||
label="Usually"
|
||||
value="USUALLY"
|
||||
checked={form.can_vc === "USUALLY"}
|
||||
onChange={() => setForm({ ...form, can_vc: "USUALLY" })}
|
||||
/>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Radio
|
||||
label="Sometimes"
|
||||
value="SOMETIMES"
|
||||
checked={form.can_vc === "SOMETIMES"}
|
||||
onChange={() => setForm({ ...form, can_vc: "SOMETIMES" })}
|
||||
/>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Radio
|
||||
label="No"
|
||||
value="NO"
|
||||
checked={form.can_vc === "NO"}
|
||||
onChange={() => setForm({ ...form, can_vc: "NO" })}
|
||||
/>
|
||||
</Form.Field>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<label>Past competitive experience</label>
|
||||
<TextAreaWithLimit
|
||||
value={form.past_experience}
|
||||
setValue={value => setForm({ ...form, past_experience: value })}
|
||||
limit={100}
|
||||
style={{ height: "75px" }}
|
||||
/>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<label>What is your activity like on a typical week?</label>
|
||||
<TextAreaWithLimit
|
||||
value={form.activity}
|
||||
setValue={value => setForm({ ...form, activity: value })}
|
||||
limit={100}
|
||||
style={{ height: "75px" }}
|
||||
/>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<label>What are you looking from a team?</label>
|
||||
<TextAreaWithLimit
|
||||
value={form.looking_for}
|
||||
setValue={value => setForm({ ...form, looking_for: value })}
|
||||
limit={100}
|
||||
style={{ height: "75px" }}
|
||||
/>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<label>Free word</label>
|
||||
<TextAreaWithLimit
|
||||
value={form.description}
|
||||
setValue={value => setForm({ ...form, description: value })}
|
||||
limit={1000}
|
||||
/>
|
||||
</Form.Field>
|
||||
<Form.Field>
|
||||
<Button type="submit">Submit</Button>
|
||||
<span style={{ marginLeft: "0.3em" }}>
|
||||
<Button type="button" negative onClick={hideForm}>
|
||||
Cancel
|
||||
</Button>
|
||||
</span>
|
||||
</Form.Field>
|
||||
</Form>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default NewFAPostForm
|
||||
71
react-ui/src/components/freeagents/RoleIcons.js
vendored
Normal file
71
react-ui/src/components/freeagents/RoleIcons.js
vendored
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import React from "react"
|
||||
import { Popup, Icon } from "semantic-ui-react"
|
||||
|
||||
const RoleIcons = ({ playstyles }) => {
|
||||
return (
|
||||
<>
|
||||
<Popup
|
||||
content={
|
||||
<>
|
||||
Frontline/Slayer{" "}
|
||||
{playstyles.FRONTLINE ? (
|
||||
<Icon name="checkmark" color="green" />
|
||||
) : (
|
||||
<Icon name="close" color="red" />
|
||||
)}
|
||||
</>
|
||||
}
|
||||
trigger={
|
||||
<Icon
|
||||
name="crosshairs"
|
||||
size="big"
|
||||
color={playstyles.FRONTLINE ? "green" : null}
|
||||
disabled={!playstyles.FRONTLINE}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Popup
|
||||
content={
|
||||
<>
|
||||
Midline/Support{" "}
|
||||
{playstyles.MIDLINE ? (
|
||||
<Icon name="checkmark" color="green" />
|
||||
) : (
|
||||
<Icon name="close" color="red" />
|
||||
)}
|
||||
</>
|
||||
}
|
||||
trigger={
|
||||
<Icon
|
||||
name="medkit"
|
||||
size="big"
|
||||
color={playstyles.MIDLINE ? "green" : null}
|
||||
disabled={!playstyles.MIDLINE}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Popup
|
||||
content={
|
||||
<>
|
||||
Backline/Anchor{" "}
|
||||
{playstyles.BACKLINE ? (
|
||||
<Icon name="checkmark" color="green" />
|
||||
) : (
|
||||
<Icon name="close" color="red" />
|
||||
)}
|
||||
</>
|
||||
}
|
||||
trigger={
|
||||
<Icon
|
||||
name="anchor"
|
||||
size="big"
|
||||
color={playstyles.BACKLINE ? "green" : null}
|
||||
disabled={!playstyles.BACKLINE}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default RoleIcons
|
||||
72
react-ui/src/components/freeagents/TextRows.js
vendored
Normal file
72
react-ui/src/components/freeagents/TextRows.js
vendored
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import React from "react"
|
||||
import { Table } from "semantic-ui-react"
|
||||
|
||||
const TextRows = ({ freeAgent }) => {
|
||||
const { activity, description, looking_for, past_experience } = freeAgent
|
||||
|
||||
const hiddenBorder = { borderStyle: "hidden" }
|
||||
return (
|
||||
<>
|
||||
{activity && (
|
||||
<>
|
||||
<Table.Row>
|
||||
<Table.Cell style={hiddenBorder}></Table.Cell>
|
||||
<Table.Cell colSpan={4} style={hiddenBorder}>
|
||||
<h4>Activity</h4>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell></Table.Cell>
|
||||
<Table.Cell colSpan={4}>{activity}</Table.Cell>
|
||||
</Table.Row>
|
||||
</>
|
||||
)}
|
||||
{past_experience && (
|
||||
<>
|
||||
<Table.Row style={hiddenBorder}>
|
||||
<Table.Cell style={hiddenBorder}></Table.Cell>
|
||||
<Table.Cell colSpan={4} style={{ borderStyle: "hidden" }}>
|
||||
<h4>Past experience</h4>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row style={{ border: "0" }}>
|
||||
<Table.Cell></Table.Cell>
|
||||
<Table.Cell colSpan={4}>{past_experience}</Table.Cell>
|
||||
</Table.Row>
|
||||
</>
|
||||
)}
|
||||
{looking_for && (
|
||||
<>
|
||||
<Table.Row>
|
||||
<Table.Cell style={hiddenBorder}></Table.Cell>
|
||||
<Table.Cell colSpan={4} style={hiddenBorder}>
|
||||
<h4>Looking for</h4>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell></Table.Cell>
|
||||
<Table.Cell colSpan={4}>{looking_for}</Table.Cell>
|
||||
</Table.Row>
|
||||
</>
|
||||
)}
|
||||
{description && (
|
||||
<>
|
||||
<Table.Row>
|
||||
<Table.Cell style={hiddenBorder}></Table.Cell>
|
||||
<Table.Cell colSpan={4} style={hiddenBorder}>
|
||||
<h4>Description</h4>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell></Table.Cell>
|
||||
<Table.Cell colSpan={4} style={{ whiteSpace: "pre-wrap" }}>
|
||||
{description}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TextRows
|
||||
21
react-ui/src/components/freeagents/VCIcon.js
vendored
Normal file
21
react-ui/src/components/freeagents/VCIcon.js
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import React from "react"
|
||||
import { Popup, Icon } from "semantic-ui-react"
|
||||
|
||||
const VCIcon = ({ canVC }) => {
|
||||
const iconColor = () => {
|
||||
if (canVC === "YES") return "green"
|
||||
else if (canVC === "NO") return "red"
|
||||
|
||||
return "yellow"
|
||||
}
|
||||
|
||||
const canVCCapitalized = canVC.charAt(0) + canVC.toLowerCase().slice(1)
|
||||
return (
|
||||
<Popup
|
||||
content={`Voice chat: ${canVCCapitalized}`}
|
||||
trigger={<Icon name="microphone" size="big" color={iconColor()} />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default VCIcon
|
||||
2
react-ui/src/components/root/App.js
vendored
2
react-ui/src/components/root/App.js
vendored
|
|
@ -12,7 +12,7 @@ const App = () => {
|
|||
<div
|
||||
style={{
|
||||
background: "white",
|
||||
padding: "2% 5%",
|
||||
padding: "2em 3em",
|
||||
margin: "0 -2em 0 -2em",
|
||||
borderRadius: "7px",
|
||||
}}
|
||||
|
|
|
|||
4
react-ui/src/components/root/Routes.js
vendored
4
react-ui/src/components/root/Routes.js
vendored
|
|
@ -20,6 +20,7 @@ const TournamentSearchPage = lazy(() =>
|
|||
import("../tournament/TournamentSearchPage")
|
||||
)
|
||||
const BuildsBrowser = lazy(() => import("../builds/BuildsBrowser"))
|
||||
const FreeAgentBrowser = lazy(() => import("../freeagents/FreeAgentBrowser"))
|
||||
const UserPage = lazy(() => import("../user/UserPage"))
|
||||
const InfoPage = lazy(() => import("./InfoPage"))
|
||||
|
||||
|
|
@ -66,6 +67,9 @@ const Routes = () => {
|
|||
<Route path="/builds">
|
||||
<BuildsBrowser />
|
||||
</Route>
|
||||
<Route path="/freeagents">
|
||||
<FreeAgentBrowser />
|
||||
</Route>
|
||||
<Route path="/u/:id">
|
||||
<UserPage />
|
||||
</Route>
|
||||
|
|
|
|||
12
react-ui/src/components/user/AddBuildForm.js
vendored
12
react-ui/src/components/user/AddBuildForm.js
vendored
|
|
@ -1,9 +1,10 @@
|
|||
import React, { useState, useEffect, useRef } from "react"
|
||||
import { Form, Message, Button, TextArea } from "semantic-ui-react"
|
||||
import { Form, Message, Button } from "semantic-ui-react"
|
||||
import BuildCard from "../common/BuildCard"
|
||||
import AbilityButtons from "./AbilityButtons"
|
||||
import WeaponDropdown from "../common/WeaponDropdown"
|
||||
import GearSearch from "./GearSearch"
|
||||
import TextAreaWithLimit from "../common/TextAreaWithLimit"
|
||||
|
||||
const AddBuildForm = ({
|
||||
addBuild,
|
||||
|
|
@ -145,14 +146,11 @@ const AddBuildForm = ({
|
|||
</Form.Field>
|
||||
<Form.Field>
|
||||
<label>Description</label>
|
||||
<TextArea
|
||||
<TextAreaWithLimit
|
||||
value={description}
|
||||
onChange={e =>
|
||||
e.target.value.length < 1000 && setDescription(e.target.value)
|
||||
}
|
||||
rows={5}
|
||||
setValue={setDescription}
|
||||
limit={1000}
|
||||
/>
|
||||
{`${description.length}/1000`}
|
||||
</Form.Field>
|
||||
<Form.Group widths="equal">
|
||||
<Form.Field>
|
||||
|
|
|
|||
3
react-ui/src/components/user/BuildTab.js
vendored
3
react-ui/src/components/user/BuildTab.js
vendored
|
|
@ -20,8 +20,7 @@ const BuildTab = ({ user, userViewed }) => {
|
|||
const [showForm, setShowForm] = useState(false)
|
||||
|
||||
const handleError = error => {
|
||||
console.log("error", error)
|
||||
setErrorMsg(error.graphQLErrors[0].message)
|
||||
setErrorMsg(error.message)
|
||||
setTimeout(() => {
|
||||
setErrorMsg(null)
|
||||
}, 10000)
|
||||
|
|
|
|||
21
react-ui/src/graphql/mutations/addFreeAgentPost.js
vendored
Normal file
21
react-ui/src/graphql/mutations/addFreeAgentPost.js
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { gql } from "apollo-boost"
|
||||
|
||||
export const addFreeAgentPost = gql`
|
||||
mutation addFreeAgentPost(
|
||||
$can_vc: CanVC!
|
||||
$playstyles: [Playstyle!]
|
||||
$activity: String
|
||||
$looking_for: String
|
||||
$past_experience: String
|
||||
$description: String
|
||||
) {
|
||||
addFreeAgentPost(
|
||||
can_vc: $can_vc
|
||||
playstyles: $playstyles
|
||||
activity: $activity
|
||||
looking_for: $looking_for
|
||||
past_experience: $past_experience
|
||||
description: $description
|
||||
)
|
||||
}
|
||||
`
|
||||
26
react-ui/src/graphql/queries/freeAgentPosts.js
vendored
Normal file
26
react-ui/src/graphql/queries/freeAgentPosts.js
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { gql } from "apollo-boost"
|
||||
|
||||
export const freeAgentPosts = gql`
|
||||
{
|
||||
freeAgentPosts {
|
||||
id
|
||||
can_vc
|
||||
playstyles
|
||||
activity
|
||||
looking_for
|
||||
past_experience
|
||||
description
|
||||
hidden
|
||||
createdAt
|
||||
discord_user {
|
||||
username
|
||||
discriminator
|
||||
discord_id
|
||||
twitter_name
|
||||
country
|
||||
weapons
|
||||
top500
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
@ -11,6 +11,7 @@ const { User, userResolvers } = require("./schemas/user")
|
|||
const { Link, linkResolvers } = require("./schemas/link")
|
||||
const { Trend, trendResolvers } = require("./schemas/trend")
|
||||
const { Tournament, tournamentResolvers } = require("./schemas/tournament")
|
||||
const { FAPost, faPostResolvers } = require("./schemas/fapost")
|
||||
|
||||
const Query = gql`
|
||||
type Query {
|
||||
|
|
@ -38,7 +39,8 @@ const schema = makeExecutableSchema({
|
|||
User,
|
||||
Link,
|
||||
Trend,
|
||||
Tournament
|
||||
Tournament,
|
||||
FAPost,
|
||||
],
|
||||
resolvers: merge(
|
||||
resolvers,
|
||||
|
|
@ -50,8 +52,9 @@ const schema = makeExecutableSchema({
|
|||
userResolvers,
|
||||
linkResolvers,
|
||||
trendResolvers,
|
||||
tournamentResolvers
|
||||
)
|
||||
tournamentResolvers,
|
||||
faPostResolvers
|
||||
),
|
||||
})
|
||||
|
||||
module.exports = schema
|
||||
|
|
|
|||
119
schemas/fapost.js
Normal file
119
schemas/fapost.js
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
const { UserInputError, gql } = require("apollo-server-express")
|
||||
const FAPost = require("../models/fapost")
|
||||
|
||||
const canVCValues = ["YES", "USUALLY", "SOMETIMES", "NO"]
|
||||
const playstyleValues = ["FRONTLINE", "MIDLINE", "BACKLINE"]
|
||||
|
||||
const typeDef = gql`
|
||||
extend type Query {
|
||||
freeAgentPosts: [FAPost!]!
|
||||
}
|
||||
|
||||
extend type Mutation {
|
||||
addFreeAgentPost(
|
||||
can_vc: CanVC!
|
||||
playstyles: [Playstyle!]
|
||||
activity: String
|
||||
looking_for: String
|
||||
past_experience: String
|
||||
description: String
|
||||
): Boolean!
|
||||
}
|
||||
|
||||
enum CanVC {
|
||||
YES
|
||||
USUALLY
|
||||
SOMETIMES
|
||||
NO
|
||||
}
|
||||
|
||||
enum Playstyle {
|
||||
FRONTLINE
|
||||
MIDLINE
|
||||
BACKLINE
|
||||
}
|
||||
|
||||
"Represents a free agent post of a player looking for a team"
|
||||
type FAPost {
|
||||
id: ID!
|
||||
discord_id: String!
|
||||
can_vc: CanVC!
|
||||
playstyles: [Playstyle!]
|
||||
"How active is the free agent"
|
||||
activity: String
|
||||
"What kind of team they are looking for"
|
||||
looking_for: String
|
||||
"Teams or other past experience in competitive"
|
||||
past_experience: String
|
||||
"Free word about anything else"
|
||||
description: String
|
||||
discord_user: User!
|
||||
hidden: Boolean!
|
||||
createdAt: String!
|
||||
}
|
||||
`
|
||||
const resolvers = {
|
||||
Query: {
|
||||
freeAgentPosts: (root, args) => {
|
||||
return FAPost.find({})
|
||||
.populate("discord_user")
|
||||
.sort({ createdAt: "desc" })
|
||||
.catch(e => {
|
||||
throw new UserInputError(e.message, {
|
||||
invalidArgs: args,
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
Mutation: {
|
||||
addFreeAgentPost: async (root, args, ctx) => {
|
||||
if (!ctx.user) throw new AuthenticationError("Not logged in.")
|
||||
|
||||
if (canVCValues.indexOf(args.can_vc) === -1) {
|
||||
throw new UserInputError("Invalid 'can vc' value provided.")
|
||||
}
|
||||
|
||||
const playstyles = args.playstyles ? [...new Set(args.playstyles)] : []
|
||||
if (
|
||||
playstyles.some(playstyle => playstyleValues.indexOf(playstyle) === -1)
|
||||
) {
|
||||
throw new UserInputError("Invalid 'playstyles' value provided.")
|
||||
}
|
||||
|
||||
if (args.activity && args.activity.length > 100) {
|
||||
throw new UserInputError("'activity' value too long.")
|
||||
}
|
||||
|
||||
if (args.looking_for && args.looking_for.length > 100) {
|
||||
throw new UserInputError("'looking_for' value too long.")
|
||||
}
|
||||
|
||||
if (args.past_experience && args.past_experience.length > 100) {
|
||||
throw new UserInputError("'past_experience' value too long.")
|
||||
}
|
||||
|
||||
if (args.past_experience && args.past_experience.length > 100) {
|
||||
throw new UserInputError("'past_experience' value too long.")
|
||||
}
|
||||
|
||||
if (args.description && args.description.length > 1000) {
|
||||
throw new UserInputError("'description' value too long.")
|
||||
}
|
||||
|
||||
const faPost = new FAPost({ ...args, discord_id: ctx.user.discord_id })
|
||||
await faPost.save().catch(e => {
|
||||
throw (new UserInputError(),
|
||||
{
|
||||
invalidArgs: args,
|
||||
})
|
||||
})
|
||||
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
FAPost: typeDef,
|
||||
faPostResolvers: resolvers,
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
const { UserInputError, gql } = require("apollo-server-express")
|
||||
const User = require("../models/user")
|
||||
const Player = require("../models/player")
|
||||
const countries = require("../utils/countries")
|
||||
const weapons = require("../utils/weapons")
|
||||
require("dotenv").config()
|
||||
|
|
@ -38,9 +39,32 @@ const typeDef = gql`
|
|||
country: String
|
||||
sens: Sens
|
||||
weapons: [String]!
|
||||
top500: Boolean!
|
||||
}
|
||||
`
|
||||
const resolvers = {
|
||||
User: {
|
||||
top500: async root => {
|
||||
if (typeof root.top500 === "boolean") return root.top500
|
||||
|
||||
const player = await Player.findOne({ twitter: root.twitter_name }).catch(
|
||||
e => {
|
||||
throw (new UserInputError(),
|
||||
{
|
||||
invalidArgs: args,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
if (!player) {
|
||||
await User.findByIdAndUpdate(root._id, { top500: false })
|
||||
return false
|
||||
}
|
||||
|
||||
await User.findByIdAndUpdate(root._id, { top500: true })
|
||||
return true
|
||||
},
|
||||
},
|
||||
Query: {
|
||||
user: (root, args, ctx) => {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
|
|
|
|||
|
|
@ -1,59 +0,0 @@
|
|||
const { UserInputError, gql } = require('apollo-server-express')
|
||||
const Video = require('../models/video')
|
||||
|
||||
const typeDef = gql`
|
||||
extend type Query {
|
||||
youtubeInfoById(youtube_id: String!): YouTubeInfo
|
||||
|
||||
}
|
||||
enum MatchType {
|
||||
RANK
|
||||
XRANK
|
||||
TURF
|
||||
TOURNAMENT
|
||||
SCRIM
|
||||
SPEC
|
||||
}
|
||||
enum Status {
|
||||
PENDING
|
||||
REJECTED
|
||||
APPROVED
|
||||
}
|
||||
"Represents single match (one video can have multiple)-"
|
||||
type Video {
|
||||
"ID of the video on YouTube"
|
||||
youtube_id: String!
|
||||
video_title: String!
|
||||
"Timestamp in second of the match in the video"
|
||||
match_begin_timestamp: Int!
|
||||
"Timestamp when the match was uploaded to YouTube"
|
||||
upload_timestamp: Int!
|
||||
map: String!
|
||||
mode: String!
|
||||
weapon: String
|
||||
"Only if the player in the video has reached Top 500."
|
||||
unique_id: String
|
||||
status: Status!
|
||||
"User ID of the user who submitted the video."
|
||||
submitter_id: String!
|
||||
match_type: MatchType!
|
||||
"True if the player in the match has reached Top 500 with the weapon in the match."
|
||||
top500: Boolean
|
||||
spec_pov: Boolean!
|
||||
}
|
||||
type YouTubeInfo {
|
||||
|
||||
}
|
||||
`
|
||||
const resolvers = {
|
||||
Query: {
|
||||
youtubeInfoById: (root, args) => {
|
||||
const youtube_id = args.youtube_id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Video: typeDef,
|
||||
videoResolvers: resolvers
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user