From e4cb6d396e2f911cfa1c572291f133fe9c01f2e2 Mon Sep 17 00:00:00 2001 From: Yo Wook Kim Date: Tue, 2 Aug 2022 19:20:40 +0900 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20=EB=8C=80=ED=91=9C=EC=9E=A5=EB=B9=84?= =?UTF-8?q?=20=EC=84=A0=ED=83=9D=EC=B0=BD=20=ED=86=A0=EA=B8=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EC=9A=94=EC=B2=AD=20=EC=B2=98=EB=A6=AC?= =?UTF-8?q?=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=88=98=EC=A0=95=20(#197,=20?= =?UTF-8?q?#199)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/ProductSelect/ProductSelect.tsx | 32 +++++++++---------- frontend/src/hooks/useInventory.tsx | 5 +-- 2 files changed, 18 insertions(+), 19 deletions(-) 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 ? ( >; otherProducts: InventoryProduct[]; refetch: () => void; - updateProfileProduct: () => Promise; + updateProfileProduct: () => Promise; }; function useInventory(): Return { @@ -50,7 +50,7 @@ function useInventory(): Return { (initialSelectedProduct && selectedProduct.id === initialSelectedProduct.id) ) { - return; + return false; } const patchBody = { selectedInventoryProductId: selectedProduct.id }; @@ -58,6 +58,7 @@ function useInventory(): Return { patchBody['unselectedInventoryProductId'] = initialSelectedProduct.id; } await patchProfileProduct(patchBody); + return true; }; const otherProducts = From f142a50a2063c7e1b409698ba93a65ee3e8cd44e Mon Sep 17 00:00:00 2001 From: Yo Wook Kim Date: Tue, 2 Aug 2022 19:26:21 +0900 Subject: [PATCH 2/5] =?UTF-8?q?fix:=20API=20=EB=AA=85=EC=84=B8=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EA=B2=8C=20=EC=B6=94=EA=B0=80=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=20=ED=82=A4=EC=9B=8C=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/Register/Register.tsx | 51 +++++++++++++----------- 1 file changed, 28 insertions(+), 23 deletions(-) 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]} )} From 15f0c690adee6268013c72ece108e17d9fe595d7 Mon Sep 17 00:00:00 2001 From: Yo Wook Kim Date: Tue, 2 Aug 2022 19:32:08 +0900 Subject: [PATCH 3/5] =?UTF-8?q?fix:=20API=20=EB=AA=85=EC=84=B8=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EA=B2=8C=20useInventory=20=ED=9B=85=EC=9D=98=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EC=B2=98=EB=A6=AC=20=ED=82=A4=20=EA=B0=92?= =?UTF-8?q?=20=EC=88=98=EC=A0=95(#273)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/useInventory.tsx | 17 +++++++---------- frontend/src/mocks/data.ts | 4 ++-- frontend/src/pages/Profile/Profile.tsx | 4 ++-- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/frontend/src/hooks/useInventory.tsx b/frontend/src/hooks/useInventory.tsx index 7e3a3980..ada32051 100644 --- a/frontend/src/hooks/useInventory.tsx +++ b/frontend/src/hooks/useInventory.tsx @@ -5,11 +5,11 @@ 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; @@ -39,8 +39,7 @@ function useInventory(): Return { }); const initialSelectedProduct = useMemo( - () => - isReady && inventoryProducts.keyboards.find(({ selected }) => selected), + () => isReady && inventoryProducts.items.find(({ selected }) => selected), [inventoryProducts] ); @@ -64,15 +63,13 @@ function useInventory(): Return { 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 ); @@ -80,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} > - + From b8125b426e375bdac69946964793227fcb2d47af Mon Sep 17 00:00:00 2001 From: Yo Wook Kim Date: Tue, 2 Aug 2022 19:45:12 +0900 Subject: [PATCH 4/5] =?UTF-8?q?fix:=20=ED=86=A0=ED=81=B0=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=9D=91=EB=8B=B5=EC=9D=B4=20=EB=B0=9C=EC=83=9D?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20(#286)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/api/useAxios.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/src/hooks/api/useAxios.tsx b/frontend/src/hooks/api/useAxios.tsx index 8a47320a..ee73c1c5 100644 --- a/frontend/src/hooks/api/useAxios.tsx +++ b/frontend/src/hooks/api/useAxios.tsx @@ -3,8 +3,9 @@ import { API_ERROR_CODE_EXCEPTION_MESSAGES, API_ERROR_MESSAGES, } from '@/constants/messages'; +import { LogoutContext } from '@/contexts/LoginContextProvider'; import axios, { AxiosError, AxiosInstance } from 'axios'; -import { useState } from 'react'; +import { useContext, useState } from 'react'; type ErrorResponseBody = { errorCode: keyof typeof API_ERROR_MESSAGES; @@ -19,6 +20,7 @@ type Return = { function useAxios(): Return { const [isLoading, setLoading] = useState(false); const [isError, setError] = useState(false); + const logout = useContext(LogoutContext); const axiosInstance = axios.create({ baseURL: BASE_URL }); @@ -28,6 +30,10 @@ function useAxios(): Return { setError(true); setLoading(false); + if (error.response.status === 401) { + logout(); + } + if (!('errorCode' in errorResponseBody)) { throw new Error(API_ERROR_CODE_EXCEPTION_MESSAGES.NO_CODE); } From d6dcf6ceeff77441e5f4cd683355454a171f7c5b Mon Sep 17 00:00:00 2001 From: Yo Wook Kim Date: Tue, 2 Aug 2022 20:32:04 +0900 Subject: [PATCH 5/5] =?UTF-8?q?fix:=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=97=86=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20=EB=AC=B4=ED=95=9C=20?= =?UTF-8?q?=EB=A1=9C=EB=94=A9=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EC=97=86=EC=9D=8C=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProductListSection/ProductListSection.tsx | 26 ++++++---- .../components/ProfileCard/ProfileCard.tsx | 3 -- .../ProfileSearchResult.tsx | 21 ++++---- .../ReviewListSection/ReviewListSection.tsx | 51 ++++++++++++------- .../NoDataPlaceholder/NoDataPlaceholder.tsx | 19 +++++++ frontend/src/hooks/api/useGetMany.tsx | 11 ++-- .../src/pages/ProfileSearch/ProfileSearch.tsx | 18 +++++-- 7 files changed, 97 insertions(+), 52 deletions(-) create mode 100644 frontend/src/components/common/NoDataPlaceholder/NoDataPlaceholder.tsx 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/hooks/api/useGetMany.tsx b/frontend/src/hooks/api/useGetMany.tsx index fec7a68d..b8c62f89 100644 --- a/frontend/src/hooks/api/useGetMany.tsx +++ b/frontend/src/hooks/api/useGetMany.tsx @@ -30,7 +30,7 @@ type Return = { }; 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/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} - /> + > + +
); }