From ecaeb33d220bb777fc98c0d8dc484c9b67b7f43e Mon Sep 17 00:00:00 2001 From: syddl0 <137189866+shroqkf@users.noreply.github.com> Date: Tue, 20 May 2025 19:30:28 +0900 Subject: [PATCH 1/8] =?UTF-8?q?Feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=EC=9D=98=20=EB=82=B4=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84-=EC=B0=9C=EB=AA=A9=EB=A1=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/myPage/getDetail.js | 5 + src/apis/myPage/queries.js | 11 +- src/pages/myPageDetail/MyPageDetailPage.jsx | 34 +++++- .../myPageDetail/components/HeartItem.jsx | 109 ++++++++++++++++++ 4 files changed, 152 insertions(+), 7 deletions(-) create mode 100644 src/pages/myPageDetail/components/HeartItem.jsx diff --git a/src/apis/myPage/getDetail.js b/src/apis/myPage/getDetail.js index b77835c..cd241be 100644 --- a/src/apis/myPage/getDetail.js +++ b/src/apis/myPage/getDetail.js @@ -1,5 +1,10 @@ import api from "@/apis/instance/api"; +export const getHearts = async () => { + const res = await api.get("/company/member-saves"); + return res.data; +}; + export const getReviews = async () => { const res = await api.get("/reviews/get-all-member-reviews"); return res.data; diff --git a/src/apis/myPage/queries.js b/src/apis/myPage/queries.js index 85049cd..862736f 100644 --- a/src/apis/myPage/queries.js +++ b/src/apis/myPage/queries.js @@ -1,6 +1,13 @@ import { useQuery } from "@tanstack/react-query"; -import { getReviews } from "@/apis/myPage/getDetail"; -import { getCheers } from "@/apis/myPage/getDetail"; +import { getHearts, getReviews, getCheers } from "@/apis/myPage/getDetail"; + +export const useGetHearts = ({ enabled }) => { + return useQuery({ + queryKey: ["userHearts"], + queryFn: getHearts, + enabled, + }); +}; export const useGetReviews = () => { return useQuery({ queryKey: ["userReview"], queryFn: () => getReviews() }); diff --git a/src/pages/myPageDetail/MyPageDetailPage.jsx b/src/pages/myPageDetail/MyPageDetailPage.jsx index b7acf29..7b948ea 100644 --- a/src/pages/myPageDetail/MyPageDetailPage.jsx +++ b/src/pages/myPageDetail/MyPageDetailPage.jsx @@ -2,24 +2,32 @@ import { useNavigate, useLocation } from "react-router-dom"; import heart from "/svgs/myPage/heart.svg"; import cheer from "/svgs/myPage/cheer.svg"; import review from "/svgs/myPage/review.svg"; -import { useGetReviews, useGetCheers } from "@/apis/myPage/queries"; +import { + useGetHearts, + useGetReviews, + useGetCheers, +} from "@/apis/myPage/queries"; import ReviewItem from "@/pages/myPageDetail/components/ReviewItem"; import StoryItem from "@/pages/myPageDetail/components/StoryItem"; import noResult from "/svgs/myPage/noResult.svg"; import Spinner from "@/components/common/Spinner"; +import HeartItem from "./components/HeartItem"; const MyPageDetailPage = () => { const navigate = useNavigate(); const location = useLocation(); const kind = location.state.kind; + const { data: heartsData, isLoading: isLoadingHearts } = useGetHearts({ + enabled: 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 === "찜" }); if (isLoadingReviews || isLoadingCheers) { return ( @@ -47,11 +55,11 @@ const MyPageDetailPage = () => { {kind === "찜" && (
-
+

저장한 장소

-

총 0개

+

총 {heartsData?.length}개

)} @@ -65,7 +73,23 @@ const MyPageDetailPage = () => {
)} -
+
+ {kind === "찜" && ( + <> + {!isLoadingHearts && heartsData?.length > 0 ? ( + heartsData.map((item, idx) => ) + ) : !isLoadingHearts && heartsData?.length === 0 ? ( +
+ +

+ 아직 저장한 장소가 +
없어요 +

+
+ ) : null} + + )} + {kind === "리뷰" && ( <> {!isLoadingReviews && reviewsData?.length > 0 ? ( diff --git a/src/pages/myPageDetail/components/HeartItem.jsx b/src/pages/myPageDetail/components/HeartItem.jsx new file mode 100644 index 0000000..83df121 --- /dev/null +++ b/src/pages/myPageDetail/components/HeartItem.jsx @@ -0,0 +1,109 @@ +import { + companyTypeNameMap, + companyTypeIconMap, + businessTypeNameMap, +} from "@constants/categoryMap"; +import HeartIcon from "/svgs/common/Ic_Heart_Fill.svg"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { unlikeCompany } from "@apis/company/getLikedCompanies"; +import ToastModal from "@components/common/ToastModal"; +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; + +const HeartItem = ({ data }) => { + const { + companyId, + companyName, + companyCategory, + companyType, + companyLocation, + business, + distance, + } = data; + + const navigate = useNavigate(); + + const handleClick = () => { + navigate("/map/:companyId"); + }; + + const formattedDistance = + typeof distance === "number" ? `${distance.toFixed(1)}km` : null; + + const queryClient = useQueryClient(); + const [toastVisible, setToastVisible] = useState(false); + + const { mutate: unlike, isLoading } = useMutation({ + mutationFn: () => unlikeCompany(companyId), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["userHearts"] }); + setToastVisible(true); + setTimeout(() => setToastVisible(false), 2000); + }, + }); + + const handleUnlike = (e) => { + e.stopPropagation(); + if (!isLoading) unlike(); + }; + + return ( + <> +
+
+
+

+ {companyName} + + {businessTypeNameMap[companyCategory] ?? companyCategory} + +

+ {formattedDistance && ( +

+ {formattedDistance} +

+ )} +

{companyLocation}

+
+ + +
+ + {business && ( +
+
+

{business}

+ + {companyType && ( +
+ {companyType} + + {companyTypeNameMap[companyType]} + +
+ )} +
+
+ )} +
+ + {toastVisible && ( + setToastVisible(false)} + /> + )} + + ); +}; + +export default HeartItem; From 858dd80247c6b2459e9915ff951625492fb0d5d2 Mon Sep 17 00:00:00 2001 From: syddl0 <137189866+shroqkf@users.noreply.github.com> Date: Tue, 20 May 2025 19:46:04 +0900 Subject: [PATCH 2/8] =?UTF-8?q?Feat:=20=EC=B0=9C=ED=95=9C=20=EA=B8=B0?= =?UTF-8?q?=EC=97=85=20=EC=A7=80=EB=8F=84=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../myPageDetail/components/HeartItem.jsx | 2 +- .../components/MapCompanyPage.jsx | 97 +++++++++++++++++++ src/routes/router.jsx | 5 + 3 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 src/pages/myPageDetail/components/MapCompanyPage.jsx diff --git a/src/pages/myPageDetail/components/HeartItem.jsx b/src/pages/myPageDetail/components/HeartItem.jsx index 83df121..c15e1f4 100644 --- a/src/pages/myPageDetail/components/HeartItem.jsx +++ b/src/pages/myPageDetail/components/HeartItem.jsx @@ -24,7 +24,7 @@ const HeartItem = ({ data }) => { const navigate = useNavigate(); const handleClick = () => { - navigate("/map/:companyId"); + navigate(`/map/${companyId}`); }; const formattedDistance = diff --git a/src/pages/myPageDetail/components/MapCompanyPage.jsx b/src/pages/myPageDetail/components/MapCompanyPage.jsx new file mode 100644 index 0000000..b1ef571 --- /dev/null +++ b/src/pages/myPageDetail/components/MapCompanyPage.jsx @@ -0,0 +1,97 @@ +import { useParams } from "react-router-dom"; +import { useEffect, useState } from "react"; +import { getCompanyPreview } from "@apis/company/getCompanyPreview"; +import { getAllCompanies } from "@apis/company/getAllCompanies"; +import { getLikedCompanies } from "@apis/company/getLikedCompanies"; +import useAuthStore from "@/store/authStore"; +import { useToggleLike } from "@pages/map/hooks/useToggleLike"; +import PlaceBottomSheet from "@pages/map/components/PlaceBottomSheet"; +import MapViewer from "@/pages/search/components/MapViewer"; + +const MapCompanyPage = () => { + const { companyId } = useParams(); + const [place, setPlace] = useState(null); + const [isBottomSheetVisible, setIsBottomSheetVisible] = useState(true); + const [isBottomSheetExpanded, setIsBottomSheetExpanded] = useState(false); + const [bottomSheetHeight, setBottomSheetHeight] = useState(220); + + useEffect(() => { + const fetchData = async () => { + try { + const [preview, allCompanies] = await Promise.all([ + getCompanyPreview(companyId), + getAllCompanies(), + ]); + + const locationData = allCompanies.find( + (c) => String(c.companyId) === String(companyId) + ); + + if (!locationData?.latitude || !locationData?.longitude) { + console.warn("기업 좌표 정보가 누락되었습니다:", locationData); + return; + } + + const isAuthenticated = await useAuthStore.getState().checkAuth(); + const likedList = isAuthenticated ? await getLikedCompanies() : []; + const liked = likedList.some((c) => c.companyId === Number(companyId)); + + const enriched = { + ...preview, + coords: { + lat: locationData.latitude, + lng: locationData.longitude, + }, + liked, + }; + + setPlace(enriched); + } catch (err) { + console.error("기업 정보 로딩 실패:", err); + } + }; + + fetchData(); + }, [companyId]); + + const { toggleLike } = useToggleLike({ + placesWithDistance: place ? [place] : [], + setPlacesWithDistance: () => {}, + setFilteredPlaces: () => {}, + selectedPlace: place, + setSelectedPlace: setPlace, + showOnlyLiked: false, + onRequireLogin: () => {}, + }); + + if (!place) return

로딩 중...

; + + return ( +
+ setIsBottomSheetVisible(true)} + moveToCurrentLocation={false} + onMoveComplete={() => {}} + userCoords={null} + disableAutoUserPan={true} + /> + {place && isBottomSheetVisible && ( + setIsBottomSheetVisible(false)} + onExpandChange={setIsBottomSheetExpanded} + onToggleLike={toggleLike} + onHeightChange={setBottomSheetHeight} + /> + )} +
+ ); +}; + +export default MapCompanyPage; diff --git a/src/routes/router.jsx b/src/routes/router.jsx index 14274db..3db23d1 100644 --- a/src/routes/router.jsx +++ b/src/routes/router.jsx @@ -17,6 +17,7 @@ import SupportRecommendPage from "@pages/support/SupportRecommendPage"; import FinancialProductList from "@pages/support/FinancialProductListPage"; import FinancialProductDetailPage from "@pages/support/FinancialProductDetailPage"; import MyPageDetailPage from "@/pages/myPageDetail/MyPageDetailPage"; +import MapCompanyPage from "@/pages/myPageDetail/components/MapCompanyPage"; const router = createBrowserRouter([ { @@ -38,6 +39,10 @@ const router = createBrowserRouter([ path: "/map/search", element: , }, + { + path: "/map/:companyId", + element: , + }, { path: "/mypage", element: , From edf09f594c7f9fe37ca8cecd4a88d0cd2d116389 Mon Sep 17 00:00:00 2001 From: syddl0 <137189866+shroqkf@users.noreply.github.com> Date: Tue, 20 May 2025 20:05:42 +0900 Subject: [PATCH 3/8] =?UTF-8?q?Feat:=20=EA=B8=B0=EC=97=85=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=92=A4=EB=A1=9C=EA=B0=80=EA=B8=B0=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../myPageDetail/components/MapCompanyPage.jsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/pages/myPageDetail/components/MapCompanyPage.jsx b/src/pages/myPageDetail/components/MapCompanyPage.jsx index b1ef571..63d2c66 100644 --- a/src/pages/myPageDetail/components/MapCompanyPage.jsx +++ b/src/pages/myPageDetail/components/MapCompanyPage.jsx @@ -1,4 +1,4 @@ -import { useParams } from "react-router-dom"; +import { useParams, useNavigate } from "react-router-dom"; import { useEffect, useState } from "react"; import { getCompanyPreview } from "@apis/company/getCompanyPreview"; import { getAllCompanies } from "@apis/company/getAllCompanies"; @@ -10,6 +10,8 @@ import MapViewer from "@/pages/search/components/MapViewer"; const MapCompanyPage = () => { const { companyId } = useParams(); + const navigate = useNavigate(); + const [place, setPlace] = useState(null); const [isBottomSheetVisible, setIsBottomSheetVisible] = useState(true); const [isBottomSheetExpanded, setIsBottomSheetExpanded] = useState(false); @@ -68,6 +70,17 @@ const MapCompanyPage = () => { return (
+
navigate(-1)} + > + 뒤로가기 +
+ Date: Tue, 20 May 2025 22:40:35 +0900 Subject: [PATCH 4/8] =?UTF-8?q?Feat:=20=EC=9D=B4=EC=95=BC=EA=B8=B0=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9D=91=EC=9B=90=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EC=95=8C=EB=A6=BC=20=EB=AA=A8=EB=8B=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/story/components/StoryDetail.jsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/pages/story/components/StoryDetail.jsx b/src/pages/story/components/StoryDetail.jsx index 6955ba1..c01610d 100644 --- a/src/pages/story/components/StoryDetail.jsx +++ b/src/pages/story/components/StoryDetail.jsx @@ -5,12 +5,14 @@ import useUIStore from "@/store/uiStore"; import { useGetBestStoryDetail } from "@/apis/story/queries"; import { usePatchStoryLike } from "@/apis/story/queries"; import HaveToLoginModal from "@components/common/HaveToLoginModal"; +import ToastModal from "@/components/common/ToastModal"; const StoryDetail = () => { const navigate = useNavigate(); const { storyId } = useParams(); const { setIsStoryDetail } = useUIStore(); const { data, isLoading } = useGetBestStoryDetail(storyId); + const [showToast, setShowToast] = useState(false); useEffect(() => { setIsStoryDetail(true); @@ -22,6 +24,10 @@ const StoryDetail = () => { const handleLike = () => { likeStory(undefined, { + onSuccess: () => { + setShowToast(true); + setTimeout(() => setShowToast(false), 3000); + }, onError: (error) => { const status = error?.response?.status; @@ -118,6 +124,13 @@ const StoryDetail = () => {
+ {showToast && ( + setShowToast(false)} + /> + )} + {errorModal.open && ( Date: Wed, 21 May 2025 00:40:14 +0900 Subject: [PATCH 5/8] =?UTF-8?q?Feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=ED=94=84=EB=A1=9C=ED=95=84=20=ED=8E=B8?= =?UTF-8?q?=EC=A7=91=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/member/postUpdateProfile.js | 10 ++ src/apis/member/queries.js | 9 +- src/pages/myPage/MyPage.jsx | 8 +- src/pages/myPage/components/.gitkeep | 0 src/pages/myPage/components/MyPageEdit.jsx | 198 +++++++++++++++++++++ src/routes/router.jsx | 5 + 6 files changed, 225 insertions(+), 5 deletions(-) create mode 100644 src/apis/member/postUpdateProfile.js delete mode 100644 src/pages/myPage/components/.gitkeep create mode 100644 src/pages/myPage/components/MyPageEdit.jsx diff --git a/src/apis/member/postUpdateProfile.js b/src/apis/member/postUpdateProfile.js new file mode 100644 index 0000000..0759777 --- /dev/null +++ b/src/apis/member/postUpdateProfile.js @@ -0,0 +1,10 @@ +import api from "@/apis/instance/api"; + +export const postUpdateProfile = async ({ name, location, profileColor }) => { + const response = await api.post("/member/update", { + name, + location, + profileColor, + }); + return response.data; +}; diff --git a/src/apis/member/queries.js b/src/apis/member/queries.js index d3c3f7c..2c1791d 100644 --- a/src/apis/member/queries.js +++ b/src/apis/member/queries.js @@ -1,8 +1,9 @@ -import { useQuery } from "@tanstack/react-query"; +import { useMutation, useQuery } from "@tanstack/react-query"; import { getMyProfile } from "@/apis/member/auth"; import { getReviewCountOfMember } from "@/apis/member/getReviewCountOfMember"; import { getCheerCountOfMember } from "@/apis/member/getCheerCountOfMember"; import { getLikeCountOfMember } from "@/apis/member/getLikeCountOfMember"; +import { postUpdateProfile } from "./postUpdateProfile"; export const useMyProfile = () => { return useQuery({ @@ -34,3 +35,9 @@ export const useGetLikeCountOfMember = () => { queryFn: () => getLikeCountOfMember(), }); }; + +export const useUpdateProfile = () => { + return useMutation({ + mutationFn: postUpdateProfile, + }); +}; diff --git a/src/pages/myPage/MyPage.jsx b/src/pages/myPage/MyPage.jsx index 9135835..d4f85aa 100644 --- a/src/pages/myPage/MyPage.jsx +++ b/src/pages/myPage/MyPage.jsx @@ -104,15 +104,15 @@ const MyPage = () => { {showLogoutModal && navigate("/")} />}
- -

{nickname}

{location}

-
-
{Object.entries(counts).map(([label, count]) => (
{ + const navigate = useNavigate(); + const { data } = useMyProfile(); + const { mutate: updateProfile } = useUpdateProfile(); + + const [name, setName] = useState(""); + const [location, setAddress] = useState(""); + const [profileColor, setProfileColor] = useState("gray"); + const [toastVisible, setToastVisible] = useState(false); + const [isNameFocused, setIsNameFocused] = useState(false); + const [isLocationFocused, setIsLocationFocused] = useState(false); + const [checked, setChecked] = useState(false); + + useEffect(() => { + if (data) { + setName(data.name || ""); + setAddress(data.address || ""); + setProfileColor(data.profileColor || "gray"); + setChecked(data.address === "서울 외 지역 거주"); + } + }, [data]); + + const handleSubmit = () => { + const fullLocation = checked + ? "서울 외 지역 거주" + : location.trim().startsWith("서울특별시") + ? location.trim() + : `서울특별시 ${location.trim()}`; + + updateProfile( + { + name, + location: fullLocation, + profileColor, + }, + { + onSuccess: () => { + setToastVisible(true); + setTimeout(() => { + setToastVisible(false); + navigate("/mypage"); + }, 1500); + }, + onError: (err) => { + console.error("프로필 업데이트 실패:", err); + alert("프로필 업데이트에 실패했습니다."); + }, + } + ); + }; + + const toggleCheck = () => { + setChecked((prev) => { + const next = !prev; + if (next) setAddress("서울 외 지역 거주"); + else setAddress(""); + return next; + }); + }; + + const trimmed = location.trim(); + const showWarning = + (!checked && !trimmed.startsWith("서울특별시")) || checked; + + const selectedIndex = profileColors.indexOf(profileColor); + const SelectedProfile = profileSvgs[selectedIndex] ?? ImgGray; + + return ( +
+
+ +

프로필 편집

+
+
+ +
+
+ +
+
+ +
+ {profileSvgs.map((SvgComponent, index) => ( +
setProfileColor(profileColors[index])} + > + + {profileColor === profileColors[index] && ( +
+ +
+ )} +
+ ))} +
+ +
+
+ + setIsNameFocused(true)} + onBlur={() => setIsNameFocused(false)} + onChange={(e) => setName(e.target.value)} + /> +
+ +
+ +
+ setIsLocationFocused(true)} + onBlur={() => setIsLocationFocused(false)} + onChange={(e) => setAddress(e.target.value)} + readOnly={checked} + className={`flex-1 bg-transparent outline-none b2 placeholder-gray-6 ${ + checked ? "cursor-not-allowed" : "" + }`} + /> +
+ + {showWarning && ( +

+ 현재는 서울에 한해 사회적 기업들을 소개하고 있습니다. +

+ )} + + +
+
+ +
+ +
+ +
+ + {toastVisible && ( + setToastVisible(false)} + /> + )} +
+ ); +}; + +export default MyPageEdit; diff --git a/src/routes/router.jsx b/src/routes/router.jsx index 3db23d1..8bbea0e 100644 --- a/src/routes/router.jsx +++ b/src/routes/router.jsx @@ -18,6 +18,7 @@ import FinancialProductList from "@pages/support/FinancialProductListPage"; import FinancialProductDetailPage from "@pages/support/FinancialProductDetailPage"; import MyPageDetailPage from "@/pages/myPageDetail/MyPageDetailPage"; import MapCompanyPage from "@/pages/myPageDetail/components/MapCompanyPage"; +import MyPageEdit from "@/pages/myPage/components/MyPageEdit"; const router = createBrowserRouter([ { @@ -97,6 +98,10 @@ const router = createBrowserRouter([ path: "/signup", // 탭 없는 별도 페이지 element: , }, + { + path: "/mypage/edit", + element: , + }, ]); export default router; From 213157a738dd6ea8c1a9c6e4747d2fdf738e34d0 Mon Sep 17 00:00:00 2001 From: syddl0 <137189866+shroqkf@users.noreply.github.com> Date: Wed, 21 May 2025 13:02:19 +0900 Subject: [PATCH 6/8] =?UTF-8?q?Feat:=20=EC=A0=80=EC=9E=A5=ED=95=9C=20?= =?UTF-8?q?=EC=9E=A5=EC=86=8C=20=EC=B9=B4=EB=93=9C=20=EA=B1=B0=EB=A6=AC=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20UI=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/myPageDetail/MyPageDetailPage.jsx | 31 +++++++++- .../myPageDetail/components/HeartItem.jsx | 59 +++++++++++++++---- 2 files changed, 74 insertions(+), 16 deletions(-) diff --git a/src/pages/myPageDetail/MyPageDetailPage.jsx b/src/pages/myPageDetail/MyPageDetailPage.jsx index 7b948ea..c21a23b 100644 --- a/src/pages/myPageDetail/MyPageDetailPage.jsx +++ b/src/pages/myPageDetail/MyPageDetailPage.jsx @@ -12,16 +12,39 @@ import StoryItem from "@/pages/myPageDetail/components/StoryItem"; import noResult from "/svgs/myPage/noResult.svg"; import Spinner from "@/components/common/Spinner"; import HeartItem from "./components/HeartItem"; +import { useEffect, useMemo, useState } from "react"; +import { getAllCompanies } from "@/apis/company/getAllCompanies"; const MyPageDetailPage = () => { const navigate = useNavigate(); const location = useLocation(); const kind = location.state.kind; + const [allCompanies, setAllCompanies] = useState([]); + const { data: heartsData, isLoading: isLoadingHearts } = useGetHearts({ enabled: kind === "찜", }); + useEffect(() => { + if (kind === "찜") { + getAllCompanies().then(setAllCompanies); + } + }, [kind]); + + const companyMap = useMemo(() => { + return new Map(allCompanies.map((c) => [c.companyId, c])); + }, [allCompanies]); + + const enrichedHeartsData = heartsData?.map((heart) => { + const matched = companyMap.get(heart.companyId); + return { + ...heart, + latitude: matched?.latitude, + longitude: matched?.longitude, + }; + }); + const { data: reviewsData, isLoading: isLoadingReviews } = useGetReviews({ enabled: kind === "리뷰", }); @@ -76,9 +99,11 @@ const MyPageDetailPage = () => {
{kind === "찜" && ( <> - {!isLoadingHearts && heartsData?.length > 0 ? ( - heartsData.map((item, idx) => ) - ) : !isLoadingHearts && heartsData?.length === 0 ? ( + {!isLoadingHearts && enrichedHeartsData?.length > 0 ? ( + enrichedHeartsData.map((item, idx) => ( + + )) + ) : !isLoadingHearts && enrichedHeartsData?.length === 0 ? (

diff --git a/src/pages/myPageDetail/components/HeartItem.jsx b/src/pages/myPageDetail/components/HeartItem.jsx index c15e1f4..d64506b 100644 --- a/src/pages/myPageDetail/components/HeartItem.jsx +++ b/src/pages/myPageDetail/components/HeartItem.jsx @@ -7,8 +7,10 @@ import HeartIcon from "/svgs/common/Ic_Heart_Fill.svg"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { unlikeCompany } from "@apis/company/getLikedCompanies"; import ToastModal from "@components/common/ToastModal"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; +import { useUserCoords } from "@pages/search/hooks/useUserCoords"; +import { getDistanceFromLatLon } from "@pages/map/utils/getDistanceFromLatLon"; const HeartItem = ({ data }) => { const { @@ -18,17 +20,38 @@ const HeartItem = ({ data }) => { companyType, companyLocation, business, - distance, + latitude, + longitude, } = data; const navigate = useNavigate(); + const userCoords = useUserCoords(); + console.log("userCoords", userCoords); const handleClick = () => { navigate(`/map/${companyId}`); }; - const formattedDistance = - typeof distance === "number" ? `${distance.toFixed(1)}km` : null; + const [distance, setDistance] = useState(null); + + useEffect(() => { + if (userCoords && latitude && longitude) { + const d = getDistanceFromLatLon( + userCoords.lat, + userCoords.lng, + latitude, + longitude + ); + setDistance(d); + } + }, [userCoords, latitude, longitude]); + + const formatDistance = (distanceInMeters) => { + if (distanceInMeters < 1000) { + return `${Math.round(distanceInMeters)}m`; + } + return `${(distanceInMeters / 1000).toFixed(1)}km`; + }; const queryClient = useQueryClient(); const [toastVisible, setToastVisible] = useState(false); @@ -53,23 +76,33 @@ const HeartItem = ({ data }) => { className="bg-white rounded-xl p-5 flex flex-col gap-3 shadow-sm cursor-pointer" onClick={handleClick} > -

-
-

+

+
+

{companyName} {businessTypeNameMap[companyCategory] ?? companyCategory}

- {formattedDistance && ( -

- {formattedDistance} -

+ + {distance !== null && ( +
+

+ {formatDistance(distance)} +

+ · +

+ {companyLocation} +

+
)} -

{companyLocation}

-
From 0dbeac143ab6a1d0536957c60b203f90cbbcc8dc Mon Sep 17 00:00:00 2001 From: syddl0 <137189866+shroqkf@users.noreply.github.com> Date: Wed, 21 May 2025 16:09:24 +0900 Subject: [PATCH 7/8] =?UTF-8?q?Feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=95=8C=EB=A6=BC=20=EB=AA=A8?= =?UTF-8?q?=EB=8B=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/svgs/modal/Ic_Warn.svg | 9 +++ src/assets/svgs/modal/index.js | 2 + src/pages/myPage/components/MyPageEdit.jsx | 71 ++++++++++++---------- 3 files changed, 51 insertions(+), 31 deletions(-) create mode 100644 src/assets/svgs/modal/Ic_Warn.svg create mode 100644 src/assets/svgs/modal/index.js diff --git a/src/assets/svgs/modal/Ic_Warn.svg b/src/assets/svgs/modal/Ic_Warn.svg new file mode 100644 index 0000000..e8dfb3b --- /dev/null +++ b/src/assets/svgs/modal/Ic_Warn.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/svgs/modal/index.js b/src/assets/svgs/modal/index.js new file mode 100644 index 0000000..60c3a4e --- /dev/null +++ b/src/assets/svgs/modal/index.js @@ -0,0 +1,2 @@ +export { default as IcError } from "./errorIcon.svg?react"; +export { default as IcWarnning } from "./Ic_Warn.svg?react"; diff --git a/src/pages/myPage/components/MyPageEdit.jsx b/src/pages/myPage/components/MyPageEdit.jsx index aef06ff..e78a580 100644 --- a/src/pages/myPage/components/MyPageEdit.jsx +++ b/src/pages/myPage/components/MyPageEdit.jsx @@ -3,6 +3,8 @@ import { useNavigate } from "react-router-dom"; import { useMyProfile, useUpdateProfile } from "@/apis/member/queries"; import ToastModal from "@components/common/ToastModal"; import BackIcon from "/svgs/common/Ic_Arrow_Left.svg"; +import { IcWarnning } from "@assets/svgs/modal"; + import { IcCheck, IcNonCheck, @@ -27,6 +29,7 @@ const MyPageEdit = () => { const [isNameFocused, setIsNameFocused] = useState(false); const [isLocationFocused, setIsLocationFocused] = useState(false); const [checked, setChecked] = useState(false); + const [toastMessage, setToastMessage] = useState(""); useEffect(() => { if (data) { @@ -38,6 +41,15 @@ const MyPageEdit = () => { }, [data]); const handleSubmit = () => { + if (!name.trim()) { + setToastVisible(true); + setToastMessage("이름을 입력해주세요."); + setTimeout(() => { + setToastVisible(false); + }, 1800); + return; + } + const fullLocation = checked ? "서울 외 지역 거주" : location.trim().startsWith("서울특별시") @@ -53,6 +65,7 @@ const MyPageEdit = () => { { onSuccess: () => { setToastVisible(true); + setToastMessage("프로필이 저장되었습니다!"); setTimeout(() => { setToastVisible(false); navigate("/mypage"); @@ -83,7 +96,7 @@ const MyPageEdit = () => { const SelectedProfile = profileSvgs[selectedIndex] ?? ImgGray; return ( -
+
- -
- -
+
- {toastVisible && ( setToastVisible(false)} /> From 671c3858f1cfc78e37b69fb39943420e7f395c52 Mon Sep 17 00:00:00 2001 From: syddl0 <137189866+shroqkf@users.noreply.github.com> Date: Wed, 21 May 2025 16:26:50 +0900 Subject: [PATCH 8/8] =?UTF-8?q?Design:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=97=AC=EB=B0=B1=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/myPage/components/MyPageEdit.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/myPage/components/MyPageEdit.jsx b/src/pages/myPage/components/MyPageEdit.jsx index e78a580..91d9d4f 100644 --- a/src/pages/myPage/components/MyPageEdit.jsx +++ b/src/pages/myPage/components/MyPageEdit.jsx @@ -104,8 +104,8 @@ const MyPageEdit = () => {

프로필 편집

-
-
+
+
@@ -128,7 +128,7 @@ const MyPageEdit = () => { ))}
-
+
{ />
-
+
{
-
+