🕐 커밋 시간: 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 컴포넌트 아키텍처 개선 • 소규모 기능 개선 • 모듈 구조 개선
1425 lines
51 KiB
JavaScript
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;
|