Skip to content

Commit

Permalink
Revert "Revert "팔로워,팔로잉-유저-확인기능-추가 (#83)" (#84)"
Browse files Browse the repository at this point in the history
This reverts commit 248dce5.
  • Loading branch information
jobkaeHenry authored Dec 13, 2023
1 parent 248dce5 commit 19644a4
Show file tree
Hide file tree
Showing 22 changed files with 498 additions and 14 deletions.
22 changes: 22 additions & 0 deletions client/src/app/(protectedRoute)/user/follow-list/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"use client";
import CustomAppbar from "@/components/layout/CustomAppbar";
import CustomContainer from "@/components/layout/CustomContainer";
import { useMyInfoQuery } from "@/queries/auth/useMyInfoQuery";
import { ReactNode } from "react";

type FollowListLayoutProps = {
children: ReactNode;
};

const FollowListLayout = ({ children }: FollowListLayoutProps) => {
const { data: myInfo } = useMyInfoQuery();

return (
<>
<CustomAppbar title={`${myInfo?.nickname??'닉네임'}의 목록`}/>
<CustomContainer>{children}</CustomContainer>
</>
);
};

export default FollowListLayout;
39 changes: 39 additions & 0 deletions client/src/app/(protectedRoute)/user/follow-list/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"use client";

import { Box } from "@mui/material";
import CustomToggleButtonGroup from "@/components/CustomToggleButtonGroup";
import { appbarHeight } from "@/const/uiSizes";
import { Suspense, useState } from "react";
import FollowingList from "@/components/user/followList/FollowingList";
import FollowingUserCardSkeleton from "@/components/user/followList/FollowingUserCardSkeleton";
import ComponentRepeater from "@/components/ComponentRepeater";
import FollowerList from "@/components/user/followList/FollowerList";

const FollowListPage = () => {
const selectableList = ["팔로잉", "팔로워"];
const [currentView, setCurrentView] = useState(selectableList[0]);

return (
<>
<CustomToggleButtonGroup
value={selectableList}
onChange={setCurrentView}
sx={{ position: "fixed", top: appbarHeight, left: 0, right: 0 }}
/>
{/* Fixed로 빠진 button 위치만큼의 place holder */}
<Box height={26} />
<Suspense
fallback={
<ComponentRepeater count={5}>
<FollowingUserCardSkeleton />
</ComponentRepeater>
}
>
{currentView === "팔로잉" && <FollowingList /> }
{currentView === "팔로워" && <FollowerList /> }
</Suspense>
</>
);
};

export default FollowListPage;
6 changes: 5 additions & 1 deletion client/src/app/(protectedRoute)/user/setting/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";

import CustomAppbar from "@/components/layout/CustomAppbar";
import { appbarHeight } from "@/const/uiSizes";
import { Container, Stack } from "@mui/material";
import { ReactNode } from "react";

Expand All @@ -12,7 +13,10 @@ const UserInfoPageLayout = ({ children }: Props) => {
return (
<>
<CustomAppbar title="설정" />
<Container sx={{ px: { xs: 0, sm: 4 } }} maxWidth={"lg"}>
<Container
sx={{ px: { xs: 0, sm: 4 }, mt: appbarHeight }}
maxWidth={"lg"}
>
<Stack gap={2}>{children}</Stack>
</Container>
</>
Expand Down
18 changes: 18 additions & 0 deletions client/src/components/ComponentRepeater.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { cloneElement, ReactComponentElement } from "react";

type Props = {
children: ReactComponentElement<any>;
count: number;
};

const ComponentRepeater = ({ children, count }: Props) => {
return (
<>
{Array.from(new Array(count)).map((_e, i) =>
cloneElement(children, { key: i })
)}
</>
);
};

export default ComponentRepeater;
72 changes: 72 additions & 0 deletions client/src/components/CustomToggleButtonGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"use client";
import {
ToggleButton,
ToggleButtonGroup,
ToggleButtonGroupProps,
Typography,
} from "@mui/material";
import { useState } from "react";

interface CustomToggleButtonGroupType
extends Omit<ToggleButtonGroupProps, "onChange" | "value" | "children"> {
onChange: (val: string) => void;
value: string[];
}

const CustomToggleButtonGroup = ({
onChange,
value,
sx,
...toggleBtnGroupProps
}: CustomToggleButtonGroupType) => {
const [currentValue, setCurrentValue] = useState(value[0]);

return (
<ToggleButtonGroup
value={currentValue}
exclusive
fullWidth
onChange={(_e, val) => {
if (val !== null) {
setCurrentValue(val);
onChange(val);
}
}}
sx={{ backgroundColor: "background.paper",px:2, ...sx }}
{...toggleBtnGroupProps}
>
{value.map((val, i) => {
return (
<ToggleButton
key={i}
disableRipple
value={val}
fullWidth
sx={ToggleButtonStyle}
>
<Typography fontSize="caption1" fontWeight="bold">
{val}
</Typography>
</ToggleButton>
);
})}
</ToggleButtonGroup>
);
};

const ToggleButtonStyle = {
border: 0,
borderRadius: 0,
"&.Mui-selected": {
backgroundColor: "background.paper",
borderBottom: "1px solid",
":hover": {
backgroundColor: "background.paper",
},
},
":hover": {
backgroundColor: "background.paper",
},
};

export default CustomToggleButtonGroup;
3 changes: 2 additions & 1 deletion client/src/components/layout/CustomAppbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import GoBackIcon from "@/assets/icons/GoBackIcon.svg";
import { MouseEventHandler, ReactNode, memo } from "react";
import { useRouter } from "next/navigation";
import { appbarHeight } from "@/const/uiSizes";

interface CustomAppbarInterface extends AppBarProps {
title?: string;
Expand All @@ -34,7 +35,7 @@ const CustomAppbar = ({
const router = useRouter();

return (
<AppBar position={position ? position : "fixed"}>
<AppBar sx={{ height: appbarHeight }} position={position ? position : "fixed"}>
<Toolbar sx={{ display: "flex", justifyContent: "space-between" }}>
{/* 프리팬드 버튼 */}
{prependButton ? (
Expand Down
6 changes: 2 additions & 4 deletions client/src/components/layout/CustomContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { appbarHeight, navbarHeight } from "@/const/uiSizes";
import { Container, ContainerProps, Paper } from "@mui/material";

interface CustomContainerInterface extends ContainerProps {
Expand All @@ -9,9 +10,6 @@ const CustomContainer = ({
disableMt,
children,
}: CustomContainerInterface) => {
const appbarHeight = '64px'
const navbarHeight = '56px'

return (
<Container
sx={{ ...sx, px: { xs: 0, sm: 4 }, mt: disableMt ? 0 : 8 }}
Expand All @@ -24,7 +22,7 @@ const CustomContainer = ({
flexDirection: "column",
gap: 2,
p: 2,
minHeight:`calc(100vh - ${appbarHeight} - ${navbarHeight})`
minHeight: `calc(100vh - ${appbarHeight} - ${navbarHeight})`,
}}
>
{children}
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/post/PostCardList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ function PostCardList(props: UseGetPostListQueryInterface) {
<PostCardSkeleton />
) : (
// 인터섹션옵저버
<div style={{ height: 60 }} ref={ref}></div>
hasNextPage&&<div style={{ height: 60 }} ref={ref}></div>
)}
</div>
</postcardContext.Provider>
Expand Down
61 changes: 61 additions & 0 deletions client/src/components/user/followList/FollowUserCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Button, Stack, Typography } from "@mui/material";
import React from "react";
import UserAvatar from "@/components/user/info/UserAvatar";
import { useRouter } from "next/navigation";
import { USER_PAGE } from "@/const/clientPath";
import useUnFollowMutation from "@/queries/user/useUnFollowMutation";

type Props = {
imageUrl?: string;
nickName: string;
userId: string;
content: string;
userPk: number;
};

const FollowUserCard = ({
userPk,
imageUrl,
nickName,
userId,
content,
}: Props) => {
const router = useRouter();
const { mutate: unfollowHandler } = useUnFollowMutation();

return (
<Stack direction="row" gap={1} py={1}>
<UserAvatar
src={imageUrl}
alt={`${nickName}의 프로필`}
fallback={nickName}
onClick={() => router.push(USER_PAGE(userPk))}
sx={{ cursor: "pointer" }}
/>
<Stack gap={1} flexGrow={1}>
<Stack direction="row" justifyContent={"space-between"}>
<Stack
sx={{ cursor: "pointer" }}
onClick={() => router.push(USER_PAGE(userPk))}
>
<Typography fontSize="caption1" color="text.main">
{nickName}
</Typography>
<Typography fontSize="caption2" color="text.secondary">
@{userId}
</Typography>
</Stack>
<Button
variant="outlined"
onClick={() => unfollowHandler(String(userPk))}
>
언팔로우
</Button>
</Stack>
<Typography>{content}</Typography>
</Stack>
</Stack>
);
};

export default FollowUserCard;
43 changes: 43 additions & 0 deletions client/src/components/user/followList/FollowerList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"use client";

import FollowUserCard from "@/components/user/followList/FollowUserCard";
import { useEffect } from "react";
import { useInView } from "react-intersection-observer";
import FollowingUserCardSkeleton from "@/components/user/followList/FollowingUserCardSkeleton";
import ComponentRepeater from "@/components/ComponentRepeater";
import useFollowerUserInfiniteQuery from "@/queries/user/useFollowerUserInfiniteQuery";

const FollowerList = () => {
const { data, isFetchingNextPage, hasNextPage, fetchNextPage } =
useFollowerUserInfiniteQuery();
const { ref, inView } = useInView();

useEffect(() => {
if (hasNextPage && inView) fetchNextPage();
}, [inView, hasNextPage]);

return (
<>
{data.pages.map((page) =>
page.content.map(({ nickname, id, introduction }) => (
<FollowUserCard
key={id}
nickName={nickname}
userId={id}
content={introduction}
/>
))
)}
{isFetchingNextPage ? (
<ComponentRepeater count={5}>
<FollowingUserCardSkeleton />
</ComponentRepeater>
) : (
// 인터섹션옵저버
hasNextPage && <div style={{ height: 60 }} ref={ref}></div>
)}
</>
);
};

export default FollowerList;
45 changes: 45 additions & 0 deletions client/src/components/user/followList/FollowingList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"use client";

import FollowUserCard from "@/components/user/followList/FollowUserCard";
import useFollowingUserInfiniteQuery from "@/queries/user/useFollowingUserInfiniteQuery";
import { useEffect } from "react";
import { useInView } from "react-intersection-observer";
import FollowingUserCardSkeleton from "@/components/user/followList/FollowingUserCardSkeleton";
import ComponentRepeater from "@/components/ComponentRepeater";

const FollowingList = () => {
const { data, isFetchingNextPage, hasNextPage, fetchNextPage } =
useFollowingUserInfiniteQuery();
const { ref, inView } = useInView();

useEffect(() => {
if (hasNextPage && inView) fetchNextPage();
}, [inView, hasNextPage]);

return (
<>
{data.pages.map((page) =>
page.content.map(({ nickname, id, introduction, profileImgUrls, userNo }) => (
<FollowUserCard
key={id}
nickName={nickname}
userId={id}
userPk={userNo}
imageUrl={profileImgUrls[0]?.attachUrl}
content={introduction}
/>
))
)}
{isFetchingNextPage ? (
<ComponentRepeater count={5}>
<FollowingUserCardSkeleton />
</ComponentRepeater>
) : (
// 인터섹션옵저버
hasNextPage && <div style={{ height: 60 }} ref={ref}></div>
)}
</>
);
};

export default FollowingList;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Stack } from "@mui/material";
import { Skeleton } from "@mui/material";

const FollowingUserCardSkeleton = () => {
return (
<Stack direction="row" gap={1} py={1}>
<Skeleton width={40} height={40} variant="circular" />
<Stack gap={1} flexGrow={1}>
<Stack direction="row" justifyContent={"space-between"}>
<Stack>
<Skeleton width={50} />
<Skeleton width={60} />
</Stack>
<Skeleton height={40} width={80} variant="rectangular" />
</Stack>
<Skeleton width={"100%"} />
</Stack>
</Stack>
);
};

export default FollowingUserCardSkeleton;
Loading

0 comments on commit 19644a4

Please sign in to comment.