mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 20:13:49 +00:00
Feat: add user header with follow info
This commit is contained in:
70
src/pages/api/users/[id]/followers.ts
Normal file
70
src/pages/api/users/[id]/followers.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { UserExtendedNextApiRequest } from '@/config/auth/types';
|
||||
import NextConnectOptions from '@/config/nextConnect/NextConnectOptions';
|
||||
import validateRequest from '@/config/nextConnect/middleware/validateRequest';
|
||||
import ServerError from '@/config/util/ServerError';
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import findUserById from '@/services/User/findUserById';
|
||||
import getUsersFollowingUser from '@/services/UserFollows/getUsersFollowingUser';
|
||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||
|
||||
import { NextApiResponse } from 'next';
|
||||
import { createRouter } from 'next-connect';
|
||||
import { z } from 'zod';
|
||||
|
||||
interface GetUserFollowInfoRequest extends UserExtendedNextApiRequest {
|
||||
query: { id: string; page_size: string; page_num: string };
|
||||
}
|
||||
|
||||
const router = createRouter<
|
||||
GetUserFollowInfoRequest,
|
||||
NextApiResponse<z.infer<typeof APIResponseValidationSchema>>
|
||||
>();
|
||||
|
||||
const getFollowingInfo = async (
|
||||
req: GetUserFollowInfoRequest,
|
||||
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
|
||||
) => {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { id, page_num, page_size } = req.query;
|
||||
|
||||
const user = await findUserById(id);
|
||||
if (!user) {
|
||||
throw new ServerError('User not found', 404);
|
||||
}
|
||||
|
||||
const pageNum = parseInt(page_num, 10);
|
||||
const pageSize = parseInt(page_size, 10);
|
||||
|
||||
const following = await getUsersFollowingUser({
|
||||
userId: id,
|
||||
pageNum,
|
||||
pageSize,
|
||||
});
|
||||
const followingCount = await DBClient.instance.userFollow.count({
|
||||
where: { following: { id } },
|
||||
});
|
||||
|
||||
res.setHeader('X-Total-Count', followingCount);
|
||||
|
||||
res.json({
|
||||
message: 'Retrieved users that are followed by queried user',
|
||||
payload: following,
|
||||
success: true,
|
||||
statusCode: 200,
|
||||
});
|
||||
};
|
||||
|
||||
router.get(
|
||||
validateRequest({
|
||||
querySchema: z.object({
|
||||
id: z.string().cuid(),
|
||||
page_size: z.string().regex(/^\d+$/),
|
||||
page_num: z.string().regex(/^\d+$/),
|
||||
}),
|
||||
}),
|
||||
getFollowingInfo,
|
||||
);
|
||||
|
||||
const handler = router.handler(NextConnectOptions);
|
||||
|
||||
export default handler;
|
||||
70
src/pages/api/users/[id]/following.ts
Normal file
70
src/pages/api/users/[id]/following.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { UserExtendedNextApiRequest } from '@/config/auth/types';
|
||||
import NextConnectOptions from '@/config/nextConnect/NextConnectOptions';
|
||||
import validateRequest from '@/config/nextConnect/middleware/validateRequest';
|
||||
import ServerError from '@/config/util/ServerError';
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import findUserById from '@/services/User/findUserById';
|
||||
import getUsersFollowedByUser from '@/services/UserFollows/getUsersFollowedByUser';
|
||||
import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema';
|
||||
|
||||
import { NextApiResponse } from 'next';
|
||||
import { createRouter } from 'next-connect';
|
||||
import { z } from 'zod';
|
||||
|
||||
interface GetUserFollowInfoRequest extends UserExtendedNextApiRequest {
|
||||
query: { id: string; page_size: string; page_num: string };
|
||||
}
|
||||
|
||||
const router = createRouter<
|
||||
GetUserFollowInfoRequest,
|
||||
NextApiResponse<z.infer<typeof APIResponseValidationSchema>>
|
||||
>();
|
||||
|
||||
const getFollowingInfo = async (
|
||||
req: GetUserFollowInfoRequest,
|
||||
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
|
||||
) => {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { id, page_num, page_size } = req.query;
|
||||
|
||||
const user = await findUserById(id);
|
||||
if (!user) {
|
||||
throw new ServerError('User not found', 404);
|
||||
}
|
||||
|
||||
const pageNum = parseInt(page_num, 10);
|
||||
const pageSize = parseInt(page_size, 10);
|
||||
|
||||
const following = await getUsersFollowedByUser({
|
||||
userId: id,
|
||||
pageNum,
|
||||
pageSize,
|
||||
});
|
||||
const followingCount = await DBClient.instance.userFollow.count({
|
||||
where: { follower: { id } },
|
||||
});
|
||||
|
||||
res.setHeader('X-Total-Count', followingCount);
|
||||
|
||||
res.json({
|
||||
message: 'Retrieved users that are followed by queried user',
|
||||
payload: following,
|
||||
success: true,
|
||||
statusCode: 200,
|
||||
});
|
||||
};
|
||||
|
||||
router.get(
|
||||
validateRequest({
|
||||
querySchema: z.object({
|
||||
id: z.string().cuid(),
|
||||
page_size: z.string().regex(/^\d+$/),
|
||||
page_num: z.string().regex(/^\d+$/),
|
||||
}),
|
||||
}),
|
||||
getFollowingInfo,
|
||||
);
|
||||
|
||||
const handler = router.handler(NextConnectOptions);
|
||||
|
||||
export default handler;
|
||||
@@ -7,7 +7,9 @@ import { GetServerSideProps } from 'next';
|
||||
import Head from 'next/head';
|
||||
import { FC } from 'react';
|
||||
import { z } from 'zod';
|
||||
import Image from 'next/image';
|
||||
import UserAvatar from '@/components/Account/UserAvatar';
|
||||
import useGetUsersFollowedByUser from '@/hooks/data-fetching/user-follows/useGetUsersFollowedByUser';
|
||||
import useGetUsersFollowingUser from '@/hooks/data-fetching/user-follows/useGetUsersFollowingUser';
|
||||
|
||||
interface UserInfoPageProps {
|
||||
user: z.infer<typeof GetUserSchema>;
|
||||
@@ -16,50 +18,63 @@ interface UserInfoPageProps {
|
||||
const UserHeader: FC<{ user: z.infer<typeof GetUserSchema> }> = ({ user }) => {
|
||||
const timeDistance = useTimeDistance(new Date(user.createdAt));
|
||||
|
||||
return (
|
||||
<article className="card flex flex-col justify-center bg-base-300">
|
||||
<div className="card-body">
|
||||
<header className="flex justify-between">
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold lg:text-4xl">
|
||||
{user.firstName} {user.lastName}
|
||||
</h1>
|
||||
const { followingCount } = useGetUsersFollowedByUser({
|
||||
userId: user.id,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
<h3 className="italic">
|
||||
joined{' '}
|
||||
{timeDistance && (
|
||||
<span
|
||||
className="tooltip tooltip-bottom"
|
||||
data-tip={format(new Date(user.createdAt), 'MM/dd/yyyy')}
|
||||
>
|
||||
{`${timeDistance} ago`}
|
||||
</span>
|
||||
)}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
const { followerCount } = useGetUsersFollowingUser({
|
||||
userId: user.id,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
return (
|
||||
<header className="card text-center items-center">
|
||||
<div className="card-body items-center w-full">
|
||||
<div className="w-40 h-40">
|
||||
<UserAvatar user={user} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold lg:text-4xl">{user.username}</h1>
|
||||
</div>
|
||||
|
||||
<div className="flex space-x-3 text-lg font-bold">
|
||||
<span>{followingCount} Following</span>
|
||||
<span>{followerCount} Followers</span>
|
||||
</div>
|
||||
|
||||
<span className="italic">
|
||||
joined{' '}
|
||||
{timeDistance && (
|
||||
<span
|
||||
className="tooltip tooltip-bottom"
|
||||
data-tip={format(new Date(user.createdAt), 'MM/dd/yyyy')}
|
||||
>
|
||||
{`${timeDistance} ago`}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</article>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
const UserInfoPage: FC<UserInfoPageProps> = ({ user }) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const isDesktop = useMediaQuery('(min-width: 1024px)');
|
||||
const title = `${user.username} | The Biergarten App`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{user ? `${user.firstName} ${user.lastName}` : 'User Info'}</title>
|
||||
<title>{title}</title>
|
||||
<meta name="description" content="User information" />
|
||||
</Head>
|
||||
<>
|
||||
<main className="mb-12 mt-10 flex w-full items-center justify-center">
|
||||
<Image src={user.userAvatar!.path} alt="avatar" width={200} height={200} />
|
||||
<div className="w-11/12 space-y-3 xl:w-9/12 2xl:w-8/12">
|
||||
<UserHeader user={user} />
|
||||
|
||||
{isDesktop ? <></> : <> </>}
|
||||
</div>
|
||||
</main>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user