Files
shoptime/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx
optrader 1b764b34d5 [251118] fix: HomeBanner 비디오 포커스 테두리
🕐 커밋 시간: 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

🔧 주요 변경 내용:
  • 소규모 기능 개선
2025-11-18 14:20:04 +09:00

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>
</>
);
}