diff --git a/next.config.ts b/next.config.ts index 9d20e0cd..63864dfe 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,12 +1,13 @@ import type { NextConfig } from 'next'; const nextConfig: NextConfig = { + reactStrictMode: false, experimental: { scrollRestoration: false, }, images: { // 외부 이미지 최적화 완전 비활성화 (Vercel 유료 기능 회피) - unoptimized: true, + // unoptimized: true, domains: [ 'team2-app-s3-bucket.s3.ap-northeast-2.amazonaws.com', 'team2-app-s3-bucket.s3.amazonaws.com', @@ -15,12 +16,14 @@ const nextConfig: NextConfig = { { protocol: 'https', hostname: 'www.thecocktaildb.com', + pathname: '/images/**', }, ], }, env: { NPUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL, }, + // webpack 설정 webpack: (config) => { // @ts-expect-error 타입 에러 무시 diff --git a/src/app/(with-layout)/mypage/layout.tsx b/src/app/(with-layout)/mypage/layout.tsx index 92109875..ee282f11 100644 --- a/src/app/(with-layout)/mypage/layout.tsx +++ b/src/app/(with-layout)/mypage/layout.tsx @@ -1,5 +1,5 @@ -import MyNav from '@/domains/mypage/main/MyNav'; -import MyProfile from '@/domains/mypage/main/MyProfile'; +import MyNav from '@/domains/mypage/components/main/MyNav'; +import MyProfile from '@/domains/mypage/components/main/MyProfile'; import SkeletonLayout from '@/domains/mypage/skeleton/main/SkeletonLayout'; import { Suspense } from 'react'; diff --git a/src/app/(with-layout)/mypage/my-setting/page.tsx b/src/app/(with-layout)/mypage/my-setting/page.tsx index 2cb147bd..d6519e97 100644 --- a/src/app/(with-layout)/mypage/my-setting/page.tsx +++ b/src/app/(with-layout)/mypage/my-setting/page.tsx @@ -1,5 +1,4 @@ -import MySetting from '@/domains/mypage/main/MySetting'; - +import MySetting from '@/domains/mypage/components/main/MySetting'; import { Metadata } from 'next'; export const metadata: Metadata = { title: '마이페이지', diff --git a/src/domains/community/hook/useItemVirtualizer.ts b/src/domains/community/hook/useItemVirtualizer.ts index b6a77869..0954c4c8 100644 --- a/src/domains/community/hook/useItemVirtualizer.ts +++ b/src/domains/community/hook/useItemVirtualizer.ts @@ -1,8 +1,9 @@ import { useVirtualizer } from '@tanstack/react-virtual'; import { CommentType, Post } from '../types/post'; +import { Cocktail } from '@/domains/recipe/types/types'; export function useItemVirtualizer( - items: CommentType[] | Post[] | null, + items: CommentType[] | Post[] | Cocktail[] | null, parentRef: React.RefObject ) { return useVirtualizer({ diff --git a/src/domains/mypage/main/MyAbv.tsx b/src/domains/mypage/components/main/MyAbv.tsx similarity index 96% rename from src/domains/mypage/main/MyAbv.tsx rename to src/domains/mypage/components/main/MyAbv.tsx index d07ac7bb..e681ffb9 100644 --- a/src/domains/mypage/main/MyAbv.tsx +++ b/src/domains/mypage/components/main/MyAbv.tsx @@ -1,7 +1,7 @@ 'use client'; import Help from '@/shared/assets/icons/help_24.svg'; import ToolTip from '@/shared/components/tool-tip/ToolTip'; -import useMedia from '../hook/useMedia'; +import useMedia from '../../hook/useMedia'; function MyAbv({ abv }: { abv: number }) { const isMd = useMedia('(min-width:768px)'); diff --git a/src/domains/mypage/main/MyNav.tsx b/src/domains/mypage/components/main/MyNav.tsx similarity index 98% rename from src/domains/mypage/main/MyNav.tsx rename to src/domains/mypage/components/main/MyNav.tsx index e4141ba7..afabdfd6 100644 --- a/src/domains/mypage/main/MyNav.tsx +++ b/src/domains/mypage/components/main/MyNav.tsx @@ -1,7 +1,8 @@ 'use client'; -import TabMenu from '@/domains/mypage/main/TabMenu'; + import Link from 'next/link'; import { usePathname } from 'next/navigation'; +import TabMenu from './TabMenu'; const MAIN_TABMENU = [ { diff --git a/src/domains/mypage/main/MyProfile.tsx b/src/domains/mypage/components/main/MyProfile.tsx similarity index 97% rename from src/domains/mypage/main/MyProfile.tsx rename to src/domains/mypage/components/main/MyProfile.tsx index 6f3b3de3..7ad9ed48 100644 --- a/src/domains/mypage/main/MyProfile.tsx +++ b/src/domains/mypage/components/main/MyProfile.tsx @@ -3,8 +3,9 @@ import AbvGraph from '@/domains/shared/components/abv-graph/AbvGraph'; import MyAbv from './MyAbv'; import SsuryImage from './SsuryImage'; -import useFetchProfile from '../api/fetchProfile'; + import { useQuery } from '@tanstack/react-query'; +import useFetchProfile from '../../api/fetchProfile'; function MyProfile() { const { fetchProfile } = useFetchProfile(); diff --git a/src/domains/mypage/main/MySetting.tsx b/src/domains/mypage/components/main/MySetting.tsx similarity index 97% rename from src/domains/mypage/main/MySetting.tsx rename to src/domains/mypage/components/main/MySetting.tsx index 716f1ecf..ab08ae14 100644 --- a/src/domains/mypage/main/MySetting.tsx +++ b/src/domains/mypage/components/main/MySetting.tsx @@ -4,8 +4,9 @@ import ToggleBtn from '@/domains/mypage/components/ToggleBtn'; import WithdrawModal from '@/domains/mypage/components/WithdrawModal'; import TextButton from '@/shared/components/button/TextButton'; import { useEffect, useState } from 'react'; -import useFetchProfile from '../api/fetchProfile'; + import { useQuery } from '@tanstack/react-query'; +import useFetchProfile from '../../api/fetchProfile'; function MySetting() { const { fetchProfile } = useFetchProfile(); diff --git a/src/domains/mypage/main/SsuryImage.tsx b/src/domains/mypage/components/main/SsuryImage.tsx similarity index 78% rename from src/domains/mypage/main/SsuryImage.tsx rename to src/domains/mypage/components/main/SsuryImage.tsx index e6fdb49c..02d5cf55 100644 --- a/src/domains/mypage/main/SsuryImage.tsx +++ b/src/domains/mypage/components/main/SsuryImage.tsx @@ -1,5 +1,5 @@ -import useProfileSsury from '../hook/useProfileSsury'; import Image from 'next/image'; +import useProfileSsury from '../../hook/useProfileSsury'; function SsuryImage({ abvLevel }: { abvLevel: number }) { const profileImage = useProfileSsury(abvLevel); diff --git a/src/domains/mypage/main/TabMenu.tsx b/src/domains/mypage/components/main/TabMenu.tsx similarity index 100% rename from src/domains/mypage/main/TabMenu.tsx rename to src/domains/mypage/components/main/TabMenu.tsx diff --git a/src/domains/mypage/components/pages/my-active/MyComment.tsx b/src/domains/mypage/components/pages/my-active/MyComment.tsx index daf1e549..f18d20a5 100644 --- a/src/domains/mypage/components/pages/my-active/MyComment.tsx +++ b/src/domains/mypage/components/pages/my-active/MyComment.tsx @@ -22,6 +22,7 @@ function MyComment() { credentials: 'include', }); const json = await res.json(); + setMyComment(json.data.items); }; @@ -31,7 +32,7 @@ function MyComment() { return (
- {CommentList.length !== 0 ? ( + {myComment.length !== 0 ? ( > => { const res = await fetch(`${getApi}/me/bar`, { method: 'GET', @@ -41,6 +42,7 @@ const fetchKeep = async (): Promise> => { return new Set(myKeep.map((v: { cocktailId: number }) => v.cocktailId)); }; +// 비 로그인 유저도 볼 수 있는 칵테일 API fetch 각 종 정렬 파라미터를 문자열로 받아서 정렬함 const fetchRecipe = async ( pageParam: PageParam | null, size: number, @@ -68,6 +70,7 @@ const fetchRecipe = async ( return json.data ?? []; }; +// 검색전용 API 여기서 필터링 토글도 받음 const searchCocktails = async (filters: SearchFilters): Promise => { const body = { keyword: filters.keyword?.trim() ?? '', @@ -90,6 +93,7 @@ const searchCocktails = async (filters: SearchFilters): Promise => { return json.data ?? []; }; +// 적용된 필터 const hasActiveFilters = (filters: SearchFilters): boolean => { return !!( filters.keyword?.trim() || @@ -99,10 +103,24 @@ const hasActiveFilters = (filters: SearchFilters): boolean => { ); }; +export const useKeepQuery = () => { + const user = useAuthStore((state) => state.user); + + return useQuery({ + queryKey: ['keeps', user?.id], + queryFn: fetchKeep, + enabled: !!user, + staleTime: 5 * 60 * 1000, + gcTime: 10 * 60 * 1000, + }); +}; + +// 무한스크롤 fetch export const useCocktailsInfiniteQuery = (size: number = 20, sortBy?: Sort) => { const user = useAuthStore((state) => state.user); const queryClient = useQueryClient(); const prevSortBy = useRef(sortBy); + const { data: keepIds } = useKeepQuery(); useEffect(() => { if (prevSortBy.current !== undefined && prevSortBy.current !== sortBy) { @@ -118,11 +136,10 @@ export const useCocktailsInfiniteQuery = (size: number = 20, sortBy?: Sort) => { queryFn: async ({ pageParam }) => { const cocktails = await fetchRecipe(pageParam, size, sortBy); - if (user) { - const keepId = await fetchKeep(); + if (user && keepIds) { return cocktails.map((item) => ({ ...item, - isKeep: keepId.has(item.cocktailId), + isKeep: keepIds.has(item.cocktailId), })); } @@ -159,31 +176,36 @@ export const useCocktailsInfiniteQuery = (size: number = 20, sortBy?: Sort) => { initialPageParam: null as PageParam | null, refetchOnMount: false, refetchOnWindowFocus: false, + staleTime: 2 * 60 * 1000, }); }; +// 검색용 fetch export const useCocktailsSearchQuery = (filters: SearchFilters) => { const user = useAuthStore((state) => state.user); const isActive = hasActiveFilters(filters); + const { data: keepIds } = useKeepQuery(); return useQuery({ queryKey: ['cocktails', 'search', filters, user?.id], queryFn: async () => { const cocktails = await searchCocktails(filters); - if (user && cocktails.length > 0) { - const keepId = await fetchKeep(); + if (user && cocktails.length > 0 && keepIds) { return cocktails.map((item) => ({ ...item, - isKeep: keepId.has(item.cocktailId), + isKeep: keepIds.has(item.cocktailId), })); } return cocktails; }, enabled: isActive, refetchOnMount: false, + refetchOnWindowFocus: false, + staleTime: 5 * 60 * 1000, }); }; +// 검색모드를 전환하여 어떤 fetch를 하는지 결정 export const useCocktails = ( filters: CocktailFilter, infiniteScrollSize: number = 20, diff --git a/src/domains/recipe/components/main/CocktailFilter.tsx b/src/domains/recipe/components/main/CocktailFilter.tsx index 2d8c323f..26313020 100644 --- a/src/domains/recipe/components/main/CocktailFilter.tsx +++ b/src/domains/recipe/components/main/CocktailFilter.tsx @@ -1,5 +1,5 @@ import SelectBox from '@/shared/components/select-box/SelectBox'; -import { useRouter } from 'next/navigation'; +import { useRouter, useSearchParams } from 'next/navigation'; interface Props { cocktailsEA: number; @@ -12,18 +12,34 @@ function CocktailFilter({ cocktailsEA }: Props) { 댓글순: 'comments', }; + const searchParams = useSearchParams(); const router = useRouter(); + const getCurrentSort = () => { + const sortBy = searchParams.get('sortBy') || 'recent'; + + const entry = Object.entries(sortMap).find(([_, value]) => value === sortBy); + return entry ? entry[0] : '최신순'; + }; + const handleChange = (selectTitle: string) => { const sortValue = sortMap[selectTitle as keyof typeof sortMap]; + const params = new URLSearchParams(searchParams.toString()); + params.set('sortBy', sortValue); + router.push(`?sortBy=${sortValue}`); }; return (

{cocktailsEA}개+

- +
); } diff --git a/src/domains/recipe/components/main/Accordion.tsx b/src/domains/recipe/components/main/CocktailFilterRadios.tsx similarity index 83% rename from src/domains/recipe/components/main/Accordion.tsx rename to src/domains/recipe/components/main/CocktailFilterRadios.tsx index 286a782d..4ee63859 100644 --- a/src/domains/recipe/components/main/Accordion.tsx +++ b/src/domains/recipe/components/main/CocktailFilterRadios.tsx @@ -1,8 +1,8 @@ 'use client'; import SelectBox from '@/shared/components/select-box/SelectBox'; -import { Dispatch, SetStateAction, useEffect } from 'react'; -import { useSearchParams, usePathname, useRouter } from 'next/navigation'; +import { usePathname, useRouter, useSearchParams } from 'next/navigation'; +import { Dispatch, SetStateAction } from 'react'; interface Props { setAlcoholBaseTypes: Dispatch>; @@ -64,26 +64,21 @@ const SELECT_OPTIONS = [ }, ]; -function Accordion({ setAlcoholBaseTypes, setCocktailTypes, setAlcoholStrengths }: Props) { +function CocktailFilterRadios({ + setAlcoholBaseTypes, + setAlcoholStrengths, + setCocktailTypes, +}: Props) { const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); - // url 파라미터에서 값을 가져와 POST를 도와줌 - useEffect(() => { - const abv = searchParams.get('abv'); - const base = searchParams.get('base'); - const glass = searchParams.get('glass'); - - setAlcoholStrengths(abv ? [abv] : []); - setAlcoholBaseTypes(base ? [base] : []); - setCocktailTypes(glass ? [glass] : []); - }, [searchParams, setAlcoholStrengths, setAlcoholBaseTypes, setCocktailTypes]); - // 파라미터 값을 한글로 역 변환해주는 함수 const getDisplayValue = (id: string, code: string | null): string => { + // 파라미터에서 가져오는 값 없다면 전체로 표시 이 값을 전체가 아닌 타이틀이 나와야함 if (!code) return '전체'; + // 파라미터에서 가져오는 아이디가 선택한 아이디와 일치하는지 const optionGroup = SELECT_OPTIONS.find((opt) => opt.id === id); if (!optionGroup) return '전체'; @@ -147,9 +142,8 @@ function Accordion({ setAlcoholBaseTypes, setCocktailTypes, setAlcoholStrengths handleSelect(id, value)} /> @@ -159,4 +153,4 @@ function Accordion({ setAlcoholBaseTypes, setCocktailTypes, setAlcoholStrengths ); } -export default Accordion; +export default CocktailFilterRadios; diff --git a/src/domains/recipe/components/main/CocktailList.tsx b/src/domains/recipe/components/main/CocktailList.tsx index 3c43da7b..c76bbdcb 100644 --- a/src/domains/recipe/components/main/CocktailList.tsx +++ b/src/domains/recipe/components/main/CocktailList.tsx @@ -9,6 +9,8 @@ interface Props { cocktails: Cocktail[]; } +// Grid반응형 구조에서 virtual의 높이가 측정하기 힘든문제로 인해 virtual사용 포기 + function CocktailList({ cocktails }: Props) { const { saveAndNavigate } = useSaveScroll({ storageKey: 'cocktail_list_scroll', @@ -21,35 +23,37 @@ function CocktailList({ cocktails }: Props) { }; return ( -
    - {cocktails.map( - ( - { cocktailImgUrl, cocktailId, cocktailName, cocktailNameKo, alcoholStrength, isKeep }, - i - ) => ( -
  • - - - -
  • - ) - )} -
+
+
    + {cocktails.map( + ( + { cocktailImgUrl, cocktailId, cocktailName, cocktailNameKo, alcoholStrength, isKeep }, + i + ) => ( +
  • + + + +
  • + ) + )} +
+
); } export default CocktailList; diff --git a/src/domains/recipe/components/main/Cocktails.tsx b/src/domains/recipe/components/main/Cocktails.tsx index f3d22eea..45fd9781 100644 --- a/src/domains/recipe/components/main/Cocktails.tsx +++ b/src/domains/recipe/components/main/Cocktails.tsx @@ -1,27 +1,26 @@ 'use client'; -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useRef } from 'react'; import CocktailFilter from './CocktailFilter'; import CocktailList from './CocktailList'; -import Accordion from './Accordion'; import CocktailSearchBar from './CocktailSearchBar'; import { useCocktails } from '../../api/fetchRecipe'; import { useInView } from 'react-intersection-observer'; -import { debounce } from '@/shared/utills/debounce'; -import { useSearchParams } from 'next/navigation'; -import { Sort } from '../../types/types'; +import CocktailFilterRadios from './CocktailFilterRadios'; +import { useCocktailFilter } from '../../hook/useCocktailFilter'; +import { useCocktailSearch } from '../../hook/useCocktailSearch'; function Cocktails() { - const searchParams = useSearchParams(); - const sortByParam = searchParams.get('sortBy') || 'recent'; - const [keyword, setKeyword] = useState(''); - const [input, setInput] = useState(''); - - const [sortBy, setSortBy] = useState(sortByParam as Sort); - const [alcoholStrengths, setAlcoholStrengths] = useState([]); - const [alcoholBaseTypes, setAlcoholBaseTypes] = useState([]); - const [cocktailTypes, setCocktailTypes] = useState([]); - + const { keyword, input, handleSearch } = useCocktailSearch(); + const { + alcoholBaseTypes, + alcoholStrengths, + cocktailTypes, + sortBy, + setAlcoholBaseTypes, + setAlcoholStrengths, + setCocktailTypes, + } = useCocktailFilter(); const { data, fetchNextPage, hasNextPage, noResults, isSearchMode } = useCocktails( { keyword, @@ -37,29 +36,22 @@ function Cocktails() { threshold: 0.1, }); + const prevInView = useRef(inView); + useEffect(() => { - if (!isSearchMode && inView && hasNextPage) { + if (!isSearchMode && inView && hasNextPage && !prevInView.current) { fetchNextPage?.(); } - }, [inView, hasNextPage, fetchNextPage]); - - useEffect(() => { - setSortBy(sortByParam as Sort); - }, [sortByParam]); - - const debounceKeyword = useMemo(() => debounce((v: string) => setKeyword(v), 300), []); - const handleSearch = (v: string) => { - setInput(v); - debounceKeyword(v); - }; + prevInView.current = inView; + }, [inView, hasNextPage]); return (
-
diff --git a/src/domains/recipe/hook/useCocktailFilter.ts b/src/domains/recipe/hook/useCocktailFilter.ts new file mode 100644 index 00000000..36d1b20e --- /dev/null +++ b/src/domains/recipe/hook/useCocktailFilter.ts @@ -0,0 +1,27 @@ +import { useSearchParams } from 'next/navigation'; +import { useEffect, useState } from 'react'; +import { Sort } from '../types/types'; + +export const useCocktailFilter = () => { + const searchParams = useSearchParams(); + const sortByParam = searchParams.get('sortBy') || 'recent'; + + const [sortBy, setSortBy] = useState(sortByParam as Sort); + const [alcoholStrengths, setAlcoholStrengths] = useState([]); + const [alcoholBaseTypes, setAlcoholBaseTypes] = useState([]); + const [cocktailTypes, setCocktailTypes] = useState([]); + + useEffect(() => { + setSortBy(sortByParam as Sort); + }, []); + + return { + sortBy, + alcoholBaseTypes, + alcoholStrengths, + cocktailTypes, + setAlcoholBaseTypes, + setAlcoholStrengths, + setCocktailTypes, + }; +}; diff --git a/src/domains/recipe/hook/useCocktailSearch.ts b/src/domains/recipe/hook/useCocktailSearch.ts new file mode 100644 index 00000000..b9e97ab1 --- /dev/null +++ b/src/domains/recipe/hook/useCocktailSearch.ts @@ -0,0 +1,19 @@ +import { debounce } from '@/shared/utills/debounce'; +import { useMemo, useState } from 'react'; + +export const useCocktailSearch = () => { + const [keyword, setKeyword] = useState(''); + const [input, setInput] = useState(''); + + const debounceKeyword = useMemo(() => debounce((v: string) => setKeyword(v), 300), []); + const handleSearch = (v: string) => { + setInput(v); + debounceKeyword(v); + }; + + return { + keyword, + input, + handleSearch, + }; +}; diff --git a/src/domains/recipe/store/accordionStore.ts b/src/domains/recipe/store/accordionStore.ts deleted file mode 100644 index 4e58151b..00000000 --- a/src/domains/recipe/store/accordionStore.ts +++ /dev/null @@ -1,34 +0,0 @@ -// zustand -import { create } from 'zustand'; - -// select박스 아코디언 메뉴 -export type ID = string | number; - -type AccordionState = { - openByGroup: Record; -}; - -type AccordionAction = { - setOpen: (group: string, id: ID | null) => void; - toggle: (group: string, id: ID) => void; - closeGroup: (group: string) => void; -}; - -type Accordion = AccordionState & AccordionAction; - -export const useAccordionStore = create((set) => ({ - openByGroup: {}, - setOpen: (group, id) => set((s) => ({ openByGroup: { ...s.openByGroup, [group]: id } })), - - // 같은 id가 이미 열려있으면 닫고 id 교체 - // 동적키를 받아서 관리 키가없으면 어떻게든 id든 타이틀이던 받아서 있음. 그래서 false면 아이디가 키로 들어가는데 - // 같은 아이디클릭시 null을 반환해서 토글이 됨. - toggle: (group, id) => - set((s) => { - const cur = s.openByGroup[group] ?? null; - return { openByGroup: { ...s.openByGroup, [group]: cur === id ? null : id } }; - }), - - // 선택 후 닫기 - closeGroup: (group) => set((s) => ({ openByGroup: { ...s.openByGroup, [group]: null } })), -})); diff --git a/src/domains/shared/components/abv-graph/AbvGraph.tsx b/src/domains/shared/components/abv-graph/AbvGraph.tsx index d93c4915..8c87a976 100644 --- a/src/domains/shared/components/abv-graph/AbvGraph.tsx +++ b/src/domains/shared/components/abv-graph/AbvGraph.tsx @@ -26,11 +26,11 @@ function AbvGraph({ max, abv, type = 'cocktail' }: Props) { const rawPct = (abv / safeMax) * 100; const pct = Math.min(100, Math.max(0, Number.isFinite(rawPct) ? rawPct : 0)); - const bandClass = clsx( - 'h-full rounded-full transition-[width] duration-500', - 'bg-gradient-to-r from-[#FFCA8D] to-[#FA2424]', // 기본 그라데이션 - pct >= 80 && 'shadow-[0_0_12px_rgba(250,36,36,0.45)]' - ); + // const bandClass = clsx( + // 'h-full rounded-full transition-[width] duration-500', + // 'bg-gradient-to-r from-[#FFCA8D] to-[#FA2424]', // 기본 그라데이션 + // pct >= 80 && 'shadow-[0_0_12px_rgba(250,36,36,0.45)]' + // ); return (
-
+
= 80 && 'shadow-[0_0_12px_rgba(250,36,36,0.45)]' + )} + style={{ + width: '100%', + clipPath: `polygon(0 0, ${pct}% 0, ${pct}% 100%, 0 100%)`, + transition: 'clip-path 500ms', + }} + />
); } diff --git a/src/domains/shared/components/cocktail-card/CocktailCard.tsx b/src/domains/shared/components/cocktail-card/CocktailCard.tsx index 593e3fa5..8345db0d 100644 --- a/src/domains/shared/components/cocktail-card/CocktailCard.tsx +++ b/src/domains/shared/components/cocktail-card/CocktailCard.tsx @@ -41,7 +41,15 @@ function CocktailCard({ className )} > - {name} + {name} {keep && (
{alcoholTitle &&
diff --git a/src/domains/shared/components/profile/Profile.tsx b/src/domains/shared/components/profile/Profile.tsx index ea7b4bd0..9dc47cfb 100644 --- a/src/domains/shared/components/profile/Profile.tsx +++ b/src/domains/shared/components/profile/Profile.tsx @@ -1,4 +1,4 @@ -import SsuryImage from '@/domains/mypage/main/SsuryImage'; +import SsuryImage from '@/domains/mypage/components/main/SsuryImage'; import { useAuthStore } from '@/domains/shared/store/auth'; type Props = { diff --git a/src/shared/components/select-box/SelectBox.tsx b/src/shared/components/select-box/SelectBox.tsx index 3bb905ad..787d3ffe 100644 --- a/src/shared/components/select-box/SelectBox.tsx +++ b/src/shared/components/select-box/SelectBox.tsx @@ -1,74 +1,31 @@ 'use client'; -import { Ref, useEffect, useMemo, useRef, useState } from 'react'; +import { Ref, useRef, useState } from 'react'; import Down from '@/shared/assets/icons/selectDown_24.svg'; -import { useShallow } from 'zustand/shallow'; -import { ID, useAccordionStore } from '@/domains/recipe/store/accordionStore'; import useCloseOutside from '@/shared/hook/useCloseOutside'; +import clsx from 'clsx'; interface Props { - id?: ID; - groupKey?: string; ref?: Ref; option: string[]; title: string; value?: string; onChange?: (value: string) => void; use?: string; + align?: 'left' | 'right'; } -function SelectBox({ id, groupKey, ref, option, title, value, onChange, use }: Props) { +function SelectBox({ ref, option, title, value, onChange, align }: Props) { const [isOpen, setIsOpen] = useState(false); - const [select, setSelect] = useState(value || ''); const menuRef = useRef(null); - - const ingroup = !!groupKey; - - const keyId = useMemo(() => id ?? title, [id, title]); - - // value prop이 변경되면 select state도 업데이트 - useEffect(() => { - if (value !== undefined) { - setSelect(value); - } - }, [value]); - + const selectedValue = value == '전체' ? title : value; useCloseOutside({ menuRef, - onClose: () => { - if (!ingroup) setIsOpen(false); - else closeGroup(groupKey); - }, + onClose: () => setIsOpen(false), }); - const { openId, toggleGroup, closeGroup } = useAccordionStore( - useShallow((s) => ({ - openId: ingroup ? (s.openByGroup[groupKey] ?? null) : null, - toggleGroup: s.toggle, - closeGroup: s.closeGroup, - })) - ); - - const localOpen = ingroup ? openId === keyId : isOpen; - - const toggle = () => { - if (ingroup) toggleGroup(groupKey, keyId); - else - setIsOpen((prev) => { - const next = !prev; - return next; - }); - }; - - const close = () => { - if (ingroup) closeGroup(groupKey); - else setIsOpen(false); - }; - const handleChoose = (v: string) => { - const value = v || title; - setSelect(value); - onChange?.(value); - close(); + onChange?.(v); + setIsOpen(false); }; return ( @@ -76,40 +33,29 @@ function SelectBox({ id, groupKey, ref, option, title, value, onChange, use }: P
    {option.map((v, i) => (
  • handleChoose(v)} - aria-selected={v === select} > - {v || title} + {v}
  • ))}