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 (