diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.jsx b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.jsx
index 6803ca1c..1d5d04fb 100644
--- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.jsx
+++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.jsx
@@ -1,61 +1,37 @@
-import React, {
- useCallback,
- useEffect,
- useMemo,
- useRef,
- useState,
-} from 'react';
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
-import {
- useDispatch,
- useSelector,
-} from 'react-redux';
+import { useDispatch, useSelector } from 'react-redux';
import { Job } from '@enact/core/util';
import Spotlight from '@enact/spotlight';
-import SpotlightContainerDecorator
- from '@enact/spotlight/SpotlightContainerDecorator';
-import {
- getContainerNode,
- setContainerLastFocusedElement,
-} from '@enact/spotlight/src/container';
+import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
+import { getContainerNode, setContainerLastFocusedElement } from '@enact/spotlight/src/container';
import { sendLogTotalRecommend } from '../../../../actions/logActions';
import { pushPanel } from '../../../../actions/panelActions';
import { hidePlayerOverlays } from '../../../../actions/videoPlayActions';
import TItemCard, { TYPES } from '../../../../components/TItemCard/TItemCard';
-import TVirtualGridList
- from '../../../../components/TVirtualGridList/TVirtualGridList';
+import TVirtualGridList from '../../../../components/TVirtualGridList/TVirtualGridList';
import useScrollTo from '../../../../hooks/useScrollTo';
-import {
- LOG_CONTEXT_NAME,
- LOG_MENU,
- LOG_MESSAGE_ID,
- panel_names,
-} from '../../../../utils/Config';
+import { LOG_CONTEXT_NAME, LOG_MENU, LOG_MESSAGE_ID, panel_names } from '../../../../utils/Config';
import { scaleH } from '../../../../utils/helperMethods';
-import ListEmptyContents
- from '../TabContents/ListEmptyContents/ListEmptyContents';
+import ListEmptyContents from '../TabContents/ListEmptyContents/ListEmptyContents';
import css1 from './ShopNowContents.module.less';
import cssV2 from './ShopNowContents.v2.module.less';
const extractPriceInfo = (priceInfo) => {
- if (!priceInfo)
- return { originalPrice: "", discountedPrice: "", discountRate: "" };
+ if (!priceInfo) return { originalPrice: '', discountedPrice: '', discountRate: '' };
- const parts = priceInfo.split("|").map((part) => part.trim());
+ const parts = priceInfo.split('|').map((part) => part.trim());
return {
- originalPrice: parts[0] || "",
- discountedPrice: parts[1] || "",
- discountRate: parts[4] || "",
+ originalPrice: parts[0] || '',
+ discountedPrice: parts[1] || '',
+ discountRate: parts[4] || '',
};
};
-const Container = SpotlightContainerDecorator(
- { enterTo: "last-focused" },
- "div"
-);
+const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
export default function ShopNowContents({
shopNowInfo,
videoVerticalVisible,
@@ -65,16 +41,40 @@ export default function ShopNowContents({
panelInfo,
tabTitle,
version = 1,
- direction = "vertical",
+ direction = 'vertical',
}) {
const css = version === 2 ? cssV2 : css1;
const { getScrollTo, scrollTop } = useScrollTo();
const dispatch = useDispatch();
const paenls = useSelector((state) => state.panels.panels[1]?.panelInfo);
+ const youmaylikeInfos = useSelector((state) => state.main.youmaylikeInfos);
const scrollTopJob = useRef(new Job((func) => func(), 0));
const [height, setHeight] = useState();
const gridStyle = useMemo(() => ({ height: `${height}px` }), [height]);
+ // ShopNow + YouMayLike 통합 아이템 (v2이고 shopNow < 3일 때만)
+ const combinedItems = useMemo(() => {
+ if (!shopNowInfo) return [];
+
+ // 기본: ShopNow 아이템
+ let items = shopNowInfo.map((item) => ({
+ ...item,
+ _type: 'shopnow',
+ }));
+
+ // v2 + ShopNow < 3 + YouMayLike 데이터 존재 시 통합
+ if (version === 2 && shopNowInfo.length < 3 && youmaylikeInfos && youmaylikeInfos.length > 0) {
+ items = items.concat(
+ youmaylikeInfos.map((item) => ({
+ ...item,
+ _type: 'youmaylike',
+ }))
+ );
+ }
+
+ return items;
+ }, [shopNowInfo, youmaylikeInfos, version]);
+
// 각 상품별 가격 정보를 미리 계산
const priceInfoMap = useMemo(() => {
if (!shopNowInfo) return {};
@@ -90,7 +90,7 @@ export default function ShopNowContents({
useEffect(() => {
return () => {
- const gridListId = "playVideoShopNowBox";
+ const gridListId = 'playVideoShopNowBox';
const girdList = getContainerNode(gridListId);
if (girdList) setContainerLastFocusedElement(null, [gridListId]);
@@ -129,6 +129,59 @@ export default function ShopNowContents({
const renderItem = useCallback(
({ index, ...rest }) => {
+ const item = combinedItems[index];
+
+ // ===== YouMayLike 아이템 처리 =====
+ if (item._type === 'youmaylike') {
+ const { imgUrl, patnrId, prdtId, prdtNm, priceInfo, offerInfo } = item;
+
+ // YouMayLike 시작 지점 여부 (구분선 표시)
+ const isYouMayLikeStart = shopNowInfo && index === shopNowInfo.length;
+
+ const handleItemClick = () => {
+ dispatch(
+ pushPanel({
+ name: panel_names.DETAIL_PANEL,
+ panelInfo: {
+ showNm: playListInfo?.showNm,
+ showId: playListInfo?.showId,
+ liveFlag: playListInfo?.liveFlag,
+ thumbnailUrl: playListInfo?.thumbnailUrl,
+ patnrId,
+ prdtId,
+ launchedFromPlayer: true,
+ },
+ })
+ );
+ };
+
+ return (
+ <>
+ {isYouMayLikeStart &&
}
+ {
+ if (handleItemFocus) {
+ handleItemFocus(LOG_MENU.FULL_YOU_MAY_LIKE);
+ }
+ }}
+ spotlightId={`you-may-like-item-${index}`}
+ type={TYPES.horizontal}
+ version={version}
+ />
+ >
+ );
+ }
+
+ // ===== ShopNow 아이템 처리 (기존 로직) =====
const {
imgUrls,
patnrId,
@@ -140,11 +193,10 @@ export default function ShopNowContents({
patncNm,
brndNm,
catNm,
- } = shopNowInfo[index];
+ } = item;
// 미리 계산된 가격 정보를 사용
- const { originalPrice, discountedPrice, discountRate } =
- priceInfoMap[index] || {};
+ const { originalPrice, discountedPrice, discountRate } = priceInfoMap[index] || {};
const handleItemClick = () => {
const params = {
@@ -173,7 +225,7 @@ export default function ShopNowContents({
showId: playListInfo?.showId,
liveFlag: playListInfo?.liveFlag,
thumbnailUrl: playListInfo?.thumbnailUrl,
- liveReqFlag: panelInfo?.shptmBanrTpNm === "LIVE" && "Y",
+ liveReqFlag: panelInfo?.shptmBanrTpNm === 'LIVE' && 'Y',
patnrId,
prdtId,
launchedFromPlayer: true,
@@ -202,7 +254,7 @@ export default function ShopNowContents({
// v2에서 첫 번째 아이템일 때 위로 가면 Close 버튼으로
e.stopPropagation();
e.preventDefault();
- Spotlight.focus("shownow_close_button");
+ Spotlight.focus('shownow_close_button');
}
: undefined
}
@@ -212,7 +264,7 @@ export default function ShopNowContents({
);
},
[
- shopNowInfo,
+ combinedItems,
videoVerticalVisible,
panelInfo?.shptmBanrTpNm,
priceInfoMap,
@@ -221,25 +273,25 @@ export default function ShopNowContents({
playListInfo,
dispatch,
version,
+ handleItemFocus,
+ handleFocus,
]
);
return (
<>
- {shopNowInfo && shopNowInfo.length > 0 ? (
+ {combinedItems && combinedItems.length > 0 ? (
diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.v2.module.less b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.v2.module.less
index b3913ff4..1b7f8781 100644
--- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.v2.module.less
+++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.v2.module.less
@@ -51,3 +51,12 @@
}
}
}
+
+// YouMayLike 시작 지점 구분선
+.youMayLikeDivider {
+ width: 2px !important;
+ height: 445px;
+ background: rgba(234, 234, 234, 0.3);
+ margin-right: 15px;
+ flex-shrink: 0;
+}
diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/ShopNowContainer.module.less b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/ShopNowContainer.module.less
index 01c07187..3efd7c1b 100644
--- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/ShopNowContainer.module.less
+++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/ShopNowContainer.module.less
@@ -12,8 +12,11 @@
border-top: 1px solid rgba(234, 234, 234, 0.8);
overflow: hidden;
.flex(@display: flex, @justifyCenter: flex-start, @alignCenter: flex-start, @direction: column);
- gap: 40px;
z-index: 5;
+
+ > * + * {
+ margin-top: 40px;
+ }
}
.header {
@@ -23,7 +26,10 @@
overflow: hidden;
border-radius: 100px;
.flex(@display: flex, @justifyCenter: flex-start, @alignCenter: center);
- gap: 15px;
+
+ > * + * {
+ margin-left: 15px;
+ }
}
.iconWrapper {
@@ -51,11 +57,14 @@
.productList {
align-self: stretch;
.flex(@display: flex, @justifyCenter: flex-start, @alignCenter: flex-start);
- gap: 30px;
overflow-x: auto;
overflow-y: hidden;
- > * + * {
- margin-left: 0;
+ > * {
+ margin-right: 30px;
+
+ &:last-child {
+ margin-right: 0;
+ }
}
}
diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/ShopNowItem.module.less b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/ShopNowItem.module.less
index 5129332a..8de653b0 100644
--- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/ShopNowItem.module.less
+++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/ShopNowItem.module.less
@@ -8,10 +8,13 @@
border-radius: 12px;
border: 1px solid transparent;
.flex(@display: flex, @justifyCenter: flex-start, @alignCenter: flex-start, @direction: column);
- gap: 15px;
flex-shrink: 0;
transition: all 0.3s ease;
+ > * + * {
+ margin-top: 15px;
+ }
+
&:focus {
border-color: @PRIMARY_COLOR_RED;
outline: 4px @PRIMARY_COLOR_RED solid;
@@ -33,7 +36,10 @@
.productInfo {
align-self: stretch;
.flex(@display: flex, @justifyCenter: center, @alignCenter: flex-start, @direction: column);
- gap: 15px;
+
+ > * + * {
+ margin-top: 15px;
+ }
}
.productName {
@@ -49,13 +55,19 @@
.priceWrapper {
align-self: stretch;
.flex(@display: flex, @justifyCenter: flex-start, @alignCenter: flex-start);
- gap: 10px;
+
+ > * + * {
+ margin-left: 10px;
+ }
}
.priceContainer {
padding: 4px 0;
.flex(@display: flex, @justifyCenter: flex-start, @alignCenter: center);
- gap: 11px;
+
+ > * + * {
+ margin-left: 11px;
+ }
}
.salePrice {
diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx
index 339b2c16..6e04ea46 100644
--- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx
+++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx
@@ -1,35 +1,26 @@
-import React, {
- useCallback,
- useEffect,
-} from 'react';
+import React, { useCallback, useEffect } from 'react';
import classNames from 'classnames';
+import { useSelector } from 'react-redux';
import Spotlight from '@enact/spotlight';
-import SpotlightContainerDecorator
- from '@enact/spotlight/SpotlightContainerDecorator';
+import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
import Spottable from '@enact/spotlight/Spottable';
// import icon_arrow_right from '../../../../../assets/images/icons';
-import icon_arrow_dwon
- from '../../../../../assets/images/player/icon_tabcontainer_arrow_down.png';
-import icon_shop_now
- from '../../../../../assets/images/player/icon_tabcontainer_shopnow.png';
+import icon_arrow_dwon from '../../../../../assets/images/player/icon_tabcontainer_arrow_down.png';
+import icon_shop_now from '../../../../../assets/images/player/icon_tabcontainer_shopnow.png';
import { LOG_MENU } from '../../../../utils/Config';
import { $L } from '../../../../utils/helperMethods';
import { SpotlightIds } from '../../../../utils/SpotlightIds';
import LiveChannelContents from '../TabContents/LiveChannelContents';
import ShopNowContents from '../TabContents/ShopNowContents';
-import YouMayLikeContents from '../TabContents/YouMayLikeContents';
import ShopNowButton from './ShopNowButton';
import css from './TabContainer.v2.module.less';
-const Container = SpotlightContainerDecorator(
- { enterTo: "last-focused" },
- "div"
-);
+const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
-const SpottableDiv = Spottable("div");
+const SpottableDiv = Spottable('div');
export default function TabContainerV2({
panelInfo,
@@ -49,11 +40,11 @@ export default function TabContainerV2({
onTabClose, // 탭 닫기 콜백 함수
tabVisible,
}) {
+ const youmaylikeInfos = useSelector((state) => state.main.youmaylikeInfos);
+
const tabList = [
- $L("SHOP NOW"),
- panelInfo?.shptmBanrTpNm === "LIVE"
- ? $L("LIVE CHANNEL")
- : $L("FEATURED SHOWS"),
+ $L('SHOP NOW'),
+ panelInfo?.shptmBanrTpNm === 'LIVE' ? $L('LIVE CHANNEL') : $L('FEATURED SHOWS'),
];
useEffect(() => {
@@ -64,10 +55,8 @@ export default function TabContainerV2({
}
if (tabIndex === 1) {
- const isLive = panelInfo?.shptmBanrTpNm === "LIVE";
- nowMenu = isLive
- ? LOG_MENU.FULL_LIVE_CHANNELS
- : LOG_MENU.FULL_FEATURED_SHOWS;
+ const isLive = panelInfo?.shptmBanrTpNm === 'LIVE';
+ nowMenu = isLive ? LOG_MENU.FULL_LIVE_CHANNELS : LOG_MENU.FULL_FEATURED_SHOWS;
}
if (nowMenu) {
@@ -89,7 +78,7 @@ export default function TabContainerV2({
if (videoVerticalVisible) {
e.stopPropagation();
e.preventDefault();
- Spotlight.focus("spotlightId-video-contaienr");
+ Spotlight.focus('spotlightId-video-contaienr');
}
},
[videoVerticalVisible]
@@ -120,7 +109,7 @@ export default function TabContainerV2({
useEffect(() => {
if (tabIndex === 2) {
setTimeout(() => {
- Spotlight.focus("below-tab-shop-now-button");
+ Spotlight.focus('below-tab-shop-now-button');
}, 100);
}
}, [tabIndex]);
@@ -136,45 +125,53 @@ export default function TabContainerV2({
>
{tabVisible && tabIndex === 0 && (
<>
-
-
{
- // 첫 번째 ShopNow 아이템으로 포커스 이동
- e.stopPropagation();
- e.preventDefault();
- Spotlight.focus("shop-now-item-0");
- }}
- >
-
-

-
- SHOP NOW
-
-

-
-
- {/*
{
- // 첫 번째 ShopNow 아이템으로 포커스 이동
- e.stopPropagation();
- e.preventDefault();
- Spotlight.focus("shop-now-item-0");
- }}
- >
- ×
- */}
+
+
+
{
+ // 첫 번째 ShopNow 아이템으로 포커스 이동
+ e.stopPropagation();
+ e.preventDefault();
+ Spotlight.focus('shop-now-item-0');
+ }}
+ >
+
+

+
+ SHOP NOW
+
+

+
+
+ {/*
{
+ // 첫 번째 ShopNow 아이템으로 포커스 이동
+ e.stopPropagation();
+ e.preventDefault();
+ Spotlight.focus("shop-now-item-0");
+ }}
+ >
+ ×
+ */}
+
+
+ {/* YouMayAlso Like 헤더 (ShopNow 아이템 < 3 && YouMayLike 데이터 존재) */}
+ {shopNowInfo &&
+ shopNowInfo.length < 3 &&
+ youmaylikeInfos &&
+ youmaylikeInfos.length > 0 && (
+
+ )}
- {shopNowInfo && shopNowInfo.length < 3 && (
-
- )}
>
)}
@@ -208,7 +196,7 @@ export default function TabContainerV2({
onSpotlightUp={handleSpotlightUpToBackButton}
onSpotlightDown={(e) => {
// 첫 번째 PlayerItem으로 포커스 이동
- Spotlight.focus("tabChannel-video-0");
+ Spotlight.focus('tabChannel-video-0');
}}
>
LIVE CHANNEL
@@ -217,7 +205,7 @@ export default function TabContainerV2({
- {panelInfo?.shptmBanrTpNm === "LIVE" && playListInfo && (
+ {panelInfo?.shptmBanrTpNm === 'LIVE' && playListInfo && (
)}
- {tabVisible && tabIndex === 2 && (
-
- )}
+ {tabVisible && tabIndex === 2 && }
);
}
diff --git a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.module.less b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.module.less
index 382248b5..7436ba63 100644
--- a/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.module.less
+++ b/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.module.less
@@ -89,14 +89,22 @@
}
}
+// ShopNow 헤더 컨테이너 (ShopNow + YouMayAlsoLike 헤더를 같은 라인에 배치)
+.shopNowHeaderContainer {
+ display: flex;
+ align-items: center;
+ width: 100%;
+}
+
// SHOP NOW 헤더 스타일 (ShopNowContainer 참고)
.shopNowHeader {
- width: 100%;
+ width: auto;
height: 70px;
padding: 20px 0;
overflow: hidden;
border-radius: 100px;
.flex(@display: flex, @justifyCenter: space-between, @alignCenter: center);
+ flex-shrink: 0;
}
.shopNowIconWrapper {
@@ -239,6 +247,31 @@
}
}
}
+
+// YOU MAY ALSO LIKE 헤더 스타일
+.youMayAlsoLikeHeader {
+ margin-left: 90px;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ padding: 20px 0;
+ overflow: hidden;
+ border-radius: 100px;
+ flex-shrink: 0;
+}
+
+.youMayAlsoLikeText {
+ margin-right: 15px;
+ color: #EAEAEA;
+ font-size: 24px;
+ font-family: @baseFont;
+ font-weight: 700;
+ line-height: 31px;
+ word-wrap: break-word;
+ white-space: nowrap;
+}
+
/* 애니메이션 정의 */
@keyframes slideInFromRight {
from {