mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-08-24 03:55:49 -05:00
user search
This commit is contained in:
@@ -1,18 +1,9 @@
|
||||
import React from "react"
|
||||
import { useQuery } from "@apollo/react-hooks"
|
||||
import { USERS } from "../../graphql/queries/users"
|
||||
import { USERS, UsersData } 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
|
||||
@@ -28,7 +19,7 @@ const UserSelector: React.FC<UserSelectorProps> = ({ setValue }) => {
|
||||
isSearchable
|
||||
options={
|
||||
!loading && data
|
||||
? data.users.map(user => ({
|
||||
? data.users.map((user) => ({
|
||||
label: `${user.username}#${user.discriminator}`,
|
||||
value: user.discord_id,
|
||||
}))
|
||||
|
||||
@@ -27,7 +27,9 @@ const Emoji: React.FC<EmojiProps> = (props) => {
|
||||
|
||||
const weaponName = weaponCodes[value]
|
||||
if (!!weaponName)
|
||||
return <WeaponImage englishName={weaponName as any} size="SMALL" />
|
||||
return (
|
||||
<WeaponImage englishName={weaponName as any} size="SMALL" asInlineBlock />
|
||||
)
|
||||
|
||||
const abilityName = abilityCodes[value]
|
||||
if (!!abilityName) return <AbilityIcon size="TINY" ability={abilityName} />
|
||||
|
||||
@@ -16,6 +16,7 @@ interface InputProps {
|
||||
required?: boolean
|
||||
disabled?: boolean
|
||||
textLeft?: string
|
||||
size?: "sm" | "md" | "lg"
|
||||
}
|
||||
|
||||
const Input: React.FC<InputProps> = ({
|
||||
@@ -26,6 +27,7 @@ const Input: React.FC<InputProps> = ({
|
||||
disabled,
|
||||
limit,
|
||||
textLeft,
|
||||
size,
|
||||
}) => {
|
||||
const { themeColorHex, grayWithShade, darkerBgColor } = useContext(
|
||||
MyThemeContext
|
||||
@@ -47,6 +49,7 @@ const Input: React.FC<InputProps> = ({
|
||||
_hover={{}}
|
||||
background={darkerBgColor}
|
||||
borderColor="#CCCCCC"
|
||||
size={size}
|
||||
/>
|
||||
</InputGroup>
|
||||
{limit && (
|
||||
|
||||
@@ -7,6 +7,7 @@ import AdminPage from "../admin/AdminPage"
|
||||
|
||||
const HomePage = lazy(() => import("../home/HomePage"))
|
||||
const UserPage = lazy(() => import("../user/UserPage"))
|
||||
const UserSearchPage = lazy(() => import("../usersearch/UserSearchPage"))
|
||||
const MarkdownHelpPage = lazy(() => import("../markdown/MarkdownHelpPage"))
|
||||
const BuildsPage = lazy(() => import("../builds/BuildsPage"))
|
||||
const CalendarPage = lazy(() => import("../calendar/CalendarPage"))
|
||||
@@ -37,6 +38,7 @@ const Routes: React.FC = () => {
|
||||
<HomePage path="/" />
|
||||
<AdminPage path="/admin" />
|
||||
<UserPage path="/u/:id" />
|
||||
<UserSearchPage path="/u" />
|
||||
<MarkdownHelpPage path="/markdown" />
|
||||
<TeamPage path="/t/:name" />
|
||||
<BuildsPage path="/builds" />
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
FaDoorOpen,
|
||||
FaListOl,
|
||||
FaPeopleCarry,
|
||||
FaSearch,
|
||||
} from "react-icons/fa"
|
||||
import Logo from "./Logo"
|
||||
import NavItem from "./NavItem"
|
||||
@@ -144,6 +145,7 @@ export const SideNavContent: React.FC<SideNavProps> = ({ showLogo = true }) => {
|
||||
<NavItem to="plans" icon={FaMap} title="Map Planner" />
|
||||
<NavItem to="calendar" icon={FaCalendarAlt} title="Calendar" />
|
||||
<NavItem to="builds" icon={FaTshirt} title="Builds" />
|
||||
<NavItem to="u" icon={FaSearch} title="User Search" />
|
||||
<NavItem to="freeagents" icon={FaUserSecret} title="Free Agents" />
|
||||
<NavItem
|
||||
to="tournaments"
|
||||
|
||||
112
frontend-react/src/components/usersearch/UserSearchPage.tsx
Normal file
112
frontend-react/src/components/usersearch/UserSearchPage.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import React, { useContext, useState, useEffect } from "react"
|
||||
import { useQuery } from "@apollo/react-hooks"
|
||||
import { RouteComponentProps, Link } from "@reach/router"
|
||||
import { USERS, UsersData } from "../../graphql/queries/users"
|
||||
import Error from "../common/Error"
|
||||
import Loading from "../common/Loading"
|
||||
import { Box, Flex, PseudoBox } from "@chakra-ui/core"
|
||||
import UserAvatar from "../common/UserAvatar"
|
||||
import { FaTwitter } from "react-icons/fa"
|
||||
import MyThemeContext from "../../themeContext"
|
||||
import PageHeader from "../common/PageHeader"
|
||||
import { Helmet } from "react-helmet-async"
|
||||
import Input from "../elements/Input"
|
||||
import Pagination from "../common/Pagination"
|
||||
|
||||
const UserSearchPage: React.FC<RouteComponentProps> = () => {
|
||||
const { darkerBgColor } = useContext(MyThemeContext)
|
||||
const { data, error, loading } = useQuery<UsersData>(USERS)
|
||||
const [filter, setFilter] = useState("")
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentPage(1)
|
||||
}, [filter])
|
||||
|
||||
if (error) return <Error errorMessage={error.message} />
|
||||
if (loading) return <Loading />
|
||||
|
||||
const usersFiltered = data!.users.filter((user) => {
|
||||
const filterLower = filter.toLowerCase()
|
||||
if (user.discord_id.includes(filterLower)) return true
|
||||
|
||||
if (
|
||||
`${user.username}#${user.discriminator}`
|
||||
.toLowerCase()
|
||||
.includes(filterLower)
|
||||
)
|
||||
return true
|
||||
|
||||
if (
|
||||
user.twitter_name &&
|
||||
user.twitter_name.toLowerCase().includes(filterLower)
|
||||
)
|
||||
return true
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
const usersSliced = usersFiltered.slice(
|
||||
20 * (currentPage - 1),
|
||||
20 * (currentPage - 1) + 20
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader title="User Search" />
|
||||
<Helmet>
|
||||
<title>User Search | sendou.ink</title>
|
||||
</Helmet>
|
||||
<Box my="1em">
|
||||
<Input
|
||||
size="lg"
|
||||
label="Filter by username, Twitter name or Discord ID"
|
||||
value={filter}
|
||||
setValue={(value: string) => setFilter(value)}
|
||||
/>
|
||||
</Box>
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
pageCount={Math.ceil(usersFiltered.length / 20)}
|
||||
onChange={(page: number) => setCurrentPage(page)}
|
||||
/>
|
||||
<Box>
|
||||
{usersSliced.map((user) => (
|
||||
<Link to={`/u/${user.discord_id}`}>
|
||||
<PseudoBox
|
||||
borderRadius="5px"
|
||||
p="10px"
|
||||
_hover={{ bg: darkerBgColor }}
|
||||
transition="0.5s all"
|
||||
>
|
||||
<Flex key={user.discord_id} alignItems="center">
|
||||
<UserAvatar
|
||||
name={user.username}
|
||||
twitterName={user.twitter_name}
|
||||
/>{" "}
|
||||
<Flex flexDir="column" ml="0.5em">
|
||||
<Box fontWeight="bold" fontSize="1.1em">
|
||||
{user.username}#{user.discriminator}
|
||||
</Box>
|
||||
{user.twitter_name && (
|
||||
<Flex alignItems="center">
|
||||
<Box as={FaTwitter} mr="0.3em" />
|
||||
{user.twitter_name}
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</PseudoBox>
|
||||
</Link>
|
||||
))}
|
||||
</Box>
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
pageCount={Math.ceil(usersFiltered.length / 20)}
|
||||
onChange={(page: number) => setCurrentPage(page)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserSearchPage
|
||||
@@ -1,5 +1,14 @@
|
||||
import { gql, DocumentNode } from "apollo-boost"
|
||||
|
||||
export interface UsersData {
|
||||
users: {
|
||||
discord_id: string
|
||||
discriminator: string
|
||||
twitter_name?: string
|
||||
username: string
|
||||
}[]
|
||||
}
|
||||
|
||||
export const USERS: DocumentNode = gql`
|
||||
{
|
||||
users {
|
||||
|
||||
Reference in New Issue
Block a user