diff --git a/src/assets/svgs/modal/errorIcon.svg b/src/assets/svgs/modal/errorIcon.svg new file mode 100644 index 0000000..6e4c52e --- /dev/null +++ b/src/assets/svgs/modal/errorIcon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/common/ToastModal.jsx b/src/components/common/ToastModal.jsx index a12d0e8..7d3b98a 100644 --- a/src/components/common/ToastModal.jsx +++ b/src/components/common/ToastModal.jsx @@ -20,7 +20,7 @@ const ToastModal = ({
- + {message}
diff --git a/src/constants/review/reviewData.js b/src/constants/review/reviewData.js deleted file mode 100644 index 681154f..0000000 --- a/src/constants/review/reviewData.js +++ /dev/null @@ -1,18 +0,0 @@ -export const reviewData = [ - { - nickname: "윤서", - text: "채움 로렘 지칭하는 시각 지칭하는 모형의 때 입숨은 문장 무언가를 입숨은 그래픽 프로젝트 그래픽 용어로도 공간만 연출을 이런 때로 프로젝트 때 보여줄 들어가는 입숨을 사용된다. 내용이 같은 시각 전에 무언가를 디자인 ", - }, - { - nickname: "찬영", - text: "채움 로렘 지칭하는 시각 지칭하는 모형의 때 입숨은 문장 무언가를 입숨은 그래픽 프로젝트 그래픽 용어로도 공간만 연출을 이런 때로 프로젝트 때 보여줄 들어가는 입숨을 사용된다. 내용이 같은 시각 전에 무언가를 디자인 ", - }, - { - nickname: "승훈", - text: "채움 로렘 지칭하는 시각 지칭하는 모형의 때 입숨은 문장 무언가를 입숨은 그래픽 프로젝트 그래픽 용어로도 공간만 연출을 이런 때로 프로젝트 때 보여줄 들어가는 입숨을 사용된다. 내용이 같은 시각 전에 무언가를 디자인 ", - }, - { - nickname: "명우", - text: "채움 로렘 지칭하는 시각 지칭하는 모형의 때 입숨은 문장 무언가를 입숨은 그래픽 프로젝트 그래픽 용어로도 공간만 연출을 이런 때로 프로젝트 때 보여줄 들어가는 입숨을 사용된다. 내용이 같은 시각 전에 무언가를 디자인 ", - }, -]; diff --git a/src/pages/map/components/review/ConfirmImage.jsx b/src/pages/map/components/review/ConfirmImage.jsx index e1d860a..0d4a7e9 100644 --- a/src/pages/map/components/review/ConfirmImage.jsx +++ b/src/pages/map/components/review/ConfirmImage.jsx @@ -9,23 +9,12 @@ import { formatDateTime } from "@/pages/map/utils/formatDateTime"; import { usePaymentStore } from "@/store/paymentStore"; import { getDistanceDiff } from "@/pages/map/utils/getDistanceDiff"; import { formatToYMDHMS } from "@/store/paymentStore"; +import { businessTypeNameMap } from "@/constants/categoryMap"; const ConfirmImage = ({ onReject, data, onConfirmComplete }) => { const navigate = useNavigate(); - // 이 화면에서 넘겨야 할 것 -> 시간 정보, 결제승인번호 - const setReviewInfo = usePaymentStore((s) => s.setReviewInfo); - const { companyId } = usePaymentStore(); - - const moveToReviewPage = () => { - onConfirmComplete?.(); - }; - // const handleClick = () => { - // const newDate = new Date(/* year, month-1, day, hour, minute */); - // setPaymentTime(newDate); - - // navigate("/writereview"); - // }; + const setReviewInfo = usePaymentStore((s) => s.setReviewInfo); const handleClick = () => { const pad = (n) => String(n).padStart(2, "0"); @@ -241,7 +230,9 @@ const ConfirmImage = ({ onReject, data, onConfirmComplete }) => {

{data?.storeName}

-

{data?.companyCategory ?? "기타"}

+

+ {businessTypeNameMap[data?.companyCategory] ?? "기타"} +

diff --git a/src/pages/map/components/review/ReviewImageCapture.jsx b/src/pages/map/components/review/ReviewImageCapture.jsx index 9878509..dda4a6a 100644 --- a/src/pages/map/components/review/ReviewImageCapture.jsx +++ b/src/pages/map/components/review/ReviewImageCapture.jsx @@ -1,12 +1,11 @@ import { useState, useRef, useEffect } from "react"; import { useMutation } from "@tanstack/react-query"; import { postRecipt } from "@/apis/review/postRecipt"; -import { useNavigate } from "react-router-dom"; import Modal from "@/pages/map/components/Modal"; import ReceiptErrorModal from "@/pages/map/components/review/ReceiptErrorModal"; import "@/styles/spinner.css"; - -import { usePaymentStore } from "@/store/paymentStore"; +import ErrorIcon from "/public/svgs/modal/errorIcon.svg?react"; +import ToastModal from "@/components/common/ToastModal"; import imageCompression from "browser-image-compression"; const ReviewImageCapture = ({ @@ -15,8 +14,6 @@ const ReviewImageCapture = ({ onCloseCamera, onCaptureSuccess, }) => { - const navigate = useNavigate(); - useEffect(() => () => stopCamera(), []); const streamRef = useRef(null); @@ -33,6 +30,17 @@ const ReviewImageCapture = ({ const [showIntroModal, setShowIntroModal] = useState(false); const [showReceiptError, setShowReceiptError] = useState(false); + // 1) 토스트 상태 & 실행 함수 + const [toast, setToast] = useState({ + show: false, + message: "", + icon: null, + }); + const fireToast = (message, icon = ErrorIcon, duration = 4000) => { + setToast({ show: true, message, icon }); + setTimeout(() => setToast((t) => ({ ...t, show: false })), duration); + }; + useEffect(() => { const hasSeenModal = localStorage.getItem("hasSeenCameraIntro"); @@ -55,7 +63,10 @@ const ReviewImageCapture = ({ } } catch (error) { console.log("카메라 접근 실패", error); - alert("카메라 접근에 실패했습니다. 카메라 접근 권한을 확인해 주세요!"); + fireToast( + "카메라 접근에 실패했습니다.\n카메라 권한을 확인해 주세요!", + ErrorIcon + ); } }; @@ -101,7 +112,7 @@ const ReviewImageCapture = ({ const file = e.target.files?.[0]; if (file) { if (!file.type.startsWith("image/")) { - alert("이미지 파일만 선택 가능합니다."); + fireToast("이미지 파일만 선택 가능합니다.", ErrorIcon); e.target.value = ""; fileInputRef.current?.click(); return; @@ -170,13 +181,13 @@ const ReviewImageCapture = ({ // FormData 에 file + companyId 담기 const form = new FormData(); form.append("file", compressedFile, compressedFile.name); - form.append("companyId", String(780)); + form.append("companyId", String(companyId)); // 단일 인자로 FormData 전달 mutate(form); } catch (e) { console.error(e); - alert("이미지 압축 실패"); + fireToast("이미지 압축에 실패했습니다. 다시 시도해 주세요!", ErrorIcon); } }; @@ -186,6 +197,14 @@ const ReviewImageCapture = ({ onTouchStart={(e) => e.stopPropagation()} onTouchEnd={(e) => e.stopPropagation()} > + {toast.show && ( + setToast((t) => ({ ...t, show: false }))} + /> + )}

{/* 숨겨진 input */} (
@@ -33,6 +35,16 @@ const MyPage = () => { const [showLogoutModal, setShowLogoutModal] = useState(false); const { logout: setLoggedOut, isLogout } = useAuthStore(); + const [toast, setToast] = useState({ + show: false, + message: "", + icon: null, + }); + const fireToast = (message, icon = ErrorIcon, duration = 4000) => { + setToast({ show: true, message, icon }); + setTimeout(() => setToast((t) => ({ ...t, show: false })), duration); + }; + const nickname = data?.name ?? ""; const location = data?.address ?? ""; const counts = { @@ -53,13 +65,14 @@ const MyPage = () => { setLoggedOut(); setShowLogoutModal(true); } catch (e) { - alert("로그아웃에 실패하였습니다."); + fireToast("로그아웃에 실패하였습니다.", ErrorIcon); } }; + if (isLoading) { return (
-

로딩 중…

+

로딩 중…

); } @@ -78,15 +91,6 @@ const MyPage = () => { return (
- {/* {showLoginModal && ( - {}} // 내부 close는 더 이상 호출되지 않음 - /> - )} */} {/* 로그아웃 완료 모달 */} {showLogoutModal && navigate("/")} />} {/* 카드 */} @@ -129,6 +133,15 @@ const MyPage = () => { 로그아웃
+ + {toast.show && ( + setToast((t) => ({ ...t, show: false }))} + /> + )}
); }; diff --git a/src/pages/review/StoreReviewPage.jsx b/src/pages/review/StoreReviewPage.jsx index ae9b3e6..50b502a 100644 --- a/src/pages/review/StoreReviewPage.jsx +++ b/src/pages/review/StoreReviewPage.jsx @@ -47,7 +47,7 @@ const StoreReviewPage = () => { navigate(-1)} + onClick={() => navigate("/")} />
@@ -66,7 +66,6 @@ const StoreReviewPage = () => { onCloseCamera={() => setTurnOnCamera(false)} onCaptureSuccess={(data) => { setCompanyInfo(data); // 즉시 로컬 상태에 저장 - // setReceiptInfo(data); // 전역 상태에도 저장 setShowConfirm(true); // 그다음 Confirm 렌더링 }} /> diff --git a/src/pages/story/components/carousel/BestStoryCarousel.jsx b/src/pages/story/components/carousel/BestStoryCarousel.jsx index 2e2a2f2..926f8a4 100644 --- a/src/pages/story/components/carousel/BestStoryCarousel.jsx +++ b/src/pages/story/components/carousel/BestStoryCarousel.jsx @@ -2,7 +2,7 @@ import { Swiper, SwiperSlide } from "swiper/react"; import { Autoplay, Pagination } from "swiper/modules"; import "swiper/css"; import "swiper/css/pagination"; -import "@/styles/swiper.css"; // 👈 아래의 스타일이 여기에 포함되어야 함 +import "@/styles/swiper.css"; import SlideContent from "@/pages/story/components/content/SlideContent"; const BestStoryCarousel = ({ data, isLoading }) => { diff --git a/src/pages/support/components/step/Step6.jsx b/src/pages/support/components/step/Step6.jsx index 02f6bf7..550c23c 100644 --- a/src/pages/support/components/step/Step6.jsx +++ b/src/pages/support/components/step/Step6.jsx @@ -1,9 +1,20 @@ import { postUserInfo } from "@/apis/recommend/postUserInfo"; import { useState } from "react"; -import "@/styles/spinner.css"; // ✅ 스피너 CSS import +import "@/styles/spinner.css"; +import ErrorIcon from "/public/svgs/modal/errorIcon.svg?react"; +import ToastModal from "@/components/common/ToastModal"; const Step6 = ({ onNext, defaultValue, userInfo, setRecommendResult }) => { const [isLoading, setIsLoading] = useState(false); + const [toast, setToast] = useState({ + show: false, + message: "", + icon: null, + }); + const fireToast = (message, icon = ErrorIcon, duration = 4000) => { + setToast({ show: true, message, icon }); + setTimeout(() => setToast((t) => ({ ...t, show: false })), duration); + }; const handleNext = async () => { try { @@ -13,7 +24,7 @@ const Step6 = ({ onNext, defaultValue, userInfo, setRecommendResult }) => { onNext(); } catch (error) { console.error("추천 요청 실패:", error); - alert("추천 결과를 불러오는 데 실패했어요. 다시 시도해주세요."); + fireToast("추천 결과를 불러오는 데 실패했어요.\n다시 시도해주세요."); } finally { setIsLoading(false); } @@ -49,6 +60,15 @@ const Step6 = ({ onNext, defaultValue, userInfo, setRecommendResult }) => { )}
+ + {toast.show && ( + setToast((t) => ({ ...t, show: false }))} + /> + )}
); }; diff --git a/src/pages/support/components/step/Step7.jsx b/src/pages/support/components/step/Step7.jsx index 4b3f6a6..8f52a1b 100644 --- a/src/pages/support/components/step/Step7.jsx +++ b/src/pages/support/components/step/Step7.jsx @@ -1,6 +1,5 @@ -import { useState } from "react"; import { useNavigate } from "react-router-dom"; -const Step7 = ({ onNext, defaultValue, userInfo, recommendResult }) => { +const Step7 = ({ recommendResult }) => { const navigate = useNavigate(); return ( diff --git a/src/pages/writeReview/components/FireTemperatureSlider.jsx b/src/pages/writeReview/components/FireTemperatureSlider.jsx index e40c1b6..67a5bb3 100644 --- a/src/pages/writeReview/components/FireTemperatureSlider.jsx +++ b/src/pages/writeReview/components/FireTemperatureSlider.jsx @@ -1,4 +1,4 @@ -import { useState, useRef, useEffect } from "react"; +import { useRef } from "react"; import { motion } from "framer-motion"; const FireTemperatureSlider = ({ temperature, setTemperature }) => { diff --git a/src/pages/writeReview/components/SelectTag.jsx b/src/pages/writeReview/components/SelectTag.jsx index 97cc261..4225bd1 100644 --- a/src/pages/writeReview/components/SelectTag.jsx +++ b/src/pages/writeReview/components/SelectTag.jsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; +import { useState } from "react"; import { tagList } from "@/constants/review/tagList"; import { usePaymentStore } from "@/store/paymentStore"; import { useNavigate } from "react-router-dom"; @@ -8,7 +8,6 @@ const SelectTag = ({ onNext }) => { const navigate = useNavigate(); const setReviewInfo = usePaymentStore((s) => s.setReviewInfo); - const { reviewInfo } = usePaymentStore(); const [selectedTags, setSelectedTags] = useState([]); const [temperature, setTemperature] = useState(50); @@ -58,8 +57,6 @@ const SelectTag = ({ onNext }) => { 슬라이드하여 온도를 남겨 보세요.

- {/*

0도

*/} - {/* */} { const navigate = useNavigate(); @@ -14,6 +16,16 @@ const WriteText = ({ onNext, onBack }) => { const [isUploading, setIsUploading] = useState(false); const { data, isLoading } = useMyProfile(); + const [toast, setToast] = useState({ + show: false, + message: "", + icon: null, + }); + const fireToast = (message, icon = ErrorIcon, duration = 4000) => { + setToast({ show: true, message, icon }); + setTimeout(() => setToast((t) => ({ ...t, show: false })), duration); + }; + const handleClick = async () => { setIsUploading(true); // 모달 띄우기 @@ -24,7 +36,7 @@ const WriteText = ({ onNext, onBack }) => { } catch (e) { console.log(e); setIsUploading(false); // 실패 시도 닫기 - alert("리뷰 등록에 실패했습니다. 다시 시도해 주세요."); + fireToast("리뷰 등록에 실패했습니다.\n다시 시도해 주세요.", ErrorIcon); } }; @@ -32,6 +44,7 @@ const WriteText = ({ onNext, onBack }) => {
{isUploading && } + {/* 닫기 버튼 */}
{ 다음
+ + {toast.show && ( + setToast((t) => ({ ...t, show: false }))} + /> + )}
); }; diff --git a/src/store/paymentStore.js b/src/store/paymentStore.js index b3c224a..2709802 100644 --- a/src/store/paymentStore.js +++ b/src/store/paymentStore.js @@ -1,4 +1,3 @@ -// src/store/paymentStore.js import { create } from "zustand"; // Date → "yyyy/MM/dd HH:mm:ss" 포맷 함수 @@ -14,10 +13,7 @@ export const formatToYMDHMS = (date) => { }; export const usePaymentStore = create((set) => ({ - // 상태 필드 - paymentTime: "", // "yyyy/MM/dd HH:mm:ss" companyId: "", // 회사 ID - receiptInfo: null, // OCR 결과 전체 보관 reviewInfo: null, // 🆕 리뷰 정보 객체 setReviewInfo: (info) => @@ -32,35 +28,7 @@ export const usePaymentStore = create((set) => ({ set({ reviewInfo: null }); }, - // 액션들 - setPaymentTime: (raw) => { - const dateObj = raw instanceof Date ? raw : new Date(raw); - if (isNaN(dateObj.getTime())) { - console.warn("[paymentStore] Invalid date:", raw); - return; - } - set({ paymentTime: formatToYMDHMS(dateObj) }); - }, - setCompanyId: (id) => { set({ companyId: String(id) }); }, - - setReceiptInfo: (info) => { - if (!info || typeof info !== "object") { - console.warn("[paymentStore] Invalid receiptInfo:", info); - return; - } - const updates = { receiptInfo: info }; - - // 날짜 있으면 paymentTime 값도 반영 - if (info.orderDateTime) { - const date = new Date(info.orderDateTime.replace(/[-]/g, "/")); - if (!isNaN(date.getTime())) updates.paymentTime = formatToYMDHMS(date); - } - - if (info.companyId) updates.companyId = String(info.companyId); - - set(updates); - }, }));