diff --git a/app/profile/[id]/follow/page.tsx b/app/profile/[id]/follow/page.tsx index 92414cca..78dbc450 100644 --- a/app/profile/[id]/follow/page.tsx +++ b/app/profile/[id]/follow/page.tsx @@ -1,9 +1,13 @@ import dynamic from 'next/dynamic'; +import { Response } from '@/apis'; +import { fetchData } from '@/apis/fetch-data'; import { LeftArrowIcon } from '@/components/atoms'; -import { HeaderBar, ProfileListItem } from '@/components/molecules'; +import { HeaderBar } from '@/components/molecules'; import { type FollowTab } from '@/features/follow'; +import { MemberInfo } from '@/features/main/types'; import { flex } from '@/styled-system/patterns'; + const DynamicBackButton = dynamic( () => import('@/components/molecules').then(({ BackButton }) => BackButton), { @@ -22,31 +26,53 @@ const DynamicTabSection = dynamic( }, ); -export default function ProfileFollow({ +const DynamicFollowingSection = dynamic( + () => + import('@/features/follow').then( + ({ FollowingSection }) => FollowingSection, + ), + { + ssr: false, + }, +); + +const DynamicFollowerSection = dynamic( + () => + import('@/features/follow').then(({ FollowerSection }) => FollowerSection), + { + ssr: false, + }, +); + +export default async function ProfileFollow({ + params, searchParams, }: { + params: { id: string }; searchParams: { tab: FollowTab }; }) { const { tab = 'follow' } = searchParams; + const { data } = await fetchData>( + `/member/${params.id}`, + 'GET', + ); + if (!data) return null; return ( <> - 수영왕 정지영 + {data.nickname}
- - - - - - - - + {tab === 'follow' ? ( + + ) : ( + + )}
); diff --git a/app/record-detail/[id]/page.tsx b/app/record-detail/[id]/page.tsx index a4c3e8e1..927069cf 100644 --- a/app/record-detail/[id]/page.tsx +++ b/app/record-detail/[id]/page.tsx @@ -12,6 +12,7 @@ import { import { EditButton } from '@/features/record-detail/components'; import { css } from '@/styled-system/css'; import { flex } from '@/styled-system/patterns'; + const DynamicBackButton = dynamic( () => import('@/components/molecules').then(({ BackButton }) => BackButton), { diff --git a/components/molecules/profile-list-item/profile-list-item.tsx b/components/molecules/profile-list-item/profile-list-item.tsx index d86dd3e9..275e91f9 100644 --- a/components/molecules/profile-list-item/profile-list-item.tsx +++ b/components/molecules/profile-list-item/profile-list-item.tsx @@ -1,43 +1,55 @@ +import Link from 'next/link'; + import { Button, Image } from '@/components/atoms'; +import { ProfileFollowContent } from '@/features/follow'; import { css } from '@/styled-system/css'; import { flex } from '@/styled-system/patterns'; type FollowListItem = { - // TODO: Profile type 수정 (required) - profile?: { - id: string; - nickname: string; - summary: string; - }; isFollow: boolean; onClick?: () => void; onClickFollow?: () => void; -}; -export const ProfileListItem = ({ isFollow }: FollowListItem) => { +} & ProfileFollowContent; +export const ProfileListItem = ({ + memberId, + name, + introduction, + profileImageUrl, + isFollow, +}: FollowListItem) => { return (
-
- profile image -
-
-

수영왕 정지영

-

맞팔/좋아요/좋아요반사

-
+ +
+ profile image +
+
+

{name}

+

{introduction}

+
+ + {isFollow ? (
); @@ -53,11 +65,21 @@ const containerStyle = flex({ const profileImageStyle = flex({ width: '40px', height: '40px', - align: 'center', + align: 'stretch', rounded: 'full', overflow: 'hidden', }); +const linkStyle = flex({ + gap: '16px', + align: 'center', + width: '100%', +}); + +const followButtonStyle = css({ + flexShrink: 0, +}); + const text = { wrapperStyle: flex({ gap: '2px', diff --git a/features/follow/apis/use-follower-list.tsx b/features/follow/apis/use-follower-list.tsx index fd091100..3e09b57c 100644 --- a/features/follow/apis/use-follower-list.tsx +++ b/features/follow/apis/use-follower-list.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useInfiniteQuery } from '@tanstack/react-query'; +import { keepPreviousData, useInfiniteQuery } from '@tanstack/react-query'; import { ProfileFollow } from '../types'; @@ -26,10 +26,11 @@ export const useFollowerList = (memberId: number) => { getNextPageParam: (lastPage) => lastPage?.data?.hasNext ? lastPage?.data?.cursorId : undefined, enabled: !!memberId, + placeholderData: keepPreviousData, }); const flattenData = - query.data?.pages.flatMap(({ data }) => data?.contents) ?? []; + query.data?.pages.flatMap(({ data }) => data?.contents ?? []) ?? []; return { ...query, diff --git a/features/follow/apis/use-following-list.tsx b/features/follow/apis/use-following-list.tsx index 902f2e5a..b0b330c8 100644 --- a/features/follow/apis/use-following-list.tsx +++ b/features/follow/apis/use-following-list.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useInfiniteQuery } from '@tanstack/react-query'; +import { keepPreviousData, useInfiniteQuery } from '@tanstack/react-query'; import { ProfileFollow } from '../types'; @@ -26,10 +26,11 @@ export const useFollowingList = (memberId: number) => { getNextPageParam: (lastPage) => lastPage?.data?.hasNext ? lastPage?.data?.cursorId : undefined, enabled: !!memberId, + placeholderData: keepPreviousData, }); const flattenData = - query.data?.pages.flatMap(({ data }) => data?.contents) ?? []; + query.data?.pages.flatMap(({ data }) => data?.contents ?? []) ?? []; return { ...query, diff --git a/features/follow/components/follow-virtual-list.tsx b/features/follow/components/follow-virtual-list.tsx new file mode 100644 index 00000000..a80255b6 --- /dev/null +++ b/features/follow/components/follow-virtual-list.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { Virtuoso } from 'react-virtuoso'; + +import { ProfileListItem } from '@/components/molecules'; + +import { ProfileFollowContent } from '../types'; + +type FollowVirtualList = { + data: ProfileFollowContent[]; + fetchNextData: () => void; +}; +export const FollowVirtualList = ({ + data, + fetchNextData, +}: FollowVirtualList) => { + const handleRangeChanged = (range: { endIndex: number }) => { + const currentContentsLastIndex = data.length - 1; + if (range.endIndex >= currentContentsLastIndex - 3) { + void fetchNextData(); + } + }; + + return ( + } + style={{ + width: '100%', + height: '100%', + }} + /> + ); +}; diff --git a/features/follow/components/index.ts b/features/follow/components/index.ts new file mode 100644 index 00000000..49cad4d9 --- /dev/null +++ b/features/follow/components/index.ts @@ -0,0 +1 @@ +export * from './follow-virtual-list'; diff --git a/features/follow/sections/follower-section.tsx b/features/follow/sections/follower-section.tsx new file mode 100644 index 00000000..48416091 --- /dev/null +++ b/features/follow/sections/follower-section.tsx @@ -0,0 +1,17 @@ +'use client'; + +import { useFollowerList } from '../apis'; +import { FollowVirtualList } from '../components'; + +export const FollowerSection = ({ id }: { id: number }) => { + const { flattenData, hasNextPage, isFetchingNextPage, fetchNextPage } = + useFollowerList(id); + + const fetchNextData = () => { + if (hasNextPage && !isFetchingNextPage) { + void fetchNextPage(); + } + }; + + return ; +}; diff --git a/features/follow/sections/following-section.tsx b/features/follow/sections/following-section.tsx new file mode 100644 index 00000000..d588b31b --- /dev/null +++ b/features/follow/sections/following-section.tsx @@ -0,0 +1,17 @@ +'use client'; + +import { useFollowingList } from '../apis'; +import { FollowVirtualList } from '../components'; + +export const FollowingSection = ({ id }: { id: number }) => { + const { flattenData, hasNextPage, isFetchingNextPage, fetchNextPage } = + useFollowingList(id); + + const fetchNextData = () => { + if (hasNextPage && !isFetchingNextPage) { + void fetchNextPage(); + } + }; + + return ; +}; diff --git a/features/follow/sections/index.ts b/features/follow/sections/index.ts index c20b4d8a..ae3494f5 100644 --- a/features/follow/sections/index.ts +++ b/features/follow/sections/index.ts @@ -1 +1,3 @@ export * from './follow-tab'; +export * from './follower-section'; +export * from './following-section'; diff --git a/features/record-detail/apis/use-cheer-list.tsx b/features/record-detail/apis/use-cheer-list.tsx index a4355149..e78dbe78 100644 --- a/features/record-detail/apis/use-cheer-list.tsx +++ b/features/record-detail/apis/use-cheer-list.tsx @@ -34,7 +34,7 @@ export const useCheerList = (memoryId: number) => { }); const flattenData = - query.data?.pages.flatMap(({ data }) => data?.reactions) ?? []; + query.data?.pages.flatMap(({ data }) => data?.reactions ?? []) ?? []; const totalCount = query.data?.pages?.[0].data?.totalCount; return {