🕐 커밋 시간: 2025. 11. 18. 14:20:04 📊 변경 통계: • 총 파일: 2개 • 추가: +26줄 • 삭제: -10줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx ~ com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.module.less 🔧 주요 변경 내용: • 소규모 기능 개선
985 lines
30 KiB
JavaScript
985 lines
30 KiB
JavaScript
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
|
import classNames from 'classnames';
|
|
import { useDispatch, useSelector } from 'react-redux';
|
|
|
|
import Spotlight from '@enact/spotlight';
|
|
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
|
|
import Spottable from '@enact/spotlight/Spottable';
|
|
import { getContainerId } from '@enact/spotlight/src/container';
|
|
|
|
import btnPlay from '../../../../assets/images/btn/btn-play-thumb-nor.png';
|
|
import defaultLogoImg from '../../../../assets/images/ic-tab-partners-default@3x.png';
|
|
import emptyHorImage from '../../../../assets/images/img-home-banner-empty-hor.png';
|
|
import emptyVerImage from '../../../../assets/images/img-home-banner-empty-ver.png';
|
|
import defaultImageItem from '../../../../assets/images/img-thumb-empty-product@3x.png';
|
|
import liveShow from '../../../../assets/images/tag-liveshow.png';
|
|
import { changeAppStatus } from '../../../actions/commonActions';
|
|
import { updateHomeInfo, setVideoTransitionLock } from '../../../actions/homeActions';
|
|
import { sendLogTopContents, sendLogTotalRecommend } from '../../../actions/logActions';
|
|
import { pushPanel, navigateFromRandomUnit } from '../../../actions/panelActions';
|
|
import {
|
|
finishVideoPreview,
|
|
startVideoPlayer,
|
|
startVideoPlayerNew,
|
|
hideModalVideo,
|
|
} from '../../../actions/playActions';
|
|
import CustomImage from '../../../components/CustomImage/CustomImage';
|
|
import usePriceInfo from '../../../hooks/usePriceInfo';
|
|
import {
|
|
LOG_CONTEXT_NAME,
|
|
LOG_MENU,
|
|
LOG_MESSAGE_ID,
|
|
LOG_TP_NO,
|
|
panel_names,
|
|
} from '../../../utils/Config';
|
|
import { selectIsPlaying } from '../../../utils/playerState/playerStateSelectors';
|
|
import { $L, formatGMTString } from '../../../utils/helperMethods';
|
|
import { TEMPLATE_CODE_CONF } from '../HomePanel';
|
|
import css from './RandomUnit.module.less';
|
|
|
|
const SpottableComponent = Spottable('div');
|
|
|
|
const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
|
|
|
|
export default function RandomUnit({
|
|
bannerData,
|
|
spotlightId,
|
|
isHorizontal,
|
|
handleShelfFocus,
|
|
handleItemFocus,
|
|
randomNumber,
|
|
videoPlayerable = false,
|
|
videoPlayIntentRef,
|
|
}) {
|
|
const dispatch = useDispatch();
|
|
|
|
const bannerDetailInfos =
|
|
bannerData && bannerData.bannerDetailInfos ? bannerData.bannerDetailInfos : [];
|
|
|
|
const shptmTmplCd = useSelector((state) => state.home?.bannerData?.shptmTmplCd);
|
|
const nowMenu = useSelector((state) => state.common.menu.nowMenu);
|
|
const entryMenu = useSelector((state) => state.common.menu.entryMenu);
|
|
|
|
const homeCategory = useSelector((state) => state.home.menuData?.data?.homeCategory);
|
|
const countryCode = useSelector((state) => state.common.httpHeader.cntry_cd);
|
|
|
|
// 현재 재생 중인 비디오 배너 ID 가져오기
|
|
const currentVideoBannerId = useSelector((state) => {
|
|
const panels = state.panels?.panels || [];
|
|
const playerPanel = panels.find((p) => p.name === panel_names.PLAYER_PANEL);
|
|
return playerPanel?.panelInfo?.playerState?.currentBannerId;
|
|
});
|
|
|
|
const isVideoPlaying = useSelector(selectIsPlaying);
|
|
const isCurrentBannerVideoPlaying = Boolean(
|
|
currentVideoBannerId && isVideoPlaying && currentVideoBannerId === spotlightId
|
|
);
|
|
|
|
// 정상적으로 로딩되면 빈객체로 넘어가고 , 에러가 나면 객체안에 타입이 담겨옵니다.
|
|
const broadcast = useSelector((state) => state.common.broadcast);
|
|
const { curationId, curationTitle } = useSelector((state) => state.home);
|
|
const [randomData, setRandomData] = useState('');
|
|
const [priceInfos, setpriceInfos] = useState('');
|
|
const [isFocused, setIsFocused] = useState(false);
|
|
const [videoError, setVideoError] = useState(false);
|
|
const [liveIndicies, setLiveIndicies] = useState([]);
|
|
const [videoIntentVersion, setVideoIntentVersion] = useState(0);
|
|
const defaultFocus = useSelector((state) => state.home.defaultFocus);
|
|
const isVideoTransitionLocked = useSelector((state) => state.home.videoTransitionLocked);
|
|
|
|
const timerRef = useRef();
|
|
const hasAutoPlayStartedRef = useRef(false);
|
|
const hasPlaybackStartedRef = useRef(false);
|
|
const videoIntentTimerRef = useRef(null);
|
|
const isDefaultAutoPlayTarget = defaultFocus === spotlightId;
|
|
const lastProcessedIntentVersionRef = useRef(-1);
|
|
const bannerDataRef = useRef(bannerData);
|
|
const randomDataRef = useRef(
|
|
bannerDetailInfos && randomNumber !== undefined && bannerDetailInfos.length > 0
|
|
? bannerDetailInfos[randomNumber]
|
|
: null
|
|
);
|
|
|
|
const playerPanelInfo = useSelector((state) => {
|
|
const playerPanel = state.panels.panels.find((p) => p.name === panel_names.PLAYER_PANEL);
|
|
return playerPanel?.panelInfo;
|
|
});
|
|
|
|
// 🔽 [251116] 새로운 비디오 상태 관리 시스템
|
|
const videoPlayState = useSelector((state) => state.play.videoPlayState);
|
|
|
|
const handleStartVideo = useCallback(
|
|
(videoProps) => {
|
|
dispatch(setVideoTransitionLock(true));
|
|
hasPlaybackStartedRef.current = true;
|
|
dispatch(startVideoPlayerNew(videoProps));
|
|
},
|
|
[dispatch]
|
|
);
|
|
|
|
const finishAndUnlock = useCallback(() => {
|
|
dispatch(finishVideoPreview());
|
|
dispatch(setVideoTransitionLock(false));
|
|
}, [dispatch]);
|
|
|
|
useEffect(() => {
|
|
if (currentVideoBannerId === spotlightId) {
|
|
hasPlaybackStartedRef.current = true;
|
|
} else {
|
|
hasPlaybackStartedRef.current = false;
|
|
}
|
|
}, [currentVideoBannerId, spotlightId]);
|
|
|
|
useEffect(() => {
|
|
if (isVideoTransitionLocked && isCurrentBannerVideoPlaying) {
|
|
dispatch(setVideoTransitionLock(false));
|
|
}
|
|
}, [dispatch, isCurrentBannerVideoPlaying, isVideoTransitionLocked]);
|
|
|
|
useEffect(() => {
|
|
if (isVideoTransitionLocked && videoError) {
|
|
dispatch(setVideoTransitionLock(false));
|
|
}
|
|
}, [dispatch, isVideoTransitionLocked, videoError]);
|
|
|
|
const topContentsLogInfo = useMemo(() => {
|
|
if (randomDataRef.current) {
|
|
const currentRandomData = randomDataRef.current;
|
|
|
|
let contId, contNm;
|
|
|
|
switch (currentRandomData?.shptmBanrTpCd) {
|
|
// case: "LIVE" or "VOD"
|
|
case 'DSP00301':
|
|
case 'DSP00302':
|
|
contId = currentRandomData?.showId;
|
|
contNm = currentRandomData?.showNm;
|
|
break;
|
|
|
|
// case: "Image Banner"
|
|
case 'DSP00303':
|
|
contId = currentRandomData?.shptmLnkTpCd;
|
|
contNm = currentRandomData?.shptmLnkTpNm;
|
|
break;
|
|
|
|
// case: "Today's Deals"
|
|
default:
|
|
contId = currentRandomData?.prdtId;
|
|
contNm = currentRandomData?.prdtNm;
|
|
break;
|
|
}
|
|
|
|
if (
|
|
currentRandomData?.shptmLnkTpCd === 'DSP00503' || // "Hot Picks"
|
|
currentRandomData?.shptmLnkTpCd === 'DSP00509' // "Theme"
|
|
) {
|
|
contNm = contNm + ' | ' + currentRandomData?.lnkCurationId;
|
|
}
|
|
|
|
return {
|
|
banrNo: `${currentRandomData?.banrDpOrd}`,
|
|
banrTpNm: currentRandomData?.vtctpYn
|
|
? currentRandomData.vtctpYn === 'Y'
|
|
? 'Vertical'
|
|
: 'Horizontal'
|
|
: '',
|
|
contId,
|
|
contNm,
|
|
contTpNm: currentRandomData?.shptmBanrTpNm ?? '',
|
|
dspyTpNm: bannerDataRef.current?.shptmDspyTpNm ?? '',
|
|
expsOrd: bannerDataRef.current?.banrLctnNo ?? '',
|
|
linkTpCd: '',
|
|
patncNm: currentRandomData?.patncNm ?? '',
|
|
patnrId: currentRandomData?.patnrId ?? '',
|
|
tmplCd: shptmTmplCd,
|
|
};
|
|
}
|
|
|
|
return {};
|
|
}, [shptmTmplCd]);
|
|
|
|
const sendBannerLog = useCallback(
|
|
(bannerClick) => {
|
|
const data = randomDataRef.current;
|
|
|
|
const newParams =
|
|
data.shptmBanrTpNm === 'LIVE'
|
|
? {
|
|
showId: data.showId,
|
|
showTitle: data.showNm,
|
|
displayType: 'Fix',
|
|
bannerType: 'Horizontal',
|
|
}
|
|
: {
|
|
contentId: data.showId,
|
|
contentTitle: data.showNm,
|
|
displayType: 'Rolling',
|
|
bannerType: 'Vertical',
|
|
};
|
|
|
|
if (data && nowMenu === LOG_MENU.HOME_TOP) {
|
|
const logParams = {
|
|
contextName: LOG_CONTEXT_NAME.HOME,
|
|
messageId: bannerClick ? LOG_MESSAGE_ID.BANNER_CLICK : LOG_MESSAGE_ID.BANNER,
|
|
...newParams,
|
|
curationId,
|
|
curationTitle,
|
|
contentType: data.shptmBanrTpNm,
|
|
productId: data.prdtId,
|
|
productTitle: data.prdtNm,
|
|
partner: data.patncNm,
|
|
brand: data.brndNm, // <- 'brnad' 확인
|
|
location: bannerData.banrLctnNo,
|
|
};
|
|
dispatch(sendLogTotalRecommend(logParams));
|
|
}
|
|
},
|
|
[dispatch, nowMenu, curationId, curationTitle]
|
|
);
|
|
|
|
// 1. 비디오(live) 에러 감지
|
|
// 2. 라이브 영상이 2개 이상이면, 그 다음 영상으로 전환
|
|
// 3. 라이브 영상이 1개면 그 다음 영상으로 전환을 할 수 없으므로 에러 로고 화면을 보여준다.
|
|
// 라이브 영상이 1개 라도 순간적인 네트워크 오류나, 일시적인 오류 일수 있으므로, 일정 시간을 주고, 비디오를 다시 호출 한다.
|
|
|
|
useEffect(() => {
|
|
if (bannerDetailInfos && bannerDetailInfos.length > 0 && randomNumber !== undefined) {
|
|
const indices = bannerDetailInfos
|
|
.map((info, index) => (info && info.shptmBanrTpNm === 'LIVE' ? index : null))
|
|
.filter((index) => index !== null && index !== randomNumber);
|
|
|
|
setLiveIndicies(indices);
|
|
}
|
|
}, [bannerDetailInfos, randomNumber]);
|
|
|
|
// 비디오 에러일시, 클릭 이동
|
|
const videoErrorClick = useCallback(() => {
|
|
return dispatch(
|
|
pushPanel({
|
|
name: panel_names.FEATURED_BRANDS_PANEL,
|
|
panelInfo: { from: 'gnb', patnrId: randomData.patnrId },
|
|
})
|
|
);
|
|
}, [randomData, dispatch]);
|
|
|
|
// 포커스 인
|
|
const handleFocus = useCallback(() => {
|
|
// 🔽 [251116] 현재 비디오 상태 로그 출력
|
|
console.log('[RandomUnit] onFocus - 현재 비디오 상태', {
|
|
playback: videoPlayState.playback,
|
|
display: videoPlayState.display,
|
|
videoId: videoPlayState.videoId,
|
|
loadingProgress: videoPlayState.loadingProgress,
|
|
loadingError: videoPlayState.loadingError,
|
|
isPlaying: videoPlayState.isPlaying,
|
|
isPaused: videoPlayState.isPaused,
|
|
lastUpdate: videoPlayState.lastUpdate,
|
|
});
|
|
|
|
console.log('[RandomUnit] onFocus - 비디오 재생 의도 저장', {
|
|
bannerId: spotlightId,
|
|
videoPlayerable,
|
|
currentIntent: videoPlayIntentRef.current?.bannerId,
|
|
currentVideoBannerId,
|
|
randomData: randomData?.showId || 'no-show-id',
|
|
});
|
|
|
|
setIsFocused(true);
|
|
|
|
// 🔽 [251116] 수정: 재생 의도만 저장, 즉시 실행 ❌
|
|
if (videoPlayerable) {
|
|
videoPlayIntentRef.current = {
|
|
bannerId: spotlightId,
|
|
videoProps: {
|
|
bannerId: spotlightId,
|
|
showUrl: randomData.showUrl,
|
|
patnrId: randomData.patnrId,
|
|
showId: randomData.showId,
|
|
shptmBanrTpNm: randomData.shptmBanrTpNm,
|
|
lgCatCd: randomData.lgCatCd,
|
|
chanId: randomData.brdcChnlId,
|
|
modal: true,
|
|
modalContainerId: spotlightId,
|
|
},
|
|
timestamp: Date.now(),
|
|
};
|
|
|
|
console.log('[RandomUnit] 비디오 재생 의도 저장됨', {
|
|
intentBannerId: spotlightId,
|
|
videoUrl: randomData.showUrl,
|
|
});
|
|
|
|
setVideoIntentVersion((version) => version + 1);
|
|
|
|
// 기존 finishVideoPreview 호출 제거!
|
|
// if (videoPlayerable && currentVideoBannerId && currentVideoBannerId !== spotlightId) {
|
|
// dispatch(finishVideoPreview());
|
|
// }
|
|
}
|
|
|
|
// video가 플레이 불가능한 경우: 재생 중인 비디오를 1px로 축소
|
|
if (!videoPlayerable && currentVideoBannerId && isCurrentBannerVideoPlaying) {
|
|
console.log('[RandomUnit] videoPlayerable=false, shrinking video to 1px', {
|
|
videoPlayerable,
|
|
currentVideoBannerId,
|
|
});
|
|
dispatch(hideModalVideo());
|
|
}
|
|
|
|
if (handleItemFocus) {
|
|
handleItemFocus();
|
|
}
|
|
}, [handleItemFocus, videoPlayerable, spotlightId, currentVideoBannerId, dispatch, randomData]);
|
|
|
|
const shelfFocus = useCallback(() => {
|
|
if (handleShelfFocus) {
|
|
handleShelfFocus();
|
|
}
|
|
}, [handleShelfFocus]);
|
|
|
|
// 포커스 아웃
|
|
const onBlur = useCallback(() => {
|
|
setIsFocused(false);
|
|
clearTimeout(timerRef.current);
|
|
|
|
// 현재 비디오가 재생 중이면 1px로 축소 (주석: HomePanel 스크롤로 처리)
|
|
// if (currentVideoBannerId && videoPlayerable) {
|
|
// console.log('[RandomUnit] onBlur: shrinking video to 1px');
|
|
// dispatch(hideModalVideo());
|
|
// }
|
|
|
|
// dispatch(finishVideoPreview());
|
|
}, [isFocused]);
|
|
|
|
// DSP00501 : Featured Brands
|
|
// DSP00502 : Trending now
|
|
// DSP00503 : HOT PICKS
|
|
// DSP00504 : ON SALE
|
|
// DSP00505 : CATEGORY
|
|
// DSP00506 : Product Detail
|
|
// DSP00507 : VOD
|
|
// DSP00508 : Show Detail
|
|
// DSP00509 : Theme
|
|
|
|
const categoryData = useMemo(() => {
|
|
if (randomData && randomData.shptmLnkTpCd === 'DSP00505') {
|
|
if (homeCategory && homeCategory.length > 0) {
|
|
const foundCategory = homeCategory.find((data) => data.lgCatCd === randomData.lgCatCd);
|
|
if (foundCategory) {
|
|
return {
|
|
lgCatNm: foundCategory.lgCatNm,
|
|
COUNT: foundCategory.COUNT,
|
|
};
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}, [homeCategory, randomData.shptmLnkTpCd]);
|
|
|
|
// 이미지 배너 클릭
|
|
const imageBannerClick = useCallback(() => {
|
|
let linkInfo = {};
|
|
const linkType = randomData.shptmLnkTpCd;
|
|
|
|
switch (linkType) {
|
|
case 'DSP00501':
|
|
linkInfo = {
|
|
name: panel_names.FEATURED_BRANDS_PANEL,
|
|
panelInfo: { from: 'gnb', patnrId: randomData.patnrId },
|
|
};
|
|
break;
|
|
|
|
case 'DSP00502':
|
|
linkInfo = {
|
|
name: panel_names.TRENDING_NOW_PANEL,
|
|
panelInfo: {},
|
|
};
|
|
break;
|
|
|
|
case 'DSP00503':
|
|
linkInfo = {
|
|
name: panel_names.HOT_PICKS_PANEL,
|
|
panelInfo: {
|
|
patnrId: randomData.patnrId,
|
|
curationId: randomData.lnkCurationId,
|
|
},
|
|
};
|
|
break;
|
|
|
|
case 'DSP00504':
|
|
linkInfo = {
|
|
name: panel_names.ON_SALE_PANEL,
|
|
panelInfo: {
|
|
lgCatCd: randomData.lgCatCd,
|
|
},
|
|
};
|
|
break;
|
|
|
|
case 'DSP00505':
|
|
if (Object.keys(categoryData).length > 0) {
|
|
linkInfo = {
|
|
name: panel_names.CATEGORY_PANEL,
|
|
panelInfo: {
|
|
lgCatCd: randomData.lgCatCd,
|
|
lgCatNm: categoryData.lgCatNm,
|
|
COUNT: categoryData.COUNT,
|
|
currentSpot: null,
|
|
dropDownTab: 0,
|
|
tab: 0,
|
|
focusedContainerId: null,
|
|
},
|
|
};
|
|
}
|
|
break;
|
|
|
|
case 'DSP00506':
|
|
linkInfo = {
|
|
name: panel_names.DETAIL_PANEL,
|
|
panelInfo: {
|
|
patnrId: randomData.patnrId,
|
|
prdtId: randomData.prdtId,
|
|
curationId: randomData.lnkCurationId,
|
|
},
|
|
};
|
|
break;
|
|
|
|
case 'DSP00507':
|
|
linkInfo = {
|
|
patnrId: randomData.patnrId,
|
|
showId: randomData.showId,
|
|
shptmBanrTpNm: 'VOD',
|
|
lgCatCd: randomData.lgCatCd,
|
|
modal: false,
|
|
};
|
|
break;
|
|
|
|
case 'DSP00508':
|
|
linkInfo = {
|
|
name: panel_names.DETAIL_PANEL,
|
|
panelInfo: {
|
|
patnrId: randomData.patnrId,
|
|
curationId: randomData.lnkCurationId,
|
|
prdtId: randomData.prdtId,
|
|
type: 'theme',
|
|
},
|
|
};
|
|
break;
|
|
|
|
case 'DSP00509':
|
|
linkInfo = {
|
|
name: panel_names.THEME_CURATION_PANEL,
|
|
panelInfo: {
|
|
curationId: randomData.lnkCurationId,
|
|
},
|
|
};
|
|
break;
|
|
|
|
default:
|
|
linkInfo = {
|
|
name: panel_names.HOME_PANEL,
|
|
panelInfo: {},
|
|
};
|
|
break;
|
|
}
|
|
|
|
let action = linkType === 'DSP00507' ? startVideoPlayer : pushPanel;
|
|
|
|
const isNavigatingToDetail = linkInfo.name === panel_names.DETAIL_PANEL;
|
|
if (isNavigatingToDetail && playerPanelInfo?.modal !== false) {
|
|
finishAndUnlock();
|
|
}
|
|
|
|
// 🔖 DetailPanel로 이동하기 전에 현재 포커스 대상 저장 (HomePanel 복귀 시 복원)
|
|
if (isNavigatingToDetail) {
|
|
const currentSpot = Spotlight.getCurrent();
|
|
const lastFocusedTargetId = getContainerId(currentSpot);
|
|
if (lastFocusedTargetId) {
|
|
dispatch(
|
|
updateHomeInfo({
|
|
name: panel_names.HOME_PANEL,
|
|
panelInfo: {
|
|
lastFocusedTargetId,
|
|
currentSpot: currentSpot?.getAttribute('data-spotlight-id'),
|
|
focusedContainerId: TEMPLATE_CODE_CONF.TOP,
|
|
},
|
|
})
|
|
);
|
|
}
|
|
}
|
|
|
|
dispatch(action(linkInfo));
|
|
sendBannerLog(true);
|
|
dispatch(
|
|
sendLogTopContents({
|
|
...topContentsLogInfo,
|
|
inDt: formatGMTString(new Date()) ?? '',
|
|
logTpNo: LOG_TP_NO.TOP_CONTENTS.CLICK,
|
|
})
|
|
);
|
|
}, [
|
|
categoryData,
|
|
dispatch,
|
|
randomData?.lgCatCd,
|
|
randomData?.lnkCurationId,
|
|
randomData?.patnrId,
|
|
randomData?.prdtId,
|
|
randomData?.showId,
|
|
randomData?.shptmLnkTpCd,
|
|
topContentsLogInfo,
|
|
playerPanelInfo?.modal,
|
|
sendBannerLog,
|
|
finishAndUnlock,
|
|
]);
|
|
|
|
// 투데이즈딜 클릭
|
|
const todayDealClick = useCallback(() => {
|
|
dispatch(
|
|
navigateFromRandomUnit({
|
|
patnrId: randomData.patnrId,
|
|
prdtId: randomData.prdtId,
|
|
type: 'product',
|
|
})
|
|
);
|
|
|
|
sendBannerLog(true);
|
|
|
|
dispatch(
|
|
sendLogTopContents({
|
|
...topContentsLogInfo,
|
|
inDt: formatGMTString(new Date()) ?? '',
|
|
logTpNo: LOG_TP_NO.TOP_CONTENTS.CLICK,
|
|
})
|
|
);
|
|
}, [
|
|
dispatch,
|
|
randomData?.patnrId,
|
|
randomData?.prdtId,
|
|
topContentsLogInfo,
|
|
sendBannerLog,
|
|
playerPanelInfo?.modal,
|
|
finishAndUnlock,
|
|
]);
|
|
|
|
// 비디오 클릭
|
|
const videoClick = useCallback(() => {
|
|
const lastFocusedTargetId = getContainerId(Spotlight.getCurrent());
|
|
const currentSpot = Spotlight.getCurrent();
|
|
if (lastFocusedTargetId) {
|
|
dispatch(
|
|
updateHomeInfo({
|
|
name: panel_names.HOME_PANEL,
|
|
panelInfo: {
|
|
lastFocusedTargetId,
|
|
focusedContainerId: TEMPLATE_CODE_CONF.TOP,
|
|
currentSpot: currentSpot?.getAttribute('data-spotlight-id'),
|
|
},
|
|
})
|
|
);
|
|
}
|
|
|
|
// ✅ modal=true → modal=false로 전환 (또는 초기 로드 시 modal=false)
|
|
// playActions의 shouldSkipVideoPlayback이 modal 상태를 확인하므로
|
|
// 모드 전환은 중복 방지 로직을 스킵하여 정상 작동
|
|
console.log(
|
|
'[RandomUnit] videoClick: current playerPanel modal state:',
|
|
playerPanelInfo?.modal
|
|
);
|
|
|
|
handleStartVideo({
|
|
bannerId: spotlightId,
|
|
showUrl: randomData.showUrl,
|
|
patnrId: randomData.patnrId,
|
|
showId: randomData.showId,
|
|
shptmBanrTpNm: randomData.showId ? randomData.shptmBanrTpNm : 'MEDIA',
|
|
lgCatCd: randomData.lgCatCd,
|
|
chanId: randomData.brdcChnlId,
|
|
modal: false,
|
|
modalContainerId: spotlightId,
|
|
modalClassName: css.videoModal,
|
|
});
|
|
|
|
dispatch(
|
|
sendLogTopContents({
|
|
...topContentsLogInfo,
|
|
inDt: formatGMTString(new Date()) ?? '',
|
|
logTpNo: LOG_TP_NO.TOP_CONTENTS.CLICK,
|
|
})
|
|
);
|
|
|
|
sendBannerLog(true);
|
|
onBlur();
|
|
}, [
|
|
randomData,
|
|
spotlightId,
|
|
topContentsLogInfo,
|
|
nowMenu,
|
|
randomDataRef,
|
|
sendBannerLog,
|
|
onBlur,
|
|
playerPanelInfo?.modal,
|
|
dispatch,
|
|
handleStartVideo,
|
|
]);
|
|
|
|
// 투데이즈 딜 가격 정보
|
|
const { originalPrice, discountedPrice, discountRate, offerInfo } =
|
|
usePriceInfo(priceInfos) || {};
|
|
|
|
// 로그
|
|
useEffect(() => {
|
|
let _nowMenu = nowMenu;
|
|
let _entryMenu = entryMenu;
|
|
|
|
if (nowMenu === LOG_MENU.HOME_TOP) {
|
|
const params = {
|
|
...topContentsLogInfo,
|
|
entryMenu: _entryMenu,
|
|
inDt: formatGMTString(new Date()) ?? '',
|
|
logTpNo: LOG_TP_NO.TOP_CONTENTS.VIEW,
|
|
nowMenu: _nowMenu,
|
|
};
|
|
|
|
return () => dispatch(sendLogTopContents(params));
|
|
}
|
|
}, [dispatch, entryMenu, nowMenu, topContentsLogInfo]);
|
|
|
|
//통합로그
|
|
useEffect(() => {
|
|
sendBannerLog();
|
|
}, [sendBannerLog]);
|
|
|
|
useEffect(() => {
|
|
if (
|
|
bannerData &&
|
|
bannerDetailInfos &&
|
|
randomNumber !== undefined &&
|
|
bannerDetailInfos.length > 0
|
|
) {
|
|
setRandomData(bannerDetailInfos[randomNumber]);
|
|
}
|
|
}, [bannerData, bannerDetailInfos, randomNumber]);
|
|
|
|
useEffect(() => {
|
|
if (randomData && randomData.priceInfo !== null) {
|
|
return setpriceInfos(randomData.priceInfo);
|
|
}
|
|
}, [randomData]);
|
|
|
|
// 🔽 [251116] 기존 로직 단순화 - videoPlayIntentRef가 모든 비디오 제어를 담당
|
|
useEffect(() => {
|
|
if (!isFocused) {
|
|
setVideoError(false);
|
|
}
|
|
}, [isFocused]);
|
|
|
|
// 🔽 [251116] 기존 복잡한 비디오 시작 로직 비활성화
|
|
/*
|
|
useEffect(() => {
|
|
if (isFocused && !videoError && !hasPlaybackStartedRef.current) {
|
|
if (timerRef.current) {
|
|
clearTimeout(timerRef.current);
|
|
}
|
|
timerRef.current = setTimeout(() => {
|
|
handleStartVideo({
|
|
bannerId: spotlightId,
|
|
showUrl: randomData.showUrl,
|
|
patnrId: randomData.patnrId,
|
|
showId: randomData.showId,
|
|
shptmBanrTpNm: randomData.showId ? randomData.shptmBanrTpNm : 'MEDIA',
|
|
lgCatCd: randomData.lgCatCd,
|
|
chanId: randomData.brdcChnlId,
|
|
modal: true,
|
|
modalContainerId: spotlightId,
|
|
modalClassName: css.videoModal,
|
|
isVerticalModal: !isHorizontal,
|
|
});
|
|
if (isDefaultAutoPlayTarget) {
|
|
hasAutoPlayStartedRef.current = true;
|
|
}
|
|
timerRef.current = null;
|
|
}, 1000);
|
|
}
|
|
if (!isFocused) {
|
|
setVideoError(false);
|
|
if (timerRef.current && !hasPlaybackStartedRef.current) {
|
|
clearTimeout(timerRef.current);
|
|
timerRef.current = null;
|
|
}
|
|
}
|
|
return () => {
|
|
if (timerRef.current && !hasPlaybackStartedRef.current) {
|
|
clearTimeout(timerRef.current);
|
|
timerRef.current = null;
|
|
}
|
|
};
|
|
}, [
|
|
isFocused,
|
|
videoError,
|
|
isHorizontal,
|
|
randomData,
|
|
isDefaultAutoPlayTarget,
|
|
spotlightId,
|
|
handleStartVideo,
|
|
]);
|
|
*/
|
|
|
|
// 🔽 [251116] 신규: videoPlayIntentRef 기반 비디오 제어
|
|
useEffect(() => {
|
|
if (!videoPlayIntentRef.current) {
|
|
return undefined;
|
|
}
|
|
if (lastProcessedIntentVersionRef.current === videoIntentVersion) {
|
|
return undefined;
|
|
}
|
|
lastProcessedIntentVersionRef.current = videoIntentVersion;
|
|
|
|
const intent = videoPlayIntentRef.current;
|
|
const currentVideoId = videoPlayState.videoId;
|
|
const activeBannerId = currentVideoBannerId;
|
|
|
|
console.log('[RandomUnit] videoPlayIntent useEffect', {
|
|
intentBannerId: intent.bannerId,
|
|
currentVideoId,
|
|
activeBannerId,
|
|
currentPlayback: videoPlayState.playback,
|
|
currentDisplay: videoPlayState.display,
|
|
loadingProgress: videoPlayState.loadingProgress,
|
|
shouldStart: intent.bannerId !== activeBannerId,
|
|
isVideoTransitionLocked,
|
|
timestamp: Date.now(),
|
|
});
|
|
|
|
if (intent.bannerId === activeBannerId) {
|
|
return undefined;
|
|
}
|
|
|
|
if (videoIntentTimerRef.current) {
|
|
clearTimeout(videoIntentTimerRef.current);
|
|
}
|
|
|
|
videoIntentTimerRef.current = setTimeout(() => {
|
|
if (videoPlayIntentRef.current?.bannerId !== intent.bannerId) {
|
|
return;
|
|
}
|
|
|
|
console.log('[RandomUnit] 비디오 변경 실행', {
|
|
from: currentVideoId,
|
|
to: intent.bannerId,
|
|
});
|
|
|
|
if (activeBannerId) {
|
|
dispatch(finishVideoPreview());
|
|
|
|
setTimeout(() => {
|
|
dispatch(setVideoTransitionLock(true));
|
|
dispatch(startVideoPlayerNew(intent.videoProps));
|
|
hasPlaybackStartedRef.current = true;
|
|
|
|
setTimeout(() => {
|
|
dispatch(setVideoTransitionLock(false));
|
|
}, 2000);
|
|
}, 100);
|
|
} else {
|
|
dispatch(setVideoTransitionLock(true));
|
|
dispatch(startVideoPlayerNew(intent.videoProps));
|
|
hasPlaybackStartedRef.current = true;
|
|
|
|
setTimeout(() => {
|
|
dispatch(setVideoTransitionLock(false));
|
|
}, 2000);
|
|
}
|
|
}, 300); // 안정화 시간
|
|
|
|
return () => {
|
|
if (videoIntentTimerRef.current) {
|
|
clearTimeout(videoIntentTimerRef.current);
|
|
videoIntentTimerRef.current = null;
|
|
}
|
|
};
|
|
}, [
|
|
videoPlayState.videoId,
|
|
dispatch,
|
|
videoPlayIntentRef,
|
|
videoIntentVersion,
|
|
isVideoTransitionLocked,
|
|
currentVideoBannerId,
|
|
]);
|
|
|
|
useEffect(() => {
|
|
if (isFocused && broadcast?.type === 'videoError') {
|
|
clearTimeout(timerRef.current);
|
|
setVideoError(true);
|
|
dispatch(finishVideoPreview());
|
|
if (liveIndicies.length > 0) {
|
|
const nextIndex = liveIndicies[0];
|
|
|
|
setLiveIndicies((prev) => prev.slice(1));
|
|
if (bannerDetailInfos && bannerDetailInfos[nextIndex]) {
|
|
setRandomData(bannerDetailInfos[nextIndex]);
|
|
}
|
|
|
|
setTimeout(() => {
|
|
setVideoError(false);
|
|
}, 0);
|
|
}
|
|
}
|
|
}, [broadcast]);
|
|
|
|
// 데이터가 없으면 렌더링하지 않음
|
|
if (!bannerData || !randomData || !bannerDetailInfos) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<Container
|
|
className={classNames(css.rollingWrap, isHorizontal && css.isHorizontalWrap)}
|
|
onFocus={shelfFocus}
|
|
>
|
|
{randomData?.shptmBanrTpNm == 'Image Banner' ? (
|
|
<SpottableComponent
|
|
className={classNames(css.itemBox, isHorizontal && css.isHorizontal)}
|
|
onClick={imageBannerClick}
|
|
spotlightId={spotlightId}
|
|
aria-label={randomData.prdtNm ? randomData.prdtNm : randomData.tmnlImgNm}
|
|
>
|
|
<div className={css.imgBanner}>
|
|
<CustomImage
|
|
delay={0}
|
|
src={randomData.tmnlImgPath}
|
|
animationSpeed="fast"
|
|
ariaLabel={randomData.tmnImgNm}
|
|
/>
|
|
</div>
|
|
</SpottableComponent>
|
|
) : randomData?.shptmBanrTpNm == 'LIVE' || randomData?.shptmBanrTpNm == 'VOD' ? (
|
|
<SpottableComponent
|
|
className={classNames(
|
|
css.itemBox,
|
|
isHorizontal && css.isHorizontal,
|
|
// 포커스가 다른 곳으로 이동한 뒤 videoPlayIntentRef로 재생되어도 테두리 노출 억제
|
|
!isFocused && css.hideFocusRing
|
|
)}
|
|
onClick={videoError === true ? videoErrorClick : videoClick}
|
|
onFocus={handleFocus}
|
|
onBlur={onBlur}
|
|
spotlightId={spotlightId}
|
|
aria-label={
|
|
randomData.shptmBanrTpNm == 'LIVE' ? 'LIVE ' + randomData.showNm : randomData.showNm
|
|
}
|
|
alt={'LIVE'}
|
|
>
|
|
{randomData.shptmBanrTpNm == 'LIVE' && videoError === false && (
|
|
<p className={css.liveIcon}>
|
|
<CustomImage delay={0} src={liveShow} animationSpeed="fast" ariaLabel="LIVE icon" />
|
|
</p>
|
|
)}
|
|
|
|
{/* 비디오 에러 발생 */}
|
|
{videoError === true && (
|
|
<div className={css.errorContents}>
|
|
<div>
|
|
{randomData.patncLogoPath && (
|
|
<img className={css.errorlogo} src={randomData.patncLogoPath} />
|
|
)}
|
|
<p className={css.errorText}>{$L('Click the screen to see more products!')}</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* 배너 메인 이미지 */}
|
|
{videoError === false && (
|
|
<div className={classNames(css.itemBox, isHorizontal && css.isHorizontal)}>
|
|
{randomData.tmnlImgPath ? (
|
|
<CustomImage
|
|
delay={0}
|
|
src={randomData.tmnlImgPath}
|
|
ariaLabel={randomData.tmnImgNm}
|
|
animationSpeed="fast"
|
|
/>
|
|
) : (
|
|
<CustomImage
|
|
delay={0}
|
|
src={randomData.vtctpYn === 'Y' ? emptyVerImage : emptyHorImage}
|
|
animationSpeed="fast"
|
|
ariaLabel={randomData.tmnImgNm}
|
|
/>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* 플레이 버튼 [>] */}
|
|
{videoError === false && (
|
|
<div className={css.btnPlay}>
|
|
{randomData.tmnlImgPath == null ? '' : <img src={btnPlay} />}
|
|
</div>
|
|
)}
|
|
|
|
{/* 배너 하단 로고 */}
|
|
{videoError === false && (
|
|
<p className={css.brandIcon}>
|
|
{randomData.showId && (
|
|
<CustomImage
|
|
delay={0}
|
|
src={randomData.showId ? randomData.patncLogoPath : null}
|
|
fallbackSrc={defaultLogoImg}
|
|
animationSpeed="fast"
|
|
ariaLabel={randomData.brdcChnlId}
|
|
/>
|
|
)}
|
|
</p>
|
|
)}
|
|
</SpottableComponent>
|
|
) : randomData?.shptmBanrTpNm == "Today's Deals" ? (
|
|
<SpottableComponent
|
|
className={classNames(
|
|
css.itemBox,
|
|
css.todaysDeals,
|
|
countryCode === 'RU' ? css.ru : '',
|
|
countryCode === 'DE' ? css.de : '',
|
|
isHorizontal && css.isHorizontal
|
|
)}
|
|
onClick={todayDealClick}
|
|
spotlightId={spotlightId}
|
|
aria-label={randomData.prdtNm ? randomData.prdtNm : randomData.tmnlImgNm}
|
|
>
|
|
<div className={css.productInfo}>
|
|
<div className={css.todaysDealTitle}>{$L("TODAY's DEALS")}</div>
|
|
<div
|
|
className={css.textBox}
|
|
dangerouslySetInnerHTML={{
|
|
__html: `${randomData.prdtNm}`,
|
|
}}
|
|
/>
|
|
<div className={css.accBox}>
|
|
{parseFloat(originalPrice?.replace('$', '')) === 0
|
|
? randomData?.offerInfo
|
|
: discountRate
|
|
? discountedPrice
|
|
: originalPrice}
|
|
{discountRate && !isHorizontal && (
|
|
<span className={css.saleAccBox}>{originalPrice}</span>
|
|
)}
|
|
</div>
|
|
{isHorizontal && parseFloat(originalPrice?.replace('$', '')) !== 0 && (
|
|
<span className={css.saleAccBox}>{originalPrice}</span>
|
|
)}
|
|
</div>
|
|
|
|
<div className={css.itemImgBox}>
|
|
<CustomImage
|
|
delay={0}
|
|
src={randomData.tmnlImgPath}
|
|
animationSpeed="fast"
|
|
fallbackSrc={defaultImageItem}
|
|
ariaLabel={randomData.tmnlImgNm}
|
|
/>
|
|
</div>
|
|
</SpottableComponent>
|
|
) : null}
|
|
</Container>
|
|
</>
|
|
);
|
|
}
|