From 1bebb7c146362c084125ba30cbcd8ed1c31fe9f0 Mon Sep 17 00:00:00 2001 From: "Kalle (Sendou)" <38327916+Sendouc@users.noreply.github.com> Date: Thu, 10 Dec 2020 13:52:32 +0200 Subject: [PATCH] closes #194 --- components/common/MyLink.tsx | 10 +- components/layout/FooterContent.tsx | 2 + lib/useDebounce.ts | 18 ++++ pages/u/index.tsx | 144 ++++++++++++++++++++++++++++ prisma/queries/getAllUsers.ts | 20 ++++ 5 files changed, 191 insertions(+), 3 deletions(-) create mode 100644 lib/useDebounce.ts create mode 100644 pages/u/index.tsx create mode 100644 prisma/queries/getAllUsers.ts diff --git a/components/common/MyLink.tsx b/components/common/MyLink.tsx index 3645b9918..fe56e7d81 100644 --- a/components/common/MyLink.tsx +++ b/components/common/MyLink.tsx @@ -7,6 +7,7 @@ interface Props { href: string; isExternal?: boolean; prefetch?: boolean; + isColored?: boolean; } const MyLink: React.FC = ({ @@ -14,19 +15,22 @@ const MyLink: React.FC = ({ href, isExternal, prefetch = true, + isColored = true, }) => { const { themeColorShade } = useMyTheme(); if (isExternal) { return ( - + {children} ); } return ( - - {children} + + + {children} + ); }; diff --git a/components/layout/FooterContent.tsx b/components/layout/FooterContent.tsx index 7694dcb7a..c3a52a92c 100644 --- a/components/layout/FooterContent.tsx +++ b/components/layout/FooterContent.tsx @@ -9,6 +9,8 @@ const FooterContent: React.FC = () => { const { themeColorHex: themeColor } = useMyTheme(); return ( { + const handler = setTimeout(() => { + setDebouncedValue(value); + }, delay); + return () => { + clearTimeout(handler); + }; + }, [value, delay]); + + return debouncedValue; +} diff --git a/pages/u/index.tsx b/pages/u/index.tsx new file mode 100644 index 000000000..bfe0afb5c --- /dev/null +++ b/pages/u/index.tsx @@ -0,0 +1,144 @@ +import { + Box, + Flex, + Input, + InputGroup, + InputLeftElement, +} from "@chakra-ui/react"; +import { t, Trans } from "@lingui/macro"; +import Breadcrumbs from "components/common/Breadcrumbs"; +import MyLink from "components/common/MyLink"; +import UserAvatar from "components/common/UserAvatar"; +import { getEmojiFlag } from "countries-list"; +import { Unpacked } from "lib/types"; +import { useDebounce } from "lib/useDebounce"; +import { useMyTheme } from "lib/useMyTheme"; +import { GetStaticProps } from "next"; +import Head from "next/head"; +import Link from "next/link"; +import { useRouter } from "next/router"; +import { getAllUsers, GetAllUsersData } from "prisma/queries/getAllUsers"; +import { useEffect, useMemo, useState } from "react"; +import { FaTwitter } from "react-icons/fa"; +import { FiSearch } from "react-icons/fi"; + +interface Props { + users: GetAllUsersData; +} + +const UserSearchPage = ({ users }: Props) => { + const router = useRouter(); + const { gray } = useMyTheme(); + + const [filter, setFilter] = useState(getInitialFilterValue()); + const debouncedFilter = useDebounce(filter); + + useEffect(() => { + if (debouncedFilter.length < 3) { + router.push(`/u`, undefined, { shallow: true }); + } else { + router.push(`/u?filter=${debouncedFilter}`, undefined, { shallow: true }); + } + }, [debouncedFilter]); + + const filteredUsers = useMemo(() => { + if (debouncedFilter.length < 3) return []; + + return users.filter((u) => { + const normalizedFilter = debouncedFilter.toLowerCase(); + const normalizedName = `${u.username}#${u.discriminator}`.toLowerCase(); + + if (normalizedName.includes(normalizedFilter)) return true; + + const normalizedTwitter = u.profile?.twitterName + ? u.profile.twitterName.toLowerCase() + : ""; + + if (normalizedTwitter.includes(normalizedFilter)) return true; + + return false; + }); + }, [debouncedFilter]); + + return ( + <> + + + + + + Search for users by their Discord tag or Twitter name + + + } + /> + setFilter(e.target.value)} + /> + + + {filteredUsers.map((user) => ( + + ))} + + {debouncedFilter.length >= 3 && !filteredUsers.length && ( + No matching users found + )} + + ); + + function getInitialFilterValue() { + if (!router.query.filter || typeof router.query.filter !== "string") + return ""; + + return router.query.filter; + } +}; + +function UserInfo({ user }: { user: Unpacked }) { + return ( + + + + + + + + + + {user.username}#{user.discriminator} + + {user.profile?.country ? ( + + {getEmojiFlag(user.profile.country)} + + ) : null} + + {user.profile?.twitterName && ( + + + + {user.profile.twitterName} + + + )} + + + ); +} + +export const getStaticProps: GetStaticProps = async () => { + await getAllUsers(); + + return { props: { users: await getAllUsers() }, revalidate: 3600 }; +}; + +export default UserSearchPage; diff --git a/prisma/queries/getAllUsers.ts b/prisma/queries/getAllUsers.ts new file mode 100644 index 000000000..361900d82 --- /dev/null +++ b/prisma/queries/getAllUsers.ts @@ -0,0 +1,20 @@ +import { Prisma } from "@prisma/client"; +import prisma from "prisma/client"; + +export type GetAllUsersData = Prisma.PromiseReturnType; + +export const getAllUsers = async () => + prisma.user.findMany({ + select: { + discordId: true, + username: true, + discriminator: true, + discordAvatar: true, + profile: { + select: { + country: true, + twitterName: true, + }, + }, + }, + });