Compare commits
105 Commits
develop_do
...
backup-202
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d93960f40a | ||
| 4dfa15b4c0 | |||
| d83e9d38f0 | |||
| 8589cde061 | |||
| 92964a5063 | |||
| dba79789a8 | |||
|
|
8a882c28ca | ||
| 61f67708a9 | |||
| c9b2e5daf5 | |||
| 4f4887ebdb | |||
| 9d8cafc0a9 | |||
|
|
929a9020a1 | ||
|
|
83905a092d | ||
| e4a64644dd | |||
| 55af96bd00 | |||
|
|
8325070138 | ||
|
|
bbb9e64120 | ||
| 99ea3e6595 | |||
| 3dc4699479 | |||
|
|
8a3bcc1f9c | ||
|
|
486fb5efd5 | ||
|
|
802484debd | ||
|
|
c540378cb5 | ||
|
|
cb3a4e9bc7 | ||
|
|
3ce4398e67 | ||
|
|
78153bae0c | ||
| 2c681bab68 | |||
| ab2dd7385b | |||
| ac5414a5fe | |||
|
|
f46090863f | ||
|
|
1e9c9bee40 | ||
|
|
f514e2468c | ||
|
|
1305158113 | ||
|
|
e97172fad5 | ||
| 42f58bf10c | |||
| e424ab761c | |||
| f62ccef420 | |||
| 1ee664e8c1 | |||
|
|
16a09b2e2b | ||
|
|
4fcd87da7d | ||
|
|
9c2ecbaa57 | ||
|
|
ad8fc598b4 | ||
|
|
ccc91ec662 | ||
|
|
b3b1151a1d | ||
|
|
4a70f321ed | ||
| ddd5d5c7ba | |||
| 9681eb42e1 | |||
| a3fe60ca70 | |||
|
|
0593f54d6e | ||
| d903610709 | |||
|
|
bc6119f902 | ||
| 38fad5ffe2 | |||
|
|
c16724f245 | ||
|
|
013055692f | ||
| 98df524ecf | |||
| 3e300749a0 | |||
| f5621b0c55 | |||
|
|
7971bbc1db | ||
| d640bb74ef | |||
|
|
cf27ed3846 | ||
| a85710421c | |||
| 05f5bf4d33 | |||
| 8057021d1c | |||
| cbdf1b89f8 | |||
|
|
4fe3c94b1e | ||
|
|
07e5d5c6de | ||
| bc8317483f | |||
| a2b29d219a | |||
| bf7af5aa2e | |||
| bc7a999cf1 | |||
| d6656848a2 | |||
|
|
d545a4de0c | ||
|
|
39d1b42ec4 | ||
|
|
53aa879ee5 | ||
| db7bc4b2ed | |||
|
|
a6275a63e9 | ||
| a6eee92641 | |||
| 844f374abb | |||
| 77987711d0 | |||
| 18c3ac3ad5 | |||
| aa1f9630e6 | |||
| 407b4c7751 | |||
| f2ab9dbdd4 | |||
| ce7916d7b0 | |||
| 0db5a72c63 | |||
| a46d34b776 | |||
| 85c44cdd8b | |||
| 2a1cda560c | |||
|
|
c7f6bf00b9 | ||
| 255b3bb2b7 | |||
| 4a6473e1e5 | |||
|
|
7f7b413aa5 | ||
| 439e5f46e3 | |||
|
|
92ee225dd1 | ||
|
|
c0223176f2 | ||
|
|
80c593e6f0 | ||
|
|
b2807c5a39 | ||
|
|
e00763f0da | ||
|
|
b040dd8c1c | ||
|
|
7507f81c34 | ||
|
|
b6bcc7dadc | ||
|
|
2627a7ac68 | ||
|
|
d164630200 | ||
|
|
6c00f6bd7d | ||
| f51e8bbfc5 |
BIN
com.twin.app.shoptime/assets/images/bg/nbcu_new.png
Normal file
BIN
com.twin.app.shoptime/assets/images/bg/nbcu_new.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.6 MiB |
126
com.twin.app.shoptime/log_keys.txt
Normal file
126
com.twin.app.shoptime/log_keys.txt
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# 로그 시스템에서 사용되는 모든 JSON 키 목록
|
||||||
|
# 추출일: 2025-01-09
|
||||||
|
|
||||||
|
## 공통 키 (Common Keys)
|
||||||
|
- entryMenu - 진입 메뉴
|
||||||
|
- nowMenu - 현재 메뉴
|
||||||
|
- logTpNo - 로그 타입 번호
|
||||||
|
- inDt - 진입 시간
|
||||||
|
- outDt - 진출 시간
|
||||||
|
|
||||||
|
## 사용자/파트너 정보 (User/Partner Info)
|
||||||
|
- patncNm - 파트너 이름
|
||||||
|
- patnrId - 파트너 아이디
|
||||||
|
- usrNo - 사용자 번호
|
||||||
|
- lginTpNm - 로그인 타입 네임
|
||||||
|
- mbrNo - 멤버 번호
|
||||||
|
|
||||||
|
## 상품 정보 (Product Info)
|
||||||
|
- prdtId - 상품 ID
|
||||||
|
- prdtNm - 상품 이름
|
||||||
|
- befPrice - 이전 가격
|
||||||
|
- lastPrice - 최종 가격
|
||||||
|
- linkTpCd - 링크 타입 코드
|
||||||
|
- tsvFlag - TSV 여부
|
||||||
|
- cartTpSno - 카트 타입 시퀀스 번호
|
||||||
|
- qty - 수량
|
||||||
|
- prodId - 상품 ID (다른 표기)
|
||||||
|
- prodNm - 상품 이름 (다른 표기)
|
||||||
|
|
||||||
|
## 방송/콘텐츠 정보 (Show/Content Info)
|
||||||
|
- showId - 방송 ID
|
||||||
|
- showNm - 방송 이름
|
||||||
|
- vdoTpNm - 비디오 타입 네임
|
||||||
|
- cnttTpNm - 콘텐츠 타입 네임
|
||||||
|
- contId - 콘텐츠 ID
|
||||||
|
- contNm - 콘텐츠 이름
|
||||||
|
- banrNo - 배너 번호
|
||||||
|
- tmplCd - 템플릿 코드
|
||||||
|
- keywordList - 키워드 리스트
|
||||||
|
|
||||||
|
## 시청 정보 (Watch Info)
|
||||||
|
- watchStrtDt - 시청 시작 시간
|
||||||
|
- watchEndDt - 시청 종료 시간
|
||||||
|
|
||||||
|
## 카테고리 정보 (Category Info)
|
||||||
|
- lgCatCd - 대 카테고리 코드
|
||||||
|
- lgCatNm - 대 카테고리 이름
|
||||||
|
- catCd - 카테고리 코드
|
||||||
|
- catNm - 카테고리 이름
|
||||||
|
- catCdLv1 - 1단계 카테고리 코드
|
||||||
|
- catCdLv2 - 2단계 카테고리 코드
|
||||||
|
|
||||||
|
## 큐레이션/테마 정보 (Curation/Theme Info)
|
||||||
|
- curationId - 큐레이션 ID
|
||||||
|
- curationNm - 큐레이션 이름
|
||||||
|
- shelfId - 셸프 ID
|
||||||
|
- shelfNm - 셸프 이름
|
||||||
|
- expsOrd - 노출 순서
|
||||||
|
- sortTpNm - 정렬 타입 네임
|
||||||
|
|
||||||
|
## 브랜드/시리즈 정보 (Brand/Series Info)
|
||||||
|
- crtrId - 크리에이터 ID
|
||||||
|
- crtrNm - 크리에이터 이름
|
||||||
|
- srsId - 시리즈 ID
|
||||||
|
- srsNm - 시리즈 이름
|
||||||
|
|
||||||
|
## 검색 정보 (Search Info)
|
||||||
|
- keyword - 키워드
|
||||||
|
- inputFlag - 입력 플래그
|
||||||
|
- itemCnt - 상품 개수
|
||||||
|
- showCnt - 방송 개수
|
||||||
|
- themeCnt - 테마 개수
|
||||||
|
|
||||||
|
## 알림 정보 (Alarm Info)
|
||||||
|
- alarmDt - 알람 날짜
|
||||||
|
- alarmType - 알람 타입
|
||||||
|
- alertFlag - 알림 플래그
|
||||||
|
- clickFlag - 클릭 플래그
|
||||||
|
- cnt - 개수
|
||||||
|
- items - 아이템들
|
||||||
|
|
||||||
|
## 쿠폰 정보 (Coupon Info)
|
||||||
|
- cpnSno - 쿠폰 시퀀스 번호
|
||||||
|
- cpnTtl - 쿠폰 제목
|
||||||
|
|
||||||
|
## 결제 정보 (Payment Info)
|
||||||
|
- dcAftrPrc - 할인 후 가격
|
||||||
|
- dcBefPrc - 할인 전 가격
|
||||||
|
|
||||||
|
## 주문 정보 (Order Info)
|
||||||
|
- reqRsn - 요청 사유
|
||||||
|
- reqTpNm - 요청 타입 네임
|
||||||
|
|
||||||
|
## 마이페이지 정보 (MyPage Info)
|
||||||
|
- itemId - 아이템 ID
|
||||||
|
- title - 제목
|
||||||
|
- btnNm - 버튼 이름
|
||||||
|
|
||||||
|
## 모바일 쇼핑 정보 (Mobile Shopping Info)
|
||||||
|
- shopByMobileFlag - 모바일 쇼핑 플래그
|
||||||
|
- mbphNoFlag - 휴대폰 번호 플래그
|
||||||
|
- shopTpNm - 쇼핑 타입 네임
|
||||||
|
- trmsAgrFlag - 약관 동의 플래그
|
||||||
|
|
||||||
|
## DeepLink 정보
|
||||||
|
- deeplinkId - 딥링크 ID
|
||||||
|
- flag - 플래그
|
||||||
|
|
||||||
|
## 네트워크/시스템 정보 (Network/System Info)
|
||||||
|
- clientIP - 클라이언트 IP
|
||||||
|
- localMacAddress - 로컬 MAC 주소
|
||||||
|
- macAddress - MAC 주소
|
||||||
|
- macAddr - MAC 주소 (다른 표기)
|
||||||
|
- hstNm - 호스트 이름
|
||||||
|
- bgImgNo - 배경 이미지 번호
|
||||||
|
|
||||||
|
## 기타 키 (Other Keys)
|
||||||
|
- menuMovSno - 메뉴 이동 시퀀스 번호
|
||||||
|
- additionalInfo - 추가 정보
|
||||||
|
- fullVideolgCatCd - 풀영상 대 카테고리 코드
|
||||||
|
- totalLogFlag - 통합 로그 플래그
|
||||||
|
- secondLayerInfo - 세컨드 레이어 정보
|
||||||
|
- panelInfo - 패널 정보
|
||||||
|
- userNumber - 사용자 번호
|
||||||
|
- loginUserData - 로그인 사용자 데이터
|
||||||
|
- appStatus - 앱 상태
|
||||||
150
com.twin.app.shoptime/shopByShow.response.json
Normal file
150
com.twin.app.shoptime/shopByShow.response.json
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
{
|
||||||
|
"retCode": 0,
|
||||||
|
"retMsg": "Success",
|
||||||
|
"data": {
|
||||||
|
"brandShopByShowContsList": [
|
||||||
|
{
|
||||||
|
"patncNm": "Peacock | Shop The Moment",
|
||||||
|
"patnrId": 21,
|
||||||
|
"contsNm": "Below Deck Med",
|
||||||
|
"contsId": "SHBD12345",
|
||||||
|
"contsExpsOrd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"patncNm": "Peacock | Shop The Moment",
|
||||||
|
"patnrId": 21,
|
||||||
|
"contsNm": "Top Chef",
|
||||||
|
"contsId": "SHTC12345",
|
||||||
|
"contsExpsOrd": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"brandShopByShowContsInfo": {
|
||||||
|
"contsId": "SHBD12345",
|
||||||
|
"contsNm": "Below Deck Med",
|
||||||
|
"patnrId": "21",
|
||||||
|
"patncNm": "Peacock | Shop The Moment",
|
||||||
|
"brandShopByShowClctInfos": [
|
||||||
|
{
|
||||||
|
"clctId": "mercury-below_deck_merch",
|
||||||
|
"clctNm": "Below Deck Merch",
|
||||||
|
"clctImgUrl": "https://nonprod-commerce.nbcuni.com/uat/content-manager-assets/nbcu-comcast/GdLF-BeUT1-Below Deck CollectionAsset 1.png",
|
||||||
|
"clctExpsOrd": "1",
|
||||||
|
"brandProductInfos": [
|
||||||
|
{
|
||||||
|
"prdtId": "8ad864e8-dc12-4f01-9f68-717ad115fd06",
|
||||||
|
"prdtNm": "Alarm clock",
|
||||||
|
"revwGrd": null,
|
||||||
|
"prdtImgUrl": "https://images.cdn.us-central1.gcp.commercetools.com/eba1c230-c331-4b91-8952-38967e532e65/d2e3f3703a9c4e94b653-dcEJtWjC.jpeg",
|
||||||
|
"priceInfo": "$ 70.00|$ 70.00|N||||",
|
||||||
|
"freeShippingFlag": "N",
|
||||||
|
"soldoutFlag": "Y",
|
||||||
|
"offerInfo": null,
|
||||||
|
"lgCatCd": null,
|
||||||
|
"lgCatNm": null,
|
||||||
|
"brndNm": "Cup-a-Bug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"prdtId": "02b81061-e59c-47fd-b3ff-e3c743d17148",
|
||||||
|
"prdtNm": "Single Delay",
|
||||||
|
"revwGrd": null,
|
||||||
|
"prdtImgUrl": "https://8bf2164a2f18e0674bc4-c19fb008b43eea897ccae6fb0e59b195.ssl.cf1.rackcdn.com/022511005a3946a2b8e1-MKskNz4E.jpeg",
|
||||||
|
"priceInfo": "$ 123.00|$ 123.00|N||||",
|
||||||
|
"freeShippingFlag": "N",
|
||||||
|
"soldoutFlag": "N",
|
||||||
|
"offerInfo": null,
|
||||||
|
"lgCatCd": null,
|
||||||
|
"lgCatNm": null,
|
||||||
|
"brndNm": "Required for sponsored collection"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"prdtId": "3058cdf6-e1b7-4bc9-912f-1ef29e70b7c6",
|
||||||
|
"prdtNm": "Women’s Casual Long Sleeve Half Zip Pullover",
|
||||||
|
"revwGrd": null,
|
||||||
|
"prdtImgUrl": "https://images.cdn.us-central1.gcp.commercetools.com/eba1c230-c331-4b91-8952-38967e532e65/2800f90b251540baa940-Az4jltNx.jpeg",
|
||||||
|
"priceInfo": "$ 58.00|$ 58.00|N||||",
|
||||||
|
"freeShippingFlag": "N",
|
||||||
|
"soldoutFlag": "N",
|
||||||
|
"offerInfo": null,
|
||||||
|
"lgCatCd": null,
|
||||||
|
"lgCatNm": null,
|
||||||
|
"brndNm": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"prdtId": "7716e71a-4d22-415e-943a-89739ac9b685",
|
||||||
|
"prdtNm": "IIII",
|
||||||
|
"revwGrd": null,
|
||||||
|
"prdtImgUrl": "https://images.cdn.us-central1.gcp.commercetools.com/eba1c230-c331-4b91-8952-38967e532e65/d9b7e7e8aa9b4051a2ce-Fm2Tq6SG.jpeg",
|
||||||
|
"priceInfo": "$ 2.00|$ 2.00|N||||",
|
||||||
|
"freeShippingFlag": "N",
|
||||||
|
"soldoutFlag": "N",
|
||||||
|
"offerInfo": null,
|
||||||
|
"lgCatCd": null,
|
||||||
|
"lgCatNm": null,
|
||||||
|
"brndNm": "chair"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clctId": "mercury-below_deck_garden",
|
||||||
|
"clctNm": "Below Deck Garden",
|
||||||
|
"clctImgUrl": "https://nonprod-commerce.nbcuni.com/uat/content-manager-assets/nbcu-comcast/-eTpg2tMOT-Below Deck CollectionAsset 3.png",
|
||||||
|
"clctExpsOrd": "2",
|
||||||
|
"brandProductInfos": [
|
||||||
|
{
|
||||||
|
"prdtId": "399d8a6c-773f-49df-93e8-44697a4248ef",
|
||||||
|
"prdtNm": "AiryWeight Eucalyptus Sheet Set v2",
|
||||||
|
"revwGrd": null,
|
||||||
|
"prdtImgUrl": "https://images.cdn.us-central1.gcp.commercetools.com/eba1c230-c331-4b91-8952-38967e532e65/d80b6a8edc03406badff-5kaDvtaq.jpeg",
|
||||||
|
"priceInfo": "$ 185.00|$ 185.00|N||||",
|
||||||
|
"freeShippingFlag": "N",
|
||||||
|
"soldoutFlag": "Y",
|
||||||
|
"offerInfo": null,
|
||||||
|
"lgCatCd": null,
|
||||||
|
"lgCatNm": null,
|
||||||
|
"brndNm": "BB SUP 9.23 - SK"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"prdtId": "4e495aa2-2b10-4120-86bd-bcc9f3843d32",
|
||||||
|
"prdtNm": "Cooling Towel",
|
||||||
|
"revwGrd": null,
|
||||||
|
"prdtImgUrl": "https://images.cdn.us-central1.gcp.commercetools.com/eba1c230-c331-4b91-8952-38967e532e65/aec0a6c4770b437ba20a-nOXvmxoD.jpeg",
|
||||||
|
"priceInfo": "$ 20.00|$ 20.00|N||||",
|
||||||
|
"freeShippingFlag": "N",
|
||||||
|
"soldoutFlag": "Y",
|
||||||
|
"offerInfo": null,
|
||||||
|
"lgCatCd": null,
|
||||||
|
"lgCatNm": null,
|
||||||
|
"brndNm": "Posh Pickler"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"prdtId": "07c2ca90-c730-4bed-8f5d-25a815c2de11",
|
||||||
|
"prdtNm": "Towel",
|
||||||
|
"revwGrd": null,
|
||||||
|
"prdtImgUrl": "https://images.cdn.us-central1.gcp.commercetools.com/eba1c230-c331-4b91-8952-38967e532e65/3cf8c0b692724bea96ec-3jTpXCmc.jpeg",
|
||||||
|
"priceInfo": "$ 50.00|$ 50.00|N||||",
|
||||||
|
"freeShippingFlag": "N",
|
||||||
|
"soldoutFlag": "N",
|
||||||
|
"offerInfo": null,
|
||||||
|
"lgCatCd": null,
|
||||||
|
"lgCatNm": null,
|
||||||
|
"brndNm": "BB SUP 9.23 - SK"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"prdtId": "cc85a17a-e6b5-4d0e-ad3b-67194d5aeafb",
|
||||||
|
"prdtNm": "STAINLESS STEEL MEASURING CUPS",
|
||||||
|
"revwGrd": null,
|
||||||
|
"prdtImgUrl": "https://8bf2164a2f18e0674bc4-c19fb008b43eea897ccae6fb0e59b195.ssl.cf1.rackcdn.com/9dc9460fd125488582e4-5GTzqS_6.jpeg",
|
||||||
|
"priceInfo": "$ 8.00|$ 8.00|N||||",
|
||||||
|
"freeShippingFlag": "N",
|
||||||
|
"soldoutFlag": "N",
|
||||||
|
"offerInfo": null,
|
||||||
|
"lgCatCd": null,
|
||||||
|
"lgCatNm": null,
|
||||||
|
"brndNm": "TARGET"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import Spotlight from '@enact/spotlight';
|
|||||||
import { Job } from '@enact/core/util';
|
import { Job } from '@enact/core/util';
|
||||||
import platform from '@enact/core/platform';
|
import platform from '@enact/core/platform';
|
||||||
import { ThemeDecorator } from '@enact/sandstone/ThemeDecorator';
|
import { ThemeDecorator } from '@enact/sandstone/ThemeDecorator';
|
||||||
|
import GlobalPopup from '../components/GlobalPopup/GlobalPopup';
|
||||||
|
|
||||||
// import "../../../assets/fontello/css/fontello.css";
|
// import "../../../assets/fontello/css/fontello.css";
|
||||||
|
|
||||||
@@ -406,8 +407,6 @@ Spotlight.silentlyFocus = function (...args) {
|
|||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Spotlight Focus 추적 로그 [251115]
|
// Spotlight Focus 추적 로그 [251115]
|
||||||
// DOM 이벤트 리스너로 대체
|
// DOM 이벤트 리스너로 대체
|
||||||
|
|
||||||
@@ -426,7 +425,7 @@ Spotlight.silentlyFocus = function (...args) {
|
|||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
|
|
||||||
function AppBase(_props /* eslint-disable-line no-unused-vars */) {
|
function AppBase(props) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const httpHeader = useSelector((state) => state.common.httpHeader);
|
const httpHeader = useSelector((state) => state.common.httpHeader);
|
||||||
const httpHeaderRef = useRef(httpHeader);
|
const httpHeaderRef = useRef(httpHeader);
|
||||||
@@ -628,7 +627,7 @@ function AppBase(_props /* eslint-disable-line no-unused-vars */) {
|
|||||||
clearLaunchParams();
|
clearLaunchParams();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[dispatch],
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleRelaunchEvent = useCallback(() => {
|
const handleRelaunchEvent = useCallback(() => {
|
||||||
@@ -682,7 +681,7 @@ function AppBase(_props /* eslint-disable-line no-unused-vars */) {
|
|||||||
if (typeof window === 'object' && window.PalmSystem) {
|
if (typeof window === 'object' && window.PalmSystem) {
|
||||||
window.PalmSystem.activate();
|
window.PalmSystem.activate();
|
||||||
}
|
}
|
||||||
}, [initService, introTermsAgreeRef]);
|
}, [initService, introTermsAgreeRef, dispatch]);
|
||||||
|
|
||||||
const visibilityChanged = useCallback(() => {
|
const visibilityChanged = useCallback(() => {
|
||||||
// console.log('document is hidden', document.hidden);
|
// console.log('document is hidden', document.hidden);
|
||||||
@@ -726,7 +725,7 @@ function AppBase(_props /* eslint-disable-line no-unused-vars */) {
|
|||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const keyDownEvent = (_event /* eslint-disable-line no-unused-vars */) => {
|
const keyDownEvent = (event) => {
|
||||||
dispatch(changeAppStatus({ cursorVisible: false }));
|
dispatch(changeAppStatus({ cursorVisible: false }));
|
||||||
Spotlight.setPointerMode(false);
|
Spotlight.setPointerMode(false);
|
||||||
};
|
};
|
||||||
@@ -735,7 +734,7 @@ function AppBase(_props /* eslint-disable-line no-unused-vars */) {
|
|||||||
let lastMoveTime = 0;
|
let lastMoveTime = 0;
|
||||||
const THROTTLE_MS = 100;
|
const THROTTLE_MS = 100;
|
||||||
|
|
||||||
const mouseMoveEvent = (_event /* eslint-disable-line no-unused-vars */) => {
|
const mouseMoveEvent = (event) => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (now - lastMoveTime < THROTTLE_MS) {
|
if (now - lastMoveTime < THROTTLE_MS) {
|
||||||
// throttle 기간 내에는 hideCursor만 재시작
|
// throttle 기간 내에는 hideCursor만 재시작
|
||||||
@@ -788,7 +787,9 @@ function AppBase(_props /* eslint-disable-line no-unused-vars */) {
|
|||||||
let userDataChanged = false;
|
let userDataChanged = false;
|
||||||
if (JSON.stringify(loginUserDataRef.current) !== JSON.stringify(loginUserData)) {
|
if (JSON.stringify(loginUserDataRef.current) !== JSON.stringify(loginUserData)) {
|
||||||
userDataChanged = true;
|
userDataChanged = true;
|
||||||
} else if (userDataChanged || httpHeaderRef.current === null) {
|
}
|
||||||
|
if (!httpHeader || !deviceId) {
|
||||||
|
} else if (userDataChanged || httpHeaderRef.current === null) {
|
||||||
//계정정보 변경시 또는 초기 로딩시
|
//계정정보 변경시 또는 초기 로딩시
|
||||||
if (!httpHeader) {
|
if (!httpHeader) {
|
||||||
dispatch(
|
dispatch(
|
||||||
@@ -888,7 +889,7 @@ function AppBase(_props /* eslint-disable-line no-unused-vars */) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
{/* <GlobalPopup /> */}
|
<GlobalPopup />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ export const handleDeepLink = (contentTarget) => (dispatch, _getState) => {
|
|||||||
patnrId: patnrId,
|
patnrId: patnrId,
|
||||||
chanId: chanId,
|
chanId: chanId,
|
||||||
shptmBanrTpNm: "LIVE",
|
shptmBanrTpNm: "LIVE",
|
||||||
|
modal: false, // DeepLink 진입 시 fullscreen으로 재생
|
||||||
// expsOrd: expsOrd,
|
// expsOrd: expsOrd,
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
@@ -101,6 +102,7 @@ export const handleDeepLink = (contentTarget) => (dispatch, _getState) => {
|
|||||||
patnrId: patnrId,
|
patnrId: patnrId,
|
||||||
showId: showId,
|
showId: showId,
|
||||||
shptmBanrTpNm: "VOD",
|
shptmBanrTpNm: "VOD",
|
||||||
|
modal: false, // DeepLink 진입 시 fullscreen으로 재생
|
||||||
// expsOrd: expsOrd,
|
// expsOrd: expsOrd,
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
@@ -274,6 +276,18 @@ export const handleDeepLink = (contentTarget) => (dispatch, _getState) => {
|
|||||||
const action =
|
const action =
|
||||||
panelName === panel_names.HOME_PANEL ? updateHomeInfo : pushPanel;
|
panelName === panel_names.HOME_PANEL ? updateHomeInfo : pushPanel;
|
||||||
|
|
||||||
|
// 🔽 LS(Live Show) 또는 VS(VOD Show)인 경우 DeepLink 진입 플래그 설정
|
||||||
|
if ((type === 'LS' || type === 'VS') && action === pushPanel) {
|
||||||
|
dispatch(
|
||||||
|
updateHomeInfo({
|
||||||
|
name: panel_names.HOME_PANEL,
|
||||||
|
panelInfo: {
|
||||||
|
isDeepLinkEntry: true, // DeepLink PlayerPanel 진입 플래그
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
action({
|
action({
|
||||||
name: panelName,
|
name: panelName,
|
||||||
|
|||||||
@@ -130,11 +130,16 @@ export const types = {
|
|||||||
GET_BRAND_CREATORS_INFO: 'GET_BRAND_CREATORS_INFO',
|
GET_BRAND_CREATORS_INFO: 'GET_BRAND_CREATORS_INFO',
|
||||||
GET_BRAND_SHOWROOM: 'GET_BRAND_SHOWROOM',
|
GET_BRAND_SHOWROOM: 'GET_BRAND_SHOWROOM',
|
||||||
GET_BRAND_RECENTLY_AIRED: 'GET_BRAND_RECENTLY_AIRED',
|
GET_BRAND_RECENTLY_AIRED: 'GET_BRAND_RECENTLY_AIRED',
|
||||||
|
GET_BRAND_SHOP_BY_SHOW: 'GET_BRAND_SHOP_BY_SHOW',
|
||||||
|
GET_BRAND_TOP_BANNER: 'GET_BRAND_TOP_BANNER',
|
||||||
SET_BRAND_LIVE_CHANNEL_UPCOMING: 'SET_BRAND_LIVE_CHANNEL_UPCOMING',
|
SET_BRAND_LIVE_CHANNEL_UPCOMING: 'SET_BRAND_LIVE_CHANNEL_UPCOMING',
|
||||||
SET_BRAND_CHAN_INFO: 'SET_BRAND_CHAN_INFO',
|
SET_BRAND_CHAN_INFO: 'SET_BRAND_CHAN_INFO',
|
||||||
RESET_BRAND_STATE: 'RESET_BRAND_STATE',
|
RESET_BRAND_STATE: 'RESET_BRAND_STATE',
|
||||||
RESET_BRAND_STATE_EXCEPT_BRAND_INFO: 'RESET_BRAND_STATE_EXCEPT_BRAND_INFO',
|
RESET_BRAND_STATE_EXCEPT_BRAND_INFO: 'RESET_BRAND_STATE_EXCEPT_BRAND_INFO',
|
||||||
RESET_BRAND_LAYOUT_INFO: 'RESET_BRAND_LAYOUT_INFO',
|
RESET_BRAND_LAYOUT_INFO: 'RESET_BRAND_LAYOUT_INFO',
|
||||||
|
// 🆕 [251210] patnrId=21 카테고리 그룹 데이터 관리
|
||||||
|
SET_BRAND_SHOP_BY_SHOW_CATEGORY_GROUPS: 'SET_BRAND_SHOP_BY_SHOW_CATEGORY_GROUPS',
|
||||||
|
RESET_BRAND_SHOP_BY_SHOW_CATEGORY_GROUPS: 'RESET_BRAND_SHOP_BY_SHOW_CATEGORY_GROUPS',
|
||||||
|
|
||||||
// main actions
|
// main actions
|
||||||
GET_SUB_CATEGORY: 'GET_SUB_CATEGORY',
|
GET_SUB_CATEGORY: 'GET_SUB_CATEGORY',
|
||||||
|
|||||||
@@ -37,10 +37,12 @@ export const getBrandList = () => (dispatch, getState) => {
|
|||||||
export const getBrandLayoutInfo = (props) => (dispatch, getState) => {
|
export const getBrandLayoutInfo = (props) => (dispatch, getState) => {
|
||||||
const { patnrId } = props;
|
const { patnrId } = props;
|
||||||
|
|
||||||
|
// console.log("[getBrandLayoutInfo] Called - patnrId:", patnrId);
|
||||||
|
|
||||||
dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } }));
|
dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } }));
|
||||||
|
|
||||||
const onSuccess = (response) => {
|
const onSuccess = (response) => {
|
||||||
// dlog("getBrandLayoutInfo onSuccess ", response.data);
|
// console.log("[getBrandLayoutInfo] onSuccess - patnrId:", patnrId, "data:", response.data.data);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.GET_BRAND_LAYOUT_INFO,
|
type: types.GET_BRAND_LAYOUT_INFO,
|
||||||
@@ -53,6 +55,7 @@ export const getBrandLayoutInfo = (props) => (dispatch, getState) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onFail = (error) => {
|
const onFail = (error) => {
|
||||||
|
// console.log("[getBrandLayoutInfo] onFail - patnrId:", patnrId, "error:", error);
|
||||||
derror('getBrandLayoutInfo onFail ', error);
|
derror('getBrandLayoutInfo onFail ', error);
|
||||||
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
|
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
|
||||||
};
|
};
|
||||||
@@ -336,10 +339,15 @@ export const getBrandCategoryProductInfo = (props) => (dispatch, getState) => {
|
|||||||
export const getBrandBestSeller = (props) => (dispatch, getState) => {
|
export const getBrandBestSeller = (props) => (dispatch, getState) => {
|
||||||
const { patnrId } = props;
|
const { patnrId } = props;
|
||||||
|
|
||||||
|
// console.log("[getBrandBestSeller] Called - patnrId:", patnrId);
|
||||||
|
|
||||||
dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } }));
|
dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } }));
|
||||||
|
|
||||||
const onSuccess = (response) => {
|
const onSuccess = (response) => {
|
||||||
// dlog("getBrandBestSeller onSuccess ", response.data);
|
// console.log("[getBrandBestSeller] onSuccess - patnrId:", patnrId);
|
||||||
|
// console.log("[getBrandBestSeller] Full response:", response.data.data);
|
||||||
|
// console.log("[getBrandBestSeller] brandBestSellerInfo:", response.data.data.brandBestSellerInfo);
|
||||||
|
// console.log("[getBrandBestSeller] brandBestSellerTitle in response:", response.data.data.brandBestSellerTitle);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: types.GET_BRAND_BEST_SELLER,
|
type: types.GET_BRAND_BEST_SELLER,
|
||||||
@@ -352,6 +360,7 @@ export const getBrandBestSeller = (props) => (dispatch, getState) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onFail = (error) => {
|
const onFail = (error) => {
|
||||||
|
// console.log("[getBrandBestSeller] onFail - patnrId:", patnrId, "error:", error);
|
||||||
derror('getBrandBestSeller onFail ', error);
|
derror('getBrandBestSeller onFail ', error);
|
||||||
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
|
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
|
||||||
};
|
};
|
||||||
@@ -386,6 +395,79 @@ export const getBrandShowroom = (props) => (dispatch, getState) => {
|
|||||||
TAxios(dispatch, getState, 'get', URLS.GET_BRAND_SHOWROOM, { patnrId }, {}, onSuccess, onFail);
|
TAxios(dispatch, getState, 'get', URLS.GET_BRAND_SHOWROOM, { patnrId }, {}, onSuccess, onFail);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Featured Brands SHOP BY SHOW 정보 조회 IF-LGSP-376
|
||||||
|
export const getBrandShopByShow = (props) => (dispatch, getState) => {
|
||||||
|
const { patnrId, contsId } = props;
|
||||||
|
|
||||||
|
// console.log("[getBrandShopByShow] Called - patnrId:", patnrId, "contsId:", contsId);
|
||||||
|
|
||||||
|
dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } }));
|
||||||
|
|
||||||
|
const onSuccess = (response) => {
|
||||||
|
// console.log("[getBrandShopByShow] onSuccess - patnrId:", patnrId, "data:", response.data.data);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: types.GET_BRAND_SHOP_BY_SHOW,
|
||||||
|
payload: {
|
||||||
|
data: response.data.data,
|
||||||
|
patnrId,
|
||||||
|
contsId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFail = (error) => {
|
||||||
|
// console.log("[getBrandShopByShow] onFail - patnrId:", patnrId, "error:", error);
|
||||||
|
derror('getBrandShopByShow onFail ', error);
|
||||||
|
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
|
||||||
|
};
|
||||||
|
|
||||||
|
// patnrId: 필수, contsId: 선택
|
||||||
|
const params = contsId ? { patnrId, contsId } : { patnrId };
|
||||||
|
|
||||||
|
TAxios(dispatch, getState, 'get', URLS.GET_BRAND_SHOP_BY_SHOW, params, {}, onSuccess, onFail);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Featured Brands Top Banner 정보 조회 IF-LGSP-377 (NBCU 전용)
|
||||||
|
export const getBrandTopBanner = (props) => (dispatch, getState) => {
|
||||||
|
const { patnrId } = props;
|
||||||
|
|
||||||
|
// console.log("[BRAND-TOP-BANNER-API] Called - patnrId:", patnrId);
|
||||||
|
|
||||||
|
// NBCU(patnrId: 21)가 아니면 호출하지 않음
|
||||||
|
if (patnrId !== 21 && patnrId !== "21") {
|
||||||
|
console.log("[BRAND-TOP-BANNER-API] Skip - patnrId is not 21 (NBCU), patnrId:", patnrId, "(type:", typeof patnrId, ")");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } }));
|
||||||
|
|
||||||
|
const onSuccess = (response) => {
|
||||||
|
// console.log("[BRAND-TOP-BANNER-API] onSuccess - patnrId:", patnrId);
|
||||||
|
// console.log("[BRAND-TOP-BANNER-API] Full response data:", response.data.data);
|
||||||
|
// console.log("[BRAND-TOP-BANNER-API] brandTopBannerInfo:", response.data.data.brandTopBannerInfo);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: types.GET_BRAND_TOP_BANNER,
|
||||||
|
payload: {
|
||||||
|
data: response.data.data,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFail = (error) => {
|
||||||
|
// console.log("[BRAND-TOP-BANNER-API] onFail - patnrId:", patnrId, "error:", error);
|
||||||
|
derror('getBrandTopBanner onFail ', error);
|
||||||
|
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
|
||||||
|
};
|
||||||
|
|
||||||
|
TAxios(dispatch, getState, 'get', URLS.GET_BRAND_TOP_BANNER, { patnrId }, {}, onSuccess, onFail);
|
||||||
|
};
|
||||||
|
|
||||||
// Featured Brands Recently Aired 조회 IF-LGSP-373
|
// Featured Brands Recently Aired 조회 IF-LGSP-373
|
||||||
export const getBrandRecentlyAired = (props) => (dispatch, getState) => {
|
export const getBrandRecentlyAired = (props) => (dispatch, getState) => {
|
||||||
const { patnrId } = props;
|
const { patnrId } = props;
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export const alertToast = (payload) => (dispatch) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSystemSettings = () => (dispatch) => {
|
export const getSystemSettings = () => (dispatch, getState) => {
|
||||||
dlog('getSystemSettings ');
|
dlog('getSystemSettings ');
|
||||||
lunaSend.getSystemSettings(
|
lunaSend.getSystemSettings(
|
||||||
{ category: 'caption', keys: ['captionEnable'] },
|
{ category: 'caption', keys: ['captionEnable'] },
|
||||||
@@ -146,7 +146,7 @@ export const getSystemSettings = () => (dispatch) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getHttpHeaderForServiceRequest = () => (dispatch, getState) => {
|
export const getHttpHeaderForServiceRequest = (onComplete) => (dispatch, getState) => {
|
||||||
dlog('getHttpHeaderForServiceRequest ');
|
dlog('getHttpHeaderForServiceRequest ');
|
||||||
const { serverType, ricCodeSetting, languageSetting } = getState().localSettings;
|
const { serverType, ricCodeSetting, languageSetting } = getState().localSettings;
|
||||||
lunaSend.getHttpHeaderForServiceRequest({
|
lunaSend.getHttpHeaderForServiceRequest({
|
||||||
@@ -285,7 +285,7 @@ export const getHttpHeaderForServiceRequest = () => (dispatch, getState) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDeviceId = (onComplete) => (dispatch) => {
|
export const getDeviceId = (onComplete) => (dispatch, getState) => {
|
||||||
lunaSend.getDeviceId(
|
lunaSend.getDeviceId(
|
||||||
{ idType: ['LGUDID'] },
|
{ idType: ['LGUDID'] },
|
||||||
{
|
{
|
||||||
@@ -463,7 +463,7 @@ export const setFocus = (spotlightId) => ({
|
|||||||
payload: spotlightId,
|
payload: spotlightId,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const focusElement = (spotlightId) => (dispatch) => {
|
export const focusElement = (spotlightId) => (dispatch, getState) => {
|
||||||
dispatch(setFocus(spotlightId));
|
dispatch(setFocus(spotlightId));
|
||||||
|
|
||||||
if (typeof window === 'object') {
|
if (typeof window === 'object') {
|
||||||
@@ -485,7 +485,7 @@ export const cancelFocusElement = () => () => {
|
|||||||
let broadcastTimer = null;
|
let broadcastTimer = null;
|
||||||
export const sendBroadCast =
|
export const sendBroadCast =
|
||||||
({ type, moreInfo }) =>
|
({ type, moreInfo }) =>
|
||||||
(dispatch) => {
|
(dispatch, getState) => {
|
||||||
clearTimeout(broadcastTimer);
|
clearTimeout(broadcastTimer);
|
||||||
dispatch(changeBroadcastEvent({ type, moreInfo }));
|
dispatch(changeBroadcastEvent({ type, moreInfo }));
|
||||||
broadcastTimer = setTimeout(() => {
|
broadcastTimer = setTimeout(() => {
|
||||||
@@ -542,7 +542,7 @@ export const addReservation = (data) => (dispatch) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteReservationCallback = (scheduleIdList) => () => {
|
export const deleteReservationCallback = (scheduleIdList) => (dispatch) => {
|
||||||
lunaSend.deleteReservationCallback(scheduleIdList, {
|
lunaSend.deleteReservationCallback(scheduleIdList, {
|
||||||
onSuccess: (res) => {
|
onSuccess: (res) => {
|
||||||
// dispatch(alertToast("success" + JSON.stringify(res)));
|
// dispatch(alertToast("success" + JSON.stringify(res)));
|
||||||
@@ -680,7 +680,7 @@ let updateNetworkStateJob = new Job((dispatch, connected) => {
|
|||||||
dispatch(changeAppStatus({ isInternetConnected: connected }));
|
dispatch(changeAppStatus({ isInternetConnected: connected }));
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getConnectionStatus = () => (dispatch) => {
|
export const getConnectionStatus = () => (dispatch, getState) => {
|
||||||
lunaSend.getConnectionStatus({
|
lunaSend.getConnectionStatus({
|
||||||
onSuccess: (res) => {
|
onSuccess: (res) => {
|
||||||
dlog('lunasend getConnectionStatus', res);
|
dlog('lunasend getConnectionStatus', res);
|
||||||
@@ -709,7 +709,7 @@ export const getConnectionStatus = () => (dispatch) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// macAddress
|
// macAddress
|
||||||
export const getConnectionInfo = () => (dispatch) => {
|
export const getConnectionInfo = () => (dispatch, getState) => {
|
||||||
lunaSend.getConnectionInfo({
|
lunaSend.getConnectionInfo({
|
||||||
onSuccess: (res) => {
|
onSuccess: (res) => {
|
||||||
dlog('lunasend getConnectionStatus', res);
|
dlog('lunasend getConnectionStatus', res);
|
||||||
@@ -731,7 +731,7 @@ export const getConnectionInfo = () => (dispatch) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const disableNotification = () => {
|
export const disableNotification = () => (dispatch, getState) => {
|
||||||
lunaSend.disableNotification({
|
lunaSend.disableNotification({
|
||||||
onSuccess: (res) => {
|
onSuccess: (res) => {
|
||||||
dlog('lunasend disable notification success', res);
|
dlog('lunasend disable notification success', res);
|
||||||
@@ -745,7 +745,7 @@ export const disableNotification = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const enableNotification = () => {
|
export const enableNotification = () => (dispatch, getState) => {
|
||||||
lunaSend.enableNotification({
|
lunaSend.enableNotification({
|
||||||
onSuccess: (res) => {
|
onSuccess: (res) => {
|
||||||
dlog('lunasend enable notification success', res);
|
dlog('lunasend enable notification success', res);
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { types } from './actionTypes';
|
|||||||
import { changeAppStatus, getTermsAgreeYn } from './commonActions';
|
import { changeAppStatus, getTermsAgreeYn } from './commonActions';
|
||||||
import { collectBannerPositions } from '../utils/domUtils';
|
import { collectBannerPositions } from '../utils/domUtils';
|
||||||
import { createDebugHelpers } from '../utils/debug';
|
import { createDebugHelpers } from '../utils/debug';
|
||||||
|
import { setHidePopup, setShowPopup } from './commonActions';
|
||||||
|
import { ACTIVE_POPUP } from '../utils/Config';
|
||||||
|
|
||||||
// 디버그 헬퍼 설정
|
// 디버그 헬퍼 설정
|
||||||
const DEBUG_MODE = false;
|
const DEBUG_MODE = false;
|
||||||
@@ -75,6 +77,38 @@ export const getHomeTerms = (props) => (dispatch, getState) => {
|
|||||||
|
|
||||||
const onFail = (error) => {
|
const onFail = (error) => {
|
||||||
derror('getHomeTerms onFail ', error);
|
derror('getHomeTerms onFail ', error);
|
||||||
|
|
||||||
|
// TODO: 임시 디버그용 팝업 (재현 후 제거하세요)
|
||||||
|
const retCode = error?.data?.retCode ?? error?.retCode ?? 'unknown';
|
||||||
|
dispatch(
|
||||||
|
setShowPopup(ACTIVE_POPUP.toast, {
|
||||||
|
button1Text: `getHomeTerms onFail retCode=${retCode}`,
|
||||||
|
button2Text: 'OK',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setTimeout(() => dispatch(setHidePopup()), 1500);
|
||||||
|
|
||||||
|
// 약관 미동의(retCode 501)로 GET_HOME_TERMS가 실패하면
|
||||||
|
// introTermsAgree를 명시적으로 false로 내려 앱이 IntroPanel을 띄우도록 한다.
|
||||||
|
if (retCode === 501) {
|
||||||
|
dispatch({
|
||||||
|
type: types.GET_TERMS_AGREE_YN_SUCCESS,
|
||||||
|
payload: {
|
||||||
|
privacyTerms: 'N',
|
||||||
|
serviceTerms: 'N',
|
||||||
|
purchaseTerms: 'N',
|
||||||
|
paymentTerms: 'N',
|
||||||
|
optionalTerms: 'N',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 실패 시 로딩 패널을 반드시 내려 백화 상태를 방지
|
||||||
|
dispatch(
|
||||||
|
changeAppStatus({
|
||||||
|
showLoadingPanel: { show: false },
|
||||||
|
})
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
TAxios(
|
TAxios(
|
||||||
|
|||||||
@@ -99,17 +99,25 @@ export const finishMediaPreview = () => (dispatch, getState) => {
|
|||||||
export const finishModalMediaForce = () => (dispatch, getState) => {
|
export const finishModalMediaForce = () => (dispatch, getState) => {
|
||||||
const panels = getState().panels.panels;
|
const panels = getState().panels.panels;
|
||||||
|
|
||||||
|
// console.log('[🟡UNIQUE_DETAIL_CLEANUP🟡] finishModalMediaForce called', {
|
||||||
|
// panelCount: panels.length,
|
||||||
|
// panelNames: panels.map((p) => p.name),
|
||||||
|
// });
|
||||||
|
|
||||||
const hasProductVideoPanel = panels.some(
|
const hasProductVideoPanel = panels.some(
|
||||||
(panel) =>
|
(panel) =>
|
||||||
panel.name === panel_names.MEDIA_PANEL &&
|
panel.name === panel_names.MEDIA_PANEL &&
|
||||||
(panel.panelInfo?.modal || panel.panelInfo?.modalContainerId === 'product-video-player')
|
(panel.panelInfo?.modal || panel.panelInfo?.modalContainerId === 'product-video-player')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// console.log('[🟡UNIQUE_DETAIL_CLEANUP🟡] hasProductVideoPanel:', hasProductVideoPanel);
|
||||||
|
|
||||||
if (hasProductVideoPanel) {
|
if (hasProductVideoPanel) {
|
||||||
if (startMediaFocusTimer) {
|
if (startMediaFocusTimer) {
|
||||||
clearTimeout(startMediaFocusTimer);
|
clearTimeout(startMediaFocusTimer);
|
||||||
startMediaFocusTimer = null;
|
startMediaFocusTimer = null;
|
||||||
}
|
}
|
||||||
|
// console.log('[🟡UNIQUE_DETAIL_CLEANUP🟡] Calling popPanel(panel_names.MEDIA_PANEL)');
|
||||||
dispatch(popPanel(panel_names.MEDIA_PANEL));
|
dispatch(popPanel(panel_names.MEDIA_PANEL));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -39,10 +39,27 @@ export const pushPanel = (panel, duplicatable = false) => ({
|
|||||||
duplicatable: duplicatable,
|
duplicatable: duplicatable,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const popPanel = (panelName) => ({
|
export const popPanel = (panelName) => {
|
||||||
type: types.POP_PANEL,
|
const stack = new Error().stack;
|
||||||
payload: panelName,
|
const stackLines = stack?.split('\n') || [];
|
||||||
});
|
|
||||||
|
// console.log('[💜UNIQUE_PANEL_STACK💜] popPanel action dispatcher - REMOVING PANEL:', {
|
||||||
|
// panelName,
|
||||||
|
// timestamp: Date.now(),
|
||||||
|
// fullStack: stackLines.slice(1, 6).map((line) => line.trim()),
|
||||||
|
// });
|
||||||
|
|
||||||
|
if (DEBUG_MODE) {
|
||||||
|
console.log('[💜UNIQUE_PANEL_STACK💜] popPanel action creator stack:', {
|
||||||
|
panelName,
|
||||||
|
caller: stackLines[2]?.trim(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: types.POP_PANEL,
|
||||||
|
payload: panelName,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const updatePanel = (panelInfo) => ({
|
export const updatePanel = (panelInfo) => ({
|
||||||
type: types.UPDATE_PANEL,
|
type: types.UPDATE_PANEL,
|
||||||
@@ -93,6 +110,11 @@ export const navigateToDetail = ({
|
|||||||
...additionalInfo,
|
...additionalInfo,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const state = getState();
|
||||||
|
const panels = state.panels.panels;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 선택적 파라미터들 추가
|
// 선택적 파라미터들 추가
|
||||||
if (curationId) panelInfo.curationId = curationId;
|
if (curationId) panelInfo.curationId = curationId;
|
||||||
if (nowShelf) panelInfo.nowShelf = nowShelf;
|
if (nowShelf) panelInfo.nowShelf = nowShelf;
|
||||||
@@ -176,8 +198,21 @@ export const navigateToDetail = ({
|
|||||||
|
|
||||||
const isCurrentBannerVideoPlaying = playerPanelInfo.panelInfo?.modal !== false;
|
const isCurrentBannerVideoPlaying = playerPanelInfo.panelInfo?.modal !== false;
|
||||||
|
|
||||||
|
console.log('[Detail-BG] 🎯 navigateToDetail - Checking HomeBanner video status:', {
|
||||||
|
playerPanelModalValue: playerPanelInfo.panelInfo?.modal,
|
||||||
|
isCurrentBannerVideoPlaying,
|
||||||
|
sourceMenu,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
// HomeBanner의 modal=true 비디오가 재생 중이면 정지
|
// HomeBanner의 modal=true 비디오가 재생 중이면 정지
|
||||||
if (isCurrentBannerVideoPlaying) {
|
if (isCurrentBannerVideoPlaying) {
|
||||||
|
console.log('[Detail-BG] 🎬 navigateToDetail - HomeBanner video is playing (modal !== false)', {
|
||||||
|
playerPanelModal: playerPanelInfo.panelInfo?.modal,
|
||||||
|
sourceMenu,
|
||||||
|
action: 'finishVideoPreview',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
// 🔽 비디오 상태 저장 후 정지
|
// 🔽 비디오 상태 저장 후 정지
|
||||||
const { finishVideoPreview } = require('./playActions');
|
const { finishVideoPreview } = require('./playActions');
|
||||||
|
|
||||||
@@ -204,11 +239,15 @@ export const navigateToDetail = ({
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// 비디오 상태 저장 후 정지 (로그는 개발 시 필요 시 주석 해제)
|
// 비디오 상태 저장 후 정지
|
||||||
|
|
||||||
dispatch(finishVideoPreview());
|
dispatch(finishVideoPreview());
|
||||||
} else {
|
} else {
|
||||||
// 비디오가 재생 중이 아니어도 HomePanel 상태 저장
|
// 비디오가 재생 중이 아니어도 HomePanel 상태 저장
|
||||||
|
console.log('[Detail-BG] ⏭️ navigateToDetail - HomeBanner video is NOT playing (modal === false or undefined)', {
|
||||||
|
playerPanelModal: playerPanelInfo.panelInfo?.modal,
|
||||||
|
sourceMenu,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
dispatch(
|
dispatch(
|
||||||
updatePanel({
|
updatePanel({
|
||||||
name: panel_names.HOME_PANEL,
|
name: panel_names.HOME_PANEL,
|
||||||
@@ -267,10 +306,34 @@ export const navigateToDetail = ({
|
|||||||
case SOURCE_MENUS.PLAYER_MEDIA: {
|
case SOURCE_MENUS.PLAYER_MEDIA: {
|
||||||
// PlayerPanel에서 온 경우
|
// PlayerPanel에서 온 경우
|
||||||
const { hidePlayerOverlays } = require('./videoPlayActions');
|
const { hidePlayerOverlays } = require('./videoPlayActions');
|
||||||
|
const statePanels = panels || getState().panels.panels;
|
||||||
|
const playerPanelEntry =
|
||||||
|
[...statePanels].reverse().find(
|
||||||
|
(p) => p.name === panel_names.PLAYER_PANEL || p.name === panel_names.PLAYER_PANEL_NEW
|
||||||
|
) || null;
|
||||||
|
|
||||||
// DetailPanel push 전에 VideoPlayer 오버레이 숨김
|
// DetailPanel push 전에 VideoPlayer 오버레이 숨김
|
||||||
dispatch(hidePlayerOverlays());
|
dispatch(hidePlayerOverlays());
|
||||||
|
|
||||||
|
// PlayerPanel이 modal=true라면 풀스크린 백그라운드로 전환 + lockModalFalse 설정 (Detail 동안 modal 복귀 방지)
|
||||||
|
if (playerPanelEntry) {
|
||||||
|
dispatch(
|
||||||
|
updatePanel({
|
||||||
|
name: playerPanelEntry.name,
|
||||||
|
panelInfo: {
|
||||||
|
...playerPanelEntry.panelInfo,
|
||||||
|
modal: false,
|
||||||
|
modalContainerId: undefined,
|
||||||
|
modalStyle: undefined,
|
||||||
|
modalScale: undefined,
|
||||||
|
shouldShrinkTo1px: false,
|
||||||
|
isHidden: false,
|
||||||
|
lockModalFalse: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 현재 포커스된 요소 저장
|
// 현재 포커스된 요소 저장
|
||||||
if (Object.keys(focusSnapshot).length > 0) {
|
if (Object.keys(focusSnapshot).length > 0) {
|
||||||
panelInfo.lastFocusedTargetId = focusSnapshot.lastFocusedTargetId;
|
panelInfo.lastFocusedTargetId = focusSnapshot.lastFocusedTargetId;
|
||||||
@@ -458,11 +521,28 @@ export const restoreVideoOnBack = () => {
|
|||||||
const homePanel = panels.find((p) => p.name === panel_names.HOME_PANEL);
|
const homePanel = panels.find((p) => p.name === panel_names.HOME_PANEL);
|
||||||
const videoStateToRestore = homePanel?.panelInfo?.videoStateToRestore;
|
const videoStateToRestore = homePanel?.panelInfo?.videoStateToRestore;
|
||||||
|
|
||||||
|
console.log('[Detail-BG] 🔍 restoreVideoOnBack - Checking video restore state:', {
|
||||||
|
hasVideoStateToRestore: !!videoStateToRestore,
|
||||||
|
restoreOnBack: videoStateToRestore?.restoreOnBack,
|
||||||
|
sourceMenu: videoStateToRestore?.sourceMenu,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
if (!videoStateToRestore || !videoStateToRestore.restoreOnBack) {
|
if (!videoStateToRestore || !videoStateToRestore.restoreOnBack) {
|
||||||
|
console.log('[Detail-BG] ⏭️ restoreVideoOnBack - No video state to restore (skipping)', {
|
||||||
|
reason: !videoStateToRestore ? 'no videoStateToRestore' : 'restoreOnBack is false',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 비디오 복원 시작 (로그는 개발 시 필요 시 주석 해제)
|
console.log('[Detail-BG] ▶️ restoreVideoOnBack - Starting video restore', {
|
||||||
|
sourceMenu: videoStateToRestore.sourceMenu,
|
||||||
|
patnrId: videoStateToRestore.patnrId,
|
||||||
|
showId: videoStateToRestore.showId,
|
||||||
|
modal: true,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
// 비디오 상태 복원
|
// 비디오 상태 복원
|
||||||
const { startVideoPlayerNew } = require('./playActions');
|
const { startVideoPlayerNew } = require('./playActions');
|
||||||
@@ -489,6 +569,11 @@ export const restoreVideoOnBack = () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log('[Detail-BG] ✅ restoreVideoOnBack - Video restore dispatched', {
|
||||||
|
restoredWithModal: restoreInfo.modal,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
// 복원 상태 정리
|
// 복원 상태 정리
|
||||||
dispatch(
|
dispatch(
|
||||||
updatePanel({
|
updatePanel({
|
||||||
|
|||||||
@@ -73,6 +73,16 @@ export const startVideoPlayer =
|
|||||||
...rest
|
...rest
|
||||||
}) =>
|
}) =>
|
||||||
(dispatch, getState) => {
|
(dispatch, getState) => {
|
||||||
|
const caller = new Error().stack?.split('\n')[2]?.trim();
|
||||||
|
dlog('[PTRACE-SP] startVideoPlayer call', {
|
||||||
|
modal,
|
||||||
|
modalContainerId,
|
||||||
|
modalClassName,
|
||||||
|
videoId,
|
||||||
|
showUrl,
|
||||||
|
caller,
|
||||||
|
});
|
||||||
|
|
||||||
dlog(
|
dlog(
|
||||||
'[startVideoPlayer] ✅ START - videoId:',
|
'[startVideoPlayer] ✅ START - videoId:',
|
||||||
videoId,
|
videoId,
|
||||||
@@ -105,6 +115,9 @@ export const startVideoPlayer =
|
|||||||
|
|
||||||
// 기존 PlayerPanel이 어디든 있으면 완전히 초기화: 타이머 정리 후 pop → 새로 push
|
// 기존 PlayerPanel이 어디든 있으면 완전히 초기화: 타이머 정리 후 pop → 새로 push
|
||||||
if (existingPlayerPanel) {
|
if (existingPlayerPanel) {
|
||||||
|
dlog('[PTRACE-SP] startVideoPlayer: popping existing player before push', {
|
||||||
|
stack: panels.map((p) => p.name),
|
||||||
|
});
|
||||||
dlog('[startVideoPlayer] 🔄 Resetting existing PLAYER_PANEL before start');
|
dlog('[startVideoPlayer] 🔄 Resetting existing PLAYER_PANEL before start');
|
||||||
clearAllVideoTimers();
|
clearAllVideoTimers();
|
||||||
dispatch(popPanel(panel_names.PLAYER_PANEL));
|
dispatch(popPanel(panel_names.PLAYER_PANEL));
|
||||||
@@ -135,14 +148,14 @@ export const startVideoPlayer =
|
|||||||
|
|
||||||
// [COMMENTED OUT] 비디오 재생 시 강제 포커스 이동 비활성화
|
// [COMMENTED OUT] 비디오 재생 시 강제 포커스 이동 비활성화
|
||||||
// if (modal && modalContainerId && !spotlightDisable) {
|
// if (modal && modalContainerId && !spotlightDisable) {
|
||||||
// console.log('[startVideoPlayer] 🎯 Setting Spotlight focus - containerId:', modalContainerId);
|
// dlog('[startVideoPlayer] 🎯 Setting Spotlight focus - containerId:', modalContainerId);
|
||||||
// Spotlight.setPointerMode(false);
|
// Spotlight.setPointerMode(false);
|
||||||
// startVideoFocusTimer = setTimeout(() => {
|
// startVideoFocusTimer = setTimeout(() => {
|
||||||
// console.log('[startVideoPlayer] 🔍 Spotlight.focus called');
|
// dlog('[startVideoPlayer] 🔍 Spotlight.focus called');
|
||||||
// Spotlight.focus(modalContainerId);
|
// Spotlight.focus(modalContainerId);
|
||||||
// }, 0);
|
// }, 0);
|
||||||
// } else {
|
// } else {
|
||||||
// console.log('[startVideoPlayer] ⏭️ Spotlight focus skipped - modal:', modal, ', modalContainerId:', !!modalContainerId, ', spotlightDisable:', spotlightDisable);
|
// dlog('[startVideoPlayer] ⏭️ Spotlight focus skipped - modal:', modal, ', modalContainerId:', !!modalContainerId, ', spotlightDisable:', spotlightDisable);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
dlog('[startVideoPlayer] ✅ END');
|
dlog('[startVideoPlayer] ✅ END');
|
||||||
@@ -182,6 +195,17 @@ export const startVideoPlayerNew =
|
|||||||
...rest
|
...rest
|
||||||
}) =>
|
}) =>
|
||||||
(dispatch, getState) => {
|
(dispatch, getState) => {
|
||||||
|
const caller = new Error().stack?.split('\n')[2]?.trim();
|
||||||
|
dlog('[PTRACE-SPN] startVideoPlayerNew call', {
|
||||||
|
bannerId,
|
||||||
|
modal,
|
||||||
|
modalContainerId,
|
||||||
|
modalClassName,
|
||||||
|
videoId,
|
||||||
|
showUrl,
|
||||||
|
caller,
|
||||||
|
});
|
||||||
|
|
||||||
dlog(
|
dlog(
|
||||||
'[startVideoPlayerNew] *** ✅ START - bannerId:',
|
'[startVideoPlayerNew] *** ✅ START - bannerId:',
|
||||||
bannerId,
|
bannerId,
|
||||||
@@ -215,6 +239,9 @@ export const startVideoPlayerNew =
|
|||||||
|
|
||||||
// 기존 PlayerPanel이 있으면 완전히 초기화: 타이머 정리 후 pop → 새로 push
|
// 기존 PlayerPanel이 있으면 완전히 초기화: 타이머 정리 후 pop → 새로 push
|
||||||
if (existingPlayerPanel) {
|
if (existingPlayerPanel) {
|
||||||
|
dlog('[PTRACE-SPN] popping existing player before push', {
|
||||||
|
stack: panels.map((p) => p.name),
|
||||||
|
});
|
||||||
dlog('[startVideoPlayerNew] *** 🔄 Resetting existing PLAYER_PANEL before start');
|
dlog('[startVideoPlayerNew] *** 🔄 Resetting existing PLAYER_PANEL before start');
|
||||||
clearAllVideoTimers();
|
clearAllVideoTimers();
|
||||||
dispatch(popPanel(panel_names.PLAYER_PANEL));
|
dispatch(popPanel(panel_names.PLAYER_PANEL));
|
||||||
@@ -305,14 +332,14 @@ export const startVideoPlayerNew =
|
|||||||
|
|
||||||
// [COMMENTED OUT] 비디오 재생 시 강제 포커스 이동 비활성화
|
// [COMMENTED OUT] 비디오 재생 시 강제 포커스 이동 비활성화
|
||||||
// if (modal && modalContainerId && !spotlightDisable) {
|
// if (modal && modalContainerId && !spotlightDisable) {
|
||||||
// console.log('[startVideoPlayerNew] *** 🎯 Setting Spotlight focus - containerId:', modalContainerId);
|
// dlog('[startVideoPlayerNew] *** 🎯 Setting Spotlight focus - containerId:', modalContainerId);
|
||||||
// Spotlight.setPointerMode(false);
|
// Spotlight.setPointerMode(false);
|
||||||
// startVideoFocusTimer = setTimeout(() => {
|
// startVideoFocusTimer = setTimeout(() => {
|
||||||
// console.log('[startVideoPlayerNew] *** 🔍 Spotlight.focus called');
|
// dlog('[startVideoPlayerNew] *** 🔍 Spotlight.focus called');
|
||||||
// Spotlight.focus(modalContainerId);
|
// Spotlight.focus(modalContainerId);
|
||||||
// }, 0);
|
// }, 0);
|
||||||
// } else {
|
// } else {
|
||||||
// console.log('[startVideoPlayerNew] *** ⏭️ Spotlight focus skipped - modal:', modal, ', modalContainerId:', !!modalContainerId, ', spotlightDisable:', spotlightDisable);
|
// dlog('[startVideoPlayerNew] *** ⏭️ Spotlight focus skipped - modal:', modal, ', modalContainerId:', !!modalContainerId, ', spotlightDisable:', spotlightDisable);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
dlog('[startVideoPlayerNew] *** ✅ END');
|
dlog('[startVideoPlayerNew] *** ✅ END');
|
||||||
@@ -325,6 +352,12 @@ export const finishVideoPreview = () => (dispatch, getState) => {
|
|||||||
const panels = getState().panels.panels;
|
const panels = getState().panels.panels;
|
||||||
const topPanel = panels[panels.length - 1];
|
const topPanel = panels[panels.length - 1];
|
||||||
if (topPanel && topPanel.name === panel_names.PLAYER_PANEL && topPanel.panelInfo.modal) {
|
if (topPanel && topPanel.name === panel_names.PLAYER_PANEL && topPanel.panelInfo.modal) {
|
||||||
|
dlog('[PANEL-TRACE] finishVideoPreview: popping modal player', {
|
||||||
|
topPanelName: topPanel.name,
|
||||||
|
modal: topPanel.panelInfo.modal,
|
||||||
|
stack: panels.map((p) => p.name),
|
||||||
|
panelInfo: topPanel.panelInfo,
|
||||||
|
});
|
||||||
if (startVideoFocusTimer) {
|
if (startVideoFocusTimer) {
|
||||||
clearTimeout(startVideoFocusTimer);
|
clearTimeout(startVideoFocusTimer);
|
||||||
startVideoFocusTimer = null;
|
startVideoFocusTimer = null;
|
||||||
@@ -384,6 +417,13 @@ export const pauseModalVideo = () => (dispatch, getState) => {
|
|||||||
(panel) => panel.name === panel_names.PLAYER_PANEL && panel.panelInfo?.modal
|
(panel) => panel.name === panel_names.PLAYER_PANEL && panel.panelInfo?.modal
|
||||||
);
|
);
|
||||||
|
|
||||||
|
dlog('[Detail-BG] ⏸️ pauseModalVideo - Pausing modal video', {
|
||||||
|
found: !!modalPlayerPanel,
|
||||||
|
playerPanelModal: modalPlayerPanel?.panelInfo?.modal,
|
||||||
|
currentIsPaused: modalPlayerPanel?.panelInfo?.isPaused,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
if (modalPlayerPanel) {
|
if (modalPlayerPanel) {
|
||||||
if (DEBUG_MODE === true) {
|
if (DEBUG_MODE === true) {
|
||||||
dlog('[pauseModalVideo] Pausing modal video');
|
dlog('[pauseModalVideo] Pausing modal video');
|
||||||
@@ -397,6 +437,14 @@ export const pauseModalVideo = () => (dispatch, getState) => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
dlog('[Detail-BG] ✅ pauseModalVideo - Modal video paused successfully', {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
dlog('[Detail-BG] ⚠️ pauseModalVideo - No modal PlayerPanel found', {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -409,6 +457,13 @@ export const resumeModalVideo = () => (dispatch, getState) => {
|
|||||||
(panel) => panel.name === panel_names.PLAYER_PANEL && panel.panelInfo?.modal
|
(panel) => panel.name === panel_names.PLAYER_PANEL && panel.panelInfo?.modal
|
||||||
);
|
);
|
||||||
|
|
||||||
|
dlog('[Detail-BG] ▶️ resumeModalVideo - Resuming modal video', {
|
||||||
|
found: !!modalPlayerPanel,
|
||||||
|
playerPanelModal: modalPlayerPanel?.panelInfo?.modal,
|
||||||
|
currentIsPaused: modalPlayerPanel?.panelInfo?.isPaused,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
if (modalPlayerPanel && modalPlayerPanel.panelInfo?.isPaused) {
|
if (modalPlayerPanel && modalPlayerPanel.panelInfo?.isPaused) {
|
||||||
if (DEBUG_MODE === true) {
|
if (DEBUG_MODE === true) {
|
||||||
dlog('[resumeModalVideo] Resuming modal video');
|
dlog('[resumeModalVideo] Resuming modal video');
|
||||||
@@ -422,6 +477,16 @@ export const resumeModalVideo = () => (dispatch, getState) => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
dlog('[Detail-BG] ✅ resumeModalVideo - Modal video resumed successfully', {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
dlog('[Detail-BG] ⚠️ resumeModalVideo - Modal video not paused or panel not found', {
|
||||||
|
found: !!modalPlayerPanel,
|
||||||
|
isPaused: modalPlayerPanel?.panelInfo?.isPaused,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -434,6 +499,13 @@ export const pauseFullscreenVideo = () => (dispatch, getState) => {
|
|||||||
(panel) => panel.name === panel_names.PLAYER_PANEL && !panel.panelInfo?.modal
|
(panel) => panel.name === panel_names.PLAYER_PANEL && !panel.panelInfo?.modal
|
||||||
);
|
);
|
||||||
|
|
||||||
|
dlog('[Detail-BG] ⏸️ pauseFullscreenVideo - Pausing fullscreen video', {
|
||||||
|
found: !!fullscreenPlayerPanel,
|
||||||
|
playerPanelModal: fullscreenPlayerPanel?.panelInfo?.modal,
|
||||||
|
currentIsPaused: fullscreenPlayerPanel?.panelInfo?.isPaused,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
if (fullscreenPlayerPanel) {
|
if (fullscreenPlayerPanel) {
|
||||||
dispatch(
|
dispatch(
|
||||||
updatePanel({
|
updatePanel({
|
||||||
@@ -444,6 +516,14 @@ export const pauseFullscreenVideo = () => (dispatch, getState) => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
dlog('[Detail-BG] ✅ pauseFullscreenVideo - Fullscreen video paused successfully', {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
dlog('[Detail-BG] ⚠️ pauseFullscreenVideo - No fullscreen PlayerPanel found', {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -451,21 +531,19 @@ export const pauseFullscreenVideo = () => (dispatch, getState) => {
|
|||||||
export const resumeFullscreenVideo = () => (dispatch, getState) => {
|
export const resumeFullscreenVideo = () => (dispatch, getState) => {
|
||||||
const panels = getState().panels.panels;
|
const panels = getState().panels.panels;
|
||||||
|
|
||||||
// console.log('[BgVideo] resumeFullscreenVideo called - panels:', {
|
|
||||||
// panelsCount: panels?.length,
|
|
||||||
// panels: panels?.map(p => ({ name: p.name, modal: p.panelInfo?.modal, isPaused: p.panelInfo?.isPaused }))
|
|
||||||
// });
|
|
||||||
|
|
||||||
// 전체화면 PlayerPanel 찾기 (modal이 false인 패널)
|
// 전체화면 PlayerPanel 찾기 (modal이 false인 패널)
|
||||||
const fullscreenPlayerPanel = panels.find(
|
const fullscreenPlayerPanel = panels.find(
|
||||||
(panel) => panel.name === panel_names.PLAYER_PANEL && !panel.panelInfo?.modal
|
(panel) => panel.name === panel_names.PLAYER_PANEL && !panel.panelInfo?.modal
|
||||||
);
|
);
|
||||||
|
|
||||||
// console.log('[BgVideo] resumeFullscreenVideo - fullscreenPlayerPanel found:', !!fullscreenPlayerPanel);
|
dlog('[Detail-BG] ▶️ resumeFullscreenVideo - Resuming fullscreen video', {
|
||||||
// console.log('[BgVideo] resumeFullscreenVideo - isPaused:', fullscreenPlayerPanel?.panelInfo?.isPaused);
|
found: !!fullscreenPlayerPanel,
|
||||||
|
playerPanelModal: fullscreenPlayerPanel?.panelInfo?.modal,
|
||||||
|
currentIsPaused: fullscreenPlayerPanel?.panelInfo?.isPaused,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
if (fullscreenPlayerPanel && fullscreenPlayerPanel.panelInfo?.isPaused) {
|
if (fullscreenPlayerPanel && fullscreenPlayerPanel.panelInfo?.isPaused) {
|
||||||
// console.log('[BgVideo] resumeFullscreenVideo - dispatching updatePanel with isPaused: false');
|
|
||||||
dispatch(
|
dispatch(
|
||||||
updatePanel({
|
updatePanel({
|
||||||
name: panel_names.PLAYER_PANEL,
|
name: panel_names.PLAYER_PANEL,
|
||||||
@@ -475,7 +553,16 @@ export const resumeFullscreenVideo = () => (dispatch, getState) => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
dlog('[Detail-BG] ✅ resumeFullscreenVideo - Fullscreen video resumed successfully', {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
|
dlog('[Detail-BG] ⚠️ resumeFullscreenVideo - Fullscreen video not paused or panel not found', {
|
||||||
|
found: !!fullscreenPlayerPanel,
|
||||||
|
isPaused: fullscreenPlayerPanel?.panelInfo?.isPaused,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
if (DEBUG_MODE === true) {
|
if (DEBUG_MODE === true) {
|
||||||
dlog('[BgVideo] resumeFullscreenVideo - Not resuming (not found or not paused)');
|
dlog('[BgVideo] resumeFullscreenVideo - Not resuming (not found or not paused)');
|
||||||
}
|
}
|
||||||
@@ -514,7 +601,7 @@ export const hideModalVideo = () => (dispatch, getState) => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// console.log('[HomePanel] hideModalVideo: saving shrinkInfo', {
|
// dlog('[HomePanel] hideModalVideo: saving shrinkInfo', {
|
||||||
// shrinkInfo: updatedPlayerState.shrinkInfo,
|
// shrinkInfo: updatedPlayerState.shrinkInfo,
|
||||||
// modalStyle: panelInfo.modalStyle,
|
// modalStyle: panelInfo.modalStyle,
|
||||||
// });
|
// });
|
||||||
@@ -951,6 +1038,12 @@ export const resumePlayerControl = (ownerId) => (dispatch, getState) => {
|
|||||||
* 이 액션은 어떤 배너에서든 클릭 시 호출됩니다.
|
* 이 액션은 어떤 배너에서든 클릭 시 호출됩니다.
|
||||||
*/
|
*/
|
||||||
export const goToFullScreen = () => (dispatch, getState) => {
|
export const goToFullScreen = () => (dispatch, getState) => {
|
||||||
|
dlog('[Detail-BG] 🎬 goToFullScreen - Setting PlayerPanel to fullscreen mode', {
|
||||||
|
targetModal: false,
|
||||||
|
action: 'updatePanel',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
// 공유 PlayerPanel의 'modal' 상태를 false로 변경하여 전체화면으로 전환
|
// 공유 PlayerPanel의 'modal' 상태를 false로 변경하여 전체화면으로 전환
|
||||||
dispatch(
|
dispatch(
|
||||||
updatePanel({
|
updatePanel({
|
||||||
@@ -961,6 +1054,10 @@ export const goToFullScreen = () => (dispatch, getState) => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
dlog('[Detail-BG] ✅ goToFullScreen - PlayerPanel modal set to false (fullscreen)', {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1171,6 +1268,14 @@ export const startBannerVideo = (videoInfo) => (dispatch, getState) => {
|
|||||||
...rest
|
...rest
|
||||||
} = videoInfo;
|
} = videoInfo;
|
||||||
|
|
||||||
|
dlog('[Detail-BG] 🎥 startBannerVideo - Starting banner video', {
|
||||||
|
modalStatus: modal,
|
||||||
|
bannerId,
|
||||||
|
displayMode: modal ? 'VISIBLE (modal=true)' : 'FULLSCREEN (modal=false)',
|
||||||
|
videoId,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
// 비디오 식별자 생성
|
// 비디오 식별자 생성
|
||||||
const videoIdentifier = videoId || showUrl || bannerId;
|
const videoIdentifier = videoId || showUrl || bannerId;
|
||||||
if (videoIdentifier) {
|
if (videoIdentifier) {
|
||||||
@@ -1190,11 +1295,21 @@ export const startBannerVideo = (videoInfo) => (dispatch, getState) => {
|
|||||||
// 기존 PlayerPanel이 있으면 초기화
|
// 기존 PlayerPanel이 있으면 초기화
|
||||||
if (existingPlayerPanel) {
|
if (existingPlayerPanel) {
|
||||||
dlog('[startBannerVideo] 🔄 Resetting existing PLAYER_PANEL before start');
|
dlog('[startBannerVideo] 🔄 Resetting existing PLAYER_PANEL before start');
|
||||||
|
dlog('[Detail-BG] 🔄 startBannerVideo - Clearing existing PlayerPanel', {
|
||||||
|
existingModalStatus: existingPlayerPanel.panelInfo?.modal,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
clearAllVideoTimers();
|
clearAllVideoTimers();
|
||||||
dispatch(popPanel(panel_names.PLAYER_PANEL));
|
dispatch(popPanel(panel_names.PLAYER_PANEL));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 새로운 PlayerPanel push
|
// 새로운 PlayerPanel push
|
||||||
|
dlog('[Detail-BG] ➕ startBannerVideo - Pushing new PlayerPanel with modal status', {
|
||||||
|
modal,
|
||||||
|
modalContainerId,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
pushPanel(
|
pushPanel(
|
||||||
{
|
{
|
||||||
@@ -1216,6 +1331,10 @@ export const startBannerVideo = (videoInfo) => (dispatch, getState) => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
dlog('[Detail-BG] ✅ startBannerVideo - PlayerPanel pushed with modal=' + modal, {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
dlog('[startBannerVideo] ✨ Panel action dispatched');
|
dlog('[startBannerVideo] ✨ Panel action dispatched');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -185,8 +185,14 @@ export const TAxios = (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 약관 미동의(501): 토큰 재발급 큐에 넣지 않고 바로 실패 처리
|
||||||
|
if (res?.data?.retCode === 501) {
|
||||||
|
if (onFail) onFail(res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// RefreshToken 만료
|
// RefreshToken 만료
|
||||||
if (res?.data?.retCode === 402 || res?.data?.retCode === 501) {
|
if (res?.data?.retCode === 402) {
|
||||||
if (baseUrl === URLS.GET_RE_AUTHENTICATION_CODE) {
|
if (baseUrl === URLS.GET_RE_AUTHENTICATION_CODE) {
|
||||||
dispatch(getAuthenticationCode());
|
dispatch(getAuthenticationCode());
|
||||||
} else {
|
} else {
|
||||||
@@ -349,10 +355,10 @@ export const TAxiosAdvancedPromise = (
|
|||||||
console.error(`TAxiosPromise error on attempt ${attempts} for ${baseUrl}:`, error);
|
console.error(`TAxiosPromise error on attempt ${attempts} for ${baseUrl}:`, error);
|
||||||
|
|
||||||
// Check if the error is due to token expiration
|
// Check if the error is due to token expiration
|
||||||
// TAxios already handles token refresh and queueing for these codes (401, 402, 501)
|
// TAxios already handles token refresh and queueing for 401/402 (501은 제외)
|
||||||
// So we should NOT retry immediately in this loop, but let TAxios handle it.
|
// So we should NOT retry immediately in this loop, but let TAxios handle it.
|
||||||
const retCode = error?.data?.retCode;
|
const retCode = error?.data?.retCode;
|
||||||
const isTokenError = retCode === 401 || retCode === 402 || retCode === 501;
|
const isTokenError = retCode === 401 || retCode === 402;
|
||||||
|
|
||||||
// 재시도 로직
|
// 재시도 로직
|
||||||
if (attempts < maxAttempts && !isTokenError) {
|
if (attempts < maxAttempts && !isTokenError) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import axios from "axios";
|
|||||||
|
|
||||||
import { createQueryString } from "../utils/helperMethods";
|
import { createQueryString } from "../utils/helperMethods";
|
||||||
import { getUrl } from "./apiConfig";
|
import { getUrl } from "./apiConfig";
|
||||||
|
import { DEBUG_LOG_MODE, sendToLogServer } from "./logServerClient";
|
||||||
|
|
||||||
export const TLogEvent = (
|
export const TLogEvent = (
|
||||||
dispatch,
|
dispatch,
|
||||||
@@ -68,6 +69,23 @@ export const TLogEvent = (
|
|||||||
prodCd,
|
prodCd,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== DEBUG_LOG_MODE: 로그서버로 데이터 전송 =====
|
||||||
|
if (DEBUG_LOG_MODE) {
|
||||||
|
sendToLogServer({
|
||||||
|
deviceId: dvcId,
|
||||||
|
cntryCd,
|
||||||
|
platCd,
|
||||||
|
prodCd,
|
||||||
|
appVersion,
|
||||||
|
deviceLang,
|
||||||
|
logModel: model,
|
||||||
|
apiUrl: url,
|
||||||
|
httpMethod: type,
|
||||||
|
totalLogFlag,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let axiosInstance;
|
let axiosInstance;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export const URLS = {
|
|||||||
// cart controller
|
// cart controller
|
||||||
ADD_TO_CART: "/lgsp/v1/myinfo/cart/add.lge",
|
ADD_TO_CART: "/lgsp/v1/myinfo/cart/add.lge",
|
||||||
REMOVE_FROM_CART: "/lgsp/v1/myinfo/cart/remove.lge",
|
REMOVE_FROM_CART: "/lgsp/v1/myinfo/cart/remove.lge",
|
||||||
UPDATE_CART_ITEM: "/lgsp/v1/myinfo/cart/update.lge",
|
UPDATE_CART_ITEM: "/lgsp/v1/myinfo/cart/update.lge",
|
||||||
// cart api
|
// cart api
|
||||||
GET_MY_INFO_CART_SEARCH: "/lgsp/v1/myinfo/cart/search.lge",
|
GET_MY_INFO_CART_SEARCH: "/lgsp/v1/myinfo/cart/search.lge",
|
||||||
INSERT_MY_INFO_CART: "/lgsp/v1/myinfo/cart/add.lge",
|
INSERT_MY_INFO_CART: "/lgsp/v1/myinfo/cart/add.lge",
|
||||||
@@ -55,6 +55,8 @@ export const URLS = {
|
|||||||
GET_BRAND_CREATORS_INFO: "/lgsp/v1/brand/creators.lge",
|
GET_BRAND_CREATORS_INFO: "/lgsp/v1/brand/creators.lge",
|
||||||
GET_BRAND_SHOWROOM: "/lgsp/v1/brand/showroom.lge",
|
GET_BRAND_SHOWROOM: "/lgsp/v1/brand/showroom.lge",
|
||||||
GET_BRAND_RECENTLY_AIRED: "/lgsp/v1/brand/recently/aired.lge",
|
GET_BRAND_RECENTLY_AIRED: "/lgsp/v1/brand/recently/aired.lge",
|
||||||
|
GET_BRAND_SHOP_BY_SHOW: "/lgsp/v1/brand/shopByShow.lge",
|
||||||
|
GET_BRAND_TOP_BANNER: "/lgsp/v1/brand/top/banner.lge",
|
||||||
|
|
||||||
//on-sale controller
|
//on-sale controller
|
||||||
GET_ON_SALE_INFO: "/lgsp/v1/onsale/onsale.lge",
|
GET_ON_SALE_INFO: "/lgsp/v1/onsale/onsale.lge",
|
||||||
@@ -146,9 +148,8 @@ export const URLS = {
|
|||||||
UPDATE_ORDER_PARTIAL_CANCEL: "/lgsp/v1/myinfo/order/orderPartialCancel.lge",
|
UPDATE_ORDER_PARTIAL_CANCEL: "/lgsp/v1/myinfo/order/orderPartialCancel.lge",
|
||||||
PAYMENT_TOTAL_CANCEL: "/lgsp/v1/myinfo/order/paymentTotalCancel.lge",
|
PAYMENT_TOTAL_CANCEL: "/lgsp/v1/myinfo/order/paymentTotalCancel.lge",
|
||||||
|
|
||||||
// foryou controller
|
// foryou controller
|
||||||
JUSTFORYOU: "/lgsp/v1/recommend/justforyou.lge",
|
JUSTFORYOU: "/lgsp/v1/recommend/justforyou.lge",
|
||||||
|
|
||||||
|
|
||||||
// emp controller
|
// emp controller
|
||||||
GET_SHOPTIME_TERMS: "/lgsp/v1/emp/shoptime/terms.lge",
|
GET_SHOPTIME_TERMS: "/lgsp/v1/emp/shoptime/terms.lge",
|
||||||
@@ -272,11 +273,11 @@ const getRicCode = (country, ricCodeSetting) => {
|
|||||||
if (ricCodeSetting !== "system") {
|
if (ricCodeSetting !== "system") {
|
||||||
return ricCodeSetting;
|
return ricCodeSetting;
|
||||||
}
|
}
|
||||||
if (country == "US") {
|
if (country === "US") {
|
||||||
return "aic";
|
return "aic";
|
||||||
} else if (country == "DE" || country == "GB") {
|
} else if (country === "DE" || country === "GB") {
|
||||||
return "eic";
|
return "eic";
|
||||||
} else if (country == "RU") {
|
} else if (country === "RU") {
|
||||||
return "ruc";
|
return "ruc";
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
85
com.twin.app.shoptime/src/api/logServerClient.js
Normal file
85
com.twin.app.shoptime/src/api/logServerClient.js
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
// ===== DEBUG_LOG_MODE =====
|
||||||
|
// true: 로그서버로 데이터 전송
|
||||||
|
// false: 로그서버 전송 안함
|
||||||
|
export const DEBUG_LOG_MODE = false;
|
||||||
|
|
||||||
|
// ===== 로그서버 기본 설정 =====
|
||||||
|
const LOG_SERVER_URL = 'http://api.optsoft.store:55003/api/logs/realtime';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TLogEvent에서 보낸 데이터를 로그서버로 전송
|
||||||
|
*
|
||||||
|
* @param {Object} logData - TLogEvent에서 보낸 로그 데이터 (params + 추가 정보)
|
||||||
|
* @param {string} logData.deviceId - 디바이스 ID
|
||||||
|
* @param {string} logData.cntryCd - 국가 코드 (또는 countryCode)
|
||||||
|
* @param {string} logData.platCd - 플랫폼 코드
|
||||||
|
* @param {string} logData.prodCd - 제품 코드
|
||||||
|
* @param {string} logData.appVersion - 앱 버전
|
||||||
|
* @param {Object} logData.logModel - TLogEvent가 axios로 보낼 모델 객체
|
||||||
|
* @param {string} logData.apiUrl - API 엔드포인트
|
||||||
|
* @param {string} logData.httpMethod - HTTP 메서드 (get, post)
|
||||||
|
*
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export async function sendToLogServer(logData) {
|
||||||
|
if (!DEBUG_LOG_MODE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// TLogEvent에서 전달된 messageId 사용, 없으면 null
|
||||||
|
const messageId = (logData.logModel && logData.logModel.messageId) || null;
|
||||||
|
|
||||||
|
// 로그서버에 전송할 데이터 구성
|
||||||
|
const logPayload = {
|
||||||
|
// ===== 필수 필드 =====
|
||||||
|
deviceId: logData.deviceId || logData.dvcId || 'unknown',
|
||||||
|
messageId: messageId,
|
||||||
|
logCreateTime: new Date().toISOString(),
|
||||||
|
|
||||||
|
// ===== 로그 기본 정보 =====
|
||||||
|
eventType: 'api_call',
|
||||||
|
apiUrl: logData.apiUrl || 'unknown',
|
||||||
|
httpMethod: logData.httpMethod || 'POST',
|
||||||
|
|
||||||
|
// ===== 디바이스 & 앱 정보 =====
|
||||||
|
countryCode: logData.countryCode || logData.cntryCd || 'unknown',
|
||||||
|
platformCode: logData.platformCode || logData.platCd || 'unknown',
|
||||||
|
platformVersion: logData.platformVersion || logData.prodCd || 'unknown',
|
||||||
|
appVersion: logData.appVersion || 'unknown',
|
||||||
|
deviceLang: logData.deviceLang || 'unknown',
|
||||||
|
|
||||||
|
// ===== 로그 타입별 데이터 =====
|
||||||
|
logTpNo: logData.logTpNo || logData.logType || 'unknown',
|
||||||
|
entryMenu: logData.entryMenu || 'unknown',
|
||||||
|
nowMenu: logData.nowMenu || 'unknown',
|
||||||
|
|
||||||
|
// ===== TLogEvent 원본 데이터 (model) =====
|
||||||
|
...(logData.logModel || {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
// console.log('[logServerClient] Sending log to server - Full Payload:', logPayload);
|
||||||
|
// console.log('[logServerClient] Input logData:', logData);
|
||||||
|
|
||||||
|
// 로그서버로 전송 (비동기, 응답 대기 안함)
|
||||||
|
axios.post(LOG_SERVER_URL, logPayload, {
|
||||||
|
timeout: 5000, // 5초 타임아웃
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
}).then((response) => {
|
||||||
|
// 성공 시 조용하게 처리
|
||||||
|
}).catch((error) => {
|
||||||
|
// 로그서버 전송 실패 시만 오류 로그 출력
|
||||||
|
console.error('[logServerClient] Failed to send log to server:', {
|
||||||
|
url: LOG_SERVER_URL,
|
||||||
|
error: error.message,
|
||||||
|
messageId: messageId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// 함수 자체의 오류는 무시 (조용하게 처리)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import React, {
|
import React, {
|
||||||
useCallback,
|
useCallback,
|
||||||
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
@@ -14,10 +15,13 @@ import {
|
|||||||
useSelector,
|
useSelector,
|
||||||
} from 'react-redux';
|
} from 'react-redux';
|
||||||
|
|
||||||
|
import Spotlight from '@enact/spotlight';
|
||||||
|
|
||||||
import { setHidePopup } from '../../actions/commonActions';
|
import { setHidePopup } from '../../actions/commonActions';
|
||||||
import { getPopupConfig } from '../../constants/popupConfig';
|
import { getPopupConfig } from '../../constants/popupConfig';
|
||||||
import usePrevious from '../../hooks/usePrevious';
|
import usePrevious from '../../hooks/usePrevious';
|
||||||
import TPopUp from '../TPopUp/TPopUp';
|
import TPopUp from '../TPopUp/TPopUp';
|
||||||
|
import TopBannerPopup from '../../views/FeaturedBrandsPanel/TopBannerImage/TopBannerPopup';
|
||||||
|
|
||||||
// 커스텀 훅: 팝업 상태 관리
|
// 커스텀 훅: 팝업 상태 관리
|
||||||
const useGlobalPopupState = () => {
|
const useGlobalPopupState = () => {
|
||||||
@@ -128,9 +132,28 @@ const GlobalPopup = () => {
|
|||||||
secondaryData
|
secondaryData
|
||||||
} = useGlobalPopupState();
|
} = useGlobalPopupState();
|
||||||
|
|
||||||
|
const [imageDimensions, setImageDimensions] = React.useState({ width: 0, height: 0 });
|
||||||
|
|
||||||
const handlers = usePopupCloseHandlers();
|
const handlers = usePopupCloseHandlers();
|
||||||
const previousPopupVisible = usePrevious(popupVisible);
|
const previousPopupVisible = usePrevious(popupVisible);
|
||||||
|
|
||||||
|
const handleImageLoad = useCallback((dimensions) => {
|
||||||
|
console.log("[GLOBAL-POPUP] Image dimensions received:", dimensions);
|
||||||
|
setImageDimensions(dimensions);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Spotlight 제어: 팝업 오픈/클로즈 시 포커스 트래핑
|
||||||
|
useEffect(() => {
|
||||||
|
if (popupVisible && activePopup === 'topBannerImagePopup') {
|
||||||
|
console.log("[GLOBAL-POPUP] Pausing Spotlight for modal popup");
|
||||||
|
Spotlight.pause();
|
||||||
|
return () => {
|
||||||
|
console.log("[GLOBAL-POPUP] Resuming Spotlight after modal close");
|
||||||
|
Spotlight.resume();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [popupVisible, activePopup]);
|
||||||
|
|
||||||
// 현재 팝업 설정
|
// 현재 팝업 설정
|
||||||
const currentConfig = useMemo(() => {
|
const currentConfig = useMemo(() => {
|
||||||
if (!activePopup) return null;
|
if (!activePopup) return null;
|
||||||
@@ -214,6 +237,53 @@ const GlobalPopup = () => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TopBannerImagePopup 특수 처리
|
||||||
|
if (activePopup === 'topBannerImagePopup') {
|
||||||
|
// Figma 디자인 기반 고정 크기
|
||||||
|
// 너비: 1060px
|
||||||
|
// 높이: 헤더(110px) + 이미지(556px) + 푸터(138px) = 804px
|
||||||
|
const popupWidth = '1060px';
|
||||||
|
const popupHeight = '804px';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
zIndex: 9999
|
||||||
|
}}
|
||||||
|
onClick={handlers.handleClose}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: popupWidth,
|
||||||
|
height: popupHeight,
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: '12px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column'
|
||||||
|
}}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<TopBannerPopup
|
||||||
|
title={popupData?.pupBanrImgNm || 'Popup'}
|
||||||
|
imageUrl={popupData?.pupBanrImgUrl}
|
||||||
|
imageAlt={popupData?.pupBanrImgNm || 'Popup Banner'}
|
||||||
|
onImageLoad={handleImageLoad}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 설정이 없으면 기본 팝업도 렌더링하지 않음
|
// 설정이 없으면 기본 팝업도 렌더링하지 않음
|
||||||
if (!currentConfig) {
|
if (!currentConfig) {
|
||||||
console.warn(`No configuration found for popup type: ${activePopup}`);
|
console.warn(`No configuration found for popup type: ${activePopup}`);
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
padding: @slider-padding-v 0;
|
padding: @slider-padding-v 0;
|
||||||
height: @sand-mediaplayer-slider-height;
|
height: @sand-mediaplayer-slider-height;
|
||||||
right: 154px;
|
right: 154px;
|
||||||
width: 1466px;
|
width: 1558px;
|
||||||
// Add a tap area that extends to the edges of the screen, to make the slider more accessible
|
// Add a tap area that extends to the edges of the screen, to make the slider more accessible
|
||||||
&::before {
|
&::before {
|
||||||
content: "";
|
content: "";
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
font-family: @baseFont;
|
font-family: @baseFont;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
right: 90px;
|
right: 20px;
|
||||||
bottom: -5px;
|
bottom: -5px;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -16,12 +16,12 @@
|
|||||||
letter-spacing: -1px;
|
letter-spacing: -1px;
|
||||||
.separator {
|
.separator {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 105px;
|
right: 95px;
|
||||||
bottom: -5px;
|
bottom: -5px;
|
||||||
}
|
}
|
||||||
.currentTime {
|
.currentTime {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 130px;
|
right: 120px;
|
||||||
bottom: -5px;
|
bottom: -5px;
|
||||||
}
|
}
|
||||||
.totalTime {
|
.totalTime {
|
||||||
|
|||||||
@@ -4,75 +4,75 @@ import React, {
|
|||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from 'react';
|
||||||
|
|
||||||
import classNames from "classnames";
|
import classNames from 'classnames';
|
||||||
import {
|
import {
|
||||||
AsYouTypeFormatter,
|
AsYouTypeFormatter,
|
||||||
PhoneNumberFormat,
|
PhoneNumberFormat,
|
||||||
PhoneNumberUtil,
|
PhoneNumberUtil,
|
||||||
} from "google-libphonenumber";
|
} from 'google-libphonenumber';
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { off, on } from "@enact/core/dispatcher";
|
import { off, on } from '@enact/core/dispatcher';
|
||||||
import spotlight, { Spotlight } from "@enact/spotlight";
|
import spotlight, { Spotlight } from '@enact/spotlight';
|
||||||
import { SpotlightContainerDecorator } from "@enact/spotlight/SpotlightContainerDecorator";
|
import { SpotlightContainerDecorator } from '@enact/spotlight/SpotlightContainerDecorator';
|
||||||
import { Spottable } from "@enact/spotlight/Spottable";
|
import { Spottable } from '@enact/spotlight/Spottable';
|
||||||
|
|
||||||
import defaultImage from "../../../assets/images/img-thumb-empty-144@3x.png";
|
import defaultImage from '../../../assets/images/img-thumb-empty-144@3x.png';
|
||||||
import { types } from "../../actions/actionTypes";
|
import { types } from '../../actions/actionTypes';
|
||||||
import { clearSMS, sendSms } from "../../actions/appDataActions";
|
import { clearSMS, sendSms } from '../../actions/appDataActions';
|
||||||
import {
|
import {
|
||||||
changeLocalSettings,
|
changeLocalSettings,
|
||||||
setHidePopup,
|
setHidePopup,
|
||||||
setShowPopup,
|
setShowPopup,
|
||||||
} from "../../actions/commonActions";
|
} from '../../actions/commonActions';
|
||||||
import {
|
import {
|
||||||
clearRegisterDeviceInfo,
|
clearRegisterDeviceInfo,
|
||||||
getDeviceAdditionInfo,
|
getDeviceAdditionInfo,
|
||||||
registerDeviceInfo,
|
registerDeviceInfo,
|
||||||
} from "../../actions/deviceActions";
|
} from '../../actions/deviceActions';
|
||||||
import {
|
import {
|
||||||
clearCurationCoupon,
|
clearCurationCoupon,
|
||||||
setEventIssueReq,
|
setEventIssueReq,
|
||||||
} from "../../actions/eventActions";
|
} from '../../actions/eventActions';
|
||||||
import {
|
import {
|
||||||
sendLogShopByMobile,
|
sendLogShopByMobile,
|
||||||
sendLogTotalRecommend,
|
sendLogTotalRecommend,
|
||||||
} from "../../actions/logActions";
|
} from '../../actions/logActions';
|
||||||
import {
|
import {
|
||||||
ACTIVE_POPUP,
|
ACTIVE_POPUP,
|
||||||
LOG_CONTEXT_NAME,
|
LOG_CONTEXT_NAME,
|
||||||
LOG_MESSAGE_ID,
|
LOG_MESSAGE_ID,
|
||||||
LOG_TP_NO,
|
LOG_TP_NO,
|
||||||
} from "../../utils/Config";
|
} from '../../utils/Config';
|
||||||
import {
|
import {
|
||||||
$L,
|
$L,
|
||||||
decryptPhoneNumber,
|
decryptPhoneNumber,
|
||||||
encryptPhoneNumber,
|
encryptPhoneNumber,
|
||||||
formatLocalDateTime,
|
formatLocalDateTime,
|
||||||
} from "../../utils/helperMethods";
|
} from '../../utils/helperMethods';
|
||||||
import CustomImage from "../CustomImage/CustomImage";
|
import CustomImage from '../CustomImage/CustomImage';
|
||||||
import TButton from "../TButton/TButton";
|
import TButton from '../TButton/TButton';
|
||||||
import TPopUp from "../TPopUp/TPopUp";
|
import TPopUp from '../TPopUp/TPopUp';
|
||||||
import HistoryPhoneNumber from "./HistoryPhoneNumber/HistoryPhoneNumber";
|
import HistoryPhoneNumber from './HistoryPhoneNumber/HistoryPhoneNumber';
|
||||||
import css from "./MobileSendPopUp.module.less";
|
import css from './MobileSendPopUp.module.less';
|
||||||
import PhoneInputSection from "./PhoneInputSection";
|
import PhoneInputSection from './PhoneInputSection';
|
||||||
import SMSNumKeyPad from "./SMSNumKeyPad";
|
import SMSNumKeyPad from './SMSNumKeyPad';
|
||||||
|
|
||||||
const SECRET_KEY = "fy7BTKuM9eeTQqEC9sF3Iw5qG43Aaip";
|
const SECRET_KEY = 'fy7BTKuM9eeTQqEC9sF3Iw5qG43Aaip';
|
||||||
|
|
||||||
const Container = SpotlightContainerDecorator(
|
const Container = SpotlightContainerDecorator(
|
||||||
{ enterTo: "last-focused" },
|
{ enterTo: 'last-focused' },
|
||||||
"div"
|
'div'
|
||||||
);
|
);
|
||||||
|
|
||||||
const InputContainer = SpotlightContainerDecorator(
|
const InputContainer = SpotlightContainerDecorator(
|
||||||
{ enterTo: "last-focused" },
|
{ enterTo: 'last-focused' },
|
||||||
"div"
|
'div'
|
||||||
);
|
);
|
||||||
|
|
||||||
const SpottableComponent = Spottable("div");
|
const SpottableComponent = Spottable('div');
|
||||||
|
|
||||||
export default function MobileSendPopUp({
|
export default function MobileSendPopUp({
|
||||||
open,
|
open,
|
||||||
@@ -116,39 +116,40 @@ export default function MobileSendPopUp({
|
|||||||
const popupVisible = useSelector((state) => state.common.popup.popupVisible);
|
const popupVisible = useSelector((state) => state.common.popup.popupVisible);
|
||||||
const nowMenu = useSelector((state) => state.common.menu.nowMenu);
|
const nowMenu = useSelector((state) => state.common.menu.nowMenu);
|
||||||
const entryMenu = useSelector((state) => state.common.menu.entryMenu);
|
const entryMenu = useSelector((state) => state.common.menu.entryMenu);
|
||||||
|
|
||||||
const [inputDisabled, setInputDisabled] = useState(true);
|
const [inputDisabled, setInputDisabled] = useState(true);
|
||||||
const [mobileNumber, setMobileNumber] = useState("");
|
const [mobileNumber, setMobileNumber] = useState('');
|
||||||
const [recentSentNumber, setRecentSentNumber] = useState([]);
|
const [recentSentNumber, setRecentSentNumber] = useState([]);
|
||||||
const [keyPadOff, setKeyPadOff] = useState(false);
|
const [keyPadOff, setKeyPadOff] = useState(false);
|
||||||
const [smsRetCode, setSmsRetCode] = useState(undefined);
|
const [smsRetCode, setSmsRetCode] = useState(undefined);
|
||||||
|
|
||||||
const agreeBtnClickedRef = useRef(false);
|
const agreeBtnClickedRef = useRef(false);
|
||||||
|
|
||||||
const deviceCountryCode = httpHeader["X-Device-Country"];
|
const deviceCountryCode = httpHeader['X-Device-Country'];
|
||||||
|
|
||||||
const mobileSendPopUpSpotlightId = useMemo(() => {
|
const mobileSendPopUpSpotlightId = useMemo(() => {
|
||||||
return !keyPadOff && recentSentNumber.length <= 0
|
return !keyPadOff && recentSentNumber.length <= 0
|
||||||
? "keypad-number-1"
|
? 'keypad-number-1'
|
||||||
: "agreeAndSend";
|
: 'agreeAndSend';
|
||||||
}, [keyPadOff, recentSentNumber]);
|
}, [keyPadOff, recentSentNumber]);
|
||||||
|
|
||||||
const getMaxNum = useCallback((_deviceCountryCode) => {
|
const getMaxNum = useCallback((_deviceCountryCode) => {
|
||||||
if (_deviceCountryCode === "DE" || _deviceCountryCode === "GB") {
|
if (_deviceCountryCode === 'DE' || _deviceCountryCode === 'GB') {
|
||||||
return 11;
|
return 11;
|
||||||
} else if (_deviceCountryCode === "KR") {
|
} else if (_deviceCountryCode === 'KR') {
|
||||||
return 12;
|
return 12;
|
||||||
} else return 10;
|
} else return 10;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const MSG_SUCCESS_SENT = $L("Text Send to") + " " + mobileNumber;
|
const MSG_SUCCESS_SENT = $L('Text Send to') + ' ' + mobileNumber;
|
||||||
const MSG_SEND_LINK = $L("Send a purchase link for this item via SMS");
|
const MSG_SEND_LINK = $L('Send a purchase link for this item via SMS');
|
||||||
|
|
||||||
const handleClickSelect = (_phoneNumber) => {
|
const handleClickSelect = (_phoneNumber) => {
|
||||||
setKeyPadOff((state) => !state);
|
setKeyPadOff((state) => !state);
|
||||||
setMobileNumber(_phoneNumber);
|
setMobileNumber(_phoneNumber);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
Spotlight.focus("keypad-number-1");
|
Spotlight.focus('keypad-number-1');
|
||||||
}, 0);
|
}, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -161,9 +162,9 @@ export default function MobileSendPopUp({
|
|||||||
|
|
||||||
const getRawPhoneNumber = useCallback(
|
const getRawPhoneNumber = useCallback(
|
||||||
(key) => {
|
(key) => {
|
||||||
let rawPhoneNumber = `${mobileNumber}${key}`.replace(/\D/g, "");
|
let rawPhoneNumber = `${mobileNumber}${key}`.replace(/\D/g, '');
|
||||||
if (rawPhoneNumber.length === getMaxNum(deviceCountryCode)) {
|
if (rawPhoneNumber.length === getMaxNum(deviceCountryCode)) {
|
||||||
Spotlight.focus("agreeAndSend");
|
Spotlight.focus('agreeAndSend');
|
||||||
}
|
}
|
||||||
// 테스트용: 12자리까지 허용
|
// 테스트용: 12자리까지 허용
|
||||||
if (rawPhoneNumber.length > 12) {
|
if (rawPhoneNumber.length > 12) {
|
||||||
@@ -182,11 +183,11 @@ export default function MobileSendPopUp({
|
|||||||
numberProto,
|
numberProto,
|
||||||
PhoneNumberFormat.NATIONAL
|
PhoneNumberFormat.NATIONAL
|
||||||
);
|
);
|
||||||
if (deviceCountryCode === "RU" && rawPhoneNumber.startsWith("8")) {
|
if (deviceCountryCode === 'RU' && rawPhoneNumber.startsWith('8')) {
|
||||||
rawPhoneNumber = rawPhoneNumber.substring(1);
|
rawPhoneNumber = rawPhoneNumber.substring(1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let formattedNumber = "";
|
let formattedNumber = '';
|
||||||
|
|
||||||
for (let i = 0; i < rawPhoneNumber.length; i++) {
|
for (let i = 0; i < rawPhoneNumber.length; i++) {
|
||||||
formattedNumber = asYouTypeFormatter.inputDigit(
|
formattedNumber = asYouTypeFormatter.inputDigit(
|
||||||
@@ -206,7 +207,7 @@ export default function MobileSendPopUp({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const getBackspaceRawNumber = useCallback(() => {
|
const getBackspaceRawNumber = useCallback(() => {
|
||||||
let rawPhoneNumber = mobileNumber.replace(/\D/g, "").slice(0, -1);
|
let rawPhoneNumber = mobileNumber.replace(/\D/g, '').slice(0, -1);
|
||||||
const phoneUtil = PhoneNumberUtil.getInstance();
|
const phoneUtil = PhoneNumberUtil.getInstance();
|
||||||
const asYouTypeFormatter = new AsYouTypeFormatter(deviceCountryCode);
|
const asYouTypeFormatter = new AsYouTypeFormatter(deviceCountryCode);
|
||||||
|
|
||||||
@@ -221,7 +222,7 @@ export default function MobileSendPopUp({
|
|||||||
PhoneNumberFormat.NATIONAL
|
PhoneNumberFormat.NATIONAL
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let formattedNumber = "";
|
let formattedNumber = '';
|
||||||
|
|
||||||
for (let i = 0; i < rawPhoneNumber.length; i++) {
|
for (let i = 0; i < rawPhoneNumber.length; i++) {
|
||||||
formattedNumber = asYouTypeFormatter.inputDigit(
|
formattedNumber = asYouTypeFormatter.inputDigit(
|
||||||
@@ -242,7 +243,7 @@ export default function MobileSendPopUp({
|
|||||||
(ev) => {
|
(ev) => {
|
||||||
if (ev && ev.key >= 0 && ev.key <= 9) {
|
if (ev && ev.key >= 0 && ev.key <= 9) {
|
||||||
getRawPhoneNumber(ev.key);
|
getRawPhoneNumber(ev.key);
|
||||||
} else if (ev.key === "Backspace") {
|
} else if (ev.key === 'Backspace') {
|
||||||
getBackspaceRawNumber();
|
getBackspaceRawNumber();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -250,9 +251,9 @@ export default function MobileSendPopUp({
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
on("keydown", handleKeydown);
|
on('keydown', handleKeydown);
|
||||||
return () => {
|
return () => {
|
||||||
off("keydown", handleKeydown);
|
off('keydown', handleKeydown);
|
||||||
};
|
};
|
||||||
}, [handleKeydown]);
|
}, [handleKeydown]);
|
||||||
|
|
||||||
@@ -264,7 +265,7 @@ export default function MobileSendPopUp({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setTimeout(() =>
|
const timer = setTimeout(() =>
|
||||||
setInputDisabled(mobileSendPopUpSpotlightId === "keypad-number-1")
|
setInputDisabled(mobileSendPopUpSpotlightId === 'keypad-number-1')
|
||||||
);
|
);
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
@@ -277,16 +278,16 @@ export default function MobileSendPopUp({
|
|||||||
setMobileNumber(recentSentNumber[0]);
|
setMobileNumber(recentSentNumber[0]);
|
||||||
} else {
|
} else {
|
||||||
setKeyPadOff(false);
|
setKeyPadOff(false);
|
||||||
setMobileNumber("");
|
setMobileNumber('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [recentSentNumber]);
|
}, [recentSentNumber]);
|
||||||
|
|
||||||
const numKeypadClicked = useCallback(
|
const numKeypadClicked = useCallback(
|
||||||
(key) => {
|
(key) => {
|
||||||
if (key === "clear") {
|
if (key === 'clear') {
|
||||||
setMobileNumber("");
|
setMobileNumber('');
|
||||||
} else if (key == "backspace") {
|
} else if (key == 'backspace') {
|
||||||
getBackspaceRawNumber();
|
getBackspaceRawNumber();
|
||||||
} else {
|
} else {
|
||||||
getRawPhoneNumber(key);
|
getRawPhoneNumber(key);
|
||||||
@@ -315,7 +316,7 @@ export default function MobileSendPopUp({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleAgreeSendClick = useCallback(() => {
|
const handleAgreeSendClick = useCallback(() => {
|
||||||
let naturalNumber = mobileNumber.replace(/\D/g, "");
|
let naturalNumber = mobileNumber.replace(/\D/g, '');
|
||||||
|
|
||||||
// 테스트용: 길이 체크를 더 유연하게 (10자리 또는 11자리 허용)
|
// 테스트용: 길이 체크를 더 유연하게 (10자리 또는 11자리 허용)
|
||||||
if (
|
if (
|
||||||
@@ -327,8 +328,8 @@ export default function MobileSendPopUp({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deviceCountryCode === "KR") {
|
if (deviceCountryCode === 'KR') {
|
||||||
naturalNumber = "82" + naturalNumber;
|
naturalNumber = '82' + naturalNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recentSentNumber && recentSentNumber.length > 0) {
|
if (recentSentNumber && recentSentNumber.length > 0) {
|
||||||
@@ -394,22 +395,22 @@ export default function MobileSendPopUp({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 호텔일 경우 날려야 하는 경우
|
// 호텔일 경우 날려야 하는 경우
|
||||||
if (smsTpCd === "APP00205") {
|
if (smsTpCd === 'APP00205') {
|
||||||
params = { ...params, hotelId, hotelNm, hotelDtlUrl, curationId };
|
params = { ...params, hotelId, hotelNm, hotelDtlUrl, curationId };
|
||||||
}
|
}
|
||||||
if (smsTpCd === "APP00204") {
|
if (smsTpCd === 'APP00204') {
|
||||||
params = { ...params, curationId };
|
params = { ...params, curationId };
|
||||||
}
|
}
|
||||||
dispatch(sendSms(params));
|
dispatch(sendSms(params));
|
||||||
}
|
}
|
||||||
// EVT00101 & APP00207(welcome) EVT00103 & APP00209 (welcome+Prizes) : smsTpCd 값을 받지 않음
|
// EVT00101 & APP00207(welcome) EVT00103 & APP00209 (welcome+Prizes) : smsTpCd 값을 받지 않음
|
||||||
if (evntTpCd === "EVT00101" || evntTpCd === "EVT00103") {
|
if (evntTpCd === 'EVT00101' || evntTpCd === 'EVT00103') {
|
||||||
dispatch(
|
dispatch(
|
||||||
registerDeviceInfo({
|
registerDeviceInfo({
|
||||||
evntTpCd,
|
evntTpCd,
|
||||||
evntId,
|
evntId,
|
||||||
evntApplcnFlag: "Y",
|
evntApplcnFlag: 'Y',
|
||||||
entryMenu: "TermsPop",
|
entryMenu: 'TermsPop',
|
||||||
mbphNo: naturalNumber,
|
mbphNo: naturalNumber,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -434,7 +435,7 @@ export default function MobileSendPopUp({
|
|||||||
onClose();
|
onClose();
|
||||||
dispatch(setShowPopup(ACTIVE_POPUP.smsPopup));
|
dispatch(setShowPopup(ACTIVE_POPUP.smsPopup));
|
||||||
|
|
||||||
setTimeout(() => Spotlight.focus("agreeAndSend"));
|
setTimeout(() => Spotlight.focus('agreeAndSend'));
|
||||||
},
|
},
|
||||||
[dispatch, smsRetCode]
|
[dispatch, smsRetCode]
|
||||||
);
|
);
|
||||||
@@ -450,7 +451,7 @@ export default function MobileSendPopUp({
|
|||||||
curationCouponSuccess === 0
|
curationCouponSuccess === 0
|
||||||
) {
|
) {
|
||||||
const logParams = {
|
const logParams = {
|
||||||
status: "send",
|
status: 'send',
|
||||||
nowMenu: nowMenu,
|
nowMenu: nowMenu,
|
||||||
partner: patncNm ?? shopByMobileLogRef?.current?.patncNm,
|
partner: patncNm ?? shopByMobileLogRef?.current?.patncNm,
|
||||||
productId: prdtId ?? shopByMobileLogRef?.current?.prdtId,
|
productId: prdtId ?? shopByMobileLogRef?.current?.prdtId,
|
||||||
@@ -465,8 +466,8 @@ export default function MobileSendPopUp({
|
|||||||
...shopByMobileLogRef.current,
|
...shopByMobileLogRef.current,
|
||||||
locDt: formatLocalDateTime(new Date()),
|
locDt: formatLocalDateTime(new Date()),
|
||||||
logTpNo: LOG_TP_NO.SHOP_BY_MOBILE.AGREE_AND_SEND,
|
logTpNo: LOG_TP_NO.SHOP_BY_MOBILE.AGREE_AND_SEND,
|
||||||
mbphNoFlag: "Y",
|
mbphNoFlag: 'Y',
|
||||||
trmsAgrFlag: "Y",
|
trmsAgrFlag: 'Y',
|
||||||
};
|
};
|
||||||
|
|
||||||
dispatch(sendLogShopByMobile(params));
|
dispatch(sendLogShopByMobile(params));
|
||||||
@@ -497,24 +498,24 @@ export default function MobileSendPopUp({
|
|||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
const getSmsErrorMsg = useMemo(() => {
|
const getSmsErrorMsg = useMemo(() => {
|
||||||
const SMS_ERROR_502 = $L("The event information has not been registered");
|
const SMS_ERROR_502 = $L('The event information has not been registered');
|
||||||
const SMS_ERROR_903 = $L("You have exceeded the daily text limit.");
|
const SMS_ERROR_903 = $L('You have exceeded the daily text limit.');
|
||||||
const SMS_ERROR_904 = $L(
|
const SMS_ERROR_904 = $L(
|
||||||
"You have exceeded the text limit for this product."
|
'You have exceeded the text limit for this product.'
|
||||||
);
|
);
|
||||||
const SMS_ERROR_905 = $L(
|
const SMS_ERROR_905 = $L(
|
||||||
"This number is currently blocked. To receive a message, please send UNSTOP to the number below. 07860 064195"
|
'This number is currently blocked. To receive a message, please send UNSTOP to the number below. 07860 064195'
|
||||||
);
|
);
|
||||||
const SMS_ERROR_906 = $L("Sorry. This item is sold out.");
|
const SMS_ERROR_906 = $L('Sorry. This item is sold out.');
|
||||||
const SMS_ERROR_600 = $L("This device had received first time coupon.");
|
const SMS_ERROR_600 = $L('This device had received first time coupon.');
|
||||||
const SMS_ERROR_601 = $L("There is no coupon.");
|
const SMS_ERROR_601 = $L('There is no coupon.');
|
||||||
const SMS_ERROR_900 = $L("Failed to send text to {mobileNumber}").replace(
|
const SMS_ERROR_900 = $L('Failed to send text to {mobileNumber}').replace(
|
||||||
"{mobileNumber}",
|
'{mobileNumber}',
|
||||||
mobileNumber
|
mobileNumber
|
||||||
);
|
);
|
||||||
const SMS_ERROR_907 = $L(
|
const SMS_ERROR_907 = $L(
|
||||||
"Only {length} digits is permitted. Please check again"
|
'Only {length} digits is permitted. Please check again'
|
||||||
).replace("{length}", getMaxNum(deviceCountryCode));
|
).replace('{length}', getMaxNum(deviceCountryCode));
|
||||||
|
|
||||||
switch (smsRetCode) {
|
switch (smsRetCode) {
|
||||||
case 502:
|
case 502:
|
||||||
@@ -542,12 +543,12 @@ export default function MobileSendPopUp({
|
|||||||
|
|
||||||
const getEvntErrorMsg = useMemo(() => {
|
const getEvntErrorMsg = useMemo(() => {
|
||||||
if (curationCouponSuccess === 600) {
|
if (curationCouponSuccess === 600) {
|
||||||
return $L("This device had received first time coupon.");
|
return $L('This device had received first time coupon.');
|
||||||
} else if (curationCouponSuccess === 601) {
|
} else if (curationCouponSuccess === 601) {
|
||||||
return $L("There is no coupon.");
|
return $L('There is no coupon.');
|
||||||
} else {
|
} else {
|
||||||
return $L("Failed to sent text to {mobileNumber}").replace(
|
return $L('Failed to sent text to {mobileNumber}').replace(
|
||||||
"{mobileNumber}",
|
'{mobileNumber}',
|
||||||
mobileNumber
|
mobileNumber
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -571,7 +572,7 @@ export default function MobileSendPopUp({
|
|||||||
regDeviceInfoRetCode === undefined &&
|
regDeviceInfoRetCode === undefined &&
|
||||||
curationCouponSuccess === undefined && (
|
curationCouponSuccess === undefined && (
|
||||||
<TPopUp
|
<TPopUp
|
||||||
kind={"mobileSendPopup"}
|
kind={'mobileSendPopup'}
|
||||||
className={css.container}
|
className={css.container}
|
||||||
open={open}
|
open={open}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
@@ -602,16 +603,22 @@ export default function MobileSendPopUp({
|
|||||||
<div className={css.headerTopRow}>
|
<div className={css.headerTopRow}>
|
||||||
{brandLogo && (
|
{brandLogo && (
|
||||||
<img
|
<img
|
||||||
className={css.headerTopRow__brandLogo}
|
className={classNames(
|
||||||
|
css.headerTopRow__brandLogo,
|
||||||
|
patnrId === '1' && css.headerTopRow__brandLogo__qvc
|
||||||
|
)}
|
||||||
src={brandLogo}
|
src={brandLogo}
|
||||||
alt="Brand"
|
alt="Brand"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{productId && (
|
{productId && patnrId !== '21' && (
|
||||||
<div className={css.headerTopRow__productId}>
|
<div className={css.headerTopRow__productId}>
|
||||||
ID: {productId}
|
ID: {productId}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{patnrId === '21' && (
|
||||||
|
<div className={css.headerTopRow__productId}>{patncNm}</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{subTitle && (
|
{subTitle && (
|
||||||
<div
|
<div
|
||||||
@@ -639,8 +646,8 @@ export default function MobileSendPopUp({
|
|||||||
onClick={handleInputClick}
|
onClick={handleInputClick}
|
||||||
spotlightDisabled={inputDisabled}
|
spotlightDisabled={inputDisabled}
|
||||||
>
|
>
|
||||||
{deviceCountryCode && deviceCountryCode === "RU" && (
|
{deviceCountryCode && deviceCountryCode === 'RU' && (
|
||||||
<span className={css.rucInput}>{"+7 "}</span>
|
<span className={css.rucInput}>{'+7 '}</span>
|
||||||
)}
|
)}
|
||||||
<span>{mobileNumber}</span>
|
<span>{mobileNumber}</span>
|
||||||
</SpottableComponent>
|
</SpottableComponent>
|
||||||
@@ -662,10 +669,10 @@ export default function MobileSendPopUp({
|
|||||||
<span
|
<span
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: `${$L(
|
__html: `${$L(
|
||||||
"By clicking Agree and Send button, I agree that LGE may collect and store my cell phone number to send text messages as I requested, for data analysis and for feature-enhancement purposes. By entering my cell phone number, I agree to receive messages from LGE with information on how to purchase the product I selected. Message and data rates may apply."
|
'By clicking Agree and Send button, I agree that LGE may collect and store my cell phone number to send text messages as I requested, for data analysis and for feature-enhancement purposes. By entering my cell phone number, I agree to receive messages from LGE with information on how to purchase the product I selected. Message and data rates may apply.'
|
||||||
)}`,
|
)}`,
|
||||||
}}
|
}}
|
||||||
className={deviceCountryCode === "RU" && css.instructionRu}
|
className={deviceCountryCode === 'RU' && css.instructionRu}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -698,11 +705,11 @@ export default function MobileSendPopUp({
|
|||||||
<Container className={css.container__btnContainer}>
|
<Container className={css.container__btnContainer}>
|
||||||
<TButton
|
<TButton
|
||||||
onClick={handleAgreeSendClick}
|
onClick={handleAgreeSendClick}
|
||||||
spotlightId={"agreeAndSend"}
|
spotlightId={'agreeAndSend'}
|
||||||
>
|
>
|
||||||
{$L("Agree and Send")}
|
{$L('Agree and Send')}
|
||||||
</TButton>
|
</TButton>
|
||||||
<TButton onClick={onClose}>{$L("Cancel")}</TButton>
|
<TButton onClick={onClose}>{$L('Cancel')}</TButton>
|
||||||
</Container>
|
</Container>
|
||||||
</TPopUp>
|
</TPopUp>
|
||||||
)}
|
)}
|
||||||
@@ -714,7 +721,7 @@ export default function MobileSendPopUp({
|
|||||||
text={smsTpCd ? getSmsErrorMsg : getEvntErrorMsg}
|
text={smsTpCd ? getSmsErrorMsg : getEvntErrorMsg}
|
||||||
onClick={_onClose}
|
onClick={_onClose}
|
||||||
hasButton
|
hasButton
|
||||||
button1Text={$L("OK")}
|
button1Text={$L('OK')}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(smsRetCode === 0 ||
|
{(smsRetCode === 0 ||
|
||||||
@@ -727,7 +734,7 @@ export default function MobileSendPopUp({
|
|||||||
text={MSG_SUCCESS_SENT}
|
text={MSG_SUCCESS_SENT}
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
hasButton
|
hasButton
|
||||||
button1Text={$L("OK")}
|
button1Text={$L('OK')}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@import "../../style/CommonStyle.module.less";
|
@import '../../style/CommonStyle.module.less';
|
||||||
@import "../../style/utils.module.less";
|
@import '../../style/utils.module.less';
|
||||||
|
|
||||||
/* 🆕 [NEW] Figma 디자인용 타이틀 헤드 스타일 */
|
/* 🆕 [NEW] Figma 디자인용 타이틀 헤드 스타일 */
|
||||||
.titleHead {
|
.titleHead {
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
text-align: left; // center → left
|
text-align: left; // center → left
|
||||||
color: black;
|
color: black;
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
font-family: "LG Smart UI";
|
font-family: 'LG Smart UI';
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 42px;
|
line-height: 42px;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
@@ -32,12 +32,15 @@
|
|||||||
height: 50px;
|
height: 50px;
|
||||||
margin-right: 15px; // TV 호환: gap 대신 margin 사용
|
margin-right: 15px; // TV 호환: gap 대신 margin 사용
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
|
&.headerTopRow__brandLogo__qvc {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.headerTopRow__productId {
|
.headerTopRow__productId {
|
||||||
color: #808080;
|
color: #808080;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-family: "LG Smart UI";
|
font-family: 'LG Smart UI';
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
@@ -57,7 +60,7 @@
|
|||||||
top: 0;
|
top: 0;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
|
|
||||||
content: "";
|
content: '';
|
||||||
}
|
}
|
||||||
display: flex;
|
display: flex;
|
||||||
> .container__header__productImg,
|
> .container__header__productImg,
|
||||||
@@ -178,7 +181,7 @@
|
|||||||
.flex {
|
.flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-wrap:wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
.instruction {
|
.instruction {
|
||||||
width: 492.5px; // 고정 너비
|
width: 492.5px; // 고정 너비
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import TNewPopUp from '../TPopUp/TNewPopUp'; // TNewPopUp 컴포넌트의 정확한 경로를 확인해주세요.
|
import TNewPopUp from '../TPopUp/TNewPopUp'; // TNewPopUp 컴포넌트의 정확한 경로를 확인해주세요.
|
||||||
import css from './OptionalConfirm.module.less';
|
import css from './OptionalConfirm.module.less';
|
||||||
|
|
||||||
@@ -16,7 +18,7 @@ const OptionalConfirm = ({
|
|||||||
onOptionalTermsClick, // 약관 자세히 보기 버튼 클릭 핸들러
|
onOptionalTermsClick, // 약관 자세히 보기 버튼 클릭 핸들러
|
||||||
onOptionalAgreeClick, // 동의 버튼 클릭 핸들러
|
onOptionalAgreeClick, // 동의 버튼 클릭 핸들러
|
||||||
onOptionalDeclineClick, // 거절 또는 다음에 하기 버튼 클릭 핸들러
|
onOptionalDeclineClick, // 거절 또는 다음에 하기 버튼 클릭 핸들러
|
||||||
customPosition,
|
customPosition,
|
||||||
position,
|
position,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
@@ -31,7 +33,7 @@ const OptionalConfirm = ({
|
|||||||
onOptionalAgreeClick={onOptionalAgreeClick}
|
onOptionalAgreeClick={onOptionalAgreeClick}
|
||||||
onOptionalDeclineClick={onOptionalDeclineClick}
|
onOptionalDeclineClick={onOptionalDeclineClick}
|
||||||
customPosition={customPosition}
|
customPosition={customPosition}
|
||||||
position={position}
|
position={position}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,14 +1,26 @@
|
|||||||
// src/components/Optional/OptionalTermsConfirm.jsx
|
// src/components/Optional/OptionalTermsConfirm.jsx
|
||||||
|
|
||||||
import React, { useEffect, useCallback, useState } from 'react';
|
import React, {
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
useCallback,
|
||||||
import TPopUp from '../TPopUp/TPopUp';
|
useState,
|
||||||
import TButton from '../TButton/TButton';
|
} from 'react';
|
||||||
import TCheckBoxSquare from '../TCheckBox/TCheckBoxSquare';
|
|
||||||
import TButtonScroller from '../TButtonScroller/TButtonScroller';
|
import {
|
||||||
import { $L, scaleH, scaleW } from '../../utils/helperMethods';
|
useDispatch,
|
||||||
|
useSelector,
|
||||||
|
} from 'react-redux';
|
||||||
|
|
||||||
import { setHidePopup } from '../../actions/commonActions';
|
import { setHidePopup } from '../../actions/commonActions';
|
||||||
import { setMyPageTermsAgree } from '../../actions/myPageActions';
|
import { setMyPageTermsAgree } from '../../actions/myPageActions';
|
||||||
|
import {
|
||||||
|
$L,
|
||||||
|
scaleH,
|
||||||
|
scaleW,
|
||||||
|
} from '../../utils/helperMethods';
|
||||||
|
import TButton from '../TButton/TButton';
|
||||||
|
import TButtonScroller from '../TButtonScroller/TButtonScroller';
|
||||||
|
import TCheckBoxSquare from '../TCheckBox/TCheckBoxSquare';
|
||||||
|
import TPopUp from '../TPopUp/TPopUp';
|
||||||
import css from './OptionalTermsConfirm.module.less';
|
import css from './OptionalTermsConfirm.module.less';
|
||||||
|
|
||||||
const OptionalTermsConfirm = ({ open }) => {
|
const OptionalTermsConfirm = ({ open }) => {
|
||||||
@@ -17,9 +29,8 @@ const OptionalTermsConfirm = ({ open }) => {
|
|||||||
const [isChecked, setIsChecked] = useState(false);
|
const [isChecked, setIsChecked] = useState(false);
|
||||||
const [isTermsPopupVisible, setIsTermsPopupVisible] = useState(false);
|
const [isTermsPopupVisible, setIsTermsPopupVisible] = useState(false);
|
||||||
const [isWarningPopupVisible, setIsWarningPopupVisible] = useState(false);
|
const [isWarningPopupVisible, setIsWarningPopupVisible] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
const optionalTermsData = useSelector((state) =>
|
const optionalTermsData = useSelector((state) =>
|
||||||
state.home.termsData?.data?.terms.find(term => term.trmsTpCd === "MST00405")
|
state.home.termsData?.data?.terms.find(term => term.trmsTpCd === "MST00405")
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -46,7 +57,7 @@ const OptionalTermsConfirm = ({ open }) => {
|
|||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
// 약관 동의할 항목들 (string array)
|
// 약관 동의할 항목들 (string array)
|
||||||
const termsList = ["TID0000222", "TID0000223", "TID0000232"];
|
const termsList = ["TID0000222", "TID0000223", "TID0000232"];
|
||||||
|
|
||||||
// 동의하지 않을 항목들 (빈 배열)
|
// 동의하지 않을 항목들 (빈 배열)
|
||||||
const notTermsList = [];
|
const notTermsList = [];
|
||||||
|
|
||||||
@@ -67,15 +78,15 @@ const OptionalTermsConfirm = ({ open }) => {
|
|||||||
setIsWarningPopupVisible(true);
|
setIsWarningPopupVisible(true);
|
||||||
}
|
}
|
||||||
}, [isChecked, dispatch]);
|
}, [isChecked, dispatch]);
|
||||||
|
|
||||||
const handleCloseWarningPopup = useCallback(() => {
|
const handleCloseWarningPopup = useCallback(() => {
|
||||||
setIsWarningPopupVisible(false);
|
setIsWarningPopupVisible(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleDontAskAgain = () => {
|
const handleDontAskAgain = useCallback(() => {
|
||||||
console.log("Don't Ask Again 처리 필요");
|
console.log("Don't Ask Again 처리 필요");
|
||||||
dispatch(setHidePopup());
|
dispatch(setHidePopup());
|
||||||
};
|
},[dispatch]);
|
||||||
|
|
||||||
if (isTermsPopupVisible) {
|
if (isTermsPopupVisible) {
|
||||||
return (
|
return (
|
||||||
@@ -144,7 +155,7 @@ const OptionalTermsConfirm = ({ open }) => {
|
|||||||
<div className={css.mainContent}>
|
<div className={css.mainContent}>
|
||||||
<div className={css.checkboxSection}>
|
<div className={css.checkboxSection}>
|
||||||
<div className={css.checkboxArea}>
|
<div className={css.checkboxArea}>
|
||||||
<TCheckBoxSquare
|
<TCheckBoxSquare
|
||||||
selected={isChecked}
|
selected={isChecked}
|
||||||
onToggle={handleCheckboxToggle}
|
onToggle={handleCheckboxToggle}
|
||||||
spotlightId="optional-checkbox"
|
spotlightId="optional-checkbox"
|
||||||
@@ -157,34 +168,34 @@ const OptionalTermsConfirm = ({ open }) => {
|
|||||||
type="terms"
|
type="terms"
|
||||||
ariaLabel={$L("View Optional Terms")}
|
ariaLabel={$L("View Optional Terms")}
|
||||||
>
|
>
|
||||||
<div className={css.termTitle}>Optional Terms</div>
|
<div className={css.termTitle}>Optional Terms</div>
|
||||||
</TButton>
|
</TButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={css.descriptionSection}>
|
<div className={css.descriptionSection}>
|
||||||
<div className={css.description}>
|
<div className={css.description}>
|
||||||
Get recommendations, special offers, and ads tailored just for you.
|
Get recommendations, special offers, and ads tailored just for you.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={css.buttonSection}>
|
<div className={css.buttonSection}>
|
||||||
<div className={css.buttonGroup}>
|
<div className={css.buttonGroup}>
|
||||||
<TButton
|
<TButton
|
||||||
onClick={handleAgree}
|
onClick={handleAgree}
|
||||||
spotlightId="agree-button"
|
spotlightId="agree-button"
|
||||||
className={css.agreeButton}
|
className={css.agreeButton}
|
||||||
>
|
>
|
||||||
{$L('Agree')}
|
{$L('Agree')}
|
||||||
</TButton>
|
</TButton>
|
||||||
<TButton
|
<TButton
|
||||||
onClick={handleMainPopupClose}
|
onClick={handleMainPopupClose}
|
||||||
spotlightId="not-now-button"
|
spotlightId="not-now-button"
|
||||||
className={css.notNowButton}
|
className={css.notNowButton}
|
||||||
>
|
>
|
||||||
{$L('Not Now')}
|
{$L('Not Now')}
|
||||||
</TButton>
|
</TButton>
|
||||||
<TButton
|
<TButton
|
||||||
onClick={handleDontAskAgain}
|
onClick={handleDontAskAgain}
|
||||||
spotlightId="dont-ask-button"
|
spotlightId="dont-ask-button"
|
||||||
className={css.dontAskButton}
|
className={css.dontAskButton}
|
||||||
|
|||||||
@@ -1,17 +1,28 @@
|
|||||||
// src/components/Optional/OptionalTermsConfirm.jsx
|
// src/components/Optional/OptionalTermsConfirm.jsx
|
||||||
|
|
||||||
import React, { useEffect, useCallback, useState } from 'react';
|
import React, {
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
useCallback,
|
||||||
import TNewPopUp from '../TPopUp/TNewPopUp';
|
useEffect,
|
||||||
import TButton from '../TButton/TButton';
|
useState,
|
||||||
import TCheckBoxSquare from '../TCheckBox/TCheckBoxSquare';
|
} from 'react';
|
||||||
import TButtonScroller from '../TButtonScroller/TButtonScroller';
|
|
||||||
import { $L, scaleH, scaleW } from '../../utils/helperMethods';
|
import {
|
||||||
|
useDispatch,
|
||||||
|
useSelector,
|
||||||
|
} from 'react-redux';
|
||||||
|
|
||||||
|
import Spotlight from '@enact/spotlight';
|
||||||
|
|
||||||
import { setHidePopup } from '../../actions/commonActions';
|
import { setHidePopup } from '../../actions/commonActions';
|
||||||
import { setMyPageTermsAgree } from '../../actions/myPageActions';
|
import { setMyPageTermsAgree } from '../../actions/myPageActions';
|
||||||
|
import {
|
||||||
|
$L,
|
||||||
|
scaleH,
|
||||||
|
scaleW,
|
||||||
|
} from '../../utils/helperMethods';
|
||||||
|
import TButtonScroller from '../TButtonScroller/TButtonScroller';
|
||||||
|
import TNewPopUp from '../TPopUp/TNewPopUp';
|
||||||
import css from './OptionalTermsConfirmBottom.module.less';
|
import css from './OptionalTermsConfirmBottom.module.less';
|
||||||
import cssPopup from '../TPopUp/TNewPopUp.module.less';
|
|
||||||
import Spotlight from "@enact/spotlight";
|
|
||||||
|
|
||||||
const OptionalTermsConfirm = ({ open }) => {
|
const OptionalTermsConfirm = ({ open }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@@ -19,9 +30,9 @@ const OptionalTermsConfirm = ({ open }) => {
|
|||||||
const [isChecked, setIsChecked] = useState(false);
|
const [isChecked, setIsChecked] = useState(false);
|
||||||
const [isTermsPopupVisible, setIsTermsPopupVisible] = useState(false);
|
const [isTermsPopupVisible, setIsTermsPopupVisible] = useState(false);
|
||||||
const [isWarningPopupVisible, setIsWarningPopupVisible] = useState(false);
|
const [isWarningPopupVisible, setIsWarningPopupVisible] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
const optionalTermsData = useSelector((state) =>
|
|
||||||
|
const optionalTermsData = useSelector((state) =>
|
||||||
state.home.termsData?.data?.terms.find(term => term.trmsTpCd === "MST00405")
|
state.home.termsData?.data?.terms.find(term => term.trmsTpCd === "MST00405")
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -37,13 +48,13 @@ const OptionalTermsConfirm = ({ open }) => {
|
|||||||
}
|
}
|
||||||
}, [open, isTermsPopupVisible, isWarningPopupVisible]);
|
}, [open, isTermsPopupVisible, isWarningPopupVisible]);
|
||||||
|
|
||||||
const handleMainPopupClose = useCallback(() => {
|
// const handleMainPopupClose = useCallback(() => {
|
||||||
dispatch(setHidePopup());
|
// dispatch(setHidePopup());
|
||||||
}, [dispatch]);
|
// }, [dispatch]);
|
||||||
|
|
||||||
const handleCheckboxToggle = useCallback(({ selected }) => {
|
// const handleCheckboxToggle = useCallback(({ selected }) => {
|
||||||
setIsChecked(selected);
|
// setIsChecked(selected);
|
||||||
}, []);
|
// }, []);
|
||||||
|
|
||||||
const handleViewTermsClick = useCallback(() => {
|
const handleViewTermsClick = useCallback(() => {
|
||||||
setIsTermsPopupVisible(true);
|
setIsTermsPopupVisible(true);
|
||||||
@@ -66,20 +77,20 @@ const OptionalTermsConfirm = ({ open }) => {
|
|||||||
const handleAgreeTest = useCallback(() => {
|
const handleAgreeTest = useCallback(() => {
|
||||||
console.log("handleAgreeTest");
|
console.log("handleAgreeTest");
|
||||||
Spotlight.pause();
|
Spotlight.pause();
|
||||||
setIsTermsPopupVisible(false);
|
setIsTermsPopupVisible(false);
|
||||||
// 상태 업데이트 후 DOM이 완전히 렌더링될 때까지 기다린 후 포커스
|
// 상태 업데이트 후 DOM이 완전히 렌더링될 때까지 기다린 후 포커스
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
Spotlight.resume();
|
Spotlight.resume();
|
||||||
Spotlight.focus("optional-terms-confirm-popup");
|
Spotlight.focus("optional-terms-confirm-popup");
|
||||||
}, 500); // 50ms에서 100ms로 증가
|
}, 500); // 50ms에서 100ms로 증가
|
||||||
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleAgree = useCallback(() => {
|
const handleAgree = useCallback(() => {
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
// 약관 동의할 항목들 (string array)
|
// 약관 동의할 항목들 (string array)
|
||||||
const termsList = ["TID0000222", "TID0000223", "TID0000232"];
|
const termsList = ["TID0000222", "TID0000223", "TID0000232"];
|
||||||
|
|
||||||
// 동의하지 않을 항목들 (빈 배열)
|
// 동의하지 않을 항목들 (빈 배열)
|
||||||
const notTermsList = [];
|
const notTermsList = [];
|
||||||
|
|
||||||
@@ -163,7 +174,7 @@ const OptionalTermsConfirm = ({ open }) => {
|
|||||||
return (
|
return (
|
||||||
<TNewPopUp
|
<TNewPopUp
|
||||||
kind="optionalConfirm"
|
kind="optionalConfirm"
|
||||||
open={open}
|
open={open}
|
||||||
spotlightId="optional-terms-confirm-popup"
|
spotlightId="optional-terms-confirm-popup"
|
||||||
spotlightRestrict="self-only"
|
spotlightRestrict="self-only"
|
||||||
className={css.optionalConfirmPopup}
|
className={css.optionalConfirmPopup}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export default function THeader({
|
|||||||
ariaLabel,
|
ariaLabel,
|
||||||
children,
|
children,
|
||||||
kind,
|
kind,
|
||||||
|
sponserImage,
|
||||||
...rest
|
...rest
|
||||||
}) {
|
}) {
|
||||||
const convertedTitle = useMemo(() => {
|
const convertedTitle = useMemo(() => {
|
||||||
@@ -86,6 +87,17 @@ export default function THeader({
|
|||||||
</Marquee>
|
</Marquee>
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
|
{sponserImage &&(
|
||||||
|
<div className={css.sponserImgBox}>
|
||||||
|
<CustomImage
|
||||||
|
src={sponserImage}
|
||||||
|
className={css.sponserImg}
|
||||||
|
/>
|
||||||
|
<div className={css.sponserTextBox}>
|
||||||
|
SPONSORED BY
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
.title {
|
.title {
|
||||||
width: 1788px;
|
width: 1788px;
|
||||||
font-size: 42px;
|
font-size: 42px;
|
||||||
@@ -42,3 +42,24 @@
|
|||||||
box-shadow: 0px 6px 30px 0 rgba(0, 0, 0, 0.4);
|
box-shadow: 0px 6px 30px 0 rgba(0, 0, 0, 0.4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sponserImgBox {
|
||||||
|
position:absolute;
|
||||||
|
right:0;
|
||||||
|
top:0;
|
||||||
|
height:30px;
|
||||||
|
display:flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
.sponserImg {
|
||||||
|
height:30px;
|
||||||
|
}
|
||||||
|
.sponserTextBox {
|
||||||
|
padding:3px;
|
||||||
|
background-color: #474747;
|
||||||
|
color:rgba(255, 255, 255, 0.5);
|
||||||
|
font-size:14px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
> img {
|
> img {
|
||||||
.size(@w: inherit, @h: inherit);
|
.size(@w: inherit, @h: inherit);
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
border: solid 1px #f0f0f0;
|
// border: solid 1px #f0f0f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// discount rate
|
// discount rate
|
||||||
@@ -144,7 +144,7 @@
|
|||||||
> img {
|
> img {
|
||||||
.size(@w: 288px, @h: 288px);
|
.size(@w: 288px, @h: 288px);
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
border: solid 1px #f0f0f0;
|
// border: solid 1px #f0f0f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// discount rate
|
// discount rate
|
||||||
|
|||||||
@@ -483,7 +483,7 @@
|
|||||||
.default-style();
|
.default-style();
|
||||||
|
|
||||||
.scrollInfo {
|
.scrollInfo {
|
||||||
width: 900px;
|
width: 850px;
|
||||||
background-color: @BG_COLOR_01;
|
background-color: @BG_COLOR_01;
|
||||||
color: @COLOR_GRAY03;
|
color: @COLOR_GRAY03;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
import React, {
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import {
|
||||||
|
useDispatch,
|
||||||
|
useSelector,
|
||||||
|
} from 'react-redux';
|
||||||
|
|
||||||
import { getDeviceAdditionInfo } from "../../actions/deviceActions";
|
import { getDeviceAdditionInfo } from '../../actions/deviceActions';
|
||||||
import { scaleH, scaleW } from "../../utils/helperMethods";
|
import {
|
||||||
|
scaleH,
|
||||||
|
scaleW,
|
||||||
|
} from '../../utils/helperMethods';
|
||||||
|
|
||||||
export default function TQRCode({
|
export default function TQRCode({
|
||||||
isBillingProductVisible,
|
isBillingProductVisible,
|
||||||
@@ -51,6 +60,6 @@ export default function TQRCode({
|
|||||||
correctLevel: window.QRCode.CorrectLevel.L,
|
correctLevel: window.QRCode.CorrectLevel.L,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [text, deviceInfo, entryMenu, nowMenu]);
|
}, [text, deviceInfo, entryMenu, nowMenu, width, height]);
|
||||||
return <div aria-label={ariaLabel} ref={qrcodeRef} />;
|
return <div aria-label={ariaLabel} ref={qrcodeRef} />;
|
||||||
}
|
}
|
||||||
|
|||||||
102
com.twin.app.shoptime/src/components/TQRCode/TQRCodeNew.jsx
Normal file
102
com.twin.app.shoptime/src/components/TQRCode/TQRCodeNew.jsx
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import React, {
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
useDispatch,
|
||||||
|
useSelector,
|
||||||
|
} from 'react-redux';
|
||||||
|
|
||||||
|
import { getDeviceAdditionInfo } from '../../actions/deviceActions';
|
||||||
|
import {
|
||||||
|
scaleH,
|
||||||
|
scaleW,
|
||||||
|
} from '../../utils/helperMethods';
|
||||||
|
|
||||||
|
export default function TQRCodeNew({
|
||||||
|
isBillingProductVisible,
|
||||||
|
ariaLabel,
|
||||||
|
text,
|
||||||
|
width = "128",
|
||||||
|
height = "128",
|
||||||
|
}) {
|
||||||
|
const qrcodeRef = useRef(null);
|
||||||
|
const deviceInfo = useSelector((state) => state.device.deviceInfo);
|
||||||
|
const { entryMenu, nowMenu } = useSelector((state) => state.common.menu);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!deviceInfo) {
|
||||||
|
dispatch(getDeviceAdditionInfo());
|
||||||
|
}
|
||||||
|
}, [deviceInfo, dispatch]);
|
||||||
|
|
||||||
|
const applyCircularMask = (scaledWidth, scaledHeight) => {
|
||||||
|
if (!qrcodeRef.current) return;
|
||||||
|
|
||||||
|
const canvas = qrcodeRef.current.querySelector('canvas');
|
||||||
|
if (!canvas) return;
|
||||||
|
|
||||||
|
const radius = scaledWidth / 2;
|
||||||
|
|
||||||
|
// 원본 canvas 저장
|
||||||
|
const tempCanvas = document.createElement('canvas');
|
||||||
|
tempCanvas.width = scaledWidth;
|
||||||
|
tempCanvas.height = scaledHeight;
|
||||||
|
const tempCtx = tempCanvas.getContext('2d');
|
||||||
|
tempCtx.drawImage(canvas, 0, 0);
|
||||||
|
|
||||||
|
// 원본 canvas 초기화
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.clearRect(0, 0, scaledWidth, scaledHeight);
|
||||||
|
|
||||||
|
// 원형 마스크 적용
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(radius, radius, radius, 0, Math.PI * 2);
|
||||||
|
ctx.clip();
|
||||||
|
|
||||||
|
// 이미지 다시 그리기
|
||||||
|
ctx.drawImage(tempCanvas, 0, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window === "object" && entryMenu && nowMenu) {
|
||||||
|
if (qrcodeRef.current) {
|
||||||
|
while (qrcodeRef.current.firstChild) {
|
||||||
|
qrcodeRef.current.removeChild(qrcodeRef.current.firstChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// nowMenu 데이터를 Base64로 인코딩
|
||||||
|
const encodedNowMenu = encodeURIComponent(nowMenu);
|
||||||
|
const encodeEntryMenu = encodeURIComponent(entryMenu);
|
||||||
|
|
||||||
|
let idx;
|
||||||
|
|
||||||
|
if (deviceInfo === null || !deviceInfo) {
|
||||||
|
idx = 0;
|
||||||
|
} else {
|
||||||
|
idx = deviceInfo?.dvcIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scaledWidth = scaleW(width);
|
||||||
|
const scaledHeight = scaleH(height);
|
||||||
|
|
||||||
|
const qrcode = new window.QRCode(qrcodeRef.current, {
|
||||||
|
text: isBillingProductVisible
|
||||||
|
? text
|
||||||
|
: `${text}&entryMenu=${encodeEntryMenu}&nowMenu=${encodedNowMenu}&idx=${idx}`,
|
||||||
|
width: scaledWidth,
|
||||||
|
height: scaledHeight,
|
||||||
|
correctLevel: window.QRCode.CorrectLevel.L,
|
||||||
|
});
|
||||||
|
|
||||||
|
// QR코드 생성 완료 후 원형 마스킹 적용
|
||||||
|
setTimeout(() => {
|
||||||
|
applyCircularMask(scaledWidth, scaledHeight);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}, [text, deviceInfo, entryMenu, nowMenu, isBillingProductVisible, width, height]);
|
||||||
|
|
||||||
|
return <div aria-label={ariaLabel} ref={qrcodeRef} />;
|
||||||
|
}
|
||||||
@@ -1,16 +1,27 @@
|
|||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import compose from 'ramda/src/compose';
|
import compose from 'ramda/src/compose';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
|
||||||
import { Job } from '@enact/core/util';
|
import { Job } from '@enact/core/util';
|
||||||
import { Marquee, MarqueeController } from '@enact/sandstone/Marquee';
|
import {
|
||||||
|
Marquee,
|
||||||
|
MarqueeController,
|
||||||
|
} from '@enact/sandstone/Marquee';
|
||||||
import Spottable from '@enact/spotlight/Spottable';
|
import Spottable from '@enact/spotlight/Spottable';
|
||||||
|
|
||||||
import css from './TabItemSub.module.less';
|
|
||||||
import { sendLogTotalRecommend } from '../../actions/logActions';
|
import { sendLogTotalRecommend } from '../../actions/logActions';
|
||||||
import { LOG_CONTEXT_NAME, LOG_MESSAGE_ID } from '../../utils/Config';
|
import {
|
||||||
import { useDispatch } from 'react-redux';
|
LOG_CONTEXT_NAME,
|
||||||
|
LOG_MESSAGE_ID,
|
||||||
|
} from '../../utils/Config';
|
||||||
|
import css from './TabItemSub.module.less';
|
||||||
|
|
||||||
const SpottableComponent = Spottable('div');
|
const SpottableComponent = Spottable('div');
|
||||||
|
|
||||||
@@ -122,11 +133,11 @@ const TabItemBase = ({
|
|||||||
<>
|
<>
|
||||||
{subtitle && (
|
{subtitle && (
|
||||||
<div className={css.textWithIcon}>
|
<div className={css.textWithIcon}>
|
||||||
{IconComponent && (
|
{/* {IconComponent && (
|
||||||
<span className={css.iconWrapper}>
|
<span className={css.iconWrapper}>
|
||||||
<IconComponent iconType={focused ? 'focused' : selected ? 'selected' : 'normal'} />
|
<IconComponent iconType={focused ? 'focused' : selected ? 'selected' : 'normal'} />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)} */}
|
||||||
<Marquee
|
<Marquee
|
||||||
marqueeDisabled={!focused}
|
marqueeDisabled={!focused}
|
||||||
marqueeOn={'focus'}
|
marqueeOn={'focus'}
|
||||||
|
|||||||
@@ -172,23 +172,8 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
|||||||
break;
|
break;
|
||||||
//브랜드
|
//브랜드
|
||||||
case 10300:
|
case 10300:
|
||||||
result = [
|
result =
|
||||||
// NBCU 브랜드 (하드코딩)
|
data?.shortFeaturedBrands?.map((item) => ({
|
||||||
{
|
|
||||||
icons: FeaturedBrandIcon,
|
|
||||||
id: 'nbcu-brand',
|
|
||||||
path: 'assets/images/featuredBrands/nbcu.svg',
|
|
||||||
patncNm: 'NBCU',
|
|
||||||
spotlightId: 'spotlight_featuredbrand_nbcu',
|
|
||||||
target: [
|
|
||||||
{
|
|
||||||
name: panel_names.FEATURED_BRANDS_PANEL,
|
|
||||||
panelInfo: { from: 'gnb', patnrId: 'NBCU' },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
// API에서 가져온 기존 브랜드들
|
|
||||||
...(data?.shortFeaturedBrands?.map((item) => ({
|
|
||||||
icons: FeaturedBrandIcon,
|
icons: FeaturedBrandIcon,
|
||||||
id: item.patnrId,
|
id: item.patnrId,
|
||||||
path: item.patncLogoPath,
|
path: item.patncLogoPath,
|
||||||
@@ -200,8 +185,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
|
|||||||
panelInfo: { from: 'gnb', patnrId: item.patnrId },
|
panelInfo: { from: 'gnb', patnrId: item.patnrId },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})) || []),
|
})) || [];
|
||||||
];
|
|
||||||
break;
|
break;
|
||||||
//
|
//
|
||||||
case 10600:
|
case 10600:
|
||||||
|
|||||||
@@ -1157,11 +1157,6 @@ const VideoPlayerBase = class extends React.Component {
|
|||||||
// detection of when "more" is pressed vs when the state is updated is mismatched. Using an
|
// detection of when "more" is pressed vs when the state is updated is mismatched. Using an
|
||||||
// instance variable that's only set and used for this express purpose seems cleanest.
|
// instance variable that's only set and used for this express purpose seems cleanest.
|
||||||
|
|
||||||
// TabContainerV2가 표시 중이면 자동으로 닫지 않음
|
|
||||||
if (this.props.tabContainerVersion === 2 && this.props.belowContentsVisible) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.autoCloseTimeout && !this.props.sideContentsVisible) {
|
if (this.props.autoCloseTimeout && !this.props.sideContentsVisible) {
|
||||||
this.autoCloseJob.startAfter(this.props.autoCloseTimeout);
|
this.autoCloseJob.startAfter(this.props.autoCloseTimeout);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useCallback, useEffect, useRef } from "react";
|
import React, { useCallback, useEffect, useRef } from "react";
|
||||||
|
|
||||||
export default function useScrollTo() {
|
export default function useScrollTo({ skipAutoScrollTop = false } = {}) {
|
||||||
const scrollTo = useRef();
|
const scrollTo = useRef();
|
||||||
|
|
||||||
const scrollTop = useCallback(
|
const scrollTop = useCallback(
|
||||||
@@ -23,8 +23,10 @@ export default function useScrollTo() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
scrollTop();
|
if (!skipAutoScrollTop) {
|
||||||
}, []);
|
scrollTop();
|
||||||
|
}
|
||||||
|
}, [skipAutoScrollTop]);
|
||||||
|
|
||||||
return { getScrollTo, scrollLeft, scrollTop, scrollToRef: scrollTo };
|
return { getScrollTo, scrollLeft, scrollTop, scrollToRef: scrollTo };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
|||||||
(action.type.includes('PANEL') || action.type === 'CLEAR_PANEL_HISTORY')
|
(action.type.includes('PANEL') || action.type === 'CLEAR_PANEL_HISTORY')
|
||||||
) {
|
) {
|
||||||
const caller = new Error().stack.split('\n')[1]?.trim();
|
const caller = new Error().stack.split('\n')[1]?.trim();
|
||||||
console.log(`[PANEL DEBUG] ${action.type} from: ${caller}`);
|
// console.log(`[PANEL DEBUG] ${action.type} from: ${caller}`);
|
||||||
console.log(' Payload:', action.payload);
|
// console.log(' Payload:', action.payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
// GNB 호출 식별을 위한 helper 함수
|
// GNB 호출 식별을 위한 helper 함수
|
||||||
@@ -81,7 +81,7 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
|||||||
const isGNB = isGNBCall();
|
const isGNB = isGNBCall();
|
||||||
const isOnTop = calculateIsOnTop(panelName); // 🎯 isOnTop 계산
|
const isOnTop = calculateIsOnTop(panelName); // 🎯 isOnTop 계산
|
||||||
if (DEBUG_MODE)
|
if (DEBUG_MODE)
|
||||||
console.log('[PANEL] PUSH_PANEL:', {
|
console.log(`[PANEL] PUSH_PANEL: ${panelName}`, {
|
||||||
panelName,
|
panelName,
|
||||||
panelInfo,
|
panelInfo,
|
||||||
isGNB,
|
isGNB,
|
||||||
@@ -106,7 +106,7 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
|||||||
const stateAfter = store.getState();
|
const stateAfter = store.getState();
|
||||||
const panelHistoryAfter = stateAfter.panelHistory;
|
const panelHistoryAfter = stateAfter.panelHistory;
|
||||||
const panelsAfter = stateAfter.panels.panels;
|
const panelsAfter = stateAfter.panels.panels;
|
||||||
console.log('[PANEL_HISTORY] After PUSH_PANEL:', {
|
console.log(`[PANEL_HISTORY] PUSH_PANEL: ${panelName}`, {
|
||||||
panelHistory: panelHistoryAfter,
|
panelHistory: panelHistoryAfter,
|
||||||
panels: panelsAfter,
|
panels: panelsAfter,
|
||||||
});
|
});
|
||||||
@@ -125,11 +125,19 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
|||||||
// POP 후 top panel을 기록 (이전 패널로 돌아감)
|
// POP 후 top panel을 기록 (이전 패널로 돌아감)
|
||||||
if (panels.length > 0) {
|
if (panels.length > 0) {
|
||||||
const topPanel = panels[panels.length - 1];
|
const topPanel = panels[panels.length - 1];
|
||||||
|
if (DEBUG_MODE) {
|
||||||
|
console.log(`[PANEL-TRACE] POP_PANEL middleware stack: ${topPanel?.name}`, {
|
||||||
|
stack: panels.map((p) => p.name),
|
||||||
|
topPanel: topPanel?.name,
|
||||||
|
payload: action.payload,
|
||||||
|
caller: new Error().stack?.split('\n')[2]?.trim(),
|
||||||
|
});
|
||||||
|
}
|
||||||
if (topPanel && topPanel.name) {
|
if (topPanel && topPanel.name) {
|
||||||
const isGNB = isGNBCall();
|
const isGNB = isGNBCall();
|
||||||
const isOnTop = calculateIsOnTop(topPanel.name); // 🎯 isOnTop 계산
|
const isOnTop = calculateIsOnTop(topPanel.name); // 🎯 isOnTop 계산
|
||||||
if (DEBUG_MODE)
|
if (DEBUG_MODE)
|
||||||
console.log('[PANEL] POP_PANEL:', {
|
console.log(`[PANEL] POP_PANEL: ${topPanel.name}`, {
|
||||||
panelName: topPanel.name,
|
panelName: topPanel.name,
|
||||||
panelInfo: topPanel.panelInfo || {},
|
panelInfo: topPanel.panelInfo || {},
|
||||||
isGNB,
|
isGNB,
|
||||||
@@ -154,7 +162,7 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
|||||||
const stateAfter = store.getState();
|
const stateAfter = store.getState();
|
||||||
const panelHistoryAfter = stateAfter.panelHistory;
|
const panelHistoryAfter = stateAfter.panelHistory;
|
||||||
const panelsAfter = stateAfter.panels.panels;
|
const panelsAfter = stateAfter.panels.panels;
|
||||||
console.log('[PANEL_HISTORY] After POP_PANEL:', {
|
console.log(`[PANEL_HISTORY] POP_PANEL: ${topPanel.name}`, {
|
||||||
panelHistory: panelHistoryAfter,
|
panelHistory: panelHistoryAfter,
|
||||||
panels: panelsAfter,
|
panels: panelsAfter,
|
||||||
});
|
});
|
||||||
@@ -177,7 +185,7 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
|||||||
const isGNB = isGNBCall();
|
const isGNB = isGNBCall();
|
||||||
const isOnTop = calculateIsOnTop(panelName); // 🎯 isOnTop 계산
|
const isOnTop = calculateIsOnTop(panelName); // 🎯 isOnTop 계산
|
||||||
if (DEBUG_MODE)
|
if (DEBUG_MODE)
|
||||||
console.log('[PANEL] UPDATE_PANEL:', {
|
console.log(`[PANEL] UPDATE_PANEL: ${panelName}`, {
|
||||||
panelName,
|
panelName,
|
||||||
panelInfo,
|
panelInfo,
|
||||||
isGNB,
|
isGNB,
|
||||||
@@ -202,7 +210,7 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
|||||||
const stateAfter = store.getState();
|
const stateAfter = store.getState();
|
||||||
const panelHistoryAfter = stateAfter.panelHistory;
|
const panelHistoryAfter = stateAfter.panelHistory;
|
||||||
const panelsAfter = stateAfter.panels.panels;
|
const panelsAfter = stateAfter.panels.panels;
|
||||||
console.log('[PANEL_HISTORY] After UPDATE_PANEL:', {
|
console.log(`[PANEL_HISTORY] UPDATE_PANEL: ${panelName}`, {
|
||||||
panelHistory: panelHistoryAfter,
|
panelHistory: panelHistoryAfter,
|
||||||
panels: panelsAfter,
|
panels: panelsAfter,
|
||||||
});
|
});
|
||||||
@@ -218,11 +226,15 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
|||||||
|
|
||||||
// RESET_PANELS: GNB 네비게이션 또는 완전 초기화
|
// RESET_PANELS: GNB 네비게이션 또는 완전 초기화
|
||||||
case types.RESET_PANELS: {
|
case types.RESET_PANELS: {
|
||||||
if (DEBUG_MODE)
|
if (DEBUG_MODE) {
|
||||||
console.log('[PANEL] RESET_PANELS:', {
|
const resetPanelNameForLog = (action.payload && action.payload.length > 0)
|
||||||
|
? action.payload[0].name
|
||||||
|
: 'homepanel';
|
||||||
|
console.log(`[PANEL] RESET_PANELS: ${resetPanelNameForLog}`, {
|
||||||
payload: action.payload,
|
payload: action.payload,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
}
|
||||||
if (DEBUG_MODE)
|
if (DEBUG_MODE)
|
||||||
console.log('[PANEL_HISTORY] Before RESET_PANELS:', store.getState().panelHistory);
|
console.log('[PANEL_HISTORY] Before RESET_PANELS:', store.getState().panelHistory);
|
||||||
|
|
||||||
@@ -284,7 +296,10 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
|
|||||||
const stateAfter = store.getState();
|
const stateAfter = store.getState();
|
||||||
const panelHistoryAfter = stateAfter.panelHistory;
|
const panelHistoryAfter = stateAfter.panelHistory;
|
||||||
const panelsAfter = stateAfter.panels.panels;
|
const panelsAfter = stateAfter.panels.panels;
|
||||||
console.log('[PANEL_HISTORY] After RESET_PANELS:', {
|
const resetPanelName = (action.payload && action.payload.length > 0)
|
||||||
|
? action.payload[0].name
|
||||||
|
: 'homepanel';
|
||||||
|
console.log(`[PANEL_HISTORY] RESET_PANELS: ${resetPanelName}`, {
|
||||||
panelHistory: panelHistoryAfter,
|
panelHistory: panelHistoryAfter,
|
||||||
panels: panelsAfter,
|
panels: panelsAfter,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -44,6 +44,17 @@ const initialState = {
|
|||||||
brandRecentlyAiredData: {
|
brandRecentlyAiredData: {
|
||||||
data: {},
|
data: {},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
brandShopByShowData: {
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
brandTopBannerData: {
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
// 🆕 [251210] patnrId=21 카테고리 그룹 데이터 저장소
|
||||||
|
brandShopByShowCategoryGroups: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const brandReducer = (state = initialState, action) => {
|
export const brandReducer = (state = initialState, action) => {
|
||||||
@@ -155,6 +166,51 @@ export const brandReducer = (state = initialState, action) => {
|
|||||||
brandRecentlyAiredData: action.payload,
|
brandRecentlyAiredData: action.payload,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
case types.GET_BRAND_SHOP_BY_SHOW: {
|
||||||
|
// 일부 응답은 리스트 없이 내려와 기존 데이터를 덮어 지우는 문제가 있어 조건부 병합
|
||||||
|
const prevData = state.brandShopByShowData?.data || {};
|
||||||
|
const nextData = action.payload?.data || {};
|
||||||
|
const hasNextList = Array.isArray(nextData.brandShopByShowContsList);
|
||||||
|
|
||||||
|
// 리스트가 없으면 이전 리스트 유지
|
||||||
|
const mergedData = hasNextList
|
||||||
|
? nextData
|
||||||
|
: { ...prevData, ...nextData, brandShopByShowContsList: prevData.brandShopByShowContsList };
|
||||||
|
|
||||||
|
// 🆕 [251210] patnrId=21인 경우 그룹 데이터 별도 저장
|
||||||
|
const updatedCategoryGroups = { ...state.brandShopByShowCategoryGroups };
|
||||||
|
|
||||||
|
if (action.payload?.patnrId === 21 || action.payload?.patnrId === "21") {
|
||||||
|
const patnrId = String(action.payload.patnrId);
|
||||||
|
|
||||||
|
// patnrId별 그룹 데이터가 없으면 초기화
|
||||||
|
if (!updatedCategoryGroups[patnrId]) {
|
||||||
|
updatedCategoryGroups[patnrId] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 현재 contsId에 대한 그룹 정보 저장
|
||||||
|
if (nextData.brandShopByShowContsInfo?.contsId) {
|
||||||
|
const contsId = nextData.brandShopByShowContsInfo.contsId;
|
||||||
|
updatedCategoryGroups[patnrId][contsId] = nextData.brandShopByShowContsInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
brandShopByShowData: {
|
||||||
|
...action.payload,
|
||||||
|
data: mergedData,
|
||||||
|
},
|
||||||
|
brandShopByShowCategoryGroups: updatedCategoryGroups,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case types.GET_BRAND_TOP_BANNER:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
brandTopBannerData: action.payload,
|
||||||
|
};
|
||||||
|
|
||||||
case types.SET_BRAND_LIVE_CHANNEL_UPCOMING:
|
case types.SET_BRAND_LIVE_CHANNEL_UPCOMING:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@@ -184,6 +240,25 @@ export const brandReducer = (state = initialState, action) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🆕 [251210] patnrId=21 카테고리 그룹 데이터 설정
|
||||||
|
case types.SET_BRAND_SHOP_BY_SHOW_CATEGORY_GROUPS: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
brandShopByShowCategoryGroups: {
|
||||||
|
...state.brandShopByShowCategoryGroups,
|
||||||
|
...action.payload,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🆕 [251210] patnrId=21 카테고리 그룹 데이터 초기화
|
||||||
|
case types.RESET_BRAND_SHOP_BY_SHOW_CATEGORY_GROUPS: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
brandShopByShowCategoryGroups: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const initialState = {
|
|||||||
* Cart Reducer
|
* Cart Reducer
|
||||||
* 장바구니 관련 상태를 관리합니다.
|
* 장바구니 관련 상태를 관리합니다.
|
||||||
*/
|
*/
|
||||||
export const cartReducer = (state = initialState, action) => {
|
export const cartReducer = (state = initialState, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
// 장바구니 조회 - API에서 가져온 전체 목록
|
// 장바구니 조회 - API에서 가져온 전체 목록
|
||||||
case types.GET_MY_INFO_CART_SEARCH:
|
case types.GET_MY_INFO_CART_SEARCH:
|
||||||
@@ -37,9 +37,9 @@ export const cartReducer = (state = initialState, action) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 장바구니에 상품 추가
|
// 장바구니에 상품 추가
|
||||||
case types.INSERT_MY_INFO_CART:
|
case types.INSERT_MY_INFO_CART:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
selectCart: {
|
selectCart: {
|
||||||
...state.selectCart,
|
...state.selectCart,
|
||||||
cartList: [...state.selectCart.cartList, action.payload],
|
cartList: [...state.selectCart.cartList, action.payload],
|
||||||
@@ -51,22 +51,22 @@ export const cartReducer = (state = initialState, action) => {
|
|||||||
case types.TOGGLE_CHECK_CART: {
|
case types.TOGGLE_CHECK_CART: {
|
||||||
const checkedItem = action.payload.item;
|
const checkedItem = action.payload.item;
|
||||||
const isChecked = action.payload.isChecked;
|
const isChecked = action.payload.isChecked;
|
||||||
|
|
||||||
let updatedCheckedList = state.selectCart?.checkedItems || [];
|
let updatedCheckedList = state.selectCart?.checkedItems || [];
|
||||||
|
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
const itemExists = updatedCheckedList.some(
|
const itemExists = updatedCheckedList.some(
|
||||||
item => item.prodSno === checkedItem.prodSno
|
item => item.prodSno === checkedItem.prodSno
|
||||||
);
|
);
|
||||||
if (!itemExists) {
|
if (!itemExists) {
|
||||||
updatedCheckedList = [...updatedCheckedList, checkedItem];
|
updatedCheckedList = [...updatedCheckedList, checkedItem];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
updatedCheckedList = updatedCheckedList.filter(
|
updatedCheckedList = updatedCheckedList.filter(
|
||||||
item => item.prodSno !== checkedItem.prodSno
|
item => item.prodSno !== checkedItem.prodSno
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
selectCart: {
|
selectCart: {
|
||||||
@@ -78,7 +78,7 @@ export const cartReducer = (state = initialState, action) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 장바구니에서 상품 삭제
|
// 장바구니에서 상품 삭제
|
||||||
case types.DELETE_MY_INFO_CART:
|
case types.DELETE_MY_INFO_CART:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
getMyinfoCartSearch: {
|
getMyinfoCartSearch: {
|
||||||
@@ -96,7 +96,7 @@ export const cartReducer = (state = initialState, action) => {
|
|||||||
getMyinfoCartSearch: {
|
getMyinfoCartSearch: {
|
||||||
...state.getMyinfoCartSearch,
|
...state.getMyinfoCartSearch,
|
||||||
cartList: (state.getMyinfoCartSearch.cartList || []).map(item =>
|
cartList: (state.getMyinfoCartSearch.cartList || []).map(item =>
|
||||||
item.prodSno === action.payload.prodSno
|
item.prodSno === action.payload.prodSno
|
||||||
? { ...item, ...action.payload }
|
? { ...item, ...action.payload }
|
||||||
: item
|
: item
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -94,6 +94,12 @@ export const panelsReducer = (state = initialState, action) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case types.POP_PANEL: {
|
case types.POP_PANEL: {
|
||||||
|
// console.log('[💜UNIQUE_PANEL_STACK💜] POP_PANEL reducer START', {
|
||||||
|
// targetPanel: action.payload || 'last_panel',
|
||||||
|
// currentPanels: state.panels.map((p) => p.name),
|
||||||
|
// timestamp: Date.now(),
|
||||||
|
// });
|
||||||
|
|
||||||
dlog('[panelReducer] 🔴 POP_PANEL START', {
|
dlog('[panelReducer] 🔴 POP_PANEL START', {
|
||||||
targetPanel: action.payload || 'last_panel',
|
targetPanel: action.payload || 'last_panel',
|
||||||
currentPanels: state.panels.map((p) => p.name),
|
currentPanels: state.panels.map((p) => p.name),
|
||||||
@@ -118,6 +124,13 @@ export const panelsReducer = (state = initialState, action) => {
|
|||||||
resultPanels = state.panels.slice(0, state.panels.length - 1);
|
resultPanels = state.panels.slice(0, state.panels.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// console.log('[💜UNIQUE_PANEL_STACK💜] POP_PANEL reducer END', {
|
||||||
|
// resultPanels: resultPanels.map((p) => p.name),
|
||||||
|
// panelCount: resultPanels.length,
|
||||||
|
// lastAction,
|
||||||
|
// timestamp: Date.now(),
|
||||||
|
// });
|
||||||
|
|
||||||
dlog('[panelReducer] 🔴 POP_PANEL END', {
|
dlog('[panelReducer] 🔴 POP_PANEL END', {
|
||||||
resultPanels: resultPanels.map((p) => p.name),
|
resultPanels: resultPanels.map((p) => p.name),
|
||||||
lastAction,
|
lastAction,
|
||||||
@@ -132,6 +145,60 @@ export const panelsReducer = (state = initialState, action) => {
|
|||||||
case types.UPDATE_PANEL: {
|
case types.UPDATE_PANEL: {
|
||||||
let lastIndex = -1;
|
let lastIndex = -1;
|
||||||
let lastAction = 'update';
|
let lastAction = 'update';
|
||||||
|
const hasDetailPanel = state.panels.some((p) => p.name === panel_names.DETAIL_PANEL);
|
||||||
|
const isPlayerPanel =
|
||||||
|
action.payload.name === panel_names.PLAYER_PANEL ||
|
||||||
|
action.payload.name === panel_names.PLAYER_PANEL_NEW;
|
||||||
|
const existingPanel = state.panels.find((p) => p.name === action.payload.name);
|
||||||
|
let nextPanelInfo = action.payload.panelInfo || {};
|
||||||
|
|
||||||
|
// lockModalFalse 플래그 처리: DetailPanel이 스택에 있거나 lock이 이미 true면 modal=true 업데이트를 차단
|
||||||
|
if (isPlayerPanel && existingPanel) {
|
||||||
|
const lockFlag =
|
||||||
|
existingPanel.panelInfo?.lockModalFalse === true || nextPanelInfo.lockModalFalse === true;
|
||||||
|
|
||||||
|
// unlock 명시 시 그대로 진행
|
||||||
|
if (nextPanelInfo.lockModalFalse === false) {
|
||||||
|
// do nothing
|
||||||
|
} else if (lockFlag && nextPanelInfo.modal === true) {
|
||||||
|
nextPanelInfo = {
|
||||||
|
...nextPanelInfo,
|
||||||
|
modal: false,
|
||||||
|
modalContainerId: undefined,
|
||||||
|
lockModalFalse: true,
|
||||||
|
modalStyle: undefined,
|
||||||
|
modalScale: undefined,
|
||||||
|
shouldShrinkTo1px: false,
|
||||||
|
isHidden: false,
|
||||||
|
};
|
||||||
|
} else if (lockFlag && nextPanelInfo.modal === undefined && hasDetailPanel) {
|
||||||
|
nextPanelInfo = {
|
||||||
|
...nextPanelInfo,
|
||||||
|
modal:
|
||||||
|
existingPanel.panelInfo?.modal === true ? false : existingPanel.panelInfo?.modal,
|
||||||
|
modalContainerId:
|
||||||
|
existingPanel.panelInfo?.modal === true
|
||||||
|
? undefined
|
||||||
|
: existingPanel.panelInfo?.modalContainerId,
|
||||||
|
lockModalFalse: true,
|
||||||
|
modalStyle: existingPanel.panelInfo?.modal === true ? undefined : nextPanelInfo.modalStyle,
|
||||||
|
modalScale: existingPanel.panelInfo?.modal === true ? undefined : nextPanelInfo.modalScale,
|
||||||
|
shouldShrinkTo1px: false,
|
||||||
|
isHidden: false,
|
||||||
|
};
|
||||||
|
} else if (hasDetailPanel && nextPanelInfo.modal === true) {
|
||||||
|
// DetailPanel 존재 시 modal=true 업데이트 차단
|
||||||
|
nextPanelInfo = {
|
||||||
|
...nextPanelInfo,
|
||||||
|
modal: false,
|
||||||
|
modalContainerId: undefined,
|
||||||
|
modalStyle: undefined,
|
||||||
|
modalScale: undefined,
|
||||||
|
shouldShrinkTo1px: false,
|
||||||
|
isHidden: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
// 배열의 끝에서부터 시작하여 조건에 맞는 마지막 인덱스 찾기
|
// 배열의 끝에서부터 시작하여 조건에 맞는 마지막 인덱스 찾기
|
||||||
for (let i = state.panels.length - 1; i >= 0; i--) {
|
for (let i = state.panels.length - 1; i >= 0; i--) {
|
||||||
if (state.panels[i].name === action.payload.name) {
|
if (state.panels[i].name === action.payload.name) {
|
||||||
@@ -143,7 +210,7 @@ export const panelsReducer = (state = initialState, action) => {
|
|||||||
index === lastIndex
|
index === lastIndex
|
||||||
? {
|
? {
|
||||||
...panel,
|
...panel,
|
||||||
panelInfo: { ...panel.panelInfo, ...action.payload.panelInfo },
|
panelInfo: { ...panel.panelInfo, ...nextPanelInfo },
|
||||||
}
|
}
|
||||||
: panel
|
: panel
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -113,7 +113,8 @@ export const ACTIVE_POPUP = {
|
|||||||
toast: 'toast',
|
toast: 'toast',
|
||||||
optionalConfirm: 'optionalConfirm',
|
optionalConfirm: 'optionalConfirm',
|
||||||
energyPopup: 'energyPopup',
|
energyPopup: 'energyPopup',
|
||||||
addCartPopup: 'addCartPopup',
|
addCartPopup: 'addCartPopup',
|
||||||
|
scrollPopup: 'scrollPopup',
|
||||||
};
|
};
|
||||||
export const DEBUG_VIDEO_SUBTITLE_TEST = false;
|
export const DEBUG_VIDEO_SUBTITLE_TEST = false;
|
||||||
export const AUTO_SCROLL_DELAY = 600;
|
export const AUTO_SCROLL_DELAY = 600;
|
||||||
|
|||||||
@@ -458,7 +458,7 @@ const tap = fp.curry((fn, value) => {
|
|||||||
* @param {*} value 대상 값
|
* @param {*} value 대상 값
|
||||||
*/
|
*/
|
||||||
const trace = fp.curry((label, value) => {
|
const trace = fp.curry((label, value) => {
|
||||||
console.log(label, value);
|
// console.log(label, value);
|
||||||
return value;
|
return value;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,10 @@ import {
|
|||||||
setShowPopup,
|
setShowPopup,
|
||||||
} from '../../../actions/commonActions';
|
} from '../../../actions/commonActions';
|
||||||
import { sendLogTotalRecommend } from '../../../actions/logActions';
|
import { sendLogTotalRecommend } from '../../../actions/logActions';
|
||||||
import { popPanel } from '../../../actions/panelActions';
|
import {
|
||||||
|
popPanel,
|
||||||
|
pushPanel,
|
||||||
|
} from '../../../actions/panelActions';
|
||||||
import TButton from '../../../components/TButton/TButton';
|
import TButton from '../../../components/TButton/TButton';
|
||||||
import TPopUp from '../../../components/TPopUp/TPopUp';
|
import TPopUp from '../../../components/TPopUp/TPopUp';
|
||||||
import TQRCode from '../../../components/TQRCode/TQRCode';
|
import TQRCode from '../../../components/TQRCode/TQRCode';
|
||||||
@@ -249,13 +252,25 @@ export default function InformationContainer({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleCancel = useCallback(() => {
|
const handleCancel = useCallback(() => {
|
||||||
dispatch(setHidePopup());
|
// dispatch(setHidePopup());
|
||||||
}, [dispatch]);
|
const { patnrId, prdtId } = checkoutData.productList[0];
|
||||||
|
|
||||||
const handleDone = useCallback(() => {
|
|
||||||
dispatch(setHidePopup());
|
|
||||||
dispatch(popPanel());
|
dispatch(popPanel());
|
||||||
}, [dispatch]);
|
dispatch(
|
||||||
|
pushPanel({
|
||||||
|
name: Config.panel_names.DETAIL_PANEL,
|
||||||
|
panelInfo: { patnrId, prdtId },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, [dispatch, checkoutData]);
|
||||||
|
|
||||||
|
const { shippingAddressList, billingAddressList, cardInfo } = checkoutData || {};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!shippingAddressList || !billingAddressList || !cardInfo) {
|
||||||
|
// if (shippingAddressList || billingAddressList || cardInfo) { //확인용도로 반대로 테스트중.
|
||||||
|
dispatch(setShowPopup(Config.ACTIVE_POPUP.qrPopup));
|
||||||
|
}
|
||||||
|
}, [shippingAddressList, billingAddressList, cardInfo, dispatch])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -282,8 +297,8 @@ export default function InformationContainer({
|
|||||||
>
|
>
|
||||||
ADD/EDIT
|
ADD/EDIT
|
||||||
</TButton>
|
</TButton>
|
||||||
{checkoutData?.shippingAddressList && (
|
{shippingAddressList && (
|
||||||
<ShippingAddressCard list={checkoutData.shippingAddressList} onFocus={handleFocus} />
|
<ShippingAddressCard list={shippingAddressList} onFocus={handleFocus} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={css.listBox}>
|
<div className={css.listBox}>
|
||||||
@@ -300,8 +315,8 @@ export default function InformationContainer({
|
|||||||
{/* <div style={{ padding: '10px', textAlign: 'center', color: '#999' }}>
|
{/* <div style={{ padding: '10px', textAlign: 'center', color: '#999' }}>
|
||||||
Mock Billing Address
|
Mock Billing Address
|
||||||
</div> */}
|
</div> */}
|
||||||
{checkoutData?.billingAddressList && (
|
{billingAddressList && (
|
||||||
<BillingAddressCard list={checkoutData.billingAddressList} onFocus={handleFocus} />
|
<BillingAddressCard list={billingAddressList} onFocus={handleFocus} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={css.listBox}>
|
<div className={css.listBox}>
|
||||||
@@ -314,7 +329,7 @@ export default function InformationContainer({
|
|||||||
>
|
>
|
||||||
ADD/EDIT
|
ADD/EDIT
|
||||||
</TButton>
|
</TButton>
|
||||||
{checkoutData?.cardInfo && <PaymentCard list={checkoutData.cardInfo} />}
|
{cardInfo && <PaymentCard list={cardInfo} />}
|
||||||
</div>
|
</div>
|
||||||
<div className={css.listBox}>
|
<div className={css.listBox}>
|
||||||
<Subject title="OFFERS & PROMOTION" />
|
<Subject title="OFFERS & PROMOTION" />
|
||||||
@@ -355,7 +370,7 @@ export default function InformationContainer({
|
|||||||
'Please update your information and complete the payment on your mobile. By clicking the OK button, you will be redirected to the product details page'
|
'Please update your information and complete the payment on your mobile. By clicking the OK button, you will be redirected to the product details page'
|
||||||
)}
|
)}
|
||||||
</h3>
|
</h3>
|
||||||
<TButton className={css.popupBtn} onClick={handleDone}>
|
<TButton className={css.popupBtn} onClick={handleCancel}>
|
||||||
{$L('OK')}
|
{$L('OK')}
|
||||||
</TButton>
|
</TButton>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -280,6 +280,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
|
// console.log('[🟡UNIQUE_DETAIL_CLEANUP🟡] DetailPanel cleanup - calling finishModalMediaForce');
|
||||||
dispatch(finishModalMediaForce());
|
dispatch(finishModalMediaForce());
|
||||||
};
|
};
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
@@ -303,18 +304,25 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
const sourcePanel = panelInfo?.sourcePanel;
|
const sourcePanel = panelInfo?.sourcePanel;
|
||||||
const sourceMenu = panelInfo?.sourceMenu;
|
const sourceMenu = panelInfo?.sourceMenu;
|
||||||
|
|
||||||
|
// console.log('[🔴UNIQUE_DETAIL_UNMOUNT🔴] DetailPanel cleanup/unmount triggered', {
|
||||||
|
// sourcePanel,
|
||||||
|
// sourceMenu,
|
||||||
|
// panelsSnapshot: panels.map((p) => p.name),
|
||||||
|
// timestamp: Date.now(),
|
||||||
|
// });
|
||||||
|
|
||||||
// DetailPanel이 unmount되는 시점
|
// DetailPanel이 unmount되는 시점
|
||||||
console.log('[DetailPanel] unmount:', {
|
// console.log('[🔴UNIQUE_DETAIL_UNMOUNT🔴] DetailPanel unmount details:', {
|
||||||
sourcePanel,
|
// sourcePanel,
|
||||||
sourceMenu,
|
// sourceMenu,
|
||||||
timestamp: Date.now(),
|
// timestamp: Date.now(),
|
||||||
});
|
// });
|
||||||
|
|
||||||
// sourcePanel에 따른 상태 업데이트
|
// sourcePanel에 따른 상태 업데이트
|
||||||
switch (sourcePanel) {
|
switch (sourcePanel) {
|
||||||
case panel_names.PLAYER_PANEL: {
|
case panel_names.PLAYER_PANEL: {
|
||||||
// PlayerPanel에서 온 경우: PlayerPanel에 detailPanelClosed flag 전달
|
// PlayerPanel에서 온 경우: PlayerPanel에 detailPanelClosed flag 전달
|
||||||
console.log('[DetailPanel] unmount - PlayerPanel에 detailPanelClosed flag 전달');
|
console.log('[PANEL][DetailPanel] unmount - PlayerPanel에 detailPanelClosed flag 전달');
|
||||||
dispatch(
|
dispatch(
|
||||||
updatePanel({
|
updatePanel({
|
||||||
name: panel_names.PLAYER_PANEL,
|
name: panel_names.PLAYER_PANEL,
|
||||||
@@ -323,6 +331,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
detailPanelClosedAt: Date.now(), // ✅ 시점 기록
|
detailPanelClosedAt: Date.now(), // ✅ 시점 기록
|
||||||
detailPanelClosedFromSource: sourceMenu, // ✅ 출처
|
detailPanelClosedFromSource: sourceMenu, // ✅ 출처
|
||||||
lastFocusedTargetId: panelInfo?.lastFocusedTargetId, // ✅ 포커스 복원 타겟 전달
|
lastFocusedTargetId: panelInfo?.lastFocusedTargetId, // ✅ 포커스 복원 타겟 전달
|
||||||
|
lockModalFalse: false, // Detail 종료 시 lock 해제
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -376,6 +385,14 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
const sourcePanel = panelInfo?.sourcePanel;
|
const sourcePanel = panelInfo?.sourcePanel;
|
||||||
const sourceMenu = panelInfo?.sourceMenu;
|
const sourceMenu = panelInfo?.sourceMenu;
|
||||||
|
|
||||||
|
// console.log('[🟠UNIQUE_DETAIL_BACK🟠] onBackClick triggered', {
|
||||||
|
// sourcePanel,
|
||||||
|
// sourceMenu,
|
||||||
|
// isCancelClick,
|
||||||
|
// currentPanels: panels.map((p) => p.name),
|
||||||
|
// timestamp: Date.now(),
|
||||||
|
// });
|
||||||
|
|
||||||
fp.pipe(
|
fp.pipe(
|
||||||
() => {
|
() => {
|
||||||
dispatch(clearAllToasts()); // BuyOption Toast 포함 모든 토스트 제거
|
dispatch(clearAllToasts()); // BuyOption Toast 포함 모든 토스트 제거
|
||||||
@@ -384,7 +401,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
switch (sourcePanel) {
|
switch (sourcePanel) {
|
||||||
case panel_names.PLAYER_PANEL:
|
case panel_names.PLAYER_PANEL:
|
||||||
// PlayerPanel에서 온 경우: 플레이어 비디오는 그대로 두고 모달만 정리
|
// PlayerPanel에서 온 경우: 플레이어 비디오는 그대로 두고 모달만 정리
|
||||||
console.log('[DetailPanel] onBackClick - PlayerPanel 출신: 모달 정리만 수행');
|
console.log('[🟠UNIQUE_DETAIL_BACK🟠] PlayerPanel 출신: 모달 정리만 수행');
|
||||||
dispatch(finishModalMediaForce()); // MEDIA_PANEL(ProductVideo) 강제 종료
|
dispatch(finishModalMediaForce()); // MEDIA_PANEL(ProductVideo) 강제 종료
|
||||||
dispatch(finishVideoPreview());
|
dispatch(finishVideoPreview());
|
||||||
break;
|
break;
|
||||||
@@ -393,17 +410,18 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
case panel_names.SEARCH_PANEL:
|
case panel_names.SEARCH_PANEL:
|
||||||
default:
|
default:
|
||||||
// HomePanel, SearchPanel 등에서 온 경우: 백그라운드 비디오 일시 중지
|
// HomePanel, SearchPanel 등에서 온 경우: 백그라운드 비디오 일시 중지
|
||||||
console.log(
|
// console.log(
|
||||||
'[DetailPanel] onBackClick - source panel:',
|
// '[🟠UNIQUE_DETAIL_BACK🟠] source panel:',
|
||||||
sourcePanel,
|
// sourcePanel,
|
||||||
'백그라운드 비디오 일시 중지'
|
// '백그라운드 비디오 일시 중지'
|
||||||
);
|
// );
|
||||||
dispatch(pauseFullscreenVideo()); // PLAYER_PANEL 비디오 중지
|
dispatch(pauseFullscreenVideo()); // PLAYER_PANEL 비디오 중지
|
||||||
dispatch(finishModalMediaForce()); // MEDIA_PANEL(ProductVideo) 강제 종료
|
dispatch(finishModalMediaForce()); // MEDIA_PANEL(ProductVideo) 강제 종료
|
||||||
dispatch(finishVideoPreview());
|
dispatch(finishVideoPreview());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// console.log('[🟠UNIQUE_DETAIL_BACK🟠] Calling popPanel(DETAIL_PANEL)');
|
||||||
dispatch(popPanel(panel_names.DETAIL_PANEL));
|
dispatch(popPanel(panel_names.DETAIL_PANEL));
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
@@ -425,7 +443,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
|
|
||||||
if (shouldUpdatePanel) {
|
if (shouldUpdatePanel) {
|
||||||
console.log(
|
console.log(
|
||||||
'[DetailPanel] onBackClick - PlayerPanel에 detailPanelClosed flag 전달'
|
'[PANEL][DetailPanel] onBackClick - PlayerPanel에 detailPanelClosed flag 전달'
|
||||||
);
|
);
|
||||||
dispatch(
|
dispatch(
|
||||||
updatePanel({
|
updatePanel({
|
||||||
@@ -1025,34 +1043,56 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
// ProductAllSection에 비디오가 있는지 확인
|
// ProductAllSection에 비디오가 있는지 확인
|
||||||
const hasProductVideo = fp.pipe(() => productData, fp.get('prdtMediaUrl'), fp.isNotNil)();
|
const hasProductVideo = fp.pipe(() => productData, fp.get('prdtMediaUrl'), fp.isNotNil)();
|
||||||
|
|
||||||
console.log('[BgVideo] DetailPanel - Video Control Check:', {
|
console.log('[Detail-BG] 🎬 DetailPanel - Video Control Check (mount/update):', {
|
||||||
hasPlayerPanel,
|
hasPlayerPanel,
|
||||||
isModal,
|
playerPanelModalStatus: isModal,
|
||||||
hasProductVideo,
|
hasProductVideo,
|
||||||
sourceMenu: panelInfo?.sourceMenu,
|
sourceMenu: panelInfo?.sourceMenu,
|
||||||
|
productDataUrl: productData?.prdtMediaUrl,
|
||||||
|
timestamp: Date.now(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// PlayerPanel이 있고, 제품에 비디오가 있을 때만 비디오 멈춤
|
// PlayerPanel이 있고, 제품에 비디오가 있을 때만 비디오 멈춤
|
||||||
if (hasPlayerPanel && hasProductVideo) {
|
if (hasPlayerPanel && hasProductVideo) {
|
||||||
console.log('[BgVideo] DetailPanel - Pausing video');
|
console.log('[Detail-BG] ⏸️ DetailPanel - Pausing PlayerPanel video (match: playerPanel + productVideo)', {
|
||||||
|
isModalVideo: isModal,
|
||||||
|
action: isModal ? 'pauseModalVideo' : 'pauseFullscreenVideo',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
if (isModal) {
|
if (isModal) {
|
||||||
dispatch(pauseModalVideo());
|
dispatch(pauseModalVideo());
|
||||||
} else {
|
} else {
|
||||||
dispatch(pauseFullscreenVideo());
|
dispatch(pauseFullscreenVideo());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('[BgVideo] DetailPanel - Skipping pause');
|
console.log('[Detail-BG] ⏭️ DetailPanel - Skipping pause (no playerPanel or no productVideo)', {
|
||||||
}
|
hasPlayerPanel,
|
||||||
|
hasProductVideo,
|
||||||
|
reason: !hasPlayerPanel ? 'no playerPanel' : 'no productVideo',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
// DetailPanel 언마운트 시: 비디오가 있었고 멈췄던 경우만 재생 재개
|
// DetailPanel 언마운트 시: 비디오가 있었고 멈췄던 경우만 재생 재개
|
||||||
if (hasPlayerPanel && hasProductVideo) {
|
if (hasPlayerPanel && hasProductVideo) {
|
||||||
console.log('[BgVideo] DetailPanel - Resuming video');
|
console.log('[Detail-BG] ▶️ DetailPanel - Resuming PlayerPanel video (unmount cleanup)', {
|
||||||
|
isModalVideo: isModal,
|
||||||
|
action: isModal ? 'resumeModalVideo' : 'resumeFullscreenVideo',
|
||||||
|
sourceMenu: panelInfo?.sourceMenu,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
if (isModal) {
|
if (isModal) {
|
||||||
dispatch(resumeModalVideo());
|
dispatch(resumeModalVideo());
|
||||||
} else {
|
} else {
|
||||||
dispatch(resumeFullscreenVideo());
|
dispatch(resumeFullscreenVideo());
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
console.log('[Detail-BG] ⏭️ DetailPanel - Skipping resume on unmount', {
|
||||||
|
hasPlayerPanel,
|
||||||
|
hasProductVideo,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@@ -1080,12 +1120,33 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
const hasPlayerPanel = panels.some(
|
const hasPlayerPanel = panels.some(
|
||||||
(panel) => panel.name === panel_names.PLAYER_PANEL && panel.panelInfo?.modal === true
|
(panel) => panel.name === panel_names.PLAYER_PANEL && panel.panelInfo?.modal === true
|
||||||
);
|
);
|
||||||
|
const launchedFromPlayer = panelInfo?.fromPlayer || panelInfo?.sourcePanel === panel_names.PLAYER_PANEL;
|
||||||
|
|
||||||
if (hasPlayerPanel) {
|
if (hasPlayerPanel && !launchedFromPlayer) {
|
||||||
console.log('[DetailPanel] PlayerPanel modal=true detected - stopping video preview');
|
console.log('[DetailPanel] PlayerPanel modal=true detected - stopping video preview (non-player source)');
|
||||||
dispatch(finishVideoPreview());
|
dispatch(finishVideoPreview());
|
||||||
|
} else if (hasPlayerPanel && launchedFromPlayer) {
|
||||||
|
console.log('[DetailPanel] PlayerPanel modal=true detected - launched from Player, skip finishVideoPreview');
|
||||||
|
|
||||||
|
// Detail 동안 modal=true로 바뀌지 않도록 lockModalFalse 설정
|
||||||
|
const playerPanelEntry = panels.find(
|
||||||
|
(p) => p.name === panel_names.PLAYER_PANEL || p.name === panel_names.PLAYER_PANEL_NEW
|
||||||
|
);
|
||||||
|
if (playerPanelEntry?.panelInfo?.modal === true) {
|
||||||
|
dispatch(
|
||||||
|
updatePanel({
|
||||||
|
name: playerPanelEntry.name,
|
||||||
|
panelInfo: {
|
||||||
|
...playerPanelEntry.panelInfo,
|
||||||
|
modal: false,
|
||||||
|
modalContainerId: undefined,
|
||||||
|
lockModalFalse: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [panels, dispatch]);
|
}, [panels, dispatch, panelInfo?.fromPlayer, panelInfo?.sourcePanel]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef}>
|
<div ref={containerRef}>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -680,7 +680,8 @@
|
|||||||
|
|
||||||
.productDetailsButton,
|
.productDetailsButton,
|
||||||
.userReviewsButton,
|
.userReviewsButton,
|
||||||
.youMayLikeButton {
|
.youMayLikeButton,
|
||||||
|
.seeMoreProductButton {
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
background: rgba(255, 255, 255, 0.05); // 기본 회색 배경
|
background: rgba(255, 255, 255, 0.05); // 기본 회색 배경
|
||||||
@@ -708,7 +709,8 @@
|
|||||||
|
|
||||||
.productDetailsButton,
|
.productDetailsButton,
|
||||||
.userReviewsButton,
|
.userReviewsButton,
|
||||||
.youMayLikeButton {
|
.youMayLikeButton,
|
||||||
|
.seeMoreProductButton {
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
background: rgba(255, 255, 255, 0.05); // 기본 회색 배경
|
background: rgba(255, 255, 255, 0.05); // 기본 회색 배경
|
||||||
|
|||||||
@@ -1,9 +1,32 @@
|
|||||||
import React, { useCallback } from "react";
|
import React, {
|
||||||
import css from "./ProductDescription.module.less";
|
useCallback,
|
||||||
import { $L, removeSpecificTags } from "../../../../utils/helperMethods";
|
useMemo,
|
||||||
import Spottable from "@enact/spotlight/Spottable";
|
} from 'react';
|
||||||
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
|
|
||||||
import Spotlight from "@enact/spotlight";
|
import {
|
||||||
|
useDispatch,
|
||||||
|
useSelector,
|
||||||
|
} from 'react-redux';
|
||||||
|
|
||||||
|
import Spotlight from '@enact/spotlight';
|
||||||
|
import SpotlightContainerDecorator
|
||||||
|
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||||
|
import Spottable from '@enact/spotlight/Spottable';
|
||||||
|
|
||||||
|
import {
|
||||||
|
setHidePopup,
|
||||||
|
setShowPopup,
|
||||||
|
} from '../../../../actions/commonActions';
|
||||||
|
import TButtonScroller
|
||||||
|
from '../../../../components/TButtonScroller/TButtonScroller';
|
||||||
|
import TNewPopUp from '../../../../components/TPopUp/TNewPopUp';
|
||||||
|
import * as Config from '../../../../utils/Config';
|
||||||
|
import {
|
||||||
|
$L,
|
||||||
|
removeSpecificTags,
|
||||||
|
} from '../../../../utils/helperMethods';
|
||||||
|
import css from './ProductDescription.module.less';
|
||||||
|
|
||||||
// TVerticalPagenator 제거됨 - TScrollerNew와 충돌 문제로 인해
|
// TVerticalPagenator 제거됨 - TScrollerNew와 충돌 문제로 인해
|
||||||
|
|
||||||
const SpottableComponent = Spottable("div");
|
const SpottableComponent = Spottable("div");
|
||||||
@@ -18,12 +41,23 @@ const Container = SpotlightContainerDecorator(
|
|||||||
"div"
|
"div"
|
||||||
);
|
);
|
||||||
|
|
||||||
export default function ProductDescription({ productInfo }) {
|
export default function ProductDescription({ productInfo }) {
|
||||||
|
const { popupVisible, activePopup } = useSelector(
|
||||||
|
(state) => state.common.popup
|
||||||
|
);
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
|
||||||
const productDescription = useCallback(() => {
|
const productDescription = useCallback(() => {
|
||||||
const sanitizedString = removeSpecificTags(productInfo?.prdtDesc);
|
const sanitizedString = removeSpecificTags(productInfo?.prdtDesc);
|
||||||
return { __html: sanitizedString };
|
return { __html: sanitizedString };
|
||||||
}, [productInfo?.prdtDesc]);
|
}, [productInfo?.prdtDesc]);
|
||||||
|
|
||||||
|
const productDescriptionText = useMemo(() => {
|
||||||
|
return removeSpecificTags(productInfo?.prdtDesc);
|
||||||
|
}, [productInfo?.prdtDesc]);
|
||||||
|
|
||||||
// 왼쪽 화살표 키 이벤트 처리
|
// 왼쪽 화살표 키 이벤트 처리
|
||||||
const handleKeyDown = useCallback((ev) => {
|
const handleKeyDown = useCallback((ev) => {
|
||||||
if (ev.keyCode === 37) { // 왼쪽 화살표 키
|
if (ev.keyCode === 37) { // 왼쪽 화살표 키
|
||||||
@@ -34,6 +68,20 @@ export default function ProductDescription({ productInfo }) {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const descriptionClick = useCallback(() => {
|
||||||
|
dispatch(setShowPopup(Config.ACTIVE_POPUP.scrollPopup));
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const _onClose = useCallback(()=>{
|
||||||
|
dispatch(setHidePopup());
|
||||||
|
// Restore focus to the description content after popup closes
|
||||||
|
setTimeout(() => {
|
||||||
|
Spotlight.focus('product-description-content');
|
||||||
|
}, 100);
|
||||||
|
},[dispatch])
|
||||||
|
|
||||||
// ProductDescription: Container 직접 사용 패턴
|
// ProductDescription: Container 직접 사용 패턴
|
||||||
// prdtDesc가 없으면 렌더링하지 않음
|
// prdtDesc가 없으면 렌더링하지 않음
|
||||||
if (!productInfo?.prdtDesc) {
|
if (!productInfo?.prdtDesc) {
|
||||||
@@ -41,36 +89,61 @@ export default function ProductDescription({ productInfo }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container
|
<>
|
||||||
className={css.descriptionContainer}
|
<Container
|
||||||
spotlightId="product-description-container"
|
className={css.descriptionContainer}
|
||||||
>
|
spotlightId="product-description-container"
|
||||||
{/* <SpottableComponent
|
|
||||||
className={css.titleWrapper}
|
|
||||||
spotlightId="product-description-title"
|
|
||||||
onClick={() => console.log("[ProductDescription] Title clicked")}
|
|
||||||
onFocus={() => console.log("[ProductDescription] Title focused")}
|
|
||||||
onBlur={() => console.log("[ProductDescription] Title blurred")}
|
|
||||||
> */}
|
|
||||||
<div className={css.titleWrapper}>
|
|
||||||
<div className={css.title}>{$L("DESCRIPTION")}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* </SpottableComponent> */}
|
|
||||||
|
|
||||||
<SpottableComponent
|
|
||||||
className={css.descriptionWrapper}
|
|
||||||
spotlightId="product-description-content"
|
|
||||||
onClick={() => console.log("[ProductDescription] Content clicked")}
|
|
||||||
onFocus={() => console.log("[ProductDescription] Content focused")}
|
|
||||||
onBlur={() => console.log("[ProductDescription] Content blurred")}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
>
|
>
|
||||||
<div
|
{/* <SpottableComponent
|
||||||
className={css.productDescription}
|
className={css.titleWrapper}
|
||||||
dangerouslySetInnerHTML={productDescription()}
|
spotlightId="product-description-title"
|
||||||
/>
|
onClick={() => console.log("[ProductDescription] Title clicked")}
|
||||||
</SpottableComponent>
|
onFocus={() => console.log("[ProductDescription] Title focused")}
|
||||||
</Container>
|
onBlur={() => console.log("[ProductDescription] Title blurred")}
|
||||||
|
> */}
|
||||||
|
<div className={css.titleWrapper}>
|
||||||
|
<div className={css.title}>{$L("DESCRIPTION")}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* </SpottableComponent> */}
|
||||||
|
|
||||||
|
<SpottableComponent
|
||||||
|
className={css.descriptionWrapper}
|
||||||
|
spotlightId="product-description-content"
|
||||||
|
// onClick={() => console.log("[ProductDescription] Content clicked")}
|
||||||
|
onClick={descriptionClick}
|
||||||
|
onFocus={() => console.log("[ProductDescription] Content focused")}
|
||||||
|
onBlur={() => console.log("[ProductDescription] Content blurred")}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={css.productDescription}
|
||||||
|
dangerouslySetInnerHTML={productDescription()}
|
||||||
|
/>
|
||||||
|
</SpottableComponent>
|
||||||
|
</Container>
|
||||||
|
{activePopup === Config.ACTIVE_POPUP.scrollPopup && (
|
||||||
|
<TNewPopUp
|
||||||
|
kind="scrollPopup"
|
||||||
|
open={popupVisible}
|
||||||
|
hasText
|
||||||
|
title={$L("DESCRIPTION")}
|
||||||
|
onClick={_onClose}
|
||||||
|
hasButton
|
||||||
|
button1Text={$L("OK")}
|
||||||
|
>
|
||||||
|
<TButtonScroller
|
||||||
|
boxHeight={460}
|
||||||
|
width={844}
|
||||||
|
kind={"figmaTermsPopup"}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={css.scrollContainer}
|
||||||
|
dangerouslySetInnerHTML={{ __html: productDescriptionText }}
|
||||||
|
/>
|
||||||
|
</TButtonScroller>
|
||||||
|
</TNewPopUp>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,3 +51,8 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.scrollContainer {
|
||||||
|
padding: 31px;
|
||||||
|
font-size: 26px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,14 +1,25 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import React, {
|
||||||
import css from "./ProductDetail.new.module.less";
|
useCallback,
|
||||||
import TVirtualGridList from "../../../../components/TVirtualGridList/TVirtualGridList";
|
useEffect,
|
||||||
import Spottable from "@enact/spotlight/Spottable";
|
useMemo,
|
||||||
import CustomImage from "../../../../components/CustomImage/CustomImage";
|
useRef,
|
||||||
import indicatorDefaultImage from "../../../../../assets/images/img-thumb-empty-144@3x.png";
|
useState,
|
||||||
import useScrollTo from "../../../../hooks/useScrollTo";
|
} from 'react';
|
||||||
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
|
|
||||||
|
import Spotlight from '@enact/spotlight';
|
||||||
|
import SpotlightContainerDecorator
|
||||||
|
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||||
|
import Spottable from '@enact/spotlight/Spottable';
|
||||||
|
|
||||||
|
import indicatorDefaultImage
|
||||||
|
from '../../../../../assets/images/img-thumb-empty-144@3x.png';
|
||||||
|
import CustomImage from '../../../../components/CustomImage/CustomImage';
|
||||||
|
import TVirtualGridList
|
||||||
|
from '../../../../components/TVirtualGridList/TVirtualGridList';
|
||||||
|
import useScrollTo from '../../../../hooks/useScrollTo';
|
||||||
// TVerticalPagenator 제거됨 - TScrollerNew와 충돌 문제로 인해
|
// TVerticalPagenator 제거됨 - TScrollerNew와 충돌 문제로 인해
|
||||||
import { removeSpecificTags } from "../../../../utils/helperMethods";
|
import { removeSpecificTags } from '../../../../utils/helperMethods';
|
||||||
import Spotlight from "@enact/spotlight";
|
import css from './ProductDetail.new.module.less';
|
||||||
|
|
||||||
const Container = SpotlightContainerDecorator(
|
const Container = SpotlightContainerDecorator(
|
||||||
{
|
{
|
||||||
@@ -59,7 +70,8 @@ export default function ProductDetail({ productInfo }) {
|
|||||||
const image = listImages[0] || indicatorDefaultImage;
|
const image = listImages[0] || indicatorDefaultImage;
|
||||||
const imageIndex = productInfo?.imageIndex ?? 0;
|
const imageIndex = productInfo?.imageIndex ?? 0;
|
||||||
const totalImages = productInfo?.totalImages ?? listImages.length;
|
const totalImages = productInfo?.totalImages ?? listImages.length;
|
||||||
|
const sponserImage = productInfo?.spnsrImgUrl;
|
||||||
|
const spnsrNm = productInfo?.spnsrNm;
|
||||||
return (
|
return (
|
||||||
<div className={css.thumbnailWrapper}>
|
<div className={css.thumbnailWrapper}>
|
||||||
<CustomImage
|
<CustomImage
|
||||||
@@ -68,6 +80,19 @@ export default function ProductDetail({ productInfo }) {
|
|||||||
fallbackSrc={indicatorDefaultImage}
|
fallbackSrc={indicatorDefaultImage}
|
||||||
className={css.productImage}
|
className={css.productImage}
|
||||||
/>
|
/>
|
||||||
|
{imageIndex === 0 && sponserImage &&(
|
||||||
|
<div className={css.sponserImgBox}>
|
||||||
|
<CustomImage
|
||||||
|
src={sponserImage}
|
||||||
|
alt={spnsrNm}
|
||||||
|
fallbackSrc={indicatorDefaultImage}
|
||||||
|
className={css.sponserImg}
|
||||||
|
/>
|
||||||
|
<div className={css.sponserTextBox}>
|
||||||
|
SPONSORED BY
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}, [listImages, productInfo?.imageIndex, productInfo?.totalImages]);
|
}, [listImages, productInfo?.imageIndex, productInfo?.totalImages]);
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.thumbnailWrapper .productImage {
|
.thumbnailWrapper .productImage {
|
||||||
transform: scale(1.015); // 가로세로 10px 정도 확대 효과
|
transform: scale(1.015); // 가로세로 10px 정도 확대 효과
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,14 +70,15 @@
|
|||||||
|
|
||||||
.thumbnailWrapper {
|
.thumbnailWrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 658px;
|
// width: 658px;
|
||||||
|
width:100%;
|
||||||
height: 610px;
|
height: 610px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
.productImage {
|
.productImage {
|
||||||
width: 100%;
|
width: 658px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -89,4 +90,20 @@
|
|||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sponserImgBox {
|
||||||
|
position:absolute;
|
||||||
|
right:20px;
|
||||||
|
top:0px;
|
||||||
|
.sponserImg {
|
||||||
|
width:145px;
|
||||||
|
}
|
||||||
|
.sponserTextBox {
|
||||||
|
padding: 3px;
|
||||||
|
background-color: #474747;
|
||||||
|
font-size:14px;
|
||||||
|
font-weight:bold;
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,10 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: 1114px; // ProductDetail과 동일한 고정 크기
|
width: 1114px; // ProductDetail과 동일한 고정 크기
|
||||||
max-width: 1114px;
|
max-width: 1114px;
|
||||||
height: 740px; // ProductDetail과 동일한 고정 높이
|
height: 632px !important; // ProductDetail과 동일한 고정 높이
|
||||||
margin-bottom: 30px; // ProductDetail과 동일한 간격
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: rgba(0, 0, 0, 1);
|
background-color: rgba(0, 0, 0, 1);
|
||||||
border-radius: 12px;
|
border-radius: 12px 12px 0 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 6px; // 포커스 테두리를 위한 공간
|
padding: 6px; // 포커스 테두리를 위한 공간
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -80,7 +79,7 @@
|
|||||||
z-index: 23; // MediaPanel(z-index: 22)보다 위에 표시되어야 비디오 재생 중에도 포커스 테두리가 보임
|
z-index: 23; // MediaPanel(z-index: 22)보다 위에 표시되어야 비디오 재생 중에도 포커스 테두리가 보임
|
||||||
border: 6px solid @PRIMARY_COLOR_RED;
|
border: 6px solid @PRIMARY_COLOR_RED;
|
||||||
box-shadow: 0 0 22px 0 rgba(0, 0, 0, 0.5);
|
box-shadow: 0 0 22px 0 rgba(0, 0, 0, 0.5);
|
||||||
border-radius: 12px;
|
border-radius: 12px 12px 0px 0px;
|
||||||
content: "";
|
content: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,30 +216,28 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.notice {
|
.notice {
|
||||||
|
width: calc(100% - 10px);
|
||||||
|
height: 54px;
|
||||||
|
background: #000000;
|
||||||
|
.flex(@justifyCenter:flex-start);
|
||||||
|
padding: 6px 18px 18px 18px;
|
||||||
|
border-radius: 0 0 12px 12px;
|
||||||
|
margin-bottom: 30px; // ProductDetail과 동일한 간격
|
||||||
|
.marquee {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 54px;
|
height: 100%;
|
||||||
background: #000000;
|
}
|
||||||
.flex(@justifyCenter:flex-start);
|
img {
|
||||||
padding: 6px 18px 18px 18px;
|
width: 18px;
|
||||||
position: absolute;
|
height: 18px;
|
||||||
bottom: 0;
|
margin: 10px 12px 0 0;
|
||||||
border-radius: 0 0 12px 12px;
|
object-fit: contain;
|
||||||
|
}
|
||||||
.marquee {
|
span {
|
||||||
width: 100%;
|
line-height: normal;
|
||||||
height: 100%;
|
letter-spacing: normal;
|
||||||
}
|
text-align: left;
|
||||||
img {
|
.font(@fontFamily:@baseFont, @fontSize:20px);
|
||||||
width: 18px;
|
color: @COLOR_GRAY04;
|
||||||
height: 18px;
|
}
|
||||||
margin: 10px 12px 0 0;
|
}
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
span {
|
|
||||||
line-height: normal;
|
|
||||||
letter-spacing: normal;
|
|
||||||
text-align: left;
|
|
||||||
.font(@fontFamily:@baseFont, @fontSize:20px);
|
|
||||||
color: @COLOR_GRAY04;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -311,6 +311,7 @@ export default function ProductVideo({
|
|||||||
if (!canPlayVideo) return null;
|
if (!canPlayVideo) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<SpottableComponent
|
<SpottableComponent
|
||||||
className={css.videoContainer}
|
className={css.videoContainer}
|
||||||
onClick={handleVideoClick}
|
onClick={handleVideoClick}
|
||||||
@@ -330,12 +331,13 @@ export default function ProductVideo({
|
|||||||
<img src={playImg} alt="재생" />
|
<img src={playImg} alt="재생" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={css.notice}>
|
|
||||||
<Marquee className={css.marquee} marqueeOn="render">
|
|
||||||
<img src={ic_warning} alt={disclaimer} />
|
|
||||||
<span>{disclaimer}</span>
|
|
||||||
</Marquee>
|
|
||||||
</div>
|
|
||||||
</SpottableComponent>
|
</SpottableComponent>
|
||||||
|
<div className={css.notice}>
|
||||||
|
<Marquee className={css.marquee} marqueeOn="render">
|
||||||
|
<img src={ic_warning} alt={disclaimer} />
|
||||||
|
<span>{disclaimer}</span>
|
||||||
|
</Marquee>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,219 @@
|
|||||||
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
useDispatch,
|
||||||
|
useSelector,
|
||||||
|
} from 'react-redux';
|
||||||
|
|
||||||
|
import { Job } from '@enact/core/util';
|
||||||
|
import SpotlightContainerDecorator
|
||||||
|
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||||
|
import Spottable from '@enact/spotlight/Spottable';
|
||||||
|
|
||||||
|
import { clearThemeDetail } from '../../../../actions/homeActions';
|
||||||
|
import { finishModalMediaForce } from '../../../../actions/mediaActions';
|
||||||
|
import {
|
||||||
|
popPanel,
|
||||||
|
pushPanel,
|
||||||
|
updatePanel,
|
||||||
|
} from '../../../../actions/panelActions';
|
||||||
|
import { finishVideoPreview } from '../../../../actions/playActions';
|
||||||
|
import THeader from '../../../../components/THeader/THeader';
|
||||||
|
import TItemCardNew from '../../../../components/TItemCard/TItemCard.new';
|
||||||
|
import TVerticalPagenator
|
||||||
|
from '../../../../components/TVerticalPagenator/TVerticalPagenator';
|
||||||
|
import useScrollTo from '../../../../hooks/useScrollTo';
|
||||||
|
import {
|
||||||
|
LOG_CONTEXT_NAME,
|
||||||
|
LOG_MESSAGE_ID,
|
||||||
|
panel_names,
|
||||||
|
} from '../../../../utils/Config';
|
||||||
|
import { $L } from '../../../../utils/helperMethods';
|
||||||
|
import css from './SeeMoreProducts.module.less';
|
||||||
|
|
||||||
|
const SpottableComponent = Spottable('div');
|
||||||
|
|
||||||
|
const Container = SpotlightContainerDecorator(
|
||||||
|
{
|
||||||
|
enterTo: 'last-focused',
|
||||||
|
leaveFor: {
|
||||||
|
left: 'spotlight-product-info-section-container',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'div'
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function SeeMoreProducts({
|
||||||
|
groupProducts,
|
||||||
|
sponserImage,
|
||||||
|
panelInfo,
|
||||||
|
onFocus,
|
||||||
|
onBlur,
|
||||||
|
}) {
|
||||||
|
const { getScrollTo, scrollLeft } = useScrollTo();
|
||||||
|
const [newGroupProductData, setNewGroupProductData] = useState([]);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const focusedContainerIdRef = useRef(null);
|
||||||
|
|
||||||
|
const panels = useSelector((state) => state.panels.panels);
|
||||||
|
const themeProductInfos = useSelector((state) => state.home.themeCurationDetailInfoData);
|
||||||
|
|
||||||
|
const launchedFromPlayer = useMemo(() => {
|
||||||
|
const detailPanelIndex = panels.findIndex(({ name }) => name === 'detailpanel');
|
||||||
|
const playerPanelIndex = panels.findIndex(({ name }) => name === 'playerpanel');
|
||||||
|
|
||||||
|
return detailPanelIndex - 1 === playerPanelIndex;
|
||||||
|
}, [panels]);
|
||||||
|
|
||||||
|
const onFocusedContainerId = useCallback((containerId) => {
|
||||||
|
focusedContainerIdRef.current = containerId;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const _onFocus = useCallback(() => {
|
||||||
|
if (onFocus) {
|
||||||
|
onFocus();
|
||||||
|
}
|
||||||
|
}, [onFocus]);
|
||||||
|
|
||||||
|
const _onBlur = useCallback(() => {
|
||||||
|
if (onBlur) {
|
||||||
|
onBlur();
|
||||||
|
}
|
||||||
|
}, [onBlur]);
|
||||||
|
|
||||||
|
// 그룹 상품 데이터 처리 (YOU MAY ALSO LIKE와 동일 로직)
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('[SeeMoreProducts] 그룹 상품 데이터 처리:', {
|
||||||
|
originalData: groupProducts,
|
||||||
|
originalLength: groupProducts?.length || 0,
|
||||||
|
hasData: !!(groupProducts && groupProducts.length > 0)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (groupProducts && groupProducts.length > 0) {
|
||||||
|
// 최대 9개로 제한 (YOU MAY ALSO LIKE와 동일)
|
||||||
|
const processedData = groupProducts.length > 9
|
||||||
|
? groupProducts.slice(0, groupProducts.length - 1)
|
||||||
|
: groupProducts;
|
||||||
|
|
||||||
|
console.log('[SeeMoreProducts] 처리된 데이터 설정:', {
|
||||||
|
processedLength: processedData.length,
|
||||||
|
processedData
|
||||||
|
});
|
||||||
|
setNewGroupProductData(processedData);
|
||||||
|
} else {
|
||||||
|
console.log('[SeeMoreProducts] 데이터 없음 - 빈 배열 설정');
|
||||||
|
setNewGroupProductData([]);
|
||||||
|
}
|
||||||
|
}, [groupProducts]);
|
||||||
|
|
||||||
|
const cursorOpen = useRef(new Job((func) => func(), 1000));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{newGroupProductData && newGroupProductData.length > 0 && (
|
||||||
|
<TVerticalPagenator
|
||||||
|
spotlightId={'detail_seeMoreProducts_area'}
|
||||||
|
data-wheel-point={true}
|
||||||
|
className={css.tVerticalPagenator}
|
||||||
|
defaultContainerId={panelInfo?.focusedContainerId}
|
||||||
|
onFocusedContainerId={onFocusedContainerId}
|
||||||
|
topMargin={36}
|
||||||
|
>
|
||||||
|
<Container className={css.container} onFocus={_onFocus} onBlur={_onBlur}>
|
||||||
|
<THeader title={newGroupProductData[0].groupNm} className={css.tHeader} sponserImage={sponserImage} />
|
||||||
|
|
||||||
|
|
||||||
|
<div className={css.renderCardContainer}>
|
||||||
|
{newGroupProductData?.map((product, index) => {
|
||||||
|
const {
|
||||||
|
imgUrl: prdtImgUrl, // 이미지 URL 변경
|
||||||
|
patnrId,
|
||||||
|
prdtId,
|
||||||
|
prdtNm,
|
||||||
|
priceInfo,
|
||||||
|
offerInfo,
|
||||||
|
patncNm,
|
||||||
|
brndNm,
|
||||||
|
lgCatCd,
|
||||||
|
euEnrgLblInfos,
|
||||||
|
} = product;
|
||||||
|
|
||||||
|
const handleItemClick = () => {
|
||||||
|
console.log('[SeeMoreProducts] 상품 클릭:', product);
|
||||||
|
|
||||||
|
// Promise 체이닝으로 순서 보장 (YOU MAY ALSO LIKE와 동일)
|
||||||
|
Promise.resolve()
|
||||||
|
.then(() => {
|
||||||
|
// 1. 기존 비디오 강제 종료
|
||||||
|
dispatch(finishVideoPreview());
|
||||||
|
dispatch(finishModalMediaForce());
|
||||||
|
|
||||||
|
if (themeProductInfos && themeProductInfos.length > 0) {
|
||||||
|
dispatch(clearThemeDetail());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// 2. 비디오 종료 후 새로운 상품으로 업데이트
|
||||||
|
dispatch(
|
||||||
|
updatePanel({
|
||||||
|
name: panel_names.DETAIL_PANEL,
|
||||||
|
panelInfo: {
|
||||||
|
showNm: panelInfo?.showNm,
|
||||||
|
showId: panelInfo?.showId,
|
||||||
|
liveFlag: panelInfo?.liveFlag,
|
||||||
|
thumbnailUrl: panelInfo?.thumbnailUrl,
|
||||||
|
patnrId,
|
||||||
|
prdtId,
|
||||||
|
launchedFromPlayer: launchedFromPlayer,
|
||||||
|
fromPanel: {
|
||||||
|
fromSeeMoreProducts: true, // 🆕 SeeMoreProducts에서 선택된 상품임을 표시
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
cursorOpen.current.stop();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// prdtId가 없는 경우를 대비한 안정적인 key 생성
|
||||||
|
const itemKey = prdtId ? `${patnrId}-${prdtId}` : `see-more-product-${index}`;
|
||||||
|
|
||||||
|
// 🆕 [251210] TItemCardNew에 spotlightId와 spottable 설정 추가
|
||||||
|
return (
|
||||||
|
<TItemCardNew
|
||||||
|
key={itemKey}
|
||||||
|
className={css.itemCardNew}
|
||||||
|
contextName={LOG_CONTEXT_NAME.YOUMAYLIKE}
|
||||||
|
messageId={LOG_MESSAGE_ID.CONTENTCLICK}
|
||||||
|
productId={prdtId}
|
||||||
|
nowProductId={panelInfo?.prdtId}
|
||||||
|
nowProductTitle={panelInfo?.prdtNm}
|
||||||
|
nowCategory={panelInfo?.catNm}
|
||||||
|
catNm={lgCatCd}
|
||||||
|
patnerName={patncNm}
|
||||||
|
brandName={brndNm}
|
||||||
|
imageAlt={prdtId}
|
||||||
|
imageSource={prdtImgUrl}
|
||||||
|
priceInfo={priceInfo}
|
||||||
|
offerInfo={offerInfo}
|
||||||
|
productName={prdtNm}
|
||||||
|
onClick={handleItemClick}
|
||||||
|
label={index * 1 + 1 + ' of ' + newGroupProductData.length}
|
||||||
|
lastLabel=" go to detail, button"
|
||||||
|
euEnrgLblInfos={euEnrgLblInfos}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</TVerticalPagenator>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
@import "../../../../style/CommonStyle.module.less";
|
||||||
|
@import "../../../../style/utils.module.less";
|
||||||
|
|
||||||
|
// .container {
|
||||||
|
// .size(@w: 874px,@h:500px);
|
||||||
|
|
||||||
|
// .itemWrapper {
|
||||||
|
// .size(@w: 874px,@h:500px);
|
||||||
|
// .item {
|
||||||
|
// .size(@w: 300px,@h:300px);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
.tVerticalPagenator {
|
||||||
|
.size(@w: 1114px, @h: auto); // 마진 포함 전체 크기 (1054px + 60px)
|
||||||
|
max-width: 1114px;
|
||||||
|
// padding-left: 30px; // 좌측 30px 마진
|
||||||
|
// padding-right: 30px; // 우측 30px 마진
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
// .sectionTitle {
|
||||||
|
// .font(@fontFamily: @baseFont, @fontSize: 30px);
|
||||||
|
// min-height: 56px;
|
||||||
|
// font-weight: 700;
|
||||||
|
// color: rgba(234, 234, 234, 1);
|
||||||
|
// // margin: 30px 0 20px 0;
|
||||||
|
// }
|
||||||
|
.tHeader {
|
||||||
|
background: transparent;
|
||||||
|
.size(@w: 100%, @h: 36px); // 마진 제외 콘텐츠 크기
|
||||||
|
margin-bottom: 20px;
|
||||||
|
position:relative;
|
||||||
|
> div{
|
||||||
|
padding:0;
|
||||||
|
}
|
||||||
|
.averageOverallRating {
|
||||||
|
.size(@w: 176px,@h:30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: 700;
|
||||||
|
height: 36px;
|
||||||
|
color: rgba(234, 234, 234, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
.flex(@direction:column,@alignCenter:flex-start);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 34px;
|
||||||
|
// > div {
|
||||||
|
// margin: 0 15px 15px 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
.renderCardContainer {
|
||||||
|
width: 1144px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
// margin-top: 34px;
|
||||||
|
> div.itemCardNew {
|
||||||
|
/* item card */
|
||||||
|
margin: 0 15px 15px 0;
|
||||||
|
.size(@w:360px,@h:494px);
|
||||||
|
background-color: rgba(51, 51, 51, 0.95);
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
> div:nth-child(1) {
|
||||||
|
/* img wrapper*/
|
||||||
|
.size(@w:323px,@h:323px);
|
||||||
|
|
||||||
|
> img {
|
||||||
|
.size(@w:100%,@h:100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> div:nth-child(2) {
|
||||||
|
margin-top: 15px;
|
||||||
|
/* desc wrapper */
|
||||||
|
> div > div > h3 {
|
||||||
|
/* title */
|
||||||
|
color: rgba(234, 234, 234, 1);
|
||||||
|
.size(@w:100%,@h:64px);
|
||||||
|
line-height: 31px;
|
||||||
|
}
|
||||||
|
> p {
|
||||||
|
/* priceInfo */
|
||||||
|
height: 43px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// width: 100%;
|
||||||
|
// padding-left: 60px;
|
||||||
|
// overflow: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,10 +38,6 @@ export default function UserReviewDetail({
|
|||||||
}
|
}
|
||||||
}, [onNext, currentIndex, totalReviews]);
|
}, [onNext, currentIndex, totalReviews]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log("####currentIndex", currentIndex);
|
|
||||||
}, [currentIndex]);
|
|
||||||
|
|
||||||
// 리뷰 데이터가 없을 때 처리
|
// 리뷰 데이터가 없을 때 처리
|
||||||
if (!currentReview) {
|
if (!currentReview) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -48,10 +48,7 @@
|
|||||||
.averageOverallRating {
|
.averageOverallRating {
|
||||||
width: 176px;
|
width: 176px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
display: flex;
|
background-size: contain;
|
||||||
align-items: center;
|
|
||||||
margin-left: auto;
|
|
||||||
background-size: cover;
|
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: center center;
|
background-position: center center;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import React, {
|
|||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import Spotlight from '@enact/spotlight';
|
||||||
import SpotlightContainerDecorator
|
import SpotlightContainerDecorator
|
||||||
from '@enact/spotlight/SpotlightContainerDecorator';
|
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||||
import Spottable from '@enact/spotlight/Spottable';
|
import Spottable from '@enact/spotlight/Spottable';
|
||||||
@@ -104,36 +105,29 @@ export default function UserReviewsPopup({
|
|||||||
// All-Images 및 User-Reviews 모드를 위한 상태
|
// All-Images 및 User-Reviews 모드를 위한 상태
|
||||||
const [currentReviewIndex, setCurrentReviewIndex] = useState(0);
|
const [currentReviewIndex, setCurrentReviewIndex] = useState(0);
|
||||||
|
|
||||||
// 모드별 리뷰 인덱스 초기화
|
// 모드 변경 시 초기화
|
||||||
// ✅ selectedImageIndex 변경 감지 추가: 클릭한 리뷰가 팝업에 표시되도록
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('[UserReviewsPopup] Mode or selectedImageIndex changed:', {
|
console.log('[UserReviewsPopup] Mode or selectedImageIndex changed:', {
|
||||||
mode,
|
mode,
|
||||||
selectedImageIndex,
|
selectedImageIndex,
|
||||||
allReviewsLength: allReviews?.length || 0,
|
allReviewsLength: allReviews?.length || 0,
|
||||||
});
|
});
|
||||||
|
if (mode === "all-images" && selectedImageIndex !== undefined) {
|
||||||
if (mode === "all-images" && images && images[selectedImageIndex]) {
|
const selectedImage = images?.[selectedImageIndex];
|
||||||
const selectedImage = images[selectedImageIndex];
|
if (selectedImage) {
|
||||||
const reviewIndex = reviewsWithImages.findIndex(
|
const reviewIndex = reviewsWithImages.findIndex(
|
||||||
(review) => review.rvwId === selectedImage.reviewId
|
(review) => review.rvwId === selectedImage.reviewId
|
||||||
);
|
);
|
||||||
if (reviewIndex !== -1) {
|
if (reviewIndex !== -1) {
|
||||||
console.log('[UserReviewsPopup] all-images mode - found review index:', reviewIndex);
|
console.log('[UserReviewsPopup] all-images mode - found review index:', reviewIndex);
|
||||||
setCurrentReviewIndex(reviewIndex);
|
setCurrentReviewIndex(reviewIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (mode === "user-reviews") {
|
} else if (mode === "user-reviews" && selectedImageIndex !== undefined) {
|
||||||
// User-Reviews 모드: selectedImageIndex를 그대로 사용
|
|
||||||
console.log('[UserReviewsPopup] user-reviews mode - setting index to:', selectedImageIndex);
|
console.log('[UserReviewsPopup] user-reviews mode - setting index to:', selectedImageIndex);
|
||||||
setCurrentReviewIndex(selectedImageIndex);
|
setCurrentReviewIndex(selectedImageIndex);
|
||||||
}
|
}
|
||||||
}, [
|
}, [mode, selectedImageIndex]); // selectedImageIndex는 명시적 변경 시만
|
||||||
mode,
|
|
||||||
selectedImageIndex, // ✅ 추가: 선택된 이미지 인덱스 변경 감지
|
|
||||||
images, // ✅ 추가: 이미지 데이터 변경 감지
|
|
||||||
reviewsWithImages, // ✅ 추가: 이미지 있는 리뷰 변경 감지
|
|
||||||
allReviews, // ✅ 추가: allReviews 변경 감지
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 리뷰 네비게이션 핸들러 (All-Images 및 User-Reviews 모드)
|
// 리뷰 네비게이션 핸들러 (All-Images 및 User-Reviews 모드)
|
||||||
const handlePreviousReview = useCallback(() => {
|
const handlePreviousReview = useCallback(() => {
|
||||||
@@ -182,6 +176,7 @@ export default function UserReviewsPopup({
|
|||||||
// 모드가 변경되면 이미지 로딩 상태 초기화
|
// 모드가 변경되면 이미지 로딩 상태 초기화
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setImageLoadStates({});
|
setImageLoadStates({});
|
||||||
|
Spotlight.focus("review-popup-container");
|
||||||
}, [mode]);
|
}, [mode]);
|
||||||
|
|
||||||
// 모든 이미지 표시
|
// 모든 이미지 표시
|
||||||
@@ -216,6 +211,7 @@ export default function UserReviewsPopup({
|
|||||||
? "user-review-detail-prev"
|
? "user-review-detail-prev"
|
||||||
: "user-review-image-0"
|
: "user-review-image-0"
|
||||||
}
|
}
|
||||||
|
spotlightId="review-popup-container"
|
||||||
>
|
>
|
||||||
{mode === "customer-images" && (
|
{mode === "customer-images" && (
|
||||||
<TScrollerDetail
|
<TScrollerDetail
|
||||||
|
|||||||
@@ -1,21 +1,41 @@
|
|||||||
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 { Job } from '@enact/core/util';
|
||||||
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
|
import SpotlightContainerDecorator
|
||||||
|
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||||
import Spottable from '@enact/spotlight/Spottable';
|
import Spottable from '@enact/spotlight/Spottable';
|
||||||
|
|
||||||
import { clearThemeDetail } from '../../../../actions/homeActions';
|
import { clearThemeDetail } from '../../../../actions/homeActions';
|
||||||
import { finishModalMediaForce } from '../../../../actions/mediaActions';
|
import { finishModalMediaForce } from '../../../../actions/mediaActions';
|
||||||
import { popPanel, pushPanel, updatePanel } from '../../../../actions/panelActions';
|
import {
|
||||||
|
popPanel,
|
||||||
|
pushPanel,
|
||||||
|
updatePanel,
|
||||||
|
} from '../../../../actions/panelActions';
|
||||||
import { finishVideoPreview } from '../../../../actions/playActions';
|
import { finishVideoPreview } from '../../../../actions/playActions';
|
||||||
import THeader from '../../../../components/THeader/THeader';
|
import THeader from '../../../../components/THeader/THeader';
|
||||||
import TItemCardNew from '../../../../components/TItemCard/TItemCard.new';
|
import TItemCardNew from '../../../../components/TItemCard/TItemCard.new';
|
||||||
import TVerticalPagenator from '../../../../components/TVerticalPagenator/TVerticalPagenator';
|
import TVerticalPagenator
|
||||||
import TVirtualGridList from '../../../../components/TVirtualGridList/TVirtualGridList';
|
from '../../../../components/TVerticalPagenator/TVerticalPagenator';
|
||||||
|
import TVirtualGridList
|
||||||
|
from '../../../../components/TVirtualGridList/TVirtualGridList';
|
||||||
import useScrollTo from '../../../../hooks/useScrollTo';
|
import useScrollTo from '../../../../hooks/useScrollTo';
|
||||||
import { LOG_CONTEXT_NAME, LOG_MESSAGE_ID, panel_names } from '../../../../utils/Config';
|
import {
|
||||||
|
LOG_CONTEXT_NAME,
|
||||||
|
LOG_MESSAGE_ID,
|
||||||
|
panel_names,
|
||||||
|
} from '../../../../utils/Config';
|
||||||
import { $L } from '../../../../utils/helperMethods';
|
import { $L } from '../../../../utils/helperMethods';
|
||||||
import css from './YouMayAlsoLike.module.less';
|
import css from './YouMayAlsoLike.module.less';
|
||||||
|
|
||||||
@@ -158,7 +178,7 @@ export default function YouMayAlsoLike({
|
|||||||
// DetailPanel을 언마운트하지 않고 상품 정보만 업데이트
|
// DetailPanel을 언마운트하지 않고 상품 정보만 업데이트
|
||||||
// 이렇게 하면 백그라운드 비디오 제어 상태가 유지됨
|
// 이렇게 하면 백그라운드 비디오 제어 상태가 유지됨
|
||||||
dispatch(
|
dispatch(
|
||||||
updatePanel({
|
pushPanel({
|
||||||
name: panel_names.DETAIL_PANEL,
|
name: panel_names.DETAIL_PANEL,
|
||||||
panelInfo: {
|
panelInfo: {
|
||||||
showNm: panelInfo?.showNm,
|
showNm: panelInfo?.showNm,
|
||||||
@@ -170,8 +190,8 @@ export default function YouMayAlsoLike({
|
|||||||
launchedFromPlayer: launchedFromPlayer,
|
launchedFromPlayer: launchedFromPlayer,
|
||||||
bgVideoInfo: bgVideoInfo, // 백그라운드 비디오 정보 유지
|
bgVideoInfo: bgVideoInfo, // 백그라운드 비디오 정보 유지
|
||||||
fromPanel: {
|
fromPanel: {
|
||||||
fromYouMayLike: true, // YouMayLike에서 선택된 상품임을 표시
|
fromYouMayLike: true, // YouMayLike에서 선택된 상품임을 표시
|
||||||
}, // 출처 정보 통합 객체
|
}, // 출처 정보 통합 객체
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import classNames from 'classnames';
|
|||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import TQRCode from '../../../../components/TQRCode/TQRCode';
|
import TQRCode from '../../../../components/TQRCode/TQRCode';
|
||||||
|
import TQRCodeNew from '../../../../components/TQRCode/TQRCodeNew';
|
||||||
|
import QRCodePatnr21 from './QRCodePatnr21';
|
||||||
import { getQRCodeUrl } from '../../../../utils/helperMethods';
|
import { getQRCodeUrl } from '../../../../utils/helperMethods';
|
||||||
import css from './QRCode.module.less';
|
import css from './QRCode.module.less';
|
||||||
|
|
||||||
@@ -56,13 +58,37 @@ export default function QRCode({
|
|||||||
return detailUrl;
|
return detailUrl;
|
||||||
}, [productInfo, isShopByMobile, detailUrl]);
|
}, [productInfo, isShopByMobile, detailUrl]);
|
||||||
|
|
||||||
|
// patnrId === 21인 경우 qrImgUrl 처리
|
||||||
|
const isPatnrId21 = productInfo?.patnrId === 21 || productInfo?.patnrId === "21";
|
||||||
|
const qrImgUrl = isPatnrId21 ? productInfo?.qrImgUrl : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(css.qrcode, kind ? css.detailQrcode : "")}>
|
<div className={classNames(css.qrcode, kind ? css.detailQrcode : "")}>
|
||||||
{/* {qrCodeUrl && <TQRCode text={qrCodeUrl} width="190" height="190" />} */}
|
{/* {qrCodeUrl && <TQRCode text={qrCodeUrl} width="190" height="190" />} */}
|
||||||
{kind === "detail" ? (
|
{kind === "detail" ? (
|
||||||
<TQRCode text={qrCodeUrl} width="240" height="240" />
|
isPatnrId21 ? (
|
||||||
|
<QRCodePatnr21
|
||||||
|
qrImgUrl={qrImgUrl}
|
||||||
|
fallbackText={qrCodeUrl}
|
||||||
|
width="240"
|
||||||
|
height="240"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<TQRCode text={qrCodeUrl} width="240" height="240" />
|
||||||
|
)
|
||||||
) : (
|
) : (
|
||||||
qrCodeUrl && <TQRCode text={qrCodeUrl} width="190" height="190" />
|
qrCodeUrl && (
|
||||||
|
isPatnrId21 ? (
|
||||||
|
<QRCodePatnr21
|
||||||
|
qrImgUrl={qrImgUrl}
|
||||||
|
fallbackText={qrCodeUrl}
|
||||||
|
width="190"
|
||||||
|
height="190"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<TQRCode text={qrCodeUrl} width="190" height="190" />
|
||||||
|
)
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
{/* todo : 시나리오,UI 릴리즈 후 */}
|
{/* todo : 시나리오,UI 릴리즈 후 */}
|
||||||
<div className={css.tooltip}>
|
<div className={css.tooltip}>
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
import TQRCode from '../../../../components/TQRCode/TQRCode';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* patnrId=21 전용 QR 이미지 처리 컴포넌트
|
||||||
|
* 서버에서 제공하는 qrImgUrl을 우선 표시하고,
|
||||||
|
* 로드 실패 시 TQRCode(QR 코드 생성)로 폴백합니다.
|
||||||
|
*
|
||||||
|
* @param {string} qrImgUrl - 서버 제공 QR 이미지 URL (productData.qrImgUrl)
|
||||||
|
* @param {string} fallbackText - TQRCode 생성 시 사용할 QR 텍스트 (qrCodeUrl)
|
||||||
|
* @param {string} width - 너비 ("190" 또는 "240")
|
||||||
|
* @param {string} height - 높이 ("190" 또는 "240")
|
||||||
|
*/
|
||||||
|
export default function QRCodePatnr21({
|
||||||
|
qrImgUrl,
|
||||||
|
fallbackText,
|
||||||
|
width = '190',
|
||||||
|
height = '190',
|
||||||
|
}) {
|
||||||
|
const [imageError, setImageError] = useState(false);
|
||||||
|
const [imageLoaded, setImageLoaded] = useState(false);
|
||||||
|
|
||||||
|
// 1. qrImgUrl이 없으면 TQRCode 폴백
|
||||||
|
if (!qrImgUrl) {
|
||||||
|
return <TQRCode text={fallbackText} width={width} height={height} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 이미지 로드 실패 → TQRCode 폴백
|
||||||
|
if (imageError) {
|
||||||
|
return <TQRCode text={fallbackText} width={width} height={height} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 이미지 로드 성공 → 이미지 표시 (기존 QRCode와 동일한 레이아웃)
|
||||||
|
const sizeInPx = `${width}px`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: sizeInPx,
|
||||||
|
height: sizeInPx,
|
||||||
|
background: 'transparent',
|
||||||
|
margin: '0 auto',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
position: 'relative',
|
||||||
|
overflow: 'hidden',
|
||||||
|
border: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={qrImgUrl}
|
||||||
|
alt="QR Code"
|
||||||
|
onLoad={() => setImageLoaded(true)}
|
||||||
|
onError={() => setImageError(true)}
|
||||||
|
style={{
|
||||||
|
display: imageLoaded ? 'block' : 'none',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
objectFit: 'contain',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{!imageLoaded && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
color: '#999',
|
||||||
|
textAlign: 'center',
|
||||||
|
position: 'absolute',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -52,6 +52,7 @@ export default function BuyNowPriceDisplay({
|
|||||||
display: "inline-flex",
|
display: "inline-flex",
|
||||||
height: "60px",
|
height: "60px",
|
||||||
lineHeight: "60px",
|
lineHeight: "60px",
|
||||||
|
flexWrap: "wrap",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className={css.price}>
|
<span className={css.price}>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@import "../../../../../style/CommonStyle.module.less";
|
@import '../../../../../style/CommonStyle.module.less';
|
||||||
@import "../../../../../style/utils.module.less";
|
@import '../../../../../style/utils.module.less';
|
||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
}
|
}
|
||||||
.name {
|
.name {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 36px;
|
font-size: 30px;
|
||||||
color: @COLOR_WHITE;
|
color: @COLOR_WHITE;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
}
|
}
|
||||||
.name {
|
.name {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 36px;
|
font-size: 30px;
|
||||||
color: @COLOR_GRAY07;
|
color: @COLOR_GRAY07;
|
||||||
}
|
}
|
||||||
.btmLayer {
|
.btmLayer {
|
||||||
@@ -188,9 +188,11 @@
|
|||||||
.productNm {
|
.productNm {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 36px;
|
font-size: 38px;
|
||||||
color: @COLOR_WHITE;
|
color: @COLOR_WHITE;
|
||||||
flex:none;
|
flex:none;
|
||||||
.elip(2);
|
.elip(2);
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
height:76px;
|
||||||
|
line-height:38px;
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,12 @@ import usePriceInfo from '../../../../../hooks/usePriceInfo';
|
|||||||
import { $L } from '../../../../../utils/helperMethods';
|
import { $L } from '../../../../../utils/helperMethods';
|
||||||
import css from './ShopByMobilePriceDisplay.module.less';
|
import css from './ShopByMobilePriceDisplay.module.less';
|
||||||
|
|
||||||
|
// 파트너명에서 "Peacock | Shop The Moment" 형식일 때 "Peacock"만 추출
|
||||||
|
const extractPartnerName = (name) => {
|
||||||
|
if (!name) return name;
|
||||||
|
return name.includes(' | ') ? name.split(' | ')[0].trim() : name;
|
||||||
|
};
|
||||||
|
|
||||||
export default function ShopByMobilePriceDisplay({
|
export default function ShopByMobilePriceDisplay({
|
||||||
priceData,
|
priceData,
|
||||||
priceInfo,
|
priceInfo,
|
||||||
@@ -26,6 +32,10 @@ export default function ShopByMobilePriceDisplay({
|
|||||||
orderPhnNo,
|
orderPhnNo,
|
||||||
} = priceData;
|
} = priceData;
|
||||||
|
|
||||||
|
// 파트너명 정규화
|
||||||
|
const cleanPatncNm = useMemo(() => extractPartnerName(patncNm), [patncNm]);
|
||||||
|
const cleanPatnrName = useMemo(() => extractPartnerName(patnrName), [patnrName]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
discountRate,
|
discountRate,
|
||||||
rewardFlag,
|
rewardFlag,
|
||||||
@@ -50,6 +60,8 @@ export default function ShopByMobilePriceDisplay({
|
|||||||
[isOriginalPriceEmpty, isDiscountedPriceEmpty, price5, offerInfo]
|
[isOriginalPriceEmpty, isDiscountedPriceEmpty, price5, offerInfo]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const renderPriceItem = useCallback(() => {
|
const renderPriceItem = useCallback(() => {
|
||||||
if (priceData && !promotionCode) {
|
if (priceData && !promotionCode) {
|
||||||
if (rewd) {
|
if (rewd) {
|
||||||
@@ -57,9 +69,9 @@ export default function ShopByMobilePriceDisplay({
|
|||||||
<div className={css.wrapper}>
|
<div className={css.wrapper}>
|
||||||
<div className={css.rewdTopLayer}>
|
<div className={css.rewdTopLayer}>
|
||||||
<span>
|
<span>
|
||||||
{patncNm
|
{cleanPatncNm
|
||||||
? patncNm + " " + $L("Price") + " "
|
? cleanPatncNm + " " + $L("Price") + " "
|
||||||
: patnrName + " " + $L("Price") + " "}
|
: cleanPatnrName + " " + $L("Price") + " "}
|
||||||
</span>
|
</span>
|
||||||
<span className={css.partnerPrc}>
|
<span className={css.partnerPrc}>
|
||||||
{TYPE_CASE.case5 || TYPE_CASE.case8
|
{TYPE_CASE.case5 || TYPE_CASE.case8
|
||||||
@@ -95,9 +107,9 @@ export default function ShopByMobilePriceDisplay({
|
|||||||
return (
|
return (
|
||||||
<div className={css.wrapper}>
|
<div className={css.wrapper}>
|
||||||
<span className={css.name}>
|
<span className={css.name}>
|
||||||
{patncNm
|
{cleanPatncNm
|
||||||
? patncNm + " " + $L("Price")
|
? cleanPatncNm + " " + $L("Price")
|
||||||
: patnrName + " " + $L("Price")}
|
: cleanPatnrName + " " + $L("Price")}
|
||||||
</span>
|
</span>
|
||||||
<div className={css.btmLayer}>
|
<div className={css.btmLayer}>
|
||||||
<span className={classNames(css.price, css.case01)}>
|
<span className={classNames(css.price, css.case01)}>
|
||||||
@@ -111,9 +123,9 @@ export default function ShopByMobilePriceDisplay({
|
|||||||
<div className={css.wrapper}>
|
<div className={css.wrapper}>
|
||||||
<div className={css.topLayer}>
|
<div className={css.topLayer}>
|
||||||
<span className={css.name}>
|
<span className={css.name}>
|
||||||
{patncNm
|
{cleanPatncNm
|
||||||
? patncNm + " " + $L("Price")
|
? cleanPatncNm + " " + $L("Price")
|
||||||
: patnrName + $L("Price")}
|
: cleanPatnrName + $L("Price")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{discountRate && Number(discountRate.replace("%", "")) > 4 && (
|
{discountRate && Number(discountRate.replace("%", "")) > 4 && (
|
||||||
@@ -140,9 +152,9 @@ export default function ShopByMobilePriceDisplay({
|
|||||||
<div className={css.wrapper}>
|
<div className={css.wrapper}>
|
||||||
<div className={css.topLayer}>
|
<div className={css.topLayer}>
|
||||||
<span className={css.name}>
|
<span className={css.name}>
|
||||||
{patncNm
|
{cleanPatncNm
|
||||||
? patncNm + " " + $L("Price")
|
? cleanPatncNm + " " + $L("Price")
|
||||||
: patnrName + " " + $L("Price")}
|
: cleanPatnrName + " " + $L("Price")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{discountRate && Number(discountRate.replace("%", "")) > 4 && (
|
{discountRate && Number(discountRate.replace("%", "")) > 4 && (
|
||||||
@@ -194,7 +206,7 @@ export default function ShopByMobilePriceDisplay({
|
|||||||
return (
|
return (
|
||||||
<div className={css.wrapper}>
|
<div className={css.wrapper}>
|
||||||
<span className={css.partnerName}>
|
<span className={css.partnerName}>
|
||||||
{patncNm ? patncNm + " " + $L("Price") : patnrName + $L("Price")}
|
{cleanPatncNm ? cleanPatncNm + " " + $L("Price") : cleanPatnrName + $L("Price")}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={css.offerInfo}
|
className={css.offerInfo}
|
||||||
@@ -204,7 +216,8 @@ export default function ShopByMobilePriceDisplay({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
patnrName,
|
cleanPatnrName,
|
||||||
|
cleanPatncNm,
|
||||||
priceInfo,
|
priceInfo,
|
||||||
isOriginalPriceEmpty,
|
isOriginalPriceEmpty,
|
||||||
isDiscountedPriceEmpty,
|
isDiscountedPriceEmpty,
|
||||||
@@ -214,4 +227,4 @@ export default function ShopByMobilePriceDisplay({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
return <div>{renderPriceItem()}</div>;
|
return <div>{renderPriceItem()}</div>;
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
@import "../../../../../style/CommonStyle.module.less";
|
@import '../../../../../style/CommonStyle.module.less';
|
||||||
@import "../../../../../style/utils.module.less";
|
@import '../../../../../style/utils.module.less';
|
||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
}
|
}
|
||||||
.name {
|
.name {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 36px;
|
font-size: 30px;
|
||||||
color: @COLOR_WHITE;
|
color: @COLOR_WHITE;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.btmLayer2 {
|
.btmLayer2 {
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -78,35 +78,35 @@ export default function ThemeItemCard({
|
|||||||
const { originalPrice, discountedPrice, discountRate } =
|
const { originalPrice, discountedPrice, discountRate } =
|
||||||
usePriceInfo(priceInfo) || {};
|
usePriceInfo(priceInfo) || {};
|
||||||
|
|
||||||
const mockEnergyLabel = [
|
// const mockEnergyLabel = [
|
||||||
{
|
// {
|
||||||
"enrgLblExpsOrd": "0",
|
// "enrgLblExpsOrd": "0",
|
||||||
"enrgLblTpCd": "EL_TYPE_05",
|
// "enrgLblTpCd": "EL_TYPE_05",
|
||||||
"enrgLblCd": "MNLC",
|
// "enrgLblCd": "MNLC",
|
||||||
"enrgClasCd": "A",
|
// "enrgClasCd": "A",
|
||||||
"enrgLblIcnUrl": "http://eic-ngfts.lge.com/fts/gftsDownload.lge?biz_code=LGSHOPPING&func_code=IMAGE&file_path=/lgshopping/image/gb_class_arrows_ag_a.png",
|
// "enrgLblIcnUrl": "http://eic-ngfts.lge.com/fts/gftsDownload.lge?biz_code=LGSHOPPING&func_code=IMAGE&file_path=/lgshopping/image/gb_class_arrows_ag_a.png",
|
||||||
"enrgLblUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241113229264&ORIGINAL_NAME_b1_a1=27U511SA EU (E).pdf&FILE_NAME=27U511SA EU (E)[20241113011401634].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N",
|
// "enrgLblUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241113229264&ORIGINAL_NAME_b1_a1=27U511SA EU (E).pdf&FILE_NAME=27U511SA EU (E)[20241113011401634].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N",
|
||||||
"enrgShetUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241125231113&ORIGINAL_NAME_b1_a1=27U511SA-W.pdf&FILE_NAME=27U511SA-W[20241125231113].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N"
|
// "enrgShetUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241125231113&ORIGINAL_NAME_b1_a1=27U511SA-W.pdf&FILE_NAME=27U511SA-W[20241125231113].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N"
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
"enrgLblExpsOrd": "0",
|
// "enrgLblExpsOrd": "0",
|
||||||
"enrgLblTpCd": "EL_TYPE_05",
|
// "enrgLblTpCd": "EL_TYPE_05",
|
||||||
"enrgLblCd": "MNLC",
|
// "enrgLblCd": "MNLC",
|
||||||
"enrgClasCd": "D",
|
// "enrgClasCd": "D",
|
||||||
"enrgLblIcnUrl": "http://eic-ngfts.lge.com/fts/gftsDownload.lge?biz_code=LGSHOPPING&func_code=IMAGE&file_path=/lgshopping/image/gb_class_arrows_ag_d.png",
|
// "enrgLblIcnUrl": "http://eic-ngfts.lge.com/fts/gftsDownload.lge?biz_code=LGSHOPPING&func_code=IMAGE&file_path=/lgshopping/image/gb_class_arrows_ag_d.png",
|
||||||
"enrgLblUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241230236057&ORIGINAL_NAME_b1_a1=27U421A EU (E).pdf&FILE_NAME=27U421A EU (E)[20241230015816192].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N",
|
// "enrgLblUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241230236057&ORIGINAL_NAME_b1_a1=27U421A EU (E).pdf&FILE_NAME=27U421A EU (E)[20241230015816192].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N",
|
||||||
"enrgShetUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241224235911&ORIGINAL_NAME_b1_a1=27U421A-B.pdf&FILE_NAME=27U421A-B[20241224235911].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N"
|
// "enrgShetUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241224235911&ORIGINAL_NAME_b1_a1=27U421A-B.pdf&FILE_NAME=27U421A-B[20241224235911].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N"
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
"enrgLblExpsOrd": "0",
|
// "enrgLblExpsOrd": "0",
|
||||||
"enrgLblTpCd": "EL_TYPE_05",
|
// "enrgLblTpCd": "EL_TYPE_05",
|
||||||
"enrgLblCd": "MNLC",
|
// "enrgLblCd": "MNLC",
|
||||||
"enrgClasCd": "D",
|
// "enrgClasCd": "D",
|
||||||
"enrgLblIcnUrl": "http://eic-ngfts.lge.com/fts/gftsDownload.lge?biz_code=LGSHOPPING&func_code=IMAGE&file_path=/lgshopping/image/gb_class_arrows_ag_d.png",
|
// "enrgLblIcnUrl": "http://eic-ngfts.lge.com/fts/gftsDownload.lge?biz_code=LGSHOPPING&func_code=IMAGE&file_path=/lgshopping/image/gb_class_arrows_ag_d.png",
|
||||||
"enrgLblUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241230236057&ORIGINAL_NAME_b1_a1=27U421A EU (E).pdf&FILE_NAME=27U421A EU (E)[20241230015816192].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N",
|
// "enrgLblUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241230236057&ORIGINAL_NAME_b1_a1=27U421A EU (E).pdf&FILE_NAME=27U421A EU (E)[20241230015816192].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N",
|
||||||
"enrgShetUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241224235911&ORIGINAL_NAME_b1_a1=27U421A-B.pdf&FILE_NAME=27U421A-B[20241224235911].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N"
|
// "enrgShetUrl": "https://www.lg.com/uk/lgecs.downloadFile.ldwf?DOC_ID=20241224235911&ORIGINAL_NAME_b1_a1=27U421A-B.pdf&FILE_NAME=27U421A-B[20241224235911].pdf&TC=DwnCmd&GSRI_DOC=GSRI&SPEC_DOWNLOAD=N"
|
||||||
}
|
// }
|
||||||
];
|
// ];
|
||||||
|
|
||||||
const setEnactFitZIndex = (zIndexValue) => {
|
const setEnactFitZIndex = (zIndexValue) => {
|
||||||
const target = document.getElementById("floatLayer");
|
const target = document.getElementById("floatLayer");
|
||||||
@@ -211,7 +211,7 @@ export default function ThemeItemCard({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)} */}
|
)} */}
|
||||||
{mockEnergyLabel && mockEnergyLabel.length > 0 && (
|
{/* {mockEnergyLabel && mockEnergyLabel.length > 0 && (
|
||||||
<div className={css.energyLabels}>
|
<div className={css.energyLabels}>
|
||||||
{mockEnergyLabel.map((label, labelIndex) => (
|
{mockEnergyLabel.map((label, labelIndex) => (
|
||||||
<SpottableTemp
|
<SpottableTemp
|
||||||
@@ -228,7 +228,7 @@ export default function ThemeItemCard({
|
|||||||
</SpottableTemp>
|
</SpottableTemp>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
</SpottableDiv>
|
</SpottableDiv>
|
||||||
{(() => {
|
{(() => {
|
||||||
|
|||||||
@@ -1425,8 +1425,6 @@ const BuyOption = ({
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
Spotlight.focus('buy-option-buy-now-button');
|
Spotlight.focus('buy-option-buy-now-button');
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatch(setHidePopup());
|
|
||||||
dispatch(launchMembershipApp());
|
dispatch(launchMembershipApp());
|
||||||
} else {
|
} else {
|
||||||
dispatch(setShowPopup(Config.ACTIVE_POPUP.qrPopup));
|
dispatch(setShowPopup(Config.ACTIVE_POPUP.qrPopup));
|
||||||
@@ -1525,6 +1523,7 @@ const handleCartMove = useCallback(() => {
|
|||||||
disabled: detail.optStkQty <= 0,
|
disabled: detail.optStkQty <= 0,
|
||||||
imageUrl: detail.optImgUrl || null,
|
imageUrl: detail.optImgUrl || null,
|
||||||
price: detail.priceInfo.split('|')[1],
|
price: detail.priceInfo.split('|')[1],
|
||||||
|
prodOptCdCval: detail.prodOptCdCval,
|
||||||
})) || []),
|
})) || []),
|
||||||
]}
|
]}
|
||||||
selectedIndex={selectedOptionItemIndex}
|
selectedIndex={selectedOptionItemIndex}
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ const CustomDropDown = ({
|
|||||||
|
|
||||||
const selectedOption = normalizedOptions[selectedIndex];
|
const selectedOption = normalizedOptions[selectedIndex];
|
||||||
const selectedLabel = selectedOption?.label || placeholder;
|
const selectedLabel = selectedOption?.label || placeholder;
|
||||||
|
const selectedId = selectedOption?.prodOptCdCval ? selectedOption?.prodOptCdCval : null;
|
||||||
const selectedImage = selectedOption?.imageUrl;
|
const selectedImage = selectedOption?.imageUrl;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -93,7 +94,7 @@ const CustomDropDown = ({
|
|||||||
className={styles.custom_dropdown__image}
|
className={styles.custom_dropdown__image}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className={styles.custom_dropdown__text}>{selectedLabel}</div>
|
<div className={styles.custom_dropdown__text}>{selectedLabel} {selectedId ? `ID : ${selectedId}` : ''}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.custom_dropdown__icon}>
|
<div className={styles.custom_dropdown__icon}>
|
||||||
<img src={iconDownArrow} alt="dropdown arrow" />
|
<img src={iconDownArrow} alt="dropdown arrow" />
|
||||||
@@ -108,7 +109,6 @@ const CustomDropDown = ({
|
|||||||
.map((option, reverseIndex) => {
|
.map((option, reverseIndex) => {
|
||||||
const originalIndex = normalizedOptions.length - 1 - reverseIndex;
|
const originalIndex = normalizedOptions.length - 1 - reverseIndex;
|
||||||
const isOptionDisabled = option.disabled;
|
const isOptionDisabled = option.disabled;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SpottableDiv
|
<SpottableDiv
|
||||||
key={originalIndex}
|
key={originalIndex}
|
||||||
@@ -133,7 +133,7 @@ const CustomDropDown = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<span className={styles.custom_dropdown__optname}>
|
<span className={styles.custom_dropdown__optname}>
|
||||||
{option.label}
|
{option.label} {option.prodOptCdCval ? `ID : ${option.prodOptCdCval}` : ''}
|
||||||
</span>
|
</span>
|
||||||
{isOptionDisabled ? (
|
{isOptionDisabled ? (
|
||||||
<span className={styles.custom_dropdown__lasttxt}>
|
<span className={styles.custom_dropdown__lasttxt}>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import ontv4u from '../../../../../assets/images/bg/ontv4u_new.png';
|
|||||||
import Pinkfong from '../../../../../assets/images/bg/Pinkfong_new.png';
|
import Pinkfong from '../../../../../assets/images/bg/Pinkfong_new.png';
|
||||||
import qvc from '../../../../../assets/images/bg/qvc_new.png';
|
import qvc from '../../../../../assets/images/bg/qvc_new.png';
|
||||||
import shoplc from '../../../../../assets/images/bg/shoplc_new.png';
|
import shoplc from '../../../../../assets/images/bg/shoplc_new.png';
|
||||||
|
import nbcu from '../../../../../assets/images/bg/nbcu_new.png';
|
||||||
import css from './DetailPanelBackground.module.less';
|
import css from './DetailPanelBackground.module.less';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,6 +39,7 @@ export default function DetailPanelBackground({
|
|||||||
11: shoplc,
|
11: shoplc,
|
||||||
16: koreaKiosk,
|
16: koreaKiosk,
|
||||||
19: Pinkfong,
|
19: Pinkfong,
|
||||||
|
21: nbcu,
|
||||||
};
|
};
|
||||||
|
|
||||||
const detailPanelBg = useMemo(() => {
|
const detailPanelBg = useMemo(() => {
|
||||||
@@ -75,26 +77,32 @@ export default function DetailPanelBackground({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// launchedFromPlayer가 true이면 배경 이미지를 로드하지 않음 (PlayerPanel 비디오 보이도록)
|
// launchedFromPlayer가 true이면 배경 이미지를 로드하지 않음 (PlayerPanel 비디오 보이도록)
|
||||||
if (launchedFromPlayer) {
|
if (launchedFromPlayer) {
|
||||||
// console.log('[DetailPanelBackground] Skip background image loading - launchedFromPlayer=true (showing PlayerPanel video)');
|
console.log('[Detail-BG] 🔵 DetailPanelBackground - Skip background image (launchedFromPlayer=true, showing PlayerPanel video)', {
|
||||||
|
launchedFromPlayer,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
setImageReady(false);
|
setImageReady(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// launchedFromPlayer가 false일 때만 배경 이미지 로드
|
// launchedFromPlayer가 false일 때만 배경 이미지 로드
|
||||||
// console.log('[DetailPanelBackground] Loading background image - launchedFromPlayer=false');
|
console.log('[Detail-BG] 🟢 DetailPanelBackground - Loading background image (launchedFromPlayer=false)', {
|
||||||
|
patnrId,
|
||||||
|
imagePath: detailPanelBg,
|
||||||
|
});
|
||||||
if (ImagePreloader.isLoaded(detailPanelBg)) {
|
if (ImagePreloader.isLoaded(detailPanelBg)) {
|
||||||
// console.log('[DetailPanelBackground] Using preloaded image:', detailPanelBg);
|
console.log('[Detail-BG] ✅ DetailPanelBackground - Using preloaded image:', detailPanelBg);
|
||||||
setImageReady(true);
|
setImageReady(true);
|
||||||
} else {
|
} else {
|
||||||
// 프리로드되지 않았다면 즉시 로드 시도
|
// 프리로드되지 않았다면 즉시 로드 시도
|
||||||
// console.log('[DetailPanelBackground] Image not preloaded, loading on-demand:', detailPanelBg);
|
console.log('[Detail-BG] 📥 DetailPanelBackground - Image not preloaded, loading on-demand:', detailPanelBg);
|
||||||
ImagePreloader.preloadImage(detailPanelBg)
|
ImagePreloader.preloadImage(detailPanelBg)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// console.log('[DetailPanelBackground] On-demand image loaded:', detailPanelBg);
|
console.log('[Detail-BG] ✅ DetailPanelBackground - On-demand image loaded:', detailPanelBg);
|
||||||
setImageReady(true);
|
setImageReady(true);
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
// console.error('[DetailPanelBackground] On-demand image load failed:', e);
|
console.error('[Detail-BG] ❌ DetailPanelBackground - On-demand image load failed:', e);
|
||||||
// 실패해도 이미지를 표시해야 함
|
// 실패해도 이미지를 표시해야 함
|
||||||
setImageReady(true);
|
setImageReady(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import ontv4u from '../../../../../assets/images/bg/ontv4u_new.png';
|
|||||||
import Pinkfong from '../../../../../assets/images/bg/Pinkfong_new.png';
|
import Pinkfong from '../../../../../assets/images/bg/Pinkfong_new.png';
|
||||||
import qvc from '../../../../../assets/images/bg/qvc_new.png';
|
import qvc from '../../../../../assets/images/bg/qvc_new.png';
|
||||||
import shoplc from '../../../../../assets/images/bg/shoplc_new.png';
|
import shoplc from '../../../../../assets/images/bg/shoplc_new.png';
|
||||||
|
import nbcu from '../../../../../assets/images/bg/nbcu_new.png';
|
||||||
import css from './DetailPanelBackground.module.less';
|
import css from './DetailPanelBackground.module.less';
|
||||||
|
|
||||||
// ==================== 로깅 함수들 ====================
|
// ==================== 로깅 함수들 ====================
|
||||||
@@ -65,6 +66,7 @@ export default function DetailPanelBackgroundV2({
|
|||||||
11: shoplc, // SHOPLC
|
11: shoplc, // SHOPLC
|
||||||
16: koreaKiosk, // KOREA KIOSK
|
16: koreaKiosk, // KOREA KIOSK
|
||||||
19: Pinkfong, // PINKFONG
|
19: Pinkfong, // PINKFONG
|
||||||
|
21: nbcu, // NBCU
|
||||||
}),
|
}),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
@@ -200,7 +202,7 @@ export function PreloadedBackgroundImages({
|
|||||||
launchedFromPlayer = false,
|
launchedFromPlayer = false,
|
||||||
}) {
|
}) {
|
||||||
// 모든 파트너사 ID 목록
|
// 모든 파트너사 ID 목록
|
||||||
const allPatnrIds = useMemo(() => [1, 2, 4, 9, 11, 16, 19], []);
|
const allPatnrIds = useMemo(() => [1, 2, 4, 9, 11, 16, 19, 21], []);
|
||||||
|
|
||||||
// ✅ 원래 로직 복원: HomePanel이 onTop이 아니고 selectedPatnrId가 있을 때만 배경 표시
|
// ✅ 원래 로직 복원: HomePanel이 onTop이 아니고 selectedPatnrId가 있을 때만 배경 표시
|
||||||
const shouldShowBackground = !isHomePanelOnTop && selectedPatnrId;
|
const shouldShowBackground = !isHomePanelOnTop && selectedPatnrId;
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export default function THeaderCustom({
|
|||||||
if(onBackButtonFocus){
|
if(onBackButtonFocus){
|
||||||
onBackButtonFocus();
|
onBackButtonFocus();
|
||||||
}
|
}
|
||||||
},[onBackButtonFocus])
|
},[onBackButtonFocus])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className={classNames(css.tHeaderCustom, className)} {...rest}>
|
<Container className={classNames(css.tHeaderCustom, className)} {...rest}>
|
||||||
@@ -93,9 +93,6 @@ export default function THeaderCustom({
|
|||||||
role="button"
|
role="button"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{type === "theme" && themeTitle && (
|
|
||||||
<span className={css.themeTitle} dangerouslySetInnerHTML={{ __html: themeTitle }} />
|
|
||||||
)}
|
|
||||||
{kind ? (
|
{kind ? (
|
||||||
""
|
""
|
||||||
) : (
|
) : (
|
||||||
@@ -107,13 +104,16 @@ export default function THeaderCustom({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{type === "theme" && themeTitle && (
|
||||||
|
<span className={css.themeTitle} dangerouslySetInnerHTML={{ __html: `[${themeTitle}]` }} />
|
||||||
|
)}
|
||||||
<Marquee
|
<Marquee
|
||||||
marqueeOn="render"
|
marqueeOn="render"
|
||||||
className={css.title}
|
className={css.title}
|
||||||
marqueeDisabled={marqueeDisabled}
|
marqueeDisabled={marqueeDisabled}
|
||||||
aria-label={ariaLabel}
|
aria-label={ariaLabel}
|
||||||
>
|
>
|
||||||
{prdtId && <span className={css.prdtId}>ID : {prdtId}</span>}
|
{(prdtId && patnrId !== "21") && <span className={css.prdtId}>ID : {prdtId}</span>}
|
||||||
{convertedTitle && (
|
{convertedTitle && (
|
||||||
<span dangerouslySetInnerHTML={{ __html: convertedTitle }} />
|
<span dangerouslySetInnerHTML={{ __html: convertedTitle }} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -61,4 +61,5 @@
|
|||||||
color: #eaeaea;
|
color: #eaeaea;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,26 @@
|
|||||||
import React, { useCallback, useEffect, useRef, useState, useMemo, forwardRef } from 'react';
|
import React, {
|
||||||
|
forwardRef,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { off, on } from '@enact/core/dispatcher';
|
import {
|
||||||
|
off,
|
||||||
|
on,
|
||||||
|
} from '@enact/core/dispatcher';
|
||||||
import { Job } from '@enact/core/util';
|
import { Job } from '@enact/core/util';
|
||||||
import Scroller from '@enact/sandstone/Scroller';
|
import Scroller from '@enact/sandstone/Scroller';
|
||||||
import Spotlight from '@enact/spotlight';
|
import Spotlight from '@enact/spotlight';
|
||||||
|
|
||||||
import AutoScrollAreaDetail, { POSITION } from '../AutoScrollAreaDetail/AutoScrollAreaDetail';
|
import AutoScrollAreaDetail, {
|
||||||
|
POSITION,
|
||||||
|
} from '../AutoScrollAreaDetail/AutoScrollAreaDetail';
|
||||||
import css from './TScrollerDetail.module.less';
|
import css from './TScrollerDetail.module.less';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -206,7 +218,8 @@ const TScrollerDetail = forwardRef(
|
|||||||
onScrollStop={_onScrollStop}
|
onScrollStop={_onScrollStop}
|
||||||
onScroll={_onScroll}
|
onScroll={_onScroll}
|
||||||
scrollMode={scrollMode || 'translate'}
|
scrollMode={scrollMode || 'translate'}
|
||||||
focusableScrollbar={focusableScrollbar}
|
// focusableScrollbar={focusableScrollbar}
|
||||||
|
focusableScrollbar={false}
|
||||||
className={classNames(isMounted && css.tScroller, noScrollByWheel && css.preventScroll)}
|
className={classNames(isMounted && css.tScroller, noScrollByWheel && css.preventScroll)}
|
||||||
direction={direction}
|
direction={direction}
|
||||||
horizontalScrollbar={horizontalScrollbar}
|
horizontalScrollbar={horizontalScrollbar}
|
||||||
@@ -220,7 +233,7 @@ const TScrollerDetail = forwardRef(
|
|||||||
}}
|
}}
|
||||||
noScrollByWheel={noScrollByWheel}
|
noScrollByWheel={noScrollByWheel}
|
||||||
noScrollByDrag
|
noScrollByDrag
|
||||||
// rest props에서 ref만 제외하고 전달
|
// rest props에서 ref만 제외하고 전달
|
||||||
{...(rest.ref ? { ...rest, ref: undefined } : rest)}
|
{...(rest.ref ? { ...rest, ref: undefined } : rest)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -2,19 +2,55 @@ import React, { memo } from "react";
|
|||||||
|
|
||||||
import IcPartnersDefault from "../../../../assets/images/ic-tab-partners-default@3x.png";
|
import IcPartnersDefault from "../../../../assets/images/ic-tab-partners-default@3x.png";
|
||||||
import CustomImage from "../../../components/CustomImage/CustomImage";
|
import CustomImage from "../../../components/CustomImage/CustomImage";
|
||||||
|
import TopBannerImage from "../TopBannerImage/TopBannerImage";
|
||||||
import css from "./Banner.module.less";
|
import css from "./Banner.module.less";
|
||||||
|
|
||||||
export default memo(function Banner({
|
export default memo(function Banner({
|
||||||
brandInfo,
|
brandInfo,
|
||||||
brandTopImgInfo,
|
brandTopImgInfo,
|
||||||
|
brandTopBannerInfo,
|
||||||
panelPatnrId,
|
panelPatnrId,
|
||||||
|
selectedPatnrId,
|
||||||
}) {
|
}) {
|
||||||
const selectedBrandInfo =
|
const selectedBrandInfo =
|
||||||
brandInfo?.find(({ patnrId }) => panelPatnrId === patnrId) || {};
|
brandInfo?.find(({ patnrId }) => panelPatnrId === patnrId) || {};
|
||||||
|
|
||||||
const { patncLogoPath, patncNm } = selectedBrandInfo;
|
const { patncLogoPath, patncNm } = selectedBrandInfo;
|
||||||
const { topImgAlt, topImgPath } = brandTopImgInfo;
|
const { topImgAlt, topImgPath } = brandTopImgInfo || {};
|
||||||
|
|
||||||
|
// NBCU(patnrId: 21)인 경우 Top Banner 정보 사용
|
||||||
|
const isNBCU = selectedPatnrId === 21 || selectedPatnrId === "21";
|
||||||
|
// console.log("[FB-BANNER-COMP] ===== BANNER COMPONENT START =====");
|
||||||
|
// console.log("[FB-BANNER-COMP] selectedPatnrId:", selectedPatnrId, "(type:", typeof selectedPatnrId, ")");
|
||||||
|
// console.log("[FB-BANNER-COMP] panelPatnrId:", panelPatnrId, "(type:", typeof panelPatnrId, ")");
|
||||||
|
// console.log("[FB-BANNER-COMP] isNBCU:", isNBCU);
|
||||||
|
// console.log("[FB-BANNER-COMP] brandTopBannerInfo:", brandTopBannerInfo);
|
||||||
|
|
||||||
|
// Top Banner 정보에서 필요한 필드 추출
|
||||||
|
const {
|
||||||
|
banrImgUrl, // 배너 이미지 URL
|
||||||
|
banrImgNm, // 배너 이미지 이름
|
||||||
|
pupBanrImgUrl, // 팝업 배너 이미지 URL
|
||||||
|
pupBanrImgNm, // 팝업 배너 이미지 이름
|
||||||
|
pupBanrTtl, // 팝업 배너 타이틀
|
||||||
|
banrNm // 배너 이름
|
||||||
|
} = brandTopBannerInfo || {};
|
||||||
|
|
||||||
|
// 현재는 Top Banner 이미지를 사용하지 않고 기존 Top 이미지만 사용
|
||||||
|
// TODO: 향후 Top Banner 이미지를 사용하려면 아래 주석 처리된 부분을 활성화
|
||||||
|
const bannerImageSrc = topImgPath; // isNBCU ? banrImgUrl : topImgPath;
|
||||||
|
const bannerImageAlt = topImgAlt; // isNBCU ? banrImgNm || banrNm : topImgAlt;
|
||||||
|
|
||||||
|
// console.log("[FB-BANNER-COMP] Top Banner 이미지는 현재 비활성화됨");
|
||||||
|
// if (isNBCU && brandTopBannerInfo) {
|
||||||
|
// console.log("[FB-BANNER-COMP] NBCU Top Banner 데이터 수신 (사용하지 않음):");
|
||||||
|
// console.log("[FB-BANNER-COMP] - banrImgUrl:", banrImgUrl);
|
||||||
|
// console.log("[FB-BANNER-COMP] - pupBanrImgUrl:", pupBanrImgUrl);
|
||||||
|
// console.log("[FB-BANNER-COMP] - pupBanrTtl:", pupBanrTtl);
|
||||||
|
// }
|
||||||
|
// console.log("[FB-BANNER-COMP] Using original Top Image");
|
||||||
|
// console.log("[FB-BANNER-COMP] bannerImageSrc:", bannerImageSrc);
|
||||||
|
// console.log("[FB-BANNER-COMP] ===== BANNER COMPONENT END =====");
|
||||||
return (
|
return (
|
||||||
<div className={css.container}>
|
<div className={css.container}>
|
||||||
<figure>
|
<figure>
|
||||||
@@ -26,7 +62,25 @@ export default memo(function Banner({
|
|||||||
/>
|
/>
|
||||||
<figcaption>{patncNm}</figcaption>
|
<figcaption>{patncNm}</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
<CustomImage src={topImgPath} alt={topImgAlt} ariaLabel={topImgAlt} />
|
{bannerImageSrc && (
|
||||||
|
<CustomImage
|
||||||
|
src={bannerImageSrc}
|
||||||
|
alt={bannerImageAlt}
|
||||||
|
ariaLabel={bannerImageAlt}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* NBCU Top Banner Image */}
|
||||||
|
{isNBCU && brandTopBannerInfo?.banrImgUrl && (
|
||||||
|
<TopBannerImage
|
||||||
|
banrImgUrl={banrImgUrl}
|
||||||
|
banrImgNm={banrImgNm}
|
||||||
|
banrNm={banrNm}
|
||||||
|
pupBanrImgUrl={pupBanrImgUrl}
|
||||||
|
pupBanrImgNm={pupBanrImgNm}
|
||||||
|
spotlightId="nbcu-top-banner-image"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
.container {
|
.container {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
// position: relative; // changed from fixed to relative for absolute positioning of children
|
||||||
.flex(@justifyCenter: flex-start, @alignCenter: flex-end);
|
.flex(@justifyCenter: flex-start, @alignCenter: flex-end);
|
||||||
.size(@w: 100%, @h: 108px);
|
.size(@w: 100%, @h: 108px);
|
||||||
margin-bottom: 58px;
|
margin-bottom: 58px;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const Container = SpotlightContainerDecorator(
|
|||||||
|
|
||||||
const FeaturedBestSeller = ({
|
const FeaturedBestSeller = ({
|
||||||
brandBestSellerInfo,
|
brandBestSellerInfo,
|
||||||
|
brandBestSellerTitle,
|
||||||
handleItemFocus,
|
handleItemFocus,
|
||||||
order,
|
order,
|
||||||
shelfOrder,
|
shelfOrder,
|
||||||
@@ -28,6 +29,12 @@ const FeaturedBestSeller = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [firstChk, setFirstChk] = useState(0);
|
const [firstChk, setFirstChk] = useState(0);
|
||||||
|
|
||||||
|
// brandBestSellerTitle 우선 사용, 없으면 shelfTitle, 없으면 기본값
|
||||||
|
const displayTitle = brandBestSellerTitle || shelfTitle || $L(STRING_CONF.BEST_SELLER);
|
||||||
|
|
||||||
|
// console.log("[FeaturedBestSeller] brandBestSellerTitle:", brandBestSellerTitle);
|
||||||
|
// console.log("[FeaturedBestSeller] displayTitle:", displayTitle);
|
||||||
|
|
||||||
const _handleItemFocus = useCallback(() => {
|
const _handleItemFocus = useCallback(() => {
|
||||||
if (handleItemFocus) handleItemFocus(spotlightId, shelfOrder);
|
if (handleItemFocus) handleItemFocus(spotlightId, shelfOrder);
|
||||||
|
|
||||||
@@ -65,7 +72,7 @@ const FeaturedBestSeller = ({
|
|||||||
spotlightId={spotlightId}
|
spotlightId={spotlightId}
|
||||||
>
|
>
|
||||||
<SectionTitle
|
<SectionTitle
|
||||||
title={$L(STRING_CONF.BEST_SELLER)}
|
title={displayTitle}
|
||||||
data-title="best-seller"
|
data-title="best-seller"
|
||||||
label="best-seller Heading 1"
|
label="best-seller Heading 1"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ export default function FeaturedBestSellerList({
|
|||||||
|
|
||||||
const panelInfo = useSelector((state) => state.panels.panels[0]?.panelInfo);
|
const panelInfo = useSelector((state) => state.panels.panels[0]?.panelInfo);
|
||||||
|
|
||||||
|
// console.log("[FeaturedBestSellerList] Mounted - selectedPatnrId:", selectedPatnrId);
|
||||||
|
// console.log("[FeaturedBestSellerList] brandBestSellerInfo:", brandBestSellerInfo);
|
||||||
|
// console.log("[FeaturedBestSellerList] Data count:", brandBestSellerInfo?.length || 0);
|
||||||
|
|
||||||
const cursorVisible = useSelector(
|
const cursorVisible = useSelector(
|
||||||
(state) => state.common.appStatus.cursorVisible
|
(state) => state.common.appStatus.cursorVisible
|
||||||
);
|
);
|
||||||
@@ -155,6 +159,9 @@ export default function FeaturedBestSellerList({
|
|||||||
lgCatNm,
|
lgCatNm,
|
||||||
euEnrgLblInfos,
|
euEnrgLblInfos,
|
||||||
} = brandBestSellerInfo[index];
|
} = brandBestSellerInfo[index];
|
||||||
|
|
||||||
|
// console.log("[FeaturedBestSellerList] renderItem - index:", index, "patnrId:", patnrId, "rankOrd:", rankOrd, "prdtNm:", prdtNm);
|
||||||
|
|
||||||
const rankText =
|
const rankText =
|
||||||
rankOrd === 1
|
rankOrd === 1
|
||||||
? rankOrd + "st,"
|
? rankOrd + "st,"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { useDispatch, useSelector } from "react-redux";
|
|||||||
|
|
||||||
import { Job } from "@enact/core/util";
|
import { Job } from "@enact/core/util";
|
||||||
import Spotlight from "@enact/spotlight";
|
import Spotlight from "@enact/spotlight";
|
||||||
|
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
|
||||||
|
|
||||||
import { types } from "../../actions/actionTypes";
|
import { types } from "../../actions/actionTypes";
|
||||||
import {
|
import {
|
||||||
@@ -23,8 +24,10 @@ import {
|
|||||||
getBrandLiveChannelInfo,
|
getBrandLiveChannelInfo,
|
||||||
getBrandRecommendedShowInfo,
|
getBrandRecommendedShowInfo,
|
||||||
getBrandSeriesInfo,
|
getBrandSeriesInfo,
|
||||||
|
getBrandShopByShow,
|
||||||
getBrandShowroom,
|
getBrandShowroom,
|
||||||
getBrandTSVInfo,
|
getBrandTSVInfo,
|
||||||
|
getBrandTopBanner,
|
||||||
} from "../../actions/brandActions";
|
} from "../../actions/brandActions";
|
||||||
import { changeAppStatus, setHidePopup } from "../../actions/commonActions";
|
import { changeAppStatus, setHidePopup } from "../../actions/commonActions";
|
||||||
import {
|
import {
|
||||||
@@ -63,6 +66,7 @@ import LiveChannels from "./LiveChannels/LiveChannels";
|
|||||||
import QuickMenu from "./QuickMenu/QuickMenu";
|
import QuickMenu from "./QuickMenu/QuickMenu";
|
||||||
import RecommendedShows from "./RecommendedShows/RecommendedShows";
|
import RecommendedShows from "./RecommendedShows/RecommendedShows";
|
||||||
import Series from "./Series/Series";
|
import Series from "./Series/Series";
|
||||||
|
import ShopByShow from "./ShopByShow/ShopByShow";
|
||||||
import Showroom from "./Showroom/Showroom";
|
import Showroom from "./Showroom/Showroom";
|
||||||
import TodaysDeals from "./TodaysDeals/TodaysDeals";
|
import TodaysDeals from "./TodaysDeals/TodaysDeals";
|
||||||
import UpComing from "./UpComing/UpComing";
|
import UpComing from "./UpComing/UpComing";
|
||||||
@@ -90,6 +94,7 @@ const TEMPLATE_CODE_CONF = {
|
|||||||
SERIES: "BRD00107",
|
SERIES: "BRD00107",
|
||||||
CATEGORY: "BRD00108",
|
CATEGORY: "BRD00108",
|
||||||
SHOWROOM: "BRD00109",
|
SHOWROOM: "BRD00109",
|
||||||
|
NBCU: "BRD00110",
|
||||||
};
|
};
|
||||||
|
|
||||||
const DISPATCH_MAP = Object.freeze({
|
const DISPATCH_MAP = Object.freeze({
|
||||||
@@ -101,10 +106,22 @@ const DISPATCH_MAP = Object.freeze({
|
|||||||
[TEMPLATE_CODE_CONF.SERIES]: getBrandSeriesInfo,
|
[TEMPLATE_CODE_CONF.SERIES]: getBrandSeriesInfo,
|
||||||
[TEMPLATE_CODE_CONF.CATEGORY]: getBrandCategoryInfo,
|
[TEMPLATE_CODE_CONF.CATEGORY]: getBrandCategoryInfo,
|
||||||
[TEMPLATE_CODE_CONF.SHOWROOM]: getBrandShowroom,
|
[TEMPLATE_CODE_CONF.SHOWROOM]: getBrandShowroom,
|
||||||
|
[TEMPLATE_CODE_CONF.NBCU]: getBrandShopByShow,
|
||||||
});
|
});
|
||||||
|
|
||||||
const TOP_MARGIN = 36;
|
const TOP_MARGIN = 36;
|
||||||
|
|
||||||
|
// 🆕 [251211] NBCU(patnrId=21) 전용 통합 Container
|
||||||
|
// Banner + Category + BestSeller를 같은 Spotlight 계층 내에서 관리
|
||||||
|
const NbcuIntegratedContainer = SpotlightContainerDecorator(
|
||||||
|
{
|
||||||
|
spotlightDirection: 'vertical',
|
||||||
|
enterTo: 'last-focused',
|
||||||
|
preserveld: true,
|
||||||
|
},
|
||||||
|
'div'
|
||||||
|
);
|
||||||
|
|
||||||
const hasTemplateCodeWithValue = (array, value) =>
|
const hasTemplateCodeWithValue = (array, value) =>
|
||||||
array?.some((obj) => obj?.shptmBrndOptTpCd === value) ?? false;
|
array?.some((obj) => obj?.shptmBrndOptTpCd === value) ?? false;
|
||||||
|
|
||||||
@@ -233,6 +250,9 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
|
|||||||
const brandBestSellerInfo = useSelector(
|
const brandBestSellerInfo = useSelector(
|
||||||
(state) => state.brand.brandBestSellerData.data.brandBestSellerInfo
|
(state) => state.brand.brandBestSellerData.data.brandBestSellerInfo
|
||||||
);
|
);
|
||||||
|
const brandBestSellerTitle = useSelector(
|
||||||
|
(state) => state.brand.brandBestSellerData.data.brandBestSellerTitle
|
||||||
|
);
|
||||||
const brandRecommendedShowCategoryInfo = useSelector(
|
const brandRecommendedShowCategoryInfo = useSelector(
|
||||||
(state) =>
|
(state) =>
|
||||||
state.brand.brandRecommendedShowInfoData.data
|
state.brand.brandRecommendedShowInfoData.data
|
||||||
@@ -263,6 +283,19 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
|
|||||||
const brandShowroomInfo = useSelector(
|
const brandShowroomInfo = useSelector(
|
||||||
(state) => state.brand.brandShowroomData.data.brandShowroomInfo
|
(state) => state.brand.brandShowroomData.data.brandShowroomInfo
|
||||||
);
|
);
|
||||||
|
const brandShopByShowContsList = useSelector(
|
||||||
|
(state) => state.brand.brandShopByShowData.data.brandShopByShowContsList
|
||||||
|
);
|
||||||
|
const brandShopByShowContsInfo = useSelector(
|
||||||
|
(state) => state.brand.brandShopByShowData.data.brandShopByShowContsInfo
|
||||||
|
);
|
||||||
|
const brandTopBannerInfo = useSelector(
|
||||||
|
(state) => state.brand.brandTopBannerData.data.brandTopBannerInfo
|
||||||
|
);
|
||||||
|
// 🆕 [251210] patnrId=21 카테고리 그룹 데이터
|
||||||
|
const brandShopByShowCategoryGroups = useSelector(
|
||||||
|
(state) => state.brand.brandShopByShowCategoryGroups
|
||||||
|
);
|
||||||
|
|
||||||
const [displayTopButton, setDisplayTopButton] = useState(false);
|
const [displayTopButton, setDisplayTopButton] = useState(false);
|
||||||
const [focusedContainerId, setFocusedContainerId] = useState(null);
|
const [focusedContainerId, setFocusedContainerId] = useState(null);
|
||||||
@@ -293,6 +326,9 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
|
|||||||
});
|
});
|
||||||
const renderedShelfCountRef = useRef(0);
|
const renderedShelfCountRef = useRef(0);
|
||||||
|
|
||||||
|
// 🆕 [251210] patnrId=21 카테고리 그룹 조회 상태 추적
|
||||||
|
const fetchedCategoryGroupsRef = useRef(new Set());
|
||||||
|
|
||||||
const fromDetail = panelInfo?.from && panelInfo.from === "detail";
|
const fromDetail = panelInfo?.from && panelInfo.from === "detail";
|
||||||
const fromGNB = panelInfo?.from && panelInfo.from === "gnb";
|
const fromGNB = panelInfo?.from && panelInfo.from === "gnb";
|
||||||
const fromUpcoming = panelInfo?.from && panelInfo.from === "upcoming";
|
const fromUpcoming = panelInfo?.from && panelInfo.from === "upcoming";
|
||||||
@@ -412,9 +448,12 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const renderPageItem = useCallback(() => {
|
const renderPageItem = useCallback(() => {
|
||||||
|
// console.log("[FeaturedBrandsPanel] renderPageItem - sortedBrandLayoutInfo length:", sortedBrandLayoutInfo.length);
|
||||||
|
// console.log("[FeaturedBrandsPanel] renderPageItem - sortedBrandLayoutInfo items:", sortedBrandLayoutInfo.map(el => el.shptmBrndOptTpCd));
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{sortedBrandLayoutInfo.map((el, idx) => {
|
{sortedBrandLayoutInfo.map((el, idx) => {
|
||||||
|
// console.log("[FeaturedBrandsPanel] Processing template code:", el.shptmBrndOptTpCd);
|
||||||
switch (el.shptmBrndOptTpCd) {
|
switch (el.shptmBrndOptTpCd) {
|
||||||
case TEMPLATE_CODE_CONF.LIVE_CHANNELS: {
|
case TEMPLATE_CODE_CONF.LIVE_CHANNELS: {
|
||||||
return (
|
return (
|
||||||
@@ -485,6 +524,10 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case TEMPLATE_CODE_CONF.BEST_SELLER: {
|
case TEMPLATE_CODE_CONF.BEST_SELLER: {
|
||||||
|
// console.log("[FeaturedBrandsPanel] BEST_SELLER - patnrId:", selectedPatnrId);
|
||||||
|
// console.log("[FeaturedBrandsPanel] BEST_SELLER - hasTemplateCode:", hasTemplateCodeWithValue(sortedBrandLayoutInfo, TEMPLATE_CODE_CONF.BEST_SELLER));
|
||||||
|
// console.log("[FeaturedBrandsPanel] BEST_SELLER - shouldRender:", shouldRenderComponent(brandBestSellerInfo));
|
||||||
|
// console.log("[FeaturedBrandsPanel] BEST_SELLER - data:", brandBestSellerInfo);
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={el.shptmBrndOptTpCd}>
|
<React.Fragment key={el.shptmBrndOptTpCd}>
|
||||||
{hasTemplateCodeWithValue(
|
{hasTemplateCodeWithValue(
|
||||||
@@ -492,15 +535,19 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
|
|||||||
TEMPLATE_CODE_CONF.BEST_SELLER
|
TEMPLATE_CODE_CONF.BEST_SELLER
|
||||||
) &&
|
) &&
|
||||||
shouldRenderComponent(brandBestSellerInfo) && (
|
shouldRenderComponent(brandBestSellerInfo) && (
|
||||||
<FeaturedBestSeller
|
<>
|
||||||
brandBestSellerInfo={brandBestSellerInfo}
|
{/* {console.log("[FeaturedBrandsPanel] Rendering FeaturedBestSeller for patnrId:", selectedPatnrId)} */}
|
||||||
handleItemFocus={handleItemFocus}
|
<FeaturedBestSeller
|
||||||
order={idx + 1}
|
brandBestSellerInfo={brandBestSellerInfo}
|
||||||
shelfOrder={el.expsOrd}
|
brandBestSellerTitle={brandBestSellerTitle}
|
||||||
shelfTitle={el.shptmBrndOptTpNm}
|
handleItemFocus={handleItemFocus}
|
||||||
spotlightId={TEMPLATE_CODE_CONF.BEST_SELLER}
|
order={idx + 1}
|
||||||
selectedPatnrId={selectedPatnrId}
|
shelfOrder={el.expsOrd}
|
||||||
/>
|
shelfTitle={el.shptmBrndOptTpNm}
|
||||||
|
spotlightId={TEMPLATE_CODE_CONF.BEST_SELLER}
|
||||||
|
selectedPatnrId={selectedPatnrId}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
@@ -650,12 +697,43 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case TEMPLATE_CODE_CONF.NBCU: {
|
||||||
|
// console.log("[FeaturedBrandsPanel] NBCU - patnrId:", selectedPatnrId);
|
||||||
|
// console.log("[FeaturedBrandsPanel] NBCU - hasTemplateCode:", hasTemplateCodeWithValue(sortedBrandLayoutInfo, TEMPLATE_CODE_CONF.NBCU));
|
||||||
|
// console.log("[FeaturedBrandsPanel] NBCU - shouldRender:", shouldRenderComponent(brandShopByShowContsList));
|
||||||
|
// console.log("[FeaturedBrandsPanel] NBCU - data:", brandShopByShowContsList);
|
||||||
|
return (
|
||||||
|
<React.Fragment key={el.shptmBrndOptTpCd}>
|
||||||
|
{hasTemplateCodeWithValue(
|
||||||
|
sortedBrandLayoutInfo,
|
||||||
|
TEMPLATE_CODE_CONF.NBCU
|
||||||
|
) &&
|
||||||
|
shouldRenderComponent(brandShopByShowContsList) && (
|
||||||
|
<>
|
||||||
|
{/* {console.log("[FeaturedBrandsPanel] Rendering ShopByShow for patnrId:", selectedPatnrId)} */}
|
||||||
|
<ShopByShow
|
||||||
|
brandShopByShowContsList={brandShopByShowContsList}
|
||||||
|
brandShopByShowContsInfo={brandShopByShowContsInfo}
|
||||||
|
handleItemFocus={handleItemFocus}
|
||||||
|
order={idx + 1}
|
||||||
|
shelfOrder={el.expsOrd}
|
||||||
|
shelfTitle={el.shptmBrndOptTpNm}
|
||||||
|
spotlightId={TEMPLATE_CODE_CONF.NBCU}
|
||||||
|
selectedPatnrId={selectedPatnrId}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
brandBestSellerInfo,
|
brandBestSellerInfo,
|
||||||
|
brandBestSellerTitle,
|
||||||
brandCategoryInfo,
|
brandCategoryInfo,
|
||||||
brandCategoryProductInfo,
|
brandCategoryProductInfo,
|
||||||
brandChanInfo,
|
brandChanInfo,
|
||||||
@@ -668,6 +746,8 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
|
|||||||
brandSeriesGroupInfo,
|
brandSeriesGroupInfo,
|
||||||
brandSeriesInfo,
|
brandSeriesInfo,
|
||||||
brandShowroomInfo,
|
brandShowroomInfo,
|
||||||
|
brandShopByShowContsList,
|
||||||
|
brandShopByShowContsInfo,
|
||||||
brandTsvInfo,
|
brandTsvInfo,
|
||||||
fromGNB,
|
fromGNB,
|
||||||
fromQuickMenu,
|
fromQuickMenu,
|
||||||
@@ -711,6 +791,7 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
|
|||||||
// effect: layout information fetching due to partner id change
|
// effect: layout information fetching due to partner id change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!fromDetail) {
|
if (!fromDetail) {
|
||||||
|
// console.log("[FeaturedBrandsPanel] Layout Info Effect - patnrId:", panelInfo?.patnrId);
|
||||||
dispatch({ type: types.RESET_BRAND_LAYOUT_INFO });
|
dispatch({ type: types.RESET_BRAND_LAYOUT_INFO });
|
||||||
dispatch(getBrandLayoutInfo({ patnrId: panelInfo?.patnrId }));
|
dispatch(getBrandLayoutInfo({ patnrId: panelInfo?.patnrId }));
|
||||||
setIsInitialFocusOccurred(false);
|
setIsInitialFocusOccurred(false);
|
||||||
@@ -720,30 +801,98 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
|
|||||||
|
|
||||||
// effect: set selectedPatnrId and selectedPatncNm
|
// effect: set selectedPatnrId and selectedPatncNm
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (brandInfo) {
|
if (brandInfo && panelInfo?.patnrId) {
|
||||||
const patnrId = panelInfo?.patnrId;
|
const patnrId = panelInfo.patnrId;
|
||||||
const patncNm = brandInfo.find((b) => b?.patnrId === patnrId).patncNm;
|
const patncNm = brandInfo.find((b) => b?.patnrId === patnrId)?.patncNm;
|
||||||
|
|
||||||
setSelectedPatncNm(patncNm);
|
if (patncNm) {
|
||||||
|
setSelectedPatncNm(patncNm);
|
||||||
|
}
|
||||||
|
|
||||||
if (!fromDetail) setSelectedPatnrId(patnrId);
|
// Detail에서 돌아와도 patnrId가 비어 있으면 다시 설정해 API 호출이 정상 동작하도록 보완
|
||||||
|
if (!fromDetail || !selectedPatnrId) {
|
||||||
|
setSelectedPatnrId(patnrId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [brandInfo, panelInfo?.patnrId]);
|
}, [brandInfo, panelInfo?.patnrId, selectedPatnrId, fromDetail]);
|
||||||
|
|
||||||
// effect: data fetching based on brandLayoutInfo and selectedPatnrId
|
// effect: data fetching based on brandLayoutInfo and selectedPatnrId
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// console.log("[FB-PANEL-DATA-FETCH] Effect triggered");
|
||||||
|
// console.log("[FB-PANEL-DATA-FETCH] sortedBrandLayoutInfo:", sortedBrandLayoutInfo);
|
||||||
|
// console.log("[FB-PANEL-DATA-FETCH] selectedPatnrId:", selectedPatnrId);
|
||||||
|
|
||||||
|
// 🆕 [251210] patnrId 변경 시 조회 상태 초기화
|
||||||
|
if (selectedPatnrId) {
|
||||||
|
const patnrIdString = String(selectedPatnrId);
|
||||||
|
// 이전 patnrId와 다르면 ref 초기화
|
||||||
|
const currentFetchKeys = Array.from(fetchedCategoryGroupsRef.current).filter(key => key.startsWith(patnrIdString));
|
||||||
|
if (currentFetchKeys.length === 0) {
|
||||||
|
// console.log("[FB-PANEL-DATA-FETCH] patnrId changed, clearing category group fetch status");
|
||||||
|
// 다른 patnrId로 전환 시 ref 초기화
|
||||||
|
fetchedCategoryGroupsRef.current.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (sortedBrandLayoutInfo && selectedPatnrId) {
|
if (sortedBrandLayoutInfo && selectedPatnrId) {
|
||||||
|
// console.log("[FB-PANEL-DATA-FETCH] Fetching data - patnrId:", selectedPatnrId);
|
||||||
Object.entries(DISPATCH_MAP) //
|
Object.entries(DISPATCH_MAP) //
|
||||||
.forEach(([templateCode, action]) => {
|
.forEach(([templateCode, action]) => {
|
||||||
if (hasTemplateCodeWithValue(sortedBrandLayoutInfo, templateCode)) {
|
if (hasTemplateCodeWithValue(sortedBrandLayoutInfo, templateCode)) {
|
||||||
|
// Detail 복귀 시 ShopByShow 데이터가 이미 있으면 재호출을 건너뛰어 선택 상태가 초기화되는 것을 방지
|
||||||
|
if (
|
||||||
|
templateCode === TEMPLATE_CODE_CONF.NBCU &&
|
||||||
|
fromDetail &&
|
||||||
|
shouldRenderComponent(brandShopByShowContsList)
|
||||||
|
) {
|
||||||
|
// console.log("[FB-PANEL-DATA-FETCH] Skip re-fetch ShopByShow on return from detail");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log("[FB-PANEL-DATA-FETCH] Dispatching for template:", templateCode, "patnrId:", selectedPatnrId);
|
||||||
dispatch(action({ patnrId: selectedPatnrId }));
|
dispatch(action({ patnrId: selectedPatnrId }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// NBCU(patnrId: 21)인 경우 Top Banner API 호출
|
||||||
|
if (selectedPatnrId === 21 || selectedPatnrId === "21") {
|
||||||
|
// console.log("[FB-PANEL-TOP-BANNER] NBCU(patnrId=21) detected - calling Top Banner API");
|
||||||
|
// console.log("[FB-PANEL-TOP-BANNER] selectedPatnrId:", selectedPatnrId, "(type:", typeof selectedPatnrId, ")");
|
||||||
|
// console.log("[FB-PANEL-TOP-BANNER] Before API call - brandTopBannerInfo:", brandTopBannerInfo);
|
||||||
|
dispatch(getBrandTopBanner({ patnrId: selectedPatnrId }));
|
||||||
|
}
|
||||||
|
|
||||||
resetStates();
|
resetStates();
|
||||||
}
|
}
|
||||||
}, [sortedBrandLayoutInfo, selectedPatnrId]);
|
}, [sortedBrandLayoutInfo, selectedPatnrId]);
|
||||||
|
|
||||||
|
// 🆕 [251210] patnrId=21인 경우 모든 카테고리 그룹 데이터 미리 조회
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedPatnrId === 21 || selectedPatnrId === "21") {
|
||||||
|
// console.log("[FB-PANEL-CATEGORY-GROUPS] patnrId=21 detected - fetching all category group data");
|
||||||
|
// console.log("[FB-PANEL-CATEGORY-GROUPS] brandShopByShowContsList:", brandShopByShowContsList);
|
||||||
|
|
||||||
|
// 각 카테고리(contsId)별 그룹 데이터 조회
|
||||||
|
if (brandShopByShowContsList && brandShopByShowContsList.length > 0) {
|
||||||
|
brandShopByShowContsList.forEach((conts) => {
|
||||||
|
const fetchKey = `${selectedPatnrId}-${conts.contsId}`;
|
||||||
|
|
||||||
|
// useRef로 이미 조회된 contsId 추적 (무한루프 방지)
|
||||||
|
if (!fetchedCategoryGroupsRef.current.has(fetchKey)) {
|
||||||
|
// console.log("[FB-PANEL-CATEGORY-GROUPS] Fetching category group for contsId:", conts.contsId);
|
||||||
|
fetchedCategoryGroupsRef.current.add(fetchKey); // 조회 상태 기록
|
||||||
|
dispatch(getBrandShopByShow({
|
||||||
|
patnrId: selectedPatnrId,
|
||||||
|
contsId: conts.contsId
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
// console.log("[FB-PANEL-CATEGORY-GROUPS] Category group already fetched for contsId:", conts.contsId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [selectedPatnrId, brandShopByShowContsList, dispatch]); // brandShopByShowCategoryGroups 제거
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedCatCd) {
|
if (selectedCatCd) {
|
||||||
dispatch(
|
dispatch(
|
||||||
@@ -905,6 +1054,18 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
|
|||||||
}
|
}
|
||||||
}, [isLogGNBSent, isInitialFocusOccurred, selectedPatnrId, selectedPatncNm]);
|
}, [isLogGNBSent, isInitialFocusOccurred, selectedPatnrId, selectedPatncNm]);
|
||||||
|
|
||||||
|
// effect: partners log for NBCU (patnrId=21)
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedPatnrId === 21 && selectedPatncNm) {
|
||||||
|
dispatch(
|
||||||
|
sendLogPartners({
|
||||||
|
patncNm: selectedPatncNm,
|
||||||
|
patnrId: selectedPatnrId,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [selectedPatnrId, selectedPatncNm]);
|
||||||
|
|
||||||
// effect: unmount
|
// effect: unmount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
@@ -959,21 +1120,49 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{brandInfo && brandTopImgInfo && (
|
{/* 🆕 [251211] patnrId=21일 때: Banner + Category + BestSeller를 통합 Container로 관리 */}
|
||||||
<Banner
|
{(selectedPatnrId === 21 || selectedPatnrId === "21") ? (
|
||||||
brandInfo={brandInfo}
|
<NbcuIntegratedContainer className={css.nbcuIntegratedContainer}>
|
||||||
brandTopImgInfo={brandTopImgInfo}
|
{brandInfo && (brandTopImgInfo || brandTopBannerInfo) && (
|
||||||
panelPatnrId={panelInfo?.patnrId}
|
<Banner
|
||||||
/>
|
brandInfo={brandInfo}
|
||||||
)}
|
brandTopImgInfo={brandTopImgInfo}
|
||||||
|
brandTopBannerInfo={brandTopBannerInfo}
|
||||||
|
panelPatnrId={panelInfo?.patnrId}
|
||||||
|
selectedPatnrId={selectedPatnrId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{sortedBrandLayoutInfo && (
|
{sortedBrandLayoutInfo && (
|
||||||
<div
|
<div
|
||||||
className={css.orderableFlexContainer}
|
className={css.orderableFlexContainer}
|
||||||
ref={orderableFlexContainerRef}
|
ref={orderableFlexContainerRef}
|
||||||
>
|
>
|
||||||
{renderPageItem()}
|
{renderPageItem()}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</NbcuIntegratedContainer>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{brandInfo && (brandTopImgInfo || (selectedPatnrId === 21 && brandTopBannerInfo)) && (
|
||||||
|
<Banner
|
||||||
|
brandInfo={brandInfo}
|
||||||
|
brandTopImgInfo={brandTopImgInfo}
|
||||||
|
brandTopBannerInfo={brandTopBannerInfo}
|
||||||
|
panelPatnrId={panelInfo?.patnrId}
|
||||||
|
selectedPatnrId={selectedPatnrId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{sortedBrandLayoutInfo && (
|
||||||
|
<div
|
||||||
|
className={css.orderableFlexContainer}
|
||||||
|
ref={orderableFlexContainerRef}
|
||||||
|
>
|
||||||
|
{renderPageItem()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{displayTopButton && (
|
{displayTopButton && (
|
||||||
|
|||||||
@@ -49,10 +49,13 @@ const QuickMenuItem = ({
|
|||||||
}, [handleStopScrolling, itemIndex]);
|
}, [handleStopScrolling, itemIndex]);
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
|
console.log("[QuickMenuItem] Click - patnrId:", patnrId, "currentPatnrId:", selectedPatnrId ?? panelInfo?.patnrId);
|
||||||
if (patnrId === (selectedPatnrId ?? panelInfo?.patnrId)) {
|
if (patnrId === (selectedPatnrId ?? panelInfo?.patnrId)) {
|
||||||
|
console.log("[QuickMenuItem] Already selected, returning");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("[QuickMenuItem] Switching to patnrId:", patnrId);
|
||||||
const from = "menu";
|
const from = "menu";
|
||||||
const name = panel_names.FEATURED_BRANDS_PANEL;
|
const name = panel_names.FEATURED_BRANDS_PANEL;
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import { useDispatch, useSelector } from "react-redux";
|
|||||||
import { Job } from "@enact/core/util";
|
import { Job } from "@enact/core/util";
|
||||||
import Spotlight from "@enact/spotlight";
|
import Spotlight from "@enact/spotlight";
|
||||||
|
|
||||||
import { updatePanel } from "../../../../../actions/panelActions";
|
import { pushPanel, updatePanel } from "../../../../../actions/panelActions";
|
||||||
import { startVideoPlayer } from "../../../../../actions/playActions";
|
// import { startVideoPlayer } from "../../../../../actions/playActions";
|
||||||
import TItemCard, {
|
import TItemCard, {
|
||||||
removeDotAndColon,
|
removeDotAndColon,
|
||||||
} from "../../../../../components/TItemCard/TItemCard";
|
} from "../../../../../components/TItemCard/TItemCard";
|
||||||
@@ -113,27 +113,36 @@ export default function RecommendedShowsProductList({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let y =
|
// 🆕 DetailPanel로 이동 (ShopByShow 방식)
|
||||||
index < 2
|
|
||||||
? 0
|
|
||||||
: index === 2
|
|
||||||
? scaleH(208)
|
|
||||||
: scaleH(index * 248 - 248 - 40);
|
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
startVideoPlayer({
|
pushPanel({
|
||||||
modal: false,
|
name: panel_names.DETAIL_PANEL,
|
||||||
patnrId,
|
panelInfo: { patnrId, prdtId },
|
||||||
prdtId,
|
|
||||||
showId,
|
|
||||||
shptmBanrTpNm: "VOD",
|
|
||||||
thumbnail: videoThumbnail,
|
|
||||||
targetId: "spotlightId-" + prdtId,
|
|
||||||
y,
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 🔴 기존 PlayerPanel 로직 (주석처리)
|
||||||
|
// let y =
|
||||||
|
// index < 2
|
||||||
|
// ? 0
|
||||||
|
// : index === 2
|
||||||
|
// ? scaleH(208)
|
||||||
|
// : scaleH(index * 248 - 248 - 40);
|
||||||
|
//
|
||||||
|
// dispatch(
|
||||||
|
// startVideoPlayer({
|
||||||
|
// modal: false,
|
||||||
|
// patnrId,
|
||||||
|
// prdtId,
|
||||||
|
// showId,
|
||||||
|
// shptmBanrTpNm: "VOD",
|
||||||
|
// thumbnail: videoThumbnail,
|
||||||
|
// targetId: "spotlightId-" + prdtId,
|
||||||
|
// y,
|
||||||
|
// })
|
||||||
|
// );
|
||||||
},
|
},
|
||||||
[catCd, dispatch, patnrId, showId, videoThumbnail]
|
[catCd, dispatch, patnrId]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFocus = useCallback(() => {
|
const handleFocus = useCallback(() => {
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
<div style={{width: '100%', height: '100%', paddingTop: 63, paddingLeft: 60, flexDirection: 'column', justifyContent: 'flex-start', alignItems: 'flex-start', gap: 10, display: 'inline-flex'}}>
|
||||||
|
<div style={{width: 1800, height: 42, justifyContent: 'flex-start', alignItems: 'center', gap: 12, display: 'inline-flex'}}>
|
||||||
|
<div style={{width: 6, height: 36, background: '#C70850'}} />
|
||||||
|
<div style={{textAlign: 'center', color: 'black', fontSize: 42, fontFamily: 'LG Smart UI', fontWeight: '700', lineHeight: 42, wordWrap: 'break-word'}}>Chef Gadget's</div>
|
||||||
|
</div>
|
||||||
|
<div style={{alignSelf: 'stretch', paddingTop: 20, paddingBottom: 20, justifyContent: 'flex-start', alignItems: 'flex-start', gap: 19, display: 'inline-flex'}}>
|
||||||
|
<div style={{width: 665, alignSelf: 'stretch', padding: 18, background: 'white', overflow: 'hidden', borderRadius: 12, outline: '1px #DADADA solid', outlineOffset: '-1px', justifyContent: 'flex-start', alignItems: 'flex-start', display: 'flex'}}>
|
||||||
|
<img style={{flex: '1 1 0', alignSelf: 'stretch', padding: 18, background: 'linear-gradient(180deg, #EC79B8 0%, #CD4F93 100%)', border: '1px rgba(218, 218, 218, 0.54) solid'}} src="https://placehold.co/629x402" alt="Chef Gadget's featured product" />
|
||||||
|
</div>
|
||||||
|
<div style={{width: 323, padding: 18, background: 'white', borderRadius: 12, outline: '1px #DADADA solid', outlineOffset: '-1px', flexDirection: 'column', justifyContent: 'flex-start', alignItems: 'flex-start', display: 'inline-flex'}}>
|
||||||
|
<div style={{alignSelf: 'stretch', height: 287, position: 'relative'}}>
|
||||||
|
<img style={{width: 287, height: 287, left: 0, top: 0, position: 'absolute'}} src="https://placehold.co/287x287" alt="Bravo's Top Chef product" />
|
||||||
|
<div style={{width: 71, height: 72, left: 216, top: 215, position: 'absolute', background: '#EFEEF0'}} />
|
||||||
|
</div>
|
||||||
|
<div style={{alignSelf: 'stretch', height: 82, paddingTop: 8, paddingBottom: 10, justifyContent: 'flex-start', alignItems: 'flex-start', gap: 10, display: 'inline-flex'}}>
|
||||||
|
<div style={{flex: '1 1 0', color: 'black', fontSize: 24, fontFamily: 'LG Smart UI', fontWeight: '700', lineHeight: 32, wordWrap: 'break-word'}}>Bravo's Top Chef</div>
|
||||||
|
</div>
|
||||||
|
<div style={{paddingBottom: 3, justifyContent: 'center', alignItems: 'center', gap: 4, display: 'inline-flex'}}>
|
||||||
|
<div style={{color: '#C70850', fontSize: 30, fontFamily: 'LG Smart UI', fontWeight: '700', lineHeight: 30, wordWrap: 'break-word'}}>$32.98</div>
|
||||||
|
<div style={{color: '#808080', fontSize: 18, fontFamily: 'LG Smart UI', fontWeight: '400', textDecoration: 'line-through', lineHeight: 18, wordWrap: 'break-word'}}>$150.00</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{width: 323, padding: 18, background: 'white', boxShadow: '0px 0px 30px rgba(0, 0, 0, 0.45)', borderRadius: 12, outline: '2px #C70850 solid', outlineOffset: '-2px', flexDirection: 'column', justifyContent: 'flex-start', alignItems: 'flex-start', display: 'inline-flex'}}>
|
||||||
|
<img style={{alignSelf: 'stretch', height: 287}} src="https://placehold.co/287x287" alt="Top Chef Knife Tote Bag" />
|
||||||
|
<div style={{alignSelf: 'stretch', height: 82, paddingTop: 8, paddingBottom: 10, justifyContent: 'flex-start', alignItems: 'flex-start', gap: 10, display: 'inline-flex'}}>
|
||||||
|
<div style={{flex: '1 1 0', color: 'black', fontSize: 24, fontFamily: 'LG Smart UI', fontWeight: '700', lineHeight: 32, wordWrap: 'break-word'}}>Top Chef Knife Tote Bag</div>
|
||||||
|
</div>
|
||||||
|
<div style={{paddingBottom: 3, justifyContent: 'center', alignItems: 'center', gap: 4, display: 'inline-flex'}}>
|
||||||
|
<div style={{color: '#C70850', fontSize: 30, fontFamily: 'LG Smart UI', fontWeight: '700', lineHeight: 30, wordWrap: 'break-word'}}>$32.98</div>
|
||||||
|
<div style={{color: '#808080', fontSize: 18, fontFamily: 'LG Smart UI', fontWeight: '400', textDecoration: 'line-through', lineHeight: 18, wordWrap: 'break-word'}}>$150.00</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{width: 323, padding: 18, background: 'white', borderRadius: 12, outline: '1px #DADADA solid', outlineOffset: '-1px', flexDirection: 'column', justifyContent: 'flex-start', alignItems: 'flex-start', display: 'inline-flex'}}>
|
||||||
|
<div style={{alignSelf: 'stretch', height: 287, position: 'relative'}}>
|
||||||
|
<img style={{width: 287, height: 287, left: 0, top: 0, position: 'absolute'}} src="https://placehold.co/287x287" alt="Salt, Maldon Traditional product on sale 17%" />
|
||||||
|
<div style={{width: 60, height: 60, left: 219, top: 219, position: 'absolute', background: '#C70850', borderRadius: 1000, justifyContent: 'center', alignItems: 'center', display: 'inline-flex'}}>
|
||||||
|
<div style={{justifyContent: 'center', display: 'flex', flexDirection: 'column', color: 'white', fontSize: 24, fontFamily: 'LG Smart UI', fontWeight: '700', lineHeight: 26, wordWrap: 'break-word'}}>17%</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{alignSelf: 'stretch', height: 82, paddingTop: 8, paddingBottom: 10, justifyContent: 'flex-start', alignItems: 'flex-start', gap: 10, display: 'inline-flex'}}>
|
||||||
|
<div style={{flex: '1 1 0', color: 'black', fontSize: 24, fontFamily: 'LG Smart UI', fontWeight: '700', lineHeight: 32, wordWrap: 'break-word'}}>Salt, Maldon Traditional</div>
|
||||||
|
</div>
|
||||||
|
<div style={{paddingBottom: 3, justifyContent: 'center', alignItems: 'center', gap: 4, display: 'inline-flex'}}>
|
||||||
|
<div style={{color: '#C70850', fontSize: 30, fontFamily: 'LG Smart UI', fontWeight: '700', lineHeight: 30, wordWrap: 'break-word'}}>$32.98</div>
|
||||||
|
<div style={{color: '#808080', fontSize: 18, fontFamily: 'LG Smart UI', fontWeight: '400', textDecoration: 'line-through', lineHeight: 18, wordWrap: 'break-word'}}>$150.00</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{width: 323, padding: 18, background: 'white', borderRadius: 12, outline: '1px #DADADA solid', outlineOffset: '-1px', flexDirection: 'column', justifyContent: 'flex-start', alignItems: 'flex-start', display: 'inline-flex'}}>
|
||||||
|
<img style={{alignSelf: 'stretch', height: 287}} src="https://placehold.co/287x287" alt="Fish Grill Pan" />
|
||||||
|
<div style={{alignSelf: 'stretch', height: 82, paddingTop: 8, paddingBottom: 10, justifyContent: 'flex-start', alignItems: 'flex-start', gap: 10, display: 'inline-flex'}}>
|
||||||
|
<div style={{flex: '1 1 0', color: 'black', fontSize: 24, fontFamily: 'LG Smart UI', fontWeight: '700', lineHeight: 32, wordWrap: 'break-word'}}>Fish Grill Pan</div>
|
||||||
|
</div>
|
||||||
|
<div style={{paddingBottom: 3, justifyContent: 'center', alignItems: 'center', gap: 4, display: 'inline-flex'}}>
|
||||||
|
<div style={{color: '#C70850', fontSize: 30, fontFamily: 'LG Smart UI', fontWeight: '700', lineHeight: 30, wordWrap: 'break-word'}}>$32.98</div>
|
||||||
|
<div style={{color: '#808080', fontSize: 18, fontFamily: 'LG Smart UI', fontWeight: '400', textDecoration: 'line-through', lineHeight: 18, wordWrap: 'break-word'}}>$150.00</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{width: 323, padding: 18, background: 'white', borderRadius: 12, outline: '1px #DADADA solid', outlineOffset: '-1px', flexDirection: 'column', justifyContent: 'flex-start', alignItems: 'flex-start', display: 'inline-flex'}}>
|
||||||
|
<img style={{alignSelf: 'stretch', height: 287}} src="https://placehold.co/287x287" alt="Product" />
|
||||||
|
<div style={{alignSelf: 'stretch', paddingTop: 8, paddingBottom: 10, justifyContent: 'flex-start', alignItems: 'flex-start', gap: 10, display: 'inline-flex'}}>
|
||||||
|
<div style={{flex: '1 1 0', color: 'black', fontSize: 24, fontFamily: 'LG Smart UI', fontWeight: '700', lineHeight: 32, wordWrap: 'break-word'}}>Productl Nameytg Product
Name Producthlyg()...</div>
|
||||||
|
</div>
|
||||||
|
<div style={{paddingBottom: 3, justifyContent: 'center', alignItems: 'center', gap: 4, display: 'inline-flex'}}>
|
||||||
|
<div style={{color: '#C70850', fontSize: 30, fontFamily: 'LG Smart UI', fontWeight: '700', lineHeight: 30, wordWrap: 'break-word'}}>$32.98</div>
|
||||||
|
<div style={{color: '#808080', fontSize: 18, fontFamily: 'LG Smart UI', fontWeight: '400', textDecoration: 'line-through', lineHeight: 18, wordWrap: 'break-word'}}>$150.00</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{width: 323, padding: 18, background: 'white', borderRadius: 12, outline: '1px #DADADA solid', outlineOffset: '-1px', flexDirection: 'column', justifyContent: 'flex-start', alignItems: 'flex-start', display: 'inline-flex'}}>
|
||||||
|
<img style={{alignSelf: 'stretch', height: 287}} src="https://placehold.co/287x287" alt="Product" />
|
||||||
|
<div style={{alignSelf: 'stretch', paddingTop: 8, paddingBottom: 10, justifyContent: 'flex-start', alignItems: 'flex-start', gap: 10, display: 'inline-flex'}}>
|
||||||
|
<div style={{flex: '1 1 0', color: 'black', fontSize: 24, fontFamily: 'LG Smart UI', fontWeight: '700', lineHeight: 32, wordWrap: 'break-word'}}>Productl Nameytg Product
Name Producthlyg()...</div>
|
||||||
|
</div>
|
||||||
|
<div style={{paddingBottom: 3, justifyContent: 'center', alignItems: 'center', gap: 4, display: 'inline-flex'}}>
|
||||||
|
<div style={{color: '#C70850', fontSize: 30, fontFamily: 'LG Smart UI', fontWeight: '700', lineHeight: 30, wordWrap: 'break-word'}}>$32.98</div>
|
||||||
|
<div style={{color: '#808080', fontSize: 18, fontFamily: 'LG Smart UI', fontWeight: '400', textDecoration: 'line-through', lineHeight: 18, wordWrap: 'break-word'}}>$150.00</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
import React, { memo, useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
|
||||||
|
import { getBrandShopByShow } from "../../../actions/brandActions";
|
||||||
|
|
||||||
|
import { Job } from "@enact/core/util";
|
||||||
|
import Spotlight from "@enact/spotlight";
|
||||||
|
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
|
||||||
|
|
||||||
|
import useScrollTo from "../../../hooks/useScrollTo";
|
||||||
|
import SectionTitle from "../../../components/SectionTitle/SectionTitle";
|
||||||
|
import { $L } from "../../../utils/helperMethods";
|
||||||
|
import { panel_names } from "../../../utils/Config";
|
||||||
|
import css from "./ShopByShow.module.less";
|
||||||
|
import ShopByShowNav from "./ShopByShowList/ShopByShowNav/ShopByShowNav";
|
||||||
|
import ShopByShowContents from "./ShopByShowList/ShopByShowContents/ShopByShowContents";
|
||||||
|
import { updatePanel } from "../../../actions/panelActions";
|
||||||
|
|
||||||
|
const STRING_CONF = {
|
||||||
|
SHOP_BY_SHOW: "SHOP BY SHOW",
|
||||||
|
};
|
||||||
|
|
||||||
|
const Container = SpotlightContainerDecorator(
|
||||||
|
{ leaveFor: { right: "" }, enterTo: "last-focused" },
|
||||||
|
"div"
|
||||||
|
);
|
||||||
|
|
||||||
|
const ShopByShow = ({
|
||||||
|
brandShopByShowContsList,
|
||||||
|
brandShopByShowContsInfo,
|
||||||
|
handleItemFocus,
|
||||||
|
order,
|
||||||
|
shelfOrder,
|
||||||
|
spotlightId,
|
||||||
|
selectedPatnrId,
|
||||||
|
shelfTitle,
|
||||||
|
}) => {
|
||||||
|
const [firstChk, setFirstChk] = useState(0);
|
||||||
|
const [selectedContsId, setSelectedContsId] = useState(null);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { getScrollTo, scrollLeft } = useScrollTo({ skipAutoScrollTop: true });
|
||||||
|
const panelInfo = useSelector((state) => state.panels.panels[0]?.panelInfo);
|
||||||
|
const scrollLeftJob = useRef(new Job((func) => func(), 0));
|
||||||
|
|
||||||
|
const brandShopByShowClctInfos = brandShopByShowContsInfo?.brandShopByShowClctInfos || [];
|
||||||
|
|
||||||
|
// DetailPanel 복귀 시 panelInfo에 저장된 contsId로 네비게이션 선택을 복구
|
||||||
|
useEffect(() => {
|
||||||
|
if (panelInfo?.contsId && panelInfo.contsId !== selectedContsId) {
|
||||||
|
setSelectedContsId(panelInfo.contsId);
|
||||||
|
}
|
||||||
|
}, [panelInfo?.contsId, selectedContsId]);
|
||||||
|
|
||||||
|
const handleContsIdChange = useCallback(
|
||||||
|
(contsId) => {
|
||||||
|
// patnrId가 없으면 Detail 복귀 직후라 정상 호출 불가
|
||||||
|
if (!selectedPatnrId) return;
|
||||||
|
|
||||||
|
setSelectedContsId(contsId);
|
||||||
|
|
||||||
|
// 현재 선택된 contsId를 패널 상태에 저장해 복귀 시 복원
|
||||||
|
dispatch(
|
||||||
|
updatePanel({
|
||||||
|
name: panel_names.FEATURED_BRANDS_PANEL,
|
||||||
|
panelInfo: {
|
||||||
|
...(panelInfo || {}),
|
||||||
|
contsId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// 'ALL' 버튼 클릭 시 (contsId === null) 첫 번째 contents 로드
|
||||||
|
const targetContsId = contsId || brandShopByShowContsList?.[0]?.contsId;
|
||||||
|
|
||||||
|
if (targetContsId) {
|
||||||
|
dispatch(getBrandShopByShow({ patnrId: selectedPatnrId, contsId: targetContsId }));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[selectedPatnrId, brandShopByShowContsList, dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (panelInfo?.section !== "shop-by-show" || !panelInfo?.x) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollLeftJobValue = scrollLeftJob.current;
|
||||||
|
const { x } = panelInfo;
|
||||||
|
|
||||||
|
scrollLeftJobValue.start(() => scrollLeft({ x }));
|
||||||
|
|
||||||
|
return () => scrollLeftJobValue.stop();
|
||||||
|
}, [panelInfo, scrollLeft]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
scrollLeft();
|
||||||
|
}, [scrollLeft, selectedPatnrId]);
|
||||||
|
|
||||||
|
const _handleItemFocus = useCallback(() => {
|
||||||
|
if (handleItemFocus) handleItemFocus(spotlightId, shelfOrder);
|
||||||
|
|
||||||
|
const c = Spotlight.getCurrent();
|
||||||
|
if (firstChk === 0) {
|
||||||
|
if (c) {
|
||||||
|
let cAriaLabel = c.getAttribute("aria-label");
|
||||||
|
if (cAriaLabel) {
|
||||||
|
cAriaLabel = "shop-by-show, Heading1," + cAriaLabel;
|
||||||
|
c.setAttribute("aria-label", cAriaLabel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setFirstChk(1);
|
||||||
|
} else if (firstChk === 1) {
|
||||||
|
if (c) {
|
||||||
|
let cAriaLabel = c.getAttribute("aria-label");
|
||||||
|
if (cAriaLabel) {
|
||||||
|
const newcAriaLabel = cAriaLabel.replace(
|
||||||
|
"shop-by-show, Heading1,",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
c.setAttribute("aria-label", newcAriaLabel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}, [handleItemFocus, firstChk]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container
|
||||||
|
className={css.container}
|
||||||
|
data-shelf-order={order}
|
||||||
|
data-wheel-point
|
||||||
|
spotlightId={spotlightId}
|
||||||
|
>
|
||||||
|
<SectionTitle
|
||||||
|
title={$L(STRING_CONF.SHOP_BY_SHOW)}
|
||||||
|
data-title="shop-by-show"
|
||||||
|
label="shop-by-show Heading 1"
|
||||||
|
/>
|
||||||
|
<ShopByShowNav
|
||||||
|
brandShopByShowContsList={brandShopByShowContsList}
|
||||||
|
brandShopByShowContsInfo={brandShopByShowContsInfo}
|
||||||
|
handleItemFocus={_handleItemFocus}
|
||||||
|
onContsIdChange={handleContsIdChange}
|
||||||
|
selectedContsId={selectedContsId}
|
||||||
|
/>
|
||||||
|
{brandShopByShowClctInfos.map((collection, collIdx) => (
|
||||||
|
<ShopByShowContents
|
||||||
|
key={`${spotlightId}-${collIdx}`}
|
||||||
|
brandProductInfos={collection.brandProductInfos}
|
||||||
|
contentsIndex={collIdx}
|
||||||
|
handleItemFocus={_handleItemFocus}
|
||||||
|
clctNm={collection.clctNm}
|
||||||
|
clctImgUrl={collection.clctImgUrl}
|
||||||
|
contsId={brandShopByShowContsInfo?.contsId || selectedContsId}
|
||||||
|
patnrId={selectedPatnrId}
|
||||||
|
selectedPatnrId={selectedPatnrId}
|
||||||
|
shelfOrder={shelfOrder}
|
||||||
|
shelfTitle={shelfTitle}
|
||||||
|
spotlightId={spotlightId}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(ShopByShow);
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
@import "../../../style/CommonStyle.module.less";
|
||||||
|
@import "../../../style/utils.module.less";
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 58px;
|
||||||
|
|
||||||
|
> h2 {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
padding-left: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> nav {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import React, { memo, useCallback } from "react";
|
||||||
|
|
||||||
|
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
|
||||||
|
|
||||||
|
import css from "./ShopByShowContents.module.less";
|
||||||
|
import ShopByShowImageCard from "./ShopByShowImageCard/ShopByShowImageCard";
|
||||||
|
import ShopByShowProductList from "./ShopByShowProductList/ShopByShowProductList";
|
||||||
|
|
||||||
|
const Container = SpotlightContainerDecorator(
|
||||||
|
{ leaveFor: { right: "" }, enterTo: null },
|
||||||
|
"div"
|
||||||
|
);
|
||||||
|
|
||||||
|
export default memo(function ShopByShowContents({
|
||||||
|
brandProductInfos,
|
||||||
|
contentsIndex,
|
||||||
|
handleItemFocus,
|
||||||
|
clctNm,
|
||||||
|
clctImgUrl,
|
||||||
|
contsId,
|
||||||
|
patnrId,
|
||||||
|
selectedPatnrId,
|
||||||
|
shelfOrder,
|
||||||
|
shelfTitle,
|
||||||
|
spotlightId,
|
||||||
|
}) {
|
||||||
|
const handleFocus = useCallback(() => {
|
||||||
|
if (handleItemFocus) handleItemFocus();
|
||||||
|
}, [handleItemFocus]);
|
||||||
|
|
||||||
|
if (!brandProductInfos || brandProductInfos.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container
|
||||||
|
className={css.container}
|
||||||
|
data-wheel-point
|
||||||
|
spotlightId={`${spotlightId}-${contentsIndex}`}
|
||||||
|
>
|
||||||
|
<h3 data-collection-subtitle={clctNm}>{clctNm}</h3>
|
||||||
|
<div>
|
||||||
|
<ShopByShowImageCard
|
||||||
|
imageAlt={clctNm}
|
||||||
|
imageSource={clctImgUrl}
|
||||||
|
clctNm={clctNm}
|
||||||
|
spotlightDisabled
|
||||||
|
ariaLabel={clctNm}
|
||||||
|
/>
|
||||||
|
<ShopByShowProductList
|
||||||
|
brandProductInfos={brandProductInfos}
|
||||||
|
contentsIndex={contentsIndex}
|
||||||
|
handleFocus={handleFocus}
|
||||||
|
contsId={contsId}
|
||||||
|
patnrId={patnrId}
|
||||||
|
selectedPatnrId={selectedPatnrId}
|
||||||
|
clctNm={clctNm}
|
||||||
|
shelfOrder={shelfOrder}
|
||||||
|
shelfTitle={shelfTitle}
|
||||||
|
spotlightId={spotlightId}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
@import "../../../../../style/CommonStyle.module.less";
|
||||||
|
@import "../../../../../style/utils.module.less";
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding-left: 60px;
|
||||||
|
|
||||||
|
> h3 {
|
||||||
|
position: relative;
|
||||||
|
.font(@fontFamily: @arialFontBold, @fontSize: 36px);
|
||||||
|
color: @COLOR_GRAY08;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div:nth-child(2) {
|
||||||
|
.flex(@justifyCenter: flex-start);
|
||||||
|
.size(@w: 100%, @h: auto);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.listContainer {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.gridContainer {
|
||||||
|
> h3 {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import React, { memo } from "react";
|
||||||
|
|
||||||
|
import Spottable from "@enact/spotlight/Spottable";
|
||||||
|
|
||||||
|
import CustomImage from "../../../../../../components/CustomImage/CustomImage";
|
||||||
|
import css from "./ShopByShowImageCard.module.less";
|
||||||
|
|
||||||
|
const SpottableComponent = Spottable("figure");
|
||||||
|
|
||||||
|
export default memo(function ShopByShowImageCard({
|
||||||
|
imageAlt,
|
||||||
|
imageSource,
|
||||||
|
ariaLabel,
|
||||||
|
...rest
|
||||||
|
}) {
|
||||||
|
delete rest.clctNm;
|
||||||
|
return (
|
||||||
|
<SpottableComponent className={css.card} aria-label={ariaLabel} {...rest}>
|
||||||
|
<CustomImage src={imageSource} alt={imageAlt} />
|
||||||
|
</SpottableComponent>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
@import "../../../../../../style/CommonStyle.module.less";
|
||||||
|
@import "../../../../../../style/utils.module.less";
|
||||||
|
|
||||||
|
.card {
|
||||||
|
position: relative;
|
||||||
|
.size(@w: 663px, @h:438px);
|
||||||
|
padding: 18px;
|
||||||
|
background-color: @COLOR_WHITE;
|
||||||
|
border: solid 1px @COLOR_GRAY02;
|
||||||
|
border-radius: 12px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
.size(@w: 627px, @h:402px);
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
&::after {
|
||||||
|
.focused(@boxShadow: 22px, @borderRadius: 12px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
import React, { useCallback, useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
|
||||||
|
import { Job } from "@enact/core/util";
|
||||||
|
import Spotlight from "@enact/spotlight";
|
||||||
|
|
||||||
|
import { pushPanel, updatePanel } from "../../../../../../actions/panelActions";
|
||||||
|
import TItemCardNew, {
|
||||||
|
removeDotAndColon,
|
||||||
|
} from "../../../../../../components/TItemCard/TItemCard.new";
|
||||||
|
import TVirtualGridList from "../../../../../../components/TVirtualGridList/TVirtualGridList";
|
||||||
|
import useScrollTo from "../../../../../../hooks/useScrollTo";
|
||||||
|
import {
|
||||||
|
LOG_CONTEXT_NAME,
|
||||||
|
LOG_MESSAGE_ID,
|
||||||
|
panel_names,
|
||||||
|
} from "../../../../../../utils/Config";
|
||||||
|
import { getTranslate3dValueByDirection } from "../../../../../../utils/helperMethods";
|
||||||
|
import css from "./ShopByShowProductList.module.less";
|
||||||
|
|
||||||
|
export default function ShopByShowProductList({
|
||||||
|
brandProductInfos,
|
||||||
|
contentsIndex,
|
||||||
|
handleFocus,
|
||||||
|
contsId,
|
||||||
|
patnrId,
|
||||||
|
selectedPatnrId,
|
||||||
|
clctNm,
|
||||||
|
shelfOrder,
|
||||||
|
shelfTitle,
|
||||||
|
spotlightId,
|
||||||
|
}) {
|
||||||
|
const { getScrollTo, scrollLeft } = useScrollTo();
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const panelInfo = useSelector((state) => state.panels.panels[0]?.panelInfo);
|
||||||
|
|
||||||
|
const scrollLeftJob = useRef(new Job((func) => func(), 0));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
panelInfo?.section !== "shop-by-show" ||
|
||||||
|
!panelInfo?.x ||
|
||||||
|
panelInfo?.exprOrd !== contentsIndex + 1
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollLeftJobValue = scrollLeftJob.current;
|
||||||
|
const { x } = panelInfo;
|
||||||
|
|
||||||
|
scrollLeftJobValue.start(() => scrollLeft({ x }));
|
||||||
|
|
||||||
|
return () => scrollLeftJobValue.stop();
|
||||||
|
}, [panelInfo, scrollLeft, contentsIndex]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
scrollLeft();
|
||||||
|
}, [scrollLeft, selectedPatnrId]);
|
||||||
|
|
||||||
|
const handleClick = useCallback(
|
||||||
|
(prdtId, productPatnrId) => (e) => {
|
||||||
|
const effectivePatnrId = productPatnrId || patnrId;
|
||||||
|
const tItemCard = e.currentTarget;
|
||||||
|
|
||||||
|
const lastFocusedTarget = Spotlight.getCurrent();
|
||||||
|
const lastFocusedTargetId =
|
||||||
|
lastFocusedTarget?.getAttribute("data-spotlight-id");
|
||||||
|
const exprOrd = parseInt(
|
||||||
|
lastFocusedTarget?.getAttribute("data-exposure-order")
|
||||||
|
);
|
||||||
|
|
||||||
|
const xContainer = tItemCard?.parentNode?.parentNode;
|
||||||
|
|
||||||
|
if (exprOrd && lastFocusedTargetId && xContainer) {
|
||||||
|
const section = "shop-by-show";
|
||||||
|
const x = getTranslate3dValueByDirection(xContainer);
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
updatePanel({
|
||||||
|
name: panel_names.FEATURED_BRANDS_PANEL,
|
||||||
|
panelInfo: {
|
||||||
|
exprOrd,
|
||||||
|
lastFocusedTargetId,
|
||||||
|
patnrId: effectivePatnrId,
|
||||||
|
section,
|
||||||
|
contsId,
|
||||||
|
x,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
pushPanel({
|
||||||
|
name: panel_names.DETAIL_PANEL,
|
||||||
|
panelInfo: { patnrId: effectivePatnrId, prdtId, contsId },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[dispatch, patnrId, contsId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderItem = useCallback(
|
||||||
|
({ index, ...rest }) => {
|
||||||
|
const product = brandProductInfos[index];
|
||||||
|
const {
|
||||||
|
prdtImgUrl,
|
||||||
|
prdtOfferId,
|
||||||
|
patnrId: productPartnerId = "21",
|
||||||
|
prdtNm,
|
||||||
|
prdtId,
|
||||||
|
priceInfo,
|
||||||
|
patncNm,
|
||||||
|
brndNm,
|
||||||
|
lgCatNm,
|
||||||
|
euEnrgLblInfos,
|
||||||
|
} = product;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TItemCardNew
|
||||||
|
catNm={lgCatNm}
|
||||||
|
contextName={LOG_CONTEXT_NAME.FEATURED_BRANDS}
|
||||||
|
messageId={LOG_MESSAGE_ID.SHELF_CLICK}
|
||||||
|
patnerName={patncNm || "Peacock | Shop The Moment"}
|
||||||
|
brandName={brndNm}
|
||||||
|
shelfId={spotlightId}
|
||||||
|
shelfLocation={shelfOrder}
|
||||||
|
shelfTitle={shelfTitle}
|
||||||
|
imageAlt={prdtNm}
|
||||||
|
imageSource={prdtImgUrl}
|
||||||
|
onClick={handleClick(prdtId, productPartnerId)}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
offerInfo={prdtOfferId}
|
||||||
|
priceInfo={priceInfo}
|
||||||
|
productId={prdtId}
|
||||||
|
productName={prdtNm}
|
||||||
|
spotlightId={"shop-by-show-list-spotlightId-" + removeDotAndColon(prdtId)}
|
||||||
|
data-exposure-order={contentsIndex + 1}
|
||||||
|
label={index + 1 + " of " + brandProductInfos.length}
|
||||||
|
lastLabel=" go to detail, button"
|
||||||
|
euEnrgLblInfos={euEnrgLblInfos}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[
|
||||||
|
brandProductInfos,
|
||||||
|
contentsIndex,
|
||||||
|
handleClick,
|
||||||
|
handleFocus,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={css.container}>
|
||||||
|
{brandProductInfos && (
|
||||||
|
<TVirtualGridList
|
||||||
|
cbScrollTo={getScrollTo}
|
||||||
|
className={css.tVirtualGridList}
|
||||||
|
dataSize={brandProductInfos.length}
|
||||||
|
direction="horizontal"
|
||||||
|
itemHeight={438}
|
||||||
|
itemWidth={324}
|
||||||
|
spacing={18}
|
||||||
|
renderItem={renderItem}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
@import "../../../../../../style/utils.module.less";
|
||||||
|
|
||||||
|
.container {
|
||||||
|
.flex();
|
||||||
|
overflow: hidden;
|
||||||
|
.size(@w: calc(100% - 663px), @h: 482px);
|
||||||
|
padding: 0 18px;
|
||||||
|
|
||||||
|
// tVirtualGridListContainer
|
||||||
|
> div:nth-child(1) {
|
||||||
|
.size(@w: 100%, @h: inherit);
|
||||||
|
|
||||||
|
> div:nth-child(1) {
|
||||||
|
padding: 22px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div:nth-child(3) {
|
||||||
|
right: -18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import React, { memo, useCallback } from "react";
|
||||||
|
|
||||||
|
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
|
||||||
|
|
||||||
|
import TButton, { TYPES } from "../../../../../components/TButton/TButton";
|
||||||
|
import TScroller from "../../../../../components/TScroller/TScroller";
|
||||||
|
import useScrollTo from "../../../../../hooks/useScrollTo";
|
||||||
|
import { $L } from "../../../../../utils/helperMethods";
|
||||||
|
import css from "./ShopByShowNav.module.less";
|
||||||
|
|
||||||
|
const Container = SpotlightContainerDecorator(
|
||||||
|
{ leaveFor: { right: "" }, enterTo: "last-focused" },
|
||||||
|
"nav"
|
||||||
|
);
|
||||||
|
|
||||||
|
const STRING_CONF = {
|
||||||
|
ALL: "ALL",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(function ShopByShowNav({
|
||||||
|
brandShopByShowContsList,
|
||||||
|
brandShopByShowContsInfo,
|
||||||
|
handleItemFocus,
|
||||||
|
onContsIdChange,
|
||||||
|
selectedContsId,
|
||||||
|
}) {
|
||||||
|
const { getScrollTo, scrollLeft } = useScrollTo();
|
||||||
|
const activeContsId = selectedContsId ?? brandShopByShowContsInfo?.contsId;
|
||||||
|
|
||||||
|
const handleClick = useCallback(
|
||||||
|
(contsId) => () => {
|
||||||
|
onContsIdChange(contsId);
|
||||||
|
},
|
||||||
|
[onContsIdChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleFocus = useCallback(() => {
|
||||||
|
if (handleItemFocus) {
|
||||||
|
handleItemFocus();
|
||||||
|
}
|
||||||
|
}, [handleItemFocus]);
|
||||||
|
|
||||||
|
const selectedText = !activeContsId ? "Selected " : "";
|
||||||
|
const allLabeltext = selectedText + "ALL 1 of " + (brandShopByShowContsList.length + 1);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className={css.nav} id="shop-by-show-nav-id" spotlightId="shop-by-show-nav-id">
|
||||||
|
<TScroller cbScrollTo={getScrollTo} direction="horizontal" noScrollByWheel>
|
||||||
|
<ul>
|
||||||
|
{/* 'ALL' 버튼 - 디자인에 없어서 주석 처리 */}
|
||||||
|
{/* <li>
|
||||||
|
<TButton
|
||||||
|
className={!selectedContsId && css.selected}
|
||||||
|
onClick={handleClick(null)}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
selected={!selectedContsId}
|
||||||
|
type={TYPES.oneDepthCategory}
|
||||||
|
ariaLabel={allLabeltext}
|
||||||
|
>
|
||||||
|
{$L(STRING_CONF.ALL)}
|
||||||
|
</TButton>
|
||||||
|
</li> */}
|
||||||
|
{brandShopByShowContsList &&
|
||||||
|
brandShopByShowContsList.map(({ contsId, contsNm }, index) => (
|
||||||
|
<li key={"shop-by-show-conts-" + index}>
|
||||||
|
<TButton
|
||||||
|
className={activeContsId === contsId && css.selected}
|
||||||
|
onClick={handleClick(contsId)}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
selected={activeContsId === contsId}
|
||||||
|
type={TYPES.oneDepthCategory}
|
||||||
|
ariaLabel={
|
||||||
|
activeContsId === contsId
|
||||||
|
? "Selected " + contsNm + " " + (index * 1 + 1) + " of " + brandShopByShowContsList.length
|
||||||
|
: "" + contsNm + " " + (index * 1 + 1) + " of " + brandShopByShowContsList.length
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{contsNm}
|
||||||
|
</TButton>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</TScroller>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
@import "../../../../../style/CommonStyle.module.less";
|
||||||
|
@import "../../../../../style/utils.module.less";
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
position: relative;
|
||||||
|
.size(@w: 100%, @h: 162px);
|
||||||
|
margin-bottom: 30px;
|
||||||
|
padding-right: 1px;
|
||||||
|
z-index: 2;
|
||||||
|
|
||||||
|
> div:nth-child(1) {
|
||||||
|
.size(@w: inherit, @h: inherit);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
.size(@w: 100%, @h: 144px);
|
||||||
|
background-color: #ddd;
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: inherit;
|
||||||
|
padding-left: 60px;
|
||||||
|
border-bottom: 18px solid transparent;
|
||||||
|
|
||||||
|
li {
|
||||||
|
flex: none;
|
||||||
|
margin-right: 12px;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -62px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
.size(@w: 0, @h: 0);
|
||||||
|
border-top: 18px solid #ddd;
|
||||||
|
border-right: 18px solid transparent;
|
||||||
|
border-bottom: 18px solid transparent;
|
||||||
|
border-left: 18px solid transparent;
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import React, {
|
||||||
|
memo,
|
||||||
|
useCallback,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
|
||||||
|
import Spottable from '@enact/spotlight/Spottable';
|
||||||
|
|
||||||
|
import { setShowPopup } from '../../../actions/commonActions';
|
||||||
|
import CustomImage from '../../../components/CustomImage/CustomImage';
|
||||||
|
import css from './TopBannerImage.module.less';
|
||||||
|
|
||||||
|
const SpottableDiv = Spottable("div");
|
||||||
|
|
||||||
|
const TopBannerImage = memo(({ banrImgUrl, banrImgNm, banrNm, pupBanrImgUrl, pupBanrImgNm, spotlightId }) => {
|
||||||
|
// console.log("[TOP-BANNER-IMG] Rendering with URL:", banrImgUrl);
|
||||||
|
// console.log("[TOP-BANNER-IMG] spotlightId:", spotlightId);
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const [imageDimensions, setImageDimensions] = useState({ width: 0, height: 0 });
|
||||||
|
|
||||||
|
const handleClick = useCallback(() => {
|
||||||
|
// console.log("[TOP-BANNER-IMG] Clicked - Opening popup");
|
||||||
|
if (pupBanrImgUrl) {
|
||||||
|
// console.log("[TOP-BANNER-IMG] Dispatching topBannerImagePopup");
|
||||||
|
dispatch(setShowPopup({
|
||||||
|
activePopup: 'topBannerImagePopup',
|
||||||
|
data: {
|
||||||
|
pupBanrImgUrl,
|
||||||
|
pupBanrImgNm: pupBanrImgNm || banrImgNm || banrNm
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}, [dispatch, pupBanrImgUrl, pupBanrImgNm, banrImgNm, banrNm]);
|
||||||
|
|
||||||
|
const handleImageLoad = useCallback((e) => {
|
||||||
|
const img = e.target;
|
||||||
|
// console.log("[TOP-BANNER-IMG] Image loaded - dimensions:", img.naturalWidth, "x", img.naturalHeight);
|
||||||
|
|
||||||
|
// 원본 이미지 크기
|
||||||
|
const naturalWidth = img.naturalWidth;
|
||||||
|
const naturalHeight = img.naturalHeight;
|
||||||
|
|
||||||
|
setImageDimensions({
|
||||||
|
width: naturalWidth,
|
||||||
|
height: naturalHeight
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!banrImgUrl) {
|
||||||
|
// console.log("[TOP-BANNER-IMG] No image URL provided");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SpottableDiv
|
||||||
|
className={css.topBannerContainer}
|
||||||
|
spotlightId={spotlightId}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={banrImgUrl}
|
||||||
|
alt={banrImgNm || banrNm || "NBCU Top Banner"}
|
||||||
|
className={css.topBannerImage}
|
||||||
|
onLoad={handleImageLoad}
|
||||||
|
style={{
|
||||||
|
width: imageDimensions.width || 'auto',
|
||||||
|
height: imageDimensions.height || 'auto'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SpottableDiv>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default TopBannerImage;
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
@import "../../../style/CommonStyle.module.less";
|
||||||
|
@import "../../../style/utils.module.less";
|
||||||
|
|
||||||
|
.topBannerContainer {
|
||||||
|
position: absolute;
|
||||||
|
right: 60px;
|
||||||
|
top: 48px;
|
||||||
|
// padding: 15px;
|
||||||
|
background-color: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
// Spotlight 포커스 스타일 (TItemCard 방식)
|
||||||
|
&:focus {
|
||||||
|
&::after {
|
||||||
|
.focused(@boxShadow: 10px, @borderRadius: 4px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 마우스 호버 스타일
|
||||||
|
&:hover {
|
||||||
|
outline: 2px solid #fff;
|
||||||
|
outline-offset: 2px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.topBannerImage {
|
||||||
|
display: block;
|
||||||
|
pointer-events: none;
|
||||||
|
// 크기는 JavaScript에서 동적으로 설정
|
||||||
|
border-radius: 4px;
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<div style={{width: '100%', height: '100%', background: 'white', overflow: 'hidden', borderRadius: 12, flexDirection: 'column', justifyContent: 'center', alignItems: 'center', display: 'inline-flex'}}>
|
||||||
|
<div style={{alignSelf: 'stretch', height: 119, padding: 30, background: '#E7EBEF', justifyContent: 'flex-start', alignItems: 'center', gap: 15, display: 'inline-flex'}}>
|
||||||
|
<div style={{textAlign: 'center', color: 'black', fontSize: 42, fontFamily: 'LG Smart UI', fontWeight: '700', lineHeight: 42, wordWrap: 'break-word'}}>Wells Fargo Active Cash Credit Card</div>
|
||||||
|
</div>
|
||||||
|
<div style={{alignSelf: 'stretch', justifyContent: 'center', alignItems: 'center', display: 'inline-flex'}}>
|
||||||
|
<img style={{flex: '1 1 0', height: 555.51}} src="https://placehold.co/1060x556" />
|
||||||
|
</div>
|
||||||
|
<div style={{alignSelf: 'stretch', paddingLeft: 60, paddingRight: 60, paddingTop: 30, paddingBottom: 30, justifyContent: 'center', alignItems: 'center', gap: 10, display: 'inline-flex'}}>
|
||||||
|
<div style={{width: 300, height: 78, background: '#7A808D', borderRadius: 12, justifyContent: 'center', alignItems: 'center', gap: 10, display: 'flex'}}>
|
||||||
|
<div style={{textAlign: 'center', color: 'white', fontSize: 30, fontFamily: 'LG Smart UI', fontWeight: '700', lineHeight: 30, wordWrap: 'break-word'}}>CLOSE</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import React, { memo, useCallback, useState, useEffect, useRef } from "react";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { setHidePopup } from "../../../actions/commonActions";
|
||||||
|
import css from "./TopBannerPopup.module.less";
|
||||||
|
|
||||||
|
const TopBannerPopup = memo(({ title, imageUrl, imageAlt, onImageLoad }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const [imageDimensions, setImageDimensions] = useState({ width: 0, height: 0 });
|
||||||
|
const closeButtonRef = useRef(null);
|
||||||
|
|
||||||
|
const handleImageLoad = useCallback((e) => {
|
||||||
|
const img = e.target;
|
||||||
|
console.log("[TOP-BANNER-POPUP] Image loaded - dimensions:", img.naturalWidth, "x", img.naturalHeight);
|
||||||
|
|
||||||
|
const dimensions = {
|
||||||
|
width: img.naturalWidth,
|
||||||
|
height: img.naturalHeight
|
||||||
|
};
|
||||||
|
|
||||||
|
setImageDimensions(dimensions);
|
||||||
|
|
||||||
|
// 부모 컴포넌트에 크기 전달
|
||||||
|
if (onImageLoad) {
|
||||||
|
onImageLoad(dimensions);
|
||||||
|
}
|
||||||
|
}, [onImageLoad]);
|
||||||
|
|
||||||
|
const handleClose = useCallback(() => {
|
||||||
|
console.log("[TOP-BANNER-POPUP] Closing popup");
|
||||||
|
dispatch(setHidePopup());
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
// 팝업이 마운트되었을 때 Close 버튼에 포커스
|
||||||
|
useEffect(() => {
|
||||||
|
console.log("[TOP-BANNER-POPUP] Component mounted - focusing close button");
|
||||||
|
if (closeButtonRef.current) {
|
||||||
|
closeButtonRef.current.focus();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={css.container}>
|
||||||
|
{/* Title Section */}
|
||||||
|
<div className={css.titleSection}>
|
||||||
|
<div className={css.titleText}>{title}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Image Section */}
|
||||||
|
<div className={css.imageSection}>
|
||||||
|
<img
|
||||||
|
src={imageUrl}
|
||||||
|
alt={imageAlt || "Popup Banner"}
|
||||||
|
className={css.popupImage}
|
||||||
|
onLoad={handleImageLoad}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Button Section */}
|
||||||
|
<div className={css.buttonSection}>
|
||||||
|
<button
|
||||||
|
ref={closeButtonRef}
|
||||||
|
className={css.closeButton}
|
||||||
|
onClick={handleClose}
|
||||||
|
aria-label="Close popup"
|
||||||
|
>
|
||||||
|
CLOSE
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
TopBannerPopup.displayName = "TopBannerPopup";
|
||||||
|
|
||||||
|
export default TopBannerPopup;
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
@import "../../../style/CommonStyle.module.less";
|
||||||
|
@import "../../../style/utils.module.less";
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: white;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 12px;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: stretch;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 헤더: 높이 110px (상단 마진 30 + 내용 50 + 하단 마진 30)
|
||||||
|
.titleSection {
|
||||||
|
flex: 0 0 110px;
|
||||||
|
background: #E7EBEF;
|
||||||
|
padding-top: 30px;
|
||||||
|
padding-bottom: 30px;
|
||||||
|
padding-left: 30px;
|
||||||
|
padding-right: 30px;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titleText {
|
||||||
|
flex: 1;
|
||||||
|
color: black;
|
||||||
|
font-size: 42px;
|
||||||
|
font-family: 'LG Smart UI', sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 42px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 이미지: 높이 556px
|
||||||
|
.imageSection {
|
||||||
|
flex: 0 0 556px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
background: white;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popupImage {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 푸터: 높이 138px (상단 마진 30 + 버튼 78 + 하단 마진 30)
|
||||||
|
.buttonSection {
|
||||||
|
flex: 0 0 138px;
|
||||||
|
padding-left: 60px;
|
||||||
|
padding-right: 60px;
|
||||||
|
padding-top: 30px;
|
||||||
|
padding-bottom: 30px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeButton {
|
||||||
|
width: 300px;
|
||||||
|
height: 78px;
|
||||||
|
background: #7A808D;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: none;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
display: flex;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
|
||||||
|
color: white;
|
||||||
|
font-size: 30px;
|
||||||
|
font-family: 'LG Smart UI', sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 30px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: @PRIMARY_COLOR_RED;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background: @PRIMARY_COLOR_RED;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: #5a6268;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,11 @@ import Spotlight from '@enact/spotlight';
|
|||||||
import { SpotlightContainerDecorator } from '@enact/spotlight/SpotlightContainerDecorator';
|
import { SpotlightContainerDecorator } from '@enact/spotlight/SpotlightContainerDecorator';
|
||||||
import Spottable from '@enact/spotlight/Spottable';
|
import Spottable from '@enact/spotlight/Spottable';
|
||||||
|
|
||||||
import { pushPanel, updatePanel, navigateFromBestSeller } from '../../../actions/panelActions';
|
import {
|
||||||
|
navigateFromBestSeller,
|
||||||
|
pushPanel,
|
||||||
|
updatePanel,
|
||||||
|
} from '../../../actions/panelActions';
|
||||||
import { navigateToDetailFromHome } from '../../../actions/panelNavigationActions';
|
import { navigateToDetailFromHome } from '../../../actions/panelNavigationActions';
|
||||||
import SectionTitle from '../../../components/SectionTitle/SectionTitle';
|
import SectionTitle from '../../../components/SectionTitle/SectionTitle';
|
||||||
import Tag from '../../../components/TItemCard/Tag';
|
import Tag from '../../../components/TItemCard/Tag';
|
||||||
@@ -15,13 +19,20 @@ import TItemCardNew from '../../../components/TItemCard/TItemCard.new';
|
|||||||
import TScroller from '../../../components/TScroller/TScroller';
|
import TScroller from '../../../components/TScroller/TScroller';
|
||||||
import useScrollReset from '../../../hooks/useScrollReset';
|
import useScrollReset from '../../../hooks/useScrollReset';
|
||||||
import useScrollTo from '../../../hooks/useScrollTo';
|
import useScrollTo from '../../../hooks/useScrollTo';
|
||||||
import { LOG_CONTEXT_NAME, LOG_MESSAGE_ID, panel_names } from '../../../utils/Config';
|
import {
|
||||||
|
LOG_CONTEXT_NAME,
|
||||||
|
LOG_MESSAGE_ID,
|
||||||
|
panel_names,
|
||||||
|
} from '../../../utils/Config';
|
||||||
import { $L, scaleW } from '../../../utils/helperMethods';
|
import { $L, scaleW } from '../../../utils/helperMethods';
|
||||||
import { SpotlightIds } from '../../../utils/SpotlightIds';
|
import { SpotlightIds } from '../../../utils/SpotlightIds';
|
||||||
import css from './BestSeller.module.less';
|
import css from './BestSeller.module.less';
|
||||||
|
|
||||||
const SpottableComponent = Spottable('div');
|
const SpottableComponent = Spottable('div');
|
||||||
const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
|
const Container = SpotlightContainerDecorator(
|
||||||
|
{ enterTo: 'last-focused' },
|
||||||
|
'div'
|
||||||
|
);
|
||||||
|
|
||||||
const BestSeller = ({
|
const BestSeller = ({
|
||||||
order,
|
order,
|
||||||
@@ -33,15 +44,26 @@ const BestSeller = ({
|
|||||||
shelfTitle,
|
shelfTitle,
|
||||||
}) => {
|
}) => {
|
||||||
const { getScrollTo, scrollLeft } = useScrollTo();
|
const { getScrollTo, scrollLeft } = useScrollTo();
|
||||||
const { handleScrollReset, handleStopScrolling } = useScrollReset(scrollLeft, true);
|
const { handleScrollReset, handleStopScrolling } = useScrollReset(
|
||||||
|
scrollLeft,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const { cursorVisible } = useSelector((state) => state.common.appStatus);
|
const { cursorVisible } = useSelector((state) => state.common.appStatus);
|
||||||
|
|
||||||
const bestSellerDatas = useSelector((state) => state.product.bestSellerData?.bestSeller);
|
const bestSellerDatas = useSelector(
|
||||||
|
(state) => state.product.bestSellerData?.bestSeller
|
||||||
|
);
|
||||||
|
|
||||||
const bestSellerNewDatas = useSelector((state) => state.foryou?.recommendInfo?.recommendProduct);
|
const bestSellerNewDatas = useSelector(
|
||||||
|
(state) => state.foryou?.recommendInfo?.recommendProduct
|
||||||
|
);
|
||||||
|
|
||||||
|
const { userNumber } = useSelector(
|
||||||
|
(state) => state.common.appStatus.loginUserData
|
||||||
|
);
|
||||||
|
|
||||||
const [drawChk, setDrawChk] = useState(false);
|
const [drawChk, setDrawChk] = useState(false);
|
||||||
const [firstChk, setFirstChk] = useState(0);
|
const [firstChk, setFirstChk] = useState(0);
|
||||||
@@ -51,37 +73,45 @@ const BestSeller = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setBestInfos(
|
setBestInfos(
|
||||||
bestSellerNewDatas?.filter((item) => item.recommendTpCd === 'BESTSELLER') || [] // 기본값으로 빈 배열 설정
|
bestSellerNewDatas?.filter(
|
||||||
|
(item) => item.recommendTpCd === 'BESTSELLER'
|
||||||
|
) || [] // 기본값으로 빈 배열 설정
|
||||||
);
|
);
|
||||||
}, [bestSellerNewDatas]);
|
}, [bestSellerNewDatas]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!bestInfos || bestInfos.length === 0) {
|
if (userNumber) {
|
||||||
|
if (!bestInfos || bestInfos.length === 0) {
|
||||||
|
const baseData =
|
||||||
|
bestSellerDatas?.map((item) => ({
|
||||||
|
...item,
|
||||||
|
foryou: false,
|
||||||
|
})) || [];
|
||||||
|
setBestItemNewData(baseData);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const recommendedData =
|
||||||
|
bestInfos[0].productInfos?.map((item) => ({
|
||||||
|
...item,
|
||||||
|
foryou: true,
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
const recommendedPrdtIds = new Set(
|
||||||
|
recommendedData.map((item) => item.prdtId)
|
||||||
|
);
|
||||||
|
|
||||||
const baseData =
|
const baseData =
|
||||||
bestSellerDatas?.map((item) => ({
|
bestSellerDatas?.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
foryou: false,
|
foryou: recommendedPrdtIds.has(item.prdtId),
|
||||||
})) || [];
|
})) || [];
|
||||||
|
|
||||||
setBestItemNewData(baseData);
|
setBestItemNewData(baseData);
|
||||||
return;
|
} else {
|
||||||
|
setBestItemNewData(bestSellerDatas);
|
||||||
}
|
}
|
||||||
|
}, [bestSellerDatas, bestInfos, userNumber]);
|
||||||
const recommendedData =
|
|
||||||
bestInfos[0].productInfos?.map((item) => ({
|
|
||||||
...item,
|
|
||||||
foryou: true,
|
|
||||||
})) || [];
|
|
||||||
|
|
||||||
const recommendedPrdtIds = new Set(recommendedData.map((item) => item.prdtId));
|
|
||||||
|
|
||||||
const baseData =
|
|
||||||
bestSellerDatas?.map((item) => ({
|
|
||||||
...item,
|
|
||||||
foryou: recommendedPrdtIds.has(item.prdtId),
|
|
||||||
})) || [];
|
|
||||||
|
|
||||||
setBestItemNewData(baseData);
|
|
||||||
}, [bestSellerDatas, bestInfos]);
|
|
||||||
|
|
||||||
const orderStyle = useMemo(() => ({ order: order }), [order]);
|
const orderStyle = useMemo(() => ({ order: order }), [order]);
|
||||||
|
|
||||||
@@ -144,7 +174,10 @@ const BestSeller = ({
|
|||||||
if (c) {
|
if (c) {
|
||||||
let cAriaLabel = c.getAttribute('aria-label');
|
let cAriaLabel = c.getAttribute('aria-label');
|
||||||
if (cAriaLabel) {
|
if (cAriaLabel) {
|
||||||
const newcAriaLabel = cAriaLabel.replace('Best Seller, Heading 1,', '');
|
const newcAriaLabel = cAriaLabel.replace(
|
||||||
|
'Best Seller, Heading 1,',
|
||||||
|
''
|
||||||
|
);
|
||||||
c.setAttribute('aria-label', newcAriaLabel);
|
c.setAttribute('aria-label', newcAriaLabel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,10 @@ export default function HomeBanner({
|
|||||||
const bannerDataList = useSelector((state) => state.home.bannerData?.bannerInfos);
|
const bannerDataList = useSelector((state) => state.home.bannerData?.bannerInfos);
|
||||||
|
|
||||||
const popupVisible = useSelector((state) => state.common.popup.popupVisible);
|
const popupVisible = useSelector((state) => state.common.popup.popupVisible);
|
||||||
|
const panels = useSelector((state) => state.panels.panels);
|
||||||
|
const isDeepLinkEntry = useSelector(
|
||||||
|
(state) => state.home.homeInfo?.panelInfo?.isDeepLinkEntry
|
||||||
|
);
|
||||||
// 🔽 useFocusHistory - 경량화된 범용 포커스 히스토리
|
// 🔽 useFocusHistory - 경량화된 범용 포커스 히스토리
|
||||||
const focusHistory = useFocusHistory({
|
const focusHistory = useFocusHistory({
|
||||||
enableLogging: true,
|
enableLogging: true,
|
||||||
@@ -163,7 +167,11 @@ export default function HomeBanner({
|
|||||||
videoData = targetBannerData.bannerDetailInfos?.[0];
|
videoData = targetBannerData.bannerDetailInfos?.[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoData && (videoData.shptmBanrTpNm === 'LIVE' || videoData.shptmBanrTpNm === 'VOD')) {
|
// 🔽 [251221] DetailPanel이나 DeepLink PlayerPanel이 떠 있으면 배너 자동 재생 스킵
|
||||||
|
const hasDetailPanel = panels.some((p) => p.name === panel_names.DETAIL_PANEL);
|
||||||
|
const hasPlayerPanel = panels.some((p) => p.name === panel_names.PLAYER_PANEL);
|
||||||
|
|
||||||
|
if (!hasDetailPanel && !hasPlayerPanel && !isDeepLinkEntry && videoData && (videoData.shptmBanrTpNm === 'LIVE' || videoData.shptmBanrTpNm === 'VOD')) {
|
||||||
console.log('[HomeBanner] 초기 비디오 자동 재생:', defaultFocus);
|
console.log('[HomeBanner] 초기 비디오 자동 재생:', defaultFocus);
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
@@ -175,12 +183,13 @@ export default function HomeBanner({
|
|||||||
shptmBanrTpNm: videoData.shptmBanrTpNm,
|
shptmBanrTpNm: videoData.shptmBanrTpNm,
|
||||||
lgCatCd: videoData.lgCatCd,
|
lgCatCd: videoData.lgCatCd,
|
||||||
chanId: videoData.brdcChnlId,
|
chanId: videoData.brdcChnlId,
|
||||||
|
// 기본: 배너는 modal=true로 재생
|
||||||
modal: true,
|
modal: true,
|
||||||
modalContainerId: defaultFocus,
|
modalContainerId: defaultFocus,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [bannerDataList, defaultFocus, dispatch]);
|
}, [bannerDataList, defaultFocus, dispatch, panels, isDeepLinkEntry]);
|
||||||
|
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
(index, isHorizontal) => {
|
(index, isHorizontal) => {
|
||||||
|
|||||||
@@ -1,31 +1,52 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, {
|
||||||
|
useCallback,
|
||||||
// 디버그 모드 설정 - true일 때만 console.log 출력
|
useEffect,
|
||||||
const DEBUG_MODE = false;
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import {
|
||||||
|
useDispatch,
|
||||||
|
useSelector,
|
||||||
|
} from 'react-redux';
|
||||||
|
|
||||||
import Spotlight from '@enact/spotlight';
|
import Spotlight from '@enact/spotlight';
|
||||||
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
|
import SpotlightContainerDecorator
|
||||||
|
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||||
import Spottable from '@enact/spotlight/Spottable';
|
import Spottable from '@enact/spotlight/Spottable';
|
||||||
import { getContainerId } from '@enact/spotlight/src/container';
|
import { getContainerId } from '@enact/spotlight/src/container';
|
||||||
|
|
||||||
import btnPlay from '../../../../assets/images/btn/btn-play-thumb-nor.png';
|
import btnPlay from '../../../../assets/images/btn/btn-play-thumb-nor.png';
|
||||||
import defaultLogoImg from '../../../../assets/images/ic-tab-partners-default@3x.png';
|
import defaultLogoImg
|
||||||
import emptyHorImage from '../../../../assets/images/img-home-banner-empty-hor.png';
|
from '../../../../assets/images/ic-tab-partners-default@3x.png';
|
||||||
import emptyVerImage from '../../../../assets/images/img-home-banner-empty-ver.png';
|
import emptyHorImage
|
||||||
import defaultImageItem from '../../../../assets/images/img-thumb-empty-product@3x.png';
|
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 liveShow from '../../../../assets/images/tag-liveshow.png';
|
||||||
import { changeAppStatus } from '../../../actions/commonActions';
|
import { changeAppStatus } from '../../../actions/commonActions';
|
||||||
import { updateHomeInfo, setVideoTransitionLock } from '../../../actions/homeActions';
|
import {
|
||||||
import { sendLogTopContents, sendLogTotalRecommend } from '../../../actions/logActions';
|
setVideoTransitionLock,
|
||||||
import { pushPanel, navigateFromRandomUnit, SOURCE_MENUS } from '../../../actions/panelActions';
|
updateHomeInfo,
|
||||||
|
} from '../../../actions/homeActions';
|
||||||
|
import {
|
||||||
|
sendLogTopContents,
|
||||||
|
sendLogTotalRecommend,
|
||||||
|
} from '../../../actions/logActions';
|
||||||
|
import {
|
||||||
|
navigateFromRandomUnit,
|
||||||
|
pushPanel,
|
||||||
|
SOURCE_MENUS,
|
||||||
|
} from '../../../actions/panelActions';
|
||||||
import {
|
import {
|
||||||
finishVideoPreview,
|
finishVideoPreview,
|
||||||
|
hideModalVideo,
|
||||||
startVideoPlayer,
|
startVideoPlayer,
|
||||||
startVideoPlayerNew,
|
startVideoPlayerNew,
|
||||||
hideModalVideo,
|
|
||||||
} from '../../../actions/playActions';
|
} from '../../../actions/playActions';
|
||||||
import CustomImage from '../../../components/CustomImage/CustomImage';
|
import CustomImage from '../../../components/CustomImage/CustomImage';
|
||||||
import usePriceInfo from '../../../hooks/usePriceInfo';
|
import usePriceInfo from '../../../hooks/usePriceInfo';
|
||||||
@@ -36,11 +57,19 @@ import {
|
|||||||
LOG_TP_NO,
|
LOG_TP_NO,
|
||||||
panel_names,
|
panel_names,
|
||||||
} from '../../../utils/Config';
|
} from '../../../utils/Config';
|
||||||
import { selectIsPlaying } from '../../../utils/playerState/playerStateSelectors';
|
import {
|
||||||
import { $L, formatGMTString } from '../../../utils/helperMethods';
|
$L,
|
||||||
|
formatGMTString,
|
||||||
|
} from '../../../utils/helperMethods';
|
||||||
|
import {
|
||||||
|
selectIsPlaying,
|
||||||
|
} from '../../../utils/playerState/playerStateSelectors';
|
||||||
import { TEMPLATE_CODE_CONF } from '../HomePanel';
|
import { TEMPLATE_CODE_CONF } from '../HomePanel';
|
||||||
import css from './RandomUnit.module.less';
|
import css from './RandomUnit.module.less';
|
||||||
|
|
||||||
|
// 디버그 모드 설정 - true일 때만 console.log 출력
|
||||||
|
const DEBUG_MODE = false;
|
||||||
|
|
||||||
const SpottableComponent = Spottable('div');
|
const SpottableComponent = Spottable('div');
|
||||||
|
|
||||||
const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
|
const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
|
||||||
@@ -66,6 +95,7 @@ export default function RandomUnit({
|
|||||||
|
|
||||||
const homeCategory = useSelector((state) => state.home.menuData?.data?.homeCategory);
|
const homeCategory = useSelector((state) => state.home.menuData?.data?.homeCategory);
|
||||||
const countryCode = useSelector((state) => state.common.httpHeader.cntry_cd);
|
const countryCode = useSelector((state) => state.common.httpHeader.cntry_cd);
|
||||||
|
const foryouInfos = useSelector((state) => state.foryou.recommendInfo.recommendProduct);
|
||||||
|
|
||||||
// 현재 재생 중인 비디오 배너 ID 가져오기
|
// 현재 재생 중인 비디오 배너 ID 가져오기
|
||||||
const currentVideoBannerId = useSelector((state) => {
|
const currentVideoBannerId = useSelector((state) => {
|
||||||
@@ -489,6 +519,14 @@ export default function RandomUnit({
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'DSP00510':
|
||||||
|
linkInfo = {
|
||||||
|
name: panel_names.JUST_FOR_YOU_TEST_PANEL,
|
||||||
|
panelInfo: {
|
||||||
|
},
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
linkInfo = {
|
linkInfo = {
|
||||||
@@ -643,6 +681,29 @@ export default function RandomUnit({
|
|||||||
// 투데이즈 딜 가격 정보
|
// 투데이즈 딜 가격 정보
|
||||||
const { originalPrice, discountedPrice, discountRate, offerInfo } =
|
const { originalPrice, discountedPrice, discountRate, offerInfo } =
|
||||||
usePriceInfo(priceInfos) || {};
|
usePriceInfo(priceInfos) || {};
|
||||||
|
|
||||||
|
// Just For You 데이터에서 첫 번째 상품 추출
|
||||||
|
const justForYouProduct = useMemo(() => {
|
||||||
|
if (foryouInfos && foryouInfos.length > 0) {
|
||||||
|
const justForYouShelf = foryouInfos.find(
|
||||||
|
(shelf) => shelf.recommendTpCd === 'JUSTFORYOU'
|
||||||
|
);
|
||||||
|
if (justForYouShelf && justForYouShelf.productInfos && justForYouShelf.productInfos.length > 0) {
|
||||||
|
return justForYouShelf.productInfos[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [foryouInfos]);
|
||||||
|
|
||||||
|
// Just For You 상품의 가격 정보
|
||||||
|
const justForYouPriceInfo = usePriceInfo(
|
||||||
|
justForYouProduct && justForYouProduct.priceInfo ? justForYouProduct.priceInfo : ''
|
||||||
|
) || {
|
||||||
|
originalPrice: '',
|
||||||
|
discountedPrice: '',
|
||||||
|
discountRate: null,
|
||||||
|
offerInfo: '',
|
||||||
|
};
|
||||||
|
|
||||||
// 로그
|
// 로그
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -999,7 +1060,72 @@ export default function RandomUnit({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</SpottableComponent>
|
</SpottableComponent>
|
||||||
) : null}
|
)
|
||||||
|
: randomData?.shptmBanrTpNm == "Just For You" ? (
|
||||||
|
{/* <SpottableComponent
|
||||||
|
className={classNames(
|
||||||
|
css.itemBox,
|
||||||
|
css.justforyou,
|
||||||
|
countryCode === 'RU' ? css.ru : '',
|
||||||
|
countryCode === 'DE' ? css.de : '',
|
||||||
|
isHorizontal && css.isHorizontal
|
||||||
|
)}
|
||||||
|
onClick={todayDealClick}
|
||||||
|
spotlightId={spotlightId}
|
||||||
|
aria-label={justForYouProduct?.prdtNm ? justForYouProduct?.prdtNm : randomData.tmnlImgNm}
|
||||||
|
>
|
||||||
|
<div className={css.productInfo}>
|
||||||
|
<div className={css.justforyouTitle}>{$L("Just For You")}</div>
|
||||||
|
<div
|
||||||
|
className={css.textBox}
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `${justForYouProduct?.prdtNm || randomData.prdtNm}`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className={css.accBox}>
|
||||||
|
|
||||||
|
{justForYouProduct ? (
|
||||||
|
<>
|
||||||
|
{parseFloat(justForYouPriceInfo.originalPrice?.replace('$', '') || '0') === 0
|
||||||
|
? justForYouProduct.offerInfo
|
||||||
|
: justForYouPriceInfo.discountRate
|
||||||
|
? justForYouPriceInfo.discountedPrice
|
||||||
|
: justForYouPriceInfo.originalPrice}
|
||||||
|
{justForYouPriceInfo.discountRate && !isHorizontal && (
|
||||||
|
<span className={css.saleAccBox}>{justForYouPriceInfo.originalPrice}</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{parseFloat(originalPrice?.replace('$', '') || '0') === 0
|
||||||
|
? randomData?.offerInfo
|
||||||
|
: discountRate
|
||||||
|
? discountedPrice
|
||||||
|
: originalPrice}
|
||||||
|
{discountRate && !isHorizontal && (
|
||||||
|
<span className={css.saleAccBox}>{originalPrice}</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{isHorizontal && justForYouProduct && parseFloat(justForYouPriceInfo.originalPrice?.replace('$', '') || '0') !== 0 && (
|
||||||
|
<span className={css.saleAccBox}>{justForYouPriceInfo.originalPrice}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={css.itemImgBox}>
|
||||||
|
<CustomImage
|
||||||
|
delay={0}
|
||||||
|
src={justForYouProduct?.imgUrl || randomData.tmnlImgPath}
|
||||||
|
animationSpeed="fast"
|
||||||
|
fallbackSrc={defaultImageItem}
|
||||||
|
ariaLabel={justForYouProduct?.prdtNm || randomData.tmnlImgNm}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</SpottableComponent> */}
|
||||||
|
)
|
||||||
|
: null}
|
||||||
</Container>
|
</Container>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user