falikes first fully working version

This commit is contained in:
Sendou 2020-03-23 01:06:14 +02:00
parent 7f18a91912
commit df9d93cf0c
12 changed files with 375 additions and 37 deletions

View File

@ -9,6 +9,7 @@ interface IconButtonProps {
onClick?: () => void
colored?: boolean
disabled?: boolean
color?: string
}
const IconButton: React.FC<IconButtonProps> = ({
@ -16,6 +17,7 @@ const IconButton: React.FC<IconButtonProps> = ({
icon,
disabled,
onClick,
color,
}) => {
const { themeColor } = useContext(MyThemeContext)
return (
@ -27,6 +29,7 @@ const IconButton: React.FC<IconButtonProps> = ({
onClick={onClick}
size="lg"
variantColor={colored ? themeColor : undefined}
color={color}
isDisabled={disabled}
/>
)

View File

@ -25,6 +25,7 @@ import { FREE_AGENT_POSTS } from "../../graphql/queries/freeAgentPosts"
import { UPDATE_FREE_AGENT_POST } from "../../graphql/mutations/updateFreeAgentPost"
import { HIDE_FREE_AGENT_POST } from "../../graphql/mutations/hideFreeAgentPost"
import Alert from "../elements/Alert"
import { FREE_AGENT_MATCHES } from "../../graphql/queries/freeAgentMatches"
interface FAPostModalProps {
closeModal: () => void
@ -88,7 +89,10 @@ const FAPostModal: React.FC<FAPostModalProps> = ({ closeModal, post }) => {
duration: 10000,
})
},
refetchQueries: [{ query: FREE_AGENT_POSTS }],
refetchQueries: [
{ query: FREE_AGENT_POSTS },
{ query: FREE_AGENT_MATCHES },
],
}
)

View File

@ -1,6 +1,13 @@
import React, { useContext, useState } from "react"
import { Flex, Box, Image, IconButton, Heading } from "@chakra-ui/core"
import { FreeAgentPost } from "../../types"
import {
Flex,
Box,
Image,
IconButton,
Heading,
useToast,
} from "@chakra-ui/core"
import { FreeAgentPost, UserData } from "../../types"
import UserAvatar from "../common/UserAvatar"
import { Link } from "@reach/router"
import MyThemeContext from "../../themeContext"
@ -11,9 +18,17 @@ import Flag from "../common/Flag"
import RoleIcons from "./RoleIcons"
import WeaponImage from "../common/WeaponImage"
import VCIcon from "./VCIcon"
import Heart from "./Heart"
import { useMutation, useQuery } from "@apollo/react-hooks"
import { ADD_LIKE } from "../../graphql/mutations/addLike"
import { FREE_AGENT_MATCHES } from "../../graphql/queries/freeAgentMatches"
import { DELETE_LIKE } from "../../graphql/mutations/deleteLike"
import { USER } from "../../graphql/queries/user"
interface FreeAgentCardProps {
post: FreeAgentPost
canLike: boolean
likedUsersIds: string[]
}
const hasExtraInfo = (post: FreeAgentPost) => {
@ -25,11 +40,75 @@ const hasExtraInfo = (post: FreeAgentPost) => {
return true
}
const FreeAgentCard: React.FC<FreeAgentCardProps> = ({ post }) => {
const FreeAgentCard: React.FC<FreeAgentCardProps> = ({
post,
canLike,
likedUsersIds,
}) => {
const [expanded, setExpanded] = useState(false)
const { grayWithShade, themeColorWithShade } = useContext(MyThemeContext)
const { discord_user } = post
const canBeExpanded = hasExtraInfo(post)
const toast = useToast()
const { data } = useQuery<UserData>(USER)
const [addLike] = useMutation<{ addLike: boolean }, { discord_id: string }>(
ADD_LIKE,
{
onCompleted: data => {
toast({
description: `Liked a free agent post by ${post.discord_user.username}`,
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: FREE_AGENT_MATCHES }],
}
)
const [deleteLike] = useMutation<
{ deleteLike: boolean },
{ discord_id: string }
>(DELETE_LIKE, {
onCompleted: data => {
toast({
description: `Unliked a free agent post by ${post.discord_user.username}`,
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: FREE_AGENT_MATCHES }],
})
const liked = likedUsersIds.indexOf(discord_user.discord_id) !== -1
const handleHeartClick = () => {
if (liked)
deleteLike({ variables: { discord_id: discord_user.discord_id } })
else addLike({ variables: { discord_id: discord_user.discord_id } })
}
return (
<Flex
rounded="lg"
@ -55,7 +134,7 @@ const FreeAgentCard: React.FC<FreeAgentCardProps> = ({ post }) => {
)}
</Box>
</Flex>
<Flex flexDirection="column" alignItems="center">
<Flex flexDirection="column" alignItems="center" justifyContent="center">
<UserAvatar
twitterName={discord_user.twitter_name}
name={discord_user.username}
@ -108,20 +187,29 @@ const FreeAgentCard: React.FC<FreeAgentCardProps> = ({ post }) => {
<VCIcon canVC={post.can_vc} />
</Box>
</Flex>
{canBeExpanded && (
<Flex mt="2em" justifyContent="center">
<Flex flexDirection="column" mt="2em" justifyContent="center">
{data &&
data.user &&
data.user.discord_id !== discord_user.discord_id && (
<Heart
disabled={!canLike}
active={liked}
onClick={() => handleHeartClick()}
/>
)}
{canBeExpanded && (
<IconButton
variant="ghost"
aria-label="More information"
isRound
fontSize="20px"
size="lg"
icon={expanded ? FaMinus : FaPlus}
onClick={() => setExpanded(!expanded)}
/>
</Flex>
)}
)}
</Flex>
{expanded && (
<Box whiteSpace="pre-wrap">
<Box whiteSpace="pre-wrap" mt="0.5em">
{post.activity && (
<Box>
<Heading size="md" color={themeColorWithShade}>

View File

@ -22,6 +22,11 @@ import { Collapse, Flex } from "@chakra-ui/core"
import Button from "../elements/Button"
import { FaFilter } from "react-icons/fa"
import FAPostModal from "./FAPostModal"
import {
FreeAgentMatchesData,
FREE_AGENT_MATCHES,
} from "../../graphql/queries/freeAgentMatches"
import Matches from "./Matches"
const playstyleToEnum = {
"Frontline/Slayer": "FRONTLINE",
@ -47,15 +52,19 @@ const FreeAgentsPage: React.FC<RouteComponentProps> = () => {
error: userQueryError,
loading: userQueryLoading,
} = useQuery<UserData>(USER)
const { data: matchesData, error: matchesError } = useQuery<
FreeAgentMatchesData
>(FREE_AGENT_MATCHES)
if (error) return <Error errorMessage={error.message} />
if (loading || userQueryLoading || !data || !userData) return <Loading />
if (userQueryError) return <Error errorMessage={userQueryError.message} />
if (matchesError) return <Error errorMessage={matchesError.message} />
if (loading || userQueryLoading) return <Loading />
const faPosts = data.freeAgentPosts
const faPosts = data!.freeAgentPosts
const ownFAPost = faPosts.find(
post => post.discord_user.discord_id === userData.user?.discord_id
post => post.discord_user.discord_id === userData!.user?.discord_id
)
const buttonText =
@ -106,7 +115,7 @@ const FreeAgentsPage: React.FC<RouteComponentProps> = () => {
}
const showModalButton = () => {
if (!userData.user) return false
if (!userData!.user) return false
if (ownFAPost && ownFAPost.hidden) {
const weekFromCreatingFAPost = parseInt(ownFAPost.createdAt) + 604800000
@ -173,7 +182,23 @@ const FreeAgentsPage: React.FC<RouteComponentProps> = () => {
/>
</Box>
</Collapse>
<Posts posts={faPosts.filter(postsFilter)} />
{matchesData && (
<Box mt="1em">
<Matches
matches={matchesData.freeAgentMatches.matched_discord_users}
likesReceived={
matchesData.freeAgentMatches.number_of_likes_received
}
/>
</Box>
)}
<Posts
posts={faPosts.filter(postsFilter)}
canLike={!!(ownFAPost && !ownFAPost.hidden)}
likedUsersIds={
!matchesData ? [] : matchesData.freeAgentMatches.liked_discord_ids
}
/>
</>
)
}

View File

@ -0,0 +1,46 @@
import React, { useContext } from "react"
import IconButton from "../elements/IconButton"
import { FaRegHeart, FaHeart } from "react-icons/fa"
import { Popover, PopoverTrigger, PopoverContent, Flex } from "@chakra-ui/core"
import MyThemeContext from "../../themeContext"
interface HeartProps {
disabled: boolean
active: boolean
onClick: () => void
}
const Heart: React.FC<HeartProps> = ({ disabled, active, onClick }) => {
const { darkerBgColor } = useContext(MyThemeContext)
const getPopoverContent = () => {
if (active)
return "You have liked this free agent! If they also give you a like match will be shown on the top."
if (disabled)
return "Make your own free agent post to like and have a chance to match up with this player!"
return "If you like what you see give this free agent a like and see if they want to team up with you as well!"
}
return (
<>
<Popover trigger="hover" placement="top-start">
<PopoverTrigger>
<Flex justifyContent="center">
<IconButton
colored
disabled={disabled}
icon={active ? FaHeart : FaRegHeart}
color="red.500"
onClick={onClick}
/>
</Flex>
</PopoverTrigger>
<PopoverContent zIndex={4} p="0.5em" bg={darkerBgColor}>
{getPopoverContent()}
</PopoverContent>
</Popover>
</>
)
}
export default Heart

View File

@ -0,0 +1,115 @@
import React, { useContext } from "react"
import FieldsetWithLegend from "../common/FieldsetWithLegend"
import {
Flex,
Box,
Popover,
PopoverTrigger,
PopoverContent,
PopoverArrow,
} from "@chakra-ui/core"
import UserAvatar from "../common/UserAvatar"
import MyThemeContext from "../../themeContext"
interface MatchesProps {
matches: {
username: string
discriminator: string
twitter_name?: string
}[]
likesReceived: number
}
const Matches: React.FC<MatchesProps> = ({ matches, likesReceived }) => {
const { grayWithShade, themeColorWithShade, darkerBgColor } = useContext(
MyThemeContext
)
if (matches.length === 0 && likesReceived === 0) return null
const unrequitedLove = likesReceived - matches.length
const getText1 = () => {
if (unrequitedLove > 1 && matches.length > 0) return "Also there are "
if (unrequitedLove > 1 && matches.length === 0) return "There are "
if (unrequitedLove === 1 && matches.length > 0) return "Also there is "
return "There is "
}
const text1 = getText1()
const text2 =
unrequitedLove > 1 ? (
<>{unrequitedLove} free agents</>
) : (
<>{unrequitedLove} free agent</>
)
const text3 =
unrequitedLove > 1 ? (
<>that liked you but you didn't like any of them back yet!</>
) : (
<>that liked you but you didn't like them back yet!</>
)
return (
<Flex justifyContent="center">
<FieldsetWithLegend title="MATCHES" titleFontSize="xs">
{matches.length > 0 ? (
<>
<Flex justifyContent="center" flexWrap="wrap">
{matches.map(match => (
<Box key={`${match.username}${match.discriminator}`} p="0.5em">
<Popover trigger="hover">
<PopoverTrigger>
<Box>
<UserAvatar
twitterName={match.twitter_name}
name={match.username}
/>
</Box>
</PopoverTrigger>
<PopoverContent zIndex={4} p="0.5em" bg={darkerBgColor}>
<PopoverArrow />
<Box>
You have a match with{" "}
<b>
{match.username}#{match.discriminator}
</b>
! Contact them to see if you can get a team going
</Box>
</PopoverContent>
</Popover>
</Box>
))}
</Flex>
{unrequitedLove > 0 && (
<Box
color={grayWithShade}
mt="1em"
fontWeight="bold"
textAlign="center"
>
{text1}
<Box as="span" color={themeColorWithShade}>
{text2}
</Box>{" "}
{text3}
</Box>
)}
</>
) : (
unrequitedLove > 0 && (
<Box color={grayWithShade} fontWeight="bold" textAlign="center">
{text1}
<Box as="span" color={themeColorWithShade}>
{text2}
</Box>{" "}
{text3}
</Box>
)
)}
</FieldsetWithLegend>
</Flex>
)
}
export default Matches

View File

@ -8,9 +8,15 @@ import Button from "../elements/Button"
interface PostsAccordionProps {
posts: FreeAgentPost[]
canLike: boolean
likedUsersIds: string[]
}
const Posts: React.FC<PostsAccordionProps> = ({ posts }) => {
const Posts: React.FC<PostsAccordionProps> = ({
posts,
canLike,
likedUsersIds,
}) => {
const [agentsToShow, setAgentsToShow] = useState(5)
if (posts.length === 0) {
@ -30,7 +36,11 @@ const Posts: React.FC<PostsAccordionProps> = ({ posts }) => {
.filter((post, index) => index < agentsToShow)
.map(post => (
<Box my="1em" key={post.id}>
<FreeAgentCard post={post} />
<FreeAgentCard
post={post}
canLike={canLike}
likedUsersIds={likedUsersIds}
/>
</Box>
))}
</InfiniteScroll>

View File

@ -0,0 +1,7 @@
import { gql, DocumentNode } from "apollo-boost"
export const ADD_LIKE: DocumentNode = gql`
mutation addLike($discord_id: String!) {
addLike(discord_id: $discord_id)
}
`

View File

@ -0,0 +1,7 @@
import { gql, DocumentNode } from "apollo-boost"
export const DELETE_LIKE: DocumentNode = gql`
mutation deleteLike($discord_id: String!) {
deleteLike(discord_id: $discord_id)
}
`

View File

@ -0,0 +1,27 @@
import { gql, DocumentNode } from "apollo-boost"
export interface FreeAgentMatchesData {
freeAgentMatches: {
matched_discord_users: {
username: string
discriminator: string
twitter_name?: string
}[]
number_of_likes_received: number
liked_discord_ids: string[]
}
}
export const FREE_AGENT_MATCHES: DocumentNode = gql`
{
freeAgentMatches {
matched_discord_users {
username
discriminator
twitter_name
}
number_of_likes_received
liked_discord_ids
}
}
`

View File

@ -52,8 +52,9 @@ const typeDef = gql`
}
type FAMatches {
matched_discord_ids: [String!]!
matched_discord_users: [User!]!
number_of_likes_received: Int!
liked_discord_ids: [String!]!
}
"Represents a free agent post of a player looking for a team"
@ -125,12 +126,12 @@ const resolvers = {
const post = await FAPost.findOne({ discord_id: ctx.user.discord_id })
if (!post) {
throw new UserInputError("Not a free agent")
return { matched_discord_ids: [], number_of_likes_received: 0 }
}
const likesGiven = await FALike.find({
liker_discord_id: ctx.user.discord_id,
})
}).populate("liked_discord_user")
const likesReceived = await FALike.find({
liked_discord_id: ctx.user.discord_id,
@ -141,13 +142,18 @@ const resolvers = {
FALike => FALike.liker_discord_id
)
const matched_discord_ids = likesGiven.reduce((acc, cur) => {
if (likerDiscordIds.indexOf(cur.liked_discord_id) === -1) return acc
const matched_discord_users = likesGiven.reduce((acc, cur) => {
if (likerDiscordIds.indexOf(cur.liked_discord_user.discord_id) === -1)
return acc
return [...acc, cur.liked_discord_id]
return [...acc, cur.liked_discord_user]
}, [])
return { matched_discord_ids, number_of_likes_received }
return {
matched_discord_users,
number_of_likes_received,
liked_discord_ids: likesGiven.map(like => like.liked_discord_id),
}
},
},
Mutation: {
@ -247,6 +253,10 @@ const resolvers = {
addLike: async (root, args, ctx) => {
if (!ctx.user) throw new AuthenticationError("Not logged in.")
if (ctx.user.discord_id === args.discord_id) {
throw new UserInputError("Can't like your own free agent post")
}
const post = await FAPost.findOne({ discord_id: ctx.user.discord_id })
if (!post) {
@ -259,21 +269,17 @@ const resolvers = {
throw new UserInputError("Liked user not a free agent")
}
await FALike.findOneAndUpdate(
{ liker_discord_id: ctx.user.discord_id },
{
liker_discord_id: ctx.user.discord_id,
liked_discord_id: args.discord_id,
},
{ upsert: true }
)
await FALike.create({
liker_discord_id: ctx.user.discord_id,
liked_discord_id: args.discord_id,
})
return true
},
deleteLike: async (root, args, ctx) => {
if (!ctx.user) throw new AuthenticationError("Not logged in.")
await FALike.deleteOne({ liked_discord_id: args.discord_id })
await FALike.deleteMany({ liked_discord_id: args.discord_id })
return true
},

View File

@ -5,11 +5,11 @@ const faLikeSchema = new mongoose.Schema({
liked_discord_id: String,
})
/*mapBallotSchema.virtual("liker_discord_id", {
faLikeSchema.virtual("liked_discord_user", {
ref: "User",
localField: "discord_id",
localField: "liked_discord_id",
foreignField: "discord_id",
justOne: true,
})*/
})
module.exports = mongoose.model("FALike", faLikeSchema)