free agents infinite scroll

This commit is contained in:
Sendou 2020-03-09 14:39:32 +02:00
parent 845821bf16
commit 011f75a785
7 changed files with 282 additions and 269 deletions

View File

@ -1,7 +1,5 @@
import React, { useContext } from "react"
import React from "react"
import "./Pagination.css"
import { Box } from "@chakra-ui/core"
import MyThemeContext from "../../themeContext"
import ReactPaginate from "react-paginate"
interface PaginationProps {
@ -10,76 +8,26 @@ interface PaginationProps {
onChange: (page: number) => void
}
const Pagination: React.FC<PaginationProps> = ({
pageCount,
currentPage,
onChange,
}) => {
const { colorMode, themeColorHex } = useContext(MyThemeContext)
const Pagination: React.FC<PaginationProps> = ({ pageCount, onChange }) => {
return (
<>
{/*<Box className="pagination">
<Box
as="span"
style={{
color: colorMode === "light" ? "black" : "white",
borderBottomColor: themeColorHex,
}}
onClick={() => onChange(currentPage === 1 ? 1 : currentPage - 1)}
>
&laquo;
</Box>
{[...Array(Math.min(pageCount, 6))].map((page, index) => {
const pageNumber = index + currentPage
return (
<Box
as="span"
className={pageNumber === currentPage ? "active" : undefined}
style={{
color: colorMode === "light" ? "black" : "white",
borderBottomColor: themeColorHex,
}}
key={pageNumber}
onClick={() => onChange(pageNumber)}
>
{pageNumber}
</Box>
)
})}
<Box
as="span"
style={{
color: colorMode === "light" ? "black" : "white",
borderBottomColor: themeColorHex,
}}
onClick={() =>
onChange(currentPage === pageCount ? pageCount : currentPage + 1)
}
>
&raquo;
</Box>
</Box>*/}
<ReactPaginate
previousLabel={<>&laquo;</>}
nextLabel={<>&raquo;</>}
breakLabel={"..."}
breakClassName={"page"}
pageCount={pageCount}
marginPagesDisplayed={2}
pageRangeDisplayed={3}
onPageChange={({ selected }) => onChange(selected)}
containerClassName="pagination"
pageClassName="page"
previousClassName="page"
nextClassName="page"
pageLinkClassName="page"
activeLinkClassName="active-page"
disabledClassName="disabled-page"
//subContainerClassName={'pages pagination'}
activeClassName={"active"}
/>
</>
<ReactPaginate
previousLabel={<>&laquo;</>}
nextLabel={<>&raquo;</>}
breakLabel={"..."}
breakClassName={"page"}
pageCount={pageCount}
marginPagesDisplayed={2}
pageRangeDisplayed={3}
onPageChange={({ selected }) => onChange(selected)}
containerClassName="pagination"
pageClassName="page"
previousClassName="page"
nextClassName="page"
pageLinkClassName="page"
activeLinkClassName="active-page"
disabledClassName="disabled-page"
activeClassName={"active"}
/>
)
}

View File

@ -4,7 +4,7 @@ import { Avatar, BoxProps } from "@chakra-ui/core"
interface UserAvatarProps {
name: string
twitterName?: string
size?: undefined | "2xl" | "xs" | "sm"
size?: undefined | "2xl" | "lg" | "xs" | "sm"
}
const UserAvatar: React.FC<UserAvatarProps & BoxProps> = ({

View File

@ -0,0 +1,170 @@
import React, { useContext, useState } from "react"
import { Flex, Box, Image, IconButton, Heading } from "@chakra-ui/core"
import { FreeAgentPost } from "../../types"
import UserAvatar from "../common/UserAvatar"
import { Link } from "@reach/router"
import MyThemeContext from "../../themeContext"
import { FaTwitter, FaPlus, FaMinus } from "react-icons/fa"
import { top500 } from "../../assets/imageImports"
import { countries } from "../../utils/lists"
import Flag from "../common/Flag"
import RoleIcons from "./RoleIcons"
import WeaponImage from "../common/WeaponImage"
import VCIcon from "./VCIcon"
interface FreeAgentCardProps {
post: FreeAgentPost
}
const hasExtraInfo = (post: FreeAgentPost) => {
const { activity, description, looking_for, past_experience } = post
if (!activity && !description && !looking_for && !past_experience) {
return false
}
return true
}
const FreeAgentCard: React.FC<FreeAgentCardProps> = ({ post }) => {
const [expanded, setExpanded] = useState(false)
const { grayWithShade, themeColorWithShade } = useContext(MyThemeContext)
const { discord_user } = post
const canBeExpanded = hasExtraInfo(post)
return (
<Flex
rounded="lg"
overflow="hidden"
boxShadow="0px 0px 16px 6px rgba(0,0,0,0.1)"
p="25px"
flexDirection="column"
justifyContent="space-between"
maxW="500px"
>
<Flex justifyContent="space-between" flexWrap="wrap" alignItems="center">
<Box color="#999999" width="50px" m="1em">
{new Date(parseInt(post.createdAt)).toLocaleDateString()}
</Box>
<Box width="50px" m="1em">
{discord_user.top500 && (
<Image
src={top500}
alt="x rank top 500 logo"
height="40px"
width="auto"
title="Free agent has reached Top 500 in X Rank"
/>
)}
</Box>
</Flex>
<Flex flexDirection="column" alignItems="center">
<UserAvatar
twitterName={discord_user.twitter_name}
name={discord_user.username}
mr="5px"
size="lg"
/>
<Box
as="span"
fontWeight="semibold"
letterSpacing="wide"
fontSize="md"
mt="0.5em"
>
<Flex alignItems="center">
<Link to={`/u/${discord_user.discord_id}`}>
{discord_user.username}#{discord_user.discriminator}
</Link>
{discord_user.twitter_name && (
<a href={`https://twitter.com/${discord_user.twitter_name}`}>
<Box color="#1DA1F2" as={FaTwitter} ml="0.5em" />
</a>
)}
</Flex>
</Box>
<Box color={grayWithShade}>
{discord_user.country && (
<>
<Flag code={discord_user.country} />
{countries.find(obj => obj.code === discord_user.country)?.name}
</>
)}
</Box>
</Flex>
<Flex flexDirection="column" alignItems="center" mt="1em">
<Flex justifyContent="center" w="250px">
{discord_user?.weapons &&
discord_user.weapons.map(wpn => (
<Box mx="0.3em" key={wpn}>
<WeaponImage englishName={wpn} size="SMALL" />
</Box>
))}
</Flex>
</Flex>
<Flex justifyContent="center" mt="2em">
<Box mr="0.5em">
<RoleIcons playstyles={post.playstyles} />
</Box>
<Box borderLeft="1px solid" borderColor="#999999" ml="0.5em" px="1em">
<VCIcon canVC={post.can_vc} />
</Box>
</Flex>
{canBeExpanded && (
<Flex mt="2em" justifyContent="center">
<IconButton
variant="ghost"
aria-label="More information"
isRound
fontSize="20px"
icon={expanded ? FaMinus : FaPlus}
onClick={() => setExpanded(!expanded)}
/>
</Flex>
)}
{expanded && (
<Box whiteSpace="pre-wrap">
{post.activity && (
<Box>
<Heading size="md" color={themeColorWithShade}>
Activity
</Heading>
{post.activity}
</Box>
)}
{post.looking_for && (
<Box mt={post.activity ? "1em" : undefined}>
<Heading size="md" color={themeColorWithShade}>
Looking for
</Heading>
{post.looking_for}
</Box>
)}
{post.past_experience && (
<Box mt={post.activity || post.looking_for ? "1em" : undefined}>
<Heading size="md" color={themeColorWithShade}>
Past experience
</Heading>
{post.past_experience}
</Box>
)}
{post.description && (
<Box
mt={
post.activity || post.looking_for || post.past_experience
? "1em"
: undefined
}
>
<Heading size="md" color={themeColorWithShade}>
Description
</Heading>
{post.description}
</Box>
)}
</Box>
)}
</Flex>
)
}
export default FreeAgentCard

View File

@ -11,13 +11,15 @@ import { FREE_AGENT_POSTS } from "../../graphql/queries/freeAgentPosts"
import Loading from "../common/Loading"
import Error from "../common/Error"
import { RouteComponentProps } from "@reach/router"
import PostsAccordion from "./PostsAccordion"
import PostsAccordion from "./Posts"
import PageHeader from "../common/PageHeader"
import { Helmet } from "react-helmet-async"
import WeaponSelector from "../common/WeaponSelector"
import RadioGroup from "../elements/RadioGroup"
import Box from "../elements/Box"
import { continents } from "../../utils/lists"
import { Collapse } from "@chakra-ui/core"
import Button from "../elements/Button"
const playstyleToEnum = {
"Frontline/Slayer": "FRONTLINE",
@ -27,6 +29,7 @@ const playstyleToEnum = {
const FreeAgentsPage: React.FC<RouteComponentProps> = () => {
const [weapon, setWeapon] = useState<Weapon | null>(null)
const [show, setShow] = useState(false)
const [playstyle, setPlaystyle] = useState<
"Any" | "Frontline/Slayer" | "Midline/Support" | "Backline/Anchor"
>("Any")
@ -96,35 +99,40 @@ const FreeAgentsPage: React.FC<RouteComponentProps> = () => {
<title>Free Agents | sendou.ink</title>
</Helmet>
<PageHeader title="Free Agents" />
<Box maxW="600px" my="1em">
<RadioGroup
value={playstyle}
setValue={setPlaystyle}
label="Filter by playstyle"
options={[
"Any",
"Frontline/Slayer",
"Midline/Support",
"Backline/Anchor",
]}
/>
</Box>
<Box maxW="600px" my="1em">
<RadioGroup
value={region}
setValue={setRegion}
label="Filter by region"
options={["Any", "Europe", "The Americas", "Oceania", "Other"]}
/>
</Box>
<Box maxW="600px" my="1em">
<WeaponSelector
label="Filter by weapon"
value={weapon}
setValue={(weapon: string) => setWeapon(weapon as Weapon)}
clearable
/>
</Box>
<Button onClick={() => setShow(!show)}>
{show ? "Hide filters" : "Show filters"}
</Button>
<Collapse mt={4} isOpen={show}>
<Box maxW="600px" my="1em">
<RadioGroup
value={playstyle}
setValue={setPlaystyle}
label="Filter by playstyle"
options={[
"Any",
"Frontline/Slayer",
"Midline/Support",
"Backline/Anchor",
]}
/>
</Box>
<Box maxW="600px" my="1em">
<RadioGroup
value={region}
setValue={setRegion}
label="Filter by region"
options={["Any", "Europe", "The Americas", "Oceania", "Other"]}
/>
</Box>
<Box maxW="600px" my="1em">
<WeaponSelector
label="Filter by weapon"
value={weapon}
setValue={(weapon: string) => setWeapon(weapon as Weapon)}
clearable
/>
</Box>
</Collapse>
<PostsAccordion posts={faPosts.filter(postsFilter)} />
</>
)

View File

@ -0,0 +1,52 @@
import React, { useState } from "react"
import { FreeAgentPost } from "../../types"
import { Grid, Box, Heading } from "@chakra-ui/core"
import Alert from "../elements/Alert"
import FreeAgentCard from "./FreeAgentCard"
import InfiniteScroll from "react-infinite-scroller"
import Button from "../elements/Button"
interface PostsAccordionProps {
posts: FreeAgentPost[]
}
const Posts: React.FC<PostsAccordionProps> = ({ posts }) => {
const [agentsToShow, setAgentsToShow] = useState(5)
if (posts.length === 0) {
return (
<Alert status="info">No free agents found with the current filter</Alert>
)
}
return (
<>
<Grid
gridGap="1em"
gridTemplateColumns="repeat(auto-fit, minmax(260px, 1fr))"
mt="1em"
>
<InfiniteScroll
pageStart={1}
loadMore={page => setAgentsToShow(page * 10)}
hasMore={agentsToShow < posts.length}
>
{posts
.filter((post, index) => index < agentsToShow)
.map(post => (
<FreeAgentCard key={post.id} post={post} />
))}
</InfiniteScroll>
</Grid>
<Box w="50%" textAlign="center" mx="auto" mt="1em">
<Heading size="sm">No more free agents to show</Heading>
<Box mt="1em">
<Button outlined onClick={() => window.scrollTo(0, 0)}>
Return to the top
</Button>
</Box>
</Box>
</>
)
}
export default Posts

View File

@ -1,165 +0,0 @@
import React from "react"
import { FreeAgentPost } from "../../types"
import {
Accordion,
AccordionItem,
AccordionHeader,
AccordionIcon,
AccordionPanel,
Box,
Grid,
Flex,
Heading,
} from "@chakra-ui/core"
import { useContext } from "react"
import MyThemeContext from "../../themeContext"
import UserAvatar from "../common/UserAvatar"
import { Link } from "@reach/router"
import Flag from "../common/Flag"
import { countries } from "../../utils/lists"
import { FaTwitter } from "react-icons/fa"
import WeaponImage from "../common/WeaponImage"
import RoleIcons from "./RoleIcons"
import VCIcon from "./VCIcon"
import Alert from "../elements/Alert"
interface PostsAccordionProps {
posts: FreeAgentPost[]
}
const hasExtraInfo = (post: FreeAgentPost) => {
const { activity, description, looking_for, past_experience } = post
if (!activity && !description && !looking_for && !past_experience) {
return false
}
return true
}
const PostsAccordion: React.FC<PostsAccordionProps> = ({ posts }) => {
const { darkerBgColor } = useContext(MyThemeContext)
if (posts.length === 0) {
return (
<Alert status="info">No free agents found with the current filter</Alert>
)
}
return (
<Accordion allowMultiple>
{posts.map(post => {
const { discord_user } = post
const canBeExpanded = hasExtraInfo(post)
return (
<AccordionItem key={post.id}>
<AccordionHeader cursor={canBeExpanded ? undefined : "default"}>
{canBeExpanded ? (
<AccordionIcon size="2em" mr="1em" />
) : (
<Box w="2em" h="2em" mr="1em" />
)}
<Grid
gridTemplateColumns="repeat(auto-fill, 200px)"
width="100%"
rowGap="0.7em"
justifyItems="center"
justifyContent="center"
alignItems="center"
>
<Link to={`/u/${discord_user.discord_id}`}>
<Flex alignItems="center">
<UserAvatar
twitterName={discord_user.twitter_name}
name={discord_user.username}
mr="5px"
/>
<span>
{discord_user.username}#{discord_user.discriminator}
</span>
</Flex>
</Link>
<Box>
{discord_user.country && (
<>
<Flag code={discord_user.country} />
{
countries.find(obj => obj.code === discord_user.country)
?.name
}
</>
)}
</Box>
<Flex alignItems="center">
{discord_user.twitter_name && (
<>
<Box as={FaTwitter} />
<Box as="span" ml="5px">
{discord_user.twitter_name}
</Box>
</>
)}
</Flex>
<Box color="#999999">
{new Date(parseInt(post.createdAt)).toLocaleDateString()}
</Box>
<Flex alignItems="center">
{discord_user?.weapons &&
discord_user.weapons.map(wpn => (
<Box mx="0.3em" key={wpn}>
<WeaponImage englishName={wpn} size="SMALL" />
</Box>
))}
</Flex>
<RoleIcons playstyles={post.playstyles} />
<VCIcon canVC={post.can_vc} />
</Grid>
</AccordionHeader>
{canBeExpanded && (
<AccordionPanel
mt="3px"
py={4}
background={darkerBgColor}
whiteSpace="pre-wrap"
textAlign="center"
>
{post.activity && (
<Box>
<Heading size="md">Activity</Heading>
{post.activity}
</Box>
)}
{post.looking_for && (
<Box mt={post.activity ? "1em" : undefined}>
<Heading size="md">Looking for</Heading>
{post.looking_for}
</Box>
)}
{post.past_experience && (
<Box
mt={post.activity || post.looking_for ? "1em" : undefined}
>
<Heading size="md">Past experience</Heading>
{post.past_experience}
</Box>
)}
{post.description && (
<Box
mt={
post.activity || post.looking_for || post.past_experience
? "1em"
: undefined
}
>
<Heading size="md">Description</Heading>
{post.description}
</Box>
)}
</AccordionPanel>
)}
</AccordionItem>
)
})}
</Accordion>
)
}
export default PostsAccordion

View File

@ -1,6 +1,6 @@
import React from "react"
import { RouteComponentProps } from "@reach/router"
import { Image, Heading, Flex, Box } from "@chakra-ui/core"
import { Image, Heading, Flex } from "@chakra-ui/core"
import { posterGirl } from "../../assets/imageImports"
import { useContext } from "react"
import MyThemeContext from "../../themeContext"