From 9f96749be2d62360418010e6a83b9196caf59af6 Mon Sep 17 00:00:00 2001 From: yujin-fe Date: Tue, 2 Dec 2025 01:31:08 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20=EB=A1=9C?= =?UTF-8?q?=EC=BB=AC=EC=8A=A4=ED=86=A0=EB=A6=AC=EC=A7=80=EC=97=90=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=ED=95=9C=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard/modal/DashboardInviteModal.tsx | 12 +++++++++--- src/constants/invitation.ts | 2 ++ src/pages/DetailLayout.tsx | 3 ++- src/utils/validation.ts | 7 ++++++- 4 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 src/constants/invitation.ts diff --git a/src/components/dashboard/modal/DashboardInviteModal.tsx b/src/components/dashboard/modal/DashboardInviteModal.tsx index 2a88a445..8fa05c4f 100644 --- a/src/components/dashboard/modal/DashboardInviteModal.tsx +++ b/src/components/dashboard/modal/DashboardInviteModal.tsx @@ -4,7 +4,7 @@ import Input from '@/components/common/input/Input'; import FormModal from '@/components/common/modal/FormModal'; import { INVITE } from '@/constants/modalName'; import { useModal } from '@/hooks/useModal'; -import { validateEmail } from '@/utils/validation'; +import { validateEmail, validateInvitation } from '@/utils/validation'; interface DashboardInviteModalProps { inviteeEmail: string; @@ -34,18 +34,24 @@ export default function DashboardInviteModal({ const handleChange = (value: string) => { setInviteeEmail(value); - if (errorMsg) { setErrorMsg(''); } }; const handleBlur = () => { + if (validateInvitation(inviteeEmail)) { + setErrorMsg(() => validateInvitation(inviteeEmail)); + return; + } const message = validateEmail(inviteeEmail); setErrorMsg(message); }; - const disabled = inviteeEmail.trim() === '' || validateEmail(inviteeEmail) !== ''; + const disabled = + inviteeEmail.trim() === '' + || validateEmail(inviteeEmail) !== '' + || validateInvitation(inviteeEmail) !== ''; return ( diff --git a/src/constants/invitation.ts b/src/constants/invitation.ts new file mode 100644 index 00000000..cbc9cac8 --- /dev/null +++ b/src/constants/invitation.ts @@ -0,0 +1,2 @@ +/**초대된 이메일 **/ +export const REQUESTED_EMAIL = 'requestedEmail'; diff --git a/src/pages/DetailLayout.tsx b/src/pages/DetailLayout.tsx index f443e9ea..d47168ed 100644 --- a/src/pages/DetailLayout.tsx +++ b/src/pages/DetailLayout.tsx @@ -3,11 +3,11 @@ import { useState } from 'react'; import { Outlet, useParams } from 'react-router'; import BaseModalFrame from '@/components/common/modal/BaseModalFrame'; import DashboardInviteModal from '@/components/dashboard/modal/DashboardInviteModal'; +import { REQUESTED_EMAIL } from '@/constants/invitation'; import { INVITE } from '@/constants/modalName'; import useBaseModal from '@/hooks/useBaseModal'; import { useModal } from '@/hooks/useModal'; import { inviteDashboard } from '@/lib/apis/Invitations'; - export default function DetailLayout() { const { dashboardId } = useParams(); const { @@ -34,6 +34,7 @@ export default function DetailLayout() { setCompletedInviteeUser(resData.invitee.nickname); openBaseModal(); closeInviteModal(); + localStorage.setItem(inviteeEmail, REQUESTED_EMAIL); } catch (err) { if (axios.isAxiosError(err)) { setApiErrorMsg(err.response?.data?.message ?? '오류가 발생했습니다.'); diff --git a/src/utils/validation.ts b/src/utils/validation.ts index ee759d23..541f2400 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -1,5 +1,5 @@ import { EMAIL_REGEX, PASSWORD_MIN_LEN, NICKNAME_MAX_LEN } from '@/constants/authRegex'; - +import { REQUESTED_EMAIL } from '@/constants/invitation'; export const validateEmail = (value: string) => { return EMAIL_REGEX.test(value) ? '' : '이메일 형식으로 작성해 주세요.'; }; @@ -18,9 +18,14 @@ export const validateNickname = (value: string) => { return value.length <= NICKNAME_MAX_LEN ? '' : `${NICKNAME_MAX_LEN}자 이하로 작성해주세요.`; }; +export const validateInvitation = (value: string) => { + return localStorage.getItem(value) === REQUESTED_EMAIL ? '이미 요청한 이메일입니다.' : ''; +}; + export const validators: Record string> = { email: validateEmail, password: validatePassword, newPassword: validateNewPassword, nickname: validateNickname, + invitation: validateInvitation, }; From 11325b99aad897abf5b64dfdebb3c445720a02f4 Mon Sep 17 00:00:00 2001 From: yujin-fe Date: Tue, 2 Dec 2025 01:57:46 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20=EB=A1=9C?= =?UTF-8?q?=EC=BB=AC=EC=8A=A4=ED=86=A0=EB=A6=AC=EC=A7=80=EC=97=90=20?= =?UTF-8?q?=EC=B7=A8=EC=86=8C=ED=95=9C=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/editpage/InvitesEdit.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/editpage/InvitesEdit.tsx b/src/components/editpage/InvitesEdit.tsx index e8d9d701..60a5f185 100644 --- a/src/components/editpage/InvitesEdit.tsx +++ b/src/components/editpage/InvitesEdit.tsx @@ -56,7 +56,14 @@ export default function InvitesEdit() { const cancelMutation = useMutation({ mutationFn: ({ dashboardId, invitationId }) => deleteInvitationdata(dashboardId, invitationId), - onSuccess: () => { + onSuccess: (_, value: DeleteInvitationParams) => { + const invitation = invitations.find( + (inv) => inv.id.toString() === value.invitationId.toString() + ); + if (invitation) { + const inviteeEmail = invitation?.invitee?.email; + localStorage.removeItem(inviteeEmail); + } setDeleteMessage('초대 취소가 완료되었습니다.'); handleModalOpen(); refetch(); From bacc20951be7f27b75c6c2a13386d8311f2d4f2b Mon Sep 17 00:00:00 2001 From: yujin-fe Date: Tue, 2 Dec 2025 02:51:19 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=90=9B=20Fix:=20=EB=8C=80=EC=8B=9C?= =?UTF-8?q?=EB=B3=B4=EB=93=9C=20=EA=B5=AC=EB=B6=84=ED=95=A0=20=EC=88=98=20?= =?UTF-8?q?=EC=9E=88=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/dashboard/modal/DashboardInviteModal.tsx | 8 +++++--- src/components/editpage/InvitesEdit.tsx | 3 ++- src/pages/DetailLayout.tsx | 3 ++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/dashboard/modal/DashboardInviteModal.tsx b/src/components/dashboard/modal/DashboardInviteModal.tsx index 8fa05c4f..6721ddeb 100644 --- a/src/components/dashboard/modal/DashboardInviteModal.tsx +++ b/src/components/dashboard/modal/DashboardInviteModal.tsx @@ -13,6 +13,7 @@ interface DashboardInviteModalProps { setErrorMsg: React.Dispatch>; apiErrorMsg: string; onSubmit: () => Promise; + dashboardId: string; } export default function DashboardInviteModal({ @@ -22,6 +23,7 @@ export default function DashboardInviteModal({ setErrorMsg, apiErrorMsg, onSubmit, + dashboardId, }: DashboardInviteModalProps) { const { handleModalClose } = useModal(INVITE); @@ -40,8 +42,8 @@ export default function DashboardInviteModal({ }; const handleBlur = () => { - if (validateInvitation(inviteeEmail)) { - setErrorMsg(() => validateInvitation(inviteeEmail)); + if (validateInvitation(inviteeEmail + dashboardId)) { + setErrorMsg(() => validateInvitation(inviteeEmail + dashboardId)); return; } const message = validateEmail(inviteeEmail); @@ -51,7 +53,7 @@ export default function DashboardInviteModal({ const disabled = inviteeEmail.trim() === '' || validateEmail(inviteeEmail) !== '' - || validateInvitation(inviteeEmail) !== ''; + || validateInvitation(inviteeEmail + dashboardId) !== ''; return ( diff --git a/src/components/editpage/InvitesEdit.tsx b/src/components/editpage/InvitesEdit.tsx index fd901218..d3fbe5fb 100644 --- a/src/components/editpage/InvitesEdit.tsx +++ b/src/components/editpage/InvitesEdit.tsx @@ -78,7 +78,7 @@ export default function InvitesEdit() { ); if (invitation) { const inviteeEmail = invitation?.invitee?.email; - localStorage.removeItem(inviteeEmail); + localStorage.removeItem(inviteeEmail + dashboardId); } setDeleteMessage('초대 취소가 완료되었습니다.'); openCancelModal(); @@ -220,6 +220,7 @@ export default function InvitesEdit() { setErrorMsg={setInputErrorMsg} onSubmit={handleInviteSubmit} apiErrorMsg={apiErrorMsg} + dashboardId={dashboardId} /> )} diff --git a/src/pages/DetailLayout.tsx b/src/pages/DetailLayout.tsx index d47168ed..5131fda7 100644 --- a/src/pages/DetailLayout.tsx +++ b/src/pages/DetailLayout.tsx @@ -34,7 +34,7 @@ export default function DetailLayout() { setCompletedInviteeUser(resData.invitee.nickname); openBaseModal(); closeInviteModal(); - localStorage.setItem(inviteeEmail, REQUESTED_EMAIL); + localStorage.setItem(inviteeEmail + dashboardId, REQUESTED_EMAIL); } catch (err) { if (axios.isAxiosError(err)) { setApiErrorMsg(err.response?.data?.message ?? '오류가 발생했습니다.'); @@ -53,6 +53,7 @@ export default function DetailLayout() { setErrorMsg={setInputErrorMsg} onSubmit={handleInviteSubmit} apiErrorMsg={apiErrorMsg} + dashboardId={dashboardId} /> )} {baseModalIsOpen && (