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

digest group list 가져오기 기능 추가 #26

Merged
merged 3 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/api/hooks/useFetchGroupListQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { axiosInstance } from '@/api/axiosInstance';
import { useQuery } from '@tanstack/react-query';
import { AxiosError, AxiosResponse } from 'axios';

interface Response {
groups: {
groupId: string;
name: string;
senders: {
name: string;
address: string;
}[];
}[];
}

export type GroupsType = {
groupId: string;
name: string;
senders: {
name: string;
address: string;
}[];
}[];

export const fetchGroupList = () => {
return axiosInstance.get('/inbox/groups');
};

export const useFetchGroupListQuery = () => {
return useQuery<AxiosResponse<Response>, AxiosError, Response>({
queryKey: ['groupList'],
queryFn: fetchGroupList,
select: ({ data }) => data,
});
};
14 changes: 1 addition & 13 deletions src/app/main/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,6 @@ import MainListTap from '@/components/ListTap/MainListTap';
import MainPageHeader from '@/components/Header/MainPageHeader';
import { Suspense } from 'react';

const tabData = {
today: {
name: '오늘의 인사이트',
},
search: {
name: '탐색 🔍',
},
Digest: {
name: 'Digest',
},
};

export default function MainPageLayout({
children,
}: Readonly<{
Expand All @@ -24,7 +12,7 @@ export default function MainPageLayout({
<MainPageHeader />
<div className='flex flex-col items-center w-full'>
<Suspense fallback={<div></div>}>
<MainListTap tabData={tabData} />
<MainListTap />
</Suspense>
<div className='flex justify-center h-full w-content'>{children}</div>
</div>
Expand Down
29 changes: 16 additions & 13 deletions src/components/ListTap/ListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
import Link from 'next/link';
import type { ComponentPropsWithoutRef } from 'react';

interface ListItemProps {
interface ListItemProps extends ComponentPropsWithoutRef<'div'> {
id: string;
name: string;
isActive: boolean;
tapCnt?: number;
onClick?: () => void;
}

const ListItem = ({ id, name, isActive, tapCnt, onClick }: ListItemProps) => {
const ListItem = ({ id, name, isActive, tapCnt, onClick, ...attributes }: ListItemProps) => {
return (
<Link
href={{
pathname: '/main',
query: { tab: id },
}}
onClick={onClick}
className={`${isActive ? (id === 'Digest' ? 'bg-background_grey rounded-t border-bottom-gradient text-black' : 'border-bottom-gradient text-black') : 'text-darkgrey'} flex flex-row items-center gap-4 px-6 py-3 whitespace-pre-wrap text-body3`}
>
{name}
{tapCnt && <span className='text-base font-light text-blue'>{tapCnt}</span>}
</Link>
<div {...attributes}>
<Link
href={{
pathname: '/main',
query: { tab: id },
}}
onClick={onClick}
className={`${isActive ? (id === 'Digest' ? 'bg-background_grey rounded-t border-bottom-gradient text-black' : 'border-bottom-gradient text-black') : 'text-darkgrey'} flex flex-row items-center gap-4 px-6 py-3 whitespace-pre-wrap text-body3`}
>
{name}
{tapCnt && <span className='text-base font-light text-blue'>{tapCnt}</span>}
</Link>
</div>
);
};

Expand Down
105 changes: 83 additions & 22 deletions src/components/ListTap/MainListTap.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,110 @@
'use client';

import { GroupsType, useFetchGroupListQuery } from '@/api/hooks/useFetchGroupListQuery';
import ListItem from '@/components/ListTap/ListItem';
import { useSearchParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';

interface TabData {
name: string;
}

interface MainListTapProps {
tabData: { [key: string]: TabData };
}

/**
*
* @param tabData tab에 들어갈 data
* @returns
*/
const MainListTap = ({ tabData }: MainListTapProps) => {
const MainListTap = () => {
const searchParams = useSearchParams();
const [currentTab, setCurrentTab] = useState(searchParams.get('tab') ?? 'today');
const [showOverlay, setShowOverlay] = useState(false);

const { data } = useFetchGroupListQuery();

const handleClickListItem = (id: string) => {
setCurrentTab(id);
};

const handleMouseoverDigest = useCallback((hover: boolean) => {
setShowOverlay(hover);
}, []);

useEffect(() => {
setCurrentTab(searchParams.get('tab') ?? 'today');
}, [searchParams]);

return (
<div className='flex justify-center w-full h-12 border-b border-lightgrey'>
<div className='flex flex-row h-full gap-4 w-content'>
{Object.keys(tabData).map(id => (
<ListItem
onClick={() => handleClickListItem('오늘의 인사이트')}
key={'오늘의 인사이트'}
id={'today'}
name={'오늘의 인사이트'}
isActive={currentTab === '오늘의 인사이트'}
/>
<ListItem
onClick={() => handleClickListItem('탐색')}
key={'탐색'}
id={'search'}
name={'탐색 🔎'}
isActive={currentTab === '탐색'}
/>
dladncks1217 marked this conversation as resolved.
Show resolved Hide resolved
<div>
<ListItem
onClick={() => handleClickListItem(id)}
key={id}
id={id}
name={tabData[id].name}
isActive={currentTab === id}
onClick={() => handleClickListItem('Digest')}
key={'Digest'}
id={'Digest'}
name={'Digest'}
isActive={currentTab === 'Digest'}
onMouseOver={() => handleMouseoverDigest(true)}
onMouseOut={() => handleMouseoverDigest(false)}
/>
))}
{showOverlay ? <DigestTabOverlay data={data ? data.groups : []} /> : <></>}
</div>
</div>
</div>
);
};

export default MainListTap;

interface TabOverlayProps {
data: GroupsType;
}

const DigestTabOverlay = ({ data }: TabOverlayProps) => {
const handleGroupMake = () => {
console.log('클릭시 [마이페이지>구독관리>그룹생성 팝업] 랜딩');
};

if (!data.length) {
return (
<div
style={{
width: '258px',
borderRadius: 'var(--Number-Spacing-spacing-3, 8px)',
background: 'var(--Color-Neutral-white, #FFF)',
boxShadow: '0px 0px 12px 0px rgba(0, 0, 0, 0.25)',
}}
className='absolute p-4 text-body2 m-2'
>
<div>뉴스레터 그룹을 만들어</div>
<div className='pb-2'>주제별로 편하게 모아보세요!</div>
<div className='w-full flex justify-end text-body2 text-blue cursor-pointer' onClick={handleGroupMake}>
+ 뉴스레터 그룹 만들기
</div>
</div>
);
}

return (
<div
style={{
width: '258px',
borderRadius: 'var(--Number-Spacing-spacing-3, 8px)',
background: 'var(--Color-Neutral-white, #FFF)',
boxShadow: '0px 0px 12px 0px rgba(0, 0, 0, 0.25)',
}}
className='absolute p-4 text-body2 m-2'
>
{data.map(group => (
<div className='pb-2'>{group.name}</div>
))}
<div className='w-full flex justify-end text-body2 text-blue cursor-pointer' onClick={handleGroupMake}>
+ 뉴스레터 그룹 만들기
</div>
</div>
);
Comment on lines +72 to +109
Copy link
Contributor

Choose a reason for hiding this comment

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

반복되는 로직과 inline 스타일을 조금 지양하기 위해 아래와 같이 바꿔보았는데, 요건 어떠한지 의견 부탁드려요!

Suggested change
if (!data.length) {
return (
<div
style={{
width: '258px',
borderRadius: 'var(--Number-Spacing-spacing-3, 8px)',
background: 'var(--Color-Neutral-white, #FFF)',
boxShadow: '0px 0px 12px 0px rgba(0, 0, 0, 0.25)',
}}
className='absolute p-4 text-body2 m-2'
>
<div>뉴스레터 그룹을 만들어</div>
<div className='pb-2'>주제별로 편하게 모아보세요!</div>
<div className='w-full flex justify-end text-body2 text-blue cursor-pointer' onClick={handleGroupMake}>
+ 뉴스레터 그룹 만들기
</div>
</div>
);
}
return (
<div
style={{
width: '258px',
borderRadius: 'var(--Number-Spacing-spacing-3, 8px)',
background: 'var(--Color-Neutral-white, #FFF)',
boxShadow: '0px 0px 12px 0px rgba(0, 0, 0, 0.25)',
}}
className='absolute p-4 text-body2 m-2'
>
{data.map(group => (
<div className='pb-2'>{group.name}</div>
))}
<div className='w-full flex justify-end text-body2 text-blue cursor-pointer' onClick={handleGroupMake}>
+ 뉴스레터 그룹 만들기
</div>
</div>
);
return (
<div
className='absolute w-[258px] p-4 text-body2 m-2 rounded-xl bg-white shadow-[0_0_12px_0_rgba(0,0,0,0.25)]'
>
{
!data.length ? (
<>
<div>뉴스레터 그룹을 만들어</div>
<div className='pb-2'>주제별로 편하게 모아보세요!</div>
</>
) :
{data.map(group => (
<div className='pb-2'>{group.name}</div>
))}
<div className='w-full flex justify-end text-body2 text-blue cursor-pointer' onClick={handleGroupMake}>
+ 뉴스레터 그룹 만들기
</div>
</div>
);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@moong23
DRY도 고려할 수 있긴 한데, 컴포넌트가 사실상 UI로직밖에 없는 코드잖아요?
그럼 이 코드를 읽을때 코드를 쭉 내려가다!data.length 부분을 읽어야만 UI가 두 케이스로 나눠진다는걸 알 수 있을 것 같아요.
컴포넌트 네이밍에서도 UI가 두 케이스로 나눠진다는 힌트는 없다보니 의도적으로 WET하게 작성했는데 어떻게 생각하시나요!?

Copy link
Contributor

Choose a reason for hiding this comment

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

옹 좋네요 요 부분에 대해서는 한번도 생각을 해본적이 없어서 사고가 여기까지 확장이 안되어있었어요 좋은 방법인것같아요

};
12 changes: 12 additions & 0 deletions src/mocks/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,18 @@ export const handlers = [
});
}),

http.get('/inbox/groups', req => {
return HttpResponse.json({
groups: [
{
groupId: 'mongo objecrt Id',
name: '그룹 이름',
senders: [{ name: '발신인 이름', address: '발신인 주소' }],
},
],
});
}),

http.get('/articleList', req => {
const { currentTab } = req.params;
return HttpResponse.json({
Expand Down
Loading