-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* New : 팔로우하는 유저 응답 인터페이스 정의 * Refactor : 페이지네이션 파라미터 인터페이스 정의 * Refactor : 다음페이지가 없는경우 intersection observer 제거 * Refactor : UI height 를 상수로 관리 * New : 팔로우 리스트 관련 URL 추가 * New : 팔로우 리스트 이동 추가 * Refactor : UI height 를 상수로 관리 * New : 컴포넌트 반복 컴포넌트 구현 * New : 팔로잉, 팔로우 유저 확인기능 추가
- Loading branch information
1 parent
5b3ce32
commit f485180
Showing
22 changed files
with
498 additions
and
14 deletions.
There are no files selected for viewing
22 changes: 22 additions & 0 deletions
22
client/src/app/(protectedRoute)/user/follow-list/layout.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
22 changes: 22 additions & 0 deletions
22
client/src/components/user/followList/FollowingUserCardSkeleton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.