Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#439] [독서모임] 모임 목록 페이지 리팩토링 #440

Merged
merged 10 commits into from
Nov 23, 2023
12 changes: 12 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ const nextConfig = {
port: '',
pathname: '/**',
},
{
protocol: 'http',
hostname: 'k.kakaocdn.net',
port: '',
pathname: '/**',
},
{
protocol: 'https',
hostname: 'blog.kakaocdn.net',
port: '',
pathname: '/**',
},
Comment on lines +37 to +48
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ask;
기존에 있던 search1.kakaocdn.net 호스트 외에 두 호스트 에서 받아오는 이미지가 있나요? 👀

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@minjongbaek
네 말씀주신 호스트 외에 독서 모임 목록 페이지에서 위의 두 호스트에서 받아오는 이미지가 존재하여 에러가 발생해 추가로 작성하게 되었습니다! 👀

],
},
};
Expand Down
125 changes: 78 additions & 47 deletions src/app/group/page.tsx
Original file line number Diff line number Diff line change
@@ -1,57 +1,37 @@
'use client';

import TopHeader from '@/ui/Base/TopHeader';
import SearchGroup from '@/v1/bookGroup/SearchGroup';
import SimpleBookGroupCard from '@/v1/bookGroup/SimpleBookGroupCard';
import DetailBookGroupCard from '@/v1/bookGroup/DetailBookGroupCard';

import useEntireGroupsQuery from '@/queries/group/useEntireGroupsQuery';
import GroupHeader from '@/ui/Group/GroupHeader';
import GroupList from '@/ui/Group/GroupList';
import GroupSearch from '@/ui/Group/GroupSearch';
import { Box, Skeleton, VStack } from '@chakra-ui/react';
import { useState, useEffect } from 'react';
import useMyGroupsQuery from '@/queries/group/useMyGroupsQuery';
import { Skeleton, VStack } from '@chakra-ui/react';
import { useEffect } from 'react';
import { useInView } from 'react-intersection-observer';

interface SearchValue {
[key: string]: string;
input: string;
select: string;
}

const GroupPage = () => {
const [searchValue, setSearchValue] = useState<SearchValue>({
input: '',
select: '모임',
});
const { ref, inView } = useInView();

const {
isSuccess,
data,
isSuccess: entireGroupsIsSuccess,
data: entireGroupsData,
isLoading,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useEntireGroupsQuery();

const { isSuccess: myGroupsIsSuccess, data: myGroupsData } =
useMyGroupsQuery();

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

const handleSumbit = () => {
const { input } = searchValue;
if (input.trim().length === 0) {
/*공백만 입력한 경우 전체 데이터 렌더링 */
} else {
/*검색 API호출 및 setMeetingListData 업데이트 */
}
};

const handleChange = (name: string, value: string) => {
if (!(name in searchValue)) return;
const tempSearchValue = { ...searchValue };
tempSearchValue[name] = value;
setSearchValue(tempSearchValue);
};

if (isLoading)
return (
<VStack gap="0.5rem" align="stretch" w="100%">
Expand All @@ -63,22 +43,73 @@ const GroupPage = () => {
);

return (
<VStack align="center">
<Box w="100%">
<GroupHeader />
<GroupSearch
searchValue={searchValue}
handleChange={handleChange}
handleSumbit={handleSumbit}
<>
<TopHeader pathname={'/group'} />
<div className="mt-[2rem] flex w-full flex-col gap-[1.5rem]">
<SearchGroup
onClick={() => {
alert('추후 업데이트 될 예정입니다.');
}}
/>
{isSuccess &&
data.pages.map((groups, idx) => {
return <GroupList key={idx} bookGroups={groups.bookGroups} />;
})}
</Box>
<Box ref={ref} />
<div className="mt-[0.7rem] flex gap-[1rem] overflow-scroll">
{myGroupsIsSuccess &&
myGroupsData.bookGroups.map(group => {
const { title, book, bookGroupId } = group;
return (
//API isOwner 값이 존재하지 않아 비교하는 로직 추가 필요
<SimpleBookGroupCard
key={bookGroupId}
title={title}
imageSource={book.imageUrl}
isOwner={false}
bookGroupId={bookGroupId}
/>
);
})}
</div>
<div className="flex flex-col gap-[1rem]">
{entireGroupsIsSuccess &&
entireGroupsData.pages.map(groups => {
return groups.bookGroups.map(group => {
Comment on lines +71 to +73
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

받아온 데이터를 두 번 풀어서 사용해야 하는 상황에서 반복문을 두 번 돌리는 것이 어색한 것 같아 별도의 방법을 찾아보던 중
Tanstack Query 예제에서 제가 작성한 코드처럼 map을 두 번 사용하는 경우도 있기에 구조적으로 큰 어색함은 없다는 생각이 들어 다음과 같은 방식으로 코드를 구현했습니다.

const {
title,
introduce,
book,
startDate,
endDate,
owner,
memberCount,
commentCount,
isPublic,
bookGroupId,
} = group;
return (
<DetailBookGroupCard
key={bookGroupId}
title={title}
description={introduce}
bookImageSrc={book.imageUrl}
date={{ start: startDate, end: endDate }}
owner={{
name: owner.nickname,
profileImageSrc: owner.profileUrl,
}}
memberCount={memberCount}
commentCount={commentCount}
isPublic={isPublic}
bookGroupId={bookGroupId}
/>
);
});
})}
</div>
</div>
<div ref={ref} />
{isFetchingNextPage && <Skeleton w="100%" height="28rem" />}
</VStack>
{/* <Link href={'/group/create'}>
<FloatingButton position="bottom-right" />
</Link> */}
</>
);
};

Expand Down
9 changes: 2 additions & 7 deletions src/stories/bookGroup/DetailBookGroupCard.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ export const Default: Story = {
title: '프롱이 리팩터링 스터디',
description:
'제1차 프롱이 기수연합 독서 스터디 입니다. 마틴 파울러의 저서 ‘리팩터링 2판’과 함께 진행합니다.',
book: {
title: '리팩터링 2판',
bookImageSrc: 'https://image.yes24.com/goods/89649360/XL',
},
bookImageSrc: 'https://image.yes24.com/goods/89649360/XL',
date: {
start: '2023-10-31',
end: '2023-11-27',
Expand All @@ -32,8 +29,6 @@ export const Default: Story = {
},
isPublic: false,
commentCount: 12,
handleClick: () => {
alert('모임 상세 페이지로 이동');
},
bookGroupId: 1,
},
};
4 changes: 2 additions & 2 deletions src/stories/bookGroup/SearchGroup.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default meta;

type Story = StoryObj<typeof SearchGroup>;

const alertMessage = () => {
const handleClick = () => {
document.getElementById('groupSearching')?.blur();
alert(
`
Expand All @@ -22,5 +22,5 @@ const alertMessage = () => {
};

export const Default: Story = {
render: () => <SearchGroup handleClick={alertMessage} />,
render: () => <SearchGroup onClick={handleClick} />,
};
8 changes: 2 additions & 6 deletions src/stories/bookGroup/SimpleBookGroupCard.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,12 @@ export default meta;

type Story = StoryObj<typeof SimpleBookGroupCard>;

const handleClick = () => {
alert('모임 상세 페이지로 이동');
};

export const Default: Story = {
args: {
title: '데일카네기 인간관계론',
imageSource: 'https://image.yes24.com/goods/79297023/XL',
isOwner: false,
onClick: handleClick,
bookGroupId: 1,
},
};

Expand All @@ -29,6 +25,6 @@ export const OwnerCase: Story = {
title: '데일카네기 인간관계론',
imageSource: 'https://image.yes24.com/goods/79297023/XL',
isOwner: true,
onClick: handleClick,
bookGroupId: 1,
},
};
3 changes: 2 additions & 1 deletion src/types/group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { Pagination } from './common';
type APIGroupOwner = {
id: APIUser['userId'];
profileUrl: APIUser['profileImage'];
nickname: APIUser['nickname'];
// FIXME nickname: APIUser['nickname'] nullable 하지 않게 수정 후 다시 반영
nickname: string;
};

type APIGroupBook = {
Expand Down
2 changes: 1 addition & 1 deletion src/ui/Base/FloatingButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const FloatingButton = ({ position, ...props }: FloatingButtonProps) => {

return createPortal(
<button
className={`absolute flex h-[5.1rem] w-[5.1rem] items-center justify-center rounded-full bg-main-900 ${positionClasses}`}
className={`${positionClasses} fixed left-[50%] top-[50%] flex h-[5.1rem] w-[5.1rem] -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-main-900`}
{...props}
>
<IconPlus />
Expand Down
4 changes: 2 additions & 2 deletions src/v1/book/BookCover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type BookCoverSize =
type BookCoverProps = Required<
Pick<ComponentPropsWithoutRef<typeof Image>, 'src'>
> & {
title: string;
title?: string;
size?: BookCoverSize;
};
Comment on lines 14 to 19
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p5;
title prop 은 <img /> 요소에서 alt 속성을 위해 사용되는 것으로 보이네요. title은 책 이름을 화면에서 표시하기 위한 prop으로 보여질 수 있다고 생각해서 alt 로 변경해도 좋을 것 같다고 생각해요.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@minjongbaek
ask;
좋은 의견 감사합니다! 궁금한 점이 있는데 민종님은 보통 prop들을 정의할때 받아오는 prop의 상태(?) 혹은 prop의 내용(?)을 기준으로 prop의 이름을 정하는것이 아니라 해당 prop을 받는 컴포넌트를 기준으로 prop을 받는 컴포넌트에서 어떻게 활용되는지를 기준으로 prop이름들을 정하시는 것을 선호하시는 것 같은데? 저 또한 좋은 관점이라는 생각이 들었는데 어떤 이유에서 그런 생각을 하시게 된 건지 여쭤봐도 될까요?? 레퍼런스나 책, 혹은 조언을 들은 건가요?? 👀

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@minjongbaek
아 참고로 BookCover 컴포넌트의 경우 @gxxrxn 님이 구현을 해주셨는데 같이 디스코드를 통해서 작업을 하던 중에 제가 일부 옵셔널로 변경을 한 사항이라 규란님의 의견도 함께 들어보고 결정하면 좋을 것 같습니다.

@gxxrxn @minjongbaek

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋은 의견 감사합니다! 궁금한 점이 있는데 민종님은 보통 prop들을 정의할때 받아오는 prop의 상태(?) 혹은 prop의 내용(?)을 기준으로 prop의 이름을 정하는것이 아니라 해당 prop을 받는 컴포넌트를 기준으로 prop을 받는 컴포넌트에서 어떻게 활용되는지를 기준으로 prop이름들을 정하시는 것을 선호하시는 것 같은데? 저 또한 좋은 관점이라는 생각이 들었는데 어떤 이유에서 그런 생각을 하시게 된 건지 여쭤봐도 될까요?? 레퍼런스나 책, 혹은 조언을 들은 건가요?? 👀

@WooDaeHyun 따로 참고한 레퍼런스는 없습니다. prop이 HTML 요소에서 지원하는 속성을 위해 사용된다면 DX 측면에서 동일한 이름을 사용하는 것이 좋지 않을까? 라고 생각하는데 이런 생각이 코드에 반영되는 것 같아요. 이 부분은 주관적인 내용을 포함하고 있기에 '누구는 이런 기준으로 prop 명을 정하는구나.' 라고 봐주시면 좋을 것 같아요. 🙇

그렇기 때문에 코멘트에 p5를 적용했습니다. 만약 대현님께서 다른 생각을 가지고 있다면 공유해주시고, 제 피드백을 반영하지 않아도 좋아요!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@minjongbaek
이 부분에서는 정말 코드 작성 스타일에 따라 의견이 나뉠 수 있을 것 같아서! 프로젝트에 참여하는 사람들의 의견을 모아 앞으로 작성되는 prop 명들을 어떤 부분을 기준으로 이름을 정할지 의견을 나눠보면 좋을 것 같아요 👀

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고민해봤는데 저는 이미지를 보여줄 수 없을 때 alt 값을 보여주는만큼 BookCover 컴포넌트에서는 title이라는 정확한 값을 받는 것이 더 좋은 것 같다는 생각이 들어요!


Expand Down Expand Up @@ -48,7 +48,7 @@ const BookCover = ({ src, title, size = 'medium' }: BookCoverProps) => {
<div className={`relative ${sizeClasses}`}>
<Image
src={src}
alt={title}
alt={title || 'book-cover'}
placeholder="blur"
blurDataURL={DATA_URL['placeholder']}
className="object-fit rounded-[0.5rem] shadow-bookcover"
Expand Down
Loading