diff --git a/public/images/1.jpeg b/public/images/1.jpeg deleted file mode 100644 index 46b18b1..0000000 Binary files a/public/images/1.jpeg and /dev/null differ diff --git a/public/images/2.jpeg b/public/images/2.jpeg deleted file mode 100644 index c4b4b76..0000000 Binary files a/public/images/2.jpeg and /dev/null differ diff --git a/public/images/kakao_login_large_wide.png b/public/images/kakao_login_large_wide.png deleted file mode 100644 index c0c1856..0000000 Binary files a/public/images/kakao_login_large_wide.png and /dev/null differ diff --git a/public/images/sample1.jpeg b/public/images/sample1.jpeg deleted file mode 100644 index 3046fe6..0000000 Binary files a/public/images/sample1.jpeg and /dev/null differ diff --git a/public/images/sample2.jpeg b/public/images/sample2.jpeg deleted file mode 100644 index 873ad00..0000000 Binary files a/public/images/sample2.jpeg and /dev/null differ diff --git a/public/images/sample3.jpeg b/public/images/sample3.jpeg deleted file mode 100644 index 62c9df4..0000000 Binary files a/public/images/sample3.jpeg and /dev/null differ diff --git a/public/images/sample4.jpeg b/public/images/sample4.jpeg deleted file mode 100644 index 81eaacb..0000000 Binary files a/public/images/sample4.jpeg and /dev/null differ diff --git a/public/svgs/myPage/backIcon.svg b/public/svgs/myPage/backIcon.svg new file mode 100644 index 0000000..04f86a8 --- /dev/null +++ b/public/svgs/myPage/backIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/svgs/myPage/cheer.svg b/public/svgs/myPage/cheer.svg new file mode 100644 index 0000000..6d7ffa9 --- /dev/null +++ b/public/svgs/myPage/cheer.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/svgs/myPage/heart.svg b/public/svgs/myPage/heart.svg new file mode 100644 index 0000000..b1023c7 --- /dev/null +++ b/public/svgs/myPage/heart.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/svgs/myPage/noResult.svg b/public/svgs/myPage/noResult.svg new file mode 100644 index 0000000..6626d1a --- /dev/null +++ b/public/svgs/myPage/noResult.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/svgs/myPage/review.svg b/public/svgs/myPage/review.svg new file mode 100644 index 0000000..bb2f1fd --- /dev/null +++ b/public/svgs/myPage/review.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/apis/myPage/getDetail.js b/src/apis/myPage/getDetail.js new file mode 100644 index 0000000..b77835c --- /dev/null +++ b/src/apis/myPage/getDetail.js @@ -0,0 +1,11 @@ +import api from "@/apis/instance/api"; + +export const getReviews = async () => { + const res = await api.get("/reviews/get-all-member-reviews"); + return res.data; +}; + +export const getCheers = async () => { + const res = await api.get("/story/member-likes"); + return res.data; +}; diff --git a/src/apis/myPage/queries.js b/src/apis/myPage/queries.js new file mode 100644 index 0000000..85049cd --- /dev/null +++ b/src/apis/myPage/queries.js @@ -0,0 +1,11 @@ +import { useQuery } from "@tanstack/react-query"; +import { getReviews } from "@/apis/myPage/getDetail"; +import { getCheers } from "@/apis/myPage/getDetail"; + +export const useGetReviews = () => { + return useQuery({ queryKey: ["userReview"], queryFn: () => getReviews() }); +}; + +export const useGetCheers = () => { + return useQuery({ queryKey: ["userCheers"], queryFn: () => getCheers() }); +}; diff --git a/src/apis/review/getPresignedUrl.js b/src/apis/review/getPresignedUrl.js deleted file mode 100644 index f55008f..0000000 --- a/src/apis/review/getPresignedUrl.js +++ /dev/null @@ -1,18 +0,0 @@ -import api from "@/apis/instance/api"; - -// 임시 url을 가져오는 함수이므로 캐싱이 의미가 없어 tanstack query로는 작성하지 않음. -export const getPresignedUrl = async (originalFileName, type, kakaoId) => { - try { - const res = await api.get("/files/presigned-url", { - params: { - originalFileName: originalFileName, - type: type, - kakaoId: kakaoId, - }, - }); - return res.data; - } catch (error) { - console.log("pre-signed url fetching error!"); - throw error; // 호출한 쪽에서도 에러 처리 가능하도록 throw - } -}; diff --git a/src/apis/review/likeToggle.js b/src/apis/review/likeToggle.js deleted file mode 100644 index ba478d6..0000000 --- a/src/apis/review/likeToggle.js +++ /dev/null @@ -1,15 +0,0 @@ -// src/apis/company/likeToggleCompany.js -import api from "@/apis/instance/api"; - -export const likeToggleCompany = async (companyId) => { - try { - if (!companyId) throw new Error("companyId is required"); - - // 좋아요 해제 → 다시 누르면 좋아요 요청만 보내는 방식 - const res = await api.post("/company/save", { params: companyId }); - return res.data; - } catch (err) { - console.error("찜 요청 실패:", err); - throw err; - } -}; diff --git a/src/apis/review/postImageKey.js b/src/apis/review/postImageKey.js deleted file mode 100644 index 70a441f..0000000 --- a/src/apis/review/postImageKey.js +++ /dev/null @@ -1,6 +0,0 @@ -import api from "@/apis/instance/api"; - -export const postImageKey = async () => { - const res = await api.post("/review/image-key"); - return res.data; -}; diff --git a/src/apis/review/postRecipt.js b/src/apis/review/postRecipt.js index 1ce3ecc..8eb3b71 100644 --- a/src/apis/review/postRecipt.js +++ b/src/apis/review/postRecipt.js @@ -1,7 +1,6 @@ import api from "@/apis/instance/api"; export const postRecipt = async (formData) => { - // axios에 formData를 던지면, multipart/form-data; boundary=... 를 자동으로 붙여줍니다. const res = await api.post("/naver/receipt", formData); return res.data; }; diff --git a/src/apis/review/postReview.js b/src/apis/review/postReview.js index 566d3ab..2057b37 100644 --- a/src/apis/review/postReview.js +++ b/src/apis/review/postReview.js @@ -4,8 +4,8 @@ export const postReview = async (reviewInfo, companyId, text) => { try { const res = await api.post("/reviews/write", { paymentInfoConfirmNum: reviewInfo.paymentInfoConfirmNum, - paymentInfoTime: reviewInfo.paymentInfoTime, // "2025-04-22T10:00:00" - companyId: companyId, // { companyId: 1 } + paymentInfoTime: reviewInfo.paymentInfoTime, + companyId: companyId, review: text, temperature: reviewInfo.temperature, reviewCategories: reviewInfo.reviewCategory, @@ -13,7 +13,7 @@ export const postReview = async (reviewInfo, companyId, text) => { return res.data; } catch (error) { console.error("[postReview] 리뷰 등록 실패:", error); - // 선택적으로 사용자에게 에러 메시지를 보여주거나 오류 객체 반환 가능 + throw error; } }; diff --git a/src/apis/review/uploadImageToS3.js b/src/apis/review/uploadImageToS3.js deleted file mode 100644 index fc9c484..0000000 --- a/src/apis/review/uploadImageToS3.js +++ /dev/null @@ -1,15 +0,0 @@ -import axios from "axios"; - -export const uploadImageToS3 = async (url, file) => { - const res = await axios.put(url, file, { - headers: { - "Content-Type": file.type, - }, - }); - - if (res.status !== 200) { - throw new Error("S3 업로드에 실패했습니다."); - } - - return true; -}; diff --git a/src/components/common/HaveToLoginModal.jsx b/src/components/common/HaveToLoginModal.jsx index 1c6caa8..cdd1307 100644 --- a/src/components/common/HaveToLoginModal.jsx +++ b/src/components/common/HaveToLoginModal.jsx @@ -23,7 +23,6 @@ const HaveToLoginModal = ({ {showClose && ( - {/* ✅ Picker 가운데 정렬 */}
diff --git a/src/pages/map/components/picker/Picker.jsx b/src/pages/map/components/picker/Picker.jsx index 50b8867..593933d 100644 --- a/src/pages/map/components/picker/Picker.jsx +++ b/src/pages/map/components/picker/Picker.jsx @@ -53,7 +53,6 @@ const Picker = ({ list, onSelectedChange }) => { onScroll={handleScroll} className="list-none p-0 m-0 w-full h-[250px] overflow-y-scroll scroll-smooth no-scrollbar" > - {/* 가운데 선택 라인 */}
{paddedList.map((item, index) => { diff --git a/src/pages/map/components/picker/TimePickerSheet.jsx b/src/pages/map/components/picker/TimePickerSheet.jsx index 0b29767..7691be9 100644 --- a/src/pages/map/components/picker/TimePickerSheet.jsx +++ b/src/pages/map/components/picker/TimePickerSheet.jsx @@ -19,10 +19,8 @@ const TimePickerSheet = ({ return (
- {/* 바깥 영역 클릭 시 닫기 */}
- {/* ✅ 슬라이드 애니메이션 바텀시트 */} { +const ReviewContent = ({ item, hasBorder = true }) => { const scrollRef = useDragScroll(); const matchedTags = tagList.filter((tag) => @@ -9,11 +9,16 @@ const ReviewContent = ({ item }) => { ); const profileIconSrc = `/svgs/profile/${item.profileColor || "gray"}.svg`; + return ( -
+
-

{item.name}

+

{item?.name}

@@ -22,10 +27,12 @@ const ReviewContent = ({ item }) => { {item?.temperature?.toFixed(0) || 0}도

+

{item?.reviewContent}

+
{matchedTags.map((tag, idx) => ( diff --git a/src/pages/map/components/review/ReviewList.jsx b/src/pages/map/components/review/ReviewList.jsx index 016ecc6..a1d548e 100644 --- a/src/pages/map/components/review/ReviewList.jsx +++ b/src/pages/map/components/review/ReviewList.jsx @@ -1,15 +1,14 @@ import { useState } from "react"; import ReviewContent from "@/pages/map/components/review/ReviewContent"; -import { Link, useNavigate } from "react-router-dom"; +import { Link } from "react-router-dom"; import { useGetStoreReviewCount, useStoreReviews } from "@/apis/review/queries"; import { getMyProfile } from "@/apis/member/auth"; import HaveToLoginModal from "@/components/common/HaveToLoginModal"; const ReviewList = ({ setTurnOnCamera, companyId }) => { - const navigate = useNavigate(); const { data: count, isLoading } = useGetStoreReviewCount(companyId); const { data: preview } = useStoreReviews(companyId); - const [loginModalConfig, setLoginModalConfig] = useState(null); // 모달 상태를 객체로 관리 + const [loginModalConfig, setLoginModalConfig] = useState(null); const handleClickWrite = async () => { try { diff --git a/src/pages/myPage/MyPage.jsx b/src/pages/myPage/MyPage.jsx index 54118ff..9135835 100644 --- a/src/pages/myPage/MyPage.jsx +++ b/src/pages/myPage/MyPage.jsx @@ -9,6 +9,8 @@ import { useNavigate } from "react-router-dom"; import useAuthStore from "@/store/authStore"; import ErrorIcon from "/public/svgs/modal/errorIcon.svg?react"; import ToastModal from "@/components/common/ToastModal"; +import Spinner from "@/components/common/Spinner"; +import useUserInfoStore from "@/store/userInfoStore"; const LogoutSuccessModal = ({ onClose }) => (
@@ -28,6 +30,14 @@ const MyPage = () => { const navigate = useNavigate(); const { data, isLoading, error } = useMyProfile(); + const { setUserInfo } = useUserInfoStore(); + + useEffect(() => { + if (data) { + setUserInfo(data.name, data.address, data.profileColor); + } + }, [data]); + const [showLoginModal, setShowLoginModal] = useState(false); const { data: reviewCount } = useGetReviewCountOfMember(); const { data: cheerCount } = useGetCheerCountOfMember(); @@ -71,8 +81,8 @@ const MyPage = () => { if (isLoading) { return ( -
-

로딩 중…

+
+
); } @@ -91,32 +101,34 @@ const MyPage = () => { return (
- {/* 로그아웃 완료 모달 */} {showLogoutModal && navigate("/")} />} - {/* 카드 */} +
- {/* 수정 아이콘 */} - {/* 닉네임 & 지역 */} +

{nickname}

{location}

- {/* 구분선 */}
- {/* 찜/리뷰/응원 */}
{Object.entries(counts).map(([label, count]) => ( -
+
+ navigate("/mypage/detail", { state: { kind: label } }) + } + className="cursor-pointer" + >

{count}

{label}

))}
- {/* 프로필 이미지 (카드 위로 걸치기) */} +
{ className="w-24 h-24 rounded-full border-4 border-white shadow-md" />
- {/* 로그아웃 버튼 (화면 하단 오른쪽) */} +
{ + const navigate = useNavigate(); + const location = useLocation(); + const kind = location.state.kind; + + const { data: reviewsData, isLoading: isLoadingReviews } = useGetReviews({ + enabled: kind === "리뷰", + }); + const { data: cheersData, isLoading: isLoadingCheers } = useGetCheers({ + enabled: kind === "응원", + }); + // const { data: heartsData, isLoading: isLoadingHearts } = useGetHearts({ enabled: kind === "찜" }); + + return ( +
+
navigate(-1)}> + +
+ + {kind === "리뷰" && ( +
+
+ +

내가 작성한 리뷰

+
+

총 {reviewsData?.length}개

+
+ )} + + {kind === "찜" && ( +
+
+ +

저장한 장소

+
+

총 0개

+
+ )} + + {kind === "응원" && ( +
+
+ +

내가 응원한 이야기

+
+

총 {cheersData?.length}개

+
+ )} + +
+ {kind === "리뷰" && ( + <> + {reviewsData?.map((item, idx) => ( + + ))} + + )} + + {kind === "응원" && + cheersData.map((item) => ( + + ))} +
+
+ ); +}; + +export default MyPageDetailPage; diff --git a/src/pages/myPageDetail/components/ReviewItem.jsx b/src/pages/myPageDetail/components/ReviewItem.jsx new file mode 100644 index 0000000..6ca3ffa --- /dev/null +++ b/src/pages/myPageDetail/components/ReviewItem.jsx @@ -0,0 +1,132 @@ +import ReviewContent from "@/pages/map/components/review/ReviewContent"; +import { useNavigate } from "react-router-dom"; +import { useGetCompanyPreview } from "@/apis/company/queries"; +import { businessTypeNameMap } from "@/constants/categoryMap"; +import { useState, useEffect } from "react"; +import { + getLikedCompanies, + likeCompany, + unlikeCompany, +} from "@/apis/company/getLikedCompanies"; +import useAuthStore from "@/store/authStore"; +import useLikeStore from "@/store/useLikeStore"; +import HaveToLoginModal from "@/components/common/HaveToLoginModal"; +import useUserInfoStore from "@/store/userInfoStore"; + +const dummyReviewItem = { + name: "김소영", + profileColor: "pink", + temperature: 74.6, + reviewContent: + "제품 품질도 후드러고 청건하게 잘 관리되어 있어요. 다시 방문하고 싶어요!", + reviewCategories: ["GOOD_QUALITY", "CLEAN", "REVISIT"], +}; + +const ReviewItem = ({ data }) => { + const navigate = useNavigate(); + const { data: companyData } = useGetCompanyPreview(data.companyId); + const [showLoginModal, setShowLoginModal] = useState(false); + const [isLiked, setIsLiked] = useState(false); + const [isLoggedIn, setIsLoggedIn] = useState(false); + const [loading, setLoading] = useState(true); + + const { userInfo } = useUserInfoStore(); + + const { likedMap, setLike } = useLikeStore(); + const isGloballyLiked = likedMap[data.companyId] ?? false; + + const combinedReviews = { + ...data, + name: userInfo.name, + profileColor: userInfo.profileColor, + }; + + useEffect(() => { + const checkLoginAndLiked = async () => { + const isAuthenticated = await useAuthStore.getState().checkAuth(); + setIsLoggedIn(isAuthenticated); + + if (isAuthenticated && data?.companyId) { + const likedList = await getLikedCompanies(); + const liked = likedList.some( + (c) => String(c.companyId) === String(data.companyId) + ); + setIsLiked(liked); + setLike(data.companyId, liked); + } + setLoading(false); + }; + + if (data?.companyId) checkLoginAndLiked(); + }, [data?.companyId]); + + const handleLikeClick = async () => { + const isAuthenticated = await useAuthStore.getState().checkAuth(); + setIsLoggedIn(isAuthenticated); + + if (!isAuthenticated) { + setShowLoginModal(true); + return; + } + + try { + setLoading(true); + const currentLiked = likedMap[data.companyId] ?? false; + + if (currentLiked) { + await unlikeCompany(data.companyId); + } else { + await likeCompany(data.companyId); + } + + const newLiked = !currentLiked; + setIsLiked(newLiked); + setLike(data.companyId, newLiked); + } catch (e) { + console.error("좋아요 토글 실패:", e); + } finally { + setLoading(false); + } + }; + + return ( +
navigate(`/review/${data.companyId}`)} + > +
+
+
+

{companyData?.companyName}

+

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

+
+

{companyData?.companyLocation}

+
+ +
+ +
+ +
+
+ ); +}; + +export default ReviewItem; diff --git a/src/pages/myPageDetail/components/StoryItem.jsx b/src/pages/myPageDetail/components/StoryItem.jsx new file mode 100644 index 0000000..6a09509 --- /dev/null +++ b/src/pages/myPageDetail/components/StoryItem.jsx @@ -0,0 +1,35 @@ +import { useNavigate } from "react-router-dom"; + +const StoryItem = ({ item }) => { + const navigate = useNavigate(); + + return ( +
navigate(`/story/${item?.storyId}`)} + > +
+ + +
+ +
+
+ +

이음숲, 숲과 사람을 이어주는 따뜻한 다리

+

2023년 3월 2일

+
+ ); +}; + +export default StoryItem; diff --git a/src/pages/review/StoreReviewPage.jsx b/src/pages/review/StoreReviewPage.jsx index 50b502a..631ce52 100644 --- a/src/pages/review/StoreReviewPage.jsx +++ b/src/pages/review/StoreReviewPage.jsx @@ -24,11 +24,19 @@ const StoreReviewPage = () => { error: placeError, } = useGetCompanyPreview(companyId); + const handleBack = () => { + const prev = sessionStorage.getItem("prevPath"); + if (prev === "/writeReview") { + navigate("/"); + } else { + navigate(-1); + } + }; + if (placeLoading) { return (
-

잠시만 기다려주세요…

); } @@ -47,7 +55,7 @@ const StoreReviewPage = () => { navigate("/")} + onClick={handleBack} />
diff --git a/src/pages/review/components/PlaceInfo.jsx b/src/pages/review/components/PlaceInfo.jsx index 027da2e..fca06ff 100644 --- a/src/pages/review/components/PlaceInfo.jsx +++ b/src/pages/review/components/PlaceInfo.jsx @@ -160,7 +160,7 @@ const PlaceInfo = ({ placeInfo }) => { src={ isLoggedIn && isLiked ? "/svgs/storeReview/fullHeartIcon.svg" - : "/svgs/storeReview/emptyHeartIcon.svg" + : "/svgs/Ic_Heart-Empty.svg" } alt="좋아요 버튼" className="w-6 h-6" diff --git a/src/pages/story/StoryPage.jsx b/src/pages/story/StoryPage.jsx index 58c4a05..d111230 100644 --- a/src/pages/story/StoryPage.jsx +++ b/src/pages/story/StoryPage.jsx @@ -11,7 +11,6 @@ const StoryPage = () => { {isLoading && (
-

잠시만 기다려주세요…

)} diff --git a/src/pages/story/components/StoryDetail.jsx b/src/pages/story/components/StoryDetail.jsx index cf1717c..6955ba1 100644 --- a/src/pages/story/components/StoryDetail.jsx +++ b/src/pages/story/components/StoryDetail.jsx @@ -60,7 +60,6 @@ const StoryDetail = () => { {isLoading && (
-

잠시만 기다려주세요…

)} { {isLoading && (
-

잠시만 기다려주세요…

)} { const navigate = useNavigate(); + const scrollRef = useDragScroll(); + const isDragging = useRef(false); + const { data: stories = [], isLoading } = useGetRecentStory(); + const handleMouseDown = () => { + isDragging.current = false; + }; + + const handleMouseMove = () => { + isDragging.current = true; + }; + + const handleClick = (storyId) => { + if (!isDragging.current) { + navigate(`/story/${storyId}`); + } + }; + if (isLoading) { return

스토리를 불러오는 중입니다…

; } return ( -
+
{stories.map((story) => (
navigate(`/story/${story.storyId}`)} + onClick={() => handleClick(story.storyId)} + onDragStart={(e) => e.preventDefault()} className="relative w-64 sm:w-72 md:w-80 aspect-[255/182] flex-shrink-0 rounded-md overflow-hidden cursor-pointer" > {story.storyTitle} 이동 아이콘 -
+
{ {story.storyLikes || 0}
- -

- {story.storyTitle} -

+

{story.storyTitle}

))} diff --git a/src/pages/story/components/content/SlideContent.jsx b/src/pages/story/components/content/SlideContent.jsx index 59b7d05..5b19fdc 100644 --- a/src/pages/story/components/content/SlideContent.jsx +++ b/src/pages/story/components/content/SlideContent.jsx @@ -18,16 +18,11 @@ const SlideContent = ({ data, title }) => { className="w-full h-full object-cover" /> - {/* 상단 넘버링 */} - {/*
- {num} -
*/} - {/* 좋아요 + 텍스트 박스 */}
{/* 좋아요 아이콘 + 숫자 */}
@@ -41,7 +36,9 @@ const SlideContent = ({ data, title }) => {
{/* 제목 */} -
{data.storyTitle}
+
+ {data.storyTitle} +
); diff --git a/src/pages/story/utils/grabbing.js b/src/pages/story/utils/grabbing.js deleted file mode 100644 index e69de29..0000000 diff --git a/src/pages/support/SupportItemPage.jsx b/src/pages/support/SupportItemPage.jsx index 5fa3e97..7b193d6 100644 --- a/src/pages/support/SupportItemPage.jsx +++ b/src/pages/support/SupportItemPage.jsx @@ -34,7 +34,6 @@ const SupportItemPage = () => { {isLoading && (
-

잠시만 기다려주세요…

)} {/* 이미지 영역 */} diff --git a/src/pages/support/SupportListPage.jsx b/src/pages/support/SupportListPage.jsx index c5107d0..213bd2e 100644 --- a/src/pages/support/SupportListPage.jsx +++ b/src/pages/support/SupportListPage.jsx @@ -7,7 +7,7 @@ const SupportListPage = () => { const { data, isLoading } = useGetAnnouncement(); return ( -
+

진행 중인 지원사업

{ }, [category]); return ( -
+
-
- {isLoading ? ( -
-
-
- ) : ( - data?.map((item, idx) => ( -
- -
- )) - )} -
+ {isLoading ? ( +
+ +
+ ) : ( + + {announces.map((item) => ( + +
+ +
+
+ ))} +
+ )} + {/* ───── 하단 맞춤 금융 영역 ───── */}

내게 맞는 금융 상품

+

기업 정보를 입력해 주세요

diff --git a/src/pages/support/components/FOACard.jsx b/src/pages/support/components/FOACard.jsx index 6ca3800..f4e3068 100644 --- a/src/pages/support/components/FOACard.jsx +++ b/src/pages/support/components/FOACard.jsx @@ -1,44 +1,24 @@ import { useNavigate } from "react-router-dom"; +import { formatEndDate, calculateDday } from "@/pages/support/utils/dateFunc"; const FOACard = ({ data }) => { const navigate = useNavigate(); - // 날짜 포맷: 2025-06-05 → 6월 5일까지 - const formatEndDate = (raw) => { - const match = raw?.match(/^(\d{4})-(\d{2})-(\d{2})$/); - if (!match) return raw; - const [, , mm, dd] = match; - return `${parseInt(mm, 10)}월 ${parseInt(dd, 10)}일까지`; - }; - - // D-day 계산: 2025-06-05 → D-3 - const calculateDday = (raw) => { - const match = raw?.match(/^(\d{4})-(\d{2})-(\d{2})$/); - if (!match) return null; - - const targetDate = new Date(`${match[1]}-${match[2]}-${match[3]}`); - const today = new Date(); - today.setHours(0, 0, 0, 0); - targetDate.setHours(0, 0, 0, 0); - - const diffTime = targetDate - today; - const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); - return diffDays >= 0 ? `D-${diffDays}` : null; - }; - const formattedEndDate = formatEndDate(data.endDate); const dday = calculateDday(data.endDate); + const handleClick = () => { + if (data?.id) { + navigate(`/support/list/${data.id}`); + } else { + navigate("/support/list"); + } + }; + return (
{ - if (data?.id) { - navigate(`/support/list/${data.id}`); - } else { - navigate("/support/list"); - } - }} + onClick={handleClick} > {/* 상단: 날짜 & D-day */}
diff --git a/src/pages/support/components/FOAItem.jsx b/src/pages/support/components/FOAItem.jsx index f4e89fb..f03c709 100644 --- a/src/pages/support/components/FOAItem.jsx +++ b/src/pages/support/components/FOAItem.jsx @@ -1,31 +1,8 @@ import { useNavigate } from "react-router-dom"; - +import { formatEndDate, calculateDday } from "@/pages/support/utils/dateFunc"; const FOAItem = ({ data }) => { const navigate = useNavigate(); - // 날짜 형식인지 확인하고 "M월 D일까지"로 변환 - const formatEndDate = (raw) => { - const match = raw?.match(/^(\d{4})-(\d{2})-(\d{2})$/); - if (!match) return raw; - const [, , mm, dd] = match; - return `${parseInt(mm, 10)}월 ${parseInt(dd, 10)}일까지`; - }; - - // 날짜 형식이면 D-day 계산, 아니면 null 반환 - const calculateDday = (raw) => { - const match = raw?.match(/^(\d{4})-(\d{2})-(\d{2})$/); - if (!match) return null; - - const targetDate = new Date(`${match[1]}-${match[2]}-${match[3]}`); - const today = new Date(); - today.setHours(0, 0, 0, 0); // 자정 기준 - targetDate.setHours(0, 0, 0, 0); - - const diffTime = targetDate - today; - const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); - return diffDays >= 0 ? `D-${diffDays}` : null; - }; - const formattedEndDate = formatEndDate(data.endDate); const dday = calculateDday(data.endDate); diff --git a/src/pages/support/components/step/Step6.jsx b/src/pages/support/components/step/Step6.jsx index 550c23c..4c6b7c0 100644 --- a/src/pages/support/components/step/Step6.jsx +++ b/src/pages/support/components/step/Step6.jsx @@ -36,7 +36,6 @@ const Step6 = ({ onNext, defaultValue, userInfo, setRecommendResult }) => { {isLoading && (
-

잠시만 기다려주세요…

)} diff --git a/src/pages/support/utils/dateFunc.js b/src/pages/support/utils/dateFunc.js new file mode 100644 index 0000000..25d2906 --- /dev/null +++ b/src/pages/support/utils/dateFunc.js @@ -0,0 +1,20 @@ +export const formatEndDate = (raw) => { + const match = raw?.match(/^(\d{4})-(\d{2})-(\d{2})$/); + if (!match) return raw; + const [, , mm, dd] = match; + return `${parseInt(mm, 10)}월 ${parseInt(dd, 10)}일까지`; +}; + +export const calculateDday = (raw) => { + const match = raw?.match(/^(\d{4})-(\d{2})-(\d{2})$/); + if (!match) return null; + + const targetDate = new Date(`${match[1]}-${match[2]}-${match[3]}`); + const today = new Date(); + today.setHours(0, 0, 0, 0); + targetDate.setHours(0, 0, 0, 0); + + const diffTime = targetDate - today; + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + return diffDays >= 0 ? `D-${diffDays}` : null; +}; diff --git a/src/routes/router.jsx b/src/routes/router.jsx index 522ff8d..14274db 100644 --- a/src/routes/router.jsx +++ b/src/routes/router.jsx @@ -8,7 +8,6 @@ import StoreReviewPage from "@pages/review/StoreReviewPage"; import SearchPage from "@pages/search/SearchPage"; import StoryPage from "@pages/story/StoryPage"; import SupportPage from "@pages/support/SupportPage"; -import ErrorPage from "@pages/error/ErrorPage"; import StoryDetail from "@pages/story/components/StoryDetail"; import WriteReviewPage from "@/pages/writeReview/WriteReviewPage"; import SignUp from "@pages/join/SignUp"; @@ -17,11 +16,11 @@ import SupportItemPage from "@pages/support/SupportItemPage"; import SupportRecommendPage from "@pages/support/SupportRecommendPage"; import FinancialProductList from "@pages/support/FinancialProductListPage"; import FinancialProductDetailPage from "@pages/support/FinancialProductDetailPage"; +import MyPageDetailPage from "@/pages/myPageDetail/MyPageDetailPage"; const router = createBrowserRouter([ { element: , // 하단 탭이 있는 Layout - errorElement: , children: [ { path: "/", @@ -43,6 +42,10 @@ const router = createBrowserRouter([ path: "/mypage", element: , }, + { + path: "/mypage/detail", + element: , + }, { path: "/review/:companyId", element: , diff --git a/src/store/useLikeStore.js b/src/store/useLikeStore.js new file mode 100644 index 0000000..af42f2d --- /dev/null +++ b/src/store/useLikeStore.js @@ -0,0 +1,14 @@ +import { create } from "zustand"; + +const useLikeStore = create((set) => ({ + likedMap: {}, + setLike: (companyId, liked) => + set((state) => ({ + likedMap: { + ...state.likedMap, + [companyId]: liked, + }, + })), +})); + +export default useLikeStore; diff --git a/src/store/userInfoStore.js b/src/store/userInfoStore.js new file mode 100644 index 0000000..53f7237 --- /dev/null +++ b/src/store/userInfoStore.js @@ -0,0 +1,19 @@ +import { create } from "zustand"; + +const useUserInfoStore = create((set) => ({ + userInfo: { + name: "", + address: "", + profileColor: "", + }, + setUserInfo: (name, address, profileColor) => + set(() => ({ + userInfo: { + name, + address, + profileColor, + }, + })), +})); + +export default useUserInfoStore; diff --git a/src/styles/spinner.css b/src/styles/spinner.css index e274cf4..2598f14 100644 --- a/src/styles/spinner.css +++ b/src/styles/spinner.css @@ -1,5 +1,3 @@ -/* /src/styles/spinner.css */ - .loader { width: 50px; aspect-ratio: 1;