diff --git a/src/components/dashboard/modal/DashboardInviteModal.tsx b/src/components/dashboard/modal/DashboardInviteModal.tsx index 2a88a445..6721ddeb 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; @@ -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); @@ -34,18 +36,24 @@ export default function DashboardInviteModal({ const handleChange = (value: string) => { setInviteeEmail(value); - if (errorMsg) { setErrorMsg(''); } }; const handleBlur = () => { + if (validateInvitation(inviteeEmail + dashboardId)) { + setErrorMsg(() => validateInvitation(inviteeEmail + dashboardId)); + return; + } const message = validateEmail(inviteeEmail); setErrorMsg(message); }; - const disabled = inviteeEmail.trim() === '' || validateEmail(inviteeEmail) !== ''; + const disabled = + inviteeEmail.trim() === '' + || validateEmail(inviteeEmail) !== '' + || validateInvitation(inviteeEmail + dashboardId) !== ''; return ( diff --git a/src/components/editpage/InvitesEdit.tsx b/src/components/editpage/InvitesEdit.tsx index 11e67fcf..d3fbe5fb 100644 --- a/src/components/editpage/InvitesEdit.tsx +++ b/src/components/editpage/InvitesEdit.tsx @@ -72,7 +72,14 @@ export default function InvitesEdit() { const cancelMutation = useMutation({ mutationFn: ({ dashboardId: id, invitationId }) => deleteInvitationdata(Number(id), 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 + dashboardId); + } setDeleteMessage('초대 취소가 완료되었습니다.'); openCancelModal(); refetch(); @@ -85,27 +92,13 @@ export default function InvitesEdit() { }, [inviteData?.totalCount]); const isNextDisabled = currentPage >= calculatedTotalPages; - const invitations: Invitation[] = useMemo(() => inviteData?.invitations || [], [inviteData]); - - const numericDashboardId: number | null = useMemo(() => { - if (dashboardId) { - const numId = Number(dashboardId); - if (!isNaN(numId)) { - return numId; - } - } - return null; - }, [dashboardId]); - if (!dashboardId) { + if (!inviteData) { return null; } + const invitations: Invitation[] = inviteData?.invitations; - if (numericDashboardId === null) { - return
유효하지 않은 대시보드 ID입니다.
; - } - - if (!inviteData) { + if (!dashboardId) { return null; } @@ -124,7 +117,7 @@ export default function InvitesEdit() { } try { - const resData = await inviteDashboard(dashboardId, { email: inviteeEmail }); + const resData = await inviteDashboard(dashboardId.toString(), { email: inviteeEmail }); setCompletedInviteeUser(resData.invitee.nickname); openBaseModal(); @@ -157,18 +150,18 @@ export default function InvitesEdit() { type='InvitesItem' email={invitation.invitee.email} id={invitation.id} - onCancel={() => handleCancel(numericDashboardId, invitation.id)}> + onCancel={() => handleCancel(+dashboardId, invitation.id)}> handleCancel(numericDashboardId, invitation.id)} + onCancel={() => handleCancel(+dashboardId, invitation.id)} /> handleCancel(numericDashboardId, invitation.id)} + onCancel={() => handleCancel(+dashboardId, invitation.id)} /> )); @@ -227,6 +220,7 @@ export default function InvitesEdit() { setErrorMsg={setInputErrorMsg} onSubmit={handleInviteSubmit} apiErrorMsg={apiErrorMsg} + dashboardId={dashboardId} /> )} 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..5131fda7 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 + dashboardId, REQUESTED_EMAIL); } catch (err) { if (axios.isAxiosError(err)) { setApiErrorMsg(err.response?.data?.message ?? '오류가 발생했습니다.'); @@ -52,6 +53,7 @@ export default function DetailLayout() { setErrorMsg={setInputErrorMsg} onSubmit={handleInviteSubmit} apiErrorMsg={apiErrorMsg} + dashboardId={dashboardId} /> )} {baseModalIsOpen && ( 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, };