fix: 선택약관 4차수정 250622
This commit is contained in:
@@ -1,28 +1,41 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
||||
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import platform from "@enact/core/platform";
|
||||
import { Job } from "@enact/core/util";
|
||||
import ThemeDecorator from "@enact/sandstone/ThemeDecorator";
|
||||
import React, {
|
||||
// useMemo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
// useState,
|
||||
} from "react";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
// import { I18nContext } from "@enact/i18n";
|
||||
// import classNames from "classnames";
|
||||
// import PropTypes from "prop-types";
|
||||
import Spotlight from "@enact/spotlight";
|
||||
import { Job } from "@enact/core/util";
|
||||
import platform from "@enact/core/platform";
|
||||
import { ThemeDecorator } from "@enact/sandstone/ThemeDecorator";
|
||||
|
||||
// import "../../../assets/fontello/css/fontello.css";
|
||||
|
||||
import appinfo from "../../webos-meta/appinfo.json";
|
||||
import {
|
||||
changeAppStatus,
|
||||
checkFirstLaunch,
|
||||
// cancelFocusElement,
|
||||
// focusElement,
|
||||
// setExitApp,
|
||||
// setPreventMouse,
|
||||
// setShowPopup,
|
||||
// setTermsAgreeYn,
|
||||
getTermsAgreeYn,
|
||||
deleteOldDb8Datas,
|
||||
getConnectionInfo,
|
||||
sendBroadCast,
|
||||
getConnectionStatus,
|
||||
getConnectionInfo,
|
||||
getDeviceId,
|
||||
getHttpHeaderForServiceRequest,
|
||||
getSystemSettings,
|
||||
sendBroadCast,
|
||||
checkFirstLaunch,
|
||||
setDeepLink,
|
||||
setGNBMenu,
|
||||
setSecondLayerInfo,
|
||||
setShowPopup,
|
||||
getTermsAgreeYn,
|
||||
} from "../actions/commonActions";
|
||||
import { getShoptimeTerms } from "../actions/empActions";
|
||||
import { getHomeMenu, getHomeTerms } from "../actions/homeActions";
|
||||
@@ -36,7 +49,14 @@ import usePrevious from "../hooks/usePrevious";
|
||||
import { lunaTest } from "../lunaSend/lunaTest";
|
||||
import { store } from "../store/store";
|
||||
import * as Config from "../utils/Config";
|
||||
import { $L, clearLaunchParams, getLaunchParams } from "../utils/helperMethods";
|
||||
import {
|
||||
// $L,
|
||||
clearLaunchParams,
|
||||
// getCountry,
|
||||
getLaunchParams,
|
||||
// getUUID,
|
||||
// resizeTo,
|
||||
} from "../utils/helperMethods";
|
||||
import { SpotlightIds } from "../utils/SpotlightIds";
|
||||
import ErrorBoundary from "../views/ErrorBoundary";
|
||||
import MainView from "../views/MainView/MainView";
|
||||
@@ -44,7 +64,11 @@ import css from "./App.module.less";
|
||||
import { handleBypassLink } from "./bypassLinkHandler";
|
||||
import { handleDeepLink } from "./deepLinkHandler";
|
||||
import { sendLogTotalRecommend } from "../actions/logActions";
|
||||
import { startFocusMonitoring, stopFocusMonitoring } from '../utils/focus-monitor';
|
||||
// import {
|
||||
// startFocusMonitoring,
|
||||
// stopFocusMonitoring,
|
||||
// } from "../utils/focus-monitor";
|
||||
// import { PanelHoc } from "../components/TPanel/TPanel";
|
||||
|
||||
let foreGroundChangeTimer = null;
|
||||
|
||||
@@ -80,7 +104,7 @@ Spotlight.focus = function (elem, containerOption) {
|
||||
if (!floatLayerNode.contains(current)) {
|
||||
if (floatLayerNode.lastElementChild) {
|
||||
const spottableNode = floatLayerNode.lastElementChild.querySelector(
|
||||
'[data-spotlight-container="true"]'
|
||||
'[data-spotlight-container="true"]',
|
||||
);
|
||||
if (spottableNode) {
|
||||
originFocus.apply(this, [spottableNode]); // this 바인딩을 유지하여 originFocus 호출
|
||||
@@ -98,7 +122,7 @@ Spotlight.focus = function (elem, containerOption) {
|
||||
sendBroadCast({
|
||||
type: "deActivateTab",
|
||||
moreInfo: { reason: "focus" },
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -111,15 +135,15 @@ function AppBase(props) {
|
||||
const httpHeader = useSelector((state) => state.common.httpHeader);
|
||||
const httpHeaderRef = useRef(httpHeader);
|
||||
const webOSVersion = useSelector(
|
||||
(state) => state.common.appStatus.webOSVersion
|
||||
(state) => state.common.appStatus.webOSVersion,
|
||||
);
|
||||
const deviceId = useSelector((state) => state.common.appStatus.deviceId);
|
||||
const loginUserData = useSelector(
|
||||
(state) => state.common.appStatus.loginUserData
|
||||
(state) => state.common.appStatus.loginUserData,
|
||||
);
|
||||
const loginUserDataRef = useRef(loginUserData);
|
||||
const cursorVisible = useSelector(
|
||||
(state) => state.common.appStatus.cursorVisible
|
||||
(state) => state.common.appStatus.cursorVisible,
|
||||
);
|
||||
const introTermsAgree = useSelector((state) => state.common.introTermsAgree);
|
||||
// const optionalTermsAgree = useSelector((state) => state.common.optionalTermsAgree);
|
||||
@@ -128,15 +152,6 @@ function AppBase(props) {
|
||||
// const termsFlag = useSelector((state) => state.common.termsFlag);
|
||||
const termsData = useSelector((state) => state.home.termsData);
|
||||
|
||||
// const shouldShowOptionalTermsPopup = useMemo(() => {
|
||||
// const terms = termsData?.data?.terms;
|
||||
// if (!terms) {
|
||||
// return false;
|
||||
// }
|
||||
// const optionalTerm = terms.find(term => term.trmsTpCd === "MST00405");
|
||||
// return optionalTerm ? optionalTerm.trmsPopFlag === 'Y' && optionalTerm.trmsAgrFlag === 'N' : false;
|
||||
// }, [termsData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (termsData?.data?.terms) {
|
||||
dispatch(getTermsAgreeYn());
|
||||
@@ -146,9 +161,9 @@ function AppBase(props) {
|
||||
const introTermsAgreeRef = usePrevious(introTermsAgree);
|
||||
const logEnable = useSelector((state) => state.localSettings.logEnable);
|
||||
const oldDb8Deleted = useSelector(
|
||||
(state) => state.localSettings.oldDb8Deleted
|
||||
(state) => state.localSettings.oldDb8Deleted,
|
||||
);
|
||||
const macAddress = useSelector((state) => state.common.macAddress);
|
||||
// const macAddress = useSelector((state) => state.common.macAddress);
|
||||
|
||||
const deviceCountryCode = httpHeader?.["X-Device-Country"] || "";
|
||||
|
||||
@@ -170,13 +185,13 @@ function AppBase(props) {
|
||||
if (!oldDb8Deleted) {
|
||||
dispatch(deleteOldDb8Datas());
|
||||
}
|
||||
}, [oldDb8Deleted]);
|
||||
}, [oldDb8Deleted, dispatch]);
|
||||
|
||||
const hideCursor = useRef(
|
||||
new Job((func) => {
|
||||
func();
|
||||
console.log("hide cursor");
|
||||
}, 5000)
|
||||
}, 5000),
|
||||
);
|
||||
|
||||
// 컴포넌트에서 모니터링 시작 - 한시적 모니터링
|
||||
@@ -199,11 +214,13 @@ function AppBase(props) {
|
||||
// called by [receive httpHeader, launch, relaunch]
|
||||
const initService = useCallback(
|
||||
(haveyInit = true) => {
|
||||
/*
|
||||
console.log(
|
||||
"<<<<<<<<<<<<< appinfo >>>>>>>>>>>>{heavyInit, appinfo} ",
|
||||
haveyInit,
|
||||
appinfo
|
||||
);
|
||||
*/
|
||||
if (httpHeaderRef.current) {
|
||||
if (haveyInit) {
|
||||
dispatch(changeAppStatus({ connectionFailed: false }));
|
||||
@@ -211,7 +228,7 @@ function AppBase(props) {
|
||||
dispatch(
|
||||
changeAppStatus({
|
||||
cursorVisible: window.PalmSystem?.cursor?.visibility,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
dispatch(getHomeMenu());
|
||||
@@ -224,7 +241,7 @@ function AppBase(props) {
|
||||
console.log(
|
||||
"initService...{haveyInit, launchParams}",
|
||||
haveyInit,
|
||||
JSON.stringify(launchParams)
|
||||
JSON.stringify(launchParams),
|
||||
);
|
||||
|
||||
// pyh TODO: edit or delete later (line 196 ~ 198)
|
||||
@@ -239,7 +256,7 @@ function AppBase(props) {
|
||||
contextName: Config.LOG_CONTEXT_NAME.ENTRY,
|
||||
messageId: Config.LOG_MESSAGE_ID.ENTRY_INFO,
|
||||
entry_menu: "App",
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -249,12 +266,12 @@ function AppBase(props) {
|
||||
contextName: Config.LOG_CONTEXT_NAME.SHOPTIME,
|
||||
messageId: Config.LOG_MESSAGE_ID.VIEW_CHANGE,
|
||||
visible: true,
|
||||
})
|
||||
}),
|
||||
);
|
||||
clearLaunchParams();
|
||||
}
|
||||
},
|
||||
[dispatch]
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
const handleRelaunchEvent = useCallback(() => {
|
||||
@@ -265,7 +282,7 @@ function AppBase(props) {
|
||||
if (typeof window === "object" && window.PalmSystem) {
|
||||
window.PalmSystem.activate();
|
||||
}
|
||||
}, [initService, dispatch]);
|
||||
}, [initService, introTermsAgreeRef]);
|
||||
|
||||
const visibilityChanged = useCallback(() => {
|
||||
console.log("document is hidden", document.hidden);
|
||||
@@ -279,7 +296,7 @@ function AppBase(props) {
|
||||
foreGroundChangeTimer = setTimeout(() => {
|
||||
console.log(
|
||||
"visibility changed !!! ==> set to foreground cursorVisible",
|
||||
JSON.stringify(window.PalmSystem?.cursor?.visibility)
|
||||
JSON.stringify(window.PalmSystem?.cursor?.visibility),
|
||||
); // eslint-disable-line no-console
|
||||
if (platform.platformName !== "webos") {
|
||||
//for debug
|
||||
@@ -287,19 +304,19 @@ function AppBase(props) {
|
||||
changeAppStatus({
|
||||
isAppForeground: true,
|
||||
cursorVisible: !platform.touchscreen,
|
||||
})
|
||||
}),
|
||||
);
|
||||
} else if (typeof window === "object") {
|
||||
dispatch(
|
||||
changeAppStatus({
|
||||
isAppForeground: true,
|
||||
cursorVisible: window.PalmSystem?.cursor?.visibility,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}, [dispatch, initService]);
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
const keyDownEvent = (event) => {
|
||||
@@ -342,7 +359,7 @@ function AppBase(props) {
|
||||
document.removeEventListener("wheel", mouseMoveEvent);
|
||||
}
|
||||
};
|
||||
}, [dispatch]);
|
||||
}, [dispatch, visibilityChanged, handleRelaunchEvent]);
|
||||
|
||||
useEffect(() => {
|
||||
let userDataChanged = false;
|
||||
@@ -358,7 +375,7 @@ function AppBase(props) {
|
||||
dispatch(
|
||||
changeAppStatus({
|
||||
showLoadingPanel: { show: true, type: "launching" },
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
dispatch(checkFirstLaunch());
|
||||
@@ -367,13 +384,13 @@ function AppBase(props) {
|
||||
getHomeTerms({
|
||||
mbrNo: loginUserData.userNumber,
|
||||
trmsTpCdList: "MST00401, MST00402, MST00405", // 선택약관 추가 25.06
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
httpHeaderRef.current = httpHeader;
|
||||
}
|
||||
loginUserDataRef.current = loginUserData;
|
||||
}, [httpHeader, deviceId]);
|
||||
}, [httpHeader, deviceId, dispatch, loginUserData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
@@ -385,8 +402,7 @@ function AppBase(props) {
|
||||
) {
|
||||
dispatch(getShoptimeTerms());
|
||||
}
|
||||
}, [webOSVersion, deviceId]);
|
||||
|
||||
}, [webOSVersion, deviceId, dispatch, deviceCountryCode]);
|
||||
|
||||
// 테스트용 인트로 화면 표시
|
||||
// useEffect(() => {
|
||||
@@ -404,29 +420,15 @@ function AppBase(props) {
|
||||
}
|
||||
|
||||
if (introTermsAgree) {
|
||||
// 필수 약관에 동의한 경우
|
||||
// if (shouldShowOptionalTermsPopup) {
|
||||
// 선택 약관 팝업을 띄워야 하는 경우
|
||||
// 3초 후에 팝업을 띄우도록 설정
|
||||
// console.log("App.js optionalTermsTest 팝업 표시");
|
||||
// const timer = setTimeout(() => {
|
||||
// dispatch(setShowPopup({ activePopup: "optionalTermsConfirm" }));
|
||||
// }, 3000); // 3000 milliseconds = 3 seconds
|
||||
|
||||
// 컴포넌트 언마운트 시 타이머 클리어
|
||||
// return () => clearTimeout(timer);
|
||||
// } else {
|
||||
// 선택 약관 팝업이 필요 없는 경우, 바로 서비스 초기화
|
||||
initService(true);
|
||||
// }
|
||||
} else {
|
||||
// 필수 약관에 동의하지 않은 경우
|
||||
dispatch(
|
||||
pushPanel({ name: Config.panel_names.INTRO_PANEL, panelInfo: {} })
|
||||
pushPanel({ name: Config.panel_names.INTRO_PANEL, panelInfo: {} }),
|
||||
);
|
||||
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
|
||||
}
|
||||
}, [introTermsAgree, dispatch, initService]);
|
||||
}, [introTermsAgree, dispatch, initService, termsLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
const launchParmas = getLaunchParams();
|
||||
@@ -442,7 +444,7 @@ function AppBase(props) {
|
||||
setDeepLink({
|
||||
contentTarget: launchParmas.contentTarget,
|
||||
isDeepLink: true,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -452,9 +454,9 @@ function AppBase(props) {
|
||||
deeplinkId: launchParmas.contentTarget ?? "",
|
||||
linkTpCd,
|
||||
logTpNo: Config.LOG_TP_NO.SECOND_LAYER,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}, [dispatch]);
|
||||
}, [dispatch, initService]);
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
|
||||
@@ -194,6 +194,17 @@ export const types = {
|
||||
GET_SUBTITLE: "GET_SUBTITLE",
|
||||
CLEAR_PLAYER_INFO: "CLEAR_PLAYER_INFO",
|
||||
|
||||
/**
|
||||
* 홈 화면 배너의 비디오 재생 제어를 위한 액션 타입.
|
||||
* 여러 컴포넌트가 동시에 비디오를 재생하려고 할 때 충돌을 방지하고,
|
||||
* 하나의 비디오만 재생되도록 보장하는 역할을 합니다.
|
||||
*
|
||||
* SET_PLAYER_CONTROL: 특정 컴포넌트에게 비디오 재생 제어권을 부여합니다.
|
||||
* CLEAR_PLAYER_CONTROL: 컴포넌트로부터 비디오 재생 제어권을 회수합니다.
|
||||
*/
|
||||
SET_PLAYER_CONTROL: "SET_PLAYER_CONTROL",
|
||||
CLEAR_PLAYER_CONTROL: "CLEAR_PLAYER_CONTROL",
|
||||
|
||||
// reset action
|
||||
RESET_REDUX_STATE: "RESET_REDUX_STATE",
|
||||
|
||||
|
||||
@@ -25,18 +25,21 @@ let startVideoTimer = null;
|
||||
//start Full -> modal mode
|
||||
let startVideoFocusTimer = null;
|
||||
export const startVideoPlayer =
|
||||
({ modal, modalContainerId, modalClassName, spotlightDisable, ...rest }) =>
|
||||
({ modal, modalContainerId, modalClassName, spotlightDisable, useNewPlayer, ...rest }) =>
|
||||
(dispatch, getState) => {
|
||||
const panels = getState().panels.panels;
|
||||
const topPanel = panels[panels.length - 1];
|
||||
let panelWorkingAction = pushPanel;
|
||||
if (topPanel && topPanel.name === panel_names.PLAYER_PANEL) {
|
||||
|
||||
const panelName = useNewPlayer ? panel_names.PLAYER_PANEL_NEW : panel_names.PLAYER_PANEL;
|
||||
|
||||
if (topPanel && topPanel.name === panelName) {
|
||||
panelWorkingAction = updatePanel;
|
||||
}
|
||||
dispatch(
|
||||
panelWorkingAction(
|
||||
{
|
||||
name: panel_names.PLAYER_PANEL,
|
||||
name: panelName,
|
||||
panelInfo: {
|
||||
modal,
|
||||
modalContainerId,
|
||||
@@ -116,3 +119,52 @@ export const getSubTitle =
|
||||
export const CLEAR_PLAYER_INFO = () => ({
|
||||
type: types.CLEAR_PLAYER_INFO,
|
||||
});
|
||||
|
||||
/**
|
||||
* 비디오 재생 제어권을 요청하는 액션.
|
||||
* 컴포넌트가 비디오를 재생하고 싶을 때 이 액션을 호출합니다.
|
||||
* 'playerControl' 상태를 확인하여 현재 다른 컴포넌트가 비디오를 제어하고 있지 않은 경우에만
|
||||
* 비디오 플레이어를 활성화(PlayerPanel을 modal로 push)합니다.
|
||||
*
|
||||
* @param {string} ownerId - 비디오 제어권을 요청하는 컴포넌트의 고유 ID.
|
||||
* @param {object} videoInfo - 'startVideoPlayer'에 필요한 비디오 정보.
|
||||
*/
|
||||
export const requestPlayControl =
|
||||
(ownerId, videoInfo) => (dispatch, getState) => {
|
||||
const { playerControl } = getState().home;
|
||||
const currentOwnerId = playerControl.ownerId;
|
||||
|
||||
// 이미 다른 컴포넌트가 제어권을 가지고 있다면, 먼저 해제한다. (선점)
|
||||
if (currentOwnerId && currentOwnerId !== ownerId) {
|
||||
dispatch(releasePlayControl(currentOwnerId, true)); // fromPreemption = true
|
||||
}
|
||||
|
||||
// 새로운 제어권을 설정하고 비디오를 재생한다.
|
||||
dispatch({
|
||||
type: types.SET_PLAYER_CONTROL,
|
||||
payload: { ownerId },
|
||||
});
|
||||
dispatch(startVideoPlayer({ ...videoInfo, modal: true }));
|
||||
};
|
||||
|
||||
/**
|
||||
* 비디오 재생 제어권을 해제하는 액션.
|
||||
* 컴포넌트가 비디오 재생을 중단할 때(예: 포커스 잃음, 언마운트) 호출합니다.
|
||||
* 현재 제어권을 가진 컴포넌트가 자신일 경우에만 'playerControl' 상태를 초기화하고
|
||||
* 비디오 플레이어를 종료(PlayerPanel을 pop)합니다.
|
||||
*
|
||||
* @param {string} ownerId - 비디오 제어권을 해제하려는 컴포넌트의 고유 ID.
|
||||
* @param {boolean} fromPreemption - 다른 요청에 의해 강제로 해제되었는지 여부.
|
||||
*/
|
||||
export const releasePlayControl = (ownerId, fromPreemption = false) => (dispatch, getState) => {
|
||||
const { playerControl } = getState().home;
|
||||
|
||||
// 제어권을 가진 컴포넌트가 자신일 경우에만 해제
|
||||
// 단, 선점 로직에 의해 호출된 경우는 소유권 확인 없이 즉시 실행
|
||||
if (fromPreemption || playerControl.ownerId === ownerId) {
|
||||
dispatch({
|
||||
type: types.CLEAR_PLAYER_CONTROL,
|
||||
});
|
||||
dispatch(finishVideoPreview());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
border-radius: 10px;
|
||||
box-sizing: border-box;
|
||||
.flex();
|
||||
box-shadow: 0 5px 5px #003 0 6px 7px #0000001a;
|
||||
box-shadow: 0 5px 5px #003, 0 6px 7px #0000001a;
|
||||
line-height: normal;
|
||||
|
||||
&:focus {
|
||||
@@ -165,7 +165,7 @@
|
||||
border-radius: 10px;
|
||||
box-sizing: border-box;
|
||||
.flex();
|
||||
box-shadow: 0 5px 5px #003 0 6px 7px #0000001a;
|
||||
box-shadow: 0 5px 5px #003, 0 6px 7px #0000001a;
|
||||
line-height: normal;
|
||||
|
||||
&:focus {
|
||||
|
||||
@@ -80,6 +80,7 @@ export default memo(function TItemCard({
|
||||
nowProductId,
|
||||
nowCategory,
|
||||
nowProductTitle,
|
||||
contentId,
|
||||
...rest
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@@ -286,6 +286,8 @@ export default function TNewPopUp({
|
||||
onOptionalAgreeClick, // Agree 버튼용
|
||||
onOptionalDeclineClick, // Not Now 버튼용
|
||||
onIntroTermsAgreeClick, // introTerms Agree 버튼용
|
||||
showAgreeButton = false,
|
||||
onAgreeClick, // onIntroTermsAgreeClick을 대체할 새로운 prop
|
||||
...rest
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
@@ -750,20 +752,21 @@ export default function TNewPopUp({
|
||||
</div>
|
||||
</div>
|
||||
<ButtonContainerComp className={getClassName(kind, "buttonContainer")}>
|
||||
{/* 기획 변경으로 Agree 버튼 임시 숨김 처리
|
||||
{showAgreeButton && (
|
||||
<TButton
|
||||
className={getClassName(kind, "agreeButton")}
|
||||
onClick={onIntroTermsAgreeClick}
|
||||
onClick={onAgreeClick}
|
||||
spotlightId="figma-terms-agree"
|
||||
type="agree"
|
||||
type="popup"
|
||||
>
|
||||
{$L('Agree')}
|
||||
</TButton>
|
||||
*/}
|
||||
)}
|
||||
<TButton
|
||||
className={getClassName(kind, "closeButton")}
|
||||
onClick={onClose}
|
||||
spotlightId="figma-terms-close"
|
||||
type="popup"
|
||||
>
|
||||
{$L('Close')}
|
||||
</TButton>
|
||||
|
||||
@@ -1082,22 +1082,18 @@
|
||||
.figmaTermsButtonContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
align-items: center; // 버튼 수직 정렬을 위해 추가
|
||||
gap: 15px; // 버튼 사이 간격
|
||||
}
|
||||
|
||||
.figmaTermsAgreeButton {
|
||||
.size(240px, 80px);
|
||||
|
||||
// type="agree"의 포커스 시 font-size 변경을 막음
|
||||
&.focused,
|
||||
&:focus { // :focus도 함께 처리
|
||||
font-size: 30px !important;
|
||||
}
|
||||
// 이제 TButton의 type="popup" 스타일을 사용하므로,
|
||||
// 여기서는 추가적인 스타일이 필요 없습니다.
|
||||
// margin-right는 gap으로 대체되었습니다.
|
||||
}
|
||||
|
||||
.figmaTermsCloseButton {
|
||||
// TButton의 기본 스타일을 그대로 사용하도록 크기만 지정
|
||||
.size(240px, 80px);
|
||||
// TButton의 type="popup" 스타일을 사용합니다.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
113
com.twin.app.shoptime/src/hooks/useRightPanelContent.js
Normal file
113
com.twin.app.shoptime/src/hooks/useRightPanelContent.js
Normal file
@@ -0,0 +1,113 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { $L } from "../utils/helperMethods";
|
||||
import OptionalTermsInfo from "../views/MyPagePanel/MyPageSub/TermsOfService/OptionalTermsInfo";
|
||||
import css from "../views/IntroPanel/IntroPanel.new.module.less";
|
||||
|
||||
const getPanelContent = (
|
||||
focusedItem,
|
||||
termsChecked,
|
||||
privacyChecked,
|
||||
shouldShowBenefitsView
|
||||
) => {
|
||||
const requiredItemIds = [
|
||||
"termsCheckbox",
|
||||
"termsButton",
|
||||
"privacyCheckbox",
|
||||
"privacyButton",
|
||||
];
|
||||
const isRequiredFocused = requiredItemIds.includes(focusedItem);
|
||||
|
||||
// 1순위: 포커스가 필수 약관 관련 아이템에 있는 경우
|
||||
if (isRequiredFocused) {
|
||||
// 필수 약관이 동의되지 않았을 때 (경고 메시지 포함)
|
||||
if (!termsChecked || !privacyChecked) {
|
||||
return (
|
||||
<div className={css.requiredInfoPanel}>
|
||||
<p className={css.infoText}>{$L("Required Consent")}</p>
|
||||
<p className={css.infoText}>
|
||||
{$L("(Necessary for using the service)")}
|
||||
</p>
|
||||
<div className={css.warningContainer}>
|
||||
<p className={css.warningText}>
|
||||
{$L("Please agree to the required Terms & Conditions and")}
|
||||
</p>
|
||||
<p className={css.warningText}>
|
||||
{$L("Privacy Policy to start enjoying Shop Time")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// 필수 약관이 모두 동의되었을 때 (단순 안내 메시지)
|
||||
return (
|
||||
<div className={css.requiredInfoPanel}>
|
||||
<p className={css.infoText}>{$L("Required Consent")}</p>
|
||||
<p className={css.infoText}>
|
||||
{$L("(Necessary for using the service)")}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 2순위: 포커스가 그 외 모든 아이템에 있는 경우
|
||||
return (
|
||||
<>
|
||||
{shouldShowBenefitsView ? (
|
||||
<div className={css.optionalDescription}>
|
||||
{$L(
|
||||
'By checking "Optional terms", you allow Shop Time to use your activity (views, purchases, searches, etc.) to show you more relevant content, product recommendations, special offers, and ads. If you do not check, you can still use all basic Shop Time features'
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<OptionalTermsInfo
|
||||
displayMode="image"
|
||||
imageTitle={$L("Agree and Enjoy Special Benefits")}
|
||||
spotlightId="optional-terms-info"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const useRightPanelContent = (
|
||||
focusedItem,
|
||||
termsChecked,
|
||||
privacyChecked,
|
||||
shouldShowBenefitsView
|
||||
) => {
|
||||
const [content, setContent] = useState(() =>
|
||||
getPanelContent(
|
||||
focusedItem,
|
||||
termsChecked,
|
||||
privacyChecked,
|
||||
shouldShowBenefitsView
|
||||
)
|
||||
);
|
||||
const updateTimeoutRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (updateTimeoutRef.current) {
|
||||
clearTimeout(updateTimeoutRef.current);
|
||||
}
|
||||
|
||||
updateTimeoutRef.current = setTimeout(() => {
|
||||
const newContent = getPanelContent(
|
||||
focusedItem,
|
||||
termsChecked,
|
||||
privacyChecked,
|
||||
shouldShowBenefitsView
|
||||
);
|
||||
setContent(newContent);
|
||||
}, 50);
|
||||
|
||||
return () => {
|
||||
if (updateTimeoutRef.current) {
|
||||
clearTimeout(updateTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, [focusedItem, termsChecked, privacyChecked, shouldShowBenefitsView]);
|
||||
|
||||
return content;
|
||||
};
|
||||
|
||||
export default useRightPanelContent;
|
||||
40
com.twin.app.shoptime/src/hooks/useSafeFocusState.js
Normal file
40
com.twin.app.shoptime/src/hooks/useSafeFocusState.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import { useState, useCallback, useRef } from "react";
|
||||
|
||||
const useSafeFocusState = () => {
|
||||
const [focusedItem, setFocusedItem] = useState(null);
|
||||
const blurTimeoutRef = useRef(null);
|
||||
|
||||
const setFocusAsync = useCallback((item) => {
|
||||
return new Promise((resolve) => {
|
||||
if (blurTimeoutRef.current) {
|
||||
clearTimeout(blurTimeoutRef.current);
|
||||
blurTimeoutRef.current = null;
|
||||
}
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.log("[useSafeFocusState] Focus Set:", item);
|
||||
}
|
||||
setFocusedItem(item);
|
||||
resolve(item);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const clearFocusAsync = useCallback((delay = 0) => {
|
||||
return new Promise((resolve) => {
|
||||
if (blurTimeoutRef.current) {
|
||||
clearTimeout(blurTimeoutRef.current);
|
||||
}
|
||||
blurTimeoutRef.current = setTimeout(() => {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.log("[useSafeFocusState] Focus Cleared");
|
||||
}
|
||||
setFocusedItem(null);
|
||||
blurTimeoutRef.current = null;
|
||||
resolve();
|
||||
}, delay);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return { focusedItem, setFocusAsync, clearFocusAsync };
|
||||
};
|
||||
|
||||
export default useSafeFocusState;
|
||||
95
com.twin.app.shoptime/src/hooks/useTermsStateMachine.js
Normal file
95
com.twin.app.shoptime/src/hooks/useTermsStateMachine.js
Normal file
@@ -0,0 +1,95 @@
|
||||
import { useState, useCallback } from "react";
|
||||
|
||||
const useTermsStateMachine = (initialState) => {
|
||||
const [isTransitioning, setIsTransitioning] = useState(false);
|
||||
const [state, setState] = useState({
|
||||
termsChecked: false,
|
||||
privacyChecked: false,
|
||||
optionalChecked: false,
|
||||
selectAllChecked: false,
|
||||
error: null, // 1. 에러 상태 추가
|
||||
...initialState,
|
||||
});
|
||||
|
||||
const updateStateAsync = useCallback(
|
||||
async (updates) => {
|
||||
if (isTransitioning) {
|
||||
const err = new Error("State transition already in progress.");
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.warn("[useTermsStateMachine] Rejected:", err.message);
|
||||
}
|
||||
// 2. 에러 상태를 설정하고 throw
|
||||
setState((s) => ({ ...s, error: err }));
|
||||
throw err;
|
||||
}
|
||||
|
||||
setIsTransitioning(true);
|
||||
// 3. 이전 에러 상태 초기화
|
||||
setState((s) => ({ ...s, error: null }));
|
||||
|
||||
try {
|
||||
const newState = await new Promise((resolve) => {
|
||||
setState((currentState) => {
|
||||
const nextState = { ...currentState, ...updates };
|
||||
|
||||
const {
|
||||
termsChecked: currentTerms,
|
||||
privacyChecked: currentPrivacy,
|
||||
optionalChecked: currentOptional,
|
||||
} = currentState;
|
||||
|
||||
const termsChecked =
|
||||
"termsChecked" in updates
|
||||
? updates.termsChecked
|
||||
: currentTerms;
|
||||
const privacyChecked =
|
||||
"privacyChecked" in updates
|
||||
? updates.privacyChecked
|
||||
: currentPrivacy;
|
||||
const optionalChecked =
|
||||
"optionalChecked" in updates
|
||||
? updates.optionalChecked
|
||||
: currentOptional;
|
||||
|
||||
if (
|
||||
"termsChecked" in updates ||
|
||||
"privacyChecked" in updates ||
|
||||
"optionalChecked" in updates
|
||||
) {
|
||||
nextState.selectAllChecked =
|
||||
termsChecked && privacyChecked && optionalChecked;
|
||||
} else if ("selectAllChecked" in updates) {
|
||||
nextState.termsChecked = updates.selectAllChecked;
|
||||
nextState.privacyChecked = updates.selectAllChecked;
|
||||
nextState.optionalChecked = updates.selectAllChecked;
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.log(
|
||||
"[useTermsStateMachine] State transition:",
|
||||
currentState,
|
||||
"->",
|
||||
nextState
|
||||
);
|
||||
}
|
||||
resolve(nextState);
|
||||
return nextState;
|
||||
});
|
||||
});
|
||||
setIsTransitioning(false);
|
||||
return newState;
|
||||
} catch (error) {
|
||||
setIsTransitioning(false);
|
||||
console.error("[useTermsStateMachine] State update failed:", error);
|
||||
// 4. 에러 상태를 설정하고 다시 throw
|
||||
setState((s) => ({ ...s, error }));
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
[isTransitioning]
|
||||
);
|
||||
|
||||
return { state, updateStateAsync };
|
||||
};
|
||||
|
||||
export default useTermsStateMachine;
|
||||
@@ -21,6 +21,9 @@ const initialState = {
|
||||
homeInfo: null,
|
||||
curationId: "",
|
||||
curationTitle: "",
|
||||
playerControl: {
|
||||
ownerId: null,
|
||||
},
|
||||
};
|
||||
|
||||
export const homeReducer = (state = initialState, action) => {
|
||||
@@ -189,6 +192,26 @@ export const homeReducer = (state = initialState, action) => {
|
||||
}
|
||||
}
|
||||
|
||||
case types.SET_PLAYER_CONTROL: {
|
||||
return {
|
||||
...state,
|
||||
playerControl: {
|
||||
...state.playerControl,
|
||||
ownerId: action.payload.ownerId,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case types.CLEAR_PLAYER_CONTROL: {
|
||||
return {
|
||||
...state,
|
||||
playerControl: {
|
||||
...state.playerControl,
|
||||
ownerId: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ export const panel_names = {
|
||||
CONFIRM_PANEL: "confirmpanel",
|
||||
DETAIL_PANEL: "detailpanel",
|
||||
PLAYER_PANEL: "playerpanel",
|
||||
PLAYER_PANEL_NEW: "playerpanel.new",
|
||||
CHECKOUT_PANEL: "checkoutpanel",
|
||||
THEME_CURATION_PANEL: "themeCurationPanel",
|
||||
IMAGE_PANEL: "imagepanel",
|
||||
|
||||
@@ -6,28 +6,38 @@ import { useDispatch, useSelector } from "react-redux";
|
||||
import Spotlight from "@enact/spotlight";
|
||||
import { SpotlightContainerDecorator } from "@enact/spotlight/SpotlightContainerDecorator";
|
||||
import Spottable from "@enact/spotlight/Spottable";
|
||||
import { $L, scaleH, scaleW } from '../../../utils/helperMethods';
|
||||
import { setDefaultFocus, setShowPopup, fetchCurrentUserHomeTerms } from "../../../actions/homeActions";
|
||||
import { $L, scaleH, scaleW } from "../../../utils/helperMethods";
|
||||
import {
|
||||
setDefaultFocus,
|
||||
setShowPopup,
|
||||
fetchCurrentUserHomeTerms,
|
||||
} from "../../../actions/homeActions";
|
||||
import { changeAppStatus } from "../../../actions/commonActions";
|
||||
import { setMyPageTermsAgree } from '../../../actions/myPageActions';
|
||||
import { setMyPageTermsAgree } from "../../../actions/myPageActions";
|
||||
import { pushPanel } from "../../../actions/panelActions";
|
||||
import {
|
||||
requestPlayControl,
|
||||
releasePlayControl,
|
||||
} from "../../../actions/playActions";
|
||||
import CustomImage from "../../../components/CustomImage/CustomImage";
|
||||
import css from "./HomeBanner.module.less";
|
||||
import Random from "./RandomUnit";
|
||||
import Rolling from "./RollingUnit";
|
||||
import RandomUnitNew from "./RandomUnit.new";
|
||||
import TNewPopUp from "../../../components/TPopUp/TNewPopUp";
|
||||
import TButtonScroller from "../../../components/TButtonScroller/TButtonScroller";
|
||||
// import TButtonScroller from "../../../components/TButtonScroller/TButtonScroller";
|
||||
import OptionalConfirm from "../../../components/Optional/OptionalConfirm";
|
||||
import * as Config from "../../../utils/Config";
|
||||
// import * as Config from "../../../utils/Config";
|
||||
import PersistentVideoUnit from "./PersistentVideoUnit";
|
||||
|
||||
const SpottableComponent = Spottable("div");
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{ enterTo: "last-focused" },
|
||||
"div"
|
||||
"div",
|
||||
);
|
||||
const ContainerBasic = SpotlightContainerDecorator(
|
||||
{ enterTo: "last-focused" },
|
||||
"div"
|
||||
"div",
|
||||
);
|
||||
|
||||
export default function HomeBanner({
|
||||
@@ -38,11 +48,11 @@ export default function HomeBanner({
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
const homeTopDisplayInfo = useSelector(
|
||||
(state) => state.home.homeTopDisplayInfo
|
||||
(state) => state.home.homeTopDisplayInfo,
|
||||
);
|
||||
|
||||
const bannerDataList = useSelector(
|
||||
(state) => state.home.bannerData?.bannerInfos
|
||||
(state) => state.home.bannerData?.bannerInfos,
|
||||
);
|
||||
|
||||
const popupVisible = useSelector((state) => state.common.popup.popupVisible);
|
||||
@@ -57,9 +67,39 @@ export default function HomeBanner({
|
||||
}
|
||||
}, [handleItemFocus]);
|
||||
|
||||
const handleSecondBannerFocus = useCallback(() => {
|
||||
const secondBannerData = bannerDataList?.[1];
|
||||
if (secondBannerData) {
|
||||
const randomData =
|
||||
secondBannerData.bannerDetailInfos[secondBannerData.randomIndex];
|
||||
const videoInfo = {
|
||||
showUrl: randomData.showUrl,
|
||||
patnrId: randomData.patnrId,
|
||||
showId: randomData.showId,
|
||||
shptmBanrTpNm: randomData.showId ? randomData.shptmBanrTpNm : "MEDIA",
|
||||
lgCatCd: randomData.lgCatCd,
|
||||
chanId: randomData.brdcChnlId,
|
||||
modal: true,
|
||||
modalContainerId: "banner1",
|
||||
modalClassName: css.videoModal,
|
||||
isVerticalModal: true, // Assuming second banner is horizontal, so modal is vertical
|
||||
};
|
||||
dispatch(requestPlayControl("banner1_preview", videoInfo));
|
||||
}
|
||||
if (handleItemFocus) {
|
||||
handleItemFocus();
|
||||
}
|
||||
}, [dispatch, bannerDataList, handleItemFocus]);
|
||||
|
||||
const handleSecondBannerBlur = useCallback(() => {
|
||||
dispatch(releasePlayControl("banner1_preview"));
|
||||
}, [dispatch]);
|
||||
|
||||
const termsData = useSelector((state) => state.home.termsData);
|
||||
const optionalTermsData = useSelector((state) =>
|
||||
state.home.termsData?.data?.terms.find(term => term.trmsTpCd === "MST00405")
|
||||
state.home.termsData?.data?.terms.find(
|
||||
(term) => term.trmsTpCd === "MST00405",
|
||||
),
|
||||
);
|
||||
const termsLoading = useSelector((state) => state.common.termsLoading);
|
||||
const isGnbOpened = useSelector((state) => state.common.isGnbOpened);
|
||||
@@ -68,7 +108,8 @@ export default function HomeBanner({
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// 팝업표시 상태
|
||||
const [isOptionalConfirmVisible, setIsOptionalConfirmVisible] = useState(false);
|
||||
const [isOptionalConfirmVisible, setIsOptionalConfirmVisible] =
|
||||
useState(false);
|
||||
const [isOptionalTermsVisible, setIsOptionalTermsVisible] = useState(false);
|
||||
|
||||
// 선택약관 팝업 표시 여부
|
||||
@@ -80,35 +121,42 @@ export default function HomeBanner({
|
||||
if (!terms) {
|
||||
return false;
|
||||
}
|
||||
const optionalTerm = terms.find(term => term.trmsTpCd === "MST00405");
|
||||
return optionalTerm ? optionalTerm.trmsPopFlag === 'Y' && optionalTerm.trmsAgrFlag === 'N' : false;
|
||||
const optionalTerm = terms.find((term) => term.trmsTpCd === "MST00405");
|
||||
return optionalTerm
|
||||
? optionalTerm.trmsPopFlag === "Y" && optionalTerm.trmsAgrFlag === "N"
|
||||
: false;
|
||||
}, [termsData, termsLoading, isGnbOpened]);
|
||||
|
||||
const handleOptionalAgree = useCallback(() => {
|
||||
console.log('handleAgree Click');
|
||||
console.log("handleAgree Click");
|
||||
|
||||
// 약관 동의할 항목들 (string array)
|
||||
const termsList = ["TID0000222", "TID0000223", "TID0000232"];
|
||||
// 동의하지 않을 항목들 (빈 배열)
|
||||
const notTermsList = [];
|
||||
console.log('OptionalTermsConfirm -약관 동의 API 호출 파라미터:', { termsList, notTermsList });
|
||||
console.log("OptionalTermsConfirm -약관 동의 API 호출 파라미터:", {
|
||||
termsList,
|
||||
notTermsList,
|
||||
});
|
||||
const callback = (response) => {
|
||||
if (response.retCode === "000" || response.retCode === 0) {
|
||||
console.log('약관 동의 성공:', response);
|
||||
console.log("약관 동의 성공:", response);
|
||||
// 약관 정보 갱신
|
||||
dispatch(fetchCurrentUserHomeTerms());
|
||||
} else {
|
||||
console.error('약관 동의 실패:', response);
|
||||
console.error("약관 동의 실패:", response);
|
||||
}
|
||||
};
|
||||
|
||||
console.log('OptionalTermsConfirm - 약관 동의 API 호출 payload:', { termsList, notTermsList });
|
||||
console.log("OptionalTermsConfirm - 약관 동의 API 호출 payload:", {
|
||||
termsList,
|
||||
notTermsList,
|
||||
});
|
||||
dispatch(setMyPageTermsAgree({ termsList, notTermsList }, callback));
|
||||
|
||||
}, [dispatch]);
|
||||
|
||||
const handleOptionalTermsClick = useCallback(() => {
|
||||
console.log('약관 자세히 보기 클릭');
|
||||
console.log("약관 자세히 보기 클릭");
|
||||
setIsOptionalConfirmVisible(false);
|
||||
setIsOptionalTermsVisible(true);
|
||||
// 약관 상세 팝업을 띄우는 로직 추가
|
||||
@@ -120,7 +168,7 @@ export default function HomeBanner({
|
||||
}, []);
|
||||
|
||||
const handleOptionalDeclineClick = useCallback(() => {
|
||||
console.log('거절/다음에 하기 버튼 클릭');
|
||||
console.log("거절/다음에 하기 버튼 클릭");
|
||||
setIsOptionalConfirmVisible(false);
|
||||
// 거절 처리 로직 추가
|
||||
}, []);
|
||||
@@ -139,7 +187,6 @@ export default function HomeBanner({
|
||||
setIsOptionalTermsVisible(false);
|
||||
}, []);
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
const _handleShelfFocus = useCallback(() => {
|
||||
if (handleShelfFocus) {
|
||||
@@ -164,7 +211,7 @@ export default function HomeBanner({
|
||||
}
|
||||
} else if (
|
||||
bannerDetailInfos.find(
|
||||
(el) => el.shptmBanrTpNm === "LIVE" || el.shptmBanrTpNm === "VOD"
|
||||
(el) => el.shptmBanrTpNm === "LIVE" || el.shptmBanrTpNm === "VOD",
|
||||
)
|
||||
) {
|
||||
targetIndex = i;
|
||||
@@ -222,6 +269,24 @@ export default function HomeBanner({
|
||||
const renderItem = useCallback(
|
||||
(index, isHorizontal) => {
|
||||
const data = bannerDataList?.[index] ?? {};
|
||||
|
||||
if (index === 1) {
|
||||
return (
|
||||
<div className={!isHorizontal ? css.imgBox : undefined}>
|
||||
<RandomUnitNew
|
||||
bannerData={data}
|
||||
isHorizontal={isHorizontal}
|
||||
key={"banner" + index}
|
||||
spotlightId={"banner" + index}
|
||||
handleShelfFocus={_handleShelfFocus}
|
||||
onFocus={handleSecondBannerFocus}
|
||||
onBlur={handleSecondBannerBlur}
|
||||
randomNumber={data.randomIndex}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={!isHorizontal ? css.imgBox : undefined}>
|
||||
{data.shptmDspyTpNm === "Rolling" ? (
|
||||
@@ -263,7 +328,60 @@ export default function HomeBanner({
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[_handleItemFocus, _handleShelfFocus, bannerDataList]
|
||||
[
|
||||
bannerDataList,
|
||||
_handleItemFocus,
|
||||
_handleShelfFocus,
|
||||
handleSecondBannerFocus,
|
||||
handleSecondBannerBlur,
|
||||
],
|
||||
);
|
||||
|
||||
const renderItemPersistentVideo = useCallback(
|
||||
(index, isHorizontal) => {
|
||||
const data = bannerDataList?.[index] ?? {};
|
||||
return (
|
||||
<div className={!isHorizontal ? css.imgBox : undefined}>
|
||||
{data.shptmDspyTpNm === "Rolling" ? (
|
||||
<Rolling
|
||||
bannerData={data}
|
||||
isHorizontal={isHorizontal}
|
||||
key={"banner" + index}
|
||||
spotlightId={"banner" + index}
|
||||
handleShelfFocus={_handleShelfFocus}
|
||||
handleItemFocus={_handleItemFocus}
|
||||
/>
|
||||
) : data.shptmDspyTpNm === "Random" ? (
|
||||
<PersistentVideoUnit
|
||||
bannerData={data}
|
||||
isHorizontal={isHorizontal}
|
||||
key={"banner" + index}
|
||||
spotlightId={"banner" + index}
|
||||
handleShelfFocus={_handleShelfFocus}
|
||||
handleItemFocus={_handleItemFocus}
|
||||
randomNumber={data.randomIndex}
|
||||
/>
|
||||
) : (
|
||||
<SpottableComponent spotlightId={"banner" + index}>
|
||||
<CustomImage
|
||||
delay={0}
|
||||
src={
|
||||
isHorizontal
|
||||
? homeTopDisplayInfo.wdthtpImgPath1
|
||||
: homeTopDisplayInfo.vtctpImgPath1
|
||||
}
|
||||
aria-label={
|
||||
isHorizontal
|
||||
? homeTopDisplayInfo.wdthtpImgNm1
|
||||
: homeTopDisplayInfo.vtctpImgNm1
|
||||
}
|
||||
/>
|
||||
</SpottableComponent>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[_handleItemFocus, _handleShelfFocus, bannerDataList, homeTopDisplayInfo],
|
||||
);
|
||||
|
||||
const renderLayout = useCallback(() => {
|
||||
@@ -272,6 +390,7 @@ export default function HomeBanner({
|
||||
return (
|
||||
<>
|
||||
<ContainerBasic className={css.smallBox}>
|
||||
{/* {renderItemPersistentVideo(0, true)} */}
|
||||
{renderItem(0, true)}
|
||||
{renderItem(1, true)}
|
||||
</ContainerBasic>
|
||||
@@ -306,7 +425,7 @@ export default function HomeBanner({
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}, [selectTemplate, renderItem]);
|
||||
}, [selectTemplate, renderItem, renderItemPersistentVideo]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -327,41 +446,23 @@ export default function HomeBanner({
|
||||
onOptionalDeclineClick={handleOptionalDeclineClick}
|
||||
customPosition={true}
|
||||
position={{
|
||||
position: 'absolute',
|
||||
top: '342px', // 가운데를 기준으로 한 좌표 (1080/2) - 198
|
||||
left: '0px',
|
||||
bottom: 'unset',
|
||||
transform: 'none',
|
||||
position: "absolute",
|
||||
top: "342px", // 가운데를 기준으로 한 좌표 (1080/2) - 198
|
||||
left: "0px",
|
||||
bottom: "unset",
|
||||
transform: "none",
|
||||
}}
|
||||
/>
|
||||
{/* 선택약관 자세히 보기 팝업 */}
|
||||
<TNewPopUp
|
||||
kind="introTermsPopup"
|
||||
kind="figmaTermsPopup"
|
||||
open={isOptionalTermsVisible}
|
||||
title={$L("Optional Terms")}
|
||||
text={optionalTermsData?.trmsCntt || ""}
|
||||
onClose={handleTermsPopupClosed}
|
||||
onClick={handleTermsPopupClosed}
|
||||
onIntroTermsAgreeClick={handleTermsPopupAgree}
|
||||
hasButton
|
||||
button1Text={$L("Close")}
|
||||
>
|
||||
{optionalTermsData && (
|
||||
<div className={css.termsViewerContent}>
|
||||
<div className={css.termsViewerTitle}>{$L("Optional Terms")}</div>
|
||||
<TButtonScroller
|
||||
boxHeight={scaleH(300)}
|
||||
width={scaleW(980)}
|
||||
className={css.termsDescription}
|
||||
>
|
||||
<div
|
||||
className={css.termsDesc}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: optionalTermsData.trmsCntt,
|
||||
}}
|
||||
onAgreeClick={handleTermsPopupAgree}
|
||||
showAgreeButton={true}
|
||||
/>
|
||||
</TButtonScroller>
|
||||
</div>
|
||||
)}
|
||||
</TNewPopUp>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import classNames from "classnames";
|
||||
import Spottable from "@enact/spotlight/Spottable";
|
||||
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
|
||||
|
||||
import { requestPlayControl, releasePlayControl, startVideoPlayer } from "../../../actions/playActions";
|
||||
import CustomImage from "../../../components/CustomImage/CustomImage";
|
||||
import liveShow from "../../../../assets/images/tag-liveshow.png";
|
||||
import emptyHorImage from "../../../../assets/images/img-home-banner-empty-hor.png";
|
||||
import emptyVerImage from "../../../../assets/images/img-home-banner-empty-ver.png";
|
||||
import btnPlay from "../../../../assets/images/btn/btn-play-thumb-nor.png";
|
||||
import defaultLogoImg from "../../../../assets/images/ic-tab-partners-default@3x.png";
|
||||
import css from "./RandomUnit.module.less";
|
||||
|
||||
const SpottableComponent = Spottable("div");
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{ enterTo: "last-focused" },
|
||||
"div"
|
||||
);
|
||||
|
||||
const PersistentVideoUnit = (props) => {
|
||||
const { bannerData, spotlightId, isHorizontal, randomNumber, handleItemFocus, handleShelfFocus } = props;
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const randomData = bannerData?.bannerDetailInfos?.[randomNumber];
|
||||
|
||||
const requestVideo = useCallback(() => {
|
||||
if (randomData) {
|
||||
const videoInfo = {
|
||||
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,
|
||||
};
|
||||
dispatch(requestPlayControl(spotlightId, videoInfo));
|
||||
}
|
||||
}, [dispatch, randomData, spotlightId, isHorizontal]);
|
||||
|
||||
useEffect(() => {
|
||||
requestVideo();
|
||||
|
||||
return () => {
|
||||
dispatch(releasePlayControl(spotlightId));
|
||||
};
|
||||
}, [dispatch, requestVideo, spotlightId]);
|
||||
|
||||
const handleFocus = useCallback(() => {
|
||||
requestVideo();
|
||||
if (handleItemFocus) {
|
||||
handleItemFocus();
|
||||
}
|
||||
}, [requestVideo, handleItemFocus]);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
if (randomData) {
|
||||
const videoInfo = {
|
||||
showUrl: randomData.showUrl,
|
||||
patnrId: randomData.patnrId,
|
||||
showId: randomData.showId,
|
||||
shptmBanrTpNm: randomData.showId ? randomData.shptmBanrTpNm : "MEDIA",
|
||||
lgCatCd: randomData.lgCatCd,
|
||||
chanId: randomData.brdcChnlId,
|
||||
modal: false,
|
||||
};
|
||||
dispatch(startVideoPlayer(videoInfo));
|
||||
}
|
||||
}, [dispatch, randomData]);
|
||||
|
||||
return (
|
||||
<Container
|
||||
className={classNames(
|
||||
css.rollingWrap,
|
||||
isHorizontal && css.isHorizontalWrap
|
||||
)}
|
||||
onFocus={handleShelfFocus}
|
||||
>
|
||||
<SpottableComponent
|
||||
className={classNames(
|
||||
css.itemBox,
|
||||
isHorizontal && css.isHorizontal
|
||||
)}
|
||||
onFocus={handleFocus}
|
||||
onClick={handleClick}
|
||||
spotlightId={spotlightId}
|
||||
aria-label={
|
||||
randomData?.shptmBanrTpNm === "LIVE"
|
||||
? "LIVE " + randomData?.showNm
|
||||
: randomData?.showNm
|
||||
}
|
||||
alt={"LIVE"}
|
||||
>
|
||||
{randomData?.shptmBanrTpNm === "LIVE" && (
|
||||
<p className={css.liveIcon}>
|
||||
<CustomImage
|
||||
delay={0}
|
||||
src={liveShow}
|
||||
animationSpeed="fast"
|
||||
ariaLabel="LIVE icon"
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={classNames(
|
||||
css.itemBox,
|
||||
isHorizontal && css.isHorizontal
|
||||
)}
|
||||
>
|
||||
{randomData?.tmnlImgPath ? (
|
||||
<CustomImage
|
||||
delay={0}
|
||||
src={randomData.tmnlImgPath}
|
||||
ariaLabel={randomData.tmnlImgNm}
|
||||
animationSpeed="fast"
|
||||
/>
|
||||
) : (
|
||||
<CustomImage
|
||||
delay={0}
|
||||
src={
|
||||
randomData?.vtctpYn === "Y" ? emptyVerImage : emptyHorImage
|
||||
}
|
||||
animationSpeed="fast"
|
||||
ariaLabel={randomData?.tmnlImgNm}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={css.btnPlay}>
|
||||
{randomData?.tmnlImgPath == null ? "" : <img src={btnPlay} alt="play" />}
|
||||
</div>
|
||||
|
||||
<p className={css.brandIcon}>
|
||||
{randomData?.showId && (
|
||||
<CustomImage
|
||||
delay={0}
|
||||
src={randomData.showId ? randomData.patncLogoPath : null}
|
||||
fallbackSrc={defaultLogoImg}
|
||||
animationSpeed="fast"
|
||||
ariaLabel={randomData.brdcChnlId}
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
</SpottableComponent>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default PersistentVideoUnit;
|
||||
@@ -0,0 +1,641 @@
|
||||
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 } from "../../../actions/homeActions";
|
||||
import {
|
||||
sendLogTopContents,
|
||||
sendLogTotalRecommend,
|
||||
} from "../../../actions/logActions";
|
||||
import { pushPanel } from "../../../actions/panelActions";
|
||||
import {
|
||||
finishVideoPreview,
|
||||
startVideoPlayer,
|
||||
} 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 { $L, formatGMTString } from "../../../utils/helperMethods";
|
||||
import { TEMPLATE_CODE_CONF } from "../HomePanel";
|
||||
import css from "./RandomUnit.new.module.less";
|
||||
|
||||
const SpottableComponent = Spottable("div");
|
||||
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{ enterTo: "last-focused" },
|
||||
"div"
|
||||
);
|
||||
|
||||
export default function RandomUnitNew({
|
||||
bannerData,
|
||||
spotlightId,
|
||||
isHorizontal,
|
||||
handleShelfFocus,
|
||||
randomNumber,
|
||||
onFocus,
|
||||
onBlur,
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const 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);
|
||||
|
||||
const broadcast = useSelector((state) => state.common.broadcast);
|
||||
const { curationId, curationTitle } = useSelector((state) => state.home);
|
||||
const [randomData, setRandomData] = useState("");
|
||||
const [priceInfos, setpriceInfos] = useState("");
|
||||
const [videoError, setVideoError] = useState(false);
|
||||
const [liveIndicies, setLiveIndicies] = useState([]);
|
||||
|
||||
const bannerDataRef = useRef(bannerData);
|
||||
const randomDataRef = useRef(bannerDetailInfos[randomNumber]);
|
||||
|
||||
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(() => {
|
||||
const data = randomDataRef.current;
|
||||
|
||||
if (data && nowMenu === LOG_MENU.HOME_TOP) {
|
||||
dispatch(
|
||||
sendLogTotalRecommend({
|
||||
contextName: LOG_CONTEXT_NAME.HOME,
|
||||
messageId: LOG_MESSAGE_ID.BANNER,
|
||||
curationId,
|
||||
curationTitle,
|
||||
contentType: data.shptmBanrTpNm,
|
||||
contentId: data.showId,
|
||||
contentTitle: data.showNm,
|
||||
productId: data.prdtId,
|
||||
productTitle: data.prdtNm,
|
||||
displayType: "rolling",
|
||||
partner: data.patncNm,
|
||||
brand: data.brndNm, // <- 'brnad' 확인
|
||||
location: data.dspyOrdr,
|
||||
bannerType: data.vtctpYn === "Y" ? "Vertical" : "Horizontal",
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [randomDataRef, nowMenu]);
|
||||
|
||||
useEffect(() => {
|
||||
if (bannerDetailInfos && randomNumber) {
|
||||
const indices = bannerDetailInfos
|
||||
.map((info, index) => (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 shelfFocus = useCallback(() => {
|
||||
if (handleShelfFocus) {
|
||||
handleShelfFocus();
|
||||
}
|
||||
}, [handleShelfFocus]);
|
||||
|
||||
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;
|
||||
|
||||
dispatch(action(linkInfo));
|
||||
sendBannerLog();
|
||||
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,
|
||||
]);
|
||||
|
||||
const todayDealClick = useCallback(() => {
|
||||
dispatch(
|
||||
pushPanel({
|
||||
name: panel_names.DETAIL_PANEL,
|
||||
panelInfo: {
|
||||
patnrId: randomData.patnrId,
|
||||
prdtId: randomData.prdtId,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
sendBannerLog();
|
||||
|
||||
dispatch(
|
||||
sendLogTopContents({
|
||||
...topContentsLogInfo,
|
||||
inDt: formatGMTString(new Date()) ?? "",
|
||||
logTpNo: LOG_TP_NO.TOP_CONTENTS.CLICK,
|
||||
})
|
||||
);
|
||||
}, [
|
||||
dispatch,
|
||||
randomData?.patnrId,
|
||||
randomData?.prdtId,
|
||||
randomDataRef,
|
||||
topContentsLogInfo,
|
||||
]);
|
||||
|
||||
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"),
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
dispatch(
|
||||
startVideoPlayer({
|
||||
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,
|
||||
})
|
||||
);
|
||||
|
||||
sendBannerLog();
|
||||
|
||||
dispatch(
|
||||
sendLogTopContents({
|
||||
...topContentsLogInfo,
|
||||
inDt: formatGMTString(new Date()) ?? "",
|
||||
logTpNo: LOG_TP_NO.TOP_CONTENTS.CLICK,
|
||||
})
|
||||
);
|
||||
|
||||
if (onBlur) {
|
||||
onBlur();
|
||||
}
|
||||
}, [randomData, spotlightId, topContentsLogInfo, nowMenu, randomDataRef, onBlur]);
|
||||
|
||||
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();
|
||||
}, [randomDataRef, nowMenu]);
|
||||
|
||||
useEffect(() => {
|
||||
if (bannerData) {
|
||||
setRandomData(bannerDetailInfos[randomNumber]);
|
||||
}
|
||||
}, [bannerData, dispatch, randomNumber]);
|
||||
|
||||
useEffect(() => {
|
||||
if (randomData && randomData.priceInfo !== null) {
|
||||
return setpriceInfos(randomData.priceInfo);
|
||||
}
|
||||
}, [randomData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (broadcast?.type === "videoError") {
|
||||
setVideoError(true);
|
||||
if (liveIndicies.length > 0) {
|
||||
const nextIndex = liveIndicies[0];
|
||||
|
||||
setLiveIndicies((prev) => prev.slice(1));
|
||||
setRandomData(bannerDetailInfos[nextIndex]);
|
||||
|
||||
setTimeout(() => {
|
||||
setVideoError(false);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
}, [broadcast, liveIndicies, bannerDetailInfos]);
|
||||
|
||||
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
|
||||
)}
|
||||
onClick={videoError === true ? videoErrorClick : videoClick}
|
||||
onFocus={onFocus}
|
||||
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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
@import "../../../style/CommonStyle.module.less";
|
||||
@import "../../../style/utils.module.less";
|
||||
|
||||
.rollingWrap {
|
||||
position: relative;
|
||||
.itemBox {
|
||||
.size(@w: 486px, @h: 858px);
|
||||
position: relative;
|
||||
text-align: center;
|
||||
.brandIcon {
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
bottom: 30px;
|
||||
> img {
|
||||
border-radius: 0;
|
||||
.size(@w: 60px, @h: 60px);
|
||||
}
|
||||
}
|
||||
.liveIcon {
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
left: 18px;
|
||||
top: 18px;
|
||||
> img {
|
||||
.size(@w: 108px, @h: 48px);
|
||||
}
|
||||
}
|
||||
.imgBanner {
|
||||
> img {
|
||||
border-radius: 10px;
|
||||
.size(@w: 486px, @h: 858px);
|
||||
}
|
||||
}
|
||||
.btnPlay {
|
||||
.size(@w: 100%, @h: 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
> img {
|
||||
.size(@w: 120px, @h: 120px);
|
||||
}
|
||||
}
|
||||
&.isHorizontal {
|
||||
.size(@w: 744px, @h: 420px);
|
||||
.imgBanner {
|
||||
> img {
|
||||
border-radius: 10px;
|
||||
.size(@w: 744px, @h: 420px);
|
||||
}
|
||||
}
|
||||
}
|
||||
.errorContents {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.errorlogo {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.errorText {
|
||||
margin-top: 30px;
|
||||
color: #ff0000;
|
||||
padding-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&.todaysDeals {
|
||||
background-image: url(../../../../assets/images/img-home-banner-td-ver@3x.png);
|
||||
background-size: 486px 858px;
|
||||
background-position: left top;
|
||||
border-radius: 10px;
|
||||
padding: 73px 36px 0;
|
||||
&.ru {
|
||||
.productInfo {
|
||||
.todaysDealTitle {
|
||||
font-size: 58px;
|
||||
line-height: 60px;
|
||||
font-family: @arialFontBold;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.de {
|
||||
.productInfo {
|
||||
.todaysDealTitle {
|
||||
font-size: 59px !important;
|
||||
line-height: 63px !important;
|
||||
letter-spacing: -1px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.productInfo {
|
||||
margin-bottom: 33px;
|
||||
.todaysDealTitle {
|
||||
.size(@w:100%,@h:132px);
|
||||
font-size: 76px;
|
||||
word-break: break-word;
|
||||
font-stretch: normal;
|
||||
color: #151515;
|
||||
text-align: center;
|
||||
line-height: 76px;
|
||||
font-family: @arialFontBold;
|
||||
}
|
||||
.textBox {
|
||||
.size(@w: 100%, @h: 80px);
|
||||
margin-top: 71px;
|
||||
.elip(@clamp:2);
|
||||
font-weight: bold;
|
||||
font-size: 30px;
|
||||
color: @COLOR_GRAY06;
|
||||
line-height: 1.27;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.accBox {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 42px;
|
||||
color: @PRIMARY_COLOR_RED;
|
||||
line-height: 1.14;
|
||||
display: inline-block;
|
||||
.elip(@clamp:1);
|
||||
> strong {
|
||||
width: 260px;
|
||||
font-size: 30px;
|
||||
line-height: 1.27;
|
||||
display: block;
|
||||
.elip(@clamp:2);
|
||||
}
|
||||
.saleAccBox {
|
||||
font-weight: normal;
|
||||
font-size: 24px;
|
||||
color: @COLOR_GRAY04;
|
||||
vertical-align: middle;
|
||||
text-decoration: line-through;
|
||||
margin-left: 9px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.itemImgBox {
|
||||
> img {
|
||||
.size(@w: 356px, @h: 356px);
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
&.isHorizontal {
|
||||
background-image: url(../../../../assets/images/img-home-banner-td-hor@3x.png);
|
||||
background-size: 744px 420px;
|
||||
background-position: center center;
|
||||
display: flex;
|
||||
padding: 0 30px 0 0;
|
||||
border-radius: 10px;
|
||||
-webkit-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
-o-border-radius: 10px;
|
||||
> div {
|
||||
flex: none;
|
||||
}
|
||||
&.ru {
|
||||
.productInfo {
|
||||
.todaysDealTitle {
|
||||
font-size: 58px;
|
||||
line-height: 60px;
|
||||
font-family: @arialFontBold;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.de {
|
||||
.productInfo {
|
||||
.todaysDealTitle {
|
||||
font-size: 59px !important;
|
||||
line-height: 63px !important;
|
||||
letter-spacing: -2px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.productInfo {
|
||||
margin-bottom: 0;
|
||||
.todaysDealTitle {
|
||||
.size(@w:305px,@h:114px);
|
||||
margin-top: 53px;
|
||||
margin-left: 49px;
|
||||
font-size: 66px;
|
||||
word-break: break-word;
|
||||
color: #151515;
|
||||
text-align: left;
|
||||
line-height: 57px;
|
||||
font-family: @arialBlack;
|
||||
}
|
||||
.textBox {
|
||||
.size(@w: 294px, @h: 80px);
|
||||
margin: 67px 0 5px 50px;
|
||||
text-align: left;
|
||||
}
|
||||
.accBox {
|
||||
.size(@w: 320px, @h: 50px);
|
||||
margin-left: 50px;
|
||||
text-align: left;
|
||||
display: block;
|
||||
.elip(@clamp:1);
|
||||
}
|
||||
.saleAccBox {
|
||||
color: #767676;
|
||||
display: block;
|
||||
text-align: left;
|
||||
margin: 5px 0 0 55px;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
.itemImgBox {
|
||||
.position(@position: absolute, @top: 47px, @left: 389px);
|
||||
.size(@w: 326px, @h: 326px);
|
||||
> img {
|
||||
.size(@w: inherit, @h: inherit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&:focus {
|
||||
&::after {
|
||||
.focused(@boxShadow:22px, @borderRadius: 12px);
|
||||
border: 6px solid @PRIMARY_COLOR_RED;
|
||||
top: -4px;
|
||||
right: -4px;
|
||||
bottom: -4px;
|
||||
left: -4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.arrow {
|
||||
z-index: 9999;
|
||||
.size(@w: 42px, @h: 42px);
|
||||
background-size: 42px 42px;
|
||||
background-position: center center;
|
||||
&.leftBtn {
|
||||
.position(@position: absolute, @top: 406px, @left: 18px);
|
||||
background-image: url("../../../../assets/images/btn/btn_prev_thumb_nor.png");
|
||||
&:focus {
|
||||
background-image: url("../../../../assets/images/btn/btn_prev_thumb_foc.png");
|
||||
}
|
||||
}
|
||||
&.rightBtn {
|
||||
.position(@position: absolute, @top: 406px, @right: 18px);
|
||||
background-image: url("../../../../assets/images/btn/btn_next_thumb_nor.png");
|
||||
&:focus {
|
||||
background-image: url("../../../../assets/images/btn/btn_next_thumb_foc.png");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.isHorizontalWrap {
|
||||
border-radius: 10px;
|
||||
.arrow {
|
||||
&.leftBtn {
|
||||
.position(@position: absolute, @top: 189px, @left: 18px);
|
||||
}
|
||||
&.rightBtn {
|
||||
.position(@position: absolute, @top: 189px, @right: 18px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.videoModal {
|
||||
&::after {
|
||||
.focused(@boxShadow:0, @borderRadius: 12px);
|
||||
border: 6px solid @PRIMARY_COLOR_RED;
|
||||
top: -4px;
|
||||
right: -4px;
|
||||
bottom: -4px;
|
||||
left: -4px;
|
||||
}
|
||||
}
|
||||
@@ -75,43 +75,43 @@ const HomePanel = ({ isOnTop }) => {
|
||||
const isGnbOpened = useSelector((state) => state.common.isGnbOpened);
|
||||
const homeLayoutInfo = useSelector((state) => state.home.layoutData);
|
||||
const panelInfo = useSelector(
|
||||
(state) => state.home.homeInfo?.panelInfo ?? {}
|
||||
(state) => state.home.homeInfo?.panelInfo ?? {},
|
||||
);
|
||||
const panels = useSelector((state) => state.panels.panels);
|
||||
const webOSVersion = useSelector(
|
||||
(state) => state.common.appStatus?.webOSVersion
|
||||
(state) => state.common.appStatus?.webOSVersion,
|
||||
);
|
||||
const enterThroughGNB = useSelector((state) => state.home.enterThroughGNB);
|
||||
const defaultFocus = useSelector((state) => state.home.defaultFocus);
|
||||
|
||||
const categoryInfos = useSelector(
|
||||
(state) => state.onSale.homeOnSaleData?.data?.categoryInfos
|
||||
(state) => state.onSale.homeOnSaleData?.data?.categoryInfos,
|
||||
);
|
||||
|
||||
const categoryItemInfos = useSelector(
|
||||
(state) => state.main.subCategoryData?.categoryItemInfos
|
||||
(state) => state.main.subCategoryData?.categoryItemInfos,
|
||||
);
|
||||
|
||||
const { popupVisible, activePopup } = useSelector(
|
||||
(state) => state.common.popup
|
||||
(state) => state.common.popup,
|
||||
);
|
||||
|
||||
const eventPopInfosData = useSelector(
|
||||
(state) => state.event.eventData.eventPopInfo
|
||||
(state) => state.event.eventData.eventPopInfo,
|
||||
);
|
||||
const eventData = useSelector((state) => state.event.eventData);
|
||||
const eventClickSuccess = useSelector(
|
||||
(state) => state.event.eventClickSuccess
|
||||
(state) => state.event.eventClickSuccess,
|
||||
);
|
||||
const homeOnSaleInfos = useSelector(
|
||||
(state) => state.onSale.homeOnSaleData?.data.homeOnSaleInfos
|
||||
(state) => state.onSale.homeOnSaleData?.data.homeOnSaleInfos,
|
||||
);
|
||||
const bestSellerDatas = useSelector(
|
||||
(state) => state.product.bestSellerData?.bestSeller
|
||||
(state) => state.product.bestSellerData?.bestSeller,
|
||||
);
|
||||
const topInfos = useSelector((state) => state.main.top20ShowData.topInfos);
|
||||
const isDeepLink = useSelector(
|
||||
(state) => state.common.deepLinkInfo.isDeepLink
|
||||
(state) => state.common.deepLinkInfo.isDeepLink,
|
||||
);
|
||||
|
||||
const [btnDisabled, setBtnDisabled] = useState(true);
|
||||
@@ -120,13 +120,13 @@ const HomePanel = ({ isOnTop }) => {
|
||||
const [eventPopOpen, setEventPopOpen] = useState(false);
|
||||
const [nowShelf, setNowShelf] = useState(panelInfo.nowShelf);
|
||||
const [firstLgCatCd, setFirstLgCatCd] = useState(
|
||||
panelInfo.currentCatCd ?? null
|
||||
panelInfo.currentCatCd ?? null,
|
||||
);
|
||||
const [cateCd, setCateCd] = useState(panelInfo.currentCatCd ?? null);
|
||||
const [cateNm, setCateNm] = useState(panelInfo.currentCateName ?? null);
|
||||
const { entryMenu, nowMenu } = useSelector((state) => state.common.menu);
|
||||
const [focusedContainerId, setFocusedContainerId] = useState(
|
||||
panelInfo.focusedContainerId
|
||||
panelInfo.focusedContainerId,
|
||||
);
|
||||
|
||||
const isInitialRender = useRef(true);
|
||||
@@ -139,7 +139,7 @@ const HomePanel = ({ isOnTop }) => {
|
||||
sendLogTotalRecommend({
|
||||
messageId: LOG_MESSAGE_ID.HOME,
|
||||
contextName: LOG_CONTEXT_NAME.HOME,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}, [entryMenu, nowMenu]);
|
||||
@@ -147,7 +147,7 @@ const HomePanel = ({ isOnTop }) => {
|
||||
const sortedHomeLayoutInfo = useMemo(() => {
|
||||
if (homeLayoutInfo && homeLayoutInfo.homeLayoutInfo) {
|
||||
const sorted = [...homeLayoutInfo.homeLayoutInfo].sort(
|
||||
(x, y) => x.expsOrd - y.expsOrd
|
||||
(x, y) => x.expsOrd - y.expsOrd,
|
||||
);
|
||||
return sorted;
|
||||
}
|
||||
@@ -183,7 +183,7 @@ const HomePanel = ({ isOnTop }) => {
|
||||
panelInfo: {
|
||||
currentSpot: currentSpot,
|
||||
},
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
dispatch(setShowPopup(ACTIVE_POPUP.exitPopup));
|
||||
@@ -197,7 +197,7 @@ const HomePanel = ({ isOnTop }) => {
|
||||
contextName: LOG_CONTEXT_NAME.SHOPTIME,
|
||||
messageId: LOG_MESSAGE_ID.VIEW_CHANGE,
|
||||
visible: false,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}, [dispatch]);
|
||||
|
||||
@@ -261,7 +261,7 @@ const HomePanel = ({ isOnTop }) => {
|
||||
shelfLocation: location,
|
||||
shelfId: containerId,
|
||||
shelfTitle: title,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
setNowShelf(containerId);
|
||||
@@ -271,14 +271,14 @@ const HomePanel = ({ isOnTop }) => {
|
||||
currentSentMenuRef.current = nowMenu;
|
||||
}
|
||||
},
|
||||
[pageSpotIds, nowShelf, panelInfo.nowShelf]
|
||||
[pageSpotIds, nowShelf, panelInfo.nowShelf],
|
||||
);
|
||||
|
||||
const handleItemFocus = useCallback(
|
||||
(containerId, location, title) => () => {
|
||||
doSendLogGNB(containerId, location, title);
|
||||
},
|
||||
[doSendLogGNB]
|
||||
[doSendLogGNB],
|
||||
);
|
||||
|
||||
const renderPageItem = useCallback(() => {
|
||||
@@ -298,7 +298,7 @@ const HomePanel = ({ isOnTop }) => {
|
||||
handleShelfFocus={handleItemFocus(
|
||||
el.shptmApphmDspyOptCd,
|
||||
el.expsOrd,
|
||||
el.shptmApphmDspyOptNm
|
||||
el.shptmApphmDspyOptNm,
|
||||
)}
|
||||
handleItemFocus={handleItemFocus(el.shptmApphmDspyOptCd)}
|
||||
/>
|
||||
@@ -320,7 +320,7 @@ const HomePanel = ({ isOnTop }) => {
|
||||
handleShelfFocus={handleItemFocus(
|
||||
el.shptmApphmDspyOptCd,
|
||||
el.expsOrd,
|
||||
el.shptmApphmDspyOptNm
|
||||
el.shptmApphmDspyOptNm,
|
||||
)}
|
||||
handleItemFocus={handleItemFocus(el.shptmApphmDspyOptCd)}
|
||||
shelfLocation={el.expsOrd}
|
||||
@@ -338,7 +338,7 @@ const HomePanel = ({ isOnTop }) => {
|
||||
handleShelfFocus={handleItemFocus(
|
||||
el.shptmApphmDspyOptCd,
|
||||
el.expsOrd,
|
||||
el.shptmApphmDspyOptNm
|
||||
el.shptmApphmDspyOptNm,
|
||||
)}
|
||||
handleItemFocus={handleItemFocus(el.shptmApphmDspyOptCd)}
|
||||
shelfLocation={el.expsOrd}
|
||||
@@ -356,7 +356,7 @@ const HomePanel = ({ isOnTop }) => {
|
||||
handleShelfFocus={handleItemFocus(
|
||||
el.shptmApphmDspyOptCd,
|
||||
el.expsOrd,
|
||||
el.shptmApphmDspyOptNm
|
||||
el.shptmApphmDspyOptNm,
|
||||
)}
|
||||
handleItemFocus={handleItemFocus(el.shptmApphmDspyOptCd)}
|
||||
shelfLocation={el.expsOrd}
|
||||
@@ -374,7 +374,7 @@ const HomePanel = ({ isOnTop }) => {
|
||||
handleShelfFocus={handleItemFocus(
|
||||
el.shptmApphmDspyOptCd,
|
||||
el.expsOrd,
|
||||
el.shptmApphmDspyOptNm
|
||||
el.shptmApphmDspyOptNm,
|
||||
)}
|
||||
handleItemFocus={handleItemFocus(el.shptmApphmDspyOptCd)}
|
||||
shelfLocation={el.expsOrd}
|
||||
@@ -473,7 +473,7 @@ const HomePanel = ({ isOnTop }) => {
|
||||
cbChangePageRef,
|
||||
dispatch,
|
||||
isOnTop,
|
||||
]
|
||||
],
|
||||
);
|
||||
|
||||
const bestSellerLoaded = useCallback(() => {
|
||||
@@ -486,7 +486,7 @@ const HomePanel = ({ isOnTop }) => {
|
||||
|
||||
if (isDeepLink || (!panels.length && !panelInfo.focusedContainerId)) {
|
||||
dispatch(
|
||||
changeAppStatus({ showLoadingPanel: { show: true, type: "wait" } })
|
||||
changeAppStatus({ showLoadingPanel: { show: true, type: "wait" } }),
|
||||
);
|
||||
dispatch(getHomeMainContents());
|
||||
dispatch(getHomeLayout());
|
||||
@@ -495,7 +495,7 @@ const HomePanel = ({ isOnTop }) => {
|
||||
homeSaleInfosIncFlag: "Y",
|
||||
categoryIncFlag: "Y",
|
||||
saleInfosIncFlag: "N",
|
||||
})
|
||||
}),
|
||||
);
|
||||
dispatch(getTop20Show());
|
||||
dispatch(getBestSeller(bestSellerLoaded));
|
||||
@@ -528,8 +528,8 @@ const HomePanel = ({ isOnTop }) => {
|
||||
tabType: "CAT00102",
|
||||
filterType: "CAT00202",
|
||||
},
|
||||
1
|
||||
)
|
||||
1,
|
||||
),
|
||||
);
|
||||
}
|
||||
}, [categoryInfos, firstLgCatCd, dispatch]);
|
||||
@@ -544,7 +544,7 @@ const HomePanel = ({ isOnTop }) => {
|
||||
} else setEventPopOpen(true);
|
||||
} else setEventPopOpen(false);
|
||||
},
|
||||
[webOSVersion]
|
||||
[webOSVersion],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -583,7 +583,7 @@ const HomePanel = ({ isOnTop }) => {
|
||||
}
|
||||
|
||||
const tBody = document.querySelector(
|
||||
`[data-spotlight-id="${SpotlightIds.HOME_TBODY}"]`
|
||||
`[data-spotlight-id="${SpotlightIds.HOME_TBODY}"]`,
|
||||
);
|
||||
const currentSpot = c && tBody.contains(c) ? targetSpotlightId : null;
|
||||
|
||||
@@ -597,7 +597,7 @@ const HomePanel = ({ isOnTop }) => {
|
||||
currentCateName: targetSpotlightCateNm,
|
||||
focusedContainerId: focusedContainerIdRef.current,
|
||||
},
|
||||
})
|
||||
}),
|
||||
);
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
// src: views/IntroPanel/IntroPanel.new.jsx
|
||||
|
||||
import React, { useCallback, useEffect, useState, useMemo, useRef } from "react";
|
||||
import { flushSync } from "react-dom";
|
||||
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import Region from "@enact/sandstone/Region";
|
||||
import Spotlight from "@enact/spotlight";
|
||||
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
|
||||
|
||||
import {
|
||||
setExitApp,
|
||||
setHidePopup,
|
||||
@@ -17,7 +19,7 @@ import {
|
||||
} from "../../actions/commonActions";
|
||||
import { registerDevice } from "../../actions/deviceActions";
|
||||
import { getWelcomeEventInfo } from "../../actions/eventActions";
|
||||
import { fetchCurrentUserHomeTerms } from "../../actions/homeActions";
|
||||
// import { fetchCurrentUserHomeTermsPromise } from "../../actions/homeActions";
|
||||
import {
|
||||
sendLogGNB,
|
||||
sendLogTerms,
|
||||
@@ -29,27 +31,29 @@ import TButton, { TYPES } from "../../components/TButton/TButton";
|
||||
// import TButtonTab from "../../components/TButtonTab/TButtonTab";
|
||||
import TCheckBoxSquare from "../../components/TCheckBox/TCheckBoxSquare";
|
||||
import TPanel from "../../components/TPanel/TPanel";
|
||||
import TPopUp, { CONTENT_TYPES } from "../../components/TPopUp/TPopUp";
|
||||
import TPopUp from "../../components/TPopUp/TPopUp";
|
||||
import TNewPopUp from "../../components/TPopUp/TNewPopUp";
|
||||
import OptionalTermsInfo from "../MyPagePanel/MyPageSub/TermsOfService/OptionalTermsInfo";
|
||||
// import OptionalTermsInfo from "../MyPagePanel/MyPageSub/TermsOfService/OptionalTermsInfo";
|
||||
import useDebugKey from "../../hooks/useDebugKey";
|
||||
import useSafeFocusState from "../../hooks/useSafeFocusState";
|
||||
import useTermsStateMachine from "../../hooks/useTermsStateMachine";
|
||||
import useRightPanelContent from "../../hooks/useRightPanelContent";
|
||||
import * as Config from "../../utils/Config";
|
||||
import { panel_names } from "../../utils/Config";
|
||||
import { $L, scaleH, scaleW } from "../../utils/helperMethods";
|
||||
import { $L } from "../../utils/helperMethods";
|
||||
import css from "./IntroPanel.new.module.less";
|
||||
import { types } from "../../actions/actionTypes";
|
||||
import { focusById } from "../../utils/spotlight-utils";
|
||||
|
||||
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{ enterTo: "last-focused" },
|
||||
"div"
|
||||
"div",
|
||||
);
|
||||
|
||||
export default function IntroPanel({
|
||||
children,
|
||||
isTabActivated,
|
||||
handleCancel,
|
||||
// children,
|
||||
// isTabActivated,
|
||||
// handleCancel,
|
||||
spotlightId,
|
||||
...rest
|
||||
}) {
|
||||
@@ -58,36 +62,50 @@ export default function IntroPanel({
|
||||
|
||||
useDebugKey({});
|
||||
const dispatch = useDispatch();
|
||||
const blurTimeout = useRef(null);
|
||||
// const blurTimeout = useRef(null);
|
||||
const termsData = useSelector((state) => state.home.termsData);
|
||||
const { popupVisible, activePopup, ...popupState } = useSelector(
|
||||
(state) => state.common.popup
|
||||
(state) => state.common.popup,
|
||||
);
|
||||
const eventInfos = useSelector((state) => state.event.eventData);
|
||||
// const eventInfos = useSelector((state) => state.event.eventData);
|
||||
const regDeviceData = useSelector((state) => state.device.regDeviceData);
|
||||
const regDeviceInfoData = useSelector(
|
||||
(state) => state.device.regDeviceInfoData
|
||||
);
|
||||
// const regDeviceInfoData = useSelector(
|
||||
// (state) => state.device.regDeviceInfoData
|
||||
// );
|
||||
|
||||
// registerDevice API 호출 중 여부
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
const [showExitMessagePopup, setShowExitMessagePopup] = useState(false);
|
||||
const [isRequiredFocused, setIsRequiredFocused] = useState(false);
|
||||
// const [isRequiredFocused, setIsRequiredFocused] = useState(false);
|
||||
|
||||
const { focusedItem, setFocusAsync, clearFocusAsync } = useSafeFocusState();
|
||||
const { state: termsState, updateStateAsync } = useTermsStateMachine();
|
||||
const {
|
||||
termsChecked,
|
||||
privacyChecked,
|
||||
optionalChecked,
|
||||
selectAllChecked,
|
||||
error: termsError, // 훅의 에러 상태를 가져옴
|
||||
} = termsState;
|
||||
|
||||
const introTermsData = useMemo(() => {
|
||||
return termsData?.data?.terms.filter(
|
||||
(item) => item.trmsTpCd === "MST00401" || item.trmsTpCd === "MST00402"
|
||||
) || [];
|
||||
return (
|
||||
termsData?.data?.terms.filter(
|
||||
(item) => item.trmsTpCd === "MST00401" || item.trmsTpCd === "MST00402",
|
||||
) || []
|
||||
);
|
||||
}, [termsData]);
|
||||
|
||||
const optionalTermsData = useMemo(() => {
|
||||
return termsData?.data?.terms.filter(
|
||||
(item) => item.trmsTpCd === "MST00405"
|
||||
) || [];
|
||||
return (
|
||||
termsData?.data?.terms.filter(
|
||||
(item) => item.trmsTpCd === "MST00405" || item.trmsTpCd === "MST00406",
|
||||
) || []
|
||||
);
|
||||
}, [termsData]);
|
||||
|
||||
const webOSVersion = useSelector(
|
||||
(state) => state.common.appStatus?.webOSVersion
|
||||
(state) => state.common.appStatus?.webOSVersion,
|
||||
);
|
||||
// WebOS 버전별 UI 표시 모드 결정
|
||||
// 이미지 표시: 4.0, 5.0, 23, 24
|
||||
@@ -98,30 +116,27 @@ export default function IntroPanel({
|
||||
const version = String(webOSVersion);
|
||||
|
||||
// 텍스트 표시 버전들
|
||||
const textVersions = ['4.5', '6.0', '22'];
|
||||
const textVersions = ["4.5", "6.0", "22"];
|
||||
|
||||
// 이미지 표시 버전들
|
||||
const imageVersions = ['4.0', '5.0', '23', '24'];
|
||||
const imageVersions = ["4.0", "5.0", "23", "24"];
|
||||
|
||||
// 텍스트 버전인지 확인
|
||||
const shouldShowText = textVersions.includes(version);
|
||||
|
||||
console.log('🔍 WebOS 버전별 UI 모드:');
|
||||
console.log(' - webOSVersion:', version);
|
||||
console.log(' - shouldShowText (텍스트 모드):', shouldShowText);
|
||||
console.log(' - 텍스트 버전들:', textVersions);
|
||||
console.log(' - 이미지 버전들:', imageVersions);
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.log("🔍 WebOS 버전별 UI 모드:");
|
||||
console.log(" - webOSVersion:", version);
|
||||
console.log(" - shouldShowText (텍스트 모드):", shouldShowText);
|
||||
console.log(" - 텍스트 버전들:", textVersions);
|
||||
console.log(" - 이미지 버전들:", imageVersions);
|
||||
}
|
||||
|
||||
return shouldShowText;
|
||||
}, [webOSVersion]);
|
||||
|
||||
// 상태 관리
|
||||
const [currentTerms, setCurrentTerms] = useState(null);
|
||||
const [termsChecked, setTermsChecked] = useState(false); // Terms & Conditions 기본 체크
|
||||
const [privacyChecked, setPrivacyChecked] = useState(false); // Privacy Policy 기본 체크
|
||||
const [optionalChecked, setOptionalChecked] = useState(false); // Optional Terms 기본 체크 안됨
|
||||
const [selectAllChecked, setSelectAllChecked] = useState(false);
|
||||
const [focusedItem, setFocusedItem] = useState(null); // 포커스 추적 상태 추가
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({ type: types.REGISTER_DEVICE_RESET });
|
||||
@@ -139,9 +154,11 @@ export default function IntroPanel({
|
||||
|
||||
// 디버깅용 WebOS 버전 로그
|
||||
useEffect(() => {
|
||||
console.log('🔍 IntroPanel WebOS 버전 정보:');
|
||||
console.log(' - webOSVersion:', webOSVersion);
|
||||
console.log(' - shouldShowBenefitsView:', shouldShowBenefitsView);
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.log("🔍 IntroPanel WebOS 버전 정보:");
|
||||
console.log(" - webOSVersion:", webOSVersion);
|
||||
console.log(" - shouldShowBenefitsView:", shouldShowBenefitsView);
|
||||
}
|
||||
}, [webOSVersion, shouldShowBenefitsView]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -153,22 +170,13 @@ export default function IntroPanel({
|
||||
contextName: Config.LOG_CONTEXT_NAME.SHOPTIME,
|
||||
messageId: Config.LOG_MESSAGE_ID.VIEW_CHANGE,
|
||||
visible: false,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}, 3000);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [showExitMessagePopup, dispatch]);
|
||||
|
||||
// Select All 상태 업데이트
|
||||
useEffect(() => {
|
||||
const allChecked = termsChecked && privacyChecked && optionalChecked;
|
||||
setSelectAllChecked(allChecked);
|
||||
if (allChecked) {
|
||||
Spotlight.focus("agreeButton");
|
||||
}
|
||||
}, [termsChecked, privacyChecked, optionalChecked]);
|
||||
|
||||
// 컴포넌트 마운트 후 0.5초 뒤에 selectAllCheckbox으로 강제 포커스
|
||||
useEffect(() => {
|
||||
const focusTimer = setTimeout(() => {
|
||||
@@ -180,6 +188,17 @@ export default function IntroPanel({
|
||||
};
|
||||
}, []);
|
||||
|
||||
// [추가] useTermsStateMachine의 에러 상태를 감지하여 팝업으로 표시
|
||||
useEffect(() => {
|
||||
if (termsError) {
|
||||
dispatch(setShowPopup(Config.ACTIVE_POPUP.alertPopup, {
|
||||
title: $L("Error"),
|
||||
text: termsError.message,
|
||||
button1Text: $L("OK")
|
||||
}));
|
||||
}
|
||||
}, [termsError, dispatch]);
|
||||
|
||||
// 약관 팝업 동의여부에 따른 이벤트 핸들러
|
||||
const handleTermsAgree = useCallback(() => {
|
||||
if (!currentTerms) {
|
||||
@@ -187,23 +206,22 @@ export default function IntroPanel({
|
||||
}
|
||||
const termType = currentTerms.trmsTpCd;
|
||||
if (termType === "MST00402") {
|
||||
setTermsChecked(true);
|
||||
updateStateAsync({ termsChecked: true });
|
||||
} else if (termType === "MST00401") {
|
||||
setPrivacyChecked(true);
|
||||
updateStateAsync({ privacyChecked: true });
|
||||
} else if (termType === "MST00405") {
|
||||
// Optional Terms
|
||||
setOptionalChecked(true);
|
||||
updateStateAsync({ optionalChecked: true });
|
||||
}
|
||||
// 팝업 닫기
|
||||
dispatch(setHidePopup());
|
||||
}, [currentTerms, dispatch]);
|
||||
|
||||
}, [currentTerms, dispatch, updateStateAsync]);
|
||||
|
||||
const handleTermsClick = useCallback(
|
||||
(trmsTpCdList) => {
|
||||
if (introTermsData) {
|
||||
const selectedTerms = introTermsData.find(
|
||||
(term) => term.trmsTpCd === trmsTpCdList
|
||||
(term) => term.trmsTpCd === trmsTpCdList,
|
||||
);
|
||||
|
||||
setCurrentTerms(selectedTerms);
|
||||
@@ -216,14 +234,14 @@ export default function IntroPanel({
|
||||
dispatch(sendLogTerms({ logTpNo }));
|
||||
}
|
||||
},
|
||||
[introTermsData, dispatch]
|
||||
[introTermsData, dispatch],
|
||||
);
|
||||
|
||||
const handleOptionalTermsClick = useCallback(
|
||||
(trmsTpCdList) => {
|
||||
if (optionalTermsData) {
|
||||
const selectedTerms = optionalTermsData.find(
|
||||
(term) => term.trmsTpCd === trmsTpCdList
|
||||
(term) => term.trmsTpCd === trmsTpCdList,
|
||||
);
|
||||
|
||||
setCurrentTerms(selectedTerms);
|
||||
@@ -236,10 +254,9 @@ export default function IntroPanel({
|
||||
// dispatch(sendLogTerms({ logTpNo }));
|
||||
}
|
||||
},
|
||||
[optionalTermsData, dispatch]
|
||||
[optionalTermsData, dispatch],
|
||||
);
|
||||
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
dispatch(setHidePopup());
|
||||
}, [dispatch]);
|
||||
@@ -276,13 +293,15 @@ export default function IntroPanel({
|
||||
agreeTerms.push("TID0000232"); // MST00405 -> TID0000232 (선택약관)
|
||||
}
|
||||
|
||||
console.log('최종 전송될 agreeTerms:', agreeTerms);
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.log("최종 전송될 agreeTerms:", agreeTerms);
|
||||
}
|
||||
|
||||
dispatch(
|
||||
registerDevice(
|
||||
{ agreeTerms: agreeTerms },
|
||||
(regDeviceData) => {
|
||||
if (regDeviceData && regDeviceData.retCode === 0) {
|
||||
(newRegDeviceData) => {
|
||||
if (newRegDeviceData && newRegDeviceData.retCode === 0) {
|
||||
dispatch(
|
||||
getWelcomeEventInfo((eventInfos) => {
|
||||
if (
|
||||
@@ -301,7 +320,7 @@ export default function IntroPanel({
|
||||
}
|
||||
|
||||
dispatch(
|
||||
sendLogTerms({ logTpNo: Config.LOG_TP_NO.TERMS.AGREE })
|
||||
sendLogTerms({ logTpNo: Config.LOG_TP_NO.TERMS.AGREE }),
|
||||
);
|
||||
|
||||
if (displayWelcomeEventPanel) {
|
||||
@@ -309,13 +328,13 @@ export default function IntroPanel({
|
||||
pushPanel({
|
||||
name: panel_names.WELCOME_EVENT_PANEL,
|
||||
panelInfo: { eventInfos: eventInfos.data },
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
dispatch(popPanel(panel_names.INTRO_PANEL));
|
||||
setIsProcessing(false);
|
||||
})
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
dispatch(
|
||||
@@ -323,7 +342,7 @@ export default function IntroPanel({
|
||||
title: $L("Error"),
|
||||
text: $L("Device registration failed. Please try again."),
|
||||
button1Text: $L("OK"),
|
||||
})
|
||||
}),
|
||||
);
|
||||
setIsProcessing(false);
|
||||
}
|
||||
@@ -334,11 +353,11 @@ export default function IntroPanel({
|
||||
title: $L("Error"),
|
||||
text: $L("Device registration failed. Please try again."),
|
||||
button1Text: $L("OK"),
|
||||
})
|
||||
}),
|
||||
);
|
||||
setIsProcessing(false);
|
||||
}
|
||||
)
|
||||
},
|
||||
),
|
||||
);
|
||||
}, [
|
||||
termsChecked,
|
||||
@@ -353,16 +372,18 @@ export default function IntroPanel({
|
||||
useEffect(() => {
|
||||
// isProcessing이 true일 때만 실패 체크 (= handleAgree 클릭 후에만)
|
||||
if (isProcessing && regDeviceData && regDeviceData.retCode !== 0) {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.error(
|
||||
`[IntroPanel] registerDevice 실패: isProcessing=${isProcessing}, retCode=${regDeviceData.retCode}`,
|
||||
regDeviceData
|
||||
regDeviceData,
|
||||
);
|
||||
}
|
||||
dispatch(
|
||||
setShowPopup(Config.ACTIVE_POPUP.alertPopup, {
|
||||
title: $L("Error"),
|
||||
text: $L("Device registration failed. Please try again."),
|
||||
button1Text: $L("OK"),
|
||||
})
|
||||
}),
|
||||
);
|
||||
setIsProcessing(false);
|
||||
}
|
||||
@@ -382,7 +403,7 @@ export default function IntroPanel({
|
||||
contextName: Config.LOG_CONTEXT_NAME.SHOPTIME,
|
||||
messageId: Config.LOG_MESSAGE_ID.VIEW_CHANGE,
|
||||
visible: false,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}, [dispatch]);
|
||||
|
||||
@@ -392,101 +413,138 @@ export default function IntroPanel({
|
||||
}
|
||||
}, [dispatch, activePopup]);
|
||||
|
||||
const handleFocus = useCallback((item) => {
|
||||
if (blurTimeout.current) {
|
||||
clearTimeout(blurTimeout.current);
|
||||
blurTimeout.current = null;
|
||||
}
|
||||
setFocusedItem(item);
|
||||
}, []);
|
||||
const handleFocus = useCallback(
|
||||
(item) => {
|
||||
setFocusAsync(item);
|
||||
},
|
||||
[setFocusAsync]
|
||||
);
|
||||
|
||||
const handleBlur = useCallback(() => {
|
||||
blurTimeout.current = setTimeout(() => {
|
||||
setFocusedItem(null);
|
||||
}, 0);
|
||||
}, []);
|
||||
clearFocusAsync(0);
|
||||
}, [clearFocusAsync]);
|
||||
|
||||
// 체크박스 핸들러들
|
||||
const handleTermsToggle = useCallback(({ selected }) => {
|
||||
setTermsChecked(selected);
|
||||
}, []);
|
||||
|
||||
const handlePrivacyToggle = useCallback(({ selected }) => {
|
||||
setPrivacyChecked(selected);
|
||||
}, []);
|
||||
|
||||
const handleOptionalToggle = useCallback(({ selected }) => {
|
||||
setOptionalChecked(selected);
|
||||
}, []);
|
||||
|
||||
const handleSelectAllToggle = useCallback(({ selected }) => {
|
||||
setSelectAllChecked(selected);
|
||||
setTermsChecked(selected);
|
||||
setPrivacyChecked(selected);
|
||||
setOptionalChecked(selected);
|
||||
}, []);
|
||||
|
||||
|
||||
const rightPanelContent = useMemo(() => {
|
||||
const requiredItemIds = [
|
||||
"termsCheckbox",
|
||||
"termsButton",
|
||||
"privacyCheckbox",
|
||||
"privacyButton",
|
||||
];
|
||||
if (!requiredItemIds.includes(focusedItem)) {
|
||||
return (
|
||||
<>
|
||||
{shouldShowBenefitsView ? (
|
||||
<div className={css.optionalDescription}>
|
||||
{$L(
|
||||
'By checking "Optional terms", you allow Shop Time to use your activity (views, purchases, searches, etc.) to show you more relevant content, product recommendations, special offers, and ads. If you do not check, you can still use all basic Shop Time features'
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<OptionalTermsInfo
|
||||
displayMode="image"
|
||||
imageTitle={$L("Agree and Enjoy Special Benefits")}
|
||||
spotlightId="optional-terms-info"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
const handleTermsToggle = useCallback(
|
||||
async ({ selected }) => {
|
||||
try {
|
||||
const newState = await updateStateAsync({ termsChecked: selected });
|
||||
if (newState.termsChecked && newState.privacyChecked && newState.optionalChecked) {
|
||||
setTimeout(() => Spotlight.focus("agreeButton"), 100);
|
||||
}
|
||||
|
||||
const hasRequiredUnchecked = !termsChecked || !privacyChecked;
|
||||
|
||||
// 우선순위 1: 필수 약관이 체크 안됨 → 항상 경고 메시지
|
||||
if (hasRequiredUnchecked) {
|
||||
return (
|
||||
<div className={css.requiredInfoPanel}>
|
||||
<p className={css.infoText}>{$L("Required Consent")}</p>
|
||||
<p className={css.infoText}>
|
||||
{$L("(Necessary for using the service)")}
|
||||
</p>
|
||||
<div className={css.warningContainer}>
|
||||
<p className={css.warningText}>
|
||||
{$L("Please agree to the required Terms & Conditions and")}
|
||||
</p>
|
||||
<p className={css.warningText}>
|
||||
{$L("Privacy Policy to start enjoying Shop Time")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} catch (error) {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.error("Toggle failed:", error);
|
||||
}
|
||||
|
||||
// 우선순위 2: 필수 약관에 포커스 있음 → 필수 약관 안내
|
||||
return (
|
||||
<div className={css.requiredInfoPanel}>
|
||||
<p className={css.infoText}>{$L("Required Consent")}</p>
|
||||
<p className={css.infoText}>
|
||||
{$L("(Necessary for using the service)")}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
},
|
||||
[updateStateAsync]
|
||||
);
|
||||
}, [termsChecked, privacyChecked, focusedItem, shouldShowBenefitsView]);
|
||||
|
||||
const handlePrivacyToggle = useCallback(
|
||||
async ({ selected }) => {
|
||||
try {
|
||||
const newState = await updateStateAsync({ privacyChecked: selected });
|
||||
if (newState.termsChecked && newState.privacyChecked && newState.optionalChecked) {
|
||||
setTimeout(() => Spotlight.focus("agreeButton"), 100);
|
||||
}
|
||||
} catch (error) {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.error("Toggle failed:", error);
|
||||
}
|
||||
}
|
||||
},
|
||||
[updateStateAsync]
|
||||
);
|
||||
|
||||
const handleOptionalToggle = useCallback(
|
||||
async ({ selected }) => {
|
||||
try {
|
||||
const newState = await updateStateAsync({ optionalChecked: selected });
|
||||
if (newState.termsChecked && newState.privacyChecked && newState.optionalChecked) {
|
||||
setTimeout(() => Spotlight.focus("agreeButton"), 100);
|
||||
}
|
||||
} catch (error) {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.error("Toggle failed:", error);
|
||||
}
|
||||
}
|
||||
},
|
||||
[updateStateAsync]
|
||||
);
|
||||
|
||||
const handleSelectAllToggle = useCallback(
|
||||
async ({ selected }) => {
|
||||
try {
|
||||
const newState = await updateStateAsync({ selectAllChecked: selected });
|
||||
if (newState.selectAllChecked) {
|
||||
setTimeout(() => Spotlight.focus("agreeButton"), 100);
|
||||
}
|
||||
} catch (error) {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.error("Toggle failed:", error);
|
||||
}
|
||||
}
|
||||
},
|
||||
[updateStateAsync]
|
||||
);
|
||||
|
||||
const handleTermsClickMST00402 = useCallback(
|
||||
() => handleTermsClick("MST00402"),
|
||||
[handleTermsClick],
|
||||
);
|
||||
const handleTermsClickMST00401 = useCallback(
|
||||
() => handleTermsClick("MST00401"),
|
||||
[handleTermsClick],
|
||||
);
|
||||
const handleOptionalTermsClickMST00405 = useCallback(
|
||||
() => handleOptionalTermsClick("MST00405"),
|
||||
[handleOptionalTermsClick],
|
||||
);
|
||||
|
||||
const handleFocusTermsCheckbox = useCallback(
|
||||
() => handleFocus("termsCheckbox"),
|
||||
[handleFocus],
|
||||
);
|
||||
const handleFocusTermsButton = useCallback(
|
||||
() => handleFocus("termsButton"),
|
||||
[handleFocus],
|
||||
);
|
||||
const handleFocusPrivacyCheckbox = useCallback(
|
||||
() => handleFocus("privacyCheckbox"),
|
||||
[handleFocus],
|
||||
);
|
||||
const handleFocusPrivacyButton = useCallback(
|
||||
() => handleFocus("privacyButton"),
|
||||
[handleFocus],
|
||||
);
|
||||
const handleFocusOptionalCheckbox = useCallback(
|
||||
() => handleFocus("optionalCheckbox"),
|
||||
[handleFocus],
|
||||
);
|
||||
const handleFocusOptionalButton = useCallback(
|
||||
() => handleFocus("optionalButton"),
|
||||
[handleFocus],
|
||||
);
|
||||
const handleFocusSelectAllCheckbox = useCallback(
|
||||
() => handleFocus("selectAllCheckbox"),
|
||||
[handleFocus],
|
||||
);
|
||||
const handleFocusAgreeButton = useCallback(
|
||||
() => handleFocus("agreeButton"),
|
||||
[handleFocus],
|
||||
);
|
||||
const handleFocusDisagreeButton = useCallback(
|
||||
() => handleFocus("disagreeButton"),
|
||||
[handleFocus],
|
||||
);
|
||||
|
||||
const rightPanelContent = useRightPanelContent(
|
||||
focusedItem,
|
||||
termsChecked,
|
||||
privacyChecked,
|
||||
shouldShowBenefitsView
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
Spotlight.focus();
|
||||
@@ -500,19 +558,23 @@ export default function IntroPanel({
|
||||
// }, [dispatch, regDeviceInfoData]);
|
||||
|
||||
const description = $L(
|
||||
"Check out more LIVE SHOWS now and enjoy shopping at Shop Time's special price on your TV by agreeing to the LG TV Shopping Terms and Conditions."
|
||||
"Check out more LIVE SHOWS now and enjoy shopping at Shop Time's special price on your TV by agreeing to the LG TV Shopping Terms and Conditions.",
|
||||
);
|
||||
const title = "welcome to shoptime!";
|
||||
delete rest.isOnTop;
|
||||
|
||||
// [추가] 약관 종류에 따라 팝업 제목을 반환하는 헬퍼 함수
|
||||
const getTermsPopupTitle = (terms) => {
|
||||
if (!terms) return '';
|
||||
if (!terms) return "";
|
||||
switch (terms.trmsTpCd) {
|
||||
case "MST00401": return $L("Privacy Policy");
|
||||
case "MST00402": return $L("Terms & Conditions");
|
||||
case "MST00405": return $L("Optional Terms");
|
||||
default: return '';
|
||||
case "MST00401":
|
||||
return $L("Privacy Policy");
|
||||
case "MST00402":
|
||||
return $L("Terms & Conditions");
|
||||
case "MST00405":
|
||||
return $L("Optional Terms");
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -528,9 +590,7 @@ export default function IntroPanel({
|
||||
{/* 첫 번째 영역: 헤더 섹션 */}
|
||||
<div className={css.headerSection}>
|
||||
<div className={css.titleContainer}>
|
||||
<div className={css.welcomeText}>
|
||||
{$L("Welcome to")}
|
||||
</div>
|
||||
<div className={css.welcomeText}>{$L("Welcome to")}</div>
|
||||
<div className={css.brandContainer}>
|
||||
<span className={css.shopText}>{$L("Sh")}</span>
|
||||
<span className={css.oText}>{$L("o")}</span>
|
||||
@@ -538,9 +598,7 @@ export default function IntroPanel({
|
||||
</div>
|
||||
</div>
|
||||
<div className={css.descriptionContainer}>
|
||||
<div className={css.descriptionText}>
|
||||
{description}
|
||||
</div>
|
||||
<div className={css.descriptionText}>{description}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -553,15 +611,15 @@ export default function IntroPanel({
|
||||
className={css.customeCheckbox}
|
||||
selected={termsChecked}
|
||||
onToggle={handleTermsToggle}
|
||||
onFocus={() => handleFocus('termsCheckbox')}
|
||||
onFocus={handleFocusTermsCheckbox}
|
||||
onBlur={handleBlur}
|
||||
spotlightId="termsCheckbox"
|
||||
ariaLabel={$L("Terms & Conditions checkbox")}
|
||||
/>
|
||||
<TButton
|
||||
className={css.termsButton}
|
||||
onClick={() => handleTermsClick("MST00402")}
|
||||
onFocus={() => handleFocus('termsButton')}
|
||||
onClick={handleTermsClickMST00402}
|
||||
onFocus={handleFocusTermsButton}
|
||||
onBlur={handleBlur}
|
||||
spotlightId="termsButton"
|
||||
type={TYPES.terms}
|
||||
@@ -579,15 +637,15 @@ export default function IntroPanel({
|
||||
className={css.customeCheckbox}
|
||||
selected={privacyChecked}
|
||||
onToggle={handlePrivacyToggle}
|
||||
onFocus={() => handleFocus('privacyCheckbox')}
|
||||
onFocus={handleFocusPrivacyCheckbox}
|
||||
onBlur={handleBlur}
|
||||
spotlightId="privacyCheckbox"
|
||||
ariaLabel={$L("Privacy Policy checkbox")}
|
||||
/>
|
||||
<TButton
|
||||
className={css.termsButton}
|
||||
onClick={() => handleTermsClick("MST00401")}
|
||||
onFocus={() => handleFocus('privacyButton')}
|
||||
onClick={handleTermsClickMST00401}
|
||||
onFocus={handleFocusPrivacyButton}
|
||||
onBlur={handleBlur}
|
||||
spotlightId="privacyButton"
|
||||
type={TYPES.terms}
|
||||
@@ -605,29 +663,25 @@ export default function IntroPanel({
|
||||
className={css.customeCheckbox}
|
||||
selected={optionalChecked}
|
||||
onToggle={handleOptionalToggle}
|
||||
onFocus={() => handleFocus('optionalCheckbox')}
|
||||
onFocus={handleFocusOptionalCheckbox}
|
||||
onBlur={handleBlur}
|
||||
spotlightId="optionalCheckbox"
|
||||
ariaLabel={$L("Optional Terms checkbox")}
|
||||
/>
|
||||
<TButton
|
||||
className={css.termsButton}
|
||||
onClick={() => handleOptionalTermsClick("MST00405")}
|
||||
onFocus={() => handleFocus('optionalButton')}
|
||||
onClick={handleOptionalTermsClickMST00405}
|
||||
onFocus={handleFocusOptionalButton}
|
||||
onBlur={handleBlur}
|
||||
spotlightId="optionalButton"
|
||||
type={TYPES.terms}
|
||||
ariaLabel={$L("View Optional Terms")}
|
||||
>
|
||||
<span className={css.termsText}>
|
||||
{$L("Optional Terms")}
|
||||
</span>
|
||||
<span className={css.termsText}>{$L("Optional Terms")}</span>
|
||||
</TButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className={css.termsRightPanel}>
|
||||
{rightPanelContent}
|
||||
</div>
|
||||
<div className={css.termsRightPanel}>{rightPanelContent}</div>
|
||||
</div>
|
||||
|
||||
{/* 세 번째 영역: Select All */}
|
||||
@@ -636,14 +690,12 @@ export default function IntroPanel({
|
||||
className={css.selectAllCheckbox}
|
||||
selected={selectAllChecked}
|
||||
onToggle={handleSelectAllToggle}
|
||||
onFocus={() => handleFocus('selectAllCheckbox')}
|
||||
onFocus={handleFocusSelectAllCheckbox}
|
||||
onBlur={handleBlur}
|
||||
spotlightId="selectAllCheckbox"
|
||||
ariaLabel={$L("Select All checkbox")}
|
||||
/>
|
||||
<span className={css.selectAllText}>
|
||||
{$L("Select All")}
|
||||
</span>
|
||||
<span className={css.selectAllText}>{$L("Select All")}</span>
|
||||
</div>
|
||||
|
||||
{/* 네 번째 영역: 버튼 섹션 */}
|
||||
@@ -651,7 +703,7 @@ export default function IntroPanel({
|
||||
<TButton
|
||||
className={css.agreeButton}
|
||||
onClick={handleAgree}
|
||||
onFocus={() => handleFocus('agreeButton')}
|
||||
onFocus={handleFocusAgreeButton}
|
||||
onBlur={handleBlur}
|
||||
spotlightId="agreeButton"
|
||||
type={TYPES.agree}
|
||||
@@ -664,7 +716,7 @@ export default function IntroPanel({
|
||||
<TButton
|
||||
className={css.disagreeButton}
|
||||
onClick={handleDisagree}
|
||||
onFocus={() => handleFocus('disagreeButton')}
|
||||
onFocus={handleFocusDisagreeButton}
|
||||
onBlur={handleBlur}
|
||||
spotlightId="disagreeButton"
|
||||
type={TYPES.agree}
|
||||
@@ -701,8 +753,7 @@ export default function IntroPanel({
|
||||
open={popupVisible}
|
||||
kind="figmaTermsPopup"
|
||||
title={getTermsPopupTitle(currentTerms)}
|
||||
text={currentTerms?.trmsCntt || ''}
|
||||
onIntroTermsAgreeClick={handleTermsAgree} // Agree 버튼 핸들러 연결
|
||||
text={currentTerms?.trmsCntt || ""}
|
||||
onClose={onClose} // Close 버튼 핸들러 연결
|
||||
/>
|
||||
</>
|
||||
@@ -745,10 +796,11 @@ export default function IntroPanel({
|
||||
open={showExitMessagePopup}
|
||||
hasText
|
||||
title={$L("Exit Shop Time")}
|
||||
text={$L("Thank you for using the Shop Time, and we hope to see you again. The app will close in 3 seconds.")}
|
||||
text={$L(
|
||||
"Thank you for using the Shop Time, and we hope to see you again. The app will close in 3 seconds.",
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</Region>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
@import "../../style/utils.module.less";
|
||||
|
||||
@COLOR_GREEN: #207847;
|
||||
@COLOR_RED: #C91D53;
|
||||
@COLOR_GRAY: #807F81;
|
||||
@COLOR_GRAY02: #CFCFCF;
|
||||
@COLOR_GRAY03: #57585A;
|
||||
@COLOR_GRAY04: #C5C6C9;
|
||||
@COLOR_GRAY05: #F8F8F8;
|
||||
@COLOR_GRAY06: #57585A;
|
||||
@COLOR_RED: #c91d53;
|
||||
@COLOR_GRAY: #807f81;
|
||||
@COLOR_GRAY02: #cfcfcf;
|
||||
@COLOR_GRAY03: #57585a;
|
||||
@COLOR_GRAY04: #c5c6c9;
|
||||
@COLOR_GRAY05: #f8f8f8;
|
||||
@COLOR_GRAY06: #57585a;
|
||||
|
||||
.panel {
|
||||
> section {
|
||||
@@ -47,9 +47,9 @@
|
||||
display: inline-flex;
|
||||
|
||||
.welcomeText {
|
||||
color: #807F81;
|
||||
color: #807f81;
|
||||
font-size: 62px;
|
||||
font-family: 'LG Smart UI';
|
||||
font-family: "LG Smart UI";
|
||||
font-weight: 400;
|
||||
line-height: 62px;
|
||||
word-wrap: break-word;
|
||||
@@ -57,27 +57,27 @@
|
||||
|
||||
.brandContainer {
|
||||
.shopText {
|
||||
color: #57585A;
|
||||
color: #57585a;
|
||||
font-size: 70px;
|
||||
font-family: 'LG Smart_Korean';
|
||||
font-family: "LG Smart_Korean";
|
||||
font-weight: 700;
|
||||
line-height: 75px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.oText {
|
||||
color: #C91D53;
|
||||
color: #c91d53;
|
||||
font-size: 70px;
|
||||
font-family: 'LG Smart_Korean';
|
||||
font-family: "LG Smart_Korean";
|
||||
font-weight: 700;
|
||||
line-height: 75px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.timeText {
|
||||
color: #57585A;
|
||||
color: #57585a;
|
||||
font-size: 70px;
|
||||
font-family: 'LG Smart_Korean';
|
||||
font-family: "LG Smart_Korean";
|
||||
font-weight: 700;
|
||||
line-height: 75px;
|
||||
word-wrap: break-word;
|
||||
@@ -95,9 +95,9 @@
|
||||
.descriptionText {
|
||||
width: 1012.49px;
|
||||
text-align: center;
|
||||
color: #807F81;
|
||||
color: #807f81;
|
||||
font-size: 36px;
|
||||
font-family: 'LG Smart UI';
|
||||
font-family: "LG Smart UI";
|
||||
font-weight: 400;
|
||||
line-height: 43px;
|
||||
word-wrap: break-word;
|
||||
@@ -126,7 +126,8 @@
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
display: inline-flex; .checkbox {
|
||||
display: inline-flex;
|
||||
.checkbox {
|
||||
/* TCheckBoxSquare 컴포넌트가 스타일링을 담당 */
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
@@ -137,9 +138,9 @@
|
||||
height: 120px;
|
||||
padding: 0 50px;
|
||||
background: @COLOR_WHITE;
|
||||
box-shadow: 0px 0px 30px rgba(0, 0, 0, 0.20);
|
||||
box-shadow: 0px 0px 30px rgba(0, 0, 0, 0.2);
|
||||
border-radius: 6px;
|
||||
border: 1px solid #CFCFCF;
|
||||
border: 1px solid #cfcfcf;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
@@ -150,7 +151,7 @@
|
||||
.termsText {
|
||||
color: black;
|
||||
font-size: 35px;
|
||||
font-family: 'LG Smart UI';
|
||||
font-family: "LG Smart UI";
|
||||
font-weight: 700;
|
||||
line-height: 35px;
|
||||
word-wrap: break-word;
|
||||
@@ -166,7 +167,7 @@
|
||||
&:focus,
|
||||
&:focus-visible,
|
||||
&:hover {
|
||||
outline: 4px #C91D53 solid !important;
|
||||
outline: 4px #c91d53 solid !important;
|
||||
outline-offset: 2px !important;
|
||||
transform: translateY(-2px) !important;
|
||||
box-shadow: 0 4px 12px rgba(201, 29, 83, 0.3) !important;
|
||||
@@ -177,12 +178,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
} .termsRightPanel {
|
||||
}
|
||||
.termsRightPanel {
|
||||
flex: 1 1 0;
|
||||
align-self: stretch;
|
||||
padding-left: 60px;
|
||||
padding-right: 60px;
|
||||
border-left: 1px #C5C6C9 solid;
|
||||
border-left: 1px #c5c6c9 solid;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
@@ -195,7 +197,8 @@
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
display: inline-flex; .selectAllCheckbox {
|
||||
display: inline-flex;
|
||||
.selectAllCheckbox {
|
||||
/* TCheckBoxSquare 컴포넌트가 스타일링을 담당 */
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
@@ -204,7 +207,7 @@
|
||||
.selectAllText {
|
||||
color: @COLOR_BLACK;
|
||||
font-size: 35px;
|
||||
font-family: 'LG Smart UI';
|
||||
font-family: "LG Smart UI";
|
||||
font-weight: 700;
|
||||
line-height: 35px;
|
||||
word-wrap: break-word;
|
||||
@@ -282,27 +285,30 @@
|
||||
|
||||
// 기본 상자 스타일
|
||||
&:before {
|
||||
content: '';
|
||||
content: "";
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
background-color: @COLOR_WHITE;
|
||||
border: 2px solid @COLOR_GRAY02;
|
||||
border-radius: 4px;
|
||||
display: block;
|
||||
transition: background-color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
transition:
|
||||
background-color 0.2s ease,
|
||||
border-color 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
// ✅ 선택된 상태: 배경색과 테두리 모두 붉은색 + 굵은 테두리
|
||||
&.selected:before {
|
||||
background-color: #C91D53 !important;
|
||||
border: 4px solid #C91D53 !important; // 굵은 테두리로 변경
|
||||
background-color: #c91d53 !important;
|
||||
border: 4px solid #c91d53 !important; // 굵은 테두리로 변경
|
||||
box-shadow: 0 0 8px rgba(201, 29, 83, 0.3); // 약간의 그림자 효과
|
||||
}
|
||||
|
||||
// 포커스 받았지만 선택 안됨
|
||||
&.focused:not(.selected):before {
|
||||
background-color: rgba(201, 29, 83, 0.1);
|
||||
border: 4px solid #C91D53 !important;
|
||||
border: 4px solid #c91d53 !important;
|
||||
box-shadow: 0 0 10px rgba(199, 8, 80, 0.3) !important;
|
||||
}
|
||||
|
||||
@@ -315,13 +321,13 @@
|
||||
|
||||
// 포커스 받음 (선택된 상태가 아닌 경우에만 적용)
|
||||
&.focused:not(.selected):before {
|
||||
border: 4px solid #C91D53 !important;
|
||||
border: 4px solid #c91d53 !important;
|
||||
box-shadow: 0 0 10px rgba(199, 8, 80, 0.3) !important;
|
||||
}
|
||||
|
||||
// 체크마크
|
||||
&.selected:after {
|
||||
content: '✓';
|
||||
content: "✓";
|
||||
color: @COLOR_WHITE;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
@@ -356,9 +362,9 @@
|
||||
|
||||
.optionalDescription {
|
||||
width: 595px;
|
||||
color: #807F81;
|
||||
color: #807f81;
|
||||
font-size: 26px;
|
||||
font-family: 'LG Smart UI';
|
||||
font-family: "LG Smart UI";
|
||||
font-weight: 400;
|
||||
line-height: 43px;
|
||||
word-wrap: break-word;
|
||||
|
||||
@@ -76,6 +76,7 @@ import LoadingPanel from "../LoadingPanel/LoadingPanel";
|
||||
import MyPagePanel from "../MyPagePanel/MyPagePanel";
|
||||
import OnSalePanel from "../OnSalePanel/OnSalePanel";
|
||||
import PlayerPanel from "../PlayerPanel/PlayerPanel";
|
||||
import PlayerPanelNew from "../PlayerPanel/PlayerPanel.new";
|
||||
import SearchPanel from "../SearchPanel/SearchPanel";
|
||||
import ThemeCurationPanel from "../ThemeCurationPanel/ThemeCurationPanel";
|
||||
import TrendingNowPanel from "../TrendingNowPanel/TrendingNowPanel";
|
||||
@@ -108,6 +109,7 @@ const panelMap = {
|
||||
[Config.panel_names.VIDEO_TEST_PANEL]: VideoTestPanel,
|
||||
[Config.panel_names.DETAIL_PANEL]: DetailPanel,
|
||||
[Config.panel_names.PLAYER_PANEL]: PlayerPanel,
|
||||
[Config.panel_names.PLAYER_PANEL_NEW]: PlayerPanelNew,
|
||||
[Config.panel_names.CHECKOUT_PANEL]: CheckOutPanel,
|
||||
[Config.panel_names.WELCOME_EVENT_PANEL]: WelcomeEventPanel,
|
||||
[Config.panel_names.THEME_CURATION_PANEL]: ThemeCurationPanel,
|
||||
@@ -139,7 +141,8 @@ export default function MainView({ className, initService }) {
|
||||
const panels = useSelector((state) => state.panels.panels);
|
||||
const lastPanelAction = useSelector((state) => state.panels.lastPanelAction);
|
||||
const loadingComplete = useSelector((state) => state.common?.loadingComplete);
|
||||
const menuData = useSelector((state) => state.home.menuData?.data); const {
|
||||
const menuData = useSelector((state) => state.home.menuData?.data);
|
||||
const {
|
||||
popupVisible,
|
||||
activePopup,
|
||||
data: errorCode,
|
||||
@@ -154,10 +157,10 @@ export default function MainView({ className, initService }) {
|
||||
deviceId,
|
||||
} = useSelector((state) => state.common.appStatus);
|
||||
const skipEndOfServicePopup = useSelector(
|
||||
(state) => state.localSettings.skipEndOfServicePopup
|
||||
(state) => state.localSettings.skipEndOfServicePopup,
|
||||
);
|
||||
const isInternetConnected = useSelector(
|
||||
(state) => state.common.appStatus.isInternetConnected
|
||||
(state) => state.common.appStatus.isInternetConnected,
|
||||
);
|
||||
|
||||
const deviceCountryCode = httpHeader?.["X-Device-Country"] || "";
|
||||
@@ -172,9 +175,8 @@ export default function MainView({ className, initService }) {
|
||||
const [showEndOfServicePopup, setShowEndOfServicePopup] = useState(false);
|
||||
const topPanel = panels[panels.length - 1];
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
console.log('🔍 MainView 팝업 상태 변경:', {
|
||||
console.log("🔍 MainView 팝업 상태 변경:", {
|
||||
popupVisible,
|
||||
activePopup,
|
||||
});
|
||||
@@ -202,6 +204,8 @@ export default function MainView({ className, initService }) {
|
||||
let renderingPanels = [];
|
||||
if (
|
||||
panels[panels.length - 1]?.name === Config.panel_names.PLAYER_PANEL ||
|
||||
panels[panels.length - 1]?.name ===
|
||||
Config.panel_names.PLAYER_PANEL_NEW ||
|
||||
panels[panels.length - 2]?.name === Config.panel_names.PLAYER_PANEL
|
||||
) {
|
||||
renderingPanels = panels.slice(-2);
|
||||
@@ -212,7 +216,8 @@ export default function MainView({ className, initService }) {
|
||||
<>
|
||||
{(isHomeOnTop ||
|
||||
(panels.length === 1 &&
|
||||
panels[0]?.name === Config.panel_names.PLAYER_PANEL)) && (
|
||||
(panels[0]?.name === Config.panel_names.PLAYER_PANEL ||
|
||||
panels[0]?.name === Config.panel_names.PLAYER_PANEL_NEW))) && (
|
||||
<HomePanel
|
||||
key={Config.panel_names.HOME_PANEL}
|
||||
isOnTop={isHomeOnTop}
|
||||
@@ -234,7 +239,9 @@ export default function MainView({ className, initService }) {
|
||||
if (
|
||||
index === 0 &&
|
||||
renderingPanels.length === 2 &&
|
||||
renderingPanels[1].name === Config.panel_names.PLAYER_PANEL &&
|
||||
(renderingPanels[1].name === Config.panel_names.PLAYER_PANEL ||
|
||||
renderingPanels[1].name ===
|
||||
Config.panel_names.PLAYER_PANEL_NEW) &&
|
||||
renderingPanels[1].panelInfo.modal
|
||||
) {
|
||||
isPanelOnTop = true;
|
||||
@@ -309,8 +316,8 @@ export default function MainView({ className, initService }) {
|
||||
mandatoryIncludeYn: "Y",
|
||||
termsList: ["MST00401", "MST00402"],
|
||||
},
|
||||
reload
|
||||
)
|
||||
reload,
|
||||
),
|
||||
);
|
||||
}
|
||||
}, [dispatch, activePopup]);
|
||||
@@ -362,10 +369,10 @@ export default function MainView({ className, initService }) {
|
||||
dispatch(
|
||||
changeAppStatus({
|
||||
cursorVisible: ev.visibility || ev.detail.visibility,
|
||||
})
|
||||
}),
|
||||
);
|
||||
},
|
||||
[dispatch]
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -377,7 +384,7 @@ export default function MainView({ className, initService }) {
|
||||
dispatch(
|
||||
changeAppStatus({
|
||||
cursorVisible: window.cursorEvent && window.cursorEvent.visibility,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
return () => {
|
||||
@@ -390,7 +397,7 @@ export default function MainView({ className, initService }) {
|
||||
const popupTimerRef = useRef(null); // 타이머 ID를 저장할 상태 변수
|
||||
|
||||
const { upComingAlertShow } = useSelector(
|
||||
(state) => state.myPage.upComingData
|
||||
(state) => state.myPage.upComingData,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -423,7 +430,7 @@ export default function MainView({ className, initService }) {
|
||||
patnrId: patnrId.toString(),
|
||||
showId,
|
||||
showNm,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}, [activePopup, alertItems, popupVisible]);
|
||||
@@ -465,7 +472,7 @@ export default function MainView({ className, initService }) {
|
||||
upComingAlertShow.alertShows?.length > 0
|
||||
) {
|
||||
const alertList = upComingAlertShow.alertShows.filter((show) =>
|
||||
isSameDateTime(show.strtDt)
|
||||
isSameDateTime(show.strtDt),
|
||||
);
|
||||
|
||||
if (alertList.length > 0) {
|
||||
@@ -518,7 +525,7 @@ export default function MainView({ className, initService }) {
|
||||
patnrId: patnrId.toString(),
|
||||
showId,
|
||||
showNm,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
dispatch(resetPanels());
|
||||
@@ -526,7 +533,7 @@ export default function MainView({ className, initService }) {
|
||||
pushPanel({
|
||||
name: panel_names.FEATURED_BRANDS_PANEL,
|
||||
panelInfo: { from: "upcoming", patnrId: patnrId.toString() },
|
||||
})
|
||||
}),
|
||||
);
|
||||
dispatch(setHidePopup());
|
||||
setIntervalActive((prev) => !prev);
|
||||
@@ -575,7 +582,7 @@ export default function MainView({ className, initService }) {
|
||||
patnrId: patnrId.toString(),
|
||||
showId,
|
||||
showNm,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
setIntervalActive((prev) => !prev);
|
||||
@@ -639,7 +646,12 @@ export default function MainView({ className, initService }) {
|
||||
}
|
||||
}, [webOSVersion]);
|
||||
const handleErrorPopupClose = useCallback(() => {
|
||||
console.log('handleErrorPopupClose 호출됨! activePopup:', activePopup, 'popupData:', popupData);
|
||||
console.log(
|
||||
"handleErrorPopupClose 호출됨! activePopup:",
|
||||
activePopup,
|
||||
"popupData:",
|
||||
popupData,
|
||||
);
|
||||
if (popupData?.shouldPopPanel) {
|
||||
dispatch(popPanel());
|
||||
}
|
||||
@@ -678,7 +690,7 @@ export default function MainView({ className, initService }) {
|
||||
loadingComplete && (
|
||||
<div
|
||||
className={classNames(
|
||||
css.container
|
||||
css.container,
|
||||
// showLoadingPanel.type === "launching" ? css.transparent : null
|
||||
)}
|
||||
>
|
||||
@@ -731,9 +743,10 @@ export default function MainView({ className, initService }) {
|
||||
popupData.errorCode,
|
||||
popupData.errorMsg,
|
||||
popupData.retDetailCode,
|
||||
popupData.returnBindStrings
|
||||
popupData.returnBindStrings,
|
||||
)}
|
||||
</p> <TButton className={css.popupBtn} onClick={handleErrorPopupClose}>
|
||||
</p>{" "}
|
||||
<TButton className={css.popupBtn} onClick={handleErrorPopupClose}>
|
||||
{$L("OK")}
|
||||
</TButton>
|
||||
</div>
|
||||
@@ -781,8 +794,6 @@ export default function MainView({ className, initService }) {
|
||||
/>
|
||||
) : null}
|
||||
|
||||
|
||||
|
||||
<SystemNotification />
|
||||
{loadingComplete &&
|
||||
activePopup === Config.ACTIVE_POPUP.endOfServicePopup &&
|
||||
|
||||
@@ -1535,8 +1535,24 @@ const PlayerPanel = ({
|
||||
if (broadcast.type === "videoError") {
|
||||
return null;
|
||||
}
|
||||
|
||||
// For previews, always use the direct URL from panelInfo.
|
||||
if (panelInfo.modal) {
|
||||
return panelInfo.showUrl;
|
||||
}
|
||||
|
||||
// For fullscreen, the playlist is the primary source.
|
||||
if (playListInfo && playListInfo.length > 0 && playListInfo[selectedIndex]?.showUrl) {
|
||||
return playListInfo[selectedIndex]?.showUrl;
|
||||
}
|
||||
|
||||
// Fallback for fullscreen if playlist isn't ready, use panelInfo URL.
|
||||
if (!panelInfo.modal && panelInfo.showUrl) {
|
||||
return panelInfo.showUrl;
|
||||
}
|
||||
|
||||
return playListInfo && playListInfo[selectedIndex]?.showUrl;
|
||||
}, [playListInfo, selectedIndex, broadcast]);
|
||||
}, [panelInfo, playListInfo, selectedIndex, broadcast]);
|
||||
|
||||
const isYoutube = useMemo(() => {
|
||||
if (currentPlayingUrl && currentPlayingUrl.includes("youtu")) {
|
||||
|
||||
2281
com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.new.jsx
Normal file
2281
com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.new.jsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,79 @@
|
||||
@import "../../style/CommonStyle.module.less";
|
||||
@import "../../style/utils.module.less";
|
||||
|
||||
@videoBackgroundColor: black;
|
||||
.videoContainer {
|
||||
position: absolute;
|
||||
background-color: @videoBackgroundColor;
|
||||
z-index: 21;
|
||||
|
||||
.playButton {
|
||||
.size(@w: 60px, @h: 60px);
|
||||
background-image: url("../../../assets/images/btn/btn-video-play-nor@3x.png");
|
||||
background-size: cover;
|
||||
|
||||
&:focus {
|
||||
.size(@w: 60px, @h: 60px);
|
||||
border-radius: 50%;
|
||||
background-color: #c70850;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.pauseButton {
|
||||
.size(@w: 60px, @h: 60px);
|
||||
background-image: url("../../../assets/images/btn/btn-voc-pause-nor@3x.png");
|
||||
background-size: cover;
|
||||
|
||||
&:focus {
|
||||
.size(@w: 60px, @h: 60px);
|
||||
border-radius: 50%;
|
||||
background-color: #c70850;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
.toOpenBtn {
|
||||
.size(@w: 147px, @h: 243px);
|
||||
min-width: 60px !important;
|
||||
margin: 192px 0 136px 512px;
|
||||
text-align: center;
|
||||
background-image: url(../../../assets/images/btn/btn-toopen-foc.svg);
|
||||
}
|
||||
|
||||
.videoReduce {
|
||||
.size(@w:78px, @h:78px);
|
||||
background: url("../../../assets/images/btn/btn-video-min-nor@3x.png")
|
||||
no-repeat center center/60px 60px;
|
||||
position: absolute;
|
||||
right: 60px;
|
||||
bottom: 150px;
|
||||
z-index: 3;
|
||||
|
||||
&:focus {
|
||||
.size(@w:78px, @h:78px);
|
||||
background-color: rgba(199, 8, 80, 0.5);
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
&.modal,
|
||||
&.background {
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
left: -1px;
|
||||
top: -1px;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
background-color: @videoBackgroundColor;
|
||||
overflow: visible;
|
||||
.tabContainer,
|
||||
.arrow,
|
||||
.toOpenBtn {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&.hideSubtitle {
|
||||
video::cue {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user