From fbf22ca5a238f1bb0411813e3efd61e6f6b96777 Mon Sep 17 00:00:00 2001 From: dladncks1217 Date: Sat, 14 Sep 2024 03:02:21 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=89=B4=EC=8A=A4=EB=A0=88=ED=84=B0=20?= =?UTF-8?q?=EA=B7=B8=EB=A3=B9=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B8=B0=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/H14/trash.svg | 7 +++ public/H24/dots.svg | 6 +++ src/app/article/[id]/ArticleFooter.tsx | 4 +- src/components/Domain/DomainListItem.tsx | 9 ++-- src/components/Domain/DomainPortal.tsx | 69 +++++++++++++++++++++--- src/components/Image.tsx | 9 +++- src/components/ListTap/ListItem.tsx | 3 +- src/components/ListTap/MainListTap.tsx | 63 +++++++++++++++------- src/mocks/handlers.ts | 27 +++++++++- src/utils/hooks/useGroupOverlayStore.ts | 14 +++++ src/utils/hooks/useThrottleCallback.ts | 4 +- 11 files changed, 178 insertions(+), 37 deletions(-) create mode 100644 public/H14/trash.svg create mode 100644 public/H24/dots.svg create mode 100644 src/utils/hooks/useGroupOverlayStore.ts diff --git a/public/H14/trash.svg b/public/H14/trash.svg new file mode 100644 index 0000000..2c22e41 --- /dev/null +++ b/public/H14/trash.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/public/H24/dots.svg b/public/H24/dots.svg new file mode 100644 index 0000000..0a2837c --- /dev/null +++ b/public/H24/dots.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/app/article/[id]/ArticleFooter.tsx b/src/app/article/[id]/ArticleFooter.tsx index 32df764..74bd718 100644 --- a/src/app/article/[id]/ArticleFooter.tsx +++ b/src/app/article/[id]/ArticleFooter.tsx @@ -17,14 +17,14 @@ const ArticleFooter = ({ mailId }: ArticleFooterProps) => { { mailId }, { onSuccess: () => { - router.push('/main?tab=Digest'); // 클라이언트 사이드에서 리디렉션 + router.push('/main?tab=Digest'); }, }, ); }; const handleRedirectMain = () => { - router.push('/main?tab=Digest'); // 클라이언트 사이드에서 리디렉션 + router.push('/main?tab=Digest'); }; return ( diff --git a/src/components/Domain/DomainListItem.tsx b/src/components/Domain/DomainListItem.tsx index 2d7b88e..ac74b00 100644 --- a/src/components/Domain/DomainListItem.tsx +++ b/src/components/Domain/DomainListItem.tsx @@ -6,6 +6,7 @@ import CloseIcon from '@/assets/icons/CloseIcon'; import FolderIcon from '@/assets/icons/FolderIcon.svg'; import { useState } from 'react'; import DomainPortal from './DomainPortal'; +import { useGroupOverlayStore } from '@/utils/hooks/useGroupOverlayStore'; interface Props { name: string; @@ -13,7 +14,7 @@ interface Props { const DomainListItem = ({ name }: Props) => { const [isHover, setIsHover] = useState(false); - const [isModalOpen, setIsModalOpen] = useState(false); + const { open, setOpen: setModalOpen } = useGroupOverlayStore(); const handleMouseEnter = () => { setIsHover(true); @@ -28,12 +29,12 @@ const DomainListItem = ({ name }: Props) => { }; const handleAddGroupAction = () => { - setIsModalOpen(true); + setModalOpen(true); }; const handlecloseModal = () => { setIsHover(false); - setIsModalOpen(false); + setModalOpen(false); }; return ( @@ -76,7 +77,7 @@ const DomainListItem = ({ name }: Props) => { )} - {isModalOpen && } + {open && } ); diff --git a/src/components/Domain/DomainPortal.tsx b/src/components/Domain/DomainPortal.tsx index 110064a..4e024a5 100644 --- a/src/components/Domain/DomainPortal.tsx +++ b/src/components/Domain/DomainPortal.tsx @@ -5,6 +5,8 @@ import FolderImage from '@/assets/images/FolderImage.svg'; import { useFunnel } from '@/utils/hooks/useFunnel'; import PlusIcon from '@/assets/icons/PlusIcon'; import { useState } from 'react'; +import { GroupsType, useFetchGroupListQuery } from '@/api/hooks/useFetchGroupListQuery'; +import { H14Image, H24Image } from '@/components/Image'; interface DomainPortalProps { handleCloseModal: () => void; @@ -14,14 +16,64 @@ interface StepProps { onNextStep: () => void; } -const DefaultStep = ({ onNextStep }: StepProps) => { +interface DefaultStepProps extends StepProps { + data: GroupsType; +} + +const DefaultStep = ({ onNextStep, data }: DefaultStepProps) => { + const [hoveredId, setHoveredId] = useState(null); + + const handleMouseEnter = (id: string) => { + setHoveredId(id); + }; + + const handleMouseLeave = () => { + setHoveredId(null); + }; + return ( -
-
- folder - 뉴스레터를 관심사별 그룹핑 해보세요! -
- +
+ {!data.length ? ( +
+ folder + 뉴스레터를 관심사별 그룹핑 해보세요! +
+ ) : ( +
+ {data.map(mail => { + return ( +
+
{mail.name}
+ handleMouseEnter(mail.groupId)} + onMouseLeave={handleMouseLeave} + > + + {hoveredId === mail.groupId && ( +
+
+ +
+
그룹 삭제하기
+
+ )} +
+
+ ); + })} +
+ )} + + 뉴스레터 그룹 만들기
@@ -53,6 +105,7 @@ const steps = ['default', 'create']; const DomainPortal = ({ handleCloseModal }: DomainPortalProps) => { const { Funnel, Step, setStep } = useFunnel(steps[0]); + const { data } = useFetchGroupListQuery(); return ( @@ -72,7 +125,7 @@ const DomainPortal = ({ handleCloseModal }: DomainPortalProps) => {
- setStep(steps[1])} /> + setStep(steps[1])} /> setStep(steps[0])} /> diff --git a/src/components/Image.tsx b/src/components/Image.tsx index 43e072c..e1c3610 100644 --- a/src/components/Image.tsx +++ b/src/components/Image.tsx @@ -24,6 +24,13 @@ function H8Image({ src, width = 8, ...props }: ImageProps) { return {alt}; } +function H12Image({ src, width = 12, ...props }: ImageProps) { + const fileName = src.split('.')[0]; + const alt = getAlt(fileName); + + return {alt}; +} + function H14Image({ src, width = 14, ...props }: ImageProps) { const fileName = src.split('.')[0]; const alt = getAlt(fileName); @@ -66,4 +73,4 @@ function H92Image({ src, width = 92, ...props }: ImageProps) { return {alt}; } -export { H8Image, H14Image, H16Image, H24Image, H36Image, H56Image, H92Image }; +export { H8Image, H12Image, H14Image, H16Image, H24Image, H36Image, H56Image, H92Image }; diff --git a/src/components/ListTap/ListItem.tsx b/src/components/ListTap/ListItem.tsx index 6d29fd2..1b655da 100644 --- a/src/components/ListTap/ListItem.tsx +++ b/src/components/ListTap/ListItem.tsx @@ -9,7 +9,7 @@ interface ListItemProps extends ComponentPropsWithoutRef<'div'> { onClick?: () => void; } -const ListItem = ({ id, name, isActive, tapCnt, onClick, ...attributes }: ListItemProps) => { +const ListItem = ({ id, name, isActive, tapCnt, onClick, children, ...attributes }: ListItemProps) => { return (
+ {children} {name} {tapCnt && {tapCnt}} diff --git a/src/components/ListTap/MainListTap.tsx b/src/components/ListTap/MainListTap.tsx index 66a626d..a5804a2 100644 --- a/src/components/ListTap/MainListTap.tsx +++ b/src/components/ListTap/MainListTap.tsx @@ -2,13 +2,15 @@ import { GroupsType, useFetchGroupListQuery } from '@/api/hooks/useFetchGroupListQuery'; import ListItem from '@/components/ListTap/ListItem'; +import { useGroupOverlayStore } from '@/utils/hooks/useGroupOverlayStore'; import { useSearchParams } from 'next/navigation'; -import { useCallback, useEffect, useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { ComponentPropsWithoutRef, MouseEvent, useCallback, useEffect, useState } from 'react'; const MainListTap = () => { const searchParams = useSearchParams(); const [currentTab, setCurrentTab] = useState(searchParams.get('tab') ?? 'today'); - const [showOverlay, setShowOverlay] = useState(false); + const [isHover, setIsHover] = useState(false); const { data } = useFetchGroupListQuery(); @@ -16,8 +18,12 @@ const MainListTap = () => { setCurrentTab(id); }; - const handleMouseoverDigest = useCallback((hover: boolean) => { - setShowOverlay(hover); + const handleMouseEnterDigest = useCallback(() => { + setIsHover(true); + }, []); + + const handleMouseLeaveDigest = useCallback(() => { + setIsHover(false); }, []); useEffect(() => { @@ -34,15 +40,28 @@ const MainListTap = () => { name={'오늘의 인사이트'} isActive={currentTab === 'today'} /> -
handleMouseoverDigest(true)} onMouseOut={() => handleMouseoverDigest(false)}> - handleClickListItem('Digest')} - key={'Digest'} - id={'Digest'} - name={'Digest'} - isActive={currentTab === 'Digest'} - /> - {showOverlay ? : <>} + handleClickListItem('탐색')} + key={'탐색'} + id={'search'} + name={'탐색 🔎'} + isActive={currentTab === 'search'} + /> +
+ + handleClickListItem('Digest')} + key={'Digest'} + id={'Digest'} + name={'Digest'} + isActive={currentTab === 'Digest'} + /> + {isHover && } +
handleClickListItem('탐색')} @@ -58,13 +77,19 @@ const MainListTap = () => { export default MainListTap; -interface TabOverlayProps { +interface TabOverlayProps extends ComponentPropsWithoutRef<'div'> { data: GroupsType; } -const DigestTabOverlay = ({ data }: TabOverlayProps) => { - const handleGroupMake = () => { - console.log('클릭시 [마이페이지>구독관리>그룹생성 팝업] 랜딩'); +const DigestTabOverlay = ({ data, onMouseEnter, onMouseLeave }: TabOverlayProps) => { + const { setOpen } = useGroupOverlayStore(); + const router = useRouter(); + + const handleGroupMake = (e: MouseEvent) => { + e.preventDefault(); + + setOpen(true); + router.push('/mypage/subscribe'); }; if (!data.length) { @@ -95,7 +120,9 @@ const DigestTabOverlay = ({ data }: TabOverlayProps) => { background: 'var(--Color-Neutral-white, #FFF)', boxShadow: '0px 0px 12px 0px rgba(0, 0, 0, 0.25)', }} - className='absolute z-50 p-4 m-2 text-body2' + className='absolute z-50 p-4 text-body2' + onMouseEnter={onMouseEnter} + onMouseLeave={onMouseLeave} > {data.map(group => (
diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index 707465e..2b8071a 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -155,10 +155,20 @@ export const handlers = [ return HttpResponse.json({ groups: [ { - groupId: 'mongo objecrt Id', + groupId: '7534132413243241', name: '그룹 이름', senders: [{ name: '발신인 이름', address: '발신인 주소' }], }, + { + groupId: '1234132413243251', + name: '그룹 이름2', + senders: [{ name: '발신인 이름', address: '발신인 주소' }], + }, + { + groupId: '1234132413243241', + name: '그룹 이름3', + senders: [{ name: '발신인 이름', address: '발신인 주소' }], + }, ], }); }), @@ -247,6 +257,21 @@ export const handlers = [ return HttpResponse.json(mailListData); }), + http.get('/inbox/subscriptions', () => { + return HttpResponse.json({ + subscriptions: [ + { + name: '[mock] LinkedIn', + address: 'asdf@gmail.com', + }, + { + name: '[mock] LinkedIn2', + address: 'asdf222@gmail.com', + }, + ], + }); + }), + http.get('/inbox/subscriptions-list', () => { return HttpResponse.json({ subscriptions: { diff --git a/src/utils/hooks/useGroupOverlayStore.ts b/src/utils/hooks/useGroupOverlayStore.ts new file mode 100644 index 0000000..59acb9d --- /dev/null +++ b/src/utils/hooks/useGroupOverlayStore.ts @@ -0,0 +1,14 @@ +import { createWithEqualityFn } from 'zustand/traditional'; + +interface groupOverlayStoreProps { + open: boolean; + setOpen: (open: boolean) => void; +} + +export const useGroupOverlayStore = createWithEqualityFn()( + set => ({ + open: false, + setOpen: (open: boolean) => set({ open }), + }), + Object.is, +); diff --git a/src/utils/hooks/useThrottleCallback.ts b/src/utils/hooks/useThrottleCallback.ts index 8daa8b0..4d47695 100644 --- a/src/utils/hooks/useThrottleCallback.ts +++ b/src/utils/hooks/useThrottleCallback.ts @@ -1,6 +1,6 @@ import { useRef } from 'react'; -function useThrottleCallback(callback: (...rest: Parameter) => void, delay: number) { +const useThrottleCallback = (callback: (...rest: Parameter) => void, delay: number) => { const timerRef = useRef(); const restRef = useRef(); @@ -21,6 +21,6 @@ function useThrottleCallback(callback: (...rest: Parame clearTimeout(timerRef.current); timerRef.current = setTimeout(timeoutCallback, delay); }; -} +}; export default useThrottleCallback;