diff --git a/src/api/inquiry.api.ts b/src/api/inquiry.api.ts index ead40038..c9fe6122 100644 --- a/src/api/inquiry.api.ts +++ b/src/api/inquiry.api.ts @@ -1,13 +1,13 @@ +import type { ApiCommonBasicType } from '../models/apiCommon'; import { httpClient } from './http.api'; export const postInquiry = async (formData: FormData) => { try { - const response = await httpClient.post('/inquiry', formData, { + await httpClient.post('/inquiry', formData, { headers: { 'Content-Type': 'multipart/form-data', }, }); - console.log(response); } catch (e) { console.error('문의하기 에러', e); throw e; diff --git a/src/components/common/header/Header.tsx b/src/components/common/header/Header.tsx index 8bb4c49e..14d86356 100644 --- a/src/components/common/header/Header.tsx +++ b/src/components/common/header/Header.tsx @@ -1,6 +1,6 @@ import * as S from './Header.styled'; import Mainlogo from '../../../assets/mainlogo.svg'; -import { Link } from 'react-router-dom'; +import { Link, useLocation } from 'react-router-dom'; import DropDown from '../dropDown/DropDown'; import Avatar from '../avatar/Avatar'; import { useAuth } from '../../../hooks/useAuth'; @@ -16,20 +16,22 @@ import { formatImgPath } from '../../../util/formatImgPath'; import bell from '../../../assets/bell.svg'; import Notification from './Notification/Notification'; import bellLogined from '../../../assets/bellLogined.svg'; -// import useNotification from '../../../hooks/useNotification'; +import useNotification from '../../../hooks/useNotification'; import { useEffect } from 'react'; import { testLiveAlarm } from '../../../api/alarm.api'; function Header() { + const location = useLocation(); const { isOpen, message, handleModalOpen, handleModalClose } = useModal(); const { userLogout } = useAuth(handleModalOpen); const isLoggedIn = useAuthStore((state) => state.isLoggedIn); const { myData, isLoading } = useMyProfileInfo(); + // const { signalData, setSignalData } = useNotification(); - useEffect(() => { - testLiveAlarm(); - }, []); + // useEffect(() => { + // testLiveAlarm(); + // }, []); const profileImg = myData?.profileImg ? `${import.meta.env.VITE_APP_IMAGE_CDN_URL}/${formatImgPath( @@ -92,7 +94,7 @@ function Header() { 공고관리 - + 문의하기 e.preventDefault()}> diff --git a/src/components/mypage/activityLog/inquiries/inquiry/Inquiry.tsx b/src/components/mypage/activityLog/inquiries/inquiry/Inquiry.tsx index 3e0a7b85..1187383f 100644 --- a/src/components/mypage/activityLog/inquiries/inquiry/Inquiry.tsx +++ b/src/components/mypage/activityLog/inquiries/inquiry/Inquiry.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import type { MyInquiries } from '../../../../../models/activityLog'; import * as S from './Inquiry.styled'; -import { INQUIRY_MESSAGE } from '../../../../../constants/customerService'; +import { My_INQUIRIES_MESSAGE } from '../../../../../constants/customerService'; interface InquiryProps { list: MyInquiries; @@ -49,7 +49,7 @@ export default function Inquiry({ list, no }: InquiryProps) { ))} - {INQUIRY_MESSAGE.blowUpMessage} + {My_INQUIRIES_MESSAGE.blowUpMessage} )} @@ -64,7 +64,7 @@ export default function Inquiry({ list, no }: InquiryProps) { } > - {INQUIRY_MESSAGE.isImageOpenMessage} + {My_INQUIRIES_MESSAGE.isImageOpenMessage} diff --git a/src/constants/customerService.ts b/src/constants/customerService.ts index 1a302463..be80ebcc 100644 --- a/src/constants/customerService.ts +++ b/src/constants/customerService.ts @@ -8,9 +8,18 @@ export const INQUIRY_CATEGORY = [ export const EMPTY_IMAGE = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' as const; -export const INQUIRY_MESSAGE = { +export const My_INQUIRIES_MESSAGE = { categoryDefault: '카테고리', fileDefault: '선택된 파일이 없습니다.', blowUpMessage: '클릭하면 이미지를 크게 볼 수 있습니다.', isImageOpenMessage: '이미지를 클릭하면 사라집니다.', }; + +export const INQUIRY_MESSAGE = { + selectCategory: '카테고리를 선택하세요.', + writeTitle: '제목을 적어주세요.', + writeContent: '내용을 적어주세요.', + inquiredSuccess: '문의글이 작성되었습니다.', + inquiredError: '문의글 작성에 실패하였습니다.', + validationFile: '파일 크기는 5MB 이하만 가능합니다.', +}; diff --git a/src/hooks/useMyInfo.ts b/src/hooks/useMyInfo.ts index 1b920c73..82438476 100644 --- a/src/hooks/useMyInfo.ts +++ b/src/hooks/useMyInfo.ts @@ -24,7 +24,7 @@ export const useMyProfileInfo = () => { const { data, isLoading } = useQuery({ queryKey: myInfoKey.myProfile, queryFn: () => getMyInfo(), - staleTime: 1 * 60 * 1000, + staleTime: Infinity, enabled: isLoggedIn, }); diff --git a/src/hooks/usePostInquiry.ts b/src/hooks/usePostInquiry.ts index 23520ae9..5d3f8206 100644 --- a/src/hooks/usePostInquiry.ts +++ b/src/hooks/usePostInquiry.ts @@ -1,14 +1,37 @@ +import { ActivityLog } from './queries/keys'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { postInquiry } from '../api/inquiry.api'; import { AxiosError } from 'axios'; +import useAuthStore from '../store/authStore'; +import { useNavigate } from 'react-router-dom'; +import { INQUIRY_MESSAGE } from '../constants/customerService'; -export const usePostInquiry = () => { +export const usePostInquiry = ( + handleModalOpen: (message: string) => void, + pathname: string = '' +) => { + const userId = useAuthStore((state) => state.userData?.id); + const navigate = useNavigate(); const queryClient = useQueryClient(); const mutate = useMutation({ mutationFn: (formData) => postInquiry(formData), onSuccess: () => { - queryClient.invalidateQueries(); + queryClient.invalidateQueries({ + queryKey: [ActivityLog.myInquiries, userId], + }); + setTimeout(() => { + if (pathname === '' || !pathname) { + navigate(-1); + } else { + navigate(pathname); + } + }, 1000); + + handleModalOpen(INQUIRY_MESSAGE.inquiredSuccess); + }, + onError: () => { + handleModalOpen(INQUIRY_MESSAGE.inquiredError); }, }); diff --git a/src/models/apiCommon.ts b/src/models/apiCommon.ts index 0a096d98..91eafc53 100644 --- a/src/models/apiCommon.ts +++ b/src/models/apiCommon.ts @@ -3,6 +3,10 @@ export interface ApiCommonType { message: string; } +export interface ApiCommonBasicType extends ApiCommonType { + data: boolean; +} + export interface User { id: number; nickname: string; diff --git a/src/pages/customerService/MoveInquiredLink.tsx b/src/pages/customerService/MoveInquiredLink.tsx index 331ea4ee..44b268c4 100644 --- a/src/pages/customerService/MoveInquiredLink.tsx +++ b/src/pages/customerService/MoveInquiredLink.tsx @@ -1,6 +1,13 @@ +import { useLocation } from 'react-router-dom'; import { ROUTES } from '../../constants/routes'; import * as S from './MoveInquiredLink.styled'; export default function MovedInquiredLink() { - return 문의하기; + const location = useLocation(); + + return ( + + 문의하기 + + ); } diff --git a/src/pages/customerService/inquiry/Inquiry.styled.ts b/src/pages/customerService/inquiry/Inquiry.styled.ts index ba8f1e47..e8c7f06d 100644 --- a/src/pages/customerService/inquiry/Inquiry.styled.ts +++ b/src/pages/customerService/inquiry/Inquiry.styled.ts @@ -35,7 +35,7 @@ export const CategoryWrapper = styled.div` position: relative; `; -export const CategorySelect = styled.button<{ $isOpen: boolean }>` +export const CategorySelect = styled.button<{ $isCategoryOpen: boolean }>` padding: 0.3rem 0.5rem; width: 9rem; display: flex; diff --git a/src/pages/customerService/inquiry/Inquiry.tsx b/src/pages/customerService/inquiry/Inquiry.tsx index 6fc9322e..6b52699b 100644 --- a/src/pages/customerService/inquiry/Inquiry.tsx +++ b/src/pages/customerService/inquiry/Inquiry.tsx @@ -2,12 +2,16 @@ import { Fragment } from 'react/jsx-runtime'; import { INQUIRY_CATEGORY, INQUIRY_MESSAGE, + My_INQUIRIES_MESSAGE, } from '../../../constants/customerService'; import * as S from './Inquiry.styled'; import { ChevronDownIcon } from '@heroicons/react/24/outline'; import React, { useEffect, useState } from 'react'; import type { InquiryFormData } from '../../../models/inquiry'; import { usePostInquiry } from '../../../hooks/usePostInquiry'; +import { useLocation } from 'react-router-dom'; +import { useModal } from '../../../hooks/useModal'; +import Modal from '../../../components/common/modal/Modal'; interface FormStateType { category: string; @@ -18,13 +22,23 @@ interface FormStateType { } export default function Inquiry() { - const { mutate: postInquiry } = usePostInquiry(); - const [isOpen, setIsOpen] = useState(false); + const location = useLocation(); + const { + isOpen: isModalOpen, + message, + handleModalOpen, + handleModalClose, + } = useModal(); + const { mutate: postInquiry } = usePostInquiry( + handleModalOpen, + location.state.from || '' + ); + const [isCategoryOpen, setIsCategoryOpen] = useState(false); const [form, setForm] = useState({ - category: INQUIRY_MESSAGE.categoryDefault, + category: My_INQUIRIES_MESSAGE.categoryDefault, title: '', content: '', - fileValue: INQUIRY_MESSAGE.fileDefault, + fileValue: My_INQUIRIES_MESSAGE.fileDefault, fileImage: null, }); @@ -45,40 +59,43 @@ export default function Inquiry() { formData.append('inquiryDto', data); - // 모달처리하기 - let isValid = true; - - Array.from(formData.entries()).forEach(([key, value]) => { - if (key === 'category' && value === INQUIRY_MESSAGE.categoryDefault) - return (isValid = false); - if (key === 'title' && value === '') return (isValid = false); - if (key === 'content' && value === '') return (isValid = false); - }); + const isValid = { + category: form.category !== My_INQUIRIES_MESSAGE.categoryDefault, + title: form.title.trim() !== '', + content: form.content.trim() !== '', + }; - if (isValid) { - postInquiry(formData); - setForm({ - category: INQUIRY_MESSAGE.categoryDefault, - title: '', - content: '', - fileValue: INQUIRY_MESSAGE.fileDefault, - fileImage: null, - }); + if (!isValid.category) { + return handleModalOpen(INQUIRY_MESSAGE.selectCategory); } + if (!isValid.title) { + return handleModalOpen(INQUIRY_MESSAGE.writeTitle); + } + if (!isValid.content) { + return handleModalOpen(INQUIRY_MESSAGE.writeContent); + } + + postInquiry(formData); + setForm({ + category: My_INQUIRIES_MESSAGE.categoryDefault, + title: '', + content: '', + fileValue: My_INQUIRIES_MESSAGE.fileDefault, + fileImage: null, + }); }; const handleClickCategoryValue = (category: string) => { setForm((prev) => ({ ...prev, category })); - setIsOpen((prev) => !prev); + setIsCategoryOpen((prev) => !prev); }; const handleChangeFile = (e: React.ChangeEvent) => { const fileValue = e.target.value; const image = e.target.files?.[0]; - // 파일 크기 제한 (예: 5MB) const MAX_FILE_SIZE = 5 * 1024 * 1024; if (image && image.size > MAX_FILE_SIZE) { - alert('파일 크기는 5MB 이하만 가능합니다.'); + handleModalOpen(INQUIRY_MESSAGE.validationFile); e.target.value = ''; return; } @@ -109,8 +126,12 @@ export default function Inquiry() { setIsOpen((prev) => !prev)} - $isOpen={isOpen} + type='button' + onClick={(e: React.MouseEvent) => { + e.stopPropagation(); + setIsCategoryOpen((prev) => !prev); + }} + $isCategoryOpen={isCategoryOpen} > {form.category} - {isOpen && ( + {isCategoryOpen && ( {INQUIRY_CATEGORY.map((category) => ( handleClickCategoryValue(category.title)} > {category.title} @@ -166,10 +188,18 @@ export default function Inquiry() { - 제출 + setIsCategoryOpen((prev) => !prev)} + > + 제출 + + + {message} + ); }