diff --git a/src/hooks/useLikeToggle.js b/src/hooks/useLikeToggle.js new file mode 100644 index 0000000..79ddedf --- /dev/null +++ b/src/hooks/useLikeToggle.js @@ -0,0 +1,66 @@ +import { useState, useEffect } from "react"; +import { + getLikedCompanies, + likeCompany, + unlikeCompany, +} from "@/apis/company/getLikedCompanies"; +import useAuthStore from "@/store/authStore"; +import useLikeStore from "@/store/useLikeStore"; + +export const useLikeToggle = (companyId) => { + const { isLoggedIn } = useAuthStore(); + const [isLiked, setIsLiked] = useState(false); + const [loading, setLoading] = useState(true); + const [showLoginModal, setShowLoginModal] = useState(false); + + const { likedMap, setLike } = useLikeStore(); + + // 로그인 및 좋아요 여부 확인 + useEffect(() => { + const check = async () => { + if (isLoggedIn && companyId) { + const likedList = await getLikedCompanies(); + const liked = likedList.some( + (c) => String(c.companyId) === String(companyId), + ); + setIsLiked(liked); + setLike(companyId, liked); + } + setLoading(false); + }; + + if (companyId) check(); + }, [companyId]); + + // 좋아요 토글 함수 + const toggleLike = async () => { + if (!isLoggedIn) { + setShowLoginModal(true); + return; + } + + try { + const currentLiked = likedMap[companyId] ?? false; + + if (currentLiked) { + await unlikeCompany(companyId); + } else { + await likeCompany(companyId); + } + const newLiked = !currentLiked; + setIsLiked(newLiked); + setLike(companyId, newLiked); + } catch (e) { + console.error("좋아요 토글 실패:", e); + } + }; + + return { + isLiked, + isLoggedIn, + loading, + toggleLike, + showLoginModal, + setShowLoginModal, + }; +}; diff --git a/src/pages/map/components/review/ReviewContent.jsx b/src/pages/map/components/review/ReviewContent.jsx index 3d8cdd9..7f9d43e 100644 --- a/src/pages/map/components/review/ReviewContent.jsx +++ b/src/pages/map/components/review/ReviewContent.jsx @@ -8,7 +8,7 @@ const ReviewContent = ({ item, hasBorder = true }) => { item.reviewCategories?.includes(tag.value) ); - const profileIconSrc = `/svgs/profile/${item.profileColor || "gray"}.svg`; + const profileIconSrc = `/svgs/profile/${item?.profileColor || "gray"}.svg`; return (
{ const navigate = useNavigate(); @@ -20,6 +21,14 @@ const MyPageDetailPage = () => { }); // const { data: heartsData, isLoading: isLoadingHearts } = useGetHearts({ enabled: kind === "찜" }); + if (isLoadingReviews || isLoadingCheers) { + return ( +
+ +
+ ); + } + return (
navigate(-1)}> @@ -38,7 +47,7 @@ const MyPageDetailPage = () => { {kind === "찜" && (
-
+

저장한 장소

@@ -48,7 +57,7 @@ const MyPageDetailPage = () => { {kind === "응원" && (
-
+

내가 응원한 이야기

@@ -59,11 +68,11 @@ const MyPageDetailPage = () => {
{kind === "리뷰" && ( <> - {reviewsData?.length > 0 ? ( + {!isLoadingReviews && reviewsData?.length > 0 ? ( reviewsData.map((item, idx) => ( )) - ) : ( + ) : !isLoadingReviews && reviewsData?.length === 0 ? (

@@ -71,17 +80,17 @@ const MyPageDetailPage = () => {
없어요

- )} + ) : null} )} {kind === "응원" && ( <> - {cheersData?.length > 0 ? ( + {!isLoadingCheers && cheersData?.length > 0 ? ( cheersData.map((item) => ( )) - ) : ( + ) : !isLoadingCheers && cheersData?.length === 0 ? (

@@ -89,7 +98,7 @@ const MyPageDetailPage = () => {
없어요

- )} + ) : null} )}
diff --git a/src/pages/myPageDetail/components/ReviewItem.jsx b/src/pages/myPageDetail/components/ReviewItem.jsx index 6ca3ffa..231c7e2 100644 --- a/src/pages/myPageDetail/components/ReviewItem.jsx +++ b/src/pages/myPageDetail/components/ReviewItem.jsx @@ -2,93 +2,28 @@ 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"], -}; +import { useLikeToggle } from "@/hooks/useLikeToggle"; 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 { likedMap } = useLikeStore(); const isGloballyLiked = likedMap[data.companyId] ?? false; + const { isLoggedIn, toggleLike } = useLikeToggle(data.companyId); + 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 (
{ )}
diff --git a/src/pages/support/FinancialProductListPage.jsx b/src/pages/support/FinancialProductListPage.jsx index 74f988f..1779873 100644 --- a/src/pages/support/FinancialProductListPage.jsx +++ b/src/pages/support/FinancialProductListPage.jsx @@ -75,7 +75,7 @@ const FinancialProductList = () => { const safeProducts = Array.isArray(sortedProducts) ? sortedProducts : []; return ( -
+

소비한 가치에 맞는 금융상품

{
-

- 총 {safeProducts.length}개 -

+

총 {safeProducts.length}개

{userName}님이 리뷰를 남긴 기업 특성과
연관된 금융상품 순으로 보여드려요! diff --git a/src/pages/support/SupportItemPage.jsx b/src/pages/support/SupportItemPage.jsx index 7b193d6..a641f78 100644 --- a/src/pages/support/SupportItemPage.jsx +++ b/src/pages/support/SupportItemPage.jsx @@ -1,21 +1,10 @@ import { useParams, useNavigate } from "react-router-dom"; import { useEffect } from "react"; import { useGetFOADetail } from "@/apis/announcement/queries"; - -// 날짜 형식 확인 함수 (YYYY-MM-DD) -const isValidDateFormat = (dateStr) => { - return /^\d{4}-\d{2}-\d{2}$/.test(dateStr); -}; - -// D-day 계산 함수 -const calculateDday = (dateStr) => { - const today = new Date(); - today.setHours(0, 0, 0, 0); - const endDate = new Date(dateStr); - endDate.setHours(0, 0, 0, 0); - const diffTime = endDate.getTime() - today.getTime(); - return Math.ceil(diffTime / (1000 * 60 * 60 * 24)); -}; +import { + isValidDateFormat, + calculateDday, +} from "@/pages/support/utils/dateFunc"; const SupportItemPage = () => { const navigate = useNavigate(); @@ -30,7 +19,7 @@ const SupportItemPage = () => { const dday = isDdayAvailable ? calculateDday(data.endDate) : null; return ( -

+
{isLoading && (
@@ -60,7 +49,7 @@ const SupportItemPage = () => {
{isDdayAvailable && (
- D-{dday} + {dday}
)}
@@ -69,7 +58,7 @@ const SupportItemPage = () => {

{data?.title}

-

{data?.agency}

+

{data?.organization}

신청기간

diff --git a/src/pages/support/SupportListPage.jsx b/src/pages/support/SupportListPage.jsx index 213bd2e..d34ce58 100644 --- a/src/pages/support/SupportListPage.jsx +++ b/src/pages/support/SupportListPage.jsx @@ -1,13 +1,23 @@ import FOAItem from "@/pages/support/components/FOAItem"; import { useNavigate } from "react-router-dom"; import { useGetAnnouncement } from "@/apis/announcement/queries"; +import Spinner from "@/components/common/Spinner"; const SupportListPage = () => { const navigate = useNavigate(); const { data, isLoading } = useGetAnnouncement(); + + if (isLoading) { + return ( +
+ +
+ ); + } + return ( -
+

진행 중인 지원사업

{
- {data.map((item, idx) => ( + {data?.map((item, idx) => ( ))}
diff --git a/src/pages/support/SupportRecommendPage.jsx b/src/pages/support/SupportRecommendPage.jsx index 62802e5..d446293 100644 --- a/src/pages/support/SupportRecommendPage.jsx +++ b/src/pages/support/SupportRecommendPage.jsx @@ -33,7 +33,7 @@ const SupportRecommendPage = () => { }; return ( -
+
{ {/* 본문: 텍스트 내용 */}
- {data.announcementType} + {data?.announcementType}

{data.title}

{data.organization}

diff --git a/src/pages/support/components/FOAItem.jsx b/src/pages/support/components/FOAItem.jsx index f03c709..1986f14 100644 --- a/src/pages/support/components/FOAItem.jsx +++ b/src/pages/support/components/FOAItem.jsx @@ -8,7 +8,7 @@ const FOAItem = ({ data }) => { return (
{ if (data?.id) { navigate(`/support/list/${data.id}`); @@ -20,6 +20,9 @@ const FOAItem = ({ data }) => { {/* 상단: 날짜 & D-day */}
+
+ {data?.announcementType} +
{formattedEndDate}
@@ -38,8 +41,8 @@ const FOAItem = ({ data }) => { {/* 본문: 텍스트 내용 */}
-

{data.title}

-

{data.agency}

+

{data?.title}

+

{data?.organization}

); diff --git a/src/pages/support/utils/dateFunc.js b/src/pages/support/utils/dateFunc.js index 25d2906..afd9dac 100644 --- a/src/pages/support/utils/dateFunc.js +++ b/src/pages/support/utils/dateFunc.js @@ -18,3 +18,7 @@ export const calculateDday = (raw) => { const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); return diffDays >= 0 ? `D-${diffDays}` : null; }; + +export const isValidDateFormat = (dateStr) => { + return /^\d{4}-\d{2}-\d{2}$/.test(dateStr); +}; diff --git a/src/pages/writeReview/components/Complete.jsx b/src/pages/writeReview/components/Complete.jsx index 20e90ce..3122b38 100644 --- a/src/pages/writeReview/components/Complete.jsx +++ b/src/pages/writeReview/components/Complete.jsx @@ -9,7 +9,7 @@ const Complete = () => {
{/* 닫기 버튼 */}
navigate(`/review/${companyId}`)} > diff --git a/src/pages/writeReview/components/WriteText.jsx b/src/pages/writeReview/components/WriteText.jsx index a2019e1..e689092 100644 --- a/src/pages/writeReview/components/WriteText.jsx +++ b/src/pages/writeReview/components/WriteText.jsx @@ -14,7 +14,7 @@ const WriteText = ({ onNext, onBack }) => { const { reviewInfo } = usePaymentStore(); const companyId = usePaymentStore((s) => s.companyId); const [isUploading, setIsUploading] = useState(false); - const { data, isLoading } = useMyProfile(); + const { data } = useMyProfile(); const [toast, setToast] = useState({ show: false, @@ -27,15 +27,15 @@ const WriteText = ({ onNext, onBack }) => { }; const handleClick = async () => { - setIsUploading(true); // 모달 띄우기 + setIsUploading(true); try { await postReview(reviewInfo, companyId, text); - setIsUploading(false); // 성공 시 닫기 + setIsUploading(false); onNext(); } catch (e) { console.log(e); - setIsUploading(false); // 실패 시도 닫기 + setIsUploading(false); fireToast("리뷰 등록에 실패했습니다.\n다시 시도해 주세요.", ErrorIcon); } };