fix(a11y): #301+#302 모달 접근성 + race condition + 필터 상태 동기화
CRITICAL — 모달 접근성: - frontend/src/lib/hooks/useModalA11y.ts 신규 (useEscapeKey, useFocusTrap, useBodyScrollLock) - BottomSheet: role='dialog' / aria-modal / aria-label / ESC 닫기 / focus trap - FilterSheet: role='dialog' / aria-modal / aria-labelledby / ESC 닫기 / focus trap, 닫기 버튼 aria-label MAJOR — race condition (#301): - RestaurantDetail useEffect에 cancelled 플래그 추가 → restaurant.id 변경 시 이전 fetch 결과 폐기 MAJOR — 필터 상태 동기화 (#302): - page.tsx에 exitSearchMode 헬퍼 추가 - 검색 모드(isSearchResult=true)에서 cuisine/price/country/city/district 변경 시 자동으로 검색 모드 해제 + 원본 restaurants 재로드 후속 분리: #319(BottomSheet 매직넘버/UX), #320(필터 정밀도/접근성/테스트) Refs: #301 #302
This commit is contained in:
@@ -23,19 +23,20 @@ export default function RestaurantDetail({
|
||||
const [favLoading, setFavLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
setLoading(true);
|
||||
api
|
||||
.getRestaurantVideos(restaurant.id)
|
||||
.then(setVideos)
|
||||
.catch(() => setVideos([]))
|
||||
.finally(() => setLoading(false));
|
||||
.then((v) => { if (!cancelled) setVideos(v); })
|
||||
.catch(() => { if (!cancelled) setVideos([]); })
|
||||
.finally(() => { if (!cancelled) setLoading(false); });
|
||||
|
||||
// Load favorite status if logged in
|
||||
if (getToken()) {
|
||||
api.getFavoriteStatus(restaurant.id)
|
||||
.then((r) => setFavorited(r.favorited))
|
||||
.then((r) => { if (!cancelled) setFavorited(r.favorited); })
|
||||
.catch(() => {});
|
||||
}
|
||||
return () => { cancelled = true; };
|
||||
}, [restaurant.id]);
|
||||
|
||||
const handleToggleFavorite = async () => {
|
||||
@@ -57,12 +58,12 @@ export default function RestaurantDetail({
|
||||
<button
|
||||
onClick={handleToggleFavorite}
|
||||
disabled={favLoading}
|
||||
className={`text-xl leading-none transition-colors ${
|
||||
className={`p-1.5 -m-1.5 transition-colors touch-manipulation ${
|
||||
favorited ? "text-rose-500" : "text-gray-300 dark:text-gray-600 hover:text-rose-400"
|
||||
}`}
|
||||
title={favorited ? "찜 해제" : "찜하기"}
|
||||
>
|
||||
<Icon name="favorite" size={20} filled={favorited} />
|
||||
<Icon name="favorite" size={22} filled={favorited} />
|
||||
</button>
|
||||
)}
|
||||
{restaurant.business_status === "CLOSED_PERMANENTLY" && (
|
||||
@@ -218,7 +219,7 @@ export default function RestaurantDetail({
|
||||
<Icon name="play_circle" size={16} filled className="flex-shrink-0" />
|
||||
{v.title}
|
||||
</a>
|
||||
{v.foods_mentioned.length > 0 && (
|
||||
{v.foods_mentioned?.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1 mt-2">
|
||||
{v.foods_mentioned.map((f, i) => (
|
||||
<span
|
||||
@@ -235,7 +236,7 @@ export default function RestaurantDetail({
|
||||
{v.evaluation.text}
|
||||
</p>
|
||||
)}
|
||||
{v.guests.length > 0 && (
|
||||
{v.guests?.length > 0 && (
|
||||
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
게스트: {v.guests.join(", ")}
|
||||
</p>
|
||||
|
||||
Reference in New Issue
Block a user