Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions src/pages/join/components/LocationStep.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,12 @@ const LocationStep = ({ onNext, onBack }) => {
const showNotSeoulMessage = checked;

return (
<div className="flex flex-col justify-start items-start h-screen mt-20 px-6 bg-white relative overflow-auto">
<button
onClick={onBack}
className="absolute top-6 left-4 sm:top-8 sm:left-6 z-10"
>
<img src={BackIcon} alt="뒤로가기 버튼" className="w-6 h-6" />
</button>
<div className="flex flex-col justify-start items-start h-screen pt-40 px-6 bg-white relative overflow-auto">
<div className="fixed top-20 left-1/2 -translate-x-1/2 w-full max-w-[760px] px-4 sm:px-6 z-50">
<button onClick={onBack} aria-label="뒤로가기" className="w-8 h-8">
<img src={BackIcon} alt="뒤로가기 버튼" className="w-8 h-8" />
</button>
</div>

<h1 className="text-h1 font-semibold mb-2">어느 동네에 사세요?</h1>
<p className="text-b5 text-gray-500 mb-8">
Expand Down
20 changes: 9 additions & 11 deletions src/pages/join/components/NameStep.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,16 @@ const NameStep = ({ onNext, onBack }) => {
onSubmit={handleSubmit}
className="flex flex-col justify-start mt-48 bg-white h-screen overflow-auto relative"
>
<button
onClick={onBack}
className="absolute top-6 left-4 sm:top-8 sm:left-6 z-10"
aria-label="뒤로가기"
>
<img src={BackIcon} alt="뒤로가기 버튼" className="w-6 h-6" />
</button>
<div className="fixed top-20 left-1/2 -translate-x-1/2 w-full max-w-[760px] px-4 sm:px-6 z-50">
<button onClick={onBack} aria-label="뒤로가기" className="w-8 h-8">
<img src={BackIcon} alt="뒤로가기 버튼" className="w-8 h-8" />
</button>
</div>

<section className="px-8">
<h1 className="text-h1 font-semibold text-gray-12">이름을 입력해주세요.</h1>
<h1 className="text-h1 font-semibold text-gray-12">
이름을 입력해주세요.
</h1>
<p className="text-b5 text-gray-9 mt-2">투명한 리뷰에 사용됩니다.</p>

<div className="mt-8">
Expand Down Expand Up @@ -79,9 +79,7 @@ const NameStep = ({ onNext, onBack }) => {
type="submit"
disabled={!name.trim()}
className={`w-full py-6 text-center text-b1 font-semibold ${
name.trim()
? "bg-primary-8 text-white"
: "bg-gray-4 text-white"
name.trim() ? "bg-primary-8 text-white" : "bg-gray-4 text-white"
}`}
>
확인
Expand Down
13 changes: 6 additions & 7 deletions src/pages/join/components/OwnerStep.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,12 @@ const OwnerStep = ({ onNext, onBack }) => {
};

return (
<div className="flex flex-col justify-start h-screen bg-white px-6 mt-20">
<button
onClick={onBack}
className="absolute top-6 left-4 sm:top-8 sm:left-6 z-10"
>
<img src={BackIcon} alt="뒤로가기 버튼" className="w-6 h-6" />
</button>
<div className="flex flex-col justify-start h-screen bg-white px-6 pt-40">
<div className="fixed top-20 left-1/2 -translate-x-1/2 w-full max-w-[760px] px-4 sm:px-6 z-50">
<button onClick={onBack} aria-label="뒤로가기" className="w-8 h-8">
<img src={BackIcon} alt="뒤로가기 버튼" className="w-8 h-8" />
</button>
</div>

<h1 className="text-h1 font-semibold mb-10">
사회적기업의 사장님이신가요?
Expand Down
12 changes: 5 additions & 7 deletions src/pages/join/components/ProfileImageStep.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,11 @@ const ProfileImageStep = ({ onNext, onBack }) => {

return (
<div className="flex flex-col h-screen px-6 bg-white overflow-hidden relative">
<button
onClick={onBack}
className="absolute top-6 left-4 sm:top-8 sm:left-6 z-10"
aria-label="뒤로가기"
>
<img src={BackIcon} alt="뒤로가기" className="w-6 h-6 sm:w-7 sm:h-7" />
</button>
<div className="fixed top-20 left-1/2 -translate-x-1/2 w-full max-w-[760px] px-4 sm:px-6 z-50">
<button onClick={onBack} aria-label="뒤로가기" className="w-8 h-8">
<img src={BackIcon} alt="뒤로가기 버튼" className="w-8 h-8" />
</button>
</div>

<h1 className="pt-24 sm:pt-32 text-h1 text-gray-12 sm:text-4xl font-semibold text-center">
프로필사진을 설정해주세요.
Expand Down
18 changes: 14 additions & 4 deletions src/pages/map/components/PlaceBottomSheet.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ import ReviewList from "@/pages/map/components/review/ReviewList";
import useUIStore from "@/store/uiStore";
import { usePaymentStore } from "@/store/paymentStore";

const PlaceBottomSheet = ({ place, onClose, onToggleLike, onExpandChange }) => {
const PlaceBottomSheet = ({
place,
onClose,
onToggleLike,
onExpandChange,
onHeightChange,
}) => {
const [shouldNavigateToReview, setShouldNavigateToReview] = useState(false);

const setCompanyId = usePaymentStore((s) => s.setCompanyId);
Expand Down Expand Up @@ -59,7 +65,8 @@ const PlaceBottomSheet = ({ place, onClose, onToggleLike, onExpandChange }) => {
useEffect(() => {
controls.start({ height: MIN_HEIGHT });
setIsExpanded(false);
}, [controls]);
onHeightChange?.(MIN_HEIGHT);
}, [controls, onHeightChange]);

const handleTouchStart = (e) => {
if (!isMobile) return;
Expand Down Expand Up @@ -89,18 +96,21 @@ const PlaceBottomSheet = ({ place, onClose, onToggleLike, onExpandChange }) => {
setIsExpanded(true);
} else if (delta > 100) {
controls.start({ height: 0 }).then(() => {
onHeightChange?.(0);
onClose?.();
});
} else {
controls.start({ height: MIN_HEIGHT });
setIsExpanded(false);
onHeightChange?.(MIN_HEIGHT);
}
};

const handleClickExpand = () => {
if (!isMobile && !isExpanded) {
controls.start({ height: MAX_HEIGHT.current });
setIsExpanded(true);
onHeightChange?.(MAX_HEIGHT.current);
}
};

Expand Down Expand Up @@ -170,8 +180,8 @@ const PlaceBottomSheet = ({ place, onClose, onToggleLike, onExpandChange }) => {
turnOnCamera={turnOnCamera}
onCloseCamera={() => setTurnOnCamera(false)}
onCaptureSuccess={(data) => {
setCompanyInfo(data); // 즉시 로컬 상태에 저장
setShowConfirm(true); // 그다음 Confirm 렌더링
setCompanyInfo(data);
setShowConfirm(true);
}}
/>
)}
Expand Down
20 changes: 16 additions & 4 deletions src/pages/map/components/PlaceContent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { useState } from "react";
const PlaceContent = ({ place, onToggleLike, showMapLink = true }) => {
const userCoords = useUserCoords();
const [showToast, setShowToast] = useState(false);
const [isLiking, setIsLiking] = useState(false);

const handleRouteClick = (e) => {
e.preventDefault();
Expand Down Expand Up @@ -76,6 +77,20 @@ const PlaceContent = ({ place, onToggleLike, showMapLink = true }) => {
}
};

const handleLikeClick = async (e) => {
e.stopPropagation();
if (isLiking) return;

setIsLiking(true);
try {
await onToggleLike?.(place.id);
} catch (err) {
console.error("좋아요 처리 실패:", err);
} finally {
setIsLiking(false);
}
};

const fireIcons = {
10: IcFire10,
20: IcFire20,
Expand Down Expand Up @@ -157,10 +172,7 @@ const PlaceContent = ({ place, onToggleLike, showMapLink = true }) => {
</a>
)}
<button
onClick={(e) => {
e.stopPropagation();
onToggleLike?.(place.id);
}}
onClick={handleLikeClick}
className="w-14 h-14 flex items-center justify-center rounded-md bg-gray-2"
>
<img
Expand Down
57 changes: 42 additions & 15 deletions src/pages/map/hooks/useToggleLike.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import useAuthStore from "@/store/authStore";
import { likeCompany, unlikeCompany } from "@apis/company/getLikedCompanies";
import {
likeCompany,
unlikeCompany,
getLikedCompanies,
} from "@apis/company/getLikedCompanies";

export const useToggleLike = ({
placesWithDistance,
Expand All @@ -13,53 +17,76 @@ export const useToggleLike = ({
onRequireLogin,
}) => {
const [loading, setLoading] = useState(false);
const [isProcessing, setIsProcessing] = useState(false);
const navigate = useNavigate();

const toggleLike = async (targetId) => {
const isAuthenticated = await useAuthStore.getState().checkAuth();

if (!isAuthenticated) {
if (onRequireLogin) {
onRequireLogin();
} else {
navigate("/auth");
}
onRequireLogin ? onRequireLogin() : navigate("/auth");
return;
}

const currentPlace = placesWithDistance.find((p) => p.id === targetId);
if (!currentPlace) return;
if (!currentPlace || isProcessing) return;

setIsProcessing(true);
setLoading(true);

Comment on lines +20 to 36
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

전역 isProcessing 플래그로 인한 다중 토글 제한

isProcessing 이 훅 단위로 하나만 존재하기 때문에 A 기업 “찜” 도중 B 기업을 바로 “찜“ 하려면 첫 요청이 끝날 때까지 대기해야 합니다.
사용자는 여러 기업을 연속으로 빠르게 찜할 수 있기를 기대할 수 있으므로, 기업 ID 별로 별도 lock 을 두거나 Set 형태로 진행 중 ID 를 저장하는 방식을 고려해 보세요.

try {
setLoading(true);
// 서버 liked 상태 확인 (불일치 방지용)
const likedList = await getLikedCompanies();
const isActuallyLiked = likedList.some(
(c) => String(c.companyId) === String(targetId),
);

if (currentPlace.liked) {
// 로컬 상태와 서버 상태 불일치 시 보정
if (currentPlace.liked !== isActuallyLiked) {
const corrected = placesWithDistance.map((p) =>
p.id === targetId ? { ...p, liked: isActuallyLiked } : p,
);
setPlacesWithDistance(corrected);
if (selectedPlace?.id === targetId) {
setSelectedPlace({
...selectedPlace,
liked: isActuallyLiked,
});
}
}

// 서버 상태 기준으로 실제 토글 실행
if (isActuallyLiked) {
await unlikeCompany(targetId);
} else {
await likeCompany(targetId);
}

// 최종 로컬 상태 반영
const updated = placesWithDistance.map((p) =>
p.id === targetId ? { ...p, liked: !p.liked } : p,
p.id === targetId ? { ...p, liked: !isActuallyLiked } : p,
);
setPlacesWithDistance(updated);

if (selectedPlace?.id === targetId) {
setSelectedPlace({
...selectedPlace,
liked: !selectedPlace.liked,
liked: !isActuallyLiked,
distance: currentPlace.distance,
formattedDistance: currentPlace.formattedDistance,
});
}

setFilteredPlaces((prev) => {
if (!prev) return [];
return showOnlyLiked ? updated.filter((p) => p.liked) : updated;
});
if (setFilteredPlaces) {
setFilteredPlaces((prev) => {
if (!prev) return [];
return showOnlyLiked ? updated.filter((p) => p.liked) : updated;
});
}
} catch (err) {
console.error("찜 토글 실패:", err);
} finally {
setIsProcessing(false);
setLoading(false);
}
};
Expand Down
Loading