This commit is contained in:
Kalle (Sendou)
2020-12-10 13:52:32 +02:00
parent babc804e4e
commit 1bebb7c146
5 changed files with 191 additions and 3 deletions

View File

@@ -7,6 +7,7 @@ interface Props {
href: string;
isExternal?: boolean;
prefetch?: boolean;
isColored?: boolean;
}
const MyLink: React.FC<Props> = ({
@@ -14,19 +15,22 @@ const MyLink: React.FC<Props> = ({
href,
isExternal,
prefetch = true,
isColored = true,
}) => {
const { themeColorShade } = useMyTheme();
if (isExternal) {
return (
<ChakraLink href={href} color={themeColorShade}>
<ChakraLink href={href} color={isColored ? themeColorShade : undefined}>
{children}
</ChakraLink>
);
}
return (
<NextLink href={href} prefetch={prefetch}>
<ChakraLink color={themeColorShade}>{children}</ChakraLink>
<NextLink href={href} prefetch={prefetch ? undefined : false} passHref>
<ChakraLink color={isColored ? themeColorShade : undefined}>
{children}
</ChakraLink>
</NextLink>
);
};

View File

@@ -9,6 +9,8 @@ const FooterContent: React.FC = () => {
const { themeColorHex: themeColor } = useMyTheme();
return (
<Flex
mt="-1px"
pt={4}
pb="50px"
flexShrink={0}
alignItems="center"

18
lib/useDebounce.ts Normal file
View File

@@ -0,0 +1,18 @@
// https://usehooks.com/useDebounce/
import { useEffect, useState } from "react";
export function useDebounce(value: string, delay: number = 500) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}

144
pages/u/index.tsx Normal file
View File

@@ -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 (
<>
<Head>
<meta name="robots" content="noindex" />
</Head>
<Breadcrumbs pages={[{ name: t`Users` }]} />
<Box color={gray} fontSize="sm" mb={4}>
<Trans>Search for users by their Discord tag or Twitter name</Trans>
</Box>
<InputGroup size="lg" maxW={80} mt={4} mb={6}>
<InputLeftElement
pointerEvents="none"
children={<Box as={FiSearch} color={gray} />}
/>
<Input
placeholder="Sendou#4059"
value={filter}
onChange={(e) => setFilter(e.target.value)}
/>
</InputGroup>
<Box mt={4}>
{filteredUsers.map((user) => (
<UserInfo key={user.discordId} user={user} />
))}
</Box>
{debouncedFilter.length >= 3 && !filteredUsers.length && (
<Box>No matching users found</Box>
)}
</>
);
function getInitialFilterValue() {
if (!router.query.filter || typeof router.query.filter !== "string")
return "";
return router.query.filter;
}
};
function UserInfo({ user }: { user: Unpacked<GetAllUsersData> }) {
return (
<Flex my={6} align="center">
<Link href={`/u/${user.discordId}`}>
<a>
<UserAvatar isSmall user={user} />
</a>
</Link>
<Box ml={4}>
<Box fontWeight="bold">
<MyLink href={`/u/${user.discordId}`} isColored={false}>
{user.username}#{user.discriminator}
</MyLink>
{user.profile?.country ? (
<Box as="span" ml={2}>
{getEmojiFlag(user.profile.country)}
</Box>
) : null}
</Box>
{user.profile?.twitterName && (
<Flex align="center" fontSize="sm" mt={1}>
<Box as={FaTwitter} color="#00acee" mr={1} />
<MyLink
isExternal
href={`https://twitter.com/${user.profile.twitterName}`}
isColored={false}
>
{user.profile.twitterName}
</MyLink>
</Flex>
)}
</Box>
</Flex>
);
}
export const getStaticProps: GetStaticProps<Props> = async () => {
await getAllUsers();
return { props: { users: await getAllUsers() }, revalidate: 3600 };
};
export default UserSearchPage;

View File

@@ -0,0 +1,20 @@
import { Prisma } from "@prisma/client";
import prisma from "prisma/client";
export type GetAllUsersData = Prisma.PromiseReturnType<typeof getAllUsers>;
export const getAllUsers = async () =>
prisma.user.findMany({
select: {
discordId: true,
username: true,
discriminator: true,
discordAvatar: true,
profile: {
select: {
country: true,
twitterName: true,
},
},
},
});