From b725a796954a7f9a2aee01811e3d20ece6fde1f1 Mon Sep 17 00:00:00 2001 From: nirii00 Date: Wed, 26 Feb 2025 14:49:15 +0900 Subject: [PATCH 01/15] =?UTF-8?q?feat:=20=EC=86=8C=EC=85=9C=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EB=B0=8F=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=97=B0=EA=B2=B0=20-=20=EC=86=8C=EC=85=9C=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8,=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=8B=9C=20=EB=A6=AC=EB=94=94=EB=A0=89=EC=85=98=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=97=B0=EB=8F=99=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C=20-=20=EC=9C=A0=EC=A0=80=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EC=A7=84=ED=96=89=20=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + pnpm-lock.yaml | 13 +++++++++++-- src/apis/auth.ts | 11 +++++++++++ src/apis/client.ts | 26 +++++++++++++------------- src/pages/Login/index.tsx | 9 +++++++++ src/stores/authStore.ts | 31 +++++++++++++++++++++++++++++++ 6 files changed, 76 insertions(+), 15 deletions(-) create mode 100644 src/apis/auth.ts create mode 100644 src/stores/authStore.ts diff --git a/package.json b/package.json index 7323da4..0e64280 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@tanstack/react-query": "^5.66.0", "axios": "^1.7.9", "gsap": "^3.12.7", + "immer": "^10.1.1", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router": "^7.1.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c0758e6..e36e08d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: gsap: specifier: ^3.12.7 version: 3.12.7 + immer: + specifier: ^10.1.1 + version: 10.1.1 react: specifier: ^18.3.1 version: 18.3.1 @@ -55,7 +58,7 @@ importers: version: 4.0.6 zustand: specifier: ^5.0.3 - version: 5.0.3(@types/react@19.0.8)(react@18.3.1) + version: 5.0.3(@types/react@19.0.8)(immer@10.1.1)(react@18.3.1) devDependencies: '@eslint/js': specifier: ^9.19.0 @@ -1551,6 +1554,9 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + immer@10.1.1: + resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==} + import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -3878,6 +3884,8 @@ snapshots: ignore@5.3.2: {} + immer@10.1.1: {} + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -4655,7 +4663,8 @@ snapshots: yocto-queue@0.1.0: {} - zustand@5.0.3(@types/react@19.0.8)(react@18.3.1): + zustand@5.0.3(@types/react@19.0.8)(immer@10.1.1)(react@18.3.1): optionalDependencies: '@types/react': 19.0.8 + immer: 10.1.1 react: 18.3.1 diff --git a/src/apis/auth.ts b/src/apis/auth.ts new file mode 100644 index 0000000..d4ebc70 --- /dev/null +++ b/src/apis/auth.ts @@ -0,0 +1,11 @@ +// import useAuthStore from '@/stores/authStore'; + +// import client from './client'; + +type LoginType = 'kakao' | 'naver' | 'google'; +export const socialLogin = (loginType: LoginType) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + // const { setUserId, setZipCode, login } = useAuthStore.getState(); + window.location.href = `http://13.209.132.150:8081/oauth2/authorization/${loginType}`; +}; + diff --git a/src/apis/client.ts b/src/apis/client.ts index d2c900a..3ec122e 100644 --- a/src/apis/client.ts +++ b/src/apis/client.ts @@ -4,18 +4,18 @@ const client = axios.create({ baseURL: import.meta.env.VITE_API_URL, }); -// client.interceptors.request.use( -// (config) => { -// const token = localStorage.getItem('authToken'); -// if (token) { -// config.headers['Authorization'] = `Bearer ${token}`; -// } -// return config; -// }, -// (error) => { -// //TODO: 에러처리 -// return Promise.reject(error); -// }, -// ); +client.interceptors.request.use( + (config) => { + const token = localStorage.getItem('authToken'); + if (token) { + config.headers['Authorization'] = `Bearer ${token}`; + } + return config; + }, + (error) => { + //TODO: 에러처리 + return Promise.reject(error); + }, +); export default client; diff --git a/src/pages/Login/index.tsx b/src/pages/Login/index.tsx index aa17b83..333e22e 100644 --- a/src/pages/Login/index.tsx +++ b/src/pages/Login/index.tsx @@ -1,8 +1,14 @@ +import { socialLogin } from '@/apis/auth'; import { GoogleIcon, KakaoIcon, NaverIcon, StampIcon } from '@/assets/icons'; import Background from './components/Background'; const LoginPage = () => { + type LoginType = 'kakao' | 'naver' | 'google'; + + const handleLogin = (loginType: LoginType) => { + socialLogin(loginType); + }; return ( <>
@@ -22,6 +28,7 @@ const LoginPage = () => { type="button" className="rounded-full bg-[#03C75A] p-3.5" aria-label="네이버 로그인" + onClick={() => handleLogin('naver')} > @@ -29,6 +36,7 @@ const LoginPage = () => { type="button" className="rounded-full bg-[#FEE500] p-3.5" aria-label="카카오 로그인" + onClick={() => handleLogin('kakao')} > @@ -36,6 +44,7 @@ const LoginPage = () => { type="button" className="border-gray-5 rounded-full border bg-white p-3.5" aria-label="구글 로그인" + onClick={() => handleLogin('google')} > diff --git a/src/stores/authStore.ts b/src/stores/authStore.ts new file mode 100644 index 0000000..9b76be7 --- /dev/null +++ b/src/stores/authStore.ts @@ -0,0 +1,31 @@ +import { create } from 'zustand'; +import { persist, createJSONStorage } from 'zustand/middleware'; + +interface AuthStore { + isLoggedIn: boolean; + userId: number | null; + zipCode: string; + login: () => void; + logout: () => void; + setUserId: (userId: number) => void; + setZipCode: (zipCode: string) => void; +} +const useAuthStore = create( + persist( + (set) => ({ + isLoggedIn: false, + userId: null, + zipCode: '', + login: () => set({ isLoggedIn: true }), + logout: () => set({ isLoggedIn: false, userId: null, zipCode: '' }), + setUserId: (userId) => set({ userId: userId }), + setZipCode: (zipCode) => set({ zipCode: zipCode }), + }), + { + name: 'userInfo', + storage: createJSONStorage(() => sessionStorage), + }, + ), +); + +export default useAuthStore; From ca231694af3f86527a6b7be1daea94a81b9e5a9e Mon Sep 17 00:00:00 2001 From: wldnjs990 <139528356+wldnjs990@users.noreply.github.com> Date: Thu, 27 Feb 2025 14:56:53 +0900 Subject: [PATCH 02/15] =?UTF-8?q?feat:=ED=8E=B8=EC=A7=80=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=ED=8E=98=EC=9D=B4=EC=A7=80=201=EC=B0=A8=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=EA=B5=AC=ED=98=84=20(#47)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat:편지전송 답장 post api 연결완료 * feat:매칭된 편지 작성시 api 분기 구현 * feat:카테고리 페이지 페이지 뒤로가기 버튼 제거 + 이전단계 위치 조정 * feat:옵션 모달 외부 클릭시 슬라이드 닫히는 로직 구현 * feat: 편지작성 페이지 전역변수 편지전송 관련 request값들 객체형태로 통일 * fix:코드리뷰 사항 수정 --- src/apis/letterDetail.ts | 5 +- src/apis/write.ts | 11 ++-- src/components/ResultLetter.tsx | 21 ++++--- src/pages/Write/CategorySelect.tsx | 40 +++++------- src/pages/Write/LetterEditor.tsx | 63 ++++++++++++------- src/pages/Write/OptionSlide.tsx | 12 ++++ src/pages/Write/components/CategoryStamp.tsx | 15 +++-- src/pages/Write/components/FontOption.tsx | 9 ++- .../components/ResultLetterAnimation.tsx | 7 ++- src/pages/Write/components/ThemeOption.tsx | 8 +-- .../Write/components/WritePageButton.tsx | 8 ++- src/pages/Write/index.tsx | 39 ++++++------ src/stores/writeStore.ts | 61 ++++++++---------- src/types/write.d.ts | 8 +-- 14 files changed, 166 insertions(+), 141 deletions(-) diff --git a/src/apis/letterDetail.ts b/src/apis/letterDetail.ts index 7f0b061..d2f176b 100644 --- a/src/apis/letterDetail.ts +++ b/src/apis/letterDetail.ts @@ -3,20 +3,23 @@ import client from './client'; const getLetter = async ( letterId: string, setLetterState: React.Dispatch>, + callBack?: () => void, ) => { try { const res = await client.get(`/api/letters/${letterId}`); setLetterState(res.data.data); + if (callBack) callBack(); console.log(res); } catch (error) { console.error(error); } }; -const deleteLetter = async (letterId: string) => { +const deleteLetter = async (letterId: string, callBack?: () => void) => { try { console.log(`/api/letters/${letterId}`); const res = await client.delete(`/api/letters/${letterId}`); + if (callBack) callBack(); console.log(res); } catch (error) { console.error(error); diff --git a/src/apis/write.ts b/src/apis/write.ts index bcecde9..2c56f8b 100644 --- a/src/apis/write.ts +++ b/src/apis/write.ts @@ -1,12 +1,9 @@ import client from './client'; -const postLetter = async ( - data: LetterRequest, - setState?: React.Dispatch>, -) => { +const postLetter = async (data: LetterRequest, callBack?: () => void) => { try { const res = await client.post('/api/letters', data); - if (setState) setState(true); + if (callBack) callBack(); console.log(res); } catch (error) { console.error(error); @@ -14,12 +11,14 @@ const postLetter = async ( }; const getPrevLetter = async ( - setPrevLetterState: React.Dispatch>, letterId: string, + setPrevLetterState: React.Dispatch>, + callBack?: () => void, ) => { try { const res = await client.get(`/api/letters/${letterId}/previous`); setPrevLetterState(res.data.data); + if (callBack) callBack(); console.log(res); } catch (error) { console.error(error); diff --git a/src/components/ResultLetter.tsx b/src/components/ResultLetter.tsx index 2ffeb77..0e56b7d 100644 --- a/src/components/ResultLetter.tsx +++ b/src/components/ResultLetter.tsx @@ -5,11 +5,12 @@ import LetterWrapper from './LetterWrapper'; export default function ResultLetter({ categoryName = 'CONSOLATION', title, + zipCode = 'error', }: { categoryName: Category; title: string; + zipCode?: string; }) { - const address = '1A3E2'; const date = new Date(); const today = `${date.getFullYear()}년 ${date.getMonth() + 1}월 ${date.getDate()}일`; @@ -26,14 +27,16 @@ export default function ResultLetter({
{today}
- {address.split('').map((spell, idx) => ( - - {spell} - - ))} + {zipCode.split('').map((spell, idx) => { + return ( + + {spell} + + ); + })}
diff --git a/src/pages/Write/CategorySelect.tsx b/src/pages/Write/CategorySelect.tsx index 08ab497..5581672 100644 --- a/src/pages/Write/CategorySelect.tsx +++ b/src/pages/Write/CategorySelect.tsx @@ -1,8 +1,6 @@ -import { useState } from 'react'; import { Link } from 'react-router'; import { postLetter } from '@/apis/write'; -import BackButton from '@/components/BackButton'; import PageTitle from '@/components/PageTitle'; import CategoryList from '@/pages/Write/components/CategoryList'; import useWrite from '@/stores/writeStore'; @@ -13,33 +11,20 @@ import WritePageButton from './components/WritePageButton'; export default function CategorySelect({ setStep, prevLetter, + send, + setSend, }: { setStep: React.Dispatch>; prevLetter: PrevLetter[]; + send: boolean; + setSend: React.Dispatch>; }) { - const letterTitle = useWrite((state) => state.letterTitle); - const letterText = useWrite((state) => state.letterText); - const category = useWrite((state) => state.category); - const paperType = useWrite((state) => state.paperType); - const fontType = useWrite((state) => state.fontType); - - const [send, setSend] = useState(false); - - const LETTER_REQUEST: LetterRequest = { - receiver: null, - parentLetterId: null, - title: letterTitle, - content: letterText, - category: category, - paperType: paperType, - fontType: fontType, - }; + const letterRequest = useWrite((state) => state.letterRequest); return ( <>
- {!send && prevLetter.length <= 0 && ( } - {prevLetter.length > 0 && ( + {send && prevLetter.length > 0 && (
- +
작성하신 편지는 @@ -74,9 +59,9 @@ export default function CategorySelect({
)} - {send && ( + {send && prevLetter.length <= 0 && (
- + 두근두근! 답장이 언제 올까요? @@ -94,8 +79,11 @@ export default function CategorySelect({
diff --git a/src/pages/Write/OptionSlide.tsx b/src/pages/Write/OptionSlide.tsx index 91f734b..cc9a05b 100644 --- a/src/pages/Write/OptionSlide.tsx +++ b/src/pages/Write/OptionSlide.tsx @@ -14,6 +14,14 @@ function OptionSlide({ prevLetter }: { prevLetter: PrevLetter[] }) { const optionRef = useRef(null); useEffect(() => { + const handleOutsideClick = (e: MouseEvent) => { + const target = e.target as HTMLElement; + if (target && !slideRef.current?.contains(target)) { + setSlideActive(false); + } + }; + document.body.addEventListener('click', handleOutsideClick); + const handleSlideButton = () => { // ref가 처음 높이를 못 받아오는거 같아서 비동기로 후처리함 if (slideRef.current) { @@ -25,6 +33,10 @@ function OptionSlide({ prevLetter }: { prevLetter: PrevLetter[] }) { } }; handleSlideButton(); + + return () => { + document.body.removeEventListener('click', handleOutsideClick); + }; }, [slideActive]); return ( diff --git a/src/pages/Write/components/CategoryStamp.tsx b/src/pages/Write/components/CategoryStamp.tsx index 695bca8..7da39ae 100644 --- a/src/pages/Write/components/CategoryStamp.tsx +++ b/src/pages/Write/components/CategoryStamp.tsx @@ -5,8 +5,11 @@ import useWrite from '@/stores/writeStore'; function CategoryStamp({ categoryName, image }: { categoryName: Category; image: string }) { const [hovered, setHovered] = useState(false); - const category = useWrite((state) => state.category); - const setCategory = useWrite((state) => state.setCategory); + const letterRequest = useWrite((state) => state.letterRequest); + const setLetterRequest = useWrite((state) => state.setLetterRequest); + + const stampLinePath = + 'M129.303 4.27673C131.675 4.27673 133.598 2.36197 133.598 2.22916e-08L137.894 0V5.34591C135.521 5.34591 133.598 7.26067 133.598 9.62264C133.598 11.9846 135.521 13.8994 137.894 13.8994V20.3145C135.521 20.3145 133.598 22.2292 133.598 24.5912C133.598 26.9532 135.521 28.8679 137.894 28.8679V35.283C135.521 35.283 133.598 37.1978 133.598 39.5597C133.598 41.9217 135.521 43.8365 137.894 43.8365V50.2516C135.521 50.2516 133.598 52.1663 133.598 54.5283C133.598 56.8903 135.521 58.805 137.894 58.805V65.2201C135.521 65.2201 133.598 67.1349 133.598 69.4969C133.598 71.8588 135.521 73.7736 137.894 73.7736V80.1887C135.521 80.1887 133.598 82.1034 133.598 84.4654C133.598 86.8274 135.521 88.7421 137.894 88.7421V95.1572C135.521 95.1572 133.598 97.072 133.598 99.434C133.598 101.796 135.521 103.711 137.894 103.711V110.126C135.521 110.126 133.598 112.041 133.598 114.403C133.598 116.764 135.521 118.679 137.894 118.679V125.094C135.521 125.094 133.598 127.009 133.598 129.371C133.598 131.733 135.521 133.648 137.894 133.648V140.063C135.521 140.063 133.598 141.978 133.598 144.34C133.598 146.702 135.521 148.616 137.894 148.616V155.031C135.521 155.031 133.598 156.946 133.598 159.308C133.598 161.67 135.521 163.585 137.894 163.585L137.894 170H133.598C133.598 167.638 131.675 165.723 129.303 165.723C126.931 165.723 125.008 167.638 125.008 170H118.565C118.565 167.638 116.642 165.723 114.27 165.723C111.898 165.723 109.975 167.638 109.975 170H103.532C103.532 167.638 101.609 165.723 99.2367 165.723C96.8645 165.723 94.9415 167.638 94.9415 170H88.4986C88.4986 167.638 86.5756 165.723 84.2034 165.723C81.8312 165.723 79.9082 167.638 79.9082 170H73.4654C73.4654 167.638 71.5424 165.723 69.1702 165.723C66.798 165.723 64.875 167.638 64.875 170H58.4321C58.4321 167.638 56.5091 165.723 54.1369 165.723C51.7648 165.723 49.8417 167.638 49.8417 170H43.3989C43.3989 167.638 41.4759 165.723 39.1037 165.723C36.7315 165.723 34.8085 167.638 34.8085 170H28.3657C28.3657 167.638 26.4426 165.723 24.0704 165.723C21.6983 165.723 19.7752 167.638 19.7752 170H13.3324C13.3324 167.638 11.4094 165.723 9.0372 165.723C6.66502 165.723 4.74199 167.638 4.74199 170H0.446785L0.446777 163.585C2.81896 163.585 4.74199 161.67 4.74199 159.308C4.74199 156.946 2.81896 155.031 0.446777 155.031L0.446777 148.616C2.81896 148.616 4.74199 146.702 4.74199 144.34C4.74199 141.978 2.81896 140.063 0.446777 140.063L0.446777 133.648C2.81896 133.648 4.74199 131.733 4.74199 129.371C4.74199 127.009 2.81896 125.094 0.446779 125.094L0.446779 118.679C2.81896 118.679 4.74199 116.764 4.74199 114.403C4.74199 112.041 2.81896 110.126 0.44678 110.126L0.44678 103.711C2.81896 103.711 4.74199 101.796 4.74199 99.434C4.74199 97.072 2.81896 95.1572 0.44678 95.1572L0.446781 88.7421C2.81896 88.7421 4.74199 86.8274 4.74199 84.4654C4.74199 82.1034 2.81896 80.1887 0.446781 80.1887L0.446781 73.7736C2.81896 73.7736 4.74199 71.8588 4.74199 69.4969C4.74199 67.1349 2.81896 65.2201 0.446782 65.2201L0.446782 58.805C2.81896 58.805 4.74199 56.8903 4.74199 54.5283C4.74199 52.1663 2.81896 50.2516 0.446782 50.2516L0.446783 43.8365C2.81896 43.8365 4.74199 41.9217 4.742 39.5597C4.742 37.1978 2.81896 35.283 0.446782 35.283L0.446783 28.8679C2.81896 28.8679 4.742 26.9532 4.742 24.5912C4.742 22.2292 2.81896 20.3145 0.446783 20.3145L0.446783 13.8994C2.81896 13.8994 4.742 11.9846 4.742 9.62264C4.742 7.26067 2.81896 5.34591 0.446784 5.34591L0.446784 7.1333e-07L4.74199 6.91038e-07C4.74199 2.36197 6.66502 4.27673 9.0372 4.27673C11.4094 4.27673 13.3324 2.36197 13.3324 6.46455e-07L19.7752 6.13018e-07C19.7752 2.36197 21.6983 4.27673 24.0704 4.27673C26.4426 4.27673 28.3657 2.36197 28.3657 5.68435e-07L34.8085 5.34998e-07C34.8085 2.36197 36.7315 4.27673 39.1037 4.27673C41.4759 4.27673 43.3989 2.36197 43.3989 4.90415e-07L49.8417 4.56977e-07C49.8417 2.36197 51.7648 4.27673 54.1369 4.27673C56.5091 4.27673 58.4321 2.36197 58.4321 4.12394e-07L64.875 3.78957e-07C64.875 2.36197 66.798 4.27673 69.1702 4.27673C71.5424 4.27673 73.4654 2.36197 73.4654 3.34373e-07L79.9082 3.00936e-07C79.9082 2.36197 81.8312 4.27673 84.2034 4.27673C86.5756 4.27673 88.4986 2.36197 88.4986 2.56353e-07L94.9415 2.22916e-07C94.9415 2.36197 96.8645 4.27673 99.2367 4.27673C101.609 4.27673 103.532 2.36197 103.532 1.78333e-07L109.975 1.44895e-07C109.975 2.36197 111.898 4.27673 114.27 4.27673C116.642 4.27673 118.565 2.36197 118.565 1.00312e-07L125.008 6.68747e-08C125.008 2.36197 126.931 4.27673 129.303 4.27673ZM119.809 18.0851H18.5324V151.915H119.809V18.0851Z'; return (
@@ -16,14 +19,16 @@ function CategoryStamp({ categoryName, image }: { categoryName: Category; image: fill="none" onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)} - onClick={() => setCategory(categoryName)} + onClick={() => { + setLetterRequest({ category: categoryName }); + }} >
diff --git a/src/pages/Write/components/FontOption.tsx b/src/pages/Write/components/FontOption.tsx index 6356198..23ac46d 100644 --- a/src/pages/Write/components/FontOption.tsx +++ b/src/pages/Write/components/FontOption.tsx @@ -6,9 +6,8 @@ import useWrite from '@/stores/writeStore'; import { FONT_LIST } from '../constants'; export default function FontOption() { - const setFontType = useWrite((state) => state.setFontType); - const fontType = useWrite((state) => state.fontType); - + const letterRequest = useWrite((state) => state.letterRequest); + const setLetterRequest = useWrite((state) => state.setLetterRequest); return (
@@ -23,13 +22,13 @@ export default function FontOption() { `${font.fontFamily}`, )} onClick={() => { - setFontType(font.fontType); + setLetterRequest({ fontType: font.fontType }); }} > 안녕! 나는 따수미야! 0123456789{' '} diff --git a/src/pages/Write/components/ResultLetterAnimation.tsx b/src/pages/Write/components/ResultLetterAnimation.tsx index 6f8cf3e..286c01d 100644 --- a/src/pages/Write/components/ResultLetterAnimation.tsx +++ b/src/pages/Write/components/ResultLetterAnimation.tsx @@ -7,9 +7,10 @@ import useWrite from '@/stores/writeStore'; import ResultLetter from '../../../components/ResultLetter'; -export default function ResultLetterAnimation({ categoryName }: { categoryName: Category }) { +export default function ResultLetterAnimation() { const [next, setNext] = useState('st'); - const letterTitle = useWrite((state) => state.letterTitle); + + const letterRequest = useWrite((state) => state.letterRequest); useEffect(() => { setTimeout(() => { @@ -23,7 +24,7 @@ export default function ResultLetterAnimation({ categoryName }: { categoryName: <> {next === 'rd' ? (
- +
) : ( <> diff --git a/src/pages/Write/components/ThemeOption.tsx b/src/pages/Write/components/ThemeOption.tsx index 641601a..072de1b 100644 --- a/src/pages/Write/components/ThemeOption.tsx +++ b/src/pages/Write/components/ThemeOption.tsx @@ -5,8 +5,8 @@ import useWrite from '@/stores/writeStore'; import { CATEGORY_LIST } from '../constants'; export default function ThemeOption() { - const paperType = useWrite((state) => state.paperType); - const setPaperType = useWrite((state) => state.setPaperType); + const letterRequest = useWrite((state) => state.letterRequest); + const setLetterRequest = useWrite((state) => state.setLetterRequest); return (
{CATEGORY_LIST.map((target, idx) => { @@ -15,7 +15,7 @@ export default function ThemeOption() { className="flex w-[30%] min-w-[30%] flex-col gap-1.5" key={idx} onClick={() => { - setPaperType(target.paperType); + setLetterRequest({ paperType: target.paperType }); }} > {target.name} @@ -24,7 +24,7 @@ export default function ThemeOption() { alt="테마 이미지" className={twMerge( 'w-full', - paperType === target.paperType && 'border-primary-1-hover border-2', + letterRequest.paperType === target.paperType && 'border-primary-1-hover border-2', )} /> diff --git a/src/pages/Write/components/WritePageButton.tsx b/src/pages/Write/components/WritePageButton.tsx index bc451d0..d2d7251 100644 --- a/src/pages/Write/components/WritePageButton.tsx +++ b/src/pages/Write/components/WritePageButton.tsx @@ -21,7 +21,13 @@ function WritePageButton({ `${target === text && slideActive && 'bg-primary-1 text-white'}`, ); return ( - ); diff --git a/src/pages/Write/index.tsx b/src/pages/Write/index.tsx index 2112a1d..6b937e4 100644 --- a/src/pages/Write/index.tsx +++ b/src/pages/Write/index.tsx @@ -12,36 +12,26 @@ import LetterEditor from './LetterEditor'; const WritePage = () => { const [searchParams] = useSearchParams(); + const [send, setSend] = useState(false); const [step, setStep] = useState('edit'); + // TODO : prevLetter를 받았을때, 데이터 중에 receiverId가 전역변수의 memberId와 일치하는지 판단해 일치하지 않으면 메인페이지로 리다이렉션 하는 로직 만들어야함(그런데 아직 prevLetter데이터에 receiverId값이 없음 진영님께 부탁해야함!) const [prevLetter, setPrevLetter] = useState([]); - const paperType = useWrite((state) => state.paperType); - const resetWrite = useWrite((state) => state.resetWrite); - - // 답글 작성 과정에서 데이터 정제 + 답글작성시 api연결 해야함(백서버가 꺼져서 내일 진행2025.02.21) - - // const LETTER_REQUEST: LetterRequest = { - // receiver: null, - // parentLetterId: null, - // title: letterTitle, - // content: letterText, - // category: searchParams, - // paperType: paperType, - // fontType: fontType, - // }; + const letterRequest = useWrite((state) => state.letterRequest); + const resetLetterRequest = useWrite((state) => state.resetLetterRequest); useEffect(() => { const letterId = searchParams.get('letterId'); if (letterId) { - getPrevLetter(setPrevLetter, letterId); + getPrevLetter(letterId, setPrevLetter); } }, [searchParams]); useEffect(() => { return () => { - resetWrite(); + resetLetterRequest(); }; - }, [resetWrite]); + }, [resetLetterRequest]); useEffect(() => { const navigationGuard = (e: BeforeUnloadEvent) => { @@ -56,12 +46,21 @@ const WritePage = () => { const wrapStyle = twMerge( 'relative p-5 w-full grow flex flex-col', - `${step === 'edit' && PAPER_TYPE_OBJ[paperType]}`, + `${step === 'edit' && PAPER_TYPE_OBJ[letterRequest.paperType]}`, ); return (
- {step === 'edit' && } - {step === 'category' && } + {step === 'edit' && ( + + )} + {step === 'category' && ( + + )}
); }; diff --git a/src/stores/writeStore.ts b/src/stores/writeStore.ts index 7f6f80a..ac7097f 100644 --- a/src/stores/writeStore.ts +++ b/src/stores/writeStore.ts @@ -1,47 +1,38 @@ import { create } from 'zustand'; interface WriteStore { - letterTitle: string; - setLetterTitle: (typing: string) => void; - letterText: string; - setLetterText: (typing: string) => void; - fontType: FontType; - setFontType: (selectedFontType: FontType) => void; - paperType: PaperType; - setPaperType: (selectedPaperType: PaperType) => void; - category: Category; - setCategory: (selectedCategory: Category) => void; - resetWrite: () => void; + letterRequest: LetterRequest; + setLetterRequest: (newLetterRequest: Partial) => void; + resetLetterRequest: () => void; } const useWrite = create((set) => ({ - letterTitle: '', - setLetterTitle: (typing) => set(() => ({ letterTitle: typing })), - - letterText: '', - setLetterText: (typing) => set(() => ({ letterText: typing })), - - fontType: 'DEFAULT', - setFontType: (selectedFontType) => { - set(() => ({ fontType: selectedFontType })); + letterRequest: { + receiverId: null, + parentLetterId: null, + title: '', + content: '', + category: 'CONSOLATION', + paperType: 'BASIC', + fontType: 'DEFAULT', }, - - paperType: 'BASIC', - setPaperType: (selectedPaperType) => - set(() => ({ - paperType: selectedPaperType, + setLetterRequest: (updateRequest) => + set((state) => ({ + letterRequest: { ...state.letterRequest, ...updateRequest }, })), - category: 'CONSOLATION', - setCategory: (selectedCategory) => set(() => ({ category: selectedCategory })), - - resetWrite: () => + resetLetterRequest: () => { set(() => ({ - letterTitle: '', - letterText: '', - fontType: 'DEFAULT', - paperType: 'BASIC', - category: 'CONSOLATION', - })), + letterRequest: { + receiverId: null, + parentLetterId: null, + title: '', + content: '', + category: 'CONSOLATION', + paperType: 'BASIC', + fontType: 'DEFAULT', + }, + })); + }, })); export default useWrite; diff --git a/src/types/write.d.ts b/src/types/write.d.ts index 5c7f026..f2e94e1 100644 --- a/src/types/write.d.ts +++ b/src/types/write.d.ts @@ -19,11 +19,11 @@ interface Fonts { } interface PrevLetter { - letterId: number; + letterId: string; title: string; content: string; - paperType: PaperType; - fontType: FontType; + category: Category; + memberId: number; } interface Categorys { @@ -48,7 +48,7 @@ interface CategoryStamps { // API 타입 interface LetterRequest { - receiver: number | null; + receiverId: number | null; parentLetterId: number | null; title: string; content: string; From 30bc2f28e11fbde01f3c1e51fb7fd48aed4e5044 Mon Sep 17 00:00:00 2001 From: wldnjs990 <139528356+wldnjs990@users.noreply.github.com> Date: Thu, 27 Feb 2025 15:14:44 +0900 Subject: [PATCH 03/15] =?UTF-8?q?feat:=EB=9E=9C=EB=8D=A4=20=ED=8E=B8?= =?UTF-8?q?=EC=A7=80=20+=20=ED=8E=B8=EC=A7=80=20=EC=83=81=EC=84=B8=201?= =?UTF-8?q?=EC=B0=A8=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20(#46)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat:편지 상세 페이지 삭제 모달 추가 * chore:기존 파일들 컴포넌트파일로 이동 * feat:쿨타임 컴포넌트 생성 + 랜덤편지 타입 수정 * feat:차단된 편지 버튼 disabled처리 * feat:편지 상세보기 모달 생성(매칭편지는 상세페이지와 분리) * fix:쿨타임 페이지 letterWrapper 적용 * feat:편지 상세, 랜덤 편지 api throw error 예외처리 추가 * feat:편지 매칭 제한시간 구현 * feat:랜덤편지 페이지 매칭 제한시간, 쿨타임 로직 구현 * feat:랜덤편지 페이지 편지 매칭 제한시간, 쿨타임 시간 구현 * feat:매칭된 편지 전달시 location값 전달처리({randomMatched: true}) * feat:코드리뷰 사항 수정 --- src/apis/letterDetail.ts | 2 + src/apis/randomLetter.ts | 17 +++ src/components/ResultLetter.tsx | 2 +- src/pages/LetterDetail/index.tsx | 25 +++- src/pages/RandomLetters/Matched.tsx | 20 --- src/pages/RandomLetters/MatchingSelect.tsx | 91 ------------ .../RandomLetters/components/CoolTime.tsx | 94 ++++++++++++ .../RandomLetters/components/Matched.tsx | 139 ++++++++++++++++++ .../components/MatchedLetter.tsx | 57 +++++++ .../components/MatchingSelect.tsx | 112 ++++++++++++++ .../{ => components}/MatchingSelectModal.tsx | 17 ++- src/pages/RandomLetters/constants/index.ts | 9 ++ src/pages/RandomLetters/index.tsx | 69 ++++++--- src/types/letterDetail.d.ts | 1 + src/types/random.d.ts | 7 +- src/utils/formatNumber.ts | 3 + 16 files changed, 521 insertions(+), 144 deletions(-) create mode 100644 src/apis/randomLetter.ts delete mode 100644 src/pages/RandomLetters/Matched.tsx delete mode 100644 src/pages/RandomLetters/MatchingSelect.tsx create mode 100644 src/pages/RandomLetters/components/CoolTime.tsx create mode 100644 src/pages/RandomLetters/components/Matched.tsx create mode 100644 src/pages/RandomLetters/components/MatchedLetter.tsx create mode 100644 src/pages/RandomLetters/components/MatchingSelect.tsx rename src/pages/RandomLetters/{ => components}/MatchingSelectModal.tsx (71%) create mode 100644 src/pages/RandomLetters/constants/index.ts create mode 100644 src/utils/formatNumber.ts diff --git a/src/apis/letterDetail.ts b/src/apis/letterDetail.ts index d2f176b..0091069 100644 --- a/src/apis/letterDetail.ts +++ b/src/apis/letterDetail.ts @@ -7,6 +7,7 @@ const getLetter = async ( ) => { try { const res = await client.get(`/api/letters/${letterId}`); + if (!res) throw new Error('편지 데이터를 가져오는 도중 에러가 발생했습니다.'); setLetterState(res.data.data); if (callBack) callBack(); console.log(res); @@ -19,6 +20,7 @@ const deleteLetter = async (letterId: string, callBack?: () => void) => { try { console.log(`/api/letters/${letterId}`); const res = await client.delete(`/api/letters/${letterId}`); + if (!res) throw new Error('편지 삭제 요청 도중 에러가 발생했습니다.'); if (callBack) callBack(); console.log(res); } catch (error) { diff --git a/src/apis/randomLetter.ts b/src/apis/randomLetter.ts new file mode 100644 index 0000000..4742f21 --- /dev/null +++ b/src/apis/randomLetter.ts @@ -0,0 +1,17 @@ +import client from './client'; + +const getRandomLetters = async ( + setRandomLettersState: React.Dispatch>, + category: string | null, +) => { + try { + const res = await client.get(`/api/random/${category}`); + if (!res) throw new Error('랜덤 편지 데이터를 가져오는 도중 에러가 발생했습니다.'); + setRandomLettersState(res.data.data); + console.log(res); + } catch (error) { + console.error(error); + } +}; + +export { getRandomLetters }; diff --git a/src/components/ResultLetter.tsx b/src/components/ResultLetter.tsx index 0e56b7d..7f1b131 100644 --- a/src/components/ResultLetter.tsx +++ b/src/components/ResultLetter.tsx @@ -5,7 +5,7 @@ import LetterWrapper from './LetterWrapper'; export default function ResultLetter({ categoryName = 'CONSOLATION', title, - zipCode = 'error', + zipCode = 'ERROR', }: { categoryName: Category; title: string; diff --git a/src/pages/LetterDetail/index.tsx b/src/pages/LetterDetail/index.tsx index c892656..cd9da97 100644 --- a/src/pages/LetterDetail/index.tsx +++ b/src/pages/LetterDetail/index.tsx @@ -12,6 +12,7 @@ import { WarmIcon, } from '@/assets/icons'; import BackButton from '@/components/BackButton'; +import ConfirmModal from '@/components/ConfirmModal'; import ReportModal from '@/components/ReportModal'; import { FONT_TYPE_OBJ, PAPER_TYPE_OBJ } from '@/pages/Write/constants'; @@ -28,6 +29,7 @@ const LetterDetailPage = () => { ]; const [degreeModalOpen, setDegreeModalOpen] = useState(false); const [reportModalOpen, setReportModalOpen] = useState(false); + const [deleteModalOpen, setDeleteModalOpen] = useState(false); const degreeButtonRef = useRef(null); const handleOutsideClick = (event: MouseEvent) => { @@ -41,7 +43,6 @@ const LetterDetailPage = () => { document.body.addEventListener('click', handleOutsideClick); if (params.id) { getLetter(params.id, setLetterDetail); - // 편지 삭제 요청 테스트(내일 삭제 버튼 만들어서 여기다 추가하긔) } return () => { @@ -72,7 +73,7 @@ const LetterDetailPage = () => { + {deleteModalOpen && ( + { + setDeleteModalOpen(false); + }} + onConfirm={() => { + if (params.id) deleteLetter(params.id); + navigate(-1); + }} + /> + )}
); diff --git a/src/pages/RandomLetters/Matched.tsx b/src/pages/RandomLetters/Matched.tsx deleted file mode 100644 index bc77d3a..0000000 --- a/src/pages/RandomLetters/Matched.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import ResultLetter from '@/components/ResultLetter'; - -export default function Matched() { - return ( -
-
-

답장까지 남은 시간

-

- {'00'} : {'00'} : {'00'} -

-
- -
- -
-
- ); -} diff --git a/src/pages/RandomLetters/MatchingSelect.tsx b/src/pages/RandomLetters/MatchingSelect.tsx deleted file mode 100644 index 375c9bd..0000000 --- a/src/pages/RandomLetters/MatchingSelect.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { useState } from 'react'; -import { EffectCards } from 'swiper/modules'; -import { Swiper, SwiperSlide } from 'swiper/react'; -import { twMerge } from 'tailwind-merge'; - -import 'swiper/swiper-bundle.css'; - -import { RestartIcon } from '@/assets/icons'; -import ResultLetter from '@/components/ResultLetter'; - -export default function MatchingSelect({ - setOpenModal, - setSelectedLetter, -}: { - setOpenModal: React.Dispatch>; - setSelectedLetter: React.Dispatch>; -}) { - const [selectedCategory, setSelectedCategory] = useState('전체'); - - const CATEGORY_LIST = ['전체', '위로와 공감', '축하와 응원', '고민 상담', '기타']; - const DUMMY_LIST: { categoryName: Category; title: string }[] = [ - { categoryName: 'CONSOLATION', title: '위로가 필요해요' }, - { categoryName: 'CELEBRATION', title: '저에게 미움받을 용기를 주세요' }, - { categoryName: 'CONSULT', title: '삶이 무료해서 고민이에요' }, - { categoryName: 'ETC', title: '어제 꾼 꿈이 신기했어요' }, - { categoryName: 'CONSULT', title: '삶이 유료해서 고민이에요' }, - { categoryName: 'CELEBRATION', title: '어제 취업했어요!!!!' }, - { categoryName: 'CELEBRATION', title: '어제 게임 신기록 세웠어요!!!!!' }, - { categoryName: 'ETC', title: '기타는 핑거스타일이 멋있는거 같아요' }, - { categoryName: 'CONSOLATION', title: '10년지기 친구가 이사를 가요' }, - { - categoryName: 'ETC', - title: - '햄부기햄북 햄북어 햄북스딱스 함부르크햄부가우가 햄비기햄부거 햄부가티햄부기온앤 온 을 차려오거라.', - }, - ]; - - return ( - <> -
- -
- - {DUMMY_LIST.map((list, idx) => { - return ( - -
{ - setOpenModal(true); - setSelectedLetter(() => ({ - categoryName: list.categoryName, - title: list.title, - })); - }} - > - -
-
- ); - })} -
-
-
- -
- {CATEGORY_LIST.map((category, idx) => { - return ( - - ); - })} -
- - ); -} diff --git a/src/pages/RandomLetters/components/CoolTime.tsx b/src/pages/RandomLetters/components/CoolTime.tsx new file mode 100644 index 0000000..68882da --- /dev/null +++ b/src/pages/RandomLetters/components/CoolTime.tsx @@ -0,0 +1,94 @@ +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router'; + +import LetterWrapper from '@/components/LetterWrapper'; +import { formatNumber } from '@/utils/formatNumber'; + +// import letterPink from '@/assets/images/letter-pink.png'; + +export default function CoolTime({ + setCoolTime, +}: { + setCoolTime: React.Dispatch>; +}) { + const navigate = useNavigate(); + + const TIME_STAMP = '2025-02-26T22:13:25.262045608'; + + const COMPLETED_DATE = new Date(TIME_STAMP); + + const END_DATE = new Date(COMPLETED_DATE); + END_DATE.setHours(COMPLETED_DATE.getHours() + 1); + + const NOW_DATE = new Date(); + + const endTime = END_DATE.getTime() - NOW_DATE.getTime(); + + const [endTimes, setEndTimes] = useState({ + hours: Math.floor((endTime / (1000 * 60 * 60)) % 24), + minutes: Math.floor((endTime / (1000 * 60)) % 60), + seconds: Math.floor((endTime / 1000) % 60), + }); + + useEffect(() => { + if (endTimes.hours < 0 || endTimes.minutes < 0 || endTimes.seconds < 0) { + setEndTimes({ hours: 0, minutes: 0, seconds: 0 }); + } + if (endTimes.hours === 0 && endTimes.minutes === 0 && endTimes.seconds === 0) { + setCoolTime(false); + return; + } + const endTimeFlow = setInterval(() => { + setEndTimes((currentTime) => { + if (currentTime.seconds > 0) { + return { ...currentTime, seconds: currentTime.seconds - 1 }; + } // + else { + if (currentTime.minutes > 0) { + return { ...currentTime, minutes: currentTime.minutes - 1, seconds: 59 }; + } // + else { + if (currentTime.hours > 0) { + return { hours: currentTime.hours - 1, minutes: 59, seconds: 59 }; + } // + else { + setCoolTime(false); + return { ...currentTime }; + } + } + } + }); + if (endTimes.hours === 0 && endTimes.minutes === 0 && endTimes.seconds === 0) { + clearInterval(endTimeFlow); + } + }, 1000); + + return () => { + clearInterval(endTimeFlow); + }; + }, [endTimes, setCoolTime]); + return ( +
+
+

랜덤 편지 활성화 까지

+

+ {formatNumber(endTimes.hours)} : {formatNumber(endTimes.minutes)} :{' '} + {formatNumber(endTimes.seconds)} +

+
+ +
+
+
+ +
+
+ ); +} diff --git a/src/pages/RandomLetters/components/Matched.tsx b/src/pages/RandomLetters/components/Matched.tsx new file mode 100644 index 0000000..5825da9 --- /dev/null +++ b/src/pages/RandomLetters/components/Matched.tsx @@ -0,0 +1,139 @@ +import { useEffect, useState } from 'react'; + +import ResultLetter from '@/components/ResultLetter'; +import { formatNumber } from '@/utils/formatNumber'; + +export default function Matched({ + setMatched, + setCoolTime, +}: { + setMatched: React.Dispatch>; + setCoolTime: React.Dispatch>; +}) { + const [isDisabled, setIsDisabled] = useState(false); + + const TIME_STAMP = '2025-02-25T21:52:25.262045608'; + + const MATCHED_DATE = new Date(TIME_STAMP); + + const END_DATE = new Date(MATCHED_DATE); + END_DATE.setHours(MATCHED_DATE.getHours() + 24); + + const GRACE_DATE = new Date(MATCHED_DATE); + GRACE_DATE.setMinutes(MATCHED_DATE.getMinutes() + 5); + + const NOW_DATE = new Date(); + + const endTime = END_DATE.getTime() - NOW_DATE.getTime(); + const graceTime = GRACE_DATE.getTime() - NOW_DATE.getTime(); + + const [endTimes, setEndTimes] = useState({ + hours: Math.floor((endTime / (1000 * 60 * 60)) % 24), + minutes: Math.floor((endTime / (1000 * 60)) % 60), + seconds: Math.floor((endTime / 1000) % 60), + }); + + const [graceTimes, setGraceTimes] = useState({ + minutes: Math.floor((graceTime / (1000 * 60)) % 60), + seconds: Math.floor((graceTime / 1000) % 60), + }); + + useEffect(() => { + if (endTimes.hours < 0 || endTimes.minutes < 0 || endTimes.seconds < 0) { + setEndTimes({ hours: 0, minutes: 0, seconds: 0 }); + } + if (endTimes.hours === 0 && endTimes.minutes === 0 && endTimes.seconds === 0) { + setMatched(false); + setCoolTime(true); + return; + } + const endTimeFlow = setInterval(() => { + setEndTimes((currentTime) => { + if (currentTime.seconds > 0) { + return { ...currentTime, seconds: currentTime.seconds - 1 }; + } // + else { + if (currentTime.minutes > 0) { + return { ...currentTime, minutes: currentTime.minutes - 1, seconds: 59 }; + } // + else { + if (currentTime.hours > 0) { + return { hours: currentTime.hours - 1, minutes: 59, seconds: 59 }; + } // + else { + setMatched(false); + setCoolTime(true); + return { ...currentTime }; + } + } + } + }); + if (endTimes.hours === 0 && endTimes.minutes === 0 && endTimes.seconds === 0) { + clearInterval(endTimeFlow); + } + }, 1000); + + return () => { + clearInterval(endTimeFlow); + }; + }, [endTimes, setMatched, setCoolTime]); + + useEffect(() => { + if (graceTimes.minutes < 0 || graceTimes.seconds < 0) { + setGraceTimes({ minutes: 0, seconds: 0 }); + } + if (graceTimes.minutes === 0 && graceTimes.seconds === 0) { + return setIsDisabled(true); + } + const graceTimeFlow = setInterval(() => { + setGraceTimes((currentTime) => { + if (currentTime.seconds > 0) { + return { ...currentTime, seconds: currentTime.seconds - 1 }; + } // + else { + if (currentTime.minutes > 0) { + return { minutes: currentTime.minutes - 1, seconds: 59 }; + } // + else { + setIsDisabled(true); + return { ...currentTime }; + } + } + }); + if (graceTimes.minutes === 0 && graceTimes.seconds === 0) { + clearInterval(graceTimeFlow); + } + }, 1000); + + return () => { + clearInterval(graceTimeFlow); + }; + }, [graceTimes]); + + return ( +
+
+

답장까지 남은 시간

+

+ {formatNumber(endTimes.hours)} : {formatNumber(endTimes.minutes)} :{' '} + {formatNumber(endTimes.seconds)} +

+
+ +
+ +
+
+ ); +} diff --git a/src/pages/RandomLetters/components/MatchedLetter.tsx b/src/pages/RandomLetters/components/MatchedLetter.tsx new file mode 100644 index 0000000..bda4395 --- /dev/null +++ b/src/pages/RandomLetters/components/MatchedLetter.tsx @@ -0,0 +1,57 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router'; +import { twMerge } from 'tailwind-merge'; + +import BackButton from '@/components/BackButton'; +import ReportModal from '@/components/ReportModal'; +import { FONT_TYPE_OBJ, PAPER_TYPE_OBJ } from '@/pages/Write/constants'; + +const MatchedLetter = ({ selectedLetter }: { selectedLetter: RandomLetters }) => { + const navigate = useNavigate(); + // 상대방의 우편번호도 데이터에 포함되어야 할 거 같음!!! + const [letterDetail] = useState(null); + + const [reportModalOpen, setReportModalOpen] = useState(false); + + return ( + <> + {reportModalOpen && setReportModalOpen(false)} />} +
+
+ +
+
+ TO. 따숨이 + {selectedLetter?.title} +
+ + FROM. {selectedLetter.zipCode} + +
+ + ); +}; + +export default MatchedLetter; diff --git a/src/pages/RandomLetters/components/MatchingSelect.tsx b/src/pages/RandomLetters/components/MatchingSelect.tsx new file mode 100644 index 0000000..45fbc79 --- /dev/null +++ b/src/pages/RandomLetters/components/MatchingSelect.tsx @@ -0,0 +1,112 @@ +import { useEffect, useState } from 'react'; +import { EffectCards } from 'swiper/modules'; +import { Swiper, SwiperSlide } from 'swiper/react'; +import { twMerge } from 'tailwind-merge'; + +import 'swiper/swiper-bundle.css'; + +import { getRandomLetters } from '@/apis/randomLetter'; +import { RestartIcon } from '@/assets/icons'; +import ResultLetter from '@/components/ResultLetter'; + +import { CATEGORY_LIST } from '../constants'; + +export default function MatchingSelect({ + setOpenModal, + setSelectedLetter, +}: { + setOpenModal: React.Dispatch>; + setSelectedLetter: React.Dispatch>; +}) { + const [selectedCategory, setSelectedCategory] = useState(null); + const [randomLetters, setRandomLetters] = useState([]); + + const DUMMY_LIST: RandomLetters[] = [ + { + letterId: 1, + title: '위로가 필요해요', + zipCode: '1aq23', + category: 'CONSOLATION', + createdAt: new Date(), + }, + { + letterId: 2, + title: '아래로가 필요해요', + zipCode: '23w7q', + category: 'CELEBRATION', + createdAt: new Date(), + }, + { + letterId: 3, + title: '안녕하세요', + zipCode: '9a5g7', + category: 'ETC', + createdAt: new Date(), + }, + ]; + + useEffect(() => { + getRandomLetters(setRandomLetters, selectedCategory); + console.log(randomLetters); + }, [selectedCategory]); + + return ( + <> +
+ +
+ + {DUMMY_LIST.map((list, idx) => { + return ( + +
{ + setOpenModal(true); + setSelectedLetter(list); + }} + > + +
+
+ ); + })} +
+
+
+ +
+ {CATEGORY_LIST.map((category, idx) => { + return ( + + ); + })} +
+ + ); +} diff --git a/src/pages/RandomLetters/MatchingSelectModal.tsx b/src/pages/RandomLetters/components/MatchingSelectModal.tsx similarity index 71% rename from src/pages/RandomLetters/MatchingSelectModal.tsx rename to src/pages/RandomLetters/components/MatchingSelectModal.tsx index 6be984e..9d634b4 100644 --- a/src/pages/RandomLetters/MatchingSelectModal.tsx +++ b/src/pages/RandomLetters/components/MatchingSelectModal.tsx @@ -1,4 +1,4 @@ -import { useNavigate } from 'react-router'; +// import { useNavigate } from 'react-router'; import ModalOverlay from '@/components/ModalOverlay'; import ResultLetter from '@/components/ResultLetter'; @@ -6,11 +6,13 @@ import ResultLetter from '@/components/ResultLetter'; function MatchingSelectModal({ setOpenModal, selectedLetter, + setOpenSelectedDetailModal, }: { setOpenModal: React.Dispatch>; - selectedLetter: SelectedLetter; + selectedLetter: RandomLetters; + setOpenSelectedDetailModal: React.Dispatch>; }) { - const navigate = useNavigate(); + // const navigate = useNavigate(); return (
@@ -19,7 +21,11 @@ function MatchingSelectModal({ 수락한 편지는 5분이 지나면 취소할 수 없습니다.
- +
; }; export default AuthCallbackPage; diff --git a/src/pages/Onboarding/SetZipCode.tsx b/src/pages/Onboarding/SetZipCode.tsx index 2748f80..bdee2a2 100644 --- a/src/pages/Onboarding/SetZipCode.tsx +++ b/src/pages/Onboarding/SetZipCode.tsx @@ -1,6 +1,5 @@ import { useEffect, useState } from 'react'; -import { getZipCode } from '@/apis/auth'; import useAuthStore from '@/stores/authStore'; import Spinner from './components/Spinner'; @@ -10,22 +9,10 @@ const SetZipCode = ({ }: { setIsZipCodeSet: React.Dispatch>; }) => { - const [zipCode, setZipCode] = useState(''); const [isBtnActive, setIsBtnActive] = useState(false); - const { accessToken } = useAuthStore.getState(); + const { zipCode } = useAuthStore.getState(); - const fetchZipCode = async () => { - try { - const response = await getZipCode(accessToken as string); - if (!response) throw new Error('fetchZipCode: no response'); - console.log(response.data.zipCode); - setZipCode(response.data.zipCode); - } catch (error) { - console.error(error); - } - }; useEffect(() => { - fetchZipCode(); setTimeout(() => { setIsBtnActive(true); }, 6300); diff --git a/src/stores/authStore.ts b/src/stores/authStore.ts index 292fddb..92c1039 100644 --- a/src/stores/authStore.ts +++ b/src/stores/authStore.ts @@ -3,12 +3,10 @@ import { persist, createJSONStorage } from 'zustand/middleware'; interface AuthStore { isLoggedIn: boolean; - userId: number | null; zipCode: string; accessToken: string; login: () => void; logout: () => void; - setUserId: (userId: number) => void; setZipCode: (zipCode: string) => void; setAccessToken: (accessToken: string) => void; } @@ -17,11 +15,9 @@ const useAuthStore = create( (set) => ({ isLoggedIn: false, accessToken: '', - userId: null, zipCode: '', login: () => set({ isLoggedIn: true }), - logout: () => set({ isLoggedIn: false, userId: null, zipCode: '' }), - setUserId: (userId) => set({ userId: userId }), + logout: () => set({ isLoggedIn: false, zipCode: '', accessToken: '' }), setZipCode: (zipCode) => set({ zipCode: zipCode }), setAccessToken: (accessToken) => set({ accessToken: accessToken }), }), From 38e5f01eefd630893b55471a085b455ce7433525 Mon Sep 17 00:00:00 2001 From: nirii00 Date: Fri, 28 Feb 2025 15:50:54 +0900 Subject: [PATCH 08/15] =?UTF-8?q?test:=20=ED=83=88=ED=87=B4=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/client.ts | 3 +++ src/pages/Auth/index.tsx | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/apis/client.ts b/src/apis/client.ts index b86af51..b623767 100644 --- a/src/apis/client.ts +++ b/src/apis/client.ts @@ -13,8 +13,11 @@ const client = axios.create({ client.interceptors.request.use( (config) => { const { accessToken } = useAuthStore.getState(); + // console.log('intercepter', accessToken); + console.log(config.url); if (config.url !== '/auth/reissue' && accessToken) { config.headers.Authorization = `Bearer ${accessToken}`; + console.log('intercepter', config.headers); } return config; }, diff --git a/src/pages/Auth/index.tsx b/src/pages/Auth/index.tsx index 636dcec..940ed53 100644 --- a/src/pages/Auth/index.tsx +++ b/src/pages/Auth/index.tsx @@ -42,7 +42,6 @@ const AuthCallbackPage = () => { if (!createZipCodeResponse) throw new Error('Error creating ZipCode'); const zipCode = createZipCodeResponse.data.data.zipCode; const newAccessToken = createZipCodeResponse.headers['authorizazion']; - setZipCode(zipCode); setAccessToken(newAccessToken); } From 1a5240f21ac0039a4fff210c1d3f314e703654a2 Mon Sep 17 00:00:00 2001 From: Sebin Kim <108220388+nirii00@users.noreply.github.com> Date: Fri, 28 Feb 2025 17:27:36 +0900 Subject: [PATCH 09/15] =?UTF-8?q?Perf:=20=EB=82=B4=20=ED=8E=B8=EC=A7=80?= =?UTF-8?q?=ED=95=A8=20=ED=83=84=EC=8A=A4=ED=83=9D=20=EC=BF=BC=EB=A6=AC=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20(#50)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 모바일에서 gradient 보이지 않는 문제 해결 * refactor: 내 편지함 및 상세 querykey 적용 - useState을 삭제 하고 데이터 관리 및 성능 최적화를 위해 tanstack query 적용 * fix: 편지 상세 무한 스크롤 오류 수정 - 마지막 편지 보일 시 새로 페이징 하는 로직을 response data에 맞게 수정 - react-intersection-observer 라이브러리를 이용하여 마지막 요소가 view에 들어오는지 확인 * fix: 내 편지함 디자인 보이지 않는 문제 해결 - gradietn class에 !important를 적용 --------- Co-authored-by: nirii00 --- package.json | 1 + pnpm-lock.yaml | 18 +++ src/apis/mailBox.ts | 6 +- src/components/LetterWrapper.tsx | 34 ++-- .../LetterBox/components/LetterBoxItem.tsx | 9 +- src/pages/LetterBox/index.tsx | 50 +++--- .../components/LetterPreview.tsx | 34 ++-- src/pages/LetterBoxDetail/index.tsx | 153 ++++++++++-------- src/styles/utilities.css | 21 +++ src/utils/formatDate.ts | 11 ++ 10 files changed, 216 insertions(+), 121 deletions(-) create mode 100644 src/utils/formatDate.ts diff --git a/package.json b/package.json index 0e64280..5b1f88f 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "immer": "^10.1.1", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-intersection-observer": "^9.15.1", "react-router": "^7.1.5", "swiper": "^11.2.4", "tailwind-merge": "^3.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e36e08d..adc74bd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,6 +44,9 @@ importers: react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) + react-intersection-observer: + specifier: ^9.15.1 + version: 9.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-router: specifier: ^7.1.5 version: 7.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -2017,6 +2020,15 @@ packages: peerDependencies: react: ^18.3.1 + react-intersection-observer@9.15.1: + resolution: {integrity: sha512-vGrqYEVWXfH+AGu241uzfUpNK4HAdhCkSAyFdkMb9VWWXs6mxzBLpWCxEy9YcnDNY2g9eO6z7qUtTBdA9hc8pA==} + peerDependencies: + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + react-dom: + optional: true + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -4263,6 +4275,12 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 + react-intersection-observer@9.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + react-is@16.13.1: {} react-is@19.0.0: {} diff --git a/src/apis/mailBox.ts b/src/apis/mailBox.ts index 7c8b120..7990b34 100644 --- a/src/apis/mailBox.ts +++ b/src/apis/mailBox.ts @@ -10,9 +10,11 @@ export const getMailbox = async () => { } }; -export const getMailboxDetail = async (id: number) => { +export const getMailboxDetail = async (id: number, pageParam: number) => { try { - const response = await client.get(`/api/mailbox/${id}`); + const response = await client.get(`/api/mailbox/${id}?page=${pageParam}&size=20`); + // const response = await client.get(`/api/mailbox/${id}`); + if (!response) throw new Error('error while fetching mailbox detail data'); return response.data; } catch (error) { diff --git a/src/components/LetterWrapper.tsx b/src/components/LetterWrapper.tsx index 365a8b2..7373ea2 100644 --- a/src/components/LetterWrapper.tsx +++ b/src/components/LetterWrapper.tsx @@ -1,3 +1,4 @@ +import { forwardRef } from 'react'; import { twMerge } from 'tailwind-merge'; interface LetterWrapperProps { @@ -7,20 +8,23 @@ interface LetterWrapperProps { onClick?: (e: React.MouseEvent) => void; } -const LetterWrapper = ({ isSender = false, className, children, onClick }: LetterWrapperProps) => { - return ( -
-
{children}
-
-
- ); -}; +const LetterWrapper = forwardRef( + ({ isSender = false, className, children, onClick }, ref) => { + return ( +
+
{children}
+
+
+ ); + }, +); export default LetterWrapper; diff --git a/src/pages/LetterBox/components/LetterBoxItem.tsx b/src/pages/LetterBox/components/LetterBoxItem.tsx index 8f7d13e..f493ffb 100644 --- a/src/pages/LetterBox/components/LetterBoxItem.tsx +++ b/src/pages/LetterBox/components/LetterBoxItem.tsx @@ -30,14 +30,15 @@ const LetterBoxItem = ({ className="flex h-fit w-fit flex-col items-center" onClick={() => handleClickItem(boxId)} > -
+

{zipCode} @@ -48,7 +49,7 @@ const LetterBoxItem = ({

{letterCount}통

diff --git a/src/pages/LetterBox/index.tsx b/src/pages/LetterBox/index.tsx index a7aa199..892cb3e 100644 --- a/src/pages/LetterBox/index.tsx +++ b/src/pages/LetterBox/index.tsx @@ -1,4 +1,5 @@ -import { useEffect, useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { useNavigate } from 'react-router'; import { getMailbox } from '@/apis/mailBox'; import DoorImg from '@/assets/images/door.png'; @@ -13,25 +14,34 @@ interface LetterBoxData { oppositeZipCode: string; active: boolean; oppositeRead: boolean; - // totalLetters: number; + letterCount: number; } + +const fetchMailLists = async () => { + const response = await getMailbox(); + if (!response) throw new Error(); + const data: LetterBoxData[] = response.data; + // 정렬? + return data; +}; + const LetterBoxPage = () => { - const [letterBox, setLetterBox] = useState([]); + const { + data: letterBox = [], + isLoading, + isError, + } = useQuery({ + queryKey: ['mailbox'], + queryFn: fetchMailLists, + staleTime: 1000 * 60 * 5, + gcTime: 1000 * 60 * 10, + }); + + const navigate = useNavigate(); - const fetchMailLists = async () => { - try { - const response = await getMailbox(); - if (!response) throw new Error(); - const data: LetterBoxData[] = response.data; - // 정렬? - setLetterBox(data); - } catch (error) { - console.error(error); - } - }; - useEffect(() => { - fetchMailLists(); - }, []); + if (isError) { + navigate('/NotFound'); + } return (
@@ -42,14 +52,16 @@ const LetterBoxPage = () => {

- {letterBox.length > 0 ? ( + {isLoading ? ( +

로딩중..

+ ) : letterBox.length > 0 ? ( chunkBox( letterBox.map((data: LetterBoxData, index) => ( diff --git a/src/pages/LetterBoxDetail/components/LetterPreview.tsx b/src/pages/LetterBoxDetail/components/LetterPreview.tsx index 4737242..98967fc 100644 --- a/src/pages/LetterBoxDetail/components/LetterPreview.tsx +++ b/src/pages/LetterBoxDetail/components/LetterPreview.tsx @@ -1,6 +1,8 @@ +import { forwardRef } from 'react'; import { useNavigate } from 'react-router'; import LetterWrapper from '@/components/LetterWrapper'; +import formatDate from '@/utils/formatDate'; interface LetterPreviewProps { id: number; @@ -13,18 +15,18 @@ interface LetterPreviewProps { isClosed: boolean; zipCode: string; } -const LetterPreview = ({ - id, - date, - title, - isSend, - checked, - isShareMode = false, - onToggle, - isClosed, - zipCode, -}: LetterPreviewProps) => { - // 차단된 편지인경우 편지 보내기 disable +const LetterPreview = forwardRef((props, ref) => { + const { + id, + date, + title, + isSend, + checked, + isShareMode = false, + onToggle, + isClosed, + zipCode, + } = props; const navigate = useNavigate(); const handleItemClick = (id: number) => { @@ -39,9 +41,9 @@ const LetterPreview = ({ if (isShareMode) return ( - +
-

{date}

+

{formatDate(date)}

- {mailLists.map((letter) => ( - toggleSelected(letter.letterId)} - zipCode={userInfo.zipCode} - /> - ))} + {isLoading ? ( + //TODO: skeleton +
Loading
+ ) : ( + mailLists.map((letter, index) => ( + toggleSelected(letter.letterId)} + zipCode={userInfo.zipCode} + ref={index === mailLists.length - 1 ? ref : null} + /> + )) + )}
- {!isShareMode && !userInfo.isClosed && ( + {!isShareMode && !userInfo.isClosed && !isLoading && (
-

로그아웃

+

logout()}> + 로그아웃 +

; + return ; }; export default AuthCallbackPage; From c8bba5293959147b9a231339e0cad1c38a2e408a Mon Sep 17 00:00:00 2001 From: nirii00 Date: Sat, 1 Mar 2025 22:28:44 +0900 Subject: [PATCH 14/15] =?UTF-8?q?refactor:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81=20-=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=ED=83=80=EC=9E=85=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EB=A7=8C=EB=93=A6=20-=20logout=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EC=84=A0=EC=96=B8=20=EC=A0=95=EB=A6=AC=20-=20myPage=20?= =?UTF-8?q?=ED=83=88=ED=87=B4=20=EC=B6=94=EA=B0=80,=20p=20=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=20->=20=EB=B2=84=ED=8A=BC=20-=20useAuthState=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8A=94=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 - pnpm-lock.yaml | 6 ++---- src/apis/auth.ts | 22 ++-------------------- src/apis/client.ts | 11 ++++++----- src/pages/Auth/index.tsx | 17 +++++------------ src/pages/Login/index.tsx | 2 -- src/pages/MyPage/index.tsx | 22 ++++++++++++++++++---- src/pages/Onboarding/SetZipCode.tsx | 2 +- src/types/auth.d.ts | 1 + 9 files changed, 35 insertions(+), 49 deletions(-) create mode 100644 src/types/auth.d.ts diff --git a/package.json b/package.json index 5b1f88f..9514795 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,6 @@ "@tanstack/react-query": "^5.66.0", "axios": "^1.7.9", "gsap": "^3.12.7", - "immer": "^10.1.1", "react": "^18.3.1", "react-dom": "^18.3.1", "react-intersection-observer": "^9.15.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index adc74bd..dac4254 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,9 +35,6 @@ importers: gsap: specifier: ^3.12.7 version: 3.12.7 - immer: - specifier: ^10.1.1 - version: 10.1.1 react: specifier: ^18.3.1 version: 18.3.1 @@ -3896,7 +3893,8 @@ snapshots: ignore@5.3.2: {} - immer@10.1.1: {} + immer@10.1.1: + optional: true import-fresh@3.3.1: dependencies: diff --git a/src/apis/auth.ts b/src/apis/auth.ts index 8b68e8b..6885010 100644 --- a/src/apis/auth.ts +++ b/src/apis/auth.ts @@ -1,25 +1,7 @@ -import useAuthStore from '@/stores/authStore'; - import client from './client'; -type LoginType = 'kakao' | 'naver' | 'google'; export const socialLogin = (loginType: LoginType) => { - window.location.href = `http://13.209.132.150:8081/oauth2/authorization/${loginType}`; -}; - -export const logout = async () => { - const { accessToken } = useAuthStore.getState(); - - try { - const response = await client.post(`/api/logout`, { - Authorization: { token: `Bearer ${accessToken}` }, - withCredentials: true, - }); - if (!response) throw new Error('logout fail'); - return response; - } catch (error) { - console.error(error); - } + window.location.href = `${import.meta.env.VITE_API_URL}/oauth2/authorization/${loginType}`; }; export const getUserToken = async (stateToken: string) => { @@ -38,7 +20,7 @@ export const getUserToken = async (stateToken: string) => { export const postZipCode = async () => { try { const response = await client.post(`/api/members/zipCode`); - if(!response) throw new Error('fail to post ZipCode') + if (!response) throw new Error('fail to post ZipCode'); return response; } catch (error) { console.error(error); diff --git a/src/apis/client.ts b/src/apis/client.ts index ca4c588..6fc9fe4 100644 --- a/src/apis/client.ts +++ b/src/apis/client.ts @@ -10,18 +10,17 @@ const client = axios.create({ client.interceptors.request.use( (config) => { - const { accessToken } = useAuthStore.getState(); + const accessToken = useAuthStore((state) => state.accessToken); console.log(config.url); console.log(accessToken); if (config.url !== '/auth/reissue' && accessToken) { config.headers.Authorization = `Bearer ${accessToken}`; console.log('intercepter', config.headers); } - return config - ; + return config; }, (error) => { - const { logout } = useAuthStore.getState(); + const logout = useAuthStore((state) => state.logout); logout(); window.location.replace('/login'); return Promise.reject(error); @@ -31,7 +30,9 @@ client.interceptors.request.use( client.interceptors.response.use( (response) => response, async (error) => { - const { setAccessToken, logout } = useAuthStore.getState(); + const setAccessToken = useAuthStore((state) => state.setAccessToken); + const logout = useAuthStore((state) => state.logout); + const originalRequest = error.config; if (!originalRequest) { diff --git a/src/pages/Auth/index.tsx b/src/pages/Auth/index.tsx index 8b3f0b7..cc157c7 100644 --- a/src/pages/Auth/index.tsx +++ b/src/pages/Auth/index.tsx @@ -2,14 +2,16 @@ import { useEffect } from 'react'; import { useNavigate } from 'react-router'; -import { getUserToken, getMydata, deleteUserInfo, postZipCode } from '@/apis/auth'; +import { getUserToken, getMydata, postZipCode } from '@/apis/auth'; import useAuthStore from '@/stores/authStore'; const AuthCallbackPage = () => { const stateToken = new URLSearchParams(window.location.search).get('state'); const redirectURL = new URLSearchParams(window.location.search).get('redirect'); - const { setZipCode, setAccessToken, login } = useAuthStore(); + const login = useAuthStore((state) => state.login); + const setAccessToken = useAuthStore((state) => state.setAccessToken); + const setZipCode = useAuthStore((state) => state.setZipCode); const navigate = useNavigate(); @@ -74,16 +76,7 @@ const AuthCallbackPage = () => { redirection(); } else navigate('/notFound'); }, []); - - const handleLeave = async () => { - try { - const response = await deleteUserInfo(); - console.log(response); - } catch (error) { - console.error(error); - } - }; - return ; + return <>; }; export default AuthCallbackPage; diff --git a/src/pages/Login/index.tsx b/src/pages/Login/index.tsx index 333e22e..ec651c7 100644 --- a/src/pages/Login/index.tsx +++ b/src/pages/Login/index.tsx @@ -4,8 +4,6 @@ import { GoogleIcon, KakaoIcon, NaverIcon, StampIcon } from '@/assets/icons'; import Background from './components/Background'; const LoginPage = () => { - type LoginType = 'kakao' | 'naver' | 'google'; - const handleLogin = (loginType: LoginType) => { socialLogin(loginType); }; diff --git a/src/pages/MyPage/index.tsx b/src/pages/MyPage/index.tsx index 691ee04..fe027c1 100644 --- a/src/pages/MyPage/index.tsx +++ b/src/pages/MyPage/index.tsx @@ -1,6 +1,7 @@ import { useState, useEffect } from 'react'; import { Link } from 'react-router'; +import { deleteUserInfo } from '@/apis/auth'; import ConfirmModal from '@/components/ConfirmModal'; import useAuthStore from '@/stores/authStore'; import useMyPageStore from '@/stores/myPageStore'; @@ -14,7 +15,7 @@ const MyPage = () => { const { data, fetchMyPageInfo } = useMyPageStore(); const [isOpenModal, setIsOpenModal] = useState(false); - const { logout } = useAuthStore(); + const logout = useAuthStore((state) => state.logout); const getDescriptionByTemperature = (temp: number) => { const range = TEMPERATURE_RANGE.find((range) => temp >= range.min && temp < range.max); @@ -23,6 +24,16 @@ const MyPage = () => { const description = getDescriptionByTemperature(Number(data.temperature)); + const handleLeave = async () => { + try { + const response = await deleteUserInfo(); + if (!response) throw new Error('deletion failed'); + console.log(response); + } catch (error) { + console.error(error); + } + }; + return ( <> {isOpenModal && ( @@ -32,7 +43,10 @@ const MyPage = () => { cancelText="되돌아가기" confirmText="탈퇴하기" onCancel={() => setIsOpenModal(false)} - onConfirm={() => setIsOpenModal(false)} + onConfirm={() => { + handleLeave(); + setIsOpenModal(false); + }} /> )}
@@ -78,9 +92,9 @@ const MyPage = () => { {data.email}

-

logout()}> +