Files
shoptime/com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx
optrader d6656848a2 [251210] feat: featuredBrandTopBanner API추가 , nbcu bg추가
🕐 커밋 시간: 2025. 12. 10. 13:02:33

📊 변경 통계:
  • 총 파일: 10개
  • 추가: +100줄
  • 삭제: -4줄

📁 추가된 파일:
  + com.twin.app.shoptime/assets/images/bg/nbcu_new.png

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/actionTypes.js
  ~ com.twin.app.shoptime/src/actions/brandActions.js
  ~ com.twin.app.shoptime/src/api/apiConfig.js
  ~ com.twin.app.shoptime/src/reducers/brandReducer.js
  ~ com.twin.app.shoptime/src/views/DetailPanel/components/DetailPanelBackground/DetailPanelBackground.jsx
  ~ com.twin.app.shoptime/src/views/DetailPanel/components/DetailPanelBackground/DetailPanelBackground.v2.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/Banner/Banner.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBrandsPanel.jsx
  ~ com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx

🔧 주요 변경 내용:
  • 타입 시스템 안정성 강화
  • 핵심 비즈니스 로직 개선
  • API 서비스 레이어 개선
  • UI 컴포넌트 아키텍처 개선
  • 소규모 기능 개선
  • 모듈 구조 개선
2025-12-10 13:02:34 +09:00

1425 lines
51 KiB
JavaScript

import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import classNames from 'classnames';
import {
useDispatch,
useSelector,
} from 'react-redux';
import { applyMiddleware } from 'redux';
import Spotlight from '@enact/spotlight';
import {
getContainerNavigableElements,
setContainerLastFocusedElement,
} from '@enact/spotlight/src/container';
// DetailPanelBackground 이미지 imports for preloading
import hsn from '../../../assets/images/bg/hsn_new.png';
import koreaKiosk from '../../../assets/images/bg/koreaKiosk_new.png';
import lgelectronics from '../../../assets/images/bg/lgelectronics_new.png';
import ontv4u from '../../../assets/images/bg/ontv4u_new.png';
import Pinkfong from '../../../assets/images/bg/Pinkfong_new.png';
import qvc from '../../../assets/images/bg/qvc_new.png';
import shoplc from '../../../assets/images/bg/shoplc_new.png';
import nbcu from '../../../assets/images/bg/nbcu_new.png';
import { types } from '../../actions/actionTypes';
import {
changeAppStatus,
setDeepLink,
setExitApp,
setHidePopup,
setOptionalTermsPopupShown,
setShowPopup,
updateOptionalTermsAgreement,
} from '../../actions/commonActions';
import { getWelcomeEventInfo } from '../../actions/eventActions';
import {
checkEnterThroughGNB,
getHomeLayout,
getHomeMainContents,
updateHomeInfo,
} from '../../actions/homeActions';
import {
sendLogGNB,
sendLogTotalRecommend,
} from '../../actions/logActions';
import {
getSubCategory,
getTop20Show,
} from '../../actions/mainActions';
import { setMyPageTermsAgree } from '../../actions/myPageActions';
import { getHomeOnSaleInfo } from '../../actions/onSaleActions';
import { updatePanel } from '../../actions/panelActions';
import {
finishVideoPreview,
hideModalVideo,
showModalVideo,
startVideoPlayerNew,
} from '../../actions/playActions';
import { getBestSeller } from '../../actions/productActions';
import OptionalConfirm from '../../components/Optional/OptionalConfirm';
import TBody from '../../components/TBody/TBody';
import TButton, { TYPES } from '../../components/TButton/TButton';
import TPanel from '../../components/TPanel/TPanel';
import TNewPopUp from '../../components/TPopUp/TNewPopUp';
import TPopUp from '../../components/TPopUp/TPopUp';
import TVerticalPagenator
from '../../components/TVerticalPagenator/TVerticalPagenator';
import useDebugKey from '../../hooks/useDebugKey';
import { useFocusHistory } from '../../hooks/useFocusHistory/useFocusHistory';
import usePrevious from '../../hooks/usePrevious';
import { useVideoPlay } from '../../hooks/useVideoPlay/useVideoPlay';
// [COMMENTED OUT] useVideoMove 관련 코드 주석 처리 - 향후 사용 검토 필요
// import { useVideoMove } from '../../hooks/useVideoTransition/useVideoMove';
// =======
// } from "../../actions/homeActions";
// import { sendLogGNB, sendLogTotalRecommend } from "../../actions/logActions";
// import { getSubCategory, getTop20Show } from "../../actions/mainActions";
// import { getHomeOnSaleInfo } from "../../actions/onSaleActions";
// import { finishVideoPreview } from "../../actions/playActions";
// import { getBestSeller } from "../../actions/productActions";
// import TBody from "../../components/TBody/TBody";
// import TButton, { TYPES } from "../../components/TButton/TButton";
// import TPanel from "../../components/TPanel/TPanel";
// import TPopUp from "../../components/TPopUp/TPopUp";
// import TVerticalPagenator from "../../components/TVerticalPagenator/TVerticalPagenator";
// import useDebugKey from "../../hooks/useDebugKey";
// >>>>>>> gitlab/develop
import {
ACTIVE_POPUP,
LOG_CONTEXT_NAME,
LOG_MENU,
LOG_MESSAGE_ID,
panel_names,
} from '../../utils/Config';
import { createDebugHelpers } from '../../utils/debug';
import { $L } from '../../utils/helperMethods';
import ImagePreloader from '../../utils/ImagePreloader';
import { SpotlightIds } from '../../utils/SpotlightIds';
import BestSeller from '../HomePanel/BestSeller/BestSeller';
import HomeBanner from '../HomePanel/HomeBanner/HomeBanner';
import HomeOnSale from '../HomePanel/HomeOnSale/HomeOnSale';
import css from '../HomePanel/HomePanel.module.less';
import PopularShow from '../HomePanel/PopularShow/PopularShow';
import SubCategory from '../HomePanel/SubCategory/SubCategory';
import EventPopUpBanner from './EventPopUpBanner/EventPopUpBanner';
import PickedForYou from './PickedForYou/PickedForYou';
// 디버그 헬퍼 설정
const DEBUG_MODE = false;
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
// 파트너사별 배경 이미지 맵
const BACKGROUND_IMAGES = {
1: qvc, // QVC
2: hsn, // HSN
4: ontv4u, // ONTV
9: lgelectronics, // LG ELECTRONICS
11: shoplc, // SHOPLC
16: koreaKiosk, // KOREA KIOSK
19: Pinkfong, // PINKFONG
21: nbcu, // NBCU
};
export const TEMPLATE_CODE_CONF = {
TOP: 'DSP00101',
CATEGORY_ITEM: 'DSP00102',
ON_SALE: 'DSP00103',
POPULAR_SHOW: 'DSP00104',
BEST_SELLER: 'DSP00105',
PICK_FOR_YOU: 'DSP00106',
};
const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
const dispatch = useDispatch();
// ✅ showGradientBackground prop 변경 추적 로그
// const prevShowGradientBackground = usePrevious(showGradientBackground);
// useEffect(() => {
// if (prevShowGradientBackground !== showGradientBackground) {
// console.log('[TRACE-GRADIENT] 📊 HomePanel prop changed:', {
// prev: prevShowGradientBackground,
// current: showGradientBackground,
// isOnTop: isOnTop
// });
// }
// }, [showGradientBackground, prevShowGradientBackground, isOnTop]);
useDebugKey({ isLandingPage: true });
// 🔽 HomeBanner 외부 7개 아이콘들의 focusHistory 추적
const focusHistory = useFocusHistory({
enableLogging: true,
useGlobalState: true,
logPrefix: '[HomePanel-Focus]',
});
// 🔽 useVideoPlay - 동영상 재생 제어
const videoPlay = useVideoPlay({
enableLogging: true,
logPrefix: '[HomePanel-VideoPlay]',
});
// 🔽 비디오 재생 의도 공유 ref
const videoPlayIntentRef = useRef(null);
// ✅ [251116] 비디오 중복 재생 방지 ref
const lastPlayedBannerIdRef = useRef(null);
// 🔽 useVideoMove - 포커스 전환 기반 동영상 제어
// [COMMENTED OUT] useVideoMove 미사용 - cleanup() 호출되지 않음
// const { playByTransition, cleanup } = useVideoMove({
// enableLogging: true,
// logPrefix: '[HomePanel-VideoMove]',
// });
const isGnbOpened = useSelector((state) => state.common.isGnbOpened);
const homeLayoutInfo = useSelector((state) => state.home.layoutData);
const panelInfo = useSelector((state) => state.home.homeInfo?.panelInfo ?? {});
const panels = useSelector((state) => state.panels.panels);
const webOSVersion = useSelector((state) => state.common.appStatus?.webOSVersion);
const enterThroughGNB = useSelector((state) => state.home.enterThroughGNB);
const defaultFocus = useSelector((state) => state.home.defaultFocus);
const bannerDataList = useSelector((state) => state.home.bannerData?.bannerInfos);
// ✅ PlayerPanel의 shouldShrinkTo1px 상태 추적
const playerPanelShouldShrink = useSelector((state) => {
const playerPanel = state.panels.panels.find((p) => p.name === panel_names.PLAYER_PANEL);
return playerPanel?.panelInfo?.shouldShrinkTo1px ?? false;
});
// ✅ PlayerPanel의 modal 상태 추적 (false → true 감지용)
const playerModalState = useSelector((state) => {
const playerPanel = state.panels.panels.find((p) => p.name === panel_names.PLAYER_PANEL);
return playerPanel?.panelInfo?.modal ?? false;
});
const prevPlayerModalStateRef = useRef(false);
const categoryInfos = useSelector((state) => state.onSale.homeOnSaleData?.data?.categoryInfos);
const categoryItemInfos = useSelector((state) => state.main.subCategoryData?.categoryItemInfos);
const { popupVisible, activePopup } = useSelector((state) => state.common.popup);
const eventPopInfosData = useSelector((state) => state.event.eventData.eventPopInfo);
const eventData = useSelector((state) => state.event.eventData);
const eventClickSuccess = useSelector((state) => state.event.eventClickSuccess);
const homeOnSaleInfos = useSelector((state) => state.onSale.homeOnSaleData?.data.homeOnSaleInfos);
const bestSellerDatas = useSelector((state) => state.product.bestSellerData?.bestSeller);
const topInfos = useSelector((state) => state.main.top20ShowData.topInfos);
const isDeepLink = useSelector((state) => state.common.deepLinkInfo.isDeepLink);
// 선택약관 관련 Redux 상태
const termsData = useSelector((state) => state.home.termsData);
const termsIdMap = useSelector((state) => state.home.termsIdMap);
const optionalTermsAvailable = useSelector((state) => state.home.optionalTermsAvailable);
const optionalTermsData = useSelector((state) => {
if (state.home.termsData && state.home.termsData.data && state.home.termsData.data.terms) {
return state.home.termsData.data.terms.find((term) => term.trmsTpCd === 'MST00405');
}
return null;
});
const termsLoading = useSelector((state) => state.common.termsLoading);
const currentTermsFlag = useSelector((state) => state.common.termsFlag);
const optionalTermsPopupFlow = useSelector((state) => state.common.optionalTermsPopupFlow);
const [btnDisabled, setBtnDisabled] = useState(true);
const [arrowBottom, setArrowBottom] = useState(true);
const [firstSpot, setFirstSpot] = useState(false);
const [eventPopOpen, setEventPopOpen] = useState(false);
const [nowShelf, setNowShelf] = useState(panelInfo.nowShelf);
const [firstLgCatCd, setFirstLgCatCd] = useState(panelInfo.currentCatCd ?? null);
const [cateCd, setCateCd] = useState(panelInfo.currentCatCd ?? null);
const [cateNm, setCateNm] = useState(panelInfo.currentCateName ?? null);
// 선택약관 팝업 상태
const [isOptionalConfirmVisible, setIsOptionalConfirmVisible] = useState(false);
const [isOptionalTermsVisible, setIsOptionalTermsVisible] = useState(false);
const [optionalTermsAgreed, setOptionalTermsAgreed] = useState(false);
const { entryMenu, nowMenu } = useSelector((state) => state.common.menu);
const [focusedContainerId, setFocusedContainerId] = useState(panelInfo.focusedContainerId);
// DetailPanel 진입 시 포커스 대상 저장
const lastFocusedTargetRef = useRef(panelInfo.lastFocusedTargetId || null);
const isInitialRender = useRef(true);
const verticalPagenatorRef = useRef(null);
const currentSentMenuRef = useRef(null);
const lastRestoredIdRef = useRef(null);
const focusedContainerIdRef = useRef(null);
const prevIsOnTopRef = useRef(isOnTop);
// ✅ [251124] HomePanel을 벗어날 때(isOnTop: true -> false) 현재 포커스 저장
useEffect(() => {
if (prevIsOnTopRef.current && !isOnTop) {
const current = Spotlight.getCurrent();
const tBody = document.querySelector(`[data-spotlight-id="${SpotlightIds.HOME_TBODY}"]`);
if (current && tBody && tBody.contains(current)) {
const targetId = current.getAttribute('data-spotlight-id');
if (targetId) {
dlog('[HomePanel] Saving focus before leaving:', targetId);
lastFocusedTargetRef.current = targetId;
dispatch(
updateHomeInfo({
name: panel_names.HOME_PANEL,
panelInfo: {
lastFocusedTargetId: targetId,
},
})
);
}
}
}
prevIsOnTopRef.current = isOnTop;
}, [isOnTop, dispatch]);
// ✅ [251119] DetailPanelBackground 이미지 프리로딩
// HomePanel 마운트 시 백그라운드로 모든 파트너사 배경 이미지를 미리 로드하여
// DetailPanel 진입 시 로딩 지연을 방지함
useEffect(() => {
dlog('[HomePanel] Starting background image preloading...');
// HomePanel의 다른 기능들에 영향을 주지 않도록 비동기로 조용히 실행
setTimeout(() => {
ImagePreloader.preloadAllImages(BACKGROUND_IMAGES)
.then((results) => {
const successCount = results.filter((r) => r !== null).length;
dlog(`[HomePanel] Background images preloaded: ${successCount}/${results.length} images`);
// 프리로딩 통계 정보 로깅 (디버깅용)
const stats = ImagePreloader.getStats();
dlog('[HomePanel] Preloader stats:', stats);
})
.catch((error) => {
derror('[HomePanel] Background image preloading failed:', error);
// 프리로딩 실패가 HomePanel 기능에 영향을 주지 않도록 조용히 처리
});
}, 1000); // HomePanel 안정화 후 1초 뒤 시작
}, []); // 마운트 시 한 번만 실행
useEffect(() => {
if (nowMenu === 'Home/Top') {
dispatch(
sendLogTotalRecommend({
messageId: LOG_MESSAGE_ID.HOME,
contextName: LOG_CONTEXT_NAME.HOME,
})
);
}
}, [entryMenu, nowMenu]);
const sortedHomeLayoutInfo = useMemo(() => {
if (homeLayoutInfo && homeLayoutInfo.homeLayoutInfo) {
const sorted = [...homeLayoutInfo.homeLayoutInfo].sort((x, y) => x.expsOrd - y.expsOrd);
return sorted;
}
return [];
}, [homeLayoutInfo]);
const pageSpotIds = useMemo(() => {
const spots = [];
for (let i = 0; i < sortedHomeLayoutInfo.length; i++) {
spots.push(sortedHomeLayoutInfo[i].shptmApphmDspyOptCd);
}
return spots;
}, [sortedHomeLayoutInfo]);
const cbChangePageRef = useRef(null);
const prevScrollTopRef = useRef(0); // 이전 scrollTop 추적
const isScrollingUpRef = useRef(false); // 스크롤 위로 감지
const scrollExpandTimerRef = useRef(null); // 스크롤 복구 타이머
// ✅ 최상단 비디오 복구 관련 Refs
const shouldShrinkRef = useRef(false); // PlayerPanel의 shouldShrinkTo1px 추적
const expandIntervalRef = useRef(null); // 최상단에서의 interval
const expandAttemptRef = useRef(0); // 복구 시도 횟수
const loadingComplete = useSelector((state) => state.common?.loadingComplete);
const isVideoTransitionLocked = useSelector((state) => state.home.videoTransitionLocked);
// 선택약관 동의 핸들러
const handleOptionalAgree = useCallback(() => {
if (!termsIdMap || Object.keys(termsIdMap).length === 0) {
if (process.env.NODE_ENV === 'development') {
derror('[HomePanel] termsIdMap이 없습니다:', termsIdMap);
}
return;
}
const requiredTermTypes = ['MST00401', 'MST00402', 'MST00405'];
const missingTerms = requiredTermTypes.filter((type) => !termsIdMap[type]);
if (missingTerms.length > 0) {
if (process.env.NODE_ENV === 'development') {
derror('[HomePanel] 누락된 약관 타입:', missingTerms);
}
return;
}
const termsList = [];
if (termsIdMap['MST00401']) {
termsList.push(termsIdMap['MST00401']); // 개인정보처리방침
}
if (termsIdMap['MST00402']) {
termsList.push(termsIdMap['MST00402']); // 이용약관
}
if (termsIdMap['MST00405']) {
termsList.push(termsIdMap['MST00405']); // 선택약관
}
const notTermsList = [];
if (process.env.NODE_ENV === 'development') {
dlog('[HomePanel] 현재 termsIdMap:', termsIdMap);
dlog('[HomePanel] 약관 동의 API 호출 파라미터:', {
termsList,
notTermsList,
});
}
const callback = (response) => {
if (response.retCode === '000' || response.retCode === 0) {
if (process.env.NODE_ENV === 'development') {
dlog('[HomePanel] 약관 동의 성공:', response);
}
dispatch(updateOptionalTermsAgreement(true));
setOptionalTermsAgreed(true);
} else {
if (process.env.NODE_ENV === 'development') {
derror('[HomePanel] 약관 동의 실패:', response);
}
}
};
dispatch(setMyPageTermsAgree({ termsList, notTermsList }, callback));
}, [dispatch, termsIdMap]);
const handleOptionalTermsClick = useCallback(() => {
if (process.env.NODE_ENV === 'development') {
dlog('[HomePanel] 약관 자세히 보기 클릭');
}
setIsOptionalConfirmVisible(false);
setIsOptionalTermsVisible(true);
}, []);
const handleOptionalAgreeClick = useCallback(() => {
handleOptionalAgree();
setIsOptionalConfirmVisible(false);
dispatch({
type: types.GET_TERMS_AGREE_YN_SUCCESS,
payload: {
...currentTermsFlag,
optionalTerms: 'Y',
},
});
setTimeout(()=>{
Spotlight.focus('home_tbody');
},100)
}, [handleOptionalAgree, dispatch, currentTermsFlag]);
const handleOptionalDeclineClick = useCallback(() => {
if (process.env.NODE_ENV === 'development') {
dlog('[HomePanel] 거절/다음에 하기 버튼 클릭');
}
dispatch(updateOptionalTermsAgreement(false));
setIsOptionalConfirmVisible(false);
setTimeout(()=>{
Spotlight.focus('home_tbody');
},100)
}, [dispatch]);
const handleTermsPopupClosed = useCallback(() => {
setIsOptionalTermsVisible(false);
setIsOptionalConfirmVisible(true);
Spotlight.focus('optional-confirm-popup');
}, []);
const handleTermsPopupAgree = useCallback(() => {
if (process.env.NODE_ENV === 'development') {
dlog('[HomePanel] handleTermsPopupAgree');
}
handleOptionalAgree();
setIsOptionalTermsVisible(false);
}, [handleOptionalAgree]);
// shouldShowOptionalTermsPopup 계산
const shouldShowOptionalTermsPopup = useMemo(() => {
// 1. 기본 조건 확인
if (termsLoading || isGnbOpened || !optionalTermsAvailable) {
return false;
}
// 2. 새로운 Redux 상태 확인 (TV 환경 최적화)
if (
optionalTermsPopupFlow.popupShown ||
optionalTermsPopupFlow.userDecision ||
optionalTermsPopupFlow.agreedInSession
) {
return false;
}
// 3. 서버 데이터 확인
const terms = termsData && termsData.data && termsData.data.terms;
if (!terms) {
return false;
}
const optionalTerm = terms.find((term) => term.trmsTpCd === 'MST00405');
const result = optionalTerm
? optionalTerm.trmsPopFlag === 'Y' && optionalTerm.trmsAgrFlag === 'N'
: false;
return result;
}, [
termsData.data?.terms,
termsLoading,
isGnbOpened,
optionalTermsAvailable,
optionalTermsPopupFlow,
]);
// 선택약관 팝업 표시 처리
useEffect(() => {
if (termsLoading) {
return;
}
if (shouldShowOptionalTermsPopup && !isOptionalConfirmVisible) {
dlog('shouldShowOptionalTermsPopup', shouldShowOptionalTermsPopup);
dlog('HomePanel optionalTermsConfirm 팝업 표시');
const timer = setTimeout(() => {
setIsOptionalConfirmVisible(true);
dispatch(setOptionalTermsPopupShown(true));
}, 1000);
return () => clearTimeout(timer);
}
}, [shouldShowOptionalTermsPopup, termsLoading, isOptionalConfirmVisible, dispatch]);
const onCancel = useCallback(() => {
const currentSpot = Spotlight.getCurrent();
if (!isOnTop) {
return;
}
if (isGnbOpened) {
return;
}
dispatch(
updateHomeInfo({
name: panel_names.HOME_PANEL,
panelInfo: {
currentSpot: currentSpot,
},
})
);
dispatch(setShowPopup(ACTIVE_POPUP.exitPopup));
}, [isOnTop, isGnbOpened, dispatch]);
const onExit = useCallback(() => {
dispatch(setExitApp());
dispatch(
sendLogTotalRecommend({
contextName: LOG_CONTEXT_NAME.SHOPTIME,
messageId: LOG_MESSAGE_ID.VIEW_CHANGE,
visible: false,
})
);
}, [dispatch, isVideoTransitionLocked]);
const onClose = useCallback(() => {
dispatch(setHidePopup());
setTimeout(() => {
Spotlight.focus(SpotlightIds.HOME_TBODY);
}, 0);
}, [dispatch]);
const handleTopButtonClick = useCallback(() => {
if (cbChangePageRef.current) {
cbChangePageRef.current(0, true);
}
if (sortedHomeLayoutInfo?.[0]) {
const containerId = sortedHomeLayoutInfo[0].shptmApphmDspyOptCd;
const navigableEls = getContainerNavigableElements(containerId);
const navigableIds = navigableEls.filter((el) => typeof el === 'string');
const target = containerId === TEMPLATE_CODE_CONF.TOP ? 'banner0' : containerId;
if (navigableIds.length > 0) {
setContainerLastFocusedElement(null, navigableIds);
}
setContainerLastFocusedElement(null, [containerId]);
setTimeout(() => Spotlight.focus(target));
}
}, [sortedHomeLayoutInfo]);
const doSendLogGNB = useCallback(
(containerId, location = null, title = null) => {
let nowMenu;
switch (containerId) {
case TEMPLATE_CODE_CONF.TOP:
nowMenu = LOG_MENU.HOME_TOP;
break;
case TEMPLATE_CODE_CONF.CATEGORY_ITEM:
nowMenu = LOG_MENU.HOME_CATEGORY;
break;
case TEMPLATE_CODE_CONF.ON_SALE:
nowMenu = LOG_MENU.HOME_ON_SALE;
break;
case TEMPLATE_CODE_CONF.POPULAR_SHOW:
nowMenu = LOG_MENU.HOME_POPULAR_SHOWS;
break;
case TEMPLATE_CODE_CONF.BEST_SELLER:
nowMenu = LOG_MENU.HOME_BEST_SELLER;
break;
case TEMPLATE_CODE_CONF.PICK_FOR_YOU:
nowMenu = LOG_MENU.HOME_PICKED_FOR_YOU;
break;
default:
nowMenu = LOG_MENU.HOME_TOP;
break;
}
if (containerId !== nowShelf && location && title) {
dispatch(
sendLogTotalRecommend({
contextName: LOG_CONTEXT_NAME.HOME,
messageId: LOG_MESSAGE_ID.SHELF,
shelfLocation: location,
shelfId: containerId,
shelfTitle: title,
})
);
setNowShelf(containerId);
}
if (nowMenu && nowMenu !== currentSentMenuRef.current) {
dispatch(sendLogGNB(nowMenu));
currentSentMenuRef.current = nowMenu;
}
},
[pageSpotIds, nowShelf, panelInfo.nowShelf]
);
const handleItemFocus = useCallback(
(containerId, location, title) => () => {
doSendLogGNB(containerId, location, title);
},
[doSendLogGNB]
);
const renderPageItem = useCallback(() => {
return (
<>
{sortedHomeLayoutInfo.map((el) => {
switch (el.shptmApphmDspyOptCd) {
case TEMPLATE_CODE_CONF.TOP: {
return (
<HomeBanner
key={el.shptmApphmDspyOptCd}
spotlightId={el.shptmApphmDspyOptCd}
firstSpot={!panelInfo.focusedContainerId && !panelInfo.currentSpot}
className={css.homeBannerWrap}
handleShelfFocus={handleItemFocus(
el.shptmApphmDspyOptCd,
el.expsOrd,
el.shptmApphmDspyOptNm
)}
handleItemFocus={handleItemFocus(el.shptmApphmDspyOptCd)}
videoPlayIntentRef={videoPlayIntentRef}
/>
);
}
case TEMPLATE_CODE_CONF.CATEGORY_ITEM: {
if (
categoryInfos &&
categoryInfos.length > 0 &&
categoryItemInfos?.subCatItemList &&
categoryItemInfos.subCatItemList.length > 0
) {
return (
<SubCategory
key={el.shptmApphmDspyOptCd}
spotlightId={el.shptmApphmDspyOptCd}
catCd={cateCd}
cateNm={cateNm}
handleShelfFocus={handleItemFocus(
el.shptmApphmDspyOptCd,
el.expsOrd,
el.shptmApphmDspyOptNm
)}
handleItemFocus={handleItemFocus(el.shptmApphmDspyOptCd)}
shelfLocation={el.expsOrd}
shelfTitle={el.shptmApphmDspyOptNm}
/>
);
} else break;
}
case TEMPLATE_CODE_CONF.ON_SALE: {
if (homeOnSaleInfos && homeOnSaleInfos.length > 0) {
return (
<HomeOnSale
key={el.shptmApphmDspyOptCd}
spotlightId={el.shptmApphmDspyOptCd}
handleShelfFocus={handleItemFocus(
el.shptmApphmDspyOptCd,
el.expsOrd,
el.shptmApphmDspyOptNm
)}
handleItemFocus={handleItemFocus(el.shptmApphmDspyOptCd)}
shelfLocation={el.expsOrd}
shelfTitle={el.shptmApphmDspyOptNm}
/>
);
} else break;
}
case TEMPLATE_CODE_CONF.POPULAR_SHOW: {
if (topInfos && topInfos.length > 0) {
return (
<PopularShow
key={el.shptmApphmDspyOptCd}
spotlightId={el.shptmApphmDspyOptCd}
handleShelfFocus={handleItemFocus(
el.shptmApphmDspyOptCd,
el.expsOrd,
el.shptmApphmDspyOptNm
)}
handleItemFocus={handleItemFocus(el.shptmApphmDspyOptCd)}
shelfLocation={el.expsOrd}
shelfTitle={el.shptmApphmDspyOptNm}
/>
);
} else break;
}
case TEMPLATE_CODE_CONF.BEST_SELLER: {
if (bestSellerDatas && bestSellerDatas.length > 0) {
return (
<BestSeller
key={el.shptmApphmDspyOptCd}
spotlightId={el.shptmApphmDspyOptCd}
handleShelfFocus={handleItemFocus(
el.shptmApphmDspyOptCd,
el.expsOrd,
el.shptmApphmDspyOptNm
)}
handleItemFocus={handleItemFocus(el.shptmApphmDspyOptCd)}
shelfLocation={el.expsOrd}
shelfTitle={el.shptmApphmDspyOptNm}
/>
);
} else break;
}
case TEMPLATE_CODE_CONF.PICK_FOR_YOU: {
if (bestSellerDatas && bestSellerDatas.length > 0) {
return (
<PickedForYou
key={el.shptmApphmDspyOptCd}
spotlightId={el.shptmApphmDspyOptCd}
handleShelfFocus={handleItemFocus(
el.shptmApphmDspyOptCd,
el.expsOrd,
el.shptmApphmDspyOptNm
)}
handleItemFocus={handleItemFocus(el.shptmApphmDspyOptCd)}
shelfLocation={el.expsOrd}
shelfTitle={el.shptmApphmDspyOptNm}
/>
);
} else break;
}
}
})}
{loadingComplete && sortedHomeLayoutInfo && sortedHomeLayoutInfo.length > 0 && (
<TButton
className={css.tButton}
onClick={handleTopButtonClick}
size={null}
type={TYPES.topButton}
spotlightId={'home-top-btn'}
spotlightDisabled={btnDisabled}
data-wheel-point={true}
aria-label="Move to Top, Button"
/>
)}
</>
);
}, [
sortedHomeLayoutInfo,
cateCd,
cateNm,
handleItemFocus,
handleTopButtonClick,
btnDisabled,
panelInfo.focusedContainerId,
panelInfo.currentSpot,
categoryItemInfos,
categoryInfos,
homeOnSaleInfos,
bestSellerDatas,
topInfos,
]);
// ✅ useEffect: Redux 상태 동기화 & Interval 정리
useEffect(() => {
// PlayerPanel의 shouldShrinkTo1px를 Ref에 동기화
shouldShrinkRef.current = playerPanelShouldShrink;
// shouldShrinkTo1px가 false가 되면 interval 즉시 정리
if (!playerPanelShouldShrink && expandIntervalRef.current) {
// console.log('[HomePanel] shouldShrinkTo1px=false - clearing expand interval');
clearInterval(expandIntervalRef.current);
expandIntervalRef.current = null;
expandAttemptRef.current = 0;
}
}, [playerPanelShouldShrink]);
const _onScrollStatusChanged = useCallback((status) => {
if (status === 'end') {
setArrowBottom(false);
} else {
setArrowBottom(true);
}
}, []);
// ✅ useCallback: 의존성은 dispatch만
const _onScroll = useCallback(
(e) => {
const currentScrollTop = e.scrollTop;
const prevScrollTop = prevScrollTopRef.current;
// ✅ 최상단 도달: 1px 비디오 복구 시도 (shouldShrinkRef.current로 읽음)
if (currentScrollTop <= 0) {
if (shouldShrinkRef.current && !expandIntervalRef.current) {
// console.log('[HomePanel] At top (scrollTop <= 1) - starting video expansion');
expandAttemptRef.current = 0;
// Interval 시작: 200ms마다 복구 시도
expandIntervalRef.current = setInterval(() => {
// 종료 조건: 최대 3회 시도
if (expandAttemptRef.current >= 3) {
// console.log('[HomePanel] Max expansion attempts reached (3)');
clearInterval(expandIntervalRef.current);
expandIntervalRef.current = null;
return;
}
// console.log('[HomePanel] Expansion attempt', expandAttemptRef.current + 1);
dispatch(showModalVideo());
expandAttemptRef.current++;
}, 200);
}
}
// 최상단 벗어남: interval 정리
else if (currentScrollTop > 1 && expandIntervalRef.current) {
// console.log('[HomePanel] Left top - clearing expand interval');
clearInterval(expandIntervalRef.current);
expandIntervalRef.current = null;
}
// 기존 로직: 아래로 스크롤
if (currentScrollTop > prevScrollTop) {
// 아래로 스크롤: 비디오를 1px로 축소
// console.log('[HomePanel] Scrolling down - shrinking video');
if (!isVideoTransitionLocked) {
dispatch(hideModalVideo());
}
// 기존 타이머 취소
if (scrollExpandTimerRef.current) {
clearTimeout(scrollExpandTimerRef.current);
scrollExpandTimerRef.current = null;
}
}
// 기존 로직: 위로 스크롤 (0이 아닌)
else if (currentScrollTop < prevScrollTop && currentScrollTop > 1) {
// 위로 스크롤 (최상단 아님): 1초 후 복구
// console.log('[HomePanel] Scrolling up - will expand after 1s');
// 기존 타이머 취소
if (scrollExpandTimerRef.current) {
clearTimeout(scrollExpandTimerRef.current);
}
// 1초 후 자동으로 크기 조정
scrollExpandTimerRef.current = setTimeout(() => {
// console.log('[HomePanel] 1s passed - auto expanding video');
dispatch(showModalVideo());
scrollExpandTimerRef.current = null;
}, 1000);
}
// 이전 scrollTop 업데이트
prevScrollTopRef.current = currentScrollTop;
},
[dispatch]
);
const _onFocusedContainerId = useCallback(
(containerId) => {
setFocusedContainerId(containerId);
dlog('[_onFocusedContainerId] 호출됨:', {
containerId,
enterThroughGNB,
isOnTop,
condition: enterThroughGNB && cbChangePageRef.current && !isOnTop,
});
if (enterThroughGNB && cbChangePageRef.current && !isOnTop) {
dlog('[_onFocusedContainerId] ⚠️ 맨 위로 스크롤 강제 실행!');
Spotlight.resume();
cbChangePageRef.current(0, false);
Spotlight.focus(defaultFocus);
setBtnDisabled(false);
dispatch(checkEnterThroughGNB(false));
return;
}
if (!firstSpot) {
// 초기 포커스 복원으로 스크롤이 튀는 현상 방지: 첫 진입 시 포커스 이동을 생략
setFirstSpot(true);
setBtnDisabled(false);
return;
}
},
[
panelInfo,
firstSpot,
enterThroughGNB,
defaultFocus,
cbChangePageRef,
dispatch,
isOnTop,
panels,
]
);
const bestSellerLoaded = useCallback(() => {
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
}, []);
useEffect(() => {
if (isInitialRender.current) {
isInitialRender.current = false;
// ✅ 마운트 시 banner0 자동 재생 설정
// console.log('[HomePanel] 마운트 - banner0 재생 시도', videoPlay);
videoPlayIntentRef.current = { bannerId: 'banner0' };
// console.log('[HomePanel] videoPlayIntentRef 설정:', videoPlayIntentRef.current);
// videoPlay.playVideo('banner0', { reason: 'mount-init' });
// console.log('[HomePanel] playVideo 호출 완료');
if (isDeepLink || (!panels.length && !panelInfo.focusedContainerId)) {
dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } }));
dispatch(getHomeMainContents());
dispatch(getHomeLayout());
dispatch(
getHomeOnSaleInfo({
homeSaleInfosIncFlag: 'Y',
categoryIncFlag: 'Y',
saleInfosIncFlag: 'N',
})
);
dispatch(getTop20Show());
dispatch(getBestSeller(bestSellerLoaded));
if (isDeepLink) {
dispatch(setDeepLink({ contentTarget: '', isDeepLink: false }));
}
}
}
}, [dispatch, isDeepLink, panels.length, panelInfo.focusedContainerId]);
useEffect(() => {
doSendLogGNB(focusedContainerId);
}, [focusedContainerId]);
useEffect(() => {
if (firstLgCatCd === null) {
if (categoryInfos) {
setFirstLgCatCd(categoryInfos[0]?.lgCatCd);
setCateCd(categoryInfos[0]?.lgCatCd);
setCateNm(categoryInfos[0]?.lgCatNm);
}
}
if (firstLgCatCd !== null) {
dispatch(
getSubCategory(
{
lgCatCd: firstLgCatCd, //LG Electronics Base
pageSize: 10,
tabType: 'CAT00102',
filterType: 'CAT00202',
recommendIncFlag: 'Y',
},
1
)
);
}
}, [categoryInfos, firstLgCatCd, dispatch]);
const checkBillngEvent = useCallback(
(eventTpCd) => {
if (webOSVersion && Number(webOSVersion) >= 4) {
if (eventTpCd === 'EVT00108' || eventTpCd === 'EVT00107') {
if (webOSVersion && Number(webOSVersion) >= 6) {
setEventPopOpen(true);
} else setEventPopOpen(false);
} else setEventPopOpen(true);
} else setEventPopOpen(false);
},
[webOSVersion]
);
useEffect(() => {
if (Object.keys(eventData).length >= 1) {
if (eventPopInfosData && eventClickSuccess === null) {
checkBillngEvent(eventPopInfosData.evntTpCd);
if (eventPopOpen) {
dispatch(setShowPopup(ACTIVE_POPUP.eventPopup));
}
}
}
}, [eventPopOpen, eventData]);
useEffect(() => {
if (Object.keys(eventData).length <= 0 && eventClickSuccess === null) {
dispatch(getWelcomeEventInfo());
}
}, [dispatch]);
// [COMMENTED OUT] 수동 포커스 복원 비활성화 - TBody의 enterTo: 'last-focused' 자동 복원 사용
// useEffect(() => {
// if (isOnTop && panelInfo?.lastFocusedTargetId) {
// Spotlight.focus(panelInfo.lastFocusedTargetId);
// }
// }, [isOnTop]);
// ✅ HomePanel 활성화 조건: 최상단 패널 또는 PlayerPanel 아래의 두 번째 패널
// 비디오가 재생이 아니면 videoPlayIntentRef의 bannerId로 비디오 재생
// [251116] isHomeOnTop인 경우에는 비디오가 항상 재생되어야 함
useEffect(() => {
// <<<<<<< HEAD
// console.log('[HomeActive] useEffect 실행 - isOnTop:', isOnTop);
// console.log('[HomeActive] videoPlayIntentRef.current:', videoPlayIntentRef.current);
// console.log(
// '[HomeActive] panels 상태:',
// panels.map((p) => ({ name: p.name, modal: p.panelInfo?.modal }))
// );
const isHomePanelActive = isOnTop;
// console.log('[HomeActive] isHomePanelActive:', isHomePanelActive);
if (!isHomePanelActive) {
// console.log('[HomeActive] 조건 실패: isHomePanelActive가 false');
return;
}
if (!videoPlayIntentRef.current?.bannerId) {
// console.log('[HomeActive] 조건 실패: videoPlayIntentRef.current?.bannerId가 없음', {
// hasRef: !!videoPlayIntentRef.current,
// hasBannerId: !!videoPlayIntentRef.current?.bannerId,
// });
return;
}
const bannerId = videoPlayIntentRef.current.bannerId;
// console.log('[HomeActive] 비디오 재생 시도 - bannerId:', bannerId);
// console.log('[HomeActive] 마지막 재생한 배너:', lastPlayedBannerIdRef.current);
// ✅ [251116] 중복 재생 방지: 같은 배너면 스킵
if (lastPlayedBannerIdRef.current === bannerId) {
// console.log('[HomeActive] 중복 호출 감지 - 스킵 (이미 재생 중)');
return;
}
// const currentPlaying = videoPlay.getCurrentPlayingBanner();
// 이미 재생 중인 배너가 다르면 새로 재생
// if (currentPlaying !== bannerId) {
// videoPlay.playVideo(bannerId, {
// reason: 'homePanel-active',
// });
// }
// ✅ [251116] startVideoPlayerNew로 비디오 자동 재생
// console.log('[HomeActive] dispatch(startVideoPlayerNew) 호출 직전:', { bannerId, modal: true });
// Spotlight.focus(bannerId);
// dispatch(
// startVideoPlayerNew({
// bannerId: bannerId,
// modal: true,
// modalContainerId: 'banner-modal-' + bannerId,
// })
// );
// console.log('[HomeActive] dispatch(startVideoPlayerNew) 호출 완료');
// 재생 기록 업데이트
lastPlayedBannerIdRef.current = bannerId;
// console.log('[HomeActive] 재생 기록 업데이트:', bannerId);
}, [isOnTop, dispatch]);
// ✅ [251120] DetailPanel 닫힘 감지 useEffect - detailPanelClosed flag 사용
const detailPanelClosed = useSelector(
(state) => state.home.homeInfo?.panelInfo?.detailPanelClosed
);
const detailPanelClosedTime = useSelector(
(state) => state.home.homeInfo?.panelInfo?.detailPanelClosedAt
);
useEffect(() => {
if (detailPanelClosed && isOnTop) {
// console.log('[TRACE-GRADIENT] 🔄 detailPanelClosed flag triggered - HomePanel reactivated');
// console.log('[HomePanel] *** ✅ HomePanel isOnTop = true');
// console.log('[HomePanel] *** detailPanelClosed:', detailPanelClosed);
// console.log('[HomePanel] *** detailPanelClosedTime:', detailPanelClosedTime);
// console.log('[HomePanel] *** isOnTop:', isOnTop);
// console.log('[HomePanel] *** videoPlayIntentRef.current:', videoPlayIntentRef.current);
// console.log('[HomePanel] *** lastPlayedBannerIdRef.current:', lastPlayedBannerIdRef.current);
// 🔽 videoPlayIntentRef가 null인 경우: 비디오 재생 가능한 첫 번째 배너 찾기
if (!videoPlayIntentRef.current && bannerDataList) {
dlog('[HomePanel] *** videoPlayIntentRef가 null - 첫 번째 비디오 배너 검색');
// HomeBanner.jsx의 defaultFocus 계산 로직과 동일
let targetIndex = 0;
let targetBannerData = null;
let videoData = null;
for (let i = 0; i < bannerDataList.length; i++) {
const data = bannerDataList[i];
const bannerDetailInfos = data.bannerDetailInfos;
if (data.shptmDspyTpNm === 'Random') {
if (
bannerDetailInfos[data.randomIndex].shptmBanrTpNm === 'LIVE' ||
bannerDetailInfos[data.randomIndex].shptmBanrTpNm === 'VOD'
) {
targetIndex = i;
targetBannerData = data;
videoData = bannerDetailInfos[data.randomIndex];
break;
}
} else if (
bannerDetailInfos.find(
(el) => el.shptmBanrTpNm === 'LIVE' || el.shptmBanrTpNm === 'VOD'
)
) {
targetIndex = i;
targetBannerData = data;
videoData = bannerDetailInfos[0];
break;
}
}
if (targetBannerData && videoData) {
const bannerId = 'banner' + targetIndex;
dlog('[HomePanel] *** 찾은 비디오 배너:', bannerId, videoData);
// videoPlayIntentRef에 값 할당 (HomeBanner.jsx의 초기 재생 로직과 동일)
videoPlayIntentRef.current = {
bannerId: bannerId,
videoProps: {
bannerId: bannerId,
showUrl: videoData.showUrl,
patnrId: videoData.patnrId,
showId: videoData.showId,
shptmBanrTpNm: videoData.shptmBanrTpNm,
lgCatCd: videoData.lgCatCd,
chanId: videoData.brdcChnlId,
modal: true,
modalContainerId: bannerId,
},
};
lastPlayedBannerIdRef.current = bannerId;
dlog('[HomePanel] *** videoPlayIntentRef 설정 완료:', videoPlayIntentRef.current);
} else {
dlog('[HomePanel] *** ⚠️ 비디오 재생 가능한 배너를 찾지 못함');
}
}
// DetailPanel 닫힘 후 비디오 자동 재생 복구
if (videoPlayIntentRef.current && lastPlayedBannerIdRef.current) {
// 🔽 videoPlayIntentRef는 videoProps에 비디오 정보를 담고 있으므로 풀어서 전달
// 혹시 videoProps에 없는 필드는 상위 레벨을 fallback으로 사용
const intent = videoPlayIntentRef.current;
const videoProps = intent.videoProps || {};
// 🔽 [251118] 현재 스크롤 위치 확인하여 비디오 크기 결정
const currentScrollTop = prevScrollTopRef.current;
const shouldShrink = currentScrollTop > 0;
dlog(
'[HomePanel] *** 비디오 복구 - currentScrollTop:',
currentScrollTop,
', shouldShrink:',
shouldShrink
);
dispatch(
startVideoPlayerNew({
...videoProps,
modal: true,
modalContainerId: videoProps.modalContainerId || 'home-video-modal',
modalClassName: videoProps.modalClassName || 'home-video-modal',
spotlightDisable: true, // 자동 복구 시 스크롤 이동 막기 위해 포커스 이동 금지
bannerId: lastPlayedBannerIdRef.current,
videoId: videoProps.videoId || intent.videoId,
showUrl: videoProps.showUrl || intent.showUrl,
patnrId: videoProps.patnrId || intent.patnrId,
showId: videoProps.showId || intent.showId,
shptmBanrTpNm: videoProps.shptmBanrTpNm || intent.shptmBanrTpNm,
lgCatCd: videoProps.lgCatCd || intent.lgCatCd,
shouldShrinkTo1px: shouldShrink, // 스크롤 위치에 따라 설정
})
);
// 🔽 DetailPanel에서 돌아온 뒤 포커스를 마지막 포커스 대상에 복원
dlog('[HomePanel] *** 🎯 Focus 복원 준비');
const targetFocusId = panelInfo.lastFocusedTargetId || lastFocusedTargetRef.current;
dlog(
'[HomePanel] *** 📍 targetFocusId:',
targetFocusId,
'(panelInfo.lastFocusedTargetId:',
panelInfo.lastFocusedTargetId,
', lastFocusedTargetRef:',
lastFocusedTargetRef.current,
')'
);
if (targetFocusId) {
dlog('[HomePanel] *** ⏰ 300ms 후 Spotlight.focus 호출 예정');
setTimeout(() => {
dlog('[HomePanel] *** 🔍 Spotlight.focus 호출:', targetFocusId);
Spotlight.focus(targetFocusId);
}, 300);
} else {
dlog('[HomePanel] *** ⚠️ targetFocusId가 없음 - Focus 복원 스킵');
}
// refs 초기화
videoPlayIntentRef.current = null;
lastPlayedBannerIdRef.current = null;
// detailPanelClosed 플래그 초기화 (다음 사이클에서 재사용 방지)
dlog('[HomePanel] *** detailPanelClosed flag 초기화');
dispatch(
updateHomeInfo({
name: panel_names.HOME_PANEL,
panelInfo: {
detailPanelClosed: false,
detailPanelClosedAt: undefined,
detailPanelClosedFromSource: undefined,
},
})
);
}
}
}, [detailPanelClosed, isOnTop, bannerDataList, dispatch]);
// =======
// const justCameBack = !prevIsOnTopRef.current && isOnTop;
// if (
// justCameBack &&
// panelInfo?.lastFocusedTargetId &&
// panelInfo.lastFocusedTargetId !== lastRestoredIdRef.current
// ) {
// lastRestoredIdRef.current = panelInfo.lastFocusedTargetId;
// setTimeout(() => {
// Spotlight.focus(panelInfo.lastFocusedTargetId);
// }, 150);
// }
// prevIsOnTopRef.current = isOnTop;
// }, [isOnTop, panelInfo?.lastFocusedTargetId]);
// >>>>>>> gitlab/develop
useEffect(() => {
return () => {
const c = Spotlight.getCurrent();
let targetSpotlightId = null;
let targetSpotlightCatcd = null;
let targetSpotlightCateNm = null;
if (c) {
targetSpotlightId = c.getAttribute('data-spotlight-id');
targetSpotlightCatcd = c.getAttribute('data-catcd-num');
targetSpotlightCateNm = c.getAttribute('data-catcd-nm');
}
const tBody = document.querySelector(`[data-spotlight-id="${SpotlightIds.HOME_TBODY}"]`);
const currentSpot = c && tBody.contains(c) ? targetSpotlightId : null;
dispatch(checkEnterThroughGNB(false));
dispatch(
updateHomeInfo({
name: panel_names.HOME_PANEL,
panelInfo: {
currentSpot: currentSpot,
currentCatCd: targetSpotlightCatcd,
currentCateName: targetSpotlightCateNm,
// <<<<<<< HEAD
focusedContainerId: focusedContainerIdRef.current,
lastFocusedTargetId: lastFocusedTargetRef.current || panelInfo.lastFocusedTargetId,
// =======
// focusedContainerId: focusedContainerId,
// >>>>>>> gitlab/develop
},
})
);
};
}, [dispatch, focusedContainerId]);
const handleArrowClick = useCallback(() => {
if (verticalPagenatorRef.current) {
const nextNode = verticalPagenatorRef.current.findNextWheelPoint();
if (nextNode) {
verticalPagenatorRef.current.moveToNode(nextNode, true, true);
}
}
}, [verticalPagenatorRef]);
// ✅ PlayerPanel modal 상태 변화 감지 (false → true) : Fullscreen -> Banner
useEffect(() => {
const prevModalState = prevPlayerModalStateRef.current;
dlog('[HomePanel] 🔍 Modal 상태 체크:', {
prevModalState,
playerModalState,
shouldExecute: prevModalState === false && playerModalState === true,
});
if (prevModalState === false && playerModalState === true) {
dlog('>>>>>[HomePanel] ▶️ PlayerPanel이 Fullscreen에서 Banner로 전환됨');
// 0.5초 후 비디오가 재생되는 배너에 포커스 테두리 효과 적용
// const focusTimer = setTimeout(() => {
// if (videoPlayIntentRef.current?.bannerId) {
// const bannerId = videoPlayIntentRef.current.bannerId;
// const bannerElement = document.querySelector(`[data-spotlight-id="${bannerId}"]`);
// if (bannerElement) {
// // 포커스 테두리 효과 적용 (기존 .focused mixin 스타일 적용)
// bannerElement.style.border = '4px solid #c70850';
// bannerElement.style.borderRadius = '12px';
// bannerElement.style.boxShadow = '0 0 22px 0 rgba(0, 0, 0, 0.5)';
// bannerElement.style.transition = 'all 0.3s ease';
// // 2초 후 효과 제거
// setTimeout(() => {
// bannerElement.style.border = '';
// bannerElement.style.borderRadius = '';
// bannerElement.style.boxShadow = '';
// }, 2000);
// }
// }
// }, 500);
}
prevPlayerModalStateRef.current = playerModalState;
}, [playerModalState, videoPlayIntentRef]);
// ✅ Cleanup: 컴포넌트 언마운트 시 모든 타이머 정리
useEffect(() => {
return () => {
if (scrollExpandTimerRef.current) {
clearTimeout(scrollExpandTimerRef.current);
scrollExpandTimerRef.current = null;
}
if (expandIntervalRef.current) {
clearInterval(expandIntervalRef.current);
expandIntervalRef.current = null;
}
};
}, []);
return (
<>
{/* HomePanel용 메모리 상주 그라데이션 배경 */}
<div
className={classNames(css.gradientBackground, { [css.visible]: showGradientBackground })}
aria-hidden="true"
/>
<TPanel className={css.panel} onCancel={onCancel}>
{homeLayoutInfo && (
<TBody
spotlightId={SpotlightIds.HOME_TBODY}
className={css.tBody}
scrollable={false}
spotlightDisabled={!isOnTop}
>
<TVerticalPagenator
ref={verticalPagenatorRef}
className={css.tVerticalPagenator}
spotlightId={'home_verticalPagenator'}
defaultContainerId={panelInfo.focusedContainerId}
disabled={!isOnTop}
onScrollStatusChanged={_onScrollStatusChanged}
onFocusedContainerId={_onFocusedContainerId}
cbChangePageRef={cbChangePageRef}
topMargin={36}
onScroll={_onScroll}
>
{renderPageItem()}
</TVerticalPagenator>
</TBody>
)}
{arrowBottom && (
<p className={classNames(css.arrow, css.arrowBottom)} onClick={handleArrowClick} />
)}
{activePopup === ACTIVE_POPUP.exitPopup && (
<TPopUp
kind="exitPopup"
open={popupVisible}
onExit={onExit}
onClose={onClose}
hasButton
button1Text={$L('Exit')}
button2Text={$L('Cancel')}
hasText
title={$L('Exit Shop Time')}
text={$L('Are you sure you want to exit Shop Time?')}
/>
)}
{(activePopup === ACTIVE_POPUP.eventPopup || activePopup === ACTIVE_POPUP.smsPopup) && (
<EventPopUpBanner />
)}
{/* 선택약관 동의 팝업 */}
<OptionalConfirm
open={isOptionalConfirmVisible}
spotlightId="optional-confirm-popup"
onClose={handleOptionalDeclineClick}
onOptionalTermsClick={handleOptionalTermsClick}
onOptionalAgreeClick={handleOptionalAgreeClick}
onOptionalDeclineClick={handleOptionalDeclineClick}
customPosition={true}
position={{
position: 'absolute',
top: '340px',
left: '0px',
bottom: 'unset',
transform: 'none',
}}
/>
{/* 선택약관 자세히 보기 팝업 */}
<TNewPopUp
kind="figmaTermsPopup"
open={isOptionalTermsVisible}
title={$L('Optional Terms')}
text={optionalTermsData?.trmsCntt || ''}
onClose={handleTermsPopupClosed}
onAgreeClick={handleTermsPopupAgree}
showAgreeButton={true}
/>
</TPanel>
</>
);
};
export default HomePanel;