diff --git a/frontend/src/components/ProductListSection/ProductListSection.tsx b/frontend/src/components/ProductListSection/ProductListSection.tsx index 37e85039..01ed7109 100644 --- a/frontend/src/components/ProductListSection/ProductListSection.tsx +++ b/frontend/src/components/ProductListSection/ProductListSection.tsx @@ -9,6 +9,7 @@ import InfiniteScroll from '@/components/common/InfiniteScroll/InfiniteScroll'; import Masonry from '@/components/common/Masonry/Masonry'; import AsyncWrapper from '@/components/common/AsyncWrapper/AsyncWrapper'; import Loading from '@/components/common/Loading/Loading'; +import NoDataPlaceholder from '@/components/common/NoDataPlaceholder/NoDataPlaceholder'; type Props = { title: string; @@ -29,14 +30,6 @@ function ProductListSection({ isError, getNextPage, }: Props) { - const ProductCardList = (data: Product[]) => { - return data.map(({ id, imageUrl, name, rating }) => ( - - - - )); - }; - return ( @@ -55,7 +48,7 @@ function ProductListSection({ isLoading={isLoading} isError={isError} > - {ProductCardList(data)} + ) : ( @@ -64,7 +57,7 @@ function ProductListSection({ isReady={isReady} isError={isError} > - {ProductCardList(data)} + )} @@ -72,4 +65,17 @@ function ProductListSection({ ); } +const ProductCardList = ({ data }: { data: Product[] }) => { + return ( + + {data.map(({ id, imageUrl, name, rating }) => ( + + + + ))} + {data.length === 0 && } + + ); +}; + export default ProductListSection; diff --git a/frontend/src/components/ProfileCard/ProfileCard.tsx b/frontend/src/components/ProfileCard/ProfileCard.tsx index fded0aec..528aeb4c 100644 --- a/frontend/src/components/ProfileCard/ProfileCard.tsx +++ b/frontend/src/components/ProfileCard/ProfileCard.tsx @@ -36,7 +36,6 @@ const chipMapper = { }; function ProfileCard({ - id, gitHubId, imageUrl, careerLevel, @@ -65,8 +64,6 @@ function ProfileCard({ (product) => product.category === 'keyboard' ); - console.log(keyboard); - return ( diff --git a/frontend/src/components/ProfileSearchResult/ProfileSearchResult.tsx b/frontend/src/components/ProfileSearchResult/ProfileSearchResult.tsx index dfe398d1..cc4cd3c4 100644 --- a/frontend/src/components/ProfileSearchResult/ProfileSearchResult.tsx +++ b/frontend/src/components/ProfileSearchResult/ProfileSearchResult.tsx @@ -1,8 +1,7 @@ import * as S from '@/components/ProfileSearchResult/ProfileSearchResult.style'; -import AsyncWrapper from '@/components/common/AsyncWrapper/AsyncWrapper'; -import Loading from '@/components/common/Loading/Loading'; import InfiniteScroll from '@/components/common/InfiniteScroll/InfiniteScroll'; import ProfileCard from '@/components/ProfileCard/ProfileCard'; +import NoDataPlaceholder from '@/components/common/NoDataPlaceholder/NoDataPlaceholder'; type Props = { data: ProfileSearchResult[]; @@ -16,7 +15,6 @@ function ProfileSearchResult({ data: profileSearchData, getNextPage, isLoading, - isReady, isError, }: Props) { const profileSearchDataList = profileSearchData.map( @@ -36,15 +34,14 @@ function ProfileSearchResult({ ); return ( - } isReady={isReady} isError={isError}> - - {profileSearchDataList} - - + + {profileSearchDataList} + {profileSearchData.length === 0 && } + ); } diff --git a/frontend/src/components/ReviewListSection/ReviewListSection.tsx b/frontend/src/components/ReviewListSection/ReviewListSection.tsx index 5794a28b..a7109d1e 100644 --- a/frontend/src/components/ReviewListSection/ReviewListSection.tsx +++ b/frontend/src/components/ReviewListSection/ReviewListSection.tsx @@ -31,22 +31,6 @@ function ReviewListSection({ const userData = useContext(UserDataContext); const loginUserGithubId = userData?.member.gitHubId; - const reviewCardList = reviewData.map( - ({ id, author, product, content, rating }) => ( - - ) - ); return ( @@ -58,11 +42,44 @@ function ReviewListSection({ isLoading={isLoading} isError={isError} > - {reviewCardList} + + + ); } +const ReviewCardList = ({ + data, + handleDelete, + handleEdit, + loginUserGithubId, +}: Pick & { + loginUserGithubId: string; +}) => ( + <> + {data.map(({ id, author, product, content, rating }) => ( + + ))} + +); + export default ReviewListSection; diff --git a/frontend/src/components/common/NoDataPlaceholder/NoDataPlaceholder.tsx b/frontend/src/components/common/NoDataPlaceholder/NoDataPlaceholder.tsx new file mode 100644 index 00000000..5c2f3958 --- /dev/null +++ b/frontend/src/components/common/NoDataPlaceholder/NoDataPlaceholder.tsx @@ -0,0 +1,19 @@ +import { Player } from '@lottiefiles/react-lottie-player'; + +function NoDataPlaceholder() { + return ( + <> + +
+ 아무것도 찾지 못했어요.. +
+ + ); +} + +export default NoDataPlaceholder; diff --git a/frontend/src/components/common/ProductSelect/ProductSelect.tsx b/frontend/src/components/common/ProductSelect/ProductSelect.tsx index dcfd54b6..a609522c 100644 --- a/frontend/src/components/common/ProductSelect/ProductSelect.tsx +++ b/frontend/src/components/common/ProductSelect/ProductSelect.tsx @@ -1,5 +1,5 @@ import ProductBar from '@/components/common/ProductBar/ProductBar'; -import { useReducer } from 'react'; +import { useState } from 'react'; import DownArrow from '@/assets/down_arrow.svg'; import * as S from '@/components/common/ProductSelect/ProductSelect.style'; @@ -10,7 +10,7 @@ type Props = { selectedProduct: InventoryProduct; setSelectedProduct: React.Dispatch>; otherProducts: InventoryProduct[]; - updateProfileProduct: () => Promise; + updateProfileProduct: () => Promise; }; function ProductSelect({ @@ -20,27 +20,21 @@ function ProductSelect({ otherProducts, updateProfileProduct, }: Props) { - const [isEditMode, setEditMode] = useReducer( - (isEditMode: boolean) => !isEditMode, - false - ); - const [isOptionsOpen, setOptionOpen] = useReducer( - (isOptionsOpen: boolean) => !isOptionsOpen, - false - ); + const [isEditMode, setEditMode] = useState(false); + const [isOptionsOpen, setOptionOpen] = useState(false); const handleProductSelect = (value: InventoryProduct) => { setSelectedProduct(value); - setOptionOpen(); + setOptionOpen(false); }; const handleEditDone = () => { if (isEditMode) { updateProfileProduct() - .then(() => { - submitHandler(); - setOptionOpen(); - setEditMode(); + .then((didPatch) => { + if (didPatch) submitHandler(); + setOptionOpen(false); + setEditMode(false); }) .catch((error) => { console.error(error); @@ -48,7 +42,11 @@ function ProductSelect({ return; } - setEditMode(); + setEditMode(true); + }; + + const handleOptionOpen = () => { + setOptionOpen(true); }; return ( @@ -59,7 +57,7 @@ function ProductSelect({ {isEditMode ? ( <> - + {selectedProduct !== undefined ? ( = { }; function useGetMany({ url, params, body, headers }: Props): Return { - const [data, setData] = useState([]); + const [data, setData] = useState(null); const [page, setPage] = useState(0); const [hasNextPage, setHasNextPage] = useState(true); @@ -64,7 +64,7 @@ function useGetMany({ url, params, body, headers }: Props): Return { setRefetchTrigger((prevTrigger) => prevTrigger + 1); setPage(0); setHasNextPage(true); - setData([]); + setData(null); }; const getCurrentParamString = () => @@ -81,7 +81,8 @@ function useGetMany({ url, params, body, headers }: Props): Return { fetchData() .then(({ hasNext, items }) => { - !!items && setData((prevData) => [...prevData, ...items]); + !!items && + setData((prevData) => (prevData ? [...prevData, ...items] : items)); return hasNext; }) .then((hasNext) => { @@ -100,11 +101,11 @@ function useGetMany({ url, params, body, headers }: Props): Return { const searchParams = new URLSearchParams(params); useEffect(() => { - if (data.length === 0) return; // 최초 렌더링 시 refetch 방지 임시 조치 + if (!data) return; // 최초 렌더링 시 refetch 방지 임시 조치 refetch(); }, [searchParams.toString()]); - const isReady = data.length !== 0; + const isReady = !!data; return { data, getNextPage, refetch, isLoading, isReady, isError }; } diff --git a/frontend/src/hooks/useInventory.tsx b/frontend/src/hooks/useInventory.tsx index 30ee2316..ada32051 100644 --- a/frontend/src/hooks/useInventory.tsx +++ b/frontend/src/hooks/useInventory.tsx @@ -5,18 +5,18 @@ import usePatch from '@/hooks/api/usePatch'; import { useContext, useEffect, useMemo, useState } from 'react'; type InventoryResponse = { - keyboards: InventoryProduct[]; + items: InventoryProduct[]; }; type Return = { - keyboards: InventoryProduct[]; + items: InventoryProduct[]; isReady: boolean; isError: boolean; selectedProduct: InventoryProduct | null; setSelectedProduct: React.Dispatch>; otherProducts: InventoryProduct[]; refetch: () => void; - updateProfileProduct: () => Promise; + updateProfileProduct: () => Promise; }; function useInventory(): Return { @@ -39,8 +39,7 @@ function useInventory(): Return { }); const initialSelectedProduct = useMemo( - () => - isReady && inventoryProducts.keyboards.find(({ selected }) => selected), + () => isReady && inventoryProducts.items.find(({ selected }) => selected), [inventoryProducts] ); @@ -50,7 +49,7 @@ function useInventory(): Return { (initialSelectedProduct && selectedProduct.id === initialSelectedProduct.id) ) { - return; + return false; } const patchBody = { selectedInventoryProductId: selectedProduct.id }; @@ -58,20 +57,19 @@ function useInventory(): Return { patchBody['unselectedInventoryProductId'] = initialSelectedProduct.id; } await patchProfileProduct(patchBody); + return true; }; const otherProducts = isReady && (selectedProduct - ? inventoryProducts.keyboards.filter( - ({ id }) => id !== selectedProduct.id - ) - : inventoryProducts.keyboards); + ? inventoryProducts.items.filter(({ id }) => id !== selectedProduct.id) + : inventoryProducts.items); useEffect(() => { if (!isReady) return; - const newSelectedProduct = inventoryProducts.keyboards.find( + const newSelectedProduct = inventoryProducts.items.find( ({ selected }) => selected ); @@ -79,7 +77,7 @@ function useInventory(): Return { }, [inventoryProducts]); return { - keyboards: inventoryProducts?.keyboards, + items: inventoryProducts?.items, isReady, isError, selectedProduct, diff --git a/frontend/src/mocks/data.ts b/frontend/src/mocks/data.ts index e7023a88..1c37d7fd 100644 --- a/frontend/src/mocks/data.ts +++ b/frontend/src/mocks/data.ts @@ -273,10 +273,10 @@ const getReviewProductData: ( }); export const InventoryProducts = { - keyboards: [ + items: [ { id: 1, - selected: false, + selected: true, product: { id: 3, name: '레오폴드 FC900RBT PD 그레이 블루 한글 (저소음 적축)', diff --git a/frontend/src/pages/Profile/Profile.tsx b/frontend/src/pages/Profile/Profile.tsx index cf52d4a2..d82ebeec 100644 --- a/frontend/src/pages/Profile/Profile.tsx +++ b/frontend/src/pages/Profile/Profile.tsx @@ -23,7 +23,7 @@ type Member = { function Profile() { const userData = useContext(UserDataContext); const { - keyboards, + items, isReady: isInventoryProductsReady, refetch: refetchInventoryProducts, selectedProduct, @@ -67,7 +67,7 @@ function Profile() { isReady={isInventoryProductsReady} isError={isMyDataError} > - +
diff --git a/frontend/src/pages/ProfileSearch/ProfileSearch.tsx b/frontend/src/pages/ProfileSearch/ProfileSearch.tsx index 7dd51303..2e69dbad 100644 --- a/frontend/src/pages/ProfileSearch/ProfileSearch.tsx +++ b/frontend/src/pages/ProfileSearch/ProfileSearch.tsx @@ -6,6 +6,8 @@ import SearchFilter from '@/components/SearchFilter/SearchFilter'; import * as S from '@/pages/ProfileSearch/ProfileSearch.style'; import useProfileSearch from '@/hooks/useProfileSearch'; import ProfileSearchResult from '@/components/ProfileSearchResult/ProfileSearchResult'; +import Loading from '@/components/common/Loading/Loading'; +import AsyncWrapper from '@/components/common/AsyncWrapper/AsyncWrapper'; function ProfileSearch() { const [careerLevelFilter, setCareerLevelFilter] = useState(''); @@ -50,13 +52,19 @@ function ProfileSearch() { handleJobTypeFilterClick={handleJobTypeFilterClick} /> - } isReady={isProfileSearchReady} isError={isProfileSearchError} - /> + > + +
); } diff --git a/frontend/src/pages/Register/Register.tsx b/frontend/src/pages/Register/Register.tsx index 933a9245..cdf7dff9 100644 --- a/frontend/src/pages/Register/Register.tsx +++ b/frontend/src/pages/Register/Register.tsx @@ -14,29 +14,29 @@ const messages = { 3: '입력한 정보를 확인해주세요', }; -const careers = { - NONE: '경력 없음', - JUNIOR: '0-2년차', - MID_LEVEL: '3-5년차', - SENIOR: '6년차 이상', +const careerLevel = { + none: '경력 없음', + junior: '0-2년차', + midlevel: '3-5년차', + senior: '6년차 이상', } as const; -const jobTypes = { - FRONT_END: '프론트엔드', - BACK_END: '백엔드', - MOBILE: '모바일', - ETC: '기타', +const jobType = { + frontend: '프론트엔드', + backend: '백엔드', + mobile: '모바일', + etc: '기타', } as const; type UserInfo = { - career: keyof typeof careers; - jobType: keyof typeof jobTypes; + careerLevel: keyof typeof careerLevel; + jobType: keyof typeof jobType; }; function Register() { const [step, setStep] = useState(1); const [additionalInfo, setAdditionalInfo] = useState({ - career: null, + careerLevel: null, jobType: null, }); const { showAlert, getConfirm } = useModal(); @@ -53,9 +53,9 @@ function Register() { const renderSelectButton = (step: number) => { switch (step) { case 1: - return careers; + return careerLevel; case 2: - return jobTypes; + return jobType; } }; @@ -63,24 +63,27 @@ function Register() { e ) => { if (!(e.target instanceof HTMLButtonElement)) return; - if (!(e.target.value in careers) && !(e.target.value in jobTypes)) return; + if (!(e.target.value in careerLevel) && !(e.target.value in jobType)) + return; if (step === 1) { setAdditionalInfo({ ...additionalInfo, - career: e.target.value as keyof typeof careers, + careerLevel: e.target.value as keyof typeof careerLevel, }); } else { setAdditionalInfo({ ...additionalInfo, - jobType: e.target.value as keyof typeof jobTypes, + jobType: e.target.value as keyof typeof jobType, }); } }; const handleAdditionalInfoSubmit = async (input: UserInfo) => { const confirmation = await getConfirm( - `${careers[input.career]}, ${jobTypes[input.jobType]} 개발자이신가요?` + `${careerLevel[input.careerLevel]}, ${ + jobType[input.jobType] + } 개발자이신가요?` ); if (confirmation) { patchAdditionalInfo(input) @@ -94,7 +97,7 @@ function Register() { }; const handleConfirmButtonClick = async () => { - if (step === 1 && additionalInfo.career === null) { + if (step === 1 && additionalInfo.careerLevel === null) { showAlert('경력을 선택해주세요.'); return; } @@ -129,7 +132,7 @@ function Register() { onClick={handleSelectButtonClick} value={value} selected={ - value === additionalInfo.career || + value === additionalInfo.careerLevel || value === additionalInfo.jobType } > @@ -139,8 +142,10 @@ function Register() { )} {step === 3 && ( <> - {careers[additionalInfo.career]} - {jobTypes[additionalInfo.jobType]} + + {careerLevel[additionalInfo.careerLevel]} + + {jobType[additionalInfo.jobType]} )}