From a9984967c1e78f0443979d1c818578fac369028d Mon Sep 17 00:00:00 2001 From: nalsae Date: Tue, 10 Oct 2023 13:21:17 +0900 Subject: [PATCH 01/20] =?UTF-8?q?[FE]=20=E2=99=BB=EF=B8=8F=20Refactor=20:?= =?UTF-8?q?=20Modal=20=EC=83=81=ED=83=9C=20=ED=86=B5=ED=95=A9=EC=9D=84=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20modalStore=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/stores/modalStore.ts | 34 +++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 client/src/stores/modalStore.ts diff --git a/client/src/stores/modalStore.ts b/client/src/stores/modalStore.ts new file mode 100644 index 00000000..f2239082 --- /dev/null +++ b/client/src/stores/modalStore.ts @@ -0,0 +1,34 @@ +import { create } from 'zustand'; + +export type PostType = 'post' | 'comment'; + +export type GardenType = + | 'leafExist' + | 'noLeafExist' + | 'selectLeaf' + | 'purchaseInfo' + | 'purchase' + | 'emptyInventory' + | 'share'; + +export type ModalType = PostType | GardenType | null; + +export interface GardenModalState { + isOpen: boolean; + type: ModalType; + + changeType: (type: ModalType) => void; + open: () => void; + close: () => void; +} + +const useGardenModalStore = create((set) => ({ + isOpen: false, + type: null, + + changeType: (type) => set(() => ({ type })), + open: () => set(() => ({ isOpen: true })), + close: () => set(() => ({ isOpen: false })), +})); + +export default useGardenModalStore; From e9d66eb6936b8a578216d3da9d61df660464ad65 Mon Sep 17 00:00:00 2001 From: nalsae Date: Tue, 10 Oct 2023 13:22:11 +0900 Subject: [PATCH 02/20] =?UTF-8?q?[FE]=20=E2=99=BB=EF=B8=8F=20Refactor=20:?= =?UTF-8?q?=20common=20=ED=8F=B4=EB=8D=94=20=ED=95=98=EC=9C=84=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=AC=B6=EC=96=B4=EC=84=9C=20exp?= =?UTF-8?q?ort?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/app/post/[id]/page.tsx | 14 +++--- client/src/components/common/index.ts | 71 +++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 client/src/components/common/index.ts diff --git a/client/src/app/post/[id]/page.tsx b/client/src/app/post/[id]/page.tsx index 1401e484..90e80706 100644 --- a/client/src/app/post/[id]/page.tsx +++ b/client/src/app/post/[id]/page.tsx @@ -13,10 +13,15 @@ import useUserStore from '@/stores/userStore'; import useEffectOnce from '@/hooks/useEffectOnce'; +import { + PageTitle, + PostCountInfo, + Screws, + LoadingNotice, + ErrorMessage, + Footer, +} from '@/components/common'; import PostDeleteModal from '@/components/post/PostDeleteModal'; -import PageTitle from '@/components/common/PageTitle'; -import PostCountInfo from '@/components/common/PostCountInfo'; -import Screws from '@/components/common/Screws'; import Comment from '@/components/post/Comment'; import CommentForm from '@/components/post/CommentForm'; import PostContent from '@/components/post/PostContent'; @@ -25,9 +30,6 @@ import PostImage from '@/components/post/PostImage'; import PostProfile from '@/components/post/PostProfile'; import HashTags from '@/components/post/HashTags'; import CommentDeleteModal from '@/components/post/CommentDeleteModal'; -import LoadingNotice from '@/components/common/LoadingNotice'; -import ErrorMessage from '@/components/common/ErrorMessage'; -import Footer from '@/components/common/Footer'; import { CommentDataInfo, PostDataInfo } from '@/types/data'; diff --git a/client/src/components/common/index.ts b/client/src/components/common/index.ts new file mode 100644 index 00000000..9aa0327a --- /dev/null +++ b/client/src/components/common/index.ts @@ -0,0 +1,71 @@ +import CommonButton from './CommonButton'; +import ConnectCheckBox from './ConnectCheckBox'; +import ControlButton from './ControlButton'; +import ErrorMessage from './ErrorMessage'; +import ErrorNotice from './ErrorNotice'; +import Footer from './Footer'; +import FooterLink from './FooterLink'; +import HashTag from './HashTag'; +import Header from './Header'; +import HeaderLink from './HeaderLink'; +import HeaderNav from './HeaderNav'; +import ImageUpload from './ImageUpload'; +import Intro from './Intro'; +import Leaf from './Leaf'; +import LeafForm from './LeafForm'; +import LeafName from './LeafName'; +import LoadingMessage from './LoadingMessage'; +import LoadingNotice from './LoadingNotice'; +import Logo from './Logo'; +import Modal from './Modal'; +import ModalPortal from './ModalPortal'; +import NoImage from './NoImage'; +import PageTitle from './PageTitle'; +import PasswordInput from './PasswordInput'; +import PostCountInfo from './PostCountInfo'; +import Preview from './Preview'; +import ReactQueryProvider from './ReactQueryProvider'; +import Screws from './Screws'; +import SeoHead from './SeoHead'; +import ShareButton from './ShareButton'; +import ShareModal from './ShareModal'; +import TagInput from './TagInput'; +import TextArea from './TextArea'; +import TextInput from './TextInput'; + +export { + CommonButton, + ConnectCheckBox, + ControlButton, + ErrorMessage, + ErrorNotice, + Footer, + FooterLink, + HashTag, + Header, + HeaderLink, + HeaderNav, + ImageUpload, + Intro, + Leaf, + LeafForm, + LeafName, + LoadingMessage, + LoadingNotice, + Logo, + Modal, + ModalPortal, + NoImage, + PageTitle, + PasswordInput, + PostCountInfo, + Preview, + ReactQueryProvider, + Screws, + SeoHead, + ShareButton, + ShareModal, + TagInput, + TextArea, + TextInput, +}; From bdf8eb0bb532bc191e02f68f847dd9616f8f7c41 Mon Sep 17 00:00:00 2001 From: nalsae Date: Tue, 10 Oct 2023 13:22:27 +0900 Subject: [PATCH 03/20] =?UTF-8?q?[FE]=20=F0=9F=92=84=20Style=20:=20RankBoa?= =?UTF-8?q?rd=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EC=9D=98=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=EB=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/board/RankBoard.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/components/board/RankBoard.tsx b/client/src/components/board/RankBoard.tsx index 6491154b..cea0e1a0 100644 --- a/client/src/components/board/RankBoard.tsx +++ b/client/src/components/board/RankBoard.tsx @@ -50,11 +50,11 @@ export default function RankBoard() { variants={MOUNT_ANIMATION_VALUES} initial="initial" animate="animate" - className="pt-[60px] w-[448px] h-[268px] flex flex-col items-center bg-contain bg-center bg-no-repeat bg-[url('/assets/img/bg_board_lg.png')] drop-shadow-[0_4px_4px_rgba(0,0,0,0.25)] scale-100 max-[604px]:w-[313px] max-[604px]:-mb-3 max-[604px]:px-6 max-[604px]:pt-[78px]"> -

+ className="pt-[64px] w-[448px] h-[268px] flex flex-col items-center bg-contain bg-center bg-no-repeat bg-[url('/assets/img/bg_board_lg.png')] drop-shadow-[0_4px_4px_rgba(0,0,0,0.25)] scale-100 max-[604px]:w-[313px] max-[604px]:-mb-12 max-[604px]:px-6 max-[604px]:pt-[78px]"> +

이주의 좋아요 순위

-
+
{boardRank.slice(0, 3).map((board) => { return ( Date: Tue, 10 Oct 2023 13:23:04 +0900 Subject: [PATCH 04/20] =?UTF-8?q?=F0=9F=90=9B=20Fix=20:=20SignupForm=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EC=9D=98=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 인증 상태 초기화되지 않는 오류 --- client/src/components/signup/SignupForm.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/client/src/components/signup/SignupForm.tsx b/client/src/components/signup/SignupForm.tsx index de31cf90..3d1cc805 100644 --- a/client/src/components/signup/SignupForm.tsx +++ b/client/src/components/signup/SignupForm.tsx @@ -8,6 +8,8 @@ import { postCreateUser, sendCodeByEmail } from '@/api/user'; import useSignModalStore from '@/stores/signModalStore'; import useSignStore from '@/stores/signStore'; +import useEffectOnce from '@/hooks/useEffectOnce'; + import SignInput from '../sign/SignInput'; import SignPasswordInput from '../sign/SignPasswordInput'; @@ -28,7 +30,9 @@ export default function SignupForm() { const { changeState, currentState } = useSignModalStore(); const { setCode, getSigninForm, getSignupForm } = useSignStore(); - const successedCode = currentState === 'Successed'; + useEffectOnce(() => { + changeState(''); + }); const handleValidateEmail = () => { changeState('AuthEmailModal'); @@ -66,6 +70,8 @@ export default function SignupForm() { } }; + const successedCode = currentState === 'Successed'; + return (
From 56f6c8def7d33d261d7faa41f85bd5df55251a7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=AC=EB=8F=84=EC=97=B0?= Date: Tue, 10 Oct 2023 22:53:50 +0900 Subject: [PATCH 05/20] =?UTF-8?q?[Refactor]=20=E2=99=BB=EF=B8=8F=20Signin,?= =?UTF-8?q?=20Signup=20Modal=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (#296) --- client/src/app/signin/page.tsx | 24 ++++++++++++------- client/src/app/signup/page.tsx | 16 +++++++------ client/src/components/signin/FailureModal.tsx | 10 ++++---- .../components/signin/FindPasswordModal.tsx | 14 +++++------ .../src/components/signin/SuccessedModal.tsx | 14 +++++------ .../src/components/signup/AuthEmailModal.tsx | 16 ++++++------- client/src/components/signup/FailureModal.tsx | 10 ++++---- client/src/stores/modalStore.ts | 14 +++++++---- 8 files changed, 61 insertions(+), 57 deletions(-) diff --git a/client/src/app/signin/page.tsx b/client/src/app/signin/page.tsx index 469e9651..31c0f33e 100644 --- a/client/src/app/signin/page.tsx +++ b/client/src/app/signin/page.tsx @@ -2,15 +2,23 @@ import { motion } from 'framer-motion'; -import useSignModalStore from '@/stores/signModalStore'; +import useModalStore, { ModalType } from '@/stores/modalStore'; -import SigninIntro from '@/components/signin/SigninIntro'; -import FindPasswordModal from '@/components/signin/FindPasswordModal'; -import SuccessedModal from '@/components/signin/SuccessedModal'; -import FailureModal from '@/components/signin/FailureModal'; +import { + SigninIntro, + FindPasswordModal, + SuccessedModal, + FailureModal, +} from '@/components/signin'; export default function Signin() { - const currentState = useSignModalStore((state) => state.currentState); + const { isOpen, type } = useModalStore(); + + const renderModal = (type: ModalType) => { + if (type === 'FindPasswordModal') return ; + if (type === 'SuccessedModal') return ; + if (type === 'FailureModal') return ; + }; return ( - {currentState === 'FindPasswordModal' && } - {currentState === 'SuccessedModal' && } - {currentState === 'FailureModal' && } + {isOpen && renderModal(type)} ); } diff --git a/client/src/app/signup/page.tsx b/client/src/app/signup/page.tsx index 2504527d..f6838285 100644 --- a/client/src/app/signup/page.tsx +++ b/client/src/app/signup/page.tsx @@ -2,14 +2,17 @@ import { motion } from 'framer-motion'; -import useSignModalStore from '@/stores/signModalStore'; +import useModalStore, { ModalType } from '@/stores/modalStore'; -import AuthEmailModal from '@/components/signup/AuthEmailModal'; -import FailureModal from '@/components/signup/FailureModal'; -import SignupIntro from '@/components/signup/SignupIntro'; +import { AuthEmailModal, FailureModal, SignupIntro } from '@/components/signup'; export default function Signup() { - const currentState = useSignModalStore((state) => state.currentState); + const { isOpen, type } = useModalStore(); + + const renderModal = (type: ModalType) => { + if (type === 'AuthEmailModal') return ; + if (type === 'FailureModal') return ; + }; return ( - {currentState === 'AuthEmailModal' && } - {currentState === 'Not Code' && } + {isOpen && renderModal(type)} ); } diff --git a/client/src/components/signin/FailureModal.tsx b/client/src/components/signin/FailureModal.tsx index e796011c..db3f12c0 100644 --- a/client/src/components/signin/FailureModal.tsx +++ b/client/src/components/signin/FailureModal.tsx @@ -1,14 +1,12 @@ -import useSignModalStore from '@/stores/signModalStore'; +import useModalStore from '@/stores/modalStore'; -import CommonButton from '../common/CommonButton'; -import Modal from '../common/Modal'; -import ModalPortal from '../common/ModalPortal'; +import { CommonButton, Modal, ModalPortal } from '../common'; export default function FailureModal() { - const { changeState } = useSignModalStore(); + const { changeType } = useModalStore(); const handleEmailFailure = () => { - return changeState('FindPasswordModal'); + return changeType('FindPasswordModal'); }; return ( diff --git a/client/src/components/signin/FindPasswordModal.tsx b/client/src/components/signin/FindPasswordModal.tsx index a7be0caa..a0ce494c 100644 --- a/client/src/components/signin/FindPasswordModal.tsx +++ b/client/src/components/signin/FindPasswordModal.tsx @@ -4,12 +4,10 @@ import { useForm } from 'react-hook-form'; import { getUsersEmail, sendTemporaryPasswordByEmail } from '@/api/user'; -import useSignModalStore from '@/stores/signModalStore'; +import useModalStore from '@/stores/modalStore'; -import Modal from '../common/Modal'; -import ModalPortal from '../common/ModalPortal'; -import SignModalInput from '../sign/SignModalInput'; -import CommonButton from '../common/CommonButton'; +import { SignModalInput } from '../sign'; +import { CommonButton, Modal, ModalPortal } from '../common'; import { SignFormValue, UserData } from '@/types/common'; @@ -20,7 +18,7 @@ export default function FindPasswordModal() { formState: { isSubmitting }, } = useForm(); - const { close, changeState } = useSignModalStore(); + const { close, changeType } = useModalStore(); const userEmail = watch('email'); @@ -39,10 +37,10 @@ export default function FindPasswordModal() { (current: UserData) => current.email === userEmail, ); - if (!existEmail) return changeState('FailureModal'); + if (!existEmail) return changeType('FailureModal'); postNewPassword(email); - return changeState('SuccessedModal'); + return changeType('SuccessedModal'); }; return ( diff --git a/client/src/components/signin/SuccessedModal.tsx b/client/src/components/signin/SuccessedModal.tsx index 5d74a68a..c0e95278 100644 --- a/client/src/components/signin/SuccessedModal.tsx +++ b/client/src/components/signin/SuccessedModal.tsx @@ -1,11 +1,9 @@ -import useSignModalStore from '@/stores/signModalStore'; +import useModalStore from '@/stores/modalStore'; -import CommonButton from '../common/CommonButton'; -import Modal from '../common/Modal'; -import ModalPortal from '../common/ModalPortal'; +import { CommonButton, Modal, ModalPortal } from '../common'; export default function SuccessedModal() { - const close = useSignModalStore((state) => state.close); + const { close } = useModalStore(); return ( @@ -24,10 +22,10 @@ export default function SuccessedModal() { + onClose={close}> + 닫기 +
diff --git a/client/src/components/signup/AuthEmailModal.tsx b/client/src/components/signup/AuthEmailModal.tsx index aa31e537..86bae225 100644 --- a/client/src/components/signup/AuthEmailModal.tsx +++ b/client/src/components/signup/AuthEmailModal.tsx @@ -2,13 +2,11 @@ import { useForm } from 'react-hook-form'; -import useSignModalStore from '@/stores/signModalStore'; +import useModalStore from '@/stores/modalStore'; import useSignStore from '@/stores/signStore'; -import Modal from '../common/Modal'; -import ModalPortal from '../common/ModalPortal'; -import SignModalInput from '../sign/SignModalInput'; -import CommonButton from '../common/CommonButton'; +import { SignModalInput } from '../sign'; +import { CommonButton, Modal, ModalPortal } from '../common'; import { SignFormValue } from '@/types/common'; @@ -19,8 +17,8 @@ export default function AuthEmailModal() { formState: { isSubmitting }, } = useForm(); - const { close, changeState } = useSignModalStore(); - const code = useSignStore((state) => state.code); + const { close, changeType } = useModalStore(); + const { code } = useSignStore(); const userCode = watch('code'); @@ -28,10 +26,10 @@ export default function AuthEmailModal() { if (!userCode) return; if (userCode === code) { - return changeState('Successed'); + return close(); } - return changeState('Not Code'); + return changeType('FailureModal'); }; return ( diff --git a/client/src/components/signup/FailureModal.tsx b/client/src/components/signup/FailureModal.tsx index 1fe682f9..35327cc2 100644 --- a/client/src/components/signup/FailureModal.tsx +++ b/client/src/components/signup/FailureModal.tsx @@ -1,14 +1,12 @@ -import useSignModalStore from '@/stores/signModalStore'; +import useModalStore from '@/stores/modalStore'; -import CommonButton from '../common/CommonButton'; -import Modal from '../common/Modal'; -import ModalPortal from '../common/ModalPortal'; +import { CommonButton, Modal, ModalPortal } from '../common'; export default function FailureModal() { - const changeState = useSignModalStore((state) => state.changeState); + const { changeType } = useModalStore(); const handleCodeFailure = () => { - return changeState('AuthEmailModal'); + return changeType('AuthEmailModal'); }; return ( diff --git a/client/src/stores/modalStore.ts b/client/src/stores/modalStore.ts index f2239082..e5734b8e 100644 --- a/client/src/stores/modalStore.ts +++ b/client/src/stores/modalStore.ts @@ -11,9 +11,15 @@ export type GardenType = | 'emptyInventory' | 'share'; -export type ModalType = PostType | GardenType | null; +export type SignType = + | 'FindPasswordModal' + | 'SuccessedModal' + | 'FailureModal' + | 'AuthEmailModal'; -export interface GardenModalState { +export type ModalType = PostType | GardenType | SignType | null; + +export interface ModalState { isOpen: boolean; type: ModalType; @@ -22,7 +28,7 @@ export interface GardenModalState { close: () => void; } -const useGardenModalStore = create((set) => ({ +const useModalStore = create((set) => ({ isOpen: false, type: null, @@ -31,4 +37,4 @@ const useGardenModalStore = create((set) => ({ close: () => set(() => ({ isOpen: false })), })); -export default useGardenModalStore; +export default useModalStore; From 809b2f69fcc5ae4875ee819ac337dc25139d57a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=AC=EB=8F=84=EC=97=B0?= Date: Tue, 10 Oct 2023 22:55:03 +0900 Subject: [PATCH 06/20] =?UTF-8?q?[Refactor]=20=E2=99=BB=EF=B8=8F=20signin.?= =?UTF-8?q?=20signup=20import=20=EB=AC=B8=20=EB=B3=91=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/history/index.ts | 27 ++++++++++++++++++++++++++ client/src/components/profile/index.ts | 15 ++++++++++++++ client/src/components/sign/index.ts | 6 ++++++ client/src/components/signin/index.ts | 15 ++++++++++++++ client/src/components/signup/index.ts | 7 +++++++ 5 files changed, 70 insertions(+) create mode 100644 client/src/components/history/index.ts create mode 100644 client/src/components/profile/index.ts create mode 100644 client/src/components/sign/index.ts create mode 100644 client/src/components/signin/index.ts create mode 100644 client/src/components/signup/index.ts diff --git a/client/src/components/history/index.ts b/client/src/components/history/index.ts new file mode 100644 index 00000000..7c343f68 --- /dev/null +++ b/client/src/components/history/index.ts @@ -0,0 +1,27 @@ +import ConfirmModal from './ConfirmModal'; +import Dropdown from './Dropdown'; +import FailureModal from './FailureModal'; +import HistoryBoard from './HistoryBoard'; +import HistoryBox from './HistoryBox'; +import HistoryComment from './HistoryComment'; +import HistoryLikes from './HistoryLikes'; +import HistoryPostCard from './HistoryPostCard'; +import ResignModal from './ResignModal'; +import SuccessedModal from './SuccessedModal'; +import UserButton from './UserButton'; +import UserInfo from './UserInfo'; + +export { + ConfirmModal, + Dropdown, + FailureModal, + HistoryBoard, + HistoryBox, + HistoryComment, + HistoryLikes, + HistoryPostCard, + ResignModal, + SuccessedModal, + UserButton, + UserInfo, +}; diff --git a/client/src/components/profile/index.ts b/client/src/components/profile/index.ts new file mode 100644 index 00000000..d1203c04 --- /dev/null +++ b/client/src/components/profile/index.ts @@ -0,0 +1,15 @@ +import ChangeNicknameModal from './ChangeNicknameModal'; +import ChangePasswordModal from './ChangePasswordModal'; +import ImageForm from './ImageForm'; +import NicknameForm from './NicknameForm'; +import PasswordForm from './PasswordForm'; +import ProfileBox from './ProfileBox'; + +export { + ChangeNicknameModal, + ChangePasswordModal, + ImageForm, + NicknameForm, + PasswordForm, + ProfileBox, +}; diff --git a/client/src/components/sign/index.ts b/client/src/components/sign/index.ts new file mode 100644 index 00000000..584da0cf --- /dev/null +++ b/client/src/components/sign/index.ts @@ -0,0 +1,6 @@ +import SignInput from './SignInput'; +import SignLink from './SignLink'; +import SignModalInput from './SignModalInput'; +import SignPasswordInput from './SignPasswordInput'; + +export { SignInput, SignLink, SignModalInput, SignPasswordInput }; diff --git a/client/src/components/signin/index.ts b/client/src/components/signin/index.ts new file mode 100644 index 00000000..b6ef6927 --- /dev/null +++ b/client/src/components/signin/index.ts @@ -0,0 +1,15 @@ +import FailureModal from './FailureModal'; +import FindPasswordModal from './FindPasswordModal'; +import LoginButtion from './LoginButton'; +import SigninForm from './SigninForm'; +import SigninIntro from './SigninIntro'; +import SuccessedModal from './SuccessedModal'; + +export { + FailureModal, + FindPasswordModal, + LoginButtion, + SigninForm, + SigninIntro, + SuccessedModal, +}; diff --git a/client/src/components/signup/index.ts b/client/src/components/signup/index.ts new file mode 100644 index 00000000..4e2221a2 --- /dev/null +++ b/client/src/components/signup/index.ts @@ -0,0 +1,7 @@ +import AuthEmailModal from './AuthEmailModal'; +import FailureModal from './FailureModal'; +import SignButton from './SignButton'; +import SignupForm from './SignupForm'; +import SignupIntro from './SignupIntro'; + +export { AuthEmailModal, FailureModal, SignButton, SignupForm, SignupIntro }; From 2322c8038f6aebd5d74290ab85e164e00f83156a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=AC=EB=8F=84=EC=97=B0?= Date: Tue, 10 Oct 2023 22:55:53 +0900 Subject: [PATCH 07/20] =?UTF-8?q?[Refactor]=20=E2=99=BB=EF=B8=8F=20signin,?= =?UTF-8?q?=20signup=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=ED=9B=85=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B9=BC=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (#296) --- client/src/utils/getPasswordByType.ts | 34 +++++++++++++++ client/src/utils/getRegisterByType.ts | 61 +++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 client/src/utils/getPasswordByType.ts create mode 100644 client/src/utils/getRegisterByType.ts diff --git a/client/src/utils/getPasswordByType.ts b/client/src/utils/getPasswordByType.ts new file mode 100644 index 00000000..38aa6478 --- /dev/null +++ b/client/src/utils/getPasswordByType.ts @@ -0,0 +1,34 @@ +import { UseFormWatch } from 'react-hook-form'; + +import { SignFormValue } from '@/types/common'; + +import { SIGN_VAILDATION } from '@/constants/contents'; + +export default function getPasswordByType( + tag: string, + watch?: UseFormWatch, +) { + if (tag === 'password') { + return { + validation: { + required: true, + pattern: { + value: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{6,12}$/, + message: SIGN_VAILDATION[tag], + }, + }, + }; + } + + if (tag === 'passwordCheck' && watch) { + return { + validation: { + required: true, + validate: (value: string) => + value === watch('password') || SIGN_VAILDATION[tag], + }, + }; + } + + return null; +} diff --git a/client/src/utils/getRegisterByType.ts b/client/src/utils/getRegisterByType.ts new file mode 100644 index 00000000..c88696bc --- /dev/null +++ b/client/src/utils/getRegisterByType.ts @@ -0,0 +1,61 @@ +import { SIGN_VAILDATION } from '@/constants/contents'; + +export default function getRegisterByType(type: string) { + if (type === 'email') { + return { + validation: { + required: true, + pattern: { + value: /\S+@\S+\.\S+/, + message: SIGN_VAILDATION[type], + }, + }, + }; + } + + if (type === 'code') { + return { + validation: { + required: true, + pattern: { + value: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8}$/, + message: SIGN_VAILDATION[type], + }, + }, + }; + } + + if (type === 'nickname') { + return { + validation: { + required: true, + pattern: { + value: /^[가-힣a-zA-Z]+$/, + message: SIGN_VAILDATION.nickname, + }, + minLength: { + value: 2, + message: SIGN_VAILDATION.nickname, + }, + maxLength: { + value: 6, + message: '6글자 이하의 영문 또는 한글을 입력해야 합니다.', + }, + }, + }; + } + + if (type === 'password') { + return { + validation: { + required: true, + pattern: { + value: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{6,12}$/, + message: SIGN_VAILDATION[type], + }, + }, + }; + } + + return null; +} From ea7cdf133bb030d2c22d364ee66d3709532349cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=AC=EB=8F=84=EC=97=B0?= Date: Tue, 10 Oct 2023 22:57:15 +0900 Subject: [PATCH 08/20] =?UTF-8?q?[Refactor]=20=E2=99=BB=EF=B8=8F=20?= =?UTF-8?q?=EC=83=81=EC=88=98=EB=AA=85=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20?= =?UTF-8?q?=EB=AC=B8=EC=9E=90=EC=97=B4=20=EC=83=81=EC=88=98=EB=A1=9C=20?= =?UTF-8?q?=EB=B9=BC=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (#296) --- client/src/components/profile/ImageForm.tsx | 3 +- .../src/components/profile/PasswordForm.tsx | 3 +- client/src/components/sign/SignInput.tsx | 58 +++++-------------- client/src/components/sign/SignModalInput.tsx | 53 +++-------------- .../src/components/sign/SignPasswordInput.tsx | 53 +++++------------ client/src/components/signin/SigninForm.tsx | 35 ++++++----- client/src/constants/contents.ts | 10 +++- 7 files changed, 67 insertions(+), 148 deletions(-) diff --git a/client/src/components/profile/ImageForm.tsx b/client/src/components/profile/ImageForm.tsx index 3e1a98a3..aa70d266 100644 --- a/client/src/components/profile/ImageForm.tsx +++ b/client/src/components/profile/ImageForm.tsx @@ -12,6 +12,7 @@ import useClient from '@/hooks/useClient'; import CommonButton from '../common/CommonButton'; import { DefaultProps } from '@/types/common'; +import { ALERT_TEXT } from '@/constants/contents'; export default function ImageForm({ className }: DefaultProps) { const isClient = useClient(); @@ -32,7 +33,7 @@ export default function ImageForm({ className }: DefaultProps) { } } - alert('2mb 이하의 이미지만 등록이 가능합니다.'); + alert(ALERT_TEXT.image); return false; }; diff --git a/client/src/components/profile/PasswordForm.tsx b/client/src/components/profile/PasswordForm.tsx index 1a69d279..a43b9c30 100644 --- a/client/src/components/profile/PasswordForm.tsx +++ b/client/src/components/profile/PasswordForm.tsx @@ -11,6 +11,7 @@ import PasswordInput from '../common/PasswordInput'; import CommonButton from '../common/CommonButton'; import { InputValues } from '@/types/common'; +import { ALERT_TEXT } from '@/constants/contents'; export default function PasswordForm() { const { @@ -31,7 +32,7 @@ export default function PasswordForm() { if (!presentPassword && !changedPassword) return; if (presentPassword === changedPassword) { - alert('기존 비밀번호와 동일합니다. 새로운 비밀번호를 입력해 주세요.'); + alert(ALERT_TEXT.password); return; } diff --git a/client/src/components/sign/SignInput.tsx b/client/src/components/sign/SignInput.tsx index c66d8f88..3feda01e 100644 --- a/client/src/components/sign/SignInput.tsx +++ b/client/src/components/sign/SignInput.tsx @@ -2,14 +2,18 @@ import { UseFormRegister, FieldErrors } from 'react-hook-form'; -import { SIGNIN_REQUIRE, SIGNIN_VAILDATION } from '@/constants/contents'; - import { SignFormValue } from '@/types/common'; +import { SIGN_REQUIRE } from '@/constants/contents'; + +import getRegisterByType from '@/utils/getRegisterByType'; + interface SignInputProps { type: 'email' | 'nickname'; + register: UseFormRegister; errors: FieldErrors; + disabled?: boolean; } @@ -19,62 +23,26 @@ export default function SignInput({ errors, disabled, }: SignInputProps) { - const errorMsg = errors[type]?.message; - - const getRegisterByType = (type: string) => { - if (type === 'email') { - return { - validation: { - required: true, - pattern: { - value: /\S+@\S+\.\S+/, - message: SIGNIN_VAILDATION[type], - }, - }, - }; - } - - if (type === 'nickname') { - return { - validation: { - required: true, - pattern: { - value: /^[가-힣a-zA-Z]+$/, - message: SIGNIN_VAILDATION.nickname, - }, - minLength: { - value: 2, - message: SIGNIN_VAILDATION.nickname, - }, - maxLength: { - value: 6, - message: '6글자 이하의 영문 또는 한글을 입력해야 합니다.', - }, - }, - }; - } - - return null; - }; - const registerFormat = getRegisterByType(type); + const errorMsg = errors[type]?.message; + return ( -
+
-
+

{errorMsg} -

-
+

+ ); } diff --git a/client/src/components/sign/SignModalInput.tsx b/client/src/components/sign/SignModalInput.tsx index 70094331..310ff119 100644 --- a/client/src/components/sign/SignModalInput.tsx +++ b/client/src/components/sign/SignModalInput.tsx @@ -1,11 +1,14 @@ import { UseFormRegister } from 'react-hook-form'; -import { SIGNIN_REQUIRE, SIGNIN_VAILDATION } from '@/constants/contents'; - import { SignFormValue } from '@/types/common'; +import { SIGN_REQUIRE } from '@/constants/contents'; + +import getRegisterByType from '@/utils/getRegisterByType'; + interface SignModalInputProps { type: 'code' | 'email' | 'password'; + register: UseFormRegister; } @@ -13,58 +16,18 @@ export default function SignModalInput({ type, register, }: SignModalInputProps) { - const getRegisterByType = (type: string) => { - if (type === 'code') { - return { - validation: { - required: '올바른 인증번호를 입력해주세요.', - pattern: { - value: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8}$/, - message: SIGNIN_VAILDATION[type], - }, - }, - }; - } - - if (type === 'email') { - return { - validation: { - required: '올바른 이메일을 입력해주세요.', - pattern: { - value: /\S+@\S+\.\S+/, - message: SIGNIN_VAILDATION[type], - }, - }, - }; - } - - if (type === 'password') { - return { - validation: { - required: true, - pattern: { - value: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{6,12}$/, - message: SIGNIN_VAILDATION[type], - }, - }, - }; - } - - return null; - }; - const registerFormat = getRegisterByType(type); return ( - <> +
- +
); } diff --git a/client/src/components/sign/SignPasswordInput.tsx b/client/src/components/sign/SignPasswordInput.tsx index fcdabc0c..f42c9de8 100644 --- a/client/src/components/sign/SignPasswordInput.tsx +++ b/client/src/components/sign/SignPasswordInput.tsx @@ -2,73 +2,48 @@ import { UseFormRegister, FieldErrors, UseFormWatch } from 'react-hook-form'; -import { SIGNIN_REQUIRE, SIGNIN_VAILDATION } from '@/constants/contents'; - import { SignFormValue } from '@/types/common'; +import { SIGN_REQUIRE } from '@/constants/contents'; + +import getPasswordByType from '@/utils/getPasswordByType'; + interface SignPasswordInputProps { tag: 'password' | 'passwordCheck'; + register: UseFormRegister; - errors: FieldErrors; watch: UseFormWatch; + errors: FieldErrors; + disabled?: boolean; } export default function SignPasswordInput({ tag, register, - errors, watch, + errors, disabled, }: SignPasswordInputProps) { - const errorMsg = errors[tag]?.message; - - const getRegisterByType = ( - tag: string, - watch?: UseFormWatch, - ) => { - if (tag === 'password') { - return { - validation: { - required: true, - pattern: { - value: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{6,12}$/, - message: SIGNIN_VAILDATION[tag], - }, - }, - }; - } + const passwordFormat = getPasswordByType(tag, watch); - if (tag === 'passwordCheck' && watch) { - return { - validation: { - required: true, - validate: (value: string) => - value === watch('password') || SIGNIN_VAILDATION[tag], - }, - }; - } - - return null; - }; - - const registerFormat = getRegisterByType(tag, watch); + const errorMsg = errors[tag]?.message; return ( -
+
{errorMsg}
-
+ ); } diff --git a/client/src/components/signin/SigninForm.tsx b/client/src/components/signin/SigninForm.tsx index 4b50f557..1a2286df 100644 --- a/client/src/components/signin/SigninForm.tsx +++ b/client/src/components/signin/SigninForm.tsx @@ -1,23 +1,24 @@ 'use client'; import { useRouter } from 'next/navigation'; - import { SubmitHandler, useForm } from 'react-hook-form'; import { postUserInfo } from '@/api/user'; -import useSignModalStore from '@/stores/signModalStore'; +import useModalStore from '@/stores/modalStore'; import useSignStore from '@/stores/signStore'; import useUserStore from '@/stores/userStore'; -import SignPasswordInput from '../sign/SignPasswordInput'; -import SignInput from '../sign/SignInput'; - -import CommonButton from '../common/CommonButton'; +import { SignPasswordInput, SignInput } from '../sign'; +import { CommonButton } from '../common'; import { SignFormValue } from '@/types/common'; +import { ALERT_TEXT } from '@/constants/contents'; + export default function SigninForm() { + const router = useRouter(); + const { register, handleSubmit, @@ -26,11 +27,9 @@ export default function SigninForm() { reset, } = useForm(); - const router = useRouter(); - - const changeState = useSignModalStore((state) => state.changeState); - const setUser = useUserStore((state) => state.setUser); + const { open, changeType } = useModalStore(); const { getSigninForm, getSignupForm } = useSignStore(); + const { setEmailUser } = useUserStore(); const onLogin: SubmitHandler = async ({ email, @@ -40,14 +39,17 @@ export default function SigninForm() { const response = await postUserInfo(email, password); const userId = String(response.data.accountId); + const accessToken = response.headers.authorization; const refreshToken = response.headers.refresh; + const displayName = decodeURIComponent( response.data.displayName, ).replaceAll('+', ' '); + const profileImageUrl = response.data.profileImageUrl; - setUser({ + setEmailUser({ userId, accessToken, refreshToken, @@ -62,13 +64,13 @@ export default function SigninForm() { router.push('/'); } catch (error) { - alert('로그인에 실패했습니다. 다시 시도해 주세요.'); + alert(ALERT_TEXT.login); console.error(error); } }; return ( -
+
@@ -92,12 +94,15 @@ export default function SigninForm() { type="button" size="md" className="w-[161px] h-[44px]" - onFind={() => changeState('FindPasswordModal')}> + onFind={() => { + changeType('FindPasswordModal'); + open(); + }}> 비밀번호 찾기
-
+ ); } diff --git a/client/src/constants/contents.ts b/client/src/constants/contents.ts index 25a044ca..ee536bd2 100644 --- a/client/src/constants/contents.ts +++ b/client/src/constants/contents.ts @@ -24,7 +24,7 @@ export const CONTROLLER_TITLES = { left: '왼쪽', }; -export const SIGNIN_REQUIRE = { +export const SIGN_REQUIRE = { email: '이메일을 입력해주세요.', nickname: '닉네임을 입력해주세요.', password: '비밀번호를 입력해주세요.', @@ -32,7 +32,7 @@ export const SIGNIN_REQUIRE = { code: '인증 번호를 입력해주세요.', } as const; -export const SIGNIN_VAILDATION = { +export const SIGN_VAILDATION = { email: '올바른 이메일 형식이 아닙니다.', nickname: '2글자 이상의 영문 또는 한글을 사용해야 합니다.', password: '6~12글자의 영문과 숫자를 함께 사용해야 합니다.', @@ -111,3 +111,9 @@ export const FOOTER_LINK = { hanbin: 'https://github.com/hanbinchoi', doyeon: 'https://github.com/shimdokite', }; + +export const ALERT_TEXT = { + login: '로그인에 실패했습니다. 다시 시도해 주세요.', + password: '기존 비밀번호와 동일합니다. 새로운 비밀번호를 입력해 주세요.', + image: '2mb 이하의 이미지만 등록이 가능합니다.', +}; From a9b07d7d63a5e54c2fb735cec7b419293323899f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=AC=EB=8F=84=EC=97=B0?= Date: Tue, 10 Oct 2023 22:58:39 +0900 Subject: [PATCH 09/20] =?UTF-8?q?[Refactor]=20=E2=99=BB=EF=B8=8F=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=EB=AA=85=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20?= =?UTF-8?q?import=20=EB=AC=B8=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (#296) --- client/src/app/page.tsx | 4 +- client/src/components/common/Header.tsx | 11 +- client/src/components/common/HeaderNav.tsx | 10 +- client/src/components/common/SeoHead.tsx | 6 +- client/src/components/sign/SignLink.tsx | 1 + client/src/components/signin/LoginButton.tsx | 29 ++-- client/src/components/signin/SigninIntro.tsx | 13 +- client/src/components/signup/SignButton.tsx | 16 +- client/src/components/signup/SignupForm.tsx | 66 ++++---- client/src/components/signup/SignupIntro.tsx | 13 +- client/src/hooks/useGetMetaData.ts | 167 +++++++++---------- client/src/stores/userStore.ts | 23 +-- 12 files changed, 184 insertions(+), 175 deletions(-) diff --git a/client/src/app/page.tsx b/client/src/app/page.tsx index 7abbcccb..6ad12916 100644 --- a/client/src/app/page.tsx +++ b/client/src/app/page.tsx @@ -18,7 +18,7 @@ import { getScrollTop } from '@/utils/getScrollTop'; export default function Home() { const isClient = useClient(); - const { isLogin, isGoogleLogin } = useUserStore(); + const { isEmailLogin, isGoogleLogin } = useUserStore(); const handleClick = () => { const top = getScrollTop(window.innerWidth); @@ -69,7 +69,7 @@ export default function Home() { ))}
- {!(isLogin || isGoogleLogin) && ( + {!(isEmailLogin || isGoogleLogin) && ( state.getSigninForm); const isClient = useClient(); @@ -72,7 +73,7 @@ export default function Header() {
  • - {isClient && (isLogin || isGoogleLogin) ? ( + {isClient && (isEmailLogin || isGoogleLogin) ? (
  • setIsProfileHover(true)} onMouseLeave={() => setIsProfileHover(false)}> @@ -109,7 +110,7 @@ export default function Header() { width={36} height={36} /> - {isProfileHover && (isLogin || isGoogleLogin) && ( + {isProfileHover && (isEmailLogin || isGoogleLogin) && ( { @@ -56,7 +56,9 @@ export default function HeaderNav({
    정원 @@ -67,7 +69,9 @@ export default function HeaderNav({
    식물 카드 diff --git a/client/src/components/common/SeoHead.tsx b/client/src/components/common/SeoHead.tsx index ceac1e26..10c951bb 100644 --- a/client/src/components/common/SeoHead.tsx +++ b/client/src/components/common/SeoHead.tsx @@ -1,8 +1,8 @@ import { useRouter } from 'next/router'; -import getMetaDate from '@/hooks/useGetMetaData'; +// import getMetaData from '@/hooks/useGetMetaData'; -import { ContextType, PageType, Post } from '@/types/common'; +import { PageType, Post } from '@/types/common'; type MetaDataProps = { post: Post; @@ -11,7 +11,7 @@ type MetaDataProps = { export default function SeoHead({ post, page }: MetaDataProps) { const router = useRouter(); - const metaData = getMetaDate(page, router.query, post); + // const metaData = getMetaData(page, router.query, post); //TODO: 주석에 원하는대로 값이 나오는지 테스트가 필요하다 //TODO: og 이미지는 어떻게 할 것인가? return; diff --git a/client/src/components/sign/SignLink.tsx b/client/src/components/sign/SignLink.tsx index 31dc48d0..6ffccf2b 100644 --- a/client/src/components/sign/SignLink.tsx +++ b/client/src/components/sign/SignLink.tsx @@ -10,6 +10,7 @@ interface SignLinkProps extends DefaultProps { type: 'signin' | 'signup'; route: '/signin' | '/signup'; text: 'signinText' | 'signupText'; + onLinkTo?: () => void; onClick?: React.MouseEventHandler; } diff --git a/client/src/components/signin/LoginButton.tsx b/client/src/components/signin/LoginButton.tsx index 1420cbdf..29539b3d 100644 --- a/client/src/components/signin/LoginButton.tsx +++ b/client/src/components/signin/LoginButton.tsx @@ -1,6 +1,7 @@ 'use client'; import { useRouter } from 'next/navigation'; + import { useEffect } from 'react'; import useUserStore from '@/stores/userStore'; @@ -8,34 +9,32 @@ import useSignStore from '@/stores/signStore'; import useClient from '@/hooks/useClient'; -import CommonButton from '../common/CommonButton'; +import { CommonButton } from '../common'; export default function LoginButtion() { const googleOauth = process.env.NEXT_PUBLIC_GOOGLE_OAUTH_URL; - const isClient = useClient(); const router = useRouter(); - const getSigninForm = useSignStore((state) => state.getSigninForm); - const { isGoogleLogin, setGoogleUser, isLogin } = useUserStore(); + const { isGoogleLogin, setGoogleUser, isEmailLogin } = useUserStore(); + const { getSigninForm } = useSignStore(); - const onGoogleLogin = () => { - router.push(`${googleOauth}`); - }; + const isClient = useClient(); useEffect(() => { const queryString = window?.location?.search; const urlParams = new URLSearchParams(queryString); const userId = String(urlParams.get('accountId')); + const accessToken = `Bearer ${urlParams.get('access_token')}`; const refreshToken = urlParams.get('refresh_token'); - const username = urlParams.get('displayName'); - //! profileIamgeUrl - const profileImageUrl = urlParams.get('profileIamgeUrl'); + const username = urlParams.get('displayName'); const displayName = decodeURIComponent(username as string); + const profileImageUrl = urlParams.get('profileIamgeUrl'); + if ( userId && accessToken && @@ -44,9 +43,9 @@ export default function LoginButtion() { profileImageUrl ) { setGoogleUser({ + userId, accessToken, refreshToken, - userId, displayName, profileImageUrl, }); @@ -55,6 +54,10 @@ export default function LoginButtion() { } }, [isGoogleLogin]); + const onGoogleLogin = () => { + router.push(`${googleOauth}`); + }; + return ( <> {isClient && ( @@ -62,8 +65,8 @@ export default function LoginButtion() { onGoogleLogin()} + disabled={isEmailLogin} className="hover:scale-105 transition-transform"> 구글로 로그인 diff --git a/client/src/components/signin/SigninIntro.tsx b/client/src/components/signin/SigninIntro.tsx index 47ca33ea..8c0a44d9 100644 --- a/client/src/components/signin/SigninIntro.tsx +++ b/client/src/components/signin/SigninIntro.tsx @@ -2,18 +2,15 @@ import useSignStore from '@/stores/signStore'; -import SignLink from '../sign/SignLink'; -import SigninForm from './SigninForm'; -import LoginButtion from './LoginButton'; - -import Logo from '@/components/common/Logo'; -import Screws from '@/components/common/Screws'; +import { SigninForm, LoginButtion } from '.'; +import { SignLink } from '../sign'; +import { Logo, Screws } from '../common'; export default function SigninIntro() { const { isEmailSignin, getSignupForm } = useSignStore(); return ( -
    +
    @@ -28,6 +25,6 @@ export default function SigninIntro() { />
    -
    + ); } diff --git a/client/src/components/signup/SignButton.tsx b/client/src/components/signup/SignButton.tsx index 70e7543f..f7c2a554 100644 --- a/client/src/components/signup/SignButton.tsx +++ b/client/src/components/signup/SignButton.tsx @@ -2,28 +2,28 @@ import useSignStore from '@/stores/signStore'; -import CommonButton from '../common/CommonButton'; +import { CommonButton } from '../common'; export default function SignButton() { const { isEmailSignup, getSignupForm } = useSignStore(); return ( <> -
    +
    -
    +

    나만의 정원을 -

    +

    꾸며 보세요!
    -
    지금 가입하면
    -
    +

    지금 가입하면

    +

    500포인트 증정! -

    +

    -
    + (); - const { changeState, currentState } = useSignModalStore(); + const { changeType, open, isOpen } = useModalStore(); const { setCode, getSigninForm, getSignupForm } = useSignStore(); + const email = watch('email'); + useEffectOnce(() => { - changeState(''); + changeType(null); }); - const handleValidateEmail = () => { - changeState('AuthEmailModal'); + const onValidateEmail = () => { + changeType('AuthEmailModal'); }; - const email = watch('email'); + const sendCodeWithEmail = async (email: string) => { + if (!email || isCode) return; + + try { + const response = await sendCodeByEmail(email); + + open(); + + setCode(response.data.data.authCode); + setIsCode(true); + } catch (error) { + console.error(error); + } + }; const onSignup: SubmitHandler = async ({ email, @@ -59,21 +76,8 @@ export default function SignupForm() { } }; - const sendCodeWithEmail = async (email: string) => { - if (!email) return; - - try { - const response = await sendCodeByEmail(email); - setCode(response.data.data.authCode); - } catch (error) { - console.error(error); - } - }; - - const successedCode = currentState === 'Successed'; - return ( -
    +
    @@ -82,12 +86,12 @@ export default function SignupForm() { type="button" size="sm" onOpen={() => { - handleValidateEmail(); + onValidateEmail(); sendCodeWithEmail(email); }} className="mb-3" - disabled={successedCode}> - {successedCode ? '인증 완료!' : '이메일 인증하기'} + disabled={isOpen}> + {isCode ? '인증 완료!' : '이메일 인증하기'}
    @@ -95,7 +99,7 @@ export default function SignupForm() { type="nickname" register={register} errors={errors} - disabled={!successedCode} + disabled={!isCode} />
    @@ -125,6 +129,6 @@ export default function SignupForm() {
    -
    + ); } diff --git a/client/src/components/signup/SignupIntro.tsx b/client/src/components/signup/SignupIntro.tsx index 078341f3..8d9047fa 100644 --- a/client/src/components/signup/SignupIntro.tsx +++ b/client/src/components/signup/SignupIntro.tsx @@ -1,17 +1,14 @@ import useSignStore from '@/stores/signStore'; -import SignupForm from './SignupForm'; -import SignLink from '../sign/SignLink'; -import SignButton from './SignButton'; - -import Logo from '@/components/common/Logo'; -import Screws from '@/components/common/Screws'; +import { SignupForm, SignButton } from '.'; +import { SignLink } from '../sign'; +import { Logo, Screws } from '../common'; export default function SignupIntro() { const { isEmailSignup, getSigninForm } = useSignStore(); return ( -
    +
    @@ -28,6 +25,6 @@ export default function SignupIntro() { className="mt-6 pb-10" />
    -
    + ); } diff --git a/client/src/hooks/useGetMetaData.ts b/client/src/hooks/useGetMetaData.ts index 8f965551..d07f1729 100644 --- a/client/src/hooks/useGetMetaData.ts +++ b/client/src/hooks/useGetMetaData.ts @@ -1,6 +1,5 @@ import { DOMAIN } from '@/constants/contents'; - -import { ContextType, PageType, Post } from '@/types/common'; +// import { ContextType, PageType, Post } from '@/types/common'; type MetaDataType = { title: string; @@ -9,97 +8,97 @@ type MetaDataType = { image?: string; }; -const getMetaDate = ( - pageType: PageType, - contextType: ContextType, - post: Post, -): MetaDataType => { - const { garden, leaf, leafId, leafs, history, slug } = contextType; +// const getMetaData = ( +// pageType: PageType, +// contextType: ContextType, +// post: Post, +// ): MetaDataType => { +// const { garden, leaf, leafId, leafs, history, slug } = contextType; - const BASIC_THUMBNAIL = '/assets/img/bg_default_post.png'; +// const BASIC_THUMBNAIL = '/assets/img/bg_default_post.png'; - const metaData = { - title: '', - url: '', - description: '', - image: BASIC_THUMBNAIL, - }; +// const metaData = { +// title: '', +// url: '', +// description: '', +// image: BASIC_THUMBNAIL, +// }; - switch (pageType) { - case PageType.Main: - metaData.title = `${DOMAIN.domain_kor} | ${DOMAIN.domain}`; - // 그로우 스토리 | grow-story.vercel.app - metaData.url = `${DOMAIN.domain}`; - // grow-story.vercel.app - metaData.description = `${DOMAIN.domain_kor} 입니다.`; - // 그로우 스토리 입니다. - break; +// switch (pageType) { +// case PageType.Main: +// metaData.title = `${DOMAIN.domain_kor} | ${DOMAIN.domain}`; +// // 그로우 스토리 | grow-story.vercel.app +// metaData.url = `${DOMAIN.domain}`; +// // grow-story.vercel.app +// metaData.description = `${DOMAIN.domain_kor} 입니다.`; +// // 그로우 스토리 입니다. +// break; - case PageType.Signup: - metaData.title = `회원 가입 | ${DOMAIN.domain}`; - // 회원 가입 | grow-story.vercel.app - metaData.url = `${DOMAIN.domain}/signup`; - // grow-story.vercel.app/signup - metaData.description = `지금 그로우 스토리에 가입해 보세요!`; - // 지금 그로우 스토리에 가입해 보세요! - break; +// case PageType.Signup: +// metaData.title = `회원 가입 | ${DOMAIN.domain}`; +// // 회원 가입 | grow-story.vercel.app +// metaData.url = `${DOMAIN.domain}/signup`; +// // grow-story.vercel.app/signup +// metaData.description = `지금 그로우 스토리에 가입해 보세요!`; +// // 지금 그로우 스토리에 가입해 보세요! +// break; - case PageType.Signin: - metaData.title = `로그인 | ${DOMAIN.domain}`; - // 로그인 | grow-story.vercel.app - metaData.url = `${DOMAIN.domain}/signin`; - // grow-story.vercel.app/signin - metaData.description = `그로우 스토리 로그인 페이지 입니다.`; - // 그로우 스토리 로그인 페이지 입니다. - break; +// case PageType.Signin: +// metaData.title = `로그인 | ${DOMAIN.domain}`; +// // 로그인 | grow-story.vercel.app +// metaData.url = `${DOMAIN.domain}/signin`; +// // grow-story.vercel.app/signin +// metaData.description = `그로우 스토리 로그인 페이지 입니다.`; +// // 그로우 스토리 로그인 페이지 입니다. +// break; - case PageType.Garden: - metaData.title = `${garden} 님의 정원 | ${DOMAIN.domain}`; - // OOO 님의 정원 | grow-story.vercel.app - metaData.url = `${DOMAIN.domain}/garden/${slug}`; - // grow-story.vercel.app/garden/1 - metaData.description = `${garden} 님의 정원 입니다.`; - // OOO 님의 정원 입니다. - break; +// case PageType.Garden: +// metaData.title = `${garden} 님의 정원 | ${DOMAIN.domain}`; +// // OOO 님의 정원 | grow-story.vercel.app +// metaData.url = `${DOMAIN.domain}/garden/${slug}`; +// // grow-story.vercel.app/garden/1 +// metaData.description = `${garden} 님의 정원 입니다.`; +// // OOO 님의 정원 입니다. +// break; - case PageType.Board: - metaData.title = `커뮤니티 | ${DOMAIN.domain}`; - // 커뮤니티 | grow-story.vercel.app - metaData.url = `${DOMAIN.domain}/board`; - // grow-story.vercel.app/board - metaData.description = `그로우 스토리에서 다른 사람들과 소통해 보세요!`; - // 그로우 스토리에서 다른 사람들과 소통해 보세요! - break; +// case PageType.Board: +// metaData.title = `커뮤니티 | ${DOMAIN.domain}`; +// // 커뮤니티 | grow-story.vercel.app +// metaData.url = `${DOMAIN.domain}/board`; +// // grow-story.vercel.app/board +// metaData.description = `그로우 스토리에서 다른 사람들과 소통해 보세요!`; +// // 그로우 스토리에서 다른 사람들과 소통해 보세요! +// break; - case PageType.Leaf: - metaData.title = `${leaf} | ${DOMAIN.domain}`; - // 바지리 | grow-story.vercel.app - metaData.url = `${DOMAIN.domain}/leaf/${slug}/${leafId}`; - // grow-story.vercel.app/leaf/1/1 - metaData.description = `${leaf}`; - // 바지리 - break; +// case PageType.Leaf: +// metaData.title = `${leaf} | ${DOMAIN.domain}`; +// // 바지리 | grow-story.vercel.app +// metaData.url = `${DOMAIN.domain}/leaf/${slug}/${leafId}`; +// // grow-story.vercel.app/leaf/1/1 +// metaData.description = `${leaf}`; +// // 바지리 +// break; - case PageType.Leafs: - metaData.title = `${leafs} 님의 식물 카드 목록 | ${DOMAIN.domain}`; - // OOO 님의 식물 카드 목록 | grow-story.vercel.app - metaData.url = `${DOMAIN.domain}/leafs/${slug}`; - // grow-story.vercel.app/leafs/1 - metaData.description = `${leafs} 님의 식물 카드 목록입니다.`; - // OOO 님의 식물 카드 목록입니다. - break; +// case PageType.Leafs: +// metaData.title = `${leafs} 님의 식물 카드 목록 | ${DOMAIN.domain}`; +// // OOO 님의 식물 카드 목록 | grow-story.vercel.app +// metaData.url = `${DOMAIN.domain}/leafs/${slug}`; +// // grow-story.vercel.app/leafs/1 +// metaData.description = `${leafs} 님의 식물 카드 목록입니다.`; +// // OOO 님의 식물 카드 목록입니다. +// break; - case PageType.History: - metaData.title = `${history} 님의 히스토리 | ${DOMAIN.domain}`; - // OOO 님의 히스토리 | grow-story.vercel.app - metaData.url = `${DOMAIN.domain}/history/${slug}`; - // grow-story.vercel.app/history/1 - metaData.description = `${garden} 님의 히스토리 입니다.`; - // OOO 님의 히스토리 입니다. - break; - } +// case PageType.History: +// metaData.title = `${history} 님의 히스토리 | ${DOMAIN.domain}`; +// // OOO 님의 히스토리 | grow-story.vercel.app +// metaData.url = `${DOMAIN.domain}/history/${slug}`; +// // grow-story.vercel.app/history/1 +// metaData.description = `${garden} 님의 히스토리 입니다.`; +// // OOO 님의 히스토리 입니다. +// break; +// } - return metaData; -}; +// return metaData; +// }; -export default getMetaDate; +// export default getMetaData; diff --git a/client/src/stores/userStore.ts b/client/src/stores/userStore.ts index 73e1e5df..c9a59036 100644 --- a/client/src/stores/userStore.ts +++ b/client/src/stores/userStore.ts @@ -6,16 +6,19 @@ const StorageKey = 'user-key'; interface UserInfo { accessToken: string; refreshToken: string; + userId: string; displayName: string; profileImageUrl: string; - isLogin?: boolean; + + isEmailLogin?: boolean; isGoogleLogin?: boolean; } interface User extends UserInfo { setAccessToken: (accessToken: string) => void; - setUser: (userInfo: UserInfo) => void; + setGoogleUser: (userInfo: UserInfo) => void; + setEmailUser: (userInfo: UserInfo) => void; setProfileImageUrl: (profileImageUrl: string) => void; setDisplayName: (displayName: string) => void; @@ -26,7 +29,7 @@ interface User extends UserInfo { const useUserStore = create( persist( (set) => ({ - isLogin: false, + isEmailLogin: false, isGoogleLogin: false, accessToken: '', @@ -40,7 +43,7 @@ const useUserStore = create( set({ accessToken }); }, - setUser: (userInfo: UserInfo) => { + setGoogleUser: (userInfo: UserInfo) => { const { accessToken, refreshToken, @@ -54,12 +57,12 @@ const useUserStore = create( userId, displayName, profileImageUrl, - isLogin: true, - isGoogleLogin: false, + isEmailLogin: false, + isGoogleLogin: true, }); }, - setGoogleUser: (userInfo: UserInfo) => { + setEmailUser: (userInfo: UserInfo) => { const { accessToken, refreshToken, @@ -73,8 +76,8 @@ const useUserStore = create( userId, displayName, profileImageUrl, - isLogin: false, - isGoogleLogin: true, + isEmailLogin: true, + isGoogleLogin: false, }); }, @@ -87,7 +90,7 @@ const useUserStore = create( setClear: () => set({ - isLogin: false, + isEmailLogin: false, isGoogleLogin: false, accessToken: '', From de003a9b699b083a493e0d8227f87bf9f3fa0f02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=AC=EB=8F=84=EC=97=B0?= Date: Fri, 13 Oct 2023 14:42:43 +0900 Subject: [PATCH 10/20] =?UTF-8?q?[FE]=20=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=9D=B4=EB=A9=94=EC=9D=BC=20=EC=9D=B8=EC=A6=9D,=20signin,=20u?= =?UTF-8?q?p=20=EB=A1=9C=EC=A7=81=20hook=EC=9C=BC=EB=A1=9C=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/signin/SigninForm.tsx | 58 +++---------------- client/src/components/signup/SignupForm.tsx | 58 +++---------------- client/src/hooks/useAuthEmail.ts | 28 ++++++++++ client/src/hooks/useSignIn.ts | 62 +++++++++++++++++++++ client/src/hooks/useSignup.ts | 39 +++++++++++++ 5 files changed, 145 insertions(+), 100 deletions(-) create mode 100644 client/src/hooks/useAuthEmail.ts create mode 100644 client/src/hooks/useSignIn.ts create mode 100644 client/src/hooks/useSignup.ts diff --git a/client/src/components/signin/SigninForm.tsx b/client/src/components/signin/SigninForm.tsx index 1a2286df..fd61797c 100644 --- a/client/src/components/signin/SigninForm.tsx +++ b/client/src/components/signin/SigninForm.tsx @@ -1,80 +1,35 @@ 'use client'; -import { useRouter } from 'next/navigation'; -import { SubmitHandler, useForm } from 'react-hook-form'; - -import { postUserInfo } from '@/api/user'; +import { useForm } from 'react-hook-form'; import useModalStore from '@/stores/modalStore'; -import useSignStore from '@/stores/signStore'; -import useUserStore from '@/stores/userStore'; + +import useSignin from '@/hooks/useSignIn'; import { SignPasswordInput, SignInput } from '../sign'; import { CommonButton } from '../common'; import { SignFormValue } from '@/types/common'; -import { ALERT_TEXT } from '@/constants/contents'; - export default function SigninForm() { - const router = useRouter(); - const { register, handleSubmit, formState: { errors, isSubmitting }, watch, - reset, } = useForm(); const { open, changeType } = useModalStore(); - const { getSigninForm, getSignupForm } = useSignStore(); - const { setEmailUser } = useUserStore(); - - const onLogin: SubmitHandler = async ({ - email, - password, - }: SignFormValue) => { - try { - const response = await postUserInfo(email, password); - - const userId = String(response.data.accountId); - - const accessToken = response.headers.authorization; - const refreshToken = response.headers.refresh; - const displayName = decodeURIComponent( - response.data.displayName, - ).replaceAll('+', ' '); - - const profileImageUrl = response.data.profileImageUrl; - - setEmailUser({ - userId, - accessToken, - refreshToken, - displayName, - profileImageUrl, - }); - - getSigninForm(false); - getSignupForm(false); - - reset(); - - router.push('/'); - } catch (error) { - alert(ALERT_TEXT.login); - console.error(error); - } - }; + const { handleLogin } = useSignin(); return (
    -
    +
    + 로그인 + (); - const { changeType, open, isOpen } = useModalStore(); - const { setCode, getSigninForm, getSignupForm } = useSignStore(); + const { changeType, isOpen } = useModalStore(); - const email = watch('email'); + const { sendCodeWithEmail, isCode } = useAuthEmail(); + const { handleSignup } = useSignup(); useEffectOnce(() => { changeType(null); }); + const email = watch('email'); + const onValidateEmail = () => { changeType('AuthEmailModal'); }; - const sendCodeWithEmail = async (email: string) => { - if (!email || isCode) return; - - try { - const response = await sendCodeByEmail(email); - - open(); - - setCode(response.data.data.authCode); - setIsCode(true); - } catch (error) { - console.error(error); - } - }; - - const onSignup: SubmitHandler = async ({ - email, - password, - nickname, - }: SignFormValue) => { - try { - await postCreateUser(email, password, nickname); - - reset(); - - getSigninForm(false); - getSignupForm(false); - - router.push('/signin'); - } catch (error) { - console.error(error); - } - }; - return (
    - +
    diff --git a/client/src/hooks/useAuthEmail.ts b/client/src/hooks/useAuthEmail.ts new file mode 100644 index 00000000..20022fd1 --- /dev/null +++ b/client/src/hooks/useAuthEmail.ts @@ -0,0 +1,28 @@ +import { useState } from 'react'; + +import { sendCodeByEmail } from '@/api/user'; + +import useSignStore from '@/stores/signStore'; + +const useAuthEmail = () => { + const { setCode } = useSignStore(); + const [isCode, setIsCode] = useState(false); + + const sendCodeWithEmail = async (email: string) => { + if (!email || isCode) return; + + try { + const response = await sendCodeByEmail(email); + + open(); + + setCode(response.data.data.authCode); + setIsCode(true); + } catch (error) { + console.error(error); + } + }; + return { sendCodeWithEmail, isCode }; +}; + +export default useAuthEmail; diff --git a/client/src/hooks/useSignIn.ts b/client/src/hooks/useSignIn.ts new file mode 100644 index 00000000..86bfcb1b --- /dev/null +++ b/client/src/hooks/useSignIn.ts @@ -0,0 +1,62 @@ +import { useRouter } from 'next/navigation'; +import { SubmitHandler, useForm } from 'react-hook-form'; + +import { postUserInfo } from '@/api/user'; + +import useSignStore from '@/stores/signStore'; +import useUserStore from '@/stores/userStore'; + +import { SignFormValue } from '@/types/common'; + +import { ALERT_TEXT } from '@/constants/contents'; + +const useSignin = () => { + const router = useRouter(); + + const { reset } = useForm(); + + const { setEmailUser } = useUserStore(); + const { getSigninForm, getSignupForm } = useSignStore(); + + const handleLogin: SubmitHandler = async ({ + email, + password, + }: SignFormValue) => { + try { + const response = await postUserInfo(email, password); + + const userId = String(response.data.accountId); + + const accessToken = response.headers.authorization; + const refreshToken = response.headers.refresh; + + const displayName = decodeURIComponent( + response.data.displayName, + ).replaceAll('+', ' '); + + const profileImageUrl = response.data.profileImageUrl; + + setEmailUser({ + userId, + accessToken, + refreshToken, + displayName, + profileImageUrl, + }); + + getSigninForm(false); + getSignupForm(false); + + reset(); + + router.push('/'); + } catch (error) { + alert(ALERT_TEXT.login); + console.error(error); + } + }; + + return { handleLogin }; +}; + +export default useSignin; diff --git a/client/src/hooks/useSignup.ts b/client/src/hooks/useSignup.ts new file mode 100644 index 00000000..2dd154d0 --- /dev/null +++ b/client/src/hooks/useSignup.ts @@ -0,0 +1,39 @@ +import { useRouter } from 'next/navigation'; +import { SubmitHandler, useForm } from 'react-hook-form'; + +import { postCreateUser } from '@/api/user'; + +import useSignStore from '@/stores/signStore'; + +import { SignFormValue } from '@/types/common'; + +const useSignup = () => { + const router = useRouter(); + + const { reset } = useForm(); + + const { getSigninForm, getSignupForm } = useSignStore(); + + const handleSignup: SubmitHandler = async ({ + email, + password, + nickname, + }: SignFormValue) => { + try { + await postCreateUser(email, password, nickname); + + reset(); + + getSigninForm(false); + getSignupForm(false); + + router.push('/signin'); + } catch (error) { + console.error(error); + } + }; + + return { handleSignup }; +}; + +export default useSignup; From 240249200f3fe51ce3b736cd3518d78d651939d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=AC=EB=8F=84=EC=97=B0?= Date: Fri, 13 Oct 2023 20:46:22 +0900 Subject: [PATCH 11/20] =?UTF-8?q?[FE]=20=E2=99=BB=EF=B8=8F=20Refactor=20:?= =?UTF-8?q?=20import=20=EB=AC=B8=20=EC=A0=95=EB=A6=AC=20=EB=B0=8F=20modal?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/app/history/[id]/page.tsx | 30 +++++++----- .../src/components/history/ConfirmModal.tsx | 19 ++++---- .../src/components/history/FailureModal.tsx | 13 +++-- client/src/components/history/HistoryBox.tsx | 37 ++++++++------- .../components/history/HistoryPostCard.tsx | 26 ++++------ .../src/components/history/SuccessedModal.tsx | 22 ++++----- client/src/components/history/UserButton.tsx | 10 ++-- client/src/components/history/UserInfo.tsx | 47 ++++++++++--------- client/src/stores/modalStore.ts | 4 +- 9 files changed, 105 insertions(+), 103 deletions(-) diff --git a/client/src/app/history/[id]/page.tsx b/client/src/app/history/[id]/page.tsx index 6e864f64..21c5cdaa 100644 --- a/client/src/app/history/[id]/page.tsx +++ b/client/src/app/history/[id]/page.tsx @@ -2,14 +2,16 @@ import { motion } from 'framer-motion'; -import useSignModalStore from '@/stores/signModalStore'; +import useModalStore, { ModalType } from '@/stores/modalStore'; -import ResignModal from '@/components/history/ResignModal'; -import ConfirmModal from '@/components/history/ConfirmModal'; -import SuccessedModal from '@/components/history/SuccessedModal'; -import FailureModal from '@/components/history/FailureModal'; -import HistoryBox from '@/components/history/HistoryBox'; -import Footer from '@/components/common/Footer'; +import { + ResignModal, + ConfirmModal, + SuccessedModal, + FailureModal, + HistoryBox, +} from '@/components/history'; +import { Footer } from '@/components/common'; import { MOUNT_ANIMATION_VALUES } from '@/constants/values'; @@ -18,7 +20,14 @@ interface HistoryProps { } export default function History({ params }: HistoryProps) { - const currentState = useSignModalStore((state) => state.currentState); + const { isOpen, type } = useModalStore(); + + const renderModal = (type: ModalType) => { + if (type === 'ResignModal') return ; + if (type === 'ConfirmModal') return ; + if (type === 'SuccessedModal') return ; + if (type === 'FailureModal') return ; + }; return ( <> @@ -29,10 +38,7 @@ export default function History({ params }: HistoryProps) { className="flex flex-col justify-center items-center h-auto min-h-full pb-[343px] mx-4"> - {currentState === 'ResignModal' && } - {currentState === 'ConfirmModal' && } - {currentState === 'FailureModal' && } - {currentState === 'SuccessedModal' && } + {isOpen && renderModal(type)}
    diff --git a/client/src/components/history/ConfirmModal.tsx b/client/src/components/history/ConfirmModal.tsx index a87efc2e..7a21909d 100644 --- a/client/src/components/history/ConfirmModal.tsx +++ b/client/src/components/history/ConfirmModal.tsx @@ -2,33 +2,31 @@ import { deleteUser } from '@/api/history'; -import useSignModalStore from '@/stores/signModalStore'; -import useUserStore from '@/stores/userStore'; +import useModalStore from '@/stores/modalStore'; -import Modal from '../common/Modal'; -import ModalPortal from '../common/ModalPortal'; -import CommonButton from '../common/CommonButton'; +import { CommonButton, Modal, ModalPortal } from '../common'; export default function ConfirmModal() { - const { close, changeState } = useSignModalStore(); + const { close, changeType } = useModalStore(); const hanldeDeleteUser = async () => { const response = await deleteUser(); if (response === 204) { - return changeState('SuccessedModal'); + return changeType('SuccessedModal'); } }; return ( -
    -
    +
    +
    정말  탈퇴 하시겠습니까?
    +
    +
    -
    +
    ); diff --git a/client/src/components/history/FailureModal.tsx b/client/src/components/history/FailureModal.tsx index 1ec15a1b..6bdc99fa 100644 --- a/client/src/components/history/FailureModal.tsx +++ b/client/src/components/history/FailureModal.tsx @@ -1,18 +1,16 @@ 'use client'; -import useSignModalStore from '@/stores/signModalStore'; +import useModalStore from '@/stores/modalStore'; -import Modal from '../common/Modal'; -import ModalPortal from '../common/ModalPortal'; -import CommonButton from '../common/CommonButton'; +import { CommonButton, Modal, ModalPortal } from '../common'; export default function FailureModal() { - const close = useSignModalStore((state) => state.close); + const { close } = useModalStore(); return ( -
    +

    비밀번호가 @@ -21,7 +19,8 @@ export default function FailureModal() { 일치하지 않습니다.

    -
    +
    +
    state.userId); - - const selectOption = useHistoryStore((state) => state.selectOption); - const isOwner = userId === id; - const isBoardWritten = selectOption === 'boardWritten'; - const isBoardLike = selectOption === 'boardLiked'; - const isComment = selectOption === 'commentWritten'; + const IS_BOARD_WRITTEN = selectOption === 'boardWritten'; + const IS_BOARD_LIKE = selectOption === 'boardLiked'; + const IS_COMMENT = selectOption === 'commentWritten'; return ( <> {isClient && ( -
    +
    {userId !== ADMIN_USER_ID && (
    @@ -55,13 +56,13 @@ export default function HistoryBox({ paramsId }: HistoryProps) { {(userId || paramsId) && (
    - {isBoardWritten && } - {isOwner && isBoardLike && } - {isOwner && isComment && } + {IS_BOARD_WRITTEN && } + {isOwner && IS_BOARD_LIKE && } + {isOwner && IS_COMMENT && }
    )}
    -
    +
    )} ); diff --git a/client/src/components/history/HistoryPostCard.tsx b/client/src/components/history/HistoryPostCard.tsx index a297b0d9..630d2ac5 100644 --- a/client/src/components/history/HistoryPostCard.tsx +++ b/client/src/components/history/HistoryPostCard.tsx @@ -1,5 +1,5 @@ -import { motion } from 'framer-motion'; import Image from 'next/image'; +import { motion } from 'framer-motion'; interface HistoryPostCardProps { imageUrl: string; @@ -20,23 +20,13 @@ export default function HistoryPostCard({ whileTap={{ scale: 0.95 }} className="flex flex-col items-center justify-center w-[200px] h-[175px] rounded-lg border-2 border-brown-50 bg-brown-10 shadow-outer/down">
    - {imageUrl ? ( - post_image - ) : ( - post_image - )} + post_image
    diff --git a/client/src/components/history/SuccessedModal.tsx b/client/src/components/history/SuccessedModal.tsx index a5dc1dec..e92bb807 100644 --- a/client/src/components/history/SuccessedModal.tsx +++ b/client/src/components/history/SuccessedModal.tsx @@ -2,22 +2,19 @@ import { useRouter } from 'next/navigation'; -import useSignModalStore from '@/stores/signModalStore'; import useUserStore from '@/stores/userStore'; +import useModalStore from '@/stores/modalStore'; -import CommonButton from '../common/CommonButton'; -import Modal from '../common/Modal'; -import ModalPortal from '../common/ModalPortal'; +import { CommonButton, Modal, ModalPortal } from '../common'; export default function SuccessedModal() { const router = useRouter(); - const close = useSignModalStore((state) => state.close); - const setClear = useUserStore((state) => state.setClear); + const { setClear } = useUserStore(); + const { close } = useModalStore(); const allCloseData = () => { setClear(); - localStorage.clear(); close(); router.push('/'); @@ -26,7 +23,7 @@ export default function SuccessedModal() { return ( -
    +
    그동안  Grow  @@ -36,16 +33,17 @@ export default function SuccessedModal() { 이용해 주셔서 감사합니다. 다음에 또 놀러 오세요!
    +
    + onClose={allCloseData}> + 닫기 +
    -
    +
    ); diff --git a/client/src/components/history/UserButton.tsx b/client/src/components/history/UserButton.tsx index e43dd67a..e0e0d66b 100644 --- a/client/src/components/history/UserButton.tsx +++ b/client/src/components/history/UserButton.tsx @@ -3,18 +3,19 @@ import { useRouter } from 'next/navigation'; import useUserStore from '@/stores/userStore'; -import useSignModalStore from '@/stores/signModalStore'; +import useModalStore from '@/stores/modalStore'; -import CommonButton from '../common/CommonButton'; +import { CommonButton } from '../common'; export default function UserButton() { const router = useRouter(); const { isGoogleLogin } = useUserStore(); - const changeState = useSignModalStore((state) => state.changeState); + const { changeType, open } = useModalStore(); const handleResignModal = () => { - isGoogleLogin ? changeState('ConfirmModal') : changeState('ResignModal'); + open(); + isGoogleLogin ? changeType('ConfirmModal') : changeType('ResignModal'); }; return ( @@ -26,6 +27,7 @@ export default function UserButton() { onProfile={() => router.push('/profile')}> 정보 수정 + state.userId); - const id = paramsId; - + const { userId } = useUserStore(); const { setHistoryUser, profileImageUrl, displayName, grade, point } = useHistoryStore(); + const isClient = useClient(); + + const id = paramsId; + useEffect(() => { const getHistoryData = async () => { const response = await getUserInfo(id); @@ -38,7 +39,7 @@ export default function UserInfo({ paramsId }: HistoryUserProps) { }, [id]); return ( -
    +
    {isClient && ( profile_img )} -
    + +
    {displayName}

    {grade}

    -
    +
    + {userId === id ? ( -
    +

    {point?.toLocaleString()}

    -
    + ) : ( <>
    router.push(`/garden/${id}`)} - /> + onGoToGarden={() => router.push(`/garden/${id}`)}> + 정원 구경하기 + + router.push(`/leafs/${id}`)} - /> + onGoToLeafs={() => router.push(`/leafs/${id}`)}> + 식물 카드 열람 +
    + + className="w-[203px] h-[52px] text-brown-50 border-brown-50 bg-[url('/assets/img/bg_wood_light.png')] mt-6 cursor-default max-[580px]:mt-3"> + 작성한 게시글 + )} -
    + ); } diff --git a/client/src/stores/modalStore.ts b/client/src/stores/modalStore.ts index e5734b8e..e17824f8 100644 --- a/client/src/stores/modalStore.ts +++ b/client/src/stores/modalStore.ts @@ -17,7 +17,9 @@ export type SignType = | 'FailureModal' | 'AuthEmailModal'; -export type ModalType = PostType | GardenType | SignType | null; +export type HistoryType = 'ResignModal' | 'ConfirmModal'; + +export type ModalType = PostType | GardenType | SignType | HistoryType | null; export interface ModalState { isOpen: boolean; From 7de0be9a0d9bb52b4079eb68b5b8b550a9e8dee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=AC=EB=8F=84=EC=97=B0?= Date: Fri, 13 Oct 2023 20:48:09 +0900 Subject: [PATCH 12/20] =?UTF-8?q?[FE]=20=E2=99=BB=EF=B8=8F=20Refactor=20:?= =?UTF-8?q?=20import=20=EB=AC=B8=20=EC=A0=95=EB=A6=AC=20=EB=B0=8F=20?= =?UTF-8?q?=EB=AC=B4=ED=95=9C=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20hook=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/history/Dropdown.tsx | 14 +++-- .../src/components/history/HistoryBoard.tsx | 46 +++++++--------- .../src/components/history/HistoryComment.tsx | 55 +++++++++---------- .../src/components/history/HistoryLikes.tsx | 34 +++++------- client/src/components/history/ResignModal.tsx | 25 +++++---- client/src/hooks/useHistoryBoard.ts | 29 ++++++++++ client/src/hooks/useHistoryComment.ts | 28 ++++++++++ client/src/hooks/useHistoryLikes.ts | 30 ++++++++++ 8 files changed, 169 insertions(+), 92 deletions(-) create mode 100644 client/src/hooks/useHistoryBoard.ts create mode 100644 client/src/hooks/useHistoryComment.ts create mode 100644 client/src/hooks/useHistoryLikes.ts diff --git a/client/src/components/history/Dropdown.tsx b/client/src/components/history/Dropdown.tsx index ebbb2abc..d1a27045 100644 --- a/client/src/components/history/Dropdown.tsx +++ b/client/src/components/history/Dropdown.tsx @@ -13,8 +13,8 @@ export default function Dropdown() { const { setIsOpen, isOpen, ref } = useDetectClose(false); return ( -
    -
    +
    -
    + + ); } diff --git a/client/src/components/history/HistoryBoard.tsx b/client/src/components/history/HistoryBoard.tsx index 89f0bba0..1d33e9c4 100644 --- a/client/src/components/history/HistoryBoard.tsx +++ b/client/src/components/history/HistoryBoard.tsx @@ -1,40 +1,30 @@ 'use client'; import { useRouter } from 'next/navigation'; -import { useInfiniteQuery } from '@tanstack/react-query'; import InfiniteScroll from 'react-infinite-scroller'; -import { getBoardWrittenByPage } from '@/api/history'; - import useUserStore from '@/stores/userStore'; -import EmptyDiary from '../leaf/EmptyDiary'; -import HistoryPostCard from './HistoryPostCard'; +import useHistoryBoard from '@/hooks/useHistoryBoard'; -import ErrorMessage from '../common/ErrorMessage'; -import LoadingMessage from '../common/LoadingMessage'; +import { HistoryPostCard } from '.'; +import EmptyDiary from '../leaf/EmptyDiary'; +import { ErrorMessage, LoadingMessage } from '../common'; import { HistoryBoradProps } from '@/types/common'; -import useClient from '@/hooks/useClient'; export default function HistoryBoard({ paramsId }: HistoryBoradProps) { const router = useRouter(); - const isClient = useClient(); - const userId = useUserStore((state) => state.userId); - const { data, fetchNextPage, hasNextPage, isLoading, isError } = - useInfiniteQuery( - ['boardWritten'], - ({ pageParam = 1 }) => getBoardWrittenByPage({ pageParam }, paramsId), + const { userId } = useUserStore(); - { - getNextPageParam: (lastPage) => { - return lastPage.pageInfo.page !== lastPage.pageInfo.totalPages - ? lastPage.pageInfo.page + 1 - : undefined; - }, - }, - ); + const { + data: boards, + fetchNextPage, + hasNextPage, + isLoading, + isError, + } = useHistoryBoard(paramsId); const likesAmount = (likes: []) => { if (likes?.length === 0) return 0; @@ -44,10 +34,10 @@ export default function HistoryBoard({ paramsId }: HistoryBoradProps) { return ( <> - {data?.pages.map((page, index) => ( + {boards?.map((page, index) => (
    {page?.boardWritten?.length === 0 ? ( -
    -
    + ) : ( fetchNextPage()}> -
    +
    {page.boardWritten?.map((board: any) => (
    ))} -
    +
    )}
    ))} + {isLoading && (
    )} + {isError && (
    diff --git a/client/src/components/history/HistoryComment.tsx b/client/src/components/history/HistoryComment.tsx index 815087f0..bf8c15a5 100644 --- a/client/src/components/history/HistoryComment.tsx +++ b/client/src/components/history/HistoryComment.tsx @@ -1,44 +1,43 @@ 'use client'; import { useRouter } from 'next/navigation'; -import { useInfiniteQuery } from '@tanstack/react-query'; import InfiniteScroll from 'react-infinite-scroller'; -import { getCommentWrittenByPage } from '@/api/history'; - import useUserStore from '@/stores/userStore'; -import EmptyDiary from '../leaf/EmptyDiary'; -import HistoryPostCard from './HistoryPostCard'; +import useHistoryComment from '@/hooks/useHistoryComment'; -import LoadingMessage from '../common/LoadingMessage'; -import ErrorMessage from '../common/ErrorMessage'; +import { HistoryPostCard } from '.'; +import EmptyDiary from '../leaf/EmptyDiary'; +import { ErrorMessage, LoadingMessage } from '../common'; import { HistoryBoradProps } from '@/types/common'; export default function HistoryComment({ paramsId }: HistoryBoradProps) { const router = useRouter(); - const userId = useUserStore((state) => state.userId); - const { data, fetchNextPage, hasNextPage, isLoading, isError } = - useInfiniteQuery( - ['commentWritten'], - ({ pageParam = 1 }) => getCommentWrittenByPage({ pageParam }, paramsId), - { - getNextPageParam: (lastPage) => { - return lastPage.pageInfo.page !== lastPage.pageInfo.totalPages - ? lastPage.pageInfo.page + 1 - : undefined; - }, - }, - ); + const { userId } = useUserStore(); + + const { + data: comments, + fetchNextPage, + hasNextPage, + isLoading, + isError, + } = useHistoryComment(paramsId); + + const likesAmount = (likes: []) => { + if (likes?.length === 0) return 0; + + return likes.length; + }; return ( <> - {data?.pages.map((page, index) => ( + {comments?.map((page, index) => (
    {page.commentWritten?.length === 0 ? ( -
    -
    + ) : ( fetchNextPage()}> -
    +
    {page.commentWritten?.map((board: any) => (
    ))} -
    +
    )}
    ))} + {isLoading && (
    )} + {isError && (
    diff --git a/client/src/components/history/HistoryLikes.tsx b/client/src/components/history/HistoryLikes.tsx index 4add05fc..a50b70d7 100644 --- a/client/src/components/history/HistoryLikes.tsx +++ b/client/src/components/history/HistoryLikes.tsx @@ -1,39 +1,30 @@ 'use client'; import { useRouter } from 'next/navigation'; -import { useInfiniteQuery } from '@tanstack/react-query'; import InfiniteScroll from 'react-infinite-scroller'; -import { getBoardLikedByPage } from '@/api/history'; +import useHistoryLikes from '@/hooks/useHistoryLikes'; import useUserStore from '@/stores/userStore'; +import { HistoryPostCard } from '.'; import EmptyDiary from '../leaf/EmptyDiary'; -import HistoryPostCard from './HistoryPostCard'; - -import LoadingMessage from '../common/LoadingMessage'; -import ErrorMessage from '../common/ErrorMessage'; +import { ErrorMessage, LoadingMessage } from '../common'; import { HistoryBoradProps } from '@/types/common'; export default function HistoryLikes({ paramsId }: HistoryBoradProps) { const router = useRouter(); - const userId = useUserStore((state) => state.userId); - const { data, fetchNextPage, hasNextPage, isLoading, isError } = - useInfiniteQuery( - ['boardLiked'], - ({ pageParam = 1 }) => getBoardLikedByPage({ pageParam }, paramsId), - { - getNextPageParam: (lastPage) => { - if (lastPage.pageInfo.totalElement === 0) return; + const { userId } = useUserStore(); - if (lastPage.pageInfo.page !== lastPage.pageInfo.totalPages) { - return lastPage.pageInfo.page + 1; - } - }, - }, - ); + const { + data: likes, + fetchNextPage, + hasNextPage, + isLoading, + isError, + } = useHistoryLikes(paramsId); const likesAmount = (likes: []) => { if (likes?.length === 0) return 0; @@ -43,7 +34,7 @@ export default function HistoryLikes({ paramsId }: HistoryBoradProps) { return ( <> - {data?.pages.map((page, index) => ( + {likes?.map((page, index) => (
    {page?.boardLiked?.length === 0 ? (
    )} + {isError && (
    diff --git a/client/src/components/history/ResignModal.tsx b/client/src/components/history/ResignModal.tsx index d9a836f3..57a08190 100644 --- a/client/src/components/history/ResignModal.tsx +++ b/client/src/components/history/ResignModal.tsx @@ -4,13 +4,11 @@ import { useForm } from 'react-hook-form'; import { postUserPassword } from '@/api/history'; -import useSignModalStore from '@/stores/signModalStore'; import useUserStore from '@/stores/userStore'; +import useModalStore from '@/stores/modalStore'; -import Modal from '../common/Modal'; -import ModalPortal from '../common/ModalPortal'; -import SignModalInput from '../sign/SignModalInput'; -import CommonButton from '../common/CommonButton'; +import { SignModalInput } from '../sign'; +import { CommonButton, Modal, ModalPortal } from '../common'; import { SignFormValue } from '@/types/common'; @@ -21,30 +19,33 @@ export default function ResignModal() { formState: { isSubmitting }, } = useForm(); - const setAccessToken = useUserStore((state) => state.setAccessToken); - const { close, changeState } = useSignModalStore(); + const { setAccessToken } = useUserStore(); + const { close, changeType } = useModalStore(); const userPassword = watch('password'); const handlePasswordCheck = async () => { if (!userPassword) return; + // console.log(userPassword); + // 비밀번호 유효성 검사만 맞으면 유저의 비밀번호와 같지 않아도 응답이 200으로 온다 const response = await postUserPassword(userPassword); + // console.log(response); if (response) { return ( - changeState('ConfirmModal'), + changeType('ConfirmModal'), setAccessToken(response.headers?.authorization) ); } - return changeState('FailureModal'); + return changeType('FailureModal'); }; return ( -
    +

    탈퇴하시려면 @@ -59,8 +60,10 @@ export default function ResignModal() {

    + -
    + +
    { + const { data, fetchNextPage, hasNextPage, isLoading, isError } = + useInfiniteQuery( + ['boardWritten'], + ({ pageParam = 1 }) => getBoardWrittenByPage({ pageParam }, paramsId), + + { + getNextPageParam: (lastPage) => { + return lastPage.pageInfo.page !== lastPage.pageInfo.totalPages + ? lastPage.pageInfo.page + 1 + : undefined; + }, + }, + ); + + return { + data: data?.pages, + fetchNextPage, + hasNextPage, + isLoading, + isError, + }; +}; + +export default useHistoryBoard; diff --git a/client/src/hooks/useHistoryComment.ts b/client/src/hooks/useHistoryComment.ts new file mode 100644 index 00000000..9b8911e8 --- /dev/null +++ b/client/src/hooks/useHistoryComment.ts @@ -0,0 +1,28 @@ +import { useInfiniteQuery } from '@tanstack/react-query'; + +import { getCommentWrittenByPage } from '@/api/history'; + +const useHistoryComment = (paramsId: string) => { + const { data, fetchNextPage, hasNextPage, isLoading, isError } = + useInfiniteQuery( + ['commentWritten'], + ({ pageParam = 1 }) => getCommentWrittenByPage({ pageParam }, paramsId), + { + getNextPageParam: (lastPage) => { + return lastPage.pageInfo.page !== lastPage.pageInfo.totalPages + ? lastPage.pageInfo.page + 1 + : undefined; + }, + }, + ); + + return { + data: data?.pages, + fetchNextPage, + hasNextPage, + isLoading, + isError, + }; +}; + +export default useHistoryComment; diff --git a/client/src/hooks/useHistoryLikes.ts b/client/src/hooks/useHistoryLikes.ts new file mode 100644 index 00000000..c02e7acb --- /dev/null +++ b/client/src/hooks/useHistoryLikes.ts @@ -0,0 +1,30 @@ +import { useInfiniteQuery } from '@tanstack/react-query'; + +import { getBoardLikedByPage } from '@/api/history'; + +const useHistoryLikes = (paramsId: string) => { + const { data, fetchNextPage, hasNextPage, isLoading, isError } = + useInfiniteQuery( + ['boardLiked'], + ({ pageParam = 1 }) => getBoardLikedByPage({ pageParam }, paramsId), + { + getNextPageParam: (lastPage) => { + if (lastPage.pageInfo.totalElement === 0) return; + + if (lastPage.pageInfo.page !== lastPage.pageInfo.totalPages) { + return lastPage.pageInfo.page + 1; + } + }, + }, + ); + + return { + data: data?.pages, + fetchNextPage, + hasNextPage, + isLoading, + isError, + }; +}; + +export default useHistoryLikes; From dd8937282e781df8338e5fa614a9663f76d49534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=AC=EB=8F=84=EC=97=B0?= Date: Fri, 13 Oct 2023 20:48:42 +0900 Subject: [PATCH 13/20] =?UTF-8?q?[FE]=20=F0=9F=90=9B=20Fix=20:=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=EA=B0=80=EC=9E=85=20=EC=8B=9C=20=EC=9D=B4=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20=EC=9D=B8=EC=A6=9D=20=EC=9D=B4=EC=8A=88=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/signup/AuthEmailModal.tsx | 4 ++-- client/src/components/signup/SignupForm.tsx | 10 +++++++--- client/src/hooks/useAuthEmail.ts | 8 +------- client/src/hooks/useSignup.ts | 3 ++- client/src/stores/signStore.ts | 4 ++++ 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/client/src/components/signup/AuthEmailModal.tsx b/client/src/components/signup/AuthEmailModal.tsx index 86bae225..a2958f54 100644 --- a/client/src/components/signup/AuthEmailModal.tsx +++ b/client/src/components/signup/AuthEmailModal.tsx @@ -18,7 +18,7 @@ export default function AuthEmailModal() { } = useForm(); const { close, changeType } = useModalStore(); - const { code } = useSignStore(); + const { code, setIsCode } = useSignStore(); const userCode = watch('code'); @@ -26,7 +26,7 @@ export default function AuthEmailModal() { if (!userCode) return; if (userCode === code) { - return close(); + return setIsCode(true), close(); } return changeType('FailureModal'); diff --git a/client/src/components/signup/SignupForm.tsx b/client/src/components/signup/SignupForm.tsx index 99cc6c82..2eebf2e8 100644 --- a/client/src/components/signup/SignupForm.tsx +++ b/client/src/components/signup/SignupForm.tsx @@ -3,6 +3,7 @@ import { useForm } from 'react-hook-form'; import useModalStore from '@/stores/modalStore'; +import useSignStore from '@/stores/signStore'; import useAuthEmail from '@/hooks/useAuthEmail'; import useSignup from '@/hooks/useSignup'; @@ -21,19 +22,22 @@ export default function SignupForm() { watch, } = useForm(); - const { changeType, isOpen } = useModalStore(); + const { changeType, open } = useModalStore(); + const { isCode, setIsCode } = useSignStore(); - const { sendCodeWithEmail, isCode } = useAuthEmail(); + const { sendCodeWithEmail } = useAuthEmail(); const { handleSignup } = useSignup(); useEffectOnce(() => { changeType(null); + setIsCode(false); }); const email = watch('email'); const onValidateEmail = () => { changeType('AuthEmailModal'); + open(); }; return ( @@ -50,7 +54,7 @@ export default function SignupForm() { sendCodeWithEmail(email); }} className="mb-3" - disabled={isOpen}> + disabled={isCode}> {isCode ? '인증 완료!' : '이메일 인증하기'}
    diff --git a/client/src/hooks/useAuthEmail.ts b/client/src/hooks/useAuthEmail.ts index 20022fd1..2e9645db 100644 --- a/client/src/hooks/useAuthEmail.ts +++ b/client/src/hooks/useAuthEmail.ts @@ -1,12 +1,9 @@ -import { useState } from 'react'; - import { sendCodeByEmail } from '@/api/user'; import useSignStore from '@/stores/signStore'; const useAuthEmail = () => { - const { setCode } = useSignStore(); - const [isCode, setIsCode] = useState(false); + const { setCode, isCode } = useSignStore(); const sendCodeWithEmail = async (email: string) => { if (!email || isCode) return; @@ -14,10 +11,7 @@ const useAuthEmail = () => { try { const response = await sendCodeByEmail(email); - open(); - setCode(response.data.data.authCode); - setIsCode(true); } catch (error) { console.error(error); } diff --git a/client/src/hooks/useSignup.ts b/client/src/hooks/useSignup.ts index 2dd154d0..ab23d89c 100644 --- a/client/src/hooks/useSignup.ts +++ b/client/src/hooks/useSignup.ts @@ -12,7 +12,7 @@ const useSignup = () => { const { reset } = useForm(); - const { getSigninForm, getSignupForm } = useSignStore(); + const { getSigninForm, getSignupForm, setIsCode } = useSignStore(); const handleSignup: SubmitHandler = async ({ email, @@ -26,6 +26,7 @@ const useSignup = () => { getSigninForm(false); getSignupForm(false); + setIsCode(false); router.push('/signin'); } catch (error) { diff --git a/client/src/stores/signStore.ts b/client/src/stores/signStore.ts index 270d42f6..7c7673cc 100644 --- a/client/src/stores/signStore.ts +++ b/client/src/stores/signStore.ts @@ -1,12 +1,14 @@ import { create } from 'zustand'; interface SignState { + isCode: boolean; isEmailSignup: boolean; isEmailSignin: boolean; isCorrectPassword: boolean; code: string; + setIsCode: (isCode: boolean) => void; setCode: (code: string) => void; getSignupForm: (isEmailSignup: boolean) => void; @@ -15,12 +17,14 @@ interface SignState { } const useSignStore = create((set) => ({ + isCode: false, isEmailSignup: false, isEmailSignin: false, isCorrectPassword: false, code: '', + setIsCode: (isCode) => set({ isCode }), setCode: (newCode) => set({ code: newCode }), getSigninForm: (isEmailSignin) => set({ isEmailSignin }), From de61c6bd44c48a7eec9d27d9224571a120cd0e7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=AC=EB=8F=84=EC=97=B0?= Date: Fri, 13 Oct 2023 23:30:42 +0900 Subject: [PATCH 14/20] =?UTF-8?q?[FE]=20=F0=9F=90=9B=20Fix=20:=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=ED=83=88=ED=87=B4=20=EC=9D=B4=EC=8A=88=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/history/ResignModal.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/client/src/components/history/ResignModal.tsx b/client/src/components/history/ResignModal.tsx index 57a08190..fb6ea190 100644 --- a/client/src/components/history/ResignModal.tsx +++ b/client/src/components/history/ResignModal.tsx @@ -26,13 +26,10 @@ export default function ResignModal() { const handlePasswordCheck = async () => { if (!userPassword) return; - // console.log(userPassword); - // 비밀번호 유효성 검사만 맞으면 유저의 비밀번호와 같지 않아도 응답이 200으로 온다 const response = await postUserPassword(userPassword); - // console.log(response); - if (response) { + if (response.data.data) { return ( changeType('ConfirmModal'), setAccessToken(response.headers?.authorization) From ab54209abca9a4c387aedcb3c197c7944f5b0ae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=AC=EB=8F=84=EC=97=B0?= Date: Fri, 13 Oct 2023 23:31:22 +0900 Subject: [PATCH 15/20] =?UTF-8?q?[FE]=20=F0=9F=92=84=20Style=20:=20Modal?= =?UTF-8?q?=20text=20=ED=81=AC=EA=B8=B0,=20width=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/history/FailureModal.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/src/components/history/FailureModal.tsx b/client/src/components/history/FailureModal.tsx index 6bdc99fa..2e33f8f6 100644 --- a/client/src/components/history/FailureModal.tsx +++ b/client/src/components/history/FailureModal.tsx @@ -9,13 +9,13 @@ export default function FailureModal() { return ( - -
    -
    -

    + +

    +
    +

    비밀번호가

    -

    +

    일치하지 않습니다.

    @@ -25,7 +25,7 @@ export default function FailureModal() { 닫기 From ced024a1c1df2cb2ac795ecb344203813f7ec315 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=AC=EB=8F=84=EC=97=B0?= Date: Tue, 17 Oct 2023 03:38:53 +0900 Subject: [PATCH 16/20] =?UTF-8?q?[FE]=20=E2=99=BB=EF=B8=8F=20Refactor=20:?= =?UTF-8?q?=20=EB=AA=A8=EB=93=A0=20http=20=EC=9A=94=EC=B2=AD=EC=9D=84=20re?= =?UTF-8?q?act=20query=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/history/ConfirmModal.tsx | 20 +--- client/src/components/history/ResignModal.tsx | 97 ++++++++----------- client/src/components/history/UserInfo.tsx | 39 +++----- client/src/components/profile/ImageForm.tsx | 65 +++---------- .../src/components/profile/NicknameForm.tsx | 31 ++---- .../src/components/profile/PasswordForm.tsx | 34 ++----- .../components/signin/FindPasswordModal.tsx | 96 ++++++++---------- client/src/components/signin/LoginButton.tsx | 44 +-------- client/src/components/signin/SigninForm.tsx | 8 +- client/src/components/signup/SignupForm.tsx | 26 +++-- client/src/hooks/useAuthEmail.ts | 19 ++-- client/src/hooks/useChagnePassword.ts | 48 +++++++++ client/src/hooks/useChangeImage.ts | 73 ++++++++++++++ client/src/hooks/useChangeNickname.ts | 40 ++++++++ client/src/hooks/useDeleteUser.ts | 20 ++++ client/src/hooks/useFindPassword.ts | 41 ++++++++ client/src/hooks/useGoogleLogin.ts | 47 +++++++++ client/src/hooks/useResignCheck.ts | 26 +++++ client/src/hooks/useSignIn.ts | 43 ++++---- client/src/hooks/useSignup.ts | 26 +++-- client/src/hooks/useUserInfo.ts | 22 +++++ 21 files changed, 514 insertions(+), 351 deletions(-) create mode 100644 client/src/hooks/useChagnePassword.ts create mode 100644 client/src/hooks/useChangeImage.ts create mode 100644 client/src/hooks/useChangeNickname.ts create mode 100644 client/src/hooks/useDeleteUser.ts create mode 100644 client/src/hooks/useFindPassword.ts create mode 100644 client/src/hooks/useGoogleLogin.ts create mode 100644 client/src/hooks/useResignCheck.ts create mode 100644 client/src/hooks/useUserInfo.ts diff --git a/client/src/components/history/ConfirmModal.tsx b/client/src/components/history/ConfirmModal.tsx index 7a21909d..0ceeaba1 100644 --- a/client/src/components/history/ConfirmModal.tsx +++ b/client/src/components/history/ConfirmModal.tsx @@ -1,26 +1,16 @@ 'use client'; -import { deleteUser } from '@/api/history'; - -import useModalStore from '@/stores/modalStore'; +import useDeleteUser from '@/hooks/useDeleteUser'; import { CommonButton, Modal, ModalPortal } from '../common'; export default function ConfirmModal() { - const { close, changeType } = useModalStore(); - - const hanldeDeleteUser = async () => { - const response = await deleteUser(); - - if (response === 204) { - return changeType('SuccessedModal'); - } - }; + const { onDeleteUser, close } = useDeleteUser(); return ( -
    +
    정말  탈퇴 @@ -32,7 +22,7 @@ export default function ConfirmModal() { type="button" size="sm" className="py-2 px-4 text-[18px]" - onCheck={hanldeDeleteUser}> + onCheck={() => onDeleteUser()}> 네 @@ -44,7 +34,7 @@ export default function ConfirmModal() { 아니오
    -
    +
    ); diff --git a/client/src/components/history/ResignModal.tsx b/client/src/components/history/ResignModal.tsx index fb6ea190..00f24111 100644 --- a/client/src/components/history/ResignModal.tsx +++ b/client/src/components/history/ResignModal.tsx @@ -2,10 +2,7 @@ import { useForm } from 'react-hook-form'; -import { postUserPassword } from '@/api/history'; - -import useUserStore from '@/stores/userStore'; -import useModalStore from '@/stores/modalStore'; +import useResignCheck from '@/hooks/useResignCheck'; import { SignModalInput } from '../sign'; import { CommonButton, Modal, ModalPortal } from '../common'; @@ -16,69 +13,61 @@ export default function ResignModal() { const { register, watch, - formState: { isSubmitting }, + handleSubmit, + formState: { errors, isSubmitting }, } = useForm(); - const { setAccessToken } = useUserStore(); - const { close, changeType } = useModalStore(); + const { onPasswordCheck, close } = useResignCheck(); const userPassword = watch('password'); - const handlePasswordCheck = async () => { - if (!userPassword) return; - - const response = await postUserPassword(userPassword); - - if (response.data.data) { - return ( - changeType('ConfirmModal'), - setAccessToken(response.headers?.authorization) - ); - } - - return changeType('FailureModal'); - }; - return ( -
    -
    -

    - 탈퇴하시려면 -

    -
    -

    - 가입 시 등록하셨던 + onPasswordCheck(userPassword))} + className="flex flex-col items-center"> +

    +
    +

    + 탈퇴하시려면

    -
    - 비밀번호 - 를 입력해주세요. +
    +

    + 가입 시 등록하셨던 +

    +
    + 비밀번호 + 를 입력해주세요. +
    -
    - - -
    -
    - - 완료 - + +
    - - 취소 - -
    +
    + + 완료 + + + + 취소 + +
    + ); diff --git a/client/src/components/history/UserInfo.tsx b/client/src/components/history/UserInfo.tsx index 9a02448e..5d7724b6 100644 --- a/client/src/components/history/UserInfo.tsx +++ b/client/src/components/history/UserInfo.tsx @@ -1,15 +1,12 @@ 'use client'; -import { useRouter } from 'next/navigation'; import Image from 'next/image'; -import { useEffect } from 'react'; - -import { getUserInfo } from '@/api/history'; +import { useRouter } from 'next/navigation'; import useUserStore from '@/stores/userStore'; -import useHistoryStore from '@/stores/historyStore'; import useClient from '@/hooks/useClient'; +import useUserInfo from '@/hooks/useUserInfo'; import { CommonButton } from '../common'; @@ -18,28 +15,17 @@ interface HistoryUserProps { } export default function UserInfo({ paramsId }: HistoryUserProps) { + const id = paramsId; + const router = useRouter(); const { userId } = useUserStore(); - const { setHistoryUser, profileImageUrl, displayName, grade, point } = - useHistoryStore(); const isClient = useClient(); - - const id = paramsId; - - useEffect(() => { - const getHistoryData = async () => { - const response = await getUserInfo(id); - - setHistoryUser(response.data); - }; - - getHistoryData(); - }, [id]); + const { profileImageUrl, displayName, grade, point } = useUserInfo(id); return ( -
    +
    {isClient && ( -
    {displayName}
    -

    {grade}

    +

    {displayName}

    +

    {grade}

    {userId === id ? (
    - + point icon

    {point?.toLocaleString()}

    @@ -92,6 +83,6 @@ export default function UserInfo({ paramsId }: HistoryUserProps) { )} -
    +
    ); } diff --git a/client/src/components/profile/ImageForm.tsx b/client/src/components/profile/ImageForm.tsx index aa70d266..4a45c350 100644 --- a/client/src/components/profile/ImageForm.tsx +++ b/client/src/components/profile/ImageForm.tsx @@ -1,68 +1,24 @@ 'use client'; -import { useRef, useState } from 'react'; import Image from 'next/image'; -import { updateUserProfileImage } from '@/api/profile'; - -import useUserStore from '@/stores/userStore'; - import useClient from '@/hooks/useClient'; +import useChangeImage from '@/hooks/useChangeImage'; -import CommonButton from '../common/CommonButton'; +import { CommonButton } from '../common'; import { DefaultProps } from '@/types/common'; -import { ALERT_TEXT } from '@/constants/contents'; export default function ImageForm({ className }: DefaultProps) { const isClient = useClient(); + const { + imageUploadRef, + imageUrl, + isDisabled, - const { profileImageUrl, setProfileImageUrl, setAccessToken } = - useUserStore(); - - const [image, setImage] = useState(); - const [imageUrl, setImageUrl] = useState(profileImageUrl); - const [isDisabled, setIsDisabled] = useState(true); - - const imageUploadRef = useRef(null); - - const checkFileSize = (file: File) => { - if (file && file.type.startsWith('image/')) { - if (file.size <= 2 * 1024 * 1024) { - return true; - } - } - - alert(ALERT_TEXT.image); - return false; - }; - - const onImageChange = (event: React.ChangeEvent) => { - const file = event.target.files; - - if (!file) return; - - if (file[0] && checkFileSize(file[0])) { - setImage(file); - - const newFileURL = URL.createObjectURL(file[0]); - setImageUrl(newFileURL); - setIsDisabled(false); - } - }; - - const onImageSubmit = async () => { - if (image && !isDisabled) { - const response = await updateUserProfileImage(image[0]); - - setProfileImageUrl(imageUrl); - setIsDisabled(true); - - if (response.status === 204) { - setAccessToken(response.headers?.authorization); - } - } - }; + onImageChange, + onImageSubmit, + } = useChangeImage(); return ( <> @@ -78,6 +34,7 @@ export default function ImageForm({ className }: DefaultProps) { priority onClick={() => imageUploadRef.current?.click()} /> + + 이미지 등록 +

    2mb 이하의 이미지만 등록이 가능합니다.

    diff --git a/client/src/components/profile/NicknameForm.tsx b/client/src/components/profile/NicknameForm.tsx index c6f10b3d..9ec0e3bc 100644 --- a/client/src/components/profile/NicknameForm.tsx +++ b/client/src/components/profile/NicknameForm.tsx @@ -3,29 +3,22 @@ import { useEffect } from 'react'; import { useForm } from 'react-hook-form'; -import { updateUserNickname } from '@/api/profile'; +import useChangeNickname from '@/hooks/useChangeNickname'; -import useUserStore from '@/stores/userStore'; -import useSignModalStore from '@/stores/signModalStore'; - -import TextInput from '../common/TextInput'; -import CommonButton from '../common/CommonButton'; +import { CommonButton, TextInput } from '../common'; import { InputValues } from '@/types/common'; export default function NicknameForm() { - const { setDisplayName, displayName } = useUserStore(); const { register, handleSubmit, formState: { errors, isSubmitting }, watch, - reset, setValue, } = useForm(); - const changeState = useSignModalStore((state) => state.changeState); - const setAccessToken = useUserStore((state) => state.setAccessToken); + const { updateNickName, displayName } = useChangeNickname(); const nickname = watch('nickname'); @@ -33,23 +26,9 @@ export default function NicknameForm() { setValue('nickname', displayName); }, [displayName]); - const updateNickName = async () => { - if (!nickname) return; - - const response = await updateUserNickname(nickname); - setDisplayName(nickname); - - if (response.status === 204) { - setAccessToken(response.headers?.authorization); - } - - reset(); - changeState('ChangeNicknameModal'); - }; - return (
    updateNickName(nickname))} className="relative w-fit flex justify-center">
    +
    +
    (); - const changeState = useSignModalStore((state) => state.changeState); - const setAccessToken = useUserStore((state) => state.setAccessToken); - const presentPassword = watch('password'); const changedPassword = watch('newPasswordCheck'); - const updatePassword = async () => { - if (!presentPassword && !changedPassword) return; - - if (presentPassword === changedPassword) { - alert(ALERT_TEXT.password); - return; - } - - const response = await updateUserPassword(presentPassword, changedPassword); - - if (response.status === 204) { - setAccessToken(response.headers?.authorization); - } - - reset(); - changeState('ChangePasswordModal'); - }; + const { updatePassword } = useChangePassword( + presentPassword, + changedPassword, + ); return ( (); - const { close, changeType } = useModalStore(); + const { onEmailCheck, close } = useFindPassword(); const userEmail = watch('email'); - const postNewPassword = async (email: string) => { - if (!email) return; - - await sendTemporaryPasswordByEmail(email); - }; - - const handleEmailCheck = async (email: string) => { - if (!userEmail) return; - - const response = await getUsersEmail(); - - const existEmail = response?.data.data.find( - (current: UserData) => current.email === userEmail, - ); - - if (!existEmail) return changeType('FailureModal'); - - postNewPassword(email); - return changeType('SuccessedModal'); - }; - return ( -
    -
    -

    - 가입하셨던 이메일로 -

    -

    - 임시 비밀번호를 - 발송해드립니다. -

    + onEmailCheck(userEmail))} + className="flex flex-col items-center"> +
    +
    +

    + 가입하셨던 이메일로 +

    +

    + 임시 비밀번호를 + 발송해드립니다. +

    +
    + + +
    + +
    + + 완료 + + + + 취소 +
    - -
    -
    - { - handleEmailCheck(userEmail); - }}> - 완료 - - - 취소 - -
    + ); diff --git a/client/src/components/signin/LoginButton.tsx b/client/src/components/signin/LoginButton.tsx index 29539b3d..edd343c4 100644 --- a/client/src/components/signin/LoginButton.tsx +++ b/client/src/components/signin/LoginButton.tsx @@ -2,12 +2,10 @@ import { useRouter } from 'next/navigation'; -import { useEffect } from 'react'; - -import useUserStore from '@/stores/userStore'; import useSignStore from '@/stores/signStore'; import useClient from '@/hooks/useClient'; +import useGoogleLogin from '@/hooks/useGoogleLogin'; import { CommonButton } from '../common'; @@ -16,46 +14,14 @@ export default function LoginButtion() { const router = useRouter(); - const { isGoogleLogin, setGoogleUser, isEmailLogin } = useUserStore(); const { getSigninForm } = useSignStore(); const isClient = useClient(); + const { isGoogleLogin, isEmailLogin, onGoogleLogin } = useGoogleLogin(); - useEffect(() => { - const queryString = window?.location?.search; - const urlParams = new URLSearchParams(queryString); - - const userId = String(urlParams.get('accountId')); - - const accessToken = `Bearer ${urlParams.get('access_token')}`; - const refreshToken = urlParams.get('refresh_token'); - - const username = urlParams.get('displayName'); - const displayName = decodeURIComponent(username as string); - - const profileImageUrl = urlParams.get('profileIamgeUrl'); - - if ( - userId && - accessToken && - refreshToken && - displayName && - profileImageUrl - ) { - setGoogleUser({ - userId, - accessToken, - refreshToken, - displayName, - profileImageUrl, - }); - - router.push('/'); - } - }, [isGoogleLogin]); - - const onGoogleLogin = () => { + const goToGoogleLogin = () => { router.push(`${googleOauth}`); + onGoogleLogin; }; return ( @@ -65,7 +31,7 @@ export default function LoginButtion() { onGoogleLogin()} + onGoogle={() => goToGoogleLogin()} disabled={isEmailLogin} className="hover:scale-105 transition-transform"> 구글로 로그인 diff --git a/client/src/components/signin/SigninForm.tsx b/client/src/components/signin/SigninForm.tsx index fd61797c..d8f2ecac 100644 --- a/client/src/components/signin/SigninForm.tsx +++ b/client/src/components/signin/SigninForm.tsx @@ -21,11 +21,14 @@ export default function SigninForm() { const { open, changeType } = useModalStore(); - const { handleLogin } = useSignin(); + const { onSiginIn } = useSignin(); + + const email = watch('email'); + const password = watch('password'); return (
    -
    + onSiginIn({ email, password }))}>
    @@ -37,6 +40,7 @@ export default function SigninForm() { watch={watch} />
    +
    { - changeType(null); - setIsCode(false); - }); + const { onSignup } = useSignup(); const email = watch('email'); + const password = watch('password'); + const nickname = watch('nickname'); const onValidateEmail = () => { + if (!email) return; + changeType('AuthEmailModal'); open(); }; + useEffectOnce(() => { + changeType(null); + setIsCode(false); + }); + return (
    - + + onSignup({ + email, + password, + nickname, + }), + )}>
    +
    { const { setCode, isCode } = useSignStore(); - const sendCodeWithEmail = async (email: string) => { - if (!email || isCode) return; - - try { - const response = await sendCodeByEmail(email); - - setCode(response.data.data.authCode); - } catch (error) { - console.error(error); - } - }; + const { mutate: sendCodeWithEmail } = useMutation({ + mutationFn: (email: string) => sendCodeByEmail(email), + onSuccess(data) { + setCode(data.data.data.authCode); + }, + }); return { sendCodeWithEmail, isCode }; }; diff --git a/client/src/hooks/useChagnePassword.ts b/client/src/hooks/useChagnePassword.ts new file mode 100644 index 00000000..f1f020bf --- /dev/null +++ b/client/src/hooks/useChagnePassword.ts @@ -0,0 +1,48 @@ +import { useForm } from 'react-hook-form'; +import { useMutation } from '@tanstack/react-query'; + +import { updateUserPassword } from '@/api/profile'; + +import useUserStore from '@/stores/userStore'; +import useModalStore from '@/stores/modalStore'; + +import { InputValues } from '@/types/common'; + +import { ALERT_TEXT } from '@/constants/contents'; + +const useChangePassword = ( + presentPassword: string, + changedPassword: string, +) => { + const { reset } = useForm(); + + const { changeType, open } = useModalStore(); + const { setAccessToken } = useUserStore(); + + const { mutate: changePassword } = useMutation({ + mutationFn: () => updateUserPassword(presentPassword, changedPassword), + + onSuccess: (data) => { + setAccessToken(data.headers?.authorization); + + changeType('ChangePasswordModal'); + open(); + }, + }); + + const updatePassword = async () => { + if (!presentPassword && !changedPassword) return; + + if (presentPassword === changedPassword) { + alert(ALERT_TEXT.password); + return; + } + + changePassword(); + reset(); + }; + + return { updatePassword }; +}; + +export default useChangePassword; diff --git a/client/src/hooks/useChangeImage.ts b/client/src/hooks/useChangeImage.ts new file mode 100644 index 00000000..96698635 --- /dev/null +++ b/client/src/hooks/useChangeImage.ts @@ -0,0 +1,73 @@ +import { useRef, useState } from 'react'; +import { useMutation } from '@tanstack/react-query'; + +import { updateUserProfileImage } from '@/api/profile'; + +import useModalStore from '@/stores/modalStore'; +import useUserStore from '@/stores/userStore'; + +import { ALERT_TEXT } from '@/constants/contents'; + +import isValidFileSize from '@/utils/isValidFileSize'; + +const useChangeImage = () => { + const { profileImageUrl, setProfileImageUrl, setAccessToken } = + useUserStore(); + const { changeType, open } = useModalStore(); + + const imageUploadRef = useRef(null); + const [image, setImage] = useState(); + const [imageUrl, setImageUrl] = useState(profileImageUrl); + const [isDisabled, setIsDisabled] = useState(true); + + const { mutate: changeImage } = useMutation({ + mutationFn: (image: FileList) => updateUserProfileImage(image[0]), + + onSuccess: (data) => { + setProfileImageUrl(imageUrl); + setIsDisabled(true); + + changeType('ChangeImageModal'); + open(); + + setAccessToken(data.headers?.authorization); + }, + }); + + const checkFileSize = (file: File) => { + if (isValidFileSize(file)) return true; + + alert(ALERT_TEXT.image); + return false; + }; + + const onImageChange = (event: React.ChangeEvent) => { + const file = event.target.files; + + if (!file) return; + + if (file[0] && checkFileSize(file[0])) { + setImage(file); + + const newFileURL = URL.createObjectURL(file[0]); + setImageUrl(newFileURL); + setIsDisabled(false); + } + }; + + const onImageSubmit = async () => { + if (image && !isDisabled) { + changeImage(image); + } + }; + + return { + imageUploadRef, + imageUrl, + isDisabled, + onImageChange, + onImageSubmit, + }; +}; + +export default useChangeImage; diff --git a/client/src/hooks/useChangeNickname.ts b/client/src/hooks/useChangeNickname.ts new file mode 100644 index 00000000..81dc9194 --- /dev/null +++ b/client/src/hooks/useChangeNickname.ts @@ -0,0 +1,40 @@ +import { useForm } from 'react-hook-form'; +import { useMutation } from '@tanstack/react-query'; + +import { updateUserNickname } from '@/api/profile'; + +import useUserStore from '@/stores/userStore'; +import useModalStore from '@/stores/modalStore'; + +import { InputValues } from '@/types/common'; + +const useChangeNickname = () => { + const { reset } = useForm(); + + const { setAccessToken, setDisplayName, displayName } = useUserStore(); + const { changeType, open } = useModalStore(); + + const { mutate: onChangeNickname } = useMutation({ + mutationFn: (nickname: string) => updateUserNickname(nickname), + + onSuccess: (data, nickname) => { + setDisplayName(nickname); + setAccessToken(data.headers?.authorization); + + reset(); + + changeType('ChangeNicknameModal'); + open(); + }, + }); + + const updateNickName = async (nickname: string) => { + if (!nickname) return; + + onChangeNickname(nickname); + }; + + return { updateNickName, displayName }; +}; + +export default useChangeNickname; diff --git a/client/src/hooks/useDeleteUser.ts b/client/src/hooks/useDeleteUser.ts new file mode 100644 index 00000000..e58345cc --- /dev/null +++ b/client/src/hooks/useDeleteUser.ts @@ -0,0 +1,20 @@ +import { useMutation } from '@tanstack/react-query'; + +import { deleteUser } from '@/api/history'; + +import useModalStore from '@/stores/modalStore'; + +const useDeleteUser = () => { + const { close, changeType } = useModalStore(); + + const { mutate: onDeleteUser } = useMutation({ + mutationFn: () => deleteUser(), + onSuccess: () => { + return changeType('SuccessedModal'); + }, + }); + + return { onDeleteUser, close }; +}; + +export default useDeleteUser; diff --git a/client/src/hooks/useFindPassword.ts b/client/src/hooks/useFindPassword.ts new file mode 100644 index 00000000..adc9c129 --- /dev/null +++ b/client/src/hooks/useFindPassword.ts @@ -0,0 +1,41 @@ +import { useMutation, useQuery } from '@tanstack/react-query'; + +import { getUsersEmail, sendTemporaryPasswordByEmail } from '@/api/user'; + +import useModalStore from '@/stores/modalStore'; + +import { UserData } from '@/types/common'; + +const useFindPassword = () => { + const { close, changeType } = useModalStore(); + + const { data } = useQuery({ + queryKey: ['userEmail'], + queryFn: () => getUsersEmail(), + }); + + const { mutate: temporaryPassword } = useMutation({ + mutationFn: (email: string) => sendTemporaryPasswordByEmail(email), + + onSuccess: () => { + changeType('SuccessedModal'); + }, + }); + + const onEmailCheck = async (userEmail: string) => { + if (!userEmail) return; + + const existEmail = data?.data.data.find( + (current: UserData) => current.email === userEmail, + ); + + if (!existEmail) return changeType('FailureModal'); + + temporaryPassword(userEmail); + return changeType('SuccessedModal'); + }; + + return { onEmailCheck, close }; +}; + +export default useFindPassword; diff --git a/client/src/hooks/useGoogleLogin.ts b/client/src/hooks/useGoogleLogin.ts new file mode 100644 index 00000000..c0516169 --- /dev/null +++ b/client/src/hooks/useGoogleLogin.ts @@ -0,0 +1,47 @@ +import { useEffect } from 'react'; +import { useRouter } from 'next/navigation'; + +import useUserStore from '@/stores/userStore'; + +const useGoogleLogin = () => { + const router = useRouter(); + + const { isGoogleLogin, setGoogleUser, isEmailLogin } = useUserStore(); + + const onGoogleLogin = useEffect(() => { + const queryString = window?.location?.search; + const urlParams = new URLSearchParams(queryString); + + const userId = String(urlParams.get('accountId')); + + const accessToken = `Bearer ${urlParams.get('access_token')}`; + const refreshToken = urlParams.get('refresh_token'); + + const username = urlParams.get('displayName'); + const displayName = decodeURIComponent(username as string); + + const profileImageUrl = urlParams.get('profileIamgeUrl'); + + if ( + userId && + accessToken && + refreshToken && + displayName && + profileImageUrl + ) { + setGoogleUser({ + userId, + accessToken, + refreshToken, + displayName, + profileImageUrl, + }); + + router.push('/'); + } + }, [isGoogleLogin]); + + return { isGoogleLogin, setGoogleUser, isEmailLogin, onGoogleLogin }; +}; + +export default useGoogleLogin; diff --git a/client/src/hooks/useResignCheck.ts b/client/src/hooks/useResignCheck.ts new file mode 100644 index 00000000..3f66a079 --- /dev/null +++ b/client/src/hooks/useResignCheck.ts @@ -0,0 +1,26 @@ +import { useMutation } from '@tanstack/react-query'; + +import { postUserPassword } from '@/api/history'; + +import useModalStore from '@/stores/modalStore'; +import useUserStore from '@/stores/userStore'; + +const useResignCheck = () => { + const { setAccessToken } = useUserStore(); + const { changeType, close } = useModalStore(); + + const { mutate: onPasswordCheck } = useMutation({ + mutationFn: (userPassword: string) => postUserPassword(userPassword), + onSuccess: (data) => { + if (!data.data.data) { + return changeType('FailureModal'); + } + + changeType('ConfirmModal'), setAccessToken(data.headers?.authorization); + }, + }); + + return { onPasswordCheck, close }; +}; + +export default useResignCheck; diff --git a/client/src/hooks/useSignIn.ts b/client/src/hooks/useSignIn.ts index 86bfcb1b..9dbdf079 100644 --- a/client/src/hooks/useSignIn.ts +++ b/client/src/hooks/useSignIn.ts @@ -1,40 +1,40 @@ import { useRouter } from 'next/navigation'; -import { SubmitHandler, useForm } from 'react-hook-form'; +import { useMutation } from '@tanstack/react-query'; +import { useForm } from 'react-hook-form'; import { postUserInfo } from '@/api/user'; import useSignStore from '@/stores/signStore'; import useUserStore from '@/stores/userStore'; -import { SignFormValue } from '@/types/common'; +import { SigninFormValue } from '@/types/common'; import { ALERT_TEXT } from '@/constants/contents'; const useSignin = () => { const router = useRouter(); - const { reset } = useForm(); + const { reset } = useForm(); const { setEmailUser } = useUserStore(); const { getSigninForm, getSignupForm } = useSignStore(); - const handleLogin: SubmitHandler = async ({ - email, - password, - }: SignFormValue) => { - try { - const response = await postUserInfo(email, password); + const { mutate: onSiginIn } = useMutation({ + mutationFn: ({ email, password }: SigninFormValue) => + postUserInfo(email, password), - const userId = String(response.data.accountId); + onSuccess: (data) => { + const userId = String(data.data.accountId); - const accessToken = response.headers.authorization; - const refreshToken = response.headers.refresh; + const accessToken = data.headers.authorization; + const refreshToken = data.headers.refresh; - const displayName = decodeURIComponent( - response.data.displayName, - ).replaceAll('+', ' '); + const displayName = decodeURIComponent(data.data.displayName).replaceAll( + '+', + ' ', + ); - const profileImageUrl = response.data.profileImageUrl; + const profileImageUrl = data.data.profileImageUrl; setEmailUser({ userId, @@ -50,13 +50,14 @@ const useSignin = () => { reset(); router.push('/'); - } catch (error) { + }, + + onError: () => { alert(ALERT_TEXT.login); - console.error(error); - } - }; + }, + }); - return { handleLogin }; + return { onSiginIn }; }; export default useSignin; diff --git a/client/src/hooks/useSignup.ts b/client/src/hooks/useSignup.ts index ab23d89c..e2341a6a 100644 --- a/client/src/hooks/useSignup.ts +++ b/client/src/hooks/useSignup.ts @@ -1,27 +1,25 @@ import { useRouter } from 'next/navigation'; -import { SubmitHandler, useForm } from 'react-hook-form'; +import { useMutation } from '@tanstack/react-query'; +import { useForm } from 'react-hook-form'; import { postCreateUser } from '@/api/user'; import useSignStore from '@/stores/signStore'; -import { SignFormValue } from '@/types/common'; +import { SignupFormValue } from '@/types/common'; const useSignup = () => { const router = useRouter(); - const { reset } = useForm(); + const { reset } = useForm(); const { getSigninForm, getSignupForm, setIsCode } = useSignStore(); - const handleSignup: SubmitHandler = async ({ - email, - password, - nickname, - }: SignFormValue) => { - try { - await postCreateUser(email, password, nickname); + const { mutate: onSignup } = useMutation({ + mutationFn: ({ email, password, nickname }: SignupFormValue) => + postCreateUser(email, password, nickname as string), + onSuccess: () => { reset(); getSigninForm(false); @@ -29,12 +27,10 @@ const useSignup = () => { setIsCode(false); router.push('/signin'); - } catch (error) { - console.error(error); - } - }; + }, + }); - return { handleSignup }; + return { onSignup }; }; export default useSignup; diff --git a/client/src/hooks/useUserInfo.ts b/client/src/hooks/useUserInfo.ts new file mode 100644 index 00000000..b774ec29 --- /dev/null +++ b/client/src/hooks/useUserInfo.ts @@ -0,0 +1,22 @@ +import { useEffect } from 'react'; + +import { useQuery } from '@tanstack/react-query'; + +import { getUserInfo } from '@/api/history'; + +import useHistoryStore from '@/stores/historyStore'; + +const useUserInfo = (id: string) => { + const { setHistoryUser, profileImageUrl, displayName, grade, point } = + useHistoryStore(); + + const { data } = useQuery(['userEmail', id], () => getUserInfo(id)); + + useEffect(() => { + if (data) return setHistoryUser(data.data); + }, [data]); + + return { profileImageUrl, displayName, grade, point }; +}; + +export default useUserInfo; From ff0254c3223ea8fc3763a6a582e5d2b79fe165ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=AC=EB=8F=84=EC=97=B0?= Date: Tue, 17 Oct 2023 03:44:10 +0900 Subject: [PATCH 17/20] =?UTF-8?q?[FE]=20=E2=99=BB=EF=B8=8F=20Refactor=20:?= =?UTF-8?q?=20Modal=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F?= =?UTF-8?q?=20=ED=83=9C=EA=B7=B8,=20=EC=84=A0=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/app/profile/page.tsx | 43 ++++++----- .../src/components/history/FailureModal.tsx | 4 +- .../src/components/history/SuccessedModal.tsx | 4 +- .../profile/ChangePasswordModal.tsx | 34 --------- ...cknameModal.tsx => ChangeProfileModal.tsx} | 31 ++++---- client/src/components/profile/ProfileBox.tsx | 20 +++--- client/src/components/profile/index.ts | 6 +- client/src/components/sign/SignModalInput.tsx | 10 ++- client/src/components/signin/FailureModal.tsx | 4 +- .../src/components/signin/SuccessedModal.tsx | 1 + .../src/components/signup/AuthEmailModal.tsx | 72 ++++++++++--------- client/src/components/signup/FailureModal.tsx | 5 +- client/src/stores/modalStore.ts | 21 +++++- client/src/utils/getRegisterByType.ts | 2 +- 14 files changed, 134 insertions(+), 123 deletions(-) delete mode 100644 client/src/components/profile/ChangePasswordModal.tsx rename client/src/components/profile/{ChangeNicknameModal.tsx => ChangeProfileModal.tsx} (53%) diff --git a/client/src/app/profile/page.tsx b/client/src/app/profile/page.tsx index e75eede6..89968a54 100644 --- a/client/src/app/profile/page.tsx +++ b/client/src/app/profile/page.tsx @@ -4,24 +4,38 @@ import { notFound } from 'next/navigation'; import { motion } from 'framer-motion'; -import useSignModalStore from '@/stores/signModalStore'; import useUserStore from '@/stores/userStore'; +import useModalStore, { ModalType } from '@/stores/modalStore'; -import ProfileBox from '@/components/profile/ProfileBox'; -import ChangePasswordModal from '@/components/profile/ChangePasswordModal'; -import ChangeNicknameModal from '@/components/profile/ChangeNicknameModal'; -import ConfirmModal from '@/components/history/ConfirmModal'; -import ResignModal from '@/components/history/ResignModal'; -import FailureModal from '@/components/history/FailureModal'; -import SuccessedModal from '@/components/history/SuccessedModal'; -import Footer from '@/components/common/Footer'; +import useEffectOnce from '@/hooks/useEffectOnce'; + +import { ProfileBox, ChangeProfileModal } from '@/components/profile'; +import { + ResignModal, + ConfirmModal, + SuccessedModal, + FailureModal, +} from '@/components/history'; +import { Footer } from '@/components/common'; import { ADMIN_USER_ID, MOUNT_ANIMATION_VALUES } from '@/constants/values'; -import useEffectOnce from '@/hooks/useEffectOnce'; export default function Profile() { - const currentState = useSignModalStore((state) => state.currentState); const { userId } = useUserStore(); + const { isOpen, type } = useModalStore(); + + const renderModal = (type: ModalType) => { + if (type === 'ChangePasswordModal') + return ; + if (type === 'ChangeNicknameModal') + return ; + if (type === 'ChangeImageModal') return ; + + if (type === 'ResignModal') return ; + if (type === 'ConfirmModal') return ; + if (type === 'SuccessedModal') return ; + if (type === 'FailureModal') return ; + }; useEffectOnce(() => { userId === ADMIN_USER_ID && notFound(); @@ -36,12 +50,7 @@ export default function Profile() { className="flex flex-col justify-center items-center h-auto min-h-full pb-[343px] mx-4"> - {currentState === 'ChangePasswordModal' && } - {currentState === 'ChangeNicknameModal' && } - {currentState === 'ConfirmModal' && } - {currentState === 'ResignModal' && } - {currentState === 'FailureModal' && } - {currentState === 'SuccessedModal' && } + {isOpen && renderModal(type)}
    diff --git a/client/src/components/history/FailureModal.tsx b/client/src/components/history/FailureModal.tsx index 2e33f8f6..f4417427 100644 --- a/client/src/components/history/FailureModal.tsx +++ b/client/src/components/history/FailureModal.tsx @@ -10,7 +10,7 @@ export default function FailureModal() { return ( -
    +

    비밀번호가 @@ -19,7 +19,7 @@ export default function FailureModal() { 일치하지 않습니다.

    -
    +
    -
    +
    그동안  Grow  @@ -43,7 +43,7 @@ export default function SuccessedModal() { 닫기
    -
    +
    ); diff --git a/client/src/components/profile/ChangePasswordModal.tsx b/client/src/components/profile/ChangePasswordModal.tsx deleted file mode 100644 index fbff2fae..00000000 --- a/client/src/components/profile/ChangePasswordModal.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import useSignModalStore from '@/stores/signModalStore'; - -import CommonButton from '../common/CommonButton'; -import Modal from '../common/Modal'; -import ModalPortal from '../common/ModalPortal'; - -export default function ChangePasswordModal() { - const close = useSignModalStore((state) => state.close); - - return ( - - -
    -
    -

    - 비밀번호가 -
    {' '} - 변경되었습니다! -

    -
    -
    - -
    -
    -
    -
    - ); -} diff --git a/client/src/components/profile/ChangeNicknameModal.tsx b/client/src/components/profile/ChangeProfileModal.tsx similarity index 53% rename from client/src/components/profile/ChangeNicknameModal.tsx rename to client/src/components/profile/ChangeProfileModal.tsx index 84a0dbee..2e90e4f7 100644 --- a/client/src/components/profile/ChangeNicknameModal.tsx +++ b/client/src/components/profile/ChangeProfileModal.tsx @@ -1,33 +1,38 @@ -import useSignModalStore from '@/stores/signModalStore'; +import useModalStore from '@/stores/modalStore'; -import CommonButton from '../common/CommonButton'; -import Modal from '../common/Modal'; -import ModalPortal from '../common/ModalPortal'; +import { CommonButton, Modal, ModalPortal } from '../common'; -export default function ChangeNicknameModal() { - const close = useSignModalStore((state) => state.close); +import { PROFILE_MODAL_TEXT } from '@/constants/contents'; + +interface ChangeProfileModalProps { + type: 'password' | 'nickname' | 'image'; +} + +export default function ChangeProfileModal({ type }: ChangeProfileModalProps) { + const { close } = useModalStore(); return ( -
    +

    - 닉네임이 -
    {' '} + {PROFILE_MODAL_TEXT[type]}  +
    변경되었습니다!

    +
    + onClose={close}> + 닫기 +
    -
    +
    ); diff --git a/client/src/components/profile/ProfileBox.tsx b/client/src/components/profile/ProfileBox.tsx index ad20c9eb..01a92247 100644 --- a/client/src/components/profile/ProfileBox.tsx +++ b/client/src/components/profile/ProfileBox.tsx @@ -1,33 +1,33 @@ 'use client'; import useUserStore from '@/stores/userStore'; -import useSignModalStore from '@/stores/signModalStore'; +import useModalStore from '@/stores/modalStore'; -import ImageForm from './ImageForm'; -import NicknameForm from './NicknameForm'; -import PasswordForm from './PasswordForm'; +import { ImageForm, NicknameForm, PasswordForm } from '.'; -import Screws from '../common/Screws'; -import CommonButton from '../common/CommonButton'; +import { Screws, CommonButton } from '../common'; import { ADMIN_USER_ID } from '@/constants/values'; export default function ProfileBox() { const { userId, isGoogleLogin } = useUserStore(); - const changeState = useSignModalStore((state) => state.changeState); + const { changeType, open } = useModalStore(); const handleResignModal = () => { - isGoogleLogin ? changeState('ConfirmModal') : changeState('ResignModal'); + isGoogleLogin ? changeType('ConfirmModal') : changeType('ResignModal'); + open(); }; return ( -
    +
    + {!isGoogleLogin && } + {userId !== ADMIN_USER_ID && ( )}
    -
    + ); } diff --git a/client/src/components/profile/index.ts b/client/src/components/profile/index.ts index d1203c04..a40c41fb 100644 --- a/client/src/components/profile/index.ts +++ b/client/src/components/profile/index.ts @@ -1,13 +1,11 @@ -import ChangeNicknameModal from './ChangeNicknameModal'; -import ChangePasswordModal from './ChangePasswordModal'; +import ChangeProfileModal from './ChangeProfileModal'; import ImageForm from './ImageForm'; import NicknameForm from './NicknameForm'; import PasswordForm from './PasswordForm'; import ProfileBox from './ProfileBox'; export { - ChangeNicknameModal, - ChangePasswordModal, + ChangeProfileModal, ImageForm, NicknameForm, PasswordForm, diff --git a/client/src/components/sign/SignModalInput.tsx b/client/src/components/sign/SignModalInput.tsx index 310ff119..e9e518e0 100644 --- a/client/src/components/sign/SignModalInput.tsx +++ b/client/src/components/sign/SignModalInput.tsx @@ -1,4 +1,4 @@ -import { UseFormRegister } from 'react-hook-form'; +import { FieldErrors, UseFormRegister } from 'react-hook-form'; import { SignFormValue } from '@/types/common'; @@ -10,14 +10,18 @@ interface SignModalInputProps { type: 'code' | 'email' | 'password'; register: UseFormRegister; + errors: FieldErrors; } export default function SignModalInput({ type, register, + errors, }: SignModalInputProps) { const registerFormat = getRegisterByType(type); + const errorMsg = errors[type]?.message; + return (
    + +

    + {errorMsg} +

    ); } diff --git a/client/src/components/signin/FailureModal.tsx b/client/src/components/signin/FailureModal.tsx index db3f12c0..8530bea9 100644 --- a/client/src/components/signin/FailureModal.tsx +++ b/client/src/components/signin/FailureModal.tsx @@ -15,12 +15,14 @@ export default function FailureModal() {

    - 등록되지 않은 이메일입니다. + 등록되지 않은  +  이메일입니다.

    다시 입력해주세요.

    +
    비밀번호를 변경해주세요.

    +
    (); const { close, changeType } = useModalStore(); @@ -35,40 +36,45 @@ export default function AuthEmailModal() { return ( -
    -
    -

    - 이메일로 전송된 -

    -

    - 인증 번호를 입력해주세요. -

    -

    - 메일이 도착하지 않았다면 -
    - 스팸 메일함을 확인해주세요! -

    + +
    +
    +

    + 이메일로 전송된 +

    +

    + 인증 번호를 입력해주세요. +

    +

    + 메일이 도착하지 않았다면 +
    + 스팸 메일함을 확인해주세요! +

    +
    + +
    - -
    -
    - - 완료 - - - 취소 - -
    +
    + + 완료 + + + + 취소 + +
    + ); diff --git a/client/src/components/signup/FailureModal.tsx b/client/src/components/signup/FailureModal.tsx index 35327cc2..974092a3 100644 --- a/client/src/components/signup/FailureModal.tsx +++ b/client/src/components/signup/FailureModal.tsx @@ -15,13 +15,14 @@ export default function FailureModal() {

    - 인증에 - 실패했습니다. + 인증에  + 실패했습니다.

    다시 입력해주세요.

    +
    Date: Tue, 17 Oct 2023 03:45:41 +0900 Subject: [PATCH 18/20] =?UTF-8?q?[FE]=20=E2=99=BB=EF=B8=8F=20Refactor=20:?= =?UTF-8?q?=20=ED=83=9C=EA=B7=B8=20=EB=B0=8F=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/common/HeaderNav.tsx | 1 - client/src/components/history/Dropdown.tsx | 15 +++++++++++++-- client/src/components/sign/SignInput.tsx | 4 ++-- client/src/components/sign/SignLink.tsx | 4 ++-- client/src/components/sign/SignPasswordInput.tsx | 6 ++---- client/src/components/signin/SigninIntro.tsx | 4 ++-- client/src/components/signup/SignupIntro.tsx | 4 ++-- client/src/constants/contents.ts | 9 ++++++++- client/src/stores/signStore.ts | 4 ---- client/src/stores/userStore.ts | 2 ++ client/src/types/common.d.ts | 13 ++++++++++++- 11 files changed, 45 insertions(+), 21 deletions(-) diff --git a/client/src/components/common/HeaderNav.tsx b/client/src/components/common/HeaderNav.tsx index 61d56e38..08e17e7a 100644 --- a/client/src/components/common/HeaderNav.tsx +++ b/client/src/components/common/HeaderNav.tsx @@ -21,7 +21,6 @@ export default function HeaderNav({ const { getSigninForm, getSignupForm } = useSignStore(); const logout = () => { - localStorage.clear(); setClear(); getSigninForm(false); diff --git a/client/src/components/history/Dropdown.tsx b/client/src/components/history/Dropdown.tsx index d1a27045..691f8f0a 100644 --- a/client/src/components/history/Dropdown.tsx +++ b/client/src/components/history/Dropdown.tsx @@ -1,5 +1,6 @@ 'use client'; +import Image from 'next/image'; import { Collapse } from '@material-tailwind/react'; import useHistoryStore from '@/stores/historyStore'; @@ -31,9 +32,19 @@ export default function Dropdown() {
    {isOpen ? ( - + up button ) : ( - + down button )}
    diff --git a/client/src/components/sign/SignInput.tsx b/client/src/components/sign/SignInput.tsx index 3feda01e..ce447bd6 100644 --- a/client/src/components/sign/SignInput.tsx +++ b/client/src/components/sign/SignInput.tsx @@ -28,7 +28,7 @@ export default function SignInput({ const errorMsg = errors[type]?.message; return ( -
    +
    {errorMsg}

    -
    +
    ); } diff --git a/client/src/components/sign/SignLink.tsx b/client/src/components/sign/SignLink.tsx index 6ffccf2b..121b3995 100644 --- a/client/src/components/sign/SignLink.tsx +++ b/client/src/components/sign/SignLink.tsx @@ -26,9 +26,9 @@ export default function SignLink({
    {SIGN_LINK_TO[text]} -
    +

     {SIGN_LINK_TO[type]} -

    +

    ); diff --git a/client/src/components/sign/SignPasswordInput.tsx b/client/src/components/sign/SignPasswordInput.tsx index f42c9de8..4aef0a6c 100644 --- a/client/src/components/sign/SignPasswordInput.tsx +++ b/client/src/components/sign/SignPasswordInput.tsx @@ -10,11 +10,9 @@ import getPasswordByType from '@/utils/getPasswordByType'; interface SignPasswordInputProps { tag: 'password' | 'passwordCheck'; - register: UseFormRegister; watch: UseFormWatch; errors: FieldErrors; - disabled?: boolean; } @@ -30,7 +28,7 @@ export default function SignPasswordInput({ const errorMsg = errors[tag]?.message; return ( -
    +
    {errorMsg}
    -
    +
    ); } diff --git a/client/src/components/signin/SigninIntro.tsx b/client/src/components/signin/SigninIntro.tsx index 8c0a44d9..fa58beb7 100644 --- a/client/src/components/signin/SigninIntro.tsx +++ b/client/src/components/signin/SigninIntro.tsx @@ -10,7 +10,7 @@ export default function SigninIntro() { const { isEmailSignin, getSignupForm } = useSignStore(); return ( -
    +
    @@ -25,6 +25,6 @@ export default function SigninIntro() { />
    -
    + ); } diff --git a/client/src/components/signup/SignupIntro.tsx b/client/src/components/signup/SignupIntro.tsx index 8d9047fa..62f5abc7 100644 --- a/client/src/components/signup/SignupIntro.tsx +++ b/client/src/components/signup/SignupIntro.tsx @@ -8,7 +8,7 @@ export default function SignupIntro() { const { isEmailSignup, getSigninForm } = useSignStore(); return ( -
    +
    @@ -25,6 +25,6 @@ export default function SignupIntro() { className="mt-6 pb-10" />
    -
    + ); } diff --git a/client/src/constants/contents.ts b/client/src/constants/contents.ts index ee536bd2..795f7e03 100644 --- a/client/src/constants/contents.ts +++ b/client/src/constants/contents.ts @@ -35,6 +35,7 @@ export const SIGN_REQUIRE = { export const SIGN_VAILDATION = { email: '올바른 이메일 형식이 아닙니다.', nickname: '2글자 이상의 영문 또는 한글을 사용해야 합니다.', + maxLength: '6글자 이하의 영문 또는 한글을 입력해야 합니다.', password: '6~12글자의 영문과 숫자를 함께 사용해야 합니다.', passwordCheck: '비밀번호가 일치하지 않습니다.', code: '올바른 인증 번호 형식이 아닙니다.', @@ -110,10 +111,16 @@ export const FOOTER_LINK = { seungtae: 'https://github.com/NtoZero', hanbin: 'https://github.com/hanbinchoi', doyeon: 'https://github.com/shimdokite', -}; +} as const; export const ALERT_TEXT = { login: '로그인에 실패했습니다. 다시 시도해 주세요.', password: '기존 비밀번호와 동일합니다. 새로운 비밀번호를 입력해 주세요.', image: '2mb 이하의 이미지만 등록이 가능합니다.', +} as const; + +export const PROFILE_MODAL_TEXT = { + password: '비밀번호가', + nickname: '닉네임이', + image: '이미지가', }; diff --git a/client/src/stores/signStore.ts b/client/src/stores/signStore.ts index 7c7673cc..bf655d8e 100644 --- a/client/src/stores/signStore.ts +++ b/client/src/stores/signStore.ts @@ -4,7 +4,6 @@ interface SignState { isCode: boolean; isEmailSignup: boolean; isEmailSignin: boolean; - isCorrectPassword: boolean; code: string; @@ -13,14 +12,12 @@ interface SignState { getSignupForm: (isEmailSignup: boolean) => void; getSigninForm: (isEmailSignin: boolean) => void; - getPassword: (isCorrectPassword: boolean) => void; } const useSignStore = create((set) => ({ isCode: false, isEmailSignup: false, isEmailSignin: false, - isCorrectPassword: false, code: '', @@ -29,7 +26,6 @@ const useSignStore = create((set) => ({ getSigninForm: (isEmailSignin) => set({ isEmailSignin }), getSignupForm: (isEmailSignup) => set({ isEmailSignup }), - getPassword: (isEmailSignup) => set({ isEmailSignup }), })); export default useSignStore; diff --git a/client/src/stores/userStore.ts b/client/src/stores/userStore.ts index c9a59036..baabccc4 100644 --- a/client/src/stores/userStore.ts +++ b/client/src/stores/userStore.ts @@ -84,6 +84,7 @@ const useUserStore = create( setProfileImageUrl: (profileImageUrl) => { set({ profileImageUrl }); }, + setDisplayName: (displayName) => { set({ displayName }); }, @@ -101,6 +102,7 @@ const useUserStore = create( profileImageUrl: '/assets/img/bg_default_profile.png', }), }), + { name: StorageKey, storage: createJSONStorage(() => localStorage), diff --git a/client/src/types/common.d.ts b/client/src/types/common.d.ts index 003d5663..f59cb456 100644 --- a/client/src/types/common.d.ts +++ b/client/src/types/common.d.ts @@ -38,13 +38,24 @@ export type InputValues = { export type SignFormValue = { email: string; - nickname: string; password: string; passwordCheck: string; + nickname: string; code: string; onLogin: () => void; }; +export type SignupFormValue = { + email: string; + password: string; + nickname: string; +}; + +export type SigninFormValue = { + email: string; + password: string; +}; + export type UserData = { accountId: number; displayName: string; From 2d93bacb2bb59e0c9d1998b763a07b35e1549141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=AC=EB=8F=84=EC=97=B0?= Date: Tue, 17 Oct 2023 03:46:29 +0900 Subject: [PATCH 19/20] =?UTF-8?q?[FE]=20=E2=9C=8F=EF=B8=8F=20Chore=20:=20a?= =?UTF-8?q?xios=20interceptors=20=EC=84=A4=EC=A0=95=20=EA=B5=AC=EC=B6=95?= =?UTF-8?q?=20=EC=8B=9C=EB=8F=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/api/axios.ts | 85 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 client/src/api/axios.ts diff --git a/client/src/api/axios.ts b/client/src/api/axios.ts new file mode 100644 index 00000000..a42fe30e --- /dev/null +++ b/client/src/api/axios.ts @@ -0,0 +1,85 @@ +import axios, { + AxiosRequestConfig, + AxiosResponse, + InternalAxiosRequestConfig, +} from 'axios'; + +import useUserStore from '@/stores/userStore'; + +const { setAccessToken } = useUserStore(); + +const accessToken = + typeof window !== 'undefined' + ? JSON.parse(localStorage.getItem('user-key') as string).state.accessToken + : null; + +const refreshToken = + typeof window !== 'undefined' + ? JSON.parse(localStorage.getItem('user-key') as string).state.refreshToken + : null; + +const instance = axios.create({ + baseURL: process.env.NEXT_PUBLIC_API_URL, + headers: { + Authorization: accessToken, + Refresh: refreshToken, + }, + withCredentials: true, +}); + +// atob을 활용한 JWT 디코딩 +const parseJWT = (token: string | null) => { + if (token) return JSON.parse(atob(token.split('.')[1])); +}; + +const authVerify = () => { + const decodedAccess = parseJWT(accessToken); + const decodedRefresh = parseJWT(refreshToken); + + if (decodedAccess.exp * 1000 < Date.now()) { + return 'Access Token Expired'; + } + + if (decodedRefresh.exp * 1000 < Date.now()) { + return 'Refresh Token Expired'; + } + + return true; +}; + +// 응답 받기 전, 새로운 accessToke이 존재하면 바꿔주기 +export const onFulfiled = async (response: AxiosResponse) => { + if (authVerify() === 'Access Token Expired') { + const { authorization: newAccessToken } = response.headers; + + setAccessToken(newAccessToken); + + // 타입 호환을 위해 새로운 객체를 만들어서 업데이트하기 + response.config.headers = Object.assign({}, response.config.headers, { + authorization: `${newAccessToken}`, + }); + + return await axios(response.config); + } + + return response; +}; + +instance.interceptors.request.use( + //! AxiosRequestConfig 대신 InternalAxiosRequestConfig를 사용하라고 하는데... + // InternalAxiosRequestConfig를 개발자가 직접 건드는게 좋은건 아니라고 하던데 흠... + async (config: InternalAxiosRequestConfig) => { + config.headers = config.headers ?? {}; + + if (accessToken) { + config.headers.Authorization = `${accessToken}`; + } + + return config; + }, + (error) => Promise.reject(error), +); + +instance.interceptors.response.use(onFulfiled, (error) => { + return Promise.reject(error); +}); From 41bbfa0e58d6b2df04177126b3d15b4d2e791d0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=AC=EB=8F=84=EC=97=B0?= Date: Tue, 17 Oct 2023 04:00:43 +0900 Subject: [PATCH 20/20] =?UTF-8?q?[FE]=20=E2=99=BB=EF=B8=8F=20Refactor=20:?= =?UTF-8?q?=20=EC=B9=B4=EB=A9=9C=EC=BC=80=EC=9D=B4=EC=8A=A4=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/history/HistoryBox.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/src/components/history/HistoryBox.tsx b/client/src/components/history/HistoryBox.tsx index 4ff67f2a..a23826cd 100644 --- a/client/src/components/history/HistoryBox.tsx +++ b/client/src/components/history/HistoryBox.tsx @@ -29,9 +29,9 @@ export default function HistoryBox({ paramsId }: HistoryProps) { const id = paramsId; const isOwner = userId === id; - const IS_BOARD_WRITTEN = selectOption === 'boardWritten'; - const IS_BOARD_LIKE = selectOption === 'boardLiked'; - const IS_COMMENT = selectOption === 'commentWritten'; + const isBoardWritten = selectOption === 'boardWritten'; + const isBoardLikes = selectOption === 'boardLiked'; + const isBoardComments = selectOption === 'commentWritten'; return ( <> @@ -56,9 +56,9 @@ export default function HistoryBox({ paramsId }: HistoryProps) { {(userId || paramsId) && (
    - {IS_BOARD_WRITTEN && } - {isOwner && IS_BOARD_LIKE && } - {isOwner && IS_COMMENT && } + {isBoardWritten && } + {isOwner && isBoardLikes && } + {isOwner && isBoardComments && }
    )}