109 Commits

Author SHA1 Message Date
junghoon86.park
0223499e12 [영상]
- 라이브 채널 next부분 관련해서 버튼부분의 배경 색상 제거
2025-12-17 13:46:23 +09:00
3fd3b66cb3 [251217] fix: PlayerPanel activity check 추가
🕐 커밋 시간: 2025. 12. 17. 13:43:33

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +133줄
  • 삭제: -6줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx

🔧 주요 변경 내용:
  • 중간 규모 기능 개선
2025-12-17 13:43:33 +09:00
be9b1faeec [251217] fix: 비디오배너 클릭 방어로직추가
🕐 커밋 시간: 2025. 12. 17. 12:11:04

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +10줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/HomePanel/HomeBanner/RandomUnit.jsx
2025-12-17 12:11:04 +09:00
07a042cca6 [251217] fix: PlayerPanel 배너동영상 위치 검증추가
🕐 커밋 시간: 2025. 12. 17. 12:03:44

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +70줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx

🔧 주요 변경 내용:
  • 소규모 기능 개선
2025-12-17 12:03:44 +09:00
junghoon86.park
d93960f40a [영상]
- 프로그레스바 크기변경
 - 타임 노출부분 변경
 - cc 버튼 위치변경
2025-12-17 10:38:36 +09:00
4dfa15b4c0 [251217] fix: TabContainer.v2.jsx ShopNowButton 포커스 10ms
🕐 커밋 시간: 2025. 12. 17. 09:13:23

📊 변경 통계:
  • 총 파일: 2개
  • 추가: +5줄
  • 삭제: -1줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductDescription/ProductDescription.jsx
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx
2025-12-17 09:13:23 +09:00
d83e9d38f0 [251217] fix: ProductAllSection PRODUCT DETAIL버튼 조건부 렌더링
🕐 커밋 시간: 2025. 12. 17. 08:58:40

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +21줄
  • 삭제: -11줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx

🔧 주요 변경 내용:
  • 소규모 기능 개선
  • 코드 정리 및 최적화
2025-12-17 08:58:40 +09:00
8589cde061 [251216] fix: FeaturedBrandsPanel TItemCard to DetailPanel
🕐 커밋 시간: 2025. 12. 16. 17:29:43

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +28줄
  • 삭제: -19줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/RecommendedShows/RecommendedShowsContents/RecommendedShowsProductList/RecommendedShowsProductList.jsx

🔧 주요 변경 내용:
  • 소규모 기능 개선
  • 코드 정리 및 최적화
2025-12-16 17:29:44 +09:00
92964a5063 [251216] fix: DetailPanel skeleton비활성화
🕐 커밋 시간: 2025. 12. 16. 17:11:50

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +2줄
  • 삭제: -2줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx

🔧 주요 변경 내용:
  • 코드 정리 및 최적화
2025-12-16 17:11:50 +09:00
dba79789a8 [251216] fix: DeepLink 처리 isDeepLinkEntry
🕐 커밋 시간: 2025. 12. 16. 17:00:52

📊 변경 통계:
  • 총 파일: 4개
  • 추가: +46줄
  • 삭제: -5줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/App/deepLinkHandler.js
  ~ com.twin.app.shoptime/src/views/HomePanel/HomeBanner/HomeBanner.jsx
  ~ com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
  • 소규모 기능 개선
2025-12-16 17:00:52 +09:00
junghoon86.park
8a882c28ca [영상 노출에 따른 부분 수정]
- 영상 상하 블랙라인 관련 하여 스타일 수정
 - discimir부분 노출관련하여 처리.
2025-12-16 16:27:49 +09:00
61f67708a9 [251216] fix: 로그정리,PlayerPanel ShopNowContents
🕐 커밋 시간: 2025. 12. 16. 16:07:17

📊 변경 통계:
  • 총 파일: 11개
  • 추가: +94줄
  • 삭제: -90줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/mediaActions.js
  ~ com.twin.app.shoptime/src/actions/panelActions.js
  ~ com.twin.app.shoptime/src/actions/playActions.js
  ~ com.twin.app.shoptime/src/middleware/panelHistoryMiddleware.js
  ~ com.twin.app.shoptime/src/reducers/panelReducer.js
  ~ com.twin.app.shoptime/src/utils/lodashFpEx.js
  ~ com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx
  ~ com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.jsx
  ~ com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.module.less
  ~ com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
  • 공통 유틸리티 함수 최적화
  • 소규모 기능 개선
  • 코드 정리 및 최적화
  • 모듈 구조 개선
2025-12-16 16:07:19 +09:00
c9b2e5daf5 [251216] fix: TrendingNowPanel DetailPanel MediaPanel popPanel fix
🕐 커밋 시간: 2025. 12. 16. 15:21:13

📊 변경 통계:
  • 총 파일: 6개
  • 추가: +62줄
  • 삭제: -14줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/mediaActions.js
  ~ com.twin.app.shoptime/src/actions/panelActions.js
  ~ com.twin.app.shoptime/src/reducers/panelReducer.js
  ~ com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx
  ~ com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.jsx
  ~ com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
  • 소규모 기능 개선
  • 모듈 구조 개선
2025-12-16 15:21:13 +09:00
4f4887ebdb [251216] fix: TrendingNowPanel PlayerPanel DetailPanel Bg Video Pause
🕐 커밋 시간: 2025. 12. 16. 14:39:08

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +3줄
  • 삭제: -3줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx

🔧 주요 변경 내용:
  • 코드 정리 및 최적화
2025-12-16 14:39:08 +09:00
9d8cafc0a9 [251216] fix: TrendingNowPanel PlayerPanel Bg
🕐 커밋 시간: 2025. 12. 16. 14:29:22

📊 변경 통계:
  • 총 파일: 2개
  • 추가: +14줄
  • 삭제: -3줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx
  ~ com.twin.app.shoptime/src/views/TrendingNowPanel/TrendingNowPanel.jsx
2025-12-16 14:29:22 +09:00
junghoon86.park
929a9020a1 [youmayalsolike]
- updatePanel이 정상적으로 먹지 않는 문제가있어 pushpanel로 변경처리.
2025-12-16 14:20:25 +09:00
junghoon86.park
83905a092d [상품상세] description 클릭시 팝업 노출 처리
- 너무 긴경우 포커스가 넘어가는문제가 있어 클릭시에는 팝업이 노출되어 전체 영역을 확인할수있도록 수정.
2025-12-16 13:55:51 +09:00
e4a64644dd [251216] merge: resolve develop_si branch conflicts
Merge remote changes from gitlab/develop_si.

🤖 Generated with Claude Code

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2025-12-16 12:41:38 +09:00
55af96bd00 [251216] fix: Panel logs update - 1
🕐 커밋 시간: 2025. 12. 16. 12:39:11

📊 변경 통계:
  • 총 파일: 3개
  • 추가: +20줄
  • 삭제: -8줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/middleware/panelHistoryMiddleware.js
  ~ com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
2025-12-16 12:39:12 +09:00
junghoon86.park
8325070138 [Tscrolldetail] 상세에서 오른쪽 스크롤 포커스 가지않도록 변경.
- focusableScrollbar를 false 로 강제로 줌.
2025-12-16 12:22:30 +09:00
junghoon86.park
bbb9e64120 [TheaderCustom]
- theme 상품 상세에서 theme명 노출부분 변경 처리.
2025-12-16 12:20:23 +09:00
99ea3e6595 [251216] fix: TrendingNowPanel 3-layer add
🕐 커밋 시간: 2025. 12. 16. 10:26:10

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +13줄
  • 삭제: -2줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/MainView/MainView.jsx
2025-12-16 10:26:10 +09:00
3dc4699479 [251216] fix: panelHistoryMiddleware log
🕐 커밋 시간: 2025. 12. 16. 10:15:57

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +7줄
  • 삭제: -4줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/middleware/panelHistoryMiddleware.js

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
  • 코드 정리 및 최적화
2025-12-16 10:15:57 +09:00
junghoon86.park
8a3bcc1f9c [테마 아이템카드] 목업 데이터 제거
- 목업 데이터 제거.
2025-12-16 09:31:34 +09:00
junghoon86.park
486fb5efd5 [상품 상세] panelInfo?.patnrId 내려오는 부분 수정
- string 으로 내려와서 number로 넣어둔부분은 제거.
2025-12-15 17:01:27 +09:00
junghoon86.park
802484debd [검색] 음성검색관련 마이크 노출 주석처리
- 음성 검색우선 제외로 인하여 마이크 버튼주석처리.
2025-12-15 16:23:54 +09:00
junghoon86.park
c540378cb5 [foryou] 로그인, 비로그인시 노출 차이 수정
- 포유 아이콘 관련 처리부분 비로그인시 노출안되도록 처리.
 - 픽포유는 홈패널에서 노출로 처리.
2025-12-15 16:15:51 +09:00
junghoon86.park
cb3a4e9bc7 [live영상] now playing노출 관련 수정
- currentVideoVisible 추가
2025-12-15 15:22:09 +09:00
junghoon86.park
3ce4398e67 [상품상세] 폰트수정
- 폰트수정
2025-12-15 14:31:13 +09:00
junghoon86.park
78153bae0c [상품상세] 폰트수정
- 글자크기가 디자인과 맞지 않아 이부분 수정.
2025-12-15 14:29:54 +09:00
2c681bab68 [251215] merge: Soft merge gitlab/develop_si
다음 변경사항들을 merge:
- [영상내 shopnow 수정]
- [라이브 채널 넥스트 버튼 수정]

충돌 없이 성공적으로 merge 완료
2025-12-15 13:16:08 +09:00
ab2dd7385b fix: DetailPanel above PlayerPanel , FeaturedBrandsPanel-2 2025-12-15 13:10:55 +09:00
ac5414a5fe fix: DetailPanel above PlayerPanel , FeaturedBrandsPanel 2025-12-15 12:38:20 +09:00
junghoon86.park
f46090863f [영상내 shopnow 수정]
- shopnowIcon이미지 변경. qvc로 고정되어있었던부분 영상에 맞게 노출되도록 수정.
 - qvc영상일때 이미지는 border-radius먹지않도록 변경.
2025-12-15 11:02:52 +09:00
junghoon86.park
1e9c9bee40 [라이브 채널 넥스트 버튼 수정]
- qvc가 짤려보이는문제 관련하여 수정.
2025-12-15 10:12:47 +09:00
junghoon86.park
f514e2468c [nbcu 상품에서의 모바일 샌드팝업 수정]
- 기존 처리 했던 useselector제거하고 patnrId 로 변경처리.
 - nbcu상품에서는 상품 id가 노출되지않고 patncNm가 노출되도록 수정.
2025-12-15 10:00:27 +09:00
junghoon86.park
1305158113 [영상노출 관련 스타일 수정]
- 영상재생시에 하단에 노출되는 livechannel부분에 qvc는 border-radius 0으로 작업.
2025-12-15 09:52:31 +09:00
junghoon86.park
e97172fad5 [모바일 샌드 팝업] qvc일때 원형으로 짤리는부분 수정
- qvc에서는 border-radius제거.
 - theme item일때 에러나는부분때문에 옵셔널체이닝 처리.
2025-12-15 09:43:22 +09:00
42f58bf10c [251215] fix: FeaturedBrandsPanel Log추가
🕐 커밋 시간: 2025. 12. 15. 09:38:59

📊 변경 통계:
  • 총 파일: 3개
  • 추가: +30줄

📁 추가된 파일:
  + com.twin.app.shoptime/src/api/logServerClient.js

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/api/TLogEvent.js
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBrandsPanel.jsx

🔧 주요 변경 내용:
  • API 서비스 레이어 개선
  • 소규모 기능 개선
2025-12-15 09:39:00 +09:00
e424ab761c [251212] fix: FeaturedBrandsPanel Debugging - 2
🕐 커밋 시간: 2025. 12. 12. 17:17:53

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +6줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/MainView/MainView.jsx
2025-12-12 17:17:54 +09:00
f62ccef420 [251212] fix: FeaturedBrandsPanel Debugging - 1
🕐 커밋 시간: 2025. 12. 12. 17:12:43

📊 변경 통계:
  • 총 파일: 5개
  • 추가: +145줄
  • 삭제: -13줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/panelActions.js
  ~ com.twin.app.shoptime/src/actions/playActions.js
  ~ com.twin.app.shoptime/src/middleware/panelHistoryMiddleware.js
  ~ com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.jsx

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
  • 중간 규모 기능 개선
2025-12-12 17:12:43 +09:00
1ee664e8c1 [251212] fix: FeaturedBrandsPanel관련 로그 정리
🕐 커밋 시간: 2025. 12. 12. 14:41:05

📊 변경 통계:
  • 총 파일: 9개
  • 추가: +118줄
  • 삭제: -87줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/brandActions.js
  ~ com.twin.app.shoptime/src/views/DetailPanel/DetailPanel.jsx
  ~ com.twin.app.shoptime/src/views/DetailPanel/components/DetailPanelBackground/DetailPanelBackground.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/Banner/Banner.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBestSeller/FeaturedBestSeller.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBestSeller/FeaturedBestSellerList/FeaturedBestSellerList.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBrandsPanel.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/TopBannerImage/TopBannerImage.jsx
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
  • UI 컴포넌트 아키텍처 개선
  • 중간 규모 기능 개선
  • 코드 정리 및 최적화
  • 모듈 구조 개선
2025-12-12 14:41:06 +09:00
junghoon86.park
16a09b2e2b [워닝제거] cartReducer 공백제거.
- 공백만 제거.
2025-12-12 13:15:52 +09:00
junghoon86.park
4fcd87da7d [워닝 제거] tqrcode
- 선언후 사용하지않는 useMemo, useState제거
 - tqrcode에서는 width, height 의존성배열 추가.
2025-12-12 13:12:33 +09:00
junghoon86.park
9c2ecbaa57 [워닝 제거]
- 공백 워닝, == 와 ===의 워닝처리건.
2025-12-12 13:08:09 +09:00
junghoon86.park
ad8fc598b4 [쿠폰 다운로드] 중복다운로드 가능하면 download complete 뜨지않도록
- 현재 쿠폰이 보이지않아 확인은 못함.(확인필요)
2025-12-12 13:05:37 +09:00
junghoon86.park
ccc91ec662 [워닝제거] 큰범위 내에서 사용하지않는것, 공백 부분만 처리
- optionalConfirm, optionalTermsConfirm, optionalTermsConfirmBottom 3파일 우선처리.
2025-12-12 11:01:33 +09:00
junghoon86.park
b3b1151a1d [저스트포유] 랜덤 배너에도 추가
- 현재 데이터가 롤링유닛에만 내려와서 확인이 불가능한 상황이라 우선 실제 노출소스는 주석처리.
 - 추후 확인후 주석해제하여 사용하겠습니다.
2025-12-12 10:39:29 +09:00
junghoon86.park
4a70f321ed [상품 상세] 금익부분이 너무 클경우 아래로 떨어지지않고 이상하게 노출되는부분 수정. 2025-12-11 18:02:48 +09:00
ddd5d5c7ba [251211] fix: PlayerPanel autoClose activityCheck
🕐 커밋 시간: 2025. 12. 11. 17:59:30

📊 변경 통계:
  • 총 파일: 2개
  • 추가: +1줄
  • 삭제: -6줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx

🔧 주요 변경 내용:
  • UI 컴포넌트 아키텍처 개선
  • 코드 정리 및 최적화

Performance: 코드 최적화로 성능 개선 기대
2025-12-11 17:59:31 +09:00
9681eb42e1 [251211] fix: IntroPanel.new.jsx 포커스 조정-3
🕐 커밋 시간: 2025. 12. 11. 17:20:46

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +11줄
  • 삭제: -4줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/IntroPanel/IntroPanel.new.jsx
2025-12-11 17:20:46 +09:00
a3fe60ca70 [251211] fix: IntroPanel.new.jsx 포커스 조정-2
🕐 커밋 시간: 2025. 12. 11. 17:08:47

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +8줄
  • 삭제: -13줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/IntroPanel/IntroPanel.new.jsx

🔧 주요 변경 내용:
  • 코드 정리 및 최적화

Performance: 코드 최적화로 성능 개선 기대
2025-12-11 17:08:47 +09:00
junghoon86.park
0593f54d6e [tabitemsub]
- 서브 카테고리 들어가면 icon이 이상하게 노출되는부분 관련하여 확인해보니 IconComponent가 돌아가고있음. 이부분 체크를 위해서 우선 주석처리.
2025-12-11 16:59:31 +09:00
d903610709 [251211] fix: IntroPanel.new.jsx 포커스 조정
🕐 커밋 시간: 2025. 12. 11. 16:47:19

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +3줄
  • 삭제: -3줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/IntroPanel/IntroPanel.new.jsx

🔧 주요 변경 내용:
  • 코드 정리 및 최적화
2025-12-11 16:47:19 +09:00
junghoon86.park
bc6119f902 [nbcu] 타이틀 배너 스타일 수정
- 타이틀 text-align:center제거
2025-12-11 15:37:12 +09:00
38fad5ffe2 [251211] fix: FeaturedBrandsPanel TopImageBanner Focus
🕐 커밋 시간: 2025. 12. 11. 15:21:58

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +55줄
  • 삭제: -17줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBrandsPanel.jsx

🔧 주요 변경 내용:
  • 소규모 기능 개선
2025-12-11 15:21:59 +09:00
junghoon86.park
c16724f245 [nbcu] qr 스타일 수정
- nbcu전용 qr에 대해서 스타일 수정.
2025-12-11 15:12:25 +09:00
junghoon86.park
013055692f [nbcu 관련수정]
- youmayalsolike 파트너사 21일때는 노출하지않음.
 - see more product 쉘프에 스폰서 이미지 추가
 - see more product 쉘프에 쉘프명 수정.
2025-12-11 15:06:09 +09:00
98df524ecf [251211] fix: NBCU Detailpanel QRCodeNew->QRCode
🕐 커밋 시간: 2025. 12. 11. 14:54:31

📊 변경 통계:
  • 총 파일: 2개
  • 추가: +15줄
  • 삭제: -3줄

📁 추가된 파일:
  + com.twin.app.shoptime/src/views/DetailPanel/ProductInfoSection/QRCode/QRCodePatnr21.jsx

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductInfoSection/QRCode/QRCode.jsx
2025-12-11 14:54:31 +09:00
3e300749a0 [251211] fix: ShopByShow PriceInfo add
🕐 커밋 시간: 2025. 12. 11. 14:36:53

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +2줄
  • 삭제: -2줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowContents/ShopByShowProductList/ShopByShowProductList.jsx

🔧 주요 변경 내용:
  • 코드 정리 및 최적화
2025-12-11 14:36:53 +09:00
f5621b0c55 [251211] feat: FeaturedBrandsPanel , TopBannerImage Modal
🕐 커밋 시간: 2025. 12. 11. 14:04:14

📊 변경 통계:
  • 총 파일: 3개
  • 추가: +63줄
  • 삭제: -25줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/components/GlobalPopup/GlobalPopup.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/TopBannerImage/TopBannerPopup.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/TopBannerImage/TopBannerPopup.module.less

🔧 주요 변경 내용:
  • UI 컴포넌트 아키텍처 개선
  • 소규모 기능 개선
2025-12-11 14:04:15 +09:00
junghoon86.park
7971bbc1db [nbcu] 상단 배너 포커스 관련 수정
- 스타일 수정
2025-12-11 14:01:56 +09:00
d640bb74ef [251211] feat: FeaturedBrandsPanel , TopBannerImage Focus
🕐 커밋 시간: 2025. 12. 11. 13:46:11

📊 변경 통계:
  • 총 파일: 6개
  • 추가: +48줄
  • 삭제: -16줄

📁 추가된 파일:
  + com.twin.app.shoptime/src/components/TQRCode/TQRCodeNew.jsx
  + com.twin.app.shoptime/src/components/TQRCode/TQRCodeNew.module.less

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductInfoSection/QRCode/QRCode.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/TopBannerImage/TopBannerImage.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/TopBannerImage/TopBannerImage.module.less

🔧 주요 변경 내용:
  • UI 컴포넌트 아키텍처 개선
  • 소규모 기능 개선
  • 모듈 구조 개선
2025-12-11 13:46:12 +09:00
junghoon86.park
cf27ed3846 [상품 상세] 디테일 부분 포커스 및 스타일 수정
- nbcu seemoreproduct 버튼 포커스및 tiemcard내에 보더 제거
2025-12-11 12:53:05 +09:00
a85710421c [251211] feat: FeaturedBrandsPanel , See More Products - 3
🕐 커밋 시간: 2025. 12. 11. 12:29:51

📊 변경 통계:
  • 총 파일: 3개
  • 추가: +25줄
  • 삭제: -5줄

📁 추가된 파일:
  + com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/SeeMoreProducts/SeeMoreProducts.jsx
  + com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/SeeMoreProducts/SeeMoreProducts.module.less

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx

🔧 주요 변경 내용:
  • 소규모 기능 개선
2025-12-11 12:29:51 +09:00
05f5bf4d33 [251211] feat: FeaturedBrandsPanel , See More Products - 2
🕐 커밋 시간: 2025. 12. 11. 09:07:19

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +93줄
  • 삭제: -4줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx

🔧 주요 변경 내용:
  • 소규모 기능 개선
2025-12-11 09:07:19 +09:00
8057021d1c [251210] feat: FeaturedBrandsPanel , See More Products - 1
🕐 커밋 시간: 2025. 12. 10. 18:29:18

📊 변경 통계:
  • 총 파일: 5개
  • 추가: +133줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/actionTypes.js
  ~ com.twin.app.shoptime/src/actions/brandActions.js
  ~ com.twin.app.shoptime/src/reducers/brandReducer.js
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBrandsPanel.jsx

🔧 주요 변경 내용:
  • 타입 시스템 안정성 강화
  • 핵심 비즈니스 로직 개선
  • 중간 규모 기능 개선
2025-12-10 18:29:18 +09:00
cbdf1b89f8 [251210] fix: DetailPanel,ProductAllSection API최적화-2
🕐 커밋 시간: 2025. 12. 10. 17:30:18

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +85줄
  • 삭제: -52줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx

🔧 주요 변경 내용:
  • 소규모 기능 개선
  • 코드 정리 및 최적화
2025-12-10 17:30:18 +09:00
junghoon86.park
4fe3c94b1e [스폰서 이미지 위치 수정]
- 스폰서 이미지 위치 수정
2025-12-10 17:19:07 +09:00
junghoon86.park
07e5d5c6de [스폰서 이미지 추가]
- 상품 상세 첫번째 이미지에 스폰서 이미지 추가.
2025-12-10 17:16:39 +09:00
bc8317483f [251210] fix: DetailPanel,ProductAllSection API최적화-1
🕐 커밋 시간: 2025. 12. 10. 16:49:51

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +49줄
  • 삭제: -43줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx

🔧 주요 변경 내용:
  • 소규모 기능 개선
  • 코드 정리 및 최적화
2025-12-10 16:49:51 +09:00
a2b29d219a [251210] feat: FeaturedBrandsPanel-TopBannerImage Popup
🕐 커밋 시간: 2025. 12. 10. 16:33:37

📊 변경 통계:
  • 총 파일: 6개
  • 추가: +69줄
  • 삭제: -5줄

📁 추가된 파일:
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/TopBannerImage/TopBannerPopup.figma.jsx
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/TopBannerImage/TopBannerPopup.jsx
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/TopBannerImage/TopBannerPopup.module.less

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/components/GlobalPopup/GlobalPopup.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/Banner/Banner.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/TopBannerImage/TopBannerImage.jsx

🔧 주요 변경 내용:
  • UI 컴포넌트 아키텍처 개선
  • 소규모 기능 개선
  • 모듈 구조 개선
2025-12-10 16:33:38 +09:00
bf7af5aa2e [251210] feat: FeaturedBrandsPanel-TopBannerImage 추가
🕐 커밋 시간: 2025. 12. 10. 15:53:45

📊 변경 통계:
  • 총 파일: 6개
  • 추가: +51줄
  • 삭제: -21줄

📁 추가된 파일:
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/TopBannerImage/TopBannerImage.jsx
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/TopBannerImage/TopBannerImage.module.less

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/brandActions.js
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/Banner/Banner.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/Banner/Banner.module.less
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBrandsPanel.jsx

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
  • 소규모 기능 개선
  • 모듈 구조 개선
2025-12-10 15:53:46 +09:00
bc7a999cf1 Merge resolve conflicts 2025-12-10 13:10:53 +09:00
d6656848a2 [251210] feat: featuredBrandTopBanner API추가 , nbcu bg추가
🕐 커밋 시간: 2025. 12. 10. 13:02:33

📊 변경 통계:
  • 총 파일: 10개
  • 추가: +100줄
  • 삭제: -4줄

📁 추가된 파일:
  + com.twin.app.shoptime/assets/images/bg/nbcu_new.png

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/actionTypes.js
  ~ com.twin.app.shoptime/src/actions/brandActions.js
  ~ com.twin.app.shoptime/src/api/apiConfig.js
  ~ com.twin.app.shoptime/src/reducers/brandReducer.js
  ~ com.twin.app.shoptime/src/views/DetailPanel/components/DetailPanelBackground/DetailPanelBackground.jsx
  ~ com.twin.app.shoptime/src/views/DetailPanel/components/DetailPanelBackground/DetailPanelBackground.v2.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/Banner/Banner.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBrandsPanel.jsx
  ~ com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx

🔧 주요 변경 내용:
  • 타입 시스템 안정성 강화
  • 핵심 비즈니스 로직 개선
  • API 서비스 레이어 개선
  • UI 컴포넌트 아키텍처 개선
  • 소규모 기능 개선
  • 모듈 구조 개선
2025-12-10 13:02:34 +09:00
junghoon86.park
d545a4de0c [상품 상세]
- price 파트너사 붙는쪽에 nbcu일 경우 peacoke | ******* 로 들어오는 부분때문에 변경.
2025-12-10 12:53:48 +09:00
junghoon86.park
39d1b42ec4 [상세 헤더]
- 파트너 아이디가 21일경우 id 값 노출안되도록 변경(2025.12.10 오현주 팀장님 요청건)
2025-12-10 12:47:33 +09:00
junghoon86.park
53aa879ee5 [checkout panel]
- 배송정보, 주문정보, 카드정보 없을시 qr팝업 노출되도록 수정.
 - qr팝업 닫혔을때 상품 상세페이지로 이동하도록 처리.
2025-12-10 12:39:31 +09:00
db7bc4b2ed [251210] feat: brandBestSelleInfo 처리
🕐 커밋 시간: 2025. 12. 10. 12:28:02

📊 변경 통계:
  • 총 파일: 3개
  • 추가: +17줄
  • 삭제: -2줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/brandActions.js
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBestSeller/FeaturedBestSeller.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBrandsPanel.jsx

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
2025-12-10 12:28:03 +09:00
junghoon86.park
a6275a63e9 [justforyou] 이미지 겉 보더 색상 변경.
- 색상변경 및 추가.
2025-12-10 10:46:35 +09:00
a6eee92641 [251210] fix: 백화현상디버깅-App.js commonActions.js 원복
🕐 커밋 시간: 2025. 12. 10. 10:01:38

📊 변경 통계:
  • 총 파일: 2개
  • 추가: +16줄
  • 삭제: -14줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/App/App.js
  ~ com.twin.app.shoptime/src/actions/commonActions.js

🔧 함수 변경 내용:
  📄 com.twin.app.shoptime/src/App/App.js (javascript):
     Added: AppBase(), keyDownEvent(), mouseMoveEvent()
    🔄 Modified: function()
  📄 com.twin.app.shoptime/src/actions/commonActions.js (javascript):
    🔄 Modified: alertToast(), setFocus(), cancelFocusElement(), addReservation(), Job()

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
2025-12-10 10:01:38 +09:00
844f374abb [251209] fix: 백화현상 디버깅-3 App.js initService dispatch의존성 추가
🕐 커밋 시간: 2025. 12. 09. 18:42:58

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +1줄
  • 삭제: -1줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/App/App.js

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
  • 코드 정리 및 최적화
2025-12-09 18:42:58 +09:00
77987711d0 [251209] fix: 백화현상 디버깅-2 App.js GlobalPopup활성화
🕐 커밋 시간: 2025. 12. 09. 18:34:56

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +2줄
  • 삭제: -1줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/App/App.js

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
2025-12-09 18:34:56 +09:00
18c3ac3ad5 [251209] fix: 백화현상 디버깅-1
🕐 커밋 시간: 2025. 12. 09. 18:18:51

📊 변경 통계:
  • 총 파일: 2개
  • 추가: +28줄
  • 삭제: -4줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/homeActions.js
  ~ com.twin.app.shoptime/src/api/TAxios.js

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
  • API 서비스 레이어 개선
  • 소규모 기능 개선
2025-12-09 18:18:51 +09:00
aa1f9630e6 [251209] feat: ShopByShow-8
🕐 커밋 시간: 2025. 12. 09. 17:49:58

📊 변경 통계:
  • 총 파일: 5개
  • 추가: +69줄
  • 삭제: -22줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBrandsPanel.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShow.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowContents/ShopByShowContents.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowContents/ShopByShowProductList/ShopByShowProductList.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowNav/ShopByShowNav.jsx

🔧 주요 변경 내용:
  • 소규모 기능 개선
2025-12-09 17:49:58 +09:00
407b4c7751 [251209] feat: 약관미동의시 처리-1
🕐 커밋 시간: 2025. 12. 09. 17:17:23

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +16줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/homeActions.js

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
2025-12-09 17:17:24 +09:00
f2ab9dbdd4 [251209] feat: NBCU-ShopByShow-7
🕐 커밋 시간: 2025. 12. 09. 17:04:16

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +16줄
  • 삭제: -2줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/reducers/brandReducer.js

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
2025-12-09 17:04:16 +09:00
ce7916d7b0 [251209] feat: NBCU-ShopByShow-6
🕐 커밋 시간: 2025. 12. 09. 16:51:12

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +5줄
  • 삭제: -4줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowContents/ShopByShowProductList/ShopByShowProductList.jsx

🔧 주요 변경 내용:
  • 코드 정리 및 최적화
2025-12-09 16:51:13 +09:00
0db5a72c63 [251209] feat: NBCU-ShopByShow-5
🕐 커밋 시간: 2025. 12. 09. 16:28:02

📊 변경 통계:
  • 총 파일: 2개
  • 추가: +32줄
  • 삭제: -2줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShow.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowNav/ShopByShowNav.jsx

🔧 주요 변경 내용:
  • 소규모 기능 개선
2025-12-09 16:28:02 +09:00
a46d34b776 [251209] feat: NBCU-ShopByShow-4
🕐 커밋 시간: 2025. 12. 09. 16:19:54

📊 변경 통계:
  • 총 파일: 5개
  • 추가: +62줄
  • 삭제: -139줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/hooks/useScrollTo.js
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShow.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShow.module.less

🗑️ 삭제된 파일:
  - com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowList.jsx
  - com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowList.module.less

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
  • 소규모 기능 개선
  • 코드 정리 및 최적화

Performance: 코드 최적화로 성능 개선 기대
2025-12-09 16:19:55 +09:00
85c44cdd8b [251209] feat: NBCU-ShopByShow-3
🕐 커밋 시간: 2025. 12. 09. 16:05:46

📊 변경 통계:
  • 총 파일: 14개
  • 추가: +50줄
  • 삭제: -553줄

📁 추가된 파일:
  + com.twin.app.shoptime/shopByShow.response.json
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowContents/ShopByShowImageCard/ShopByShowImageCard.jsx
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowContents/ShopByShowImageCard/ShopByShowImageCard.module.less
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowContents/ShopByShowProductList/ShopByShowProductList.jsx
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowContents/ShopByShowProductList/ShopByShowProductList.module.less

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowContents/ShopByShowContents.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowContents/ShopByShowContents.module.less
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowList.jsx

🗑️ 삭제된 파일:
  - com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowSection/ShopByShowImageCard/ShopByShowImageCard.jsx
  - com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowSection/ShopByShowImageCard/ShopByShowImageCard.module.less
  - com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowSection/ShopByShowProductList/ShopByShowProductList.jsx
  - com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowSection/ShopByShowProductList/ShopByShowProductList.module.less
  - com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowSection/ShopByShowSection.jsx
  - com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowSection/ShopByShowSection.module.less

🔧 주요 변경 내용:
  • 소규모 기능 개선
  • 코드 정리 및 최적화
  • 모듈 구조 개선

Performance: 코드 최적화로 성능 개선 기대
2025-12-09 16:05:48 +09:00
2a1cda560c [251209] feat: NBCU-ShopByShow-2
🕐 커밋 시간: 2025. 12. 09. 15:48:17

📊 변경 통계:
  • 총 파일: 11개
  • 추가: +25줄
  • 삭제: -114줄

📁 추가된 파일:
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShow.figma.jsx
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowSection/ShopByShowImageCard/ShopByShowImageCard.jsx
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowSection/ShopByShowImageCard/ShopByShowImageCard.module.less
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowSection/ShopByShowProductList/ShopByShowProductList.jsx
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowSection/ShopByShowProductList/ShopByShowProductList.module.less
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowSection/ShopByShowSection.jsx
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowSection/ShopByShowSection.module.less

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowContents/ShopByShowContents.module.less
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowList.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowList.module.less
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowNav/ShopByShowNav.jsx

🔧 주요 변경 내용:
  • 소규모 기능 개선
  • 코드 정리 및 최적화
  • 모듈 구조 개선

Performance: 코드 최적화로 성능 개선 기대
2025-12-09 15:48:19 +09:00
junghoon86.park
c7f6bf00b9 [상품 상세] shopbymobile 팝업 노출관련 수정
- 팝업이 정상적이지않아 의존성배열에 추가하여 수정.
2025-12-09 15:16:31 +09:00
255b3bb2b7 Merge remote-tracking branch 'gitlab/develop_si' into develop_si 2025-12-09 14:34:24 +09:00
4a6473e1e5 [251209] feat: NBCU-ShopByShow-1
🕐 커밋 시간: 2025. 12. 09. 14:33:20

📊 변경 통계:
  • 총 파일: 15개
  • 추가: +128줄
  • 삭제: -11줄

📁 추가된 파일:
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShow.jsx
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShow.module.less
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowContents/ShopByShowContents.jsx
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowContents/ShopByShowContents.module.less
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowList.jsx
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowList.module.less
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowNav/ShopByShowNav.jsx
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/ShopByShow/ShopByShowList/ShopByShowNav/ShopByShowNav.module.less

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/actionTypes.js
  ~ com.twin.app.shoptime/src/actions/brandActions.js
  ~ com.twin.app.shoptime/src/api/apiConfig.js
  ~ com.twin.app.shoptime/src/reducers/brandReducer.js
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBestSeller/FeaturedBestSellerList/FeaturedBestSellerList.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/FeaturedBrandsPanel.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/QuickMenu/QuickMenuItem/QuickMenuItem.jsx

🔧 주요 변경 내용:
  • 타입 시스템 안정성 강화
  • 핵심 비즈니스 로직 개선
  • API 서비스 레이어 개선
  • 중간 규모 기능 개선
  • 모듈 구조 개선
2025-12-09 14:33:21 +09:00
junghoon86.park
7f7b413aa5 [저스트포유 배너 변경]
- 기존 image banner로들어가는부분이 아닌 today deals와 같은 포맷으로 들어가도록 변경.
 - 이미지, 데이터 불러오는부분 추가
2025-12-09 13:54:06 +09:00
439e5f46e3 [251209] feat: log_keys.txt
🕐 커밋 시간: 2025. 12. 09. 13:00:08

📊 변경 통계:
  • 총 파일: 1개

📁 추가된 파일:
  + com.twin.app.shoptime/log_keys.txt

🔧 주요 변경 내용:
  • 로깅 시스템 개선
2025-12-09 13:00:08 +09:00
junghoon86.park
92ee225dd1 [리뷰 패널 필터 스타일 수정]
- 리뷰 패널 필터 스타일 수정(작동되고있는 필터 구분을 위해서 처리)
2025-12-09 09:45:32 +09:00
junghoon86.park
c0223176f2 [리뷰 팝업 노출 관련 수정]
- 리뷰 팝업 노출시 인디케이터로 이동후 포커스 벗어나면 초기화되는부분이 있어 이부분에 대한 수정.
2025-12-09 09:34:07 +09:00
junghoon86.park
80c593e6f0 [상품 상세]
- 상풉 옵션별 ID노출 처리.
2025-12-08 20:10:02 +09:00
junghoon86.park
b2807c5a39 [리뷰 팝업 포커스 사라짐 수정]
- 모드 변경시에 포커스 박스 안에서만 유지되도록 변경.
2025-12-08 19:38:04 +09:00
junghoon86.park
e00763f0da [리뷰 별점관련 스타일 수정]
- 티비 화면에서 확인필요
2025-12-08 19:25:42 +09:00
junghoon86.park
b040dd8c1c [buyoption] 로그인 관련 수정
- 뜰때 하이드 팝업 되는 문제로 인해서 멤버십이 안뜨는부분때문에 sethidepopup제거.
2025-12-08 18:44:57 +09:00
junghoon86.park
7507f81c34 [상품상세 포커스 수정]
- 영상 존재시 영상으로 포커스 , 이외에는 상품 이미지 첫번째로 포커스가도록 수정.
2025-12-08 17:05:50 +09:00
junghoon86.park
b6bcc7dadc [상품명 부분 노출수정]
- 기존 스타일 수정한것으로 확인하니 상품명이 너무 길경우 padding 으로 인해서 글자가 아래쪽에 보이는문제 발견하여 수정
2025-12-08 16:24:05 +09:00
junghoon86.park
2627a7ac68 [상품 상세]
- 글자 폰트 변경 및 노출관련하여 margin을 Padding으로 변경.
2025-12-08 15:10:29 +09:00
junghoon86.park
d164630200 [homepanel]
- 선택약관 동의, 비동의시 팝업 닫힌 뒤 포커스 이동 처리.
 - settimeout100으로 home_tbody로 포커스 줌.
2025-12-08 14:01:21 +09:00
junghoon86.park
6c00f6bd7d [detail]
- 하단 부분 scrolldown부분 노출 관련하여 isBottom이 들어가있을시 정상작동하지않아 제거.
2025-12-08 13:37:54 +09:00
f51e8bbfc5 [251208] fix: GNB-Featured Brands mock menu remove
🕐 커밋 시간: 2025. 12. 08. 12:25:30

📊 변경 통계:
  • 총 파일: 1개
  • 추가: +3줄
  • 삭제: -19줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/components/TabLayout/TabLayout.jsx

🔧 주요 변경 내용:
  • UI 컴포넌트 아키텍처 개선
  • 코드 정리 및 최적화

Performance: 코드 최적화로 성능 개선 기대
2025-12-08 12:25:30 +09:00
126 changed files with 6062 additions and 1332 deletions

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

View 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 - 앱 상태

View 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": "Womens 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"
}
]
}
]
}
}
}

View File

@@ -13,6 +13,7 @@ import Spotlight from '@enact/spotlight';
import { Job } from '@enact/core/util';
import platform from '@enact/core/platform';
import { ThemeDecorator } from '@enact/sandstone/ThemeDecorator';
import GlobalPopup from '../components/GlobalPopup/GlobalPopup';
// import "../../../assets/fontello/css/fontello.css";
@@ -406,8 +407,6 @@ Spotlight.silentlyFocus = function (...args) {
return ret;
};
// Spotlight Focus 추적 로그 [251115]
// 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 httpHeader = useSelector((state) => state.common.httpHeader);
const httpHeaderRef = useRef(httpHeader);
@@ -628,7 +627,7 @@ function AppBase(_props /* eslint-disable-line no-unused-vars */) {
clearLaunchParams();
}
},
[dispatch],
[dispatch]
);
const handleRelaunchEvent = useCallback(() => {
@@ -682,7 +681,7 @@ function AppBase(_props /* eslint-disable-line no-unused-vars */) {
if (typeof window === 'object' && window.PalmSystem) {
window.PalmSystem.activate();
}
}, [initService, introTermsAgreeRef]);
}, [initService, introTermsAgreeRef, dispatch]);
const visibilityChanged = useCallback(() => {
// console.log('document is hidden', document.hidden);
@@ -726,7 +725,7 @@ function AppBase(_props /* eslint-disable-line no-unused-vars */) {
}, [dispatch]);
useEffect(() => {
const keyDownEvent = (_event /* eslint-disable-line no-unused-vars */) => {
const keyDownEvent = (event) => {
dispatch(changeAppStatus({ cursorVisible: false }));
Spotlight.setPointerMode(false);
};
@@ -735,7 +734,7 @@ function AppBase(_props /* eslint-disable-line no-unused-vars */) {
let lastMoveTime = 0;
const THROTTLE_MS = 100;
const mouseMoveEvent = (_event /* eslint-disable-line no-unused-vars */) => {
const mouseMoveEvent = (event) => {
const now = Date.now();
if (now - lastMoveTime < THROTTLE_MS) {
// throttle 기간 내에는 hideCursor만 재시작
@@ -788,6 +787,8 @@ function AppBase(_props /* eslint-disable-line no-unused-vars */) {
let userDataChanged = false;
if (JSON.stringify(loginUserDataRef.current) !== JSON.stringify(loginUserData)) {
userDataChanged = true;
}
if (!httpHeader || !deviceId) {
} else if (userDataChanged || httpHeaderRef.current === null) {
//계정정보 변경시 또는 초기 로딩시
if (!httpHeader) {
@@ -888,7 +889,7 @@ function AppBase(_props /* eslint-disable-line no-unused-vars */) {
/>
)}
<ToastContainer />
{/* <GlobalPopup /> */}
<GlobalPopup />
</ErrorBoundary>
);
}

View File

@@ -85,6 +85,7 @@ export const handleDeepLink = (contentTarget) => (dispatch, _getState) => {
patnrId: patnrId,
chanId: chanId,
shptmBanrTpNm: "LIVE",
modal: false, // DeepLink 진입 시 fullscreen으로 재생
// expsOrd: expsOrd,
};
break;
@@ -101,6 +102,7 @@ export const handleDeepLink = (contentTarget) => (dispatch, _getState) => {
patnrId: patnrId,
showId: showId,
shptmBanrTpNm: "VOD",
modal: false, // DeepLink 진입 시 fullscreen으로 재생
// expsOrd: expsOrd,
};
break;
@@ -274,6 +276,18 @@ export const handleDeepLink = (contentTarget) => (dispatch, _getState) => {
const action =
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(
action({
name: panelName,

View File

@@ -130,11 +130,16 @@ export const types = {
GET_BRAND_CREATORS_INFO: 'GET_BRAND_CREATORS_INFO',
GET_BRAND_SHOWROOM: 'GET_BRAND_SHOWROOM',
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_CHAN_INFO: 'SET_BRAND_CHAN_INFO',
RESET_BRAND_STATE: 'RESET_BRAND_STATE',
RESET_BRAND_STATE_EXCEPT_BRAND_INFO: 'RESET_BRAND_STATE_EXCEPT_BRAND_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
GET_SUB_CATEGORY: 'GET_SUB_CATEGORY',

View File

@@ -37,10 +37,12 @@ export const getBrandList = () => (dispatch, getState) => {
export const getBrandLayoutInfo = (props) => (dispatch, getState) => {
const { patnrId } = props;
// console.log("[getBrandLayoutInfo] Called - patnrId:", patnrId);
dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } }));
const onSuccess = (response) => {
// dlog("getBrandLayoutInfo onSuccess ", response.data);
// console.log("[getBrandLayoutInfo] onSuccess - patnrId:", patnrId, "data:", response.data.data);
dispatch({
type: types.GET_BRAND_LAYOUT_INFO,
@@ -53,6 +55,7 @@ export const getBrandLayoutInfo = (props) => (dispatch, getState) => {
};
const onFail = (error) => {
// console.log("[getBrandLayoutInfo] onFail - patnrId:", patnrId, "error:", error);
derror('getBrandLayoutInfo onFail ', error);
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
};
@@ -336,10 +339,15 @@ export const getBrandCategoryProductInfo = (props) => (dispatch, getState) => {
export const getBrandBestSeller = (props) => (dispatch, getState) => {
const { patnrId } = props;
// console.log("[getBrandBestSeller] Called - patnrId:", patnrId);
dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } }));
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({
type: types.GET_BRAND_BEST_SELLER,
@@ -352,6 +360,7 @@ export const getBrandBestSeller = (props) => (dispatch, getState) => {
};
const onFail = (error) => {
// console.log("[getBrandBestSeller] onFail - patnrId:", patnrId, "error:", error);
derror('getBrandBestSeller onFail ', error);
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);
};
// 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
export const getBrandRecentlyAired = (props) => (dispatch, getState) => {
const { patnrId } = props;

View File

@@ -122,7 +122,7 @@ export const alertToast = (payload) => (dispatch) => {
}
};
export const getSystemSettings = () => (dispatch) => {
export const getSystemSettings = () => (dispatch, getState) => {
dlog('getSystemSettings ');
lunaSend.getSystemSettings(
{ 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 ');
const { serverType, ricCodeSetting, languageSetting } = getState().localSettings;
lunaSend.getHttpHeaderForServiceRequest({
@@ -285,7 +285,7 @@ export const getHttpHeaderForServiceRequest = () => (dispatch, getState) => {
});
};
export const getDeviceId = (onComplete) => (dispatch) => {
export const getDeviceId = (onComplete) => (dispatch, getState) => {
lunaSend.getDeviceId(
{ idType: ['LGUDID'] },
{
@@ -463,7 +463,7 @@ export const setFocus = (spotlightId) => ({
payload: spotlightId,
});
export const focusElement = (spotlightId) => (dispatch) => {
export const focusElement = (spotlightId) => (dispatch, getState) => {
dispatch(setFocus(spotlightId));
if (typeof window === 'object') {
@@ -485,7 +485,7 @@ export const cancelFocusElement = () => () => {
let broadcastTimer = null;
export const sendBroadCast =
({ type, moreInfo }) =>
(dispatch) => {
(dispatch, getState) => {
clearTimeout(broadcastTimer);
dispatch(changeBroadcastEvent({ type, moreInfo }));
broadcastTimer = setTimeout(() => {
@@ -542,7 +542,7 @@ export const addReservation = (data) => (dispatch) => {
});
};
export const deleteReservationCallback = (scheduleIdList) => () => {
export const deleteReservationCallback = (scheduleIdList) => (dispatch) => {
lunaSend.deleteReservationCallback(scheduleIdList, {
onSuccess: (res) => {
// dispatch(alertToast("success" + JSON.stringify(res)));
@@ -680,7 +680,7 @@ let updateNetworkStateJob = new Job((dispatch, connected) => {
dispatch(changeAppStatus({ isInternetConnected: connected }));
});
export const getConnectionStatus = () => (dispatch) => {
export const getConnectionStatus = () => (dispatch, getState) => {
lunaSend.getConnectionStatus({
onSuccess: (res) => {
dlog('lunasend getConnectionStatus', res);
@@ -709,7 +709,7 @@ export const getConnectionStatus = () => (dispatch) => {
};
// macAddress
export const getConnectionInfo = () => (dispatch) => {
export const getConnectionInfo = () => (dispatch, getState) => {
lunaSend.getConnectionInfo({
onSuccess: (res) => {
dlog('lunasend getConnectionStatus', res);
@@ -731,7 +731,7 @@ export const getConnectionInfo = () => (dispatch) => {
});
};
export const disableNotification = () => {
export const disableNotification = () => (dispatch, getState) => {
lunaSend.disableNotification({
onSuccess: (res) => {
dlog('lunasend disable notification success', res);
@@ -745,7 +745,7 @@ export const disableNotification = () => {
});
};
export const enableNotification = () => {
export const enableNotification = () => (dispatch, getState) => {
lunaSend.enableNotification({
onSuccess: (res) => {
dlog('lunasend enable notification success', res);

View File

@@ -4,6 +4,8 @@ import { types } from './actionTypes';
import { changeAppStatus, getTermsAgreeYn } from './commonActions';
import { collectBannerPositions } from '../utils/domUtils';
import { createDebugHelpers } from '../utils/debug';
import { setHidePopup, setShowPopup } from './commonActions';
import { ACTIVE_POPUP } from '../utils/Config';
// 디버그 헬퍼 설정
const DEBUG_MODE = false;
@@ -75,6 +77,38 @@ export const getHomeTerms = (props) => (dispatch, getState) => {
const 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(

View File

@@ -99,17 +99,25 @@ export const finishMediaPreview = () => (dispatch, getState) => {
export const finishModalMediaForce = () => (dispatch, getState) => {
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(
(panel) =>
panel.name === panel_names.MEDIA_PANEL &&
(panel.panelInfo?.modal || panel.panelInfo?.modalContainerId === 'product-video-player')
);
// console.log('[🟡UNIQUE_DETAIL_CLEANUP🟡] hasProductVideoPanel:', hasProductVideoPanel);
if (hasProductVideoPanel) {
if (startMediaFocusTimer) {
clearTimeout(startMediaFocusTimer);
startMediaFocusTimer = null;
}
// console.log('[🟡UNIQUE_DETAIL_CLEANUP🟡] Calling popPanel(panel_names.MEDIA_PANEL)');
dispatch(popPanel(panel_names.MEDIA_PANEL));
}
};

View File

@@ -39,10 +39,27 @@ export const pushPanel = (panel, duplicatable = false) => ({
duplicatable: duplicatable,
});
export const popPanel = (panelName) => ({
export const popPanel = (panelName) => {
const stack = new Error().stack;
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) => ({
type: types.UPDATE_PANEL,
@@ -93,6 +110,11 @@ export const navigateToDetail = ({
...additionalInfo,
};
const state = getState();
const panels = state.panels.panels;
// 선택적 파라미터들 추가
if (curationId) panelInfo.curationId = curationId;
if (nowShelf) panelInfo.nowShelf = nowShelf;
@@ -176,8 +198,21 @@ export const navigateToDetail = ({
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 비디오가 재생 중이면 정지
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');
@@ -204,11 +239,15 @@ export const navigateToDetail = ({
})
);
// 비디오 상태 저장 후 정지 (로그는 개발 시 필요 시 주석 해제)
// 비디오 상태 저장 후 정지
dispatch(finishVideoPreview());
} else {
// 비디오가 재생 중이 아니어도 HomePanel 상태 저장
console.log('[Detail-BG] ⏭️ navigateToDetail - HomeBanner video is NOT playing (modal === false or undefined)', {
playerPanelModal: playerPanelInfo.panelInfo?.modal,
sourceMenu,
timestamp: Date.now(),
});
dispatch(
updatePanel({
name: panel_names.HOME_PANEL,
@@ -267,10 +306,34 @@ export const navigateToDetail = ({
case SOURCE_MENUS.PLAYER_MEDIA: {
// PlayerPanel에서 온 경우
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 오버레이 숨김
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) {
panelInfo.lastFocusedTargetId = focusSnapshot.lastFocusedTargetId;
@@ -458,11 +521,28 @@ export const restoreVideoOnBack = () => {
const homePanel = panels.find((p) => p.name === panel_names.HOME_PANEL);
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) {
console.log('[Detail-BG] ⏭️ restoreVideoOnBack - No video state to restore (skipping)', {
reason: !videoStateToRestore ? 'no videoStateToRestore' : 'restoreOnBack is false',
timestamp: Date.now(),
});
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');
@@ -489,6 +569,11 @@ export const restoreVideoOnBack = () => {
})
);
console.log('[Detail-BG] ✅ restoreVideoOnBack - Video restore dispatched', {
restoredWithModal: restoreInfo.modal,
timestamp: Date.now(),
});
// 복원 상태 정리
dispatch(
updatePanel({

View File

@@ -73,6 +73,16 @@ export const startVideoPlayer =
...rest
}) =>
(dispatch, getState) => {
const caller = new Error().stack?.split('\n')[2]?.trim();
dlog('[PTRACE-SP] startVideoPlayer call', {
modal,
modalContainerId,
modalClassName,
videoId,
showUrl,
caller,
});
dlog(
'[startVideoPlayer] ✅ START - videoId:',
videoId,
@@ -105,6 +115,9 @@ export const startVideoPlayer =
// 기존 PlayerPanel이 어디든 있으면 완전히 초기화: 타이머 정리 후 pop → 새로 push
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');
clearAllVideoTimers();
dispatch(popPanel(panel_names.PLAYER_PANEL));
@@ -135,14 +148,14 @@ export const startVideoPlayer =
// [COMMENTED OUT] 비디오 재생 시 강제 포커스 이동 비활성화
// if (modal && modalContainerId && !spotlightDisable) {
// console.log('[startVideoPlayer] 🎯 Setting Spotlight focus - containerId:', modalContainerId);
// dlog('[startVideoPlayer] 🎯 Setting Spotlight focus - containerId:', modalContainerId);
// Spotlight.setPointerMode(false);
// startVideoFocusTimer = setTimeout(() => {
// console.log('[startVideoPlayer] 🔍 Spotlight.focus called');
// dlog('[startVideoPlayer] 🔍 Spotlight.focus called');
// Spotlight.focus(modalContainerId);
// }, 0);
// } 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');
@@ -182,6 +195,17 @@ export const startVideoPlayerNew =
...rest
}) =>
(dispatch, getState) => {
const caller = new Error().stack?.split('\n')[2]?.trim();
dlog('[PTRACE-SPN] startVideoPlayerNew call', {
bannerId,
modal,
modalContainerId,
modalClassName,
videoId,
showUrl,
caller,
});
dlog(
'[startVideoPlayerNew] *** ✅ START - bannerId:',
bannerId,
@@ -215,6 +239,9 @@ export const startVideoPlayerNew =
// 기존 PlayerPanel이 있으면 완전히 초기화: 타이머 정리 후 pop → 새로 push
if (existingPlayerPanel) {
dlog('[PTRACE-SPN] popping existing player before push', {
stack: panels.map((p) => p.name),
});
dlog('[startVideoPlayerNew] *** 🔄 Resetting existing PLAYER_PANEL before start');
clearAllVideoTimers();
dispatch(popPanel(panel_names.PLAYER_PANEL));
@@ -305,14 +332,14 @@ export const startVideoPlayerNew =
// [COMMENTED OUT] 비디오 재생 시 강제 포커스 이동 비활성화
// if (modal && modalContainerId && !spotlightDisable) {
// console.log('[startVideoPlayerNew] *** 🎯 Setting Spotlight focus - containerId:', modalContainerId);
// dlog('[startVideoPlayerNew] *** 🎯 Setting Spotlight focus - containerId:', modalContainerId);
// Spotlight.setPointerMode(false);
// startVideoFocusTimer = setTimeout(() => {
// console.log('[startVideoPlayerNew] *** 🔍 Spotlight.focus called');
// dlog('[startVideoPlayerNew] *** 🔍 Spotlight.focus called');
// Spotlight.focus(modalContainerId);
// }, 0);
// } 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');
@@ -325,6 +352,12 @@ export const finishVideoPreview = () => (dispatch, getState) => {
const panels = getState().panels.panels;
const topPanel = panels[panels.length - 1];
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) {
clearTimeout(startVideoFocusTimer);
startVideoFocusTimer = null;
@@ -384,6 +417,13 @@ export const pauseModalVideo = () => (dispatch, getState) => {
(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 (DEBUG_MODE === true) {
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
);
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 (DEBUG_MODE === true) {
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
);
dlog('[Detail-BG] ⏸️ pauseFullscreenVideo - Pausing fullscreen video', {
found: !!fullscreenPlayerPanel,
playerPanelModal: fullscreenPlayerPanel?.panelInfo?.modal,
currentIsPaused: fullscreenPlayerPanel?.panelInfo?.isPaused,
timestamp: Date.now(),
});
if (fullscreenPlayerPanel) {
dispatch(
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) => {
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인 패널)
const fullscreenPlayerPanel = panels.find(
(panel) => panel.name === panel_names.PLAYER_PANEL && !panel.panelInfo?.modal
);
// console.log('[BgVideo] resumeFullscreenVideo - fullscreenPlayerPanel found:', !!fullscreenPlayerPanel);
// console.log('[BgVideo] resumeFullscreenVideo - isPaused:', fullscreenPlayerPanel?.panelInfo?.isPaused);
dlog('[Detail-BG] ▶️ resumeFullscreenVideo - Resuming fullscreen video', {
found: !!fullscreenPlayerPanel,
playerPanelModal: fullscreenPlayerPanel?.panelInfo?.modal,
currentIsPaused: fullscreenPlayerPanel?.panelInfo?.isPaused,
timestamp: Date.now(),
});
if (fullscreenPlayerPanel && fullscreenPlayerPanel.panelInfo?.isPaused) {
// console.log('[BgVideo] resumeFullscreenVideo - dispatching updatePanel with isPaused: false');
dispatch(
updatePanel({
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 {
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) {
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,
// modalStyle: panelInfo.modalStyle,
// });
@@ -951,6 +1038,12 @@ export const resumePlayerControl = (ownerId) => (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로 변경하여 전체화면으로 전환
dispatch(
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
} = 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;
if (videoIdentifier) {
@@ -1190,11 +1295,21 @@ export const startBannerVideo = (videoInfo) => (dispatch, getState) => {
// 기존 PlayerPanel이 있으면 초기화
if (existingPlayerPanel) {
dlog('[startBannerVideo] 🔄 Resetting existing PLAYER_PANEL before start');
dlog('[Detail-BG] 🔄 startBannerVideo - Clearing existing PlayerPanel', {
existingModalStatus: existingPlayerPanel.panelInfo?.modal,
timestamp: Date.now(),
});
clearAllVideoTimers();
dispatch(popPanel(panel_names.PLAYER_PANEL));
}
// 새로운 PlayerPanel push
dlog('[Detail-BG] startBannerVideo - Pushing new PlayerPanel with modal status', {
modal,
modalContainerId,
timestamp: Date.now(),
});
dispatch(
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');
};

View File

@@ -185,8 +185,14 @@ export const TAxios = (
return;
}
// 약관 미동의(501): 토큰 재발급 큐에 넣지 않고 바로 실패 처리
if (res?.data?.retCode === 501) {
if (onFail) onFail(res);
return;
}
// RefreshToken 만료
if (res?.data?.retCode === 402 || res?.data?.retCode === 501) {
if (res?.data?.retCode === 402) {
if (baseUrl === URLS.GET_RE_AUTHENTICATION_CODE) {
dispatch(getAuthenticationCode());
} else {
@@ -349,10 +355,10 @@ export const TAxiosAdvancedPromise = (
console.error(`TAxiosPromise error on attempt ${attempts} for ${baseUrl}:`, error);
// 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.
const retCode = error?.data?.retCode;
const isTokenError = retCode === 401 || retCode === 402 || retCode === 501;
const isTokenError = retCode === 401 || retCode === 402;
// 재시도 로직
if (attempts < maxAttempts && !isTokenError) {

View File

@@ -2,6 +2,7 @@ import axios from "axios";
import { createQueryString } from "../utils/helperMethods";
import { getUrl } from "./apiConfig";
import { DEBUG_LOG_MODE, sendToLogServer } from "./logServerClient";
export const TLogEvent = (
dispatch,
@@ -68,6 +69,23 @@ export const TLogEvent = (
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;
switch (type) {

View File

@@ -55,6 +55,8 @@ export const URLS = {
GET_BRAND_CREATORS_INFO: "/lgsp/v1/brand/creators.lge",
GET_BRAND_SHOWROOM: "/lgsp/v1/brand/showroom.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
GET_ON_SALE_INFO: "/lgsp/v1/onsale/onsale.lge",
@@ -149,7 +151,6 @@ export const URLS = {
// foryou controller
JUSTFORYOU: "/lgsp/v1/recommend/justforyou.lge",
// emp controller
GET_SHOPTIME_TERMS: "/lgsp/v1/emp/shoptime/terms.lge",
SET_MYPAGE_TERMS_AGREE: "/lgsp/v1/mypage/terms/agree.lge",
@@ -272,11 +273,11 @@ const getRicCode = (country, ricCodeSetting) => {
if (ricCodeSetting !== "system") {
return ricCodeSetting;
}
if (country == "US") {
if (country === "US") {
return "aic";
} else if (country == "DE" || country == "GB") {
} else if (country === "DE" || country === "GB") {
return "eic";
} else if (country == "RU") {
} else if (country === "RU") {
return "ruc";
}
return null;

View 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) {
// 함수 자체의 오류는 무시 (조용하게 처리)
}
}

View File

@@ -6,6 +6,7 @@
import React, {
useCallback,
useEffect,
useMemo,
} from 'react';
@@ -14,10 +15,13 @@ import {
useSelector,
} from 'react-redux';
import Spotlight from '@enact/spotlight';
import { setHidePopup } from '../../actions/commonActions';
import { getPopupConfig } from '../../constants/popupConfig';
import usePrevious from '../../hooks/usePrevious';
import TPopUp from '../TPopUp/TPopUp';
import TopBannerPopup from '../../views/FeaturedBrandsPanel/TopBannerImage/TopBannerPopup';
// 커스텀 훅: 팝업 상태 관리
const useGlobalPopupState = () => {
@@ -128,9 +132,28 @@ const GlobalPopup = () => {
secondaryData
} = useGlobalPopupState();
const [imageDimensions, setImageDimensions] = React.useState({ width: 0, height: 0 });
const handlers = usePopupCloseHandlers();
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(() => {
if (!activePopup) return null;
@@ -214,6 +237,53 @@ const GlobalPopup = () => {
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) {
console.warn(`No configuration found for popup type: ${activePopup}`);

View File

@@ -34,7 +34,7 @@
padding: @slider-padding-v 0;
height: @sand-mediaplayer-slider-height;
right: 154px;
width: 1466px;
width: 1558px;
// Add a tap area that extends to the edges of the screen, to make the slider more accessible
&::before {
content: "";

View File

@@ -7,7 +7,7 @@
position: absolute;
font-family: @baseFont;
width: 100%;
right: 90px;
right: 20px;
bottom: -5px;
font-size: 24px;
font-weight: bold;
@@ -16,12 +16,12 @@
letter-spacing: -1px;
.separator {
position: absolute;
right: 105px;
right: 95px;
bottom: -5px;
}
.currentTime {
position: absolute;
right: 130px;
right: 120px;
bottom: -5px;
}
.totalTime {

View File

@@ -4,75 +4,75 @@ import React, {
useMemo,
useRef,
useState,
} from "react";
} from 'react';
import classNames from "classnames";
import classNames from 'classnames';
import {
AsYouTypeFormatter,
PhoneNumberFormat,
PhoneNumberUtil,
} from "google-libphonenumber";
import { useDispatch, useSelector } from "react-redux";
} from 'google-libphonenumber';
import { useDispatch, useSelector } from 'react-redux';
import { off, on } from "@enact/core/dispatcher";
import spotlight, { Spotlight } from "@enact/spotlight";
import { SpotlightContainerDecorator } from "@enact/spotlight/SpotlightContainerDecorator";
import { Spottable } from "@enact/spotlight/Spottable";
import { off, on } from '@enact/core/dispatcher';
import spotlight, { Spotlight } from '@enact/spotlight';
import { SpotlightContainerDecorator } from '@enact/spotlight/SpotlightContainerDecorator';
import { Spottable } from '@enact/spotlight/Spottable';
import defaultImage from "../../../assets/images/img-thumb-empty-144@3x.png";
import { types } from "../../actions/actionTypes";
import { clearSMS, sendSms } from "../../actions/appDataActions";
import defaultImage from '../../../assets/images/img-thumb-empty-144@3x.png';
import { types } from '../../actions/actionTypes';
import { clearSMS, sendSms } from '../../actions/appDataActions';
import {
changeLocalSettings,
setHidePopup,
setShowPopup,
} from "../../actions/commonActions";
} from '../../actions/commonActions';
import {
clearRegisterDeviceInfo,
getDeviceAdditionInfo,
registerDeviceInfo,
} from "../../actions/deviceActions";
} from '../../actions/deviceActions';
import {
clearCurationCoupon,
setEventIssueReq,
} from "../../actions/eventActions";
} from '../../actions/eventActions';
import {
sendLogShopByMobile,
sendLogTotalRecommend,
} from "../../actions/logActions";
} from '../../actions/logActions';
import {
ACTIVE_POPUP,
LOG_CONTEXT_NAME,
LOG_MESSAGE_ID,
LOG_TP_NO,
} from "../../utils/Config";
} from '../../utils/Config';
import {
$L,
decryptPhoneNumber,
encryptPhoneNumber,
formatLocalDateTime,
} from "../../utils/helperMethods";
import CustomImage from "../CustomImage/CustomImage";
import TButton from "../TButton/TButton";
import TPopUp from "../TPopUp/TPopUp";
import HistoryPhoneNumber from "./HistoryPhoneNumber/HistoryPhoneNumber";
import css from "./MobileSendPopUp.module.less";
import PhoneInputSection from "./PhoneInputSection";
import SMSNumKeyPad from "./SMSNumKeyPad";
} from '../../utils/helperMethods';
import CustomImage from '../CustomImage/CustomImage';
import TButton from '../TButton/TButton';
import TPopUp from '../TPopUp/TPopUp';
import HistoryPhoneNumber from './HistoryPhoneNumber/HistoryPhoneNumber';
import css from './MobileSendPopUp.module.less';
import PhoneInputSection from './PhoneInputSection';
import SMSNumKeyPad from './SMSNumKeyPad';
const SECRET_KEY = "fy7BTKuM9eeTQqEC9sF3Iw5qG43Aaip";
const SECRET_KEY = 'fy7BTKuM9eeTQqEC9sF3Iw5qG43Aaip';
const Container = SpotlightContainerDecorator(
{ enterTo: "last-focused" },
"div"
{ enterTo: 'last-focused' },
'div'
);
const InputContainer = SpotlightContainerDecorator(
{ enterTo: "last-focused" },
"div"
{ enterTo: 'last-focused' },
'div'
);
const SpottableComponent = Spottable("div");
const SpottableComponent = Spottable('div');
export default function MobileSendPopUp({
open,
@@ -116,39 +116,40 @@ export default function MobileSendPopUp({
const popupVisible = useSelector((state) => state.common.popup.popupVisible);
const nowMenu = useSelector((state) => state.common.menu.nowMenu);
const entryMenu = useSelector((state) => state.common.menu.entryMenu);
const [inputDisabled, setInputDisabled] = useState(true);
const [mobileNumber, setMobileNumber] = useState("");
const [mobileNumber, setMobileNumber] = useState('');
const [recentSentNumber, setRecentSentNumber] = useState([]);
const [keyPadOff, setKeyPadOff] = useState(false);
const [smsRetCode, setSmsRetCode] = useState(undefined);
const agreeBtnClickedRef = useRef(false);
const deviceCountryCode = httpHeader["X-Device-Country"];
const deviceCountryCode = httpHeader['X-Device-Country'];
const mobileSendPopUpSpotlightId = useMemo(() => {
return !keyPadOff && recentSentNumber.length <= 0
? "keypad-number-1"
: "agreeAndSend";
? 'keypad-number-1'
: 'agreeAndSend';
}, [keyPadOff, recentSentNumber]);
const getMaxNum = useCallback((_deviceCountryCode) => {
if (_deviceCountryCode === "DE" || _deviceCountryCode === "GB") {
if (_deviceCountryCode === 'DE' || _deviceCountryCode === 'GB') {
return 11;
} else if (_deviceCountryCode === "KR") {
} else if (_deviceCountryCode === 'KR') {
return 12;
} else return 10;
}, []);
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_SUCCESS_SENT = $L('Text Send to') + ' ' + mobileNumber;
const MSG_SEND_LINK = $L('Send a purchase link for this item via SMS');
const handleClickSelect = (_phoneNumber) => {
setKeyPadOff((state) => !state);
setMobileNumber(_phoneNumber);
setTimeout(() => {
Spotlight.focus("keypad-number-1");
Spotlight.focus('keypad-number-1');
}, 0);
};
@@ -161,9 +162,9 @@ export default function MobileSendPopUp({
const getRawPhoneNumber = useCallback(
(key) => {
let rawPhoneNumber = `${mobileNumber}${key}`.replace(/\D/g, "");
let rawPhoneNumber = `${mobileNumber}${key}`.replace(/\D/g, '');
if (rawPhoneNumber.length === getMaxNum(deviceCountryCode)) {
Spotlight.focus("agreeAndSend");
Spotlight.focus('agreeAndSend');
}
// 테스트용: 12자리까지 허용
if (rawPhoneNumber.length > 12) {
@@ -182,11 +183,11 @@ export default function MobileSendPopUp({
numberProto,
PhoneNumberFormat.NATIONAL
);
if (deviceCountryCode === "RU" && rawPhoneNumber.startsWith("8")) {
if (deviceCountryCode === 'RU' && rawPhoneNumber.startsWith('8')) {
rawPhoneNumber = rawPhoneNumber.substring(1);
}
} else {
let formattedNumber = "";
let formattedNumber = '';
for (let i = 0; i < rawPhoneNumber.length; i++) {
formattedNumber = asYouTypeFormatter.inputDigit(
@@ -206,7 +207,7 @@ export default function MobileSendPopUp({
);
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 asYouTypeFormatter = new AsYouTypeFormatter(deviceCountryCode);
@@ -221,7 +222,7 @@ export default function MobileSendPopUp({
PhoneNumberFormat.NATIONAL
);
} else {
let formattedNumber = "";
let formattedNumber = '';
for (let i = 0; i < rawPhoneNumber.length; i++) {
formattedNumber = asYouTypeFormatter.inputDigit(
@@ -242,7 +243,7 @@ export default function MobileSendPopUp({
(ev) => {
if (ev && ev.key >= 0 && ev.key <= 9) {
getRawPhoneNumber(ev.key);
} else if (ev.key === "Backspace") {
} else if (ev.key === 'Backspace') {
getBackspaceRawNumber();
}
},
@@ -250,9 +251,9 @@ export default function MobileSendPopUp({
);
useEffect(() => {
on("keydown", handleKeydown);
on('keydown', handleKeydown);
return () => {
off("keydown", handleKeydown);
off('keydown', handleKeydown);
};
}, [handleKeydown]);
@@ -264,7 +265,7 @@ export default function MobileSendPopUp({
useEffect(() => {
const timer = setTimeout(() =>
setInputDisabled(mobileSendPopUpSpotlightId === "keypad-number-1")
setInputDisabled(mobileSendPopUpSpotlightId === 'keypad-number-1')
);
return () => clearTimeout(timer);
@@ -277,16 +278,16 @@ export default function MobileSendPopUp({
setMobileNumber(recentSentNumber[0]);
} else {
setKeyPadOff(false);
setMobileNumber("");
setMobileNumber('');
}
}
}, [recentSentNumber]);
const numKeypadClicked = useCallback(
(key) => {
if (key === "clear") {
setMobileNumber("");
} else if (key == "backspace") {
if (key === 'clear') {
setMobileNumber('');
} else if (key == 'backspace') {
getBackspaceRawNumber();
} else {
getRawPhoneNumber(key);
@@ -315,7 +316,7 @@ export default function MobileSendPopUp({
};
const handleAgreeSendClick = useCallback(() => {
let naturalNumber = mobileNumber.replace(/\D/g, "");
let naturalNumber = mobileNumber.replace(/\D/g, '');
// 테스트용: 길이 체크를 더 유연하게 (10자리 또는 11자리 허용)
if (
@@ -327,8 +328,8 @@ export default function MobileSendPopUp({
return;
}
if (deviceCountryCode === "KR") {
naturalNumber = "82" + naturalNumber;
if (deviceCountryCode === 'KR') {
naturalNumber = '82' + naturalNumber;
}
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 };
}
if (smsTpCd === "APP00204") {
if (smsTpCd === 'APP00204') {
params = { ...params, curationId };
}
dispatch(sendSms(params));
}
// EVT00101 & APP00207(welcome) EVT00103 & APP00209 (welcome+Prizes) : smsTpCd 값을 받지 않음
if (evntTpCd === "EVT00101" || evntTpCd === "EVT00103") {
if (evntTpCd === 'EVT00101' || evntTpCd === 'EVT00103') {
dispatch(
registerDeviceInfo({
evntTpCd,
evntId,
evntApplcnFlag: "Y",
entryMenu: "TermsPop",
evntApplcnFlag: 'Y',
entryMenu: 'TermsPop',
mbphNo: naturalNumber,
})
);
@@ -434,7 +435,7 @@ export default function MobileSendPopUp({
onClose();
dispatch(setShowPopup(ACTIVE_POPUP.smsPopup));
setTimeout(() => Spotlight.focus("agreeAndSend"));
setTimeout(() => Spotlight.focus('agreeAndSend'));
},
[dispatch, smsRetCode]
);
@@ -450,7 +451,7 @@ export default function MobileSendPopUp({
curationCouponSuccess === 0
) {
const logParams = {
status: "send",
status: 'send',
nowMenu: nowMenu,
partner: patncNm ?? shopByMobileLogRef?.current?.patncNm,
productId: prdtId ?? shopByMobileLogRef?.current?.prdtId,
@@ -465,8 +466,8 @@ export default function MobileSendPopUp({
...shopByMobileLogRef.current,
locDt: formatLocalDateTime(new Date()),
logTpNo: LOG_TP_NO.SHOP_BY_MOBILE.AGREE_AND_SEND,
mbphNoFlag: "Y",
trmsAgrFlag: "Y",
mbphNoFlag: 'Y',
trmsAgrFlag: 'Y',
};
dispatch(sendLogShopByMobile(params));
@@ -497,24 +498,24 @@ export default function MobileSendPopUp({
}, [dispatch]);
const getSmsErrorMsg = useMemo(() => {
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_502 = $L('The event information has not been registered');
const SMS_ERROR_903 = $L('You have exceeded the daily text limit.');
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(
"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_600 = $L("This device had received first time coupon.");
const SMS_ERROR_601 = $L("There is no coupon.");
const SMS_ERROR_900 = $L("Failed to send text to {mobileNumber}").replace(
"{mobileNumber}",
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_601 = $L('There is no coupon.');
const SMS_ERROR_900 = $L('Failed to send text to {mobileNumber}').replace(
'{mobileNumber}',
mobileNumber
);
const SMS_ERROR_907 = $L(
"Only {length} digits is permitted. Please check again"
).replace("{length}", getMaxNum(deviceCountryCode));
'Only {length} digits is permitted. Please check again'
).replace('{length}', getMaxNum(deviceCountryCode));
switch (smsRetCode) {
case 502:
@@ -542,12 +543,12 @@ export default function MobileSendPopUp({
const getEvntErrorMsg = useMemo(() => {
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) {
return $L("There is no coupon.");
return $L('There is no coupon.');
} else {
return $L("Failed to sent text to {mobileNumber}").replace(
"{mobileNumber}",
return $L('Failed to sent text to {mobileNumber}').replace(
'{mobileNumber}',
mobileNumber
);
}
@@ -571,7 +572,7 @@ export default function MobileSendPopUp({
regDeviceInfoRetCode === undefined &&
curationCouponSuccess === undefined && (
<TPopUp
kind={"mobileSendPopup"}
kind={'mobileSendPopup'}
className={css.container}
open={open}
onClose={onClose}
@@ -602,16 +603,22 @@ export default function MobileSendPopUp({
<div className={css.headerTopRow}>
{brandLogo && (
<img
className={css.headerTopRow__brandLogo}
className={classNames(
css.headerTopRow__brandLogo,
patnrId === '1' && css.headerTopRow__brandLogo__qvc
)}
src={brandLogo}
alt="Brand"
/>
)}
{productId && (
{productId && patnrId !== '21' && (
<div className={css.headerTopRow__productId}>
ID: {productId}
</div>
)}
{patnrId === '21' && (
<div className={css.headerTopRow__productId}>{patncNm}</div>
)}
</div>
{subTitle && (
<div
@@ -639,8 +646,8 @@ export default function MobileSendPopUp({
onClick={handleInputClick}
spotlightDisabled={inputDisabled}
>
{deviceCountryCode && deviceCountryCode === "RU" && (
<span className={css.rucInput}>{"+7 "}</span>
{deviceCountryCode && deviceCountryCode === 'RU' && (
<span className={css.rucInput}>{'+7 '}</span>
)}
<span>{mobileNumber}</span>
</SpottableComponent>
@@ -662,10 +669,10 @@ export default function MobileSendPopUp({
<span
dangerouslySetInnerHTML={{
__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>
@@ -698,11 +705,11 @@ export default function MobileSendPopUp({
<Container className={css.container__btnContainer}>
<TButton
onClick={handleAgreeSendClick}
spotlightId={"agreeAndSend"}
spotlightId={'agreeAndSend'}
>
{$L("Agree and Send")}
{$L('Agree and Send')}
</TButton>
<TButton onClick={onClose}>{$L("Cancel")}</TButton>
<TButton onClick={onClose}>{$L('Cancel')}</TButton>
</Container>
</TPopUp>
)}
@@ -714,7 +721,7 @@ export default function MobileSendPopUp({
text={smsTpCd ? getSmsErrorMsg : getEvntErrorMsg}
onClick={_onClose}
hasButton
button1Text={$L("OK")}
button1Text={$L('OK')}
/>
)}
{(smsRetCode === 0 ||
@@ -727,7 +734,7 @@ export default function MobileSendPopUp({
text={MSG_SUCCESS_SENT}
onClick={onClose}
hasButton
button1Text={$L("OK")}
button1Text={$L('OK')}
/>
)}
</>

View File

@@ -1,5 +1,5 @@
@import "../../style/CommonStyle.module.less";
@import "../../style/utils.module.less";
@import '../../style/CommonStyle.module.less';
@import '../../style/utils.module.less';
/* 🆕 [NEW] Figma 디자인용 타이틀 헤드 스타일 */
.titleHead {
@@ -14,7 +14,7 @@
text-align: left; // center → left
color: black;
font-size: 32px;
font-family: "LG Smart UI";
font-family: 'LG Smart UI';
font-weight: 700;
line-height: 42px;
word-wrap: break-word;
@@ -32,12 +32,15 @@
height: 50px;
margin-right: 15px; // TV 호환: gap 대신 margin 사용
border-radius: 100%;
&.headerTopRow__brandLogo__qvc {
border-radius: 0;
}
}
.headerTopRow__productId {
color: #808080;
font-size: 24px;
font-family: "LG Smart UI";
font-family: 'LG Smart UI';
font-weight: 600;
line-height: 18px;
word-wrap: break-word;
@@ -57,7 +60,7 @@
top: 0;
z-index: 0;
content: "";
content: '';
}
display: flex;
> .container__header__productImg,

View File

@@ -1,5 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import TNewPopUp from '../TPopUp/TNewPopUp'; // TNewPopUp 컴포넌트의 정확한 경로를 확인해주세요.
import css from './OptionalConfirm.module.less';

View File

@@ -1,14 +1,26 @@
// src/components/Optional/OptionalTermsConfirm.jsx
import React, { useEffect, useCallback, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import TPopUp from '../TPopUp/TPopUp';
import TButton from '../TButton/TButton';
import TCheckBoxSquare from '../TCheckBox/TCheckBoxSquare';
import TButtonScroller from '../TButtonScroller/TButtonScroller';
import { $L, scaleH, scaleW } from '../../utils/helperMethods';
import React, {
useCallback,
useState,
} from 'react';
import {
useDispatch,
useSelector,
} from 'react-redux';
import { setHidePopup } from '../../actions/commonActions';
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';
const OptionalTermsConfirm = ({ open }) => {
@@ -18,7 +30,6 @@ const OptionalTermsConfirm = ({ open }) => {
const [isTermsPopupVisible, setIsTermsPopupVisible] = useState(false);
const [isWarningPopupVisible, setIsWarningPopupVisible] = useState(false);
const optionalTermsData = useSelector((state) =>
state.home.termsData?.data?.terms.find(term => term.trmsTpCd === "MST00405")
);
@@ -72,10 +83,10 @@ const OptionalTermsConfirm = ({ open }) => {
setIsWarningPopupVisible(false);
}, []);
const handleDontAskAgain = () => {
const handleDontAskAgain = useCallback(() => {
console.log("Don't Ask Again 처리 필요");
dispatch(setHidePopup());
};
},[dispatch]);
if (isTermsPopupVisible) {
return (

View File

@@ -1,17 +1,28 @@
// src/components/Optional/OptionalTermsConfirm.jsx
import React, { useEffect, useCallback, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import TNewPopUp from '../TPopUp/TNewPopUp';
import TButton from '../TButton/TButton';
import TCheckBoxSquare from '../TCheckBox/TCheckBoxSquare';
import TButtonScroller from '../TButtonScroller/TButtonScroller';
import { $L, scaleH, scaleW } from '../../utils/helperMethods';
import React, {
useCallback,
useEffect,
useState,
} from 'react';
import {
useDispatch,
useSelector,
} from 'react-redux';
import Spotlight from '@enact/spotlight';
import { setHidePopup } from '../../actions/commonActions';
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 cssPopup from '../TPopUp/TNewPopUp.module.less';
import Spotlight from "@enact/spotlight";
const OptionalTermsConfirm = ({ open }) => {
const dispatch = useDispatch();
@@ -37,13 +48,13 @@ const OptionalTermsConfirm = ({ open }) => {
}
}, [open, isTermsPopupVisible, isWarningPopupVisible]);
const handleMainPopupClose = useCallback(() => {
dispatch(setHidePopup());
}, [dispatch]);
// const handleMainPopupClose = useCallback(() => {
// dispatch(setHidePopup());
// }, [dispatch]);
const handleCheckboxToggle = useCallback(({ selected }) => {
setIsChecked(selected);
}, []);
// const handleCheckboxToggle = useCallback(({ selected }) => {
// setIsChecked(selected);
// }, []);
const handleViewTermsClick = useCallback(() => {
setIsTermsPopupVisible(true);

View File

@@ -30,6 +30,7 @@ export default function THeader({
ariaLabel,
children,
kind,
sponserImage,
...rest
}) {
const convertedTitle = useMemo(() => {
@@ -86,6 +87,17 @@ export default function THeader({
</Marquee>
{children}
{sponserImage &&(
<div className={css.sponserImgBox}>
<CustomImage
src={sponserImage}
className={css.sponserImg}
/>
<div className={css.sponserTextBox}>
SPONSORED BY
</div>
</div>
)}
</Container>
);
}

View File

@@ -10,7 +10,7 @@
display: flex;
justify-content: flex-start;
align-items: center;
position: relative;
.title {
width: 1788px;
font-size: 42px;
@@ -42,3 +42,24 @@
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;
}
}

View File

@@ -29,7 +29,7 @@
> img {
.size(@w: inherit, @h: inherit);
object-fit: cover;
border: solid 1px #f0f0f0;
// border: solid 1px #f0f0f0;
}
// discount rate
@@ -144,7 +144,7 @@
> img {
.size(@w: 288px, @h: 288px);
object-fit: contain;
border: solid 1px #f0f0f0;
// border: solid 1px #f0f0f0;
}
// discount rate

View File

@@ -483,7 +483,7 @@
.default-style();
.scrollInfo {
width: 900px;
width: 850px;
background-color: @BG_COLOR_01;
color: @COLOR_GRAY03;
display: flex;

View File

@@ -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 { scaleH, scaleW } from "../../utils/helperMethods";
import { getDeviceAdditionInfo } from '../../actions/deviceActions';
import {
scaleH,
scaleW,
} from '../../utils/helperMethods';
export default function TQRCode({
isBillingProductVisible,
@@ -51,6 +60,6 @@ export default function TQRCode({
correctLevel: window.QRCode.CorrectLevel.L,
});
}
}, [text, deviceInfo, entryMenu, nowMenu]);
}, [text, deviceInfo, entryMenu, nowMenu, width, height]);
return <div aria-label={ariaLabel} ref={qrcodeRef} />;
}

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

View File

@@ -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 compose from 'ramda/src/compose';
import { useDispatch } from 'react-redux';
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 css from './TabItemSub.module.less';
import { sendLogTotalRecommend } from '../../actions/logActions';
import { LOG_CONTEXT_NAME, LOG_MESSAGE_ID } from '../../utils/Config';
import { useDispatch } from 'react-redux';
import {
LOG_CONTEXT_NAME,
LOG_MESSAGE_ID,
} from '../../utils/Config';
import css from './TabItemSub.module.less';
const SpottableComponent = Spottable('div');
@@ -122,11 +133,11 @@ const TabItemBase = ({
<>
{subtitle && (
<div className={css.textWithIcon}>
{IconComponent && (
{/* {IconComponent && (
<span className={css.iconWrapper}>
<IconComponent iconType={focused ? 'focused' : selected ? 'selected' : 'normal'} />
</span>
)}
)} */}
<Marquee
marqueeDisabled={!focused}
marqueeOn={'focus'}

View File

@@ -172,23 +172,8 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
break;
//브랜드
case 10300:
result = [
// NBCU 브랜드 (하드코딩)
{
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) => ({
result =
data?.shortFeaturedBrands?.map((item) => ({
icons: FeaturedBrandIcon,
id: item.patnrId,
path: item.patncLogoPath,
@@ -200,8 +185,7 @@ export default function TabLayout({ topPanelName, onTabActivated, panelInfo }) {
panelInfo: { from: 'gnb', patnrId: item.patnrId },
},
],
})) || []),
];
})) || [];
break;
//
case 10600:

View File

@@ -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
// 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) {
this.autoCloseJob.startAfter(this.props.autoCloseTimeout);
}

View File

@@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useRef } from "react";
export default function useScrollTo() {
export default function useScrollTo({ skipAutoScrollTop = false } = {}) {
const scrollTo = useRef();
const scrollTop = useCallback(
@@ -23,8 +23,10 @@ export default function useScrollTo() {
}, []);
useEffect(() => {
if (!skipAutoScrollTop) {
scrollTop();
}, []);
}
}, [skipAutoScrollTop]);
return { getScrollTo, scrollLeft, scrollTop, scrollToRef: scrollTo };
}

View File

@@ -33,8 +33,8 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
(action.type.includes('PANEL') || action.type === 'CLEAR_PANEL_HISTORY')
) {
const caller = new Error().stack.split('\n')[1]?.trim();
console.log(`[PANEL DEBUG] ${action.type} from: ${caller}`);
console.log(' Payload:', action.payload);
// console.log(`[PANEL DEBUG] ${action.type} from: ${caller}`);
// console.log(' Payload:', action.payload);
}
// GNB 호출 식별을 위한 helper 함수
@@ -81,7 +81,7 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
const isGNB = isGNBCall();
const isOnTop = calculateIsOnTop(panelName); // 🎯 isOnTop 계산
if (DEBUG_MODE)
console.log('[PANEL] PUSH_PANEL:', {
console.log(`[PANEL] PUSH_PANEL: ${panelName}`, {
panelName,
panelInfo,
isGNB,
@@ -106,7 +106,7 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
const stateAfter = store.getState();
const panelHistoryAfter = stateAfter.panelHistory;
const panelsAfter = stateAfter.panels.panels;
console.log('[PANEL_HISTORY] After PUSH_PANEL:', {
console.log(`[PANEL_HISTORY] PUSH_PANEL: ${panelName}`, {
panelHistory: panelHistoryAfter,
panels: panelsAfter,
});
@@ -125,11 +125,19 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
// POP 후 top panel을 기록 (이전 패널로 돌아감)
if (panels.length > 0) {
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) {
const isGNB = isGNBCall();
const isOnTop = calculateIsOnTop(topPanel.name); // 🎯 isOnTop 계산
if (DEBUG_MODE)
console.log('[PANEL] POP_PANEL:', {
console.log(`[PANEL] POP_PANEL: ${topPanel.name}`, {
panelName: topPanel.name,
panelInfo: topPanel.panelInfo || {},
isGNB,
@@ -154,7 +162,7 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
const stateAfter = store.getState();
const panelHistoryAfter = stateAfter.panelHistory;
const panelsAfter = stateAfter.panels.panels;
console.log('[PANEL_HISTORY] After POP_PANEL:', {
console.log(`[PANEL_HISTORY] POP_PANEL: ${topPanel.name}`, {
panelHistory: panelHistoryAfter,
panels: panelsAfter,
});
@@ -177,7 +185,7 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
const isGNB = isGNBCall();
const isOnTop = calculateIsOnTop(panelName); // 🎯 isOnTop 계산
if (DEBUG_MODE)
console.log('[PANEL] UPDATE_PANEL:', {
console.log(`[PANEL] UPDATE_PANEL: ${panelName}`, {
panelName,
panelInfo,
isGNB,
@@ -202,7 +210,7 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
const stateAfter = store.getState();
const panelHistoryAfter = stateAfter.panelHistory;
const panelsAfter = stateAfter.panels.panels;
console.log('[PANEL_HISTORY] After UPDATE_PANEL:', {
console.log(`[PANEL_HISTORY] UPDATE_PANEL: ${panelName}`, {
panelHistory: panelHistoryAfter,
panels: panelsAfter,
});
@@ -218,11 +226,15 @@ export const panelHistoryMiddleware = (store) => (next) => (action) => {
// RESET_PANELS: GNB 네비게이션 또는 완전 초기화
case types.RESET_PANELS: {
if (DEBUG_MODE)
console.log('[PANEL] RESET_PANELS:', {
if (DEBUG_MODE) {
const resetPanelNameForLog = (action.payload && action.payload.length > 0)
? action.payload[0].name
: 'homepanel';
console.log(`[PANEL] RESET_PANELS: ${resetPanelNameForLog}`, {
payload: action.payload,
timestamp: new Date().toISOString(),
});
}
if (DEBUG_MODE)
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 panelHistoryAfter = stateAfter.panelHistory;
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,
panels: panelsAfter,
});

View File

@@ -44,6 +44,17 @@ const initialState = {
brandRecentlyAiredData: {
data: {},
},
brandShopByShowData: {
data: {},
},
brandTopBannerData: {
data: {},
},
// 🆕 [251210] patnrId=21 카테고리 그룹 데이터 저장소
brandShopByShowCategoryGroups: {},
};
export const brandReducer = (state = initialState, action) => {
@@ -155,6 +166,51 @@ export const brandReducer = (state = initialState, action) => {
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:
return {
...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:
return state;
}

View File

@@ -94,6 +94,12 @@ export const panelsReducer = (state = initialState, action) => {
}
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', {
targetPanel: action.payload || 'last_panel',
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);
}
// 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', {
resultPanels: resultPanels.map((p) => p.name),
lastAction,
@@ -132,6 +145,60 @@ export const panelsReducer = (state = initialState, action) => {
case types.UPDATE_PANEL: {
let lastIndex = -1;
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--) {
if (state.panels[i].name === action.payload.name) {
@@ -143,7 +210,7 @@ export const panelsReducer = (state = initialState, action) => {
index === lastIndex
? {
...panel,
panelInfo: { ...panel.panelInfo, ...action.payload.panelInfo },
panelInfo: { ...panel.panelInfo, ...nextPanelInfo },
}
: panel
);

View File

@@ -114,6 +114,7 @@ export const ACTIVE_POPUP = {
optionalConfirm: 'optionalConfirm',
energyPopup: 'energyPopup',
addCartPopup: 'addCartPopup',
scrollPopup: 'scrollPopup',
};
export const DEBUG_VIDEO_SUBTITLE_TEST = false;
export const AUTO_SCROLL_DELAY = 600;

View File

@@ -458,7 +458,7 @@ const tap = fp.curry((fn, value) => {
* @param {*} value 대상 값
*/
const trace = fp.curry((label, value) => {
console.log(label, value);
// console.log(label, value);
return value;
});

View File

@@ -20,7 +20,10 @@ import {
setShowPopup,
} from '../../../actions/commonActions';
import { sendLogTotalRecommend } from '../../../actions/logActions';
import { popPanel } from '../../../actions/panelActions';
import {
popPanel,
pushPanel,
} from '../../../actions/panelActions';
import TButton from '../../../components/TButton/TButton';
import TPopUp from '../../../components/TPopUp/TPopUp';
import TQRCode from '../../../components/TQRCode/TQRCode';
@@ -249,13 +252,25 @@ export default function InformationContainer({
);
const handleCancel = useCallback(() => {
dispatch(setHidePopup());
}, [dispatch]);
const handleDone = useCallback(() => {
dispatch(setHidePopup());
// dispatch(setHidePopup());
const { patnrId, prdtId } = checkoutData.productList[0];
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 (
<>
@@ -282,8 +297,8 @@ export default function InformationContainer({
>
ADD/EDIT
</TButton>
{checkoutData?.shippingAddressList && (
<ShippingAddressCard list={checkoutData.shippingAddressList} onFocus={handleFocus} />
{shippingAddressList && (
<ShippingAddressCard list={shippingAddressList} onFocus={handleFocus} />
)}
</div>
<div className={css.listBox}>
@@ -300,8 +315,8 @@ export default function InformationContainer({
{/* <div style={{ padding: '10px', textAlign: 'center', color: '#999' }}>
Mock Billing Address
</div> */}
{checkoutData?.billingAddressList && (
<BillingAddressCard list={checkoutData.billingAddressList} onFocus={handleFocus} />
{billingAddressList && (
<BillingAddressCard list={billingAddressList} onFocus={handleFocus} />
)}
</div>
<div className={css.listBox}>
@@ -314,7 +329,7 @@ export default function InformationContainer({
>
ADD/EDIT
</TButton>
{checkoutData?.cardInfo && <PaymentCard list={checkoutData.cardInfo} />}
{cardInfo && <PaymentCard list={cardInfo} />}
</div>
<div className={css.listBox}>
<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'
)}
</h3>
<TButton className={css.popupBtn} onClick={handleDone}>
<TButton className={css.popupBtn} onClick={handleCancel}>
{$L('OK')}
</TButton>
</div>

View File

@@ -280,6 +280,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
useEffect(() => {
return () => {
// console.log('[🟡UNIQUE_DETAIL_CLEANUP🟡] DetailPanel cleanup - calling finishModalMediaForce');
dispatch(finishModalMediaForce());
};
}, [dispatch]);
@@ -303,18 +304,25 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
const sourcePanel = panelInfo?.sourcePanel;
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되는 시점
console.log('[DetailPanel] unmount:', {
sourcePanel,
sourceMenu,
timestamp: Date.now(),
});
// console.log('[🔴UNIQUE_DETAIL_UNMOUNT🔴] DetailPanel unmount details:', {
// sourcePanel,
// sourceMenu,
// timestamp: Date.now(),
// });
// sourcePanel에 따른 상태 업데이트
switch (sourcePanel) {
case panel_names.PLAYER_PANEL: {
// PlayerPanel에서 온 경우: PlayerPanel에 detailPanelClosed flag 전달
console.log('[DetailPanel] unmount - PlayerPanel에 detailPanelClosed flag 전달');
console.log('[PANEL][DetailPanel] unmount - PlayerPanel에 detailPanelClosed flag 전달');
dispatch(
updatePanel({
name: panel_names.PLAYER_PANEL,
@@ -323,6 +331,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
detailPanelClosedAt: Date.now(), // ✅ 시점 기록
detailPanelClosedFromSource: sourceMenu, // ✅ 출처
lastFocusedTargetId: panelInfo?.lastFocusedTargetId, // ✅ 포커스 복원 타겟 전달
lockModalFalse: false, // Detail 종료 시 lock 해제
},
})
);
@@ -376,6 +385,14 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
const sourcePanel = panelInfo?.sourcePanel;
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(
() => {
dispatch(clearAllToasts()); // BuyOption Toast 포함 모든 토스트 제거
@@ -384,7 +401,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
switch (sourcePanel) {
case panel_names.PLAYER_PANEL:
// PlayerPanel에서 온 경우: 플레이어 비디오는 그대로 두고 모달만 정리
console.log('[DetailPanel] onBackClick - PlayerPanel 출신: 모달 정리만 수행');
console.log('[🟠UNIQUE_DETAIL_BACK🟠] PlayerPanel 출신: 모달 정리만 수행');
dispatch(finishModalMediaForce()); // MEDIA_PANEL(ProductVideo) 강제 종료
dispatch(finishVideoPreview());
break;
@@ -393,17 +410,18 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
case panel_names.SEARCH_PANEL:
default:
// HomePanel, SearchPanel 등에서 온 경우: 백그라운드 비디오 일시 중지
console.log(
'[DetailPanel] onBackClick - source panel:',
sourcePanel,
'백그라운드 비디오 일시 중지'
);
// console.log(
// '[🟠UNIQUE_DETAIL_BACK🟠] source panel:',
// sourcePanel,
// '백그라운드 비디오 일시 중지'
// );
dispatch(pauseFullscreenVideo()); // PLAYER_PANEL 비디오 중지
dispatch(finishModalMediaForce()); // MEDIA_PANEL(ProductVideo) 강제 종료
dispatch(finishVideoPreview());
break;
}
// console.log('[🟠UNIQUE_DETAIL_BACK🟠] Calling popPanel(DETAIL_PANEL)');
dispatch(popPanel(panel_names.DETAIL_PANEL));
},
() => {
@@ -425,7 +443,7 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
if (shouldUpdatePanel) {
console.log(
'[DetailPanel] onBackClick - PlayerPanel에 detailPanelClosed flag 전달'
'[PANEL][DetailPanel] onBackClick - PlayerPanel에 detailPanelClosed flag 전달'
);
dispatch(
updatePanel({
@@ -1025,34 +1043,56 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
// ProductAllSection에 비디오가 있는지 확인
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,
isModal,
playerPanelModalStatus: isModal,
hasProductVideo,
sourceMenu: panelInfo?.sourceMenu,
productDataUrl: productData?.prdtMediaUrl,
timestamp: Date.now(),
});
// PlayerPanel이 있고, 제품에 비디오가 있을 때만 비디오 멈춤
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) {
dispatch(pauseModalVideo());
} else {
dispatch(pauseFullscreenVideo());
}
} 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 () => {
// DetailPanel 언마운트 시: 비디오가 있었고 멈췄던 경우만 재생 재개
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) {
dispatch(resumeModalVideo());
} else {
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
@@ -1080,12 +1120,33 @@ export default function DetailPanel({ panelInfo, isOnTop, spotlightId }) {
const hasPlayerPanel = panels.some(
(panel) => panel.name === panel_names.PLAYER_PANEL && panel.panelInfo?.modal === true
);
const launchedFromPlayer = panelInfo?.fromPlayer || panelInfo?.sourcePanel === panel_names.PLAYER_PANEL;
if (hasPlayerPanel) {
console.log('[DetailPanel] PlayerPanel modal=true detected - stopping video preview');
if (hasPlayerPanel && !launchedFromPlayer) {
console.log('[DetailPanel] PlayerPanel modal=true detected - stopping video preview (non-player source)');
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 (
<div ref={containerRef}>

View File

@@ -680,7 +680,8 @@
.productDetailsButton,
.userReviewsButton,
.youMayLikeButton {
.youMayLikeButton,
.seeMoreProductButton {
align-self: stretch;
height: 60px;
background: rgba(255, 255, 255, 0.05); // 기본 회색 배경
@@ -708,7 +709,8 @@
.productDetailsButton,
.userReviewsButton,
.youMayLikeButton {
.youMayLikeButton,
.seeMoreProductButton {
align-self: stretch;
height: 60px;
background: rgba(255, 255, 255, 0.05); // 기본 회색 배경

View File

@@ -1,9 +1,32 @@
import React, { useCallback } from "react";
import css from "./ProductDescription.module.less";
import { $L, removeSpecificTags } from "../../../../utils/helperMethods";
import Spottable from "@enact/spotlight/Spottable";
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
import Spotlight from "@enact/spotlight";
import React, {
useCallback,
useMemo,
} from 'react';
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와 충돌 문제로 인해
const SpottableComponent = Spottable("div");
@@ -19,11 +42,22 @@ const Container = SpotlightContainerDecorator(
);
export default function ProductDescription({ productInfo }) {
const { popupVisible, activePopup } = useSelector(
(state) => state.common.popup
);
const dispatch = useDispatch();
const productDescription = useCallback(() => {
const sanitizedString = removeSpecificTags(productInfo?.prdtDesc);
return { __html: sanitizedString };
}, [productInfo?.prdtDesc]);
const productDescriptionText = useMemo(() => {
return removeSpecificTags(productInfo?.prdtDesc);
}, [productInfo?.prdtDesc]);
// 왼쪽 화살표 키 이벤트 처리
const handleKeyDown = useCallback((ev) => {
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 직접 사용 패턴
// prdtDesc가 없으면 렌더링하지 않음
if (!productInfo?.prdtDesc) {
@@ -41,6 +89,7 @@ export default function ProductDescription({ productInfo }) {
}
return (
<>
<Container
className={css.descriptionContainer}
spotlightId="product-description-container"
@@ -61,7 +110,8 @@ export default function ProductDescription({ productInfo }) {
<SpottableComponent
className={css.descriptionWrapper}
spotlightId="product-description-content"
onClick={() => console.log("[ProductDescription] Content clicked")}
// onClick={() => console.log("[ProductDescription] Content clicked")}
onClick={descriptionClick}
onFocus={() => console.log("[ProductDescription] Content focused")}
onBlur={() => console.log("[ProductDescription] Content blurred")}
onKeyDown={handleKeyDown}
@@ -72,5 +122,28 @@ export default function ProductDescription({ productInfo }) {
/>
</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>
)}
</>
);
}

View File

@@ -51,3 +51,8 @@
}
}
.scrollContainer {
padding: 31px;
font-size: 26px;
line-height: 1.5;
}

View File

@@ -1,14 +1,25 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import css from "./ProductDetail.new.module.less";
import TVirtualGridList from "../../../../components/TVirtualGridList/TVirtualGridList";
import Spottable from "@enact/spotlight/Spottable";
import CustomImage from "../../../../components/CustomImage/CustomImage";
import indicatorDefaultImage from "../../../../../assets/images/img-thumb-empty-144@3x.png";
import useScrollTo from "../../../../hooks/useScrollTo";
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
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와 충돌 문제로 인해
import { removeSpecificTags } from "../../../../utils/helperMethods";
import Spotlight from "@enact/spotlight";
import { removeSpecificTags } from '../../../../utils/helperMethods';
import css from './ProductDetail.new.module.less';
const Container = SpotlightContainerDecorator(
{
@@ -59,7 +70,8 @@ export default function ProductDetail({ productInfo }) {
const image = listImages[0] || indicatorDefaultImage;
const imageIndex = productInfo?.imageIndex ?? 0;
const totalImages = productInfo?.totalImages ?? listImages.length;
const sponserImage = productInfo?.spnsrImgUrl;
const spnsrNm = productInfo?.spnsrNm;
return (
<div className={css.thumbnailWrapper}>
<CustomImage
@@ -68,6 +80,19 @@ export default function ProductDetail({ productInfo }) {
fallbackSrc={indicatorDefaultImage}
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>
);
}, [listImages, productInfo?.imageIndex, productInfo?.totalImages]);

View File

@@ -70,14 +70,15 @@
.thumbnailWrapper {
position: relative;
width: 658px;
// width: 658px;
width:100%;
height: 610px;
display: flex;
align-items: center;
justify-content: center;
.productImage {
width: 100%;
width: 658px;
height: 100%;
margin: 0;
padding: 0;
@@ -89,4 +90,20 @@
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;
}
}
}

View File

@@ -5,11 +5,10 @@
position: relative;
width: 1114px; // ProductDetail과 동일한 고정 크기
max-width: 1114px;
height: 740px; // ProductDetail과 동일한 고정 높이
margin-bottom: 30px; // ProductDetail과 동일한 간격
height: 632px !important; // ProductDetail과 동일한 고정 높이
cursor: pointer;
background-color: rgba(0, 0, 0, 1);
border-radius: 12px;
border-radius: 12px 12px 0 0;
box-sizing: border-box;
padding: 6px; // 포커스 테두리를 위한 공간
overflow: hidden;
@@ -80,7 +79,7 @@
z-index: 23; // MediaPanel(z-index: 22)보다 위에 표시되어야 비디오 재생 중에도 포커스 테두리가 보임
border: 6px solid @PRIMARY_COLOR_RED;
box-shadow: 0 0 22px 0 rgba(0, 0, 0, 0.5);
border-radius: 12px;
border-radius: 12px 12px 0px 0px;
content: "";
}
@@ -217,15 +216,13 @@
}
.notice {
width: 100%;
width: calc(100% - 10px);
height: 54px;
background: #000000;
.flex(@justifyCenter:flex-start);
padding: 6px 18px 18px 18px;
position: absolute;
bottom: 0;
border-radius: 0 0 12px 12px;
margin-bottom: 30px; // ProductDetail과 동일한 간격
.marquee {
width: 100%;
height: 100%;

View File

@@ -311,6 +311,7 @@ export default function ProductVideo({
if (!canPlayVideo) return null;
return (
<>
<SpottableComponent
className={css.videoContainer}
onClick={handleVideoClick}
@@ -330,12 +331,13 @@ export default function ProductVideo({
<img src={playImg} alt="재생" />
</div>
</div>
</SpottableComponent>
<div className={css.notice}>
<Marquee className={css.marquee} marqueeOn="render">
<img src={ic_warning} alt={disclaimer} />
<span>{disclaimer}</span>
</Marquee>
</div>
</SpottableComponent>
</>
);
}

View File

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

View File

@@ -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;
}
}
}
}

View File

@@ -38,10 +38,6 @@ export default function UserReviewDetail({
}
}, [onNext, currentIndex, totalReviews]);
useEffect(() => {
console.log("####currentIndex", currentIndex);
}, [currentIndex]);
// 리뷰 데이터가 없을 때 처리
if (!currentReview) {
return (

View File

@@ -48,10 +48,7 @@
.averageOverallRating {
width: 176px;
height: 30px;
display: flex;
align-items: center;
margin-left: auto;
background-size: cover;
background-size: contain;
background-repeat: no-repeat;
background-position: center center;
}

View File

@@ -7,6 +7,7 @@ import React, {
import classNames from 'classnames';
import Spotlight from '@enact/spotlight';
import SpotlightContainerDecorator
from '@enact/spotlight/SpotlightContainerDecorator';
import Spottable from '@enact/spotlight/Spottable';
@@ -104,17 +105,16 @@ export default function UserReviewsPopup({
// All-Images 및 User-Reviews 모드를 위한 상태
const [currentReviewIndex, setCurrentReviewIndex] = useState(0);
// 모드별 리뷰 인덱스 초기화
// ✅ selectedImageIndex 변경 감지 추가: 클릭한 리뷰가 팝업에 표시되도록
// 모드 변경 시 초기화
useEffect(() => {
console.log('[UserReviewsPopup] Mode or selectedImageIndex changed:', {
mode,
selectedImageIndex,
allReviewsLength: allReviews?.length || 0,
});
if (mode === "all-images" && images && images[selectedImageIndex]) {
const selectedImage = images[selectedImageIndex];
if (mode === "all-images" && selectedImageIndex !== undefined) {
const selectedImage = images?.[selectedImageIndex];
if (selectedImage) {
const reviewIndex = reviewsWithImages.findIndex(
(review) => review.rvwId === selectedImage.reviewId
);
@@ -122,18 +122,12 @@ export default function UserReviewsPopup({
console.log('[UserReviewsPopup] all-images mode - found review index:', reviewIndex);
setCurrentReviewIndex(reviewIndex);
}
} else if (mode === "user-reviews") {
// User-Reviews 모드: selectedImageIndex를 그대로 사용
}
} else if (mode === "user-reviews" && selectedImageIndex !== undefined) {
console.log('[UserReviewsPopup] user-reviews mode - setting index to:', selectedImageIndex);
setCurrentReviewIndex(selectedImageIndex);
}
}, [
mode,
selectedImageIndex, // ✅ 추가: 선택된 이미지 인덱스 변경 감지
images, // ✅ 추가: 이미지 데이터 변경 감지
reviewsWithImages, // ✅ 추가: 이미지 있는 리뷰 변경 감지
allReviews, // ✅ 추가: allReviews 변경 감지
]);
}, [mode, selectedImageIndex]); // selectedImageIndex는 명시적 변경 시만
// 리뷰 네비게이션 핸들러 (All-Images 및 User-Reviews 모드)
const handlePreviousReview = useCallback(() => {
@@ -182,6 +176,7 @@ export default function UserReviewsPopup({
// 모드가 변경되면 이미지 로딩 상태 초기화
useEffect(() => {
setImageLoadStates({});
Spotlight.focus("review-popup-container");
}, [mode]);
// 모든 이미지 표시
@@ -216,6 +211,7 @@ export default function UserReviewsPopup({
? "user-review-detail-prev"
: "user-review-image-0"
}
spotlightId="review-popup-container"
>
{mode === "customer-images" && (
<TScrollerDetail

View File

@@ -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 SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
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 {
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 TVirtualGridList from '../../../../components/TVirtualGridList/TVirtualGridList';
import TVerticalPagenator
from '../../../../components/TVerticalPagenator/TVerticalPagenator';
import TVirtualGridList
from '../../../../components/TVirtualGridList/TVirtualGridList';
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 css from './YouMayAlsoLike.module.less';
@@ -158,7 +178,7 @@ export default function YouMayAlsoLike({
// DetailPanel을 언마운트하지 않고 상품 정보만 업데이트
// 이렇게 하면 백그라운드 비디오 제어 상태가 유지됨
dispatch(
updatePanel({
pushPanel({
name: panel_names.DETAIL_PANEL,
panelInfo: {
showNm: panelInfo?.showNm,

View File

@@ -4,6 +4,8 @@ import classNames from 'classnames';
import { useSelector } from 'react-redux';
import TQRCode from '../../../../components/TQRCode/TQRCode';
import TQRCodeNew from '../../../../components/TQRCode/TQRCodeNew';
import QRCodePatnr21 from './QRCodePatnr21';
import { getQRCodeUrl } from '../../../../utils/helperMethods';
import css from './QRCode.module.less';
@@ -56,13 +58,37 @@ export default function QRCode({
return detailUrl;
}, [productInfo, isShopByMobile, detailUrl]);
// patnrId === 21인 경우 qrImgUrl 처리
const isPatnrId21 = productInfo?.patnrId === 21 || productInfo?.patnrId === "21";
const qrImgUrl = isPatnrId21 ? productInfo?.qrImgUrl : null;
return (
<div className={classNames(css.qrcode, kind ? css.detailQrcode : "")}>
{/* {qrCodeUrl && <TQRCode text={qrCodeUrl} width="190" height="190" />} */}
{kind === "detail" ? (
<TQRCode text={qrCodeUrl} width="240" height="240" />
isPatnrId21 ? (
<QRCodePatnr21
qrImgUrl={qrImgUrl}
fallbackText={qrCodeUrl}
width="240"
height="240"
/>
) : (
qrCodeUrl && <TQRCode text={qrCodeUrl} width="190" height="190" />
<TQRCode text={qrCodeUrl} width="240" height="240" />
)
) : (
qrCodeUrl && (
isPatnrId21 ? (
<QRCodePatnr21
qrImgUrl={qrImgUrl}
fallbackText={qrCodeUrl}
width="190"
height="190"
/>
) : (
<TQRCode text={qrCodeUrl} width="190" height="190" />
)
)
)}
{/* todo : 시나리오,UI 릴리즈 후 */}
<div className={css.tooltip}>

View File

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

View File

@@ -52,6 +52,7 @@ export default function BuyNowPriceDisplay({
display: "inline-flex",
height: "60px",
lineHeight: "60px",
flexWrap: "wrap",
}}
>
<span className={css.price}>

View File

@@ -1,5 +1,5 @@
@import "../../../../../style/CommonStyle.module.less";
@import "../../../../../style/utils.module.less";
@import '../../../../../style/CommonStyle.module.less';
@import '../../../../../style/utils.module.less';
.wrapper {
height: 100%;
@@ -30,7 +30,7 @@
}
.name {
font-weight: bold;
font-size: 36px;
font-size: 30px;
color: @COLOR_WHITE;
width: 100%;
}

View File

@@ -30,7 +30,7 @@
}
.name {
font-weight: bold;
font-size: 36px;
font-size: 30px;
color: @COLOR_GRAY07;
}
.btmLayer {
@@ -188,9 +188,11 @@
.productNm {
width: 100%;
font-weight: bold;
font-size: 36px;
font-size: 38px;
color: @COLOR_WHITE;
flex:none;
.elip(2);
margin-bottom: 20px;
height:76px;
line-height:38px;
}

View File

@@ -9,6 +9,12 @@ import usePriceInfo from '../../../../../hooks/usePriceInfo';
import { $L } from '../../../../../utils/helperMethods';
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({
priceData,
priceInfo,
@@ -26,6 +32,10 @@ export default function ShopByMobilePriceDisplay({
orderPhnNo,
} = priceData;
// 파트너명 정규화
const cleanPatncNm = useMemo(() => extractPartnerName(patncNm), [patncNm]);
const cleanPatnrName = useMemo(() => extractPartnerName(patnrName), [patnrName]);
const {
discountRate,
rewardFlag,
@@ -50,6 +60,8 @@ export default function ShopByMobilePriceDisplay({
[isOriginalPriceEmpty, isDiscountedPriceEmpty, price5, offerInfo]
);
const renderPriceItem = useCallback(() => {
if (priceData && !promotionCode) {
if (rewd) {
@@ -57,9 +69,9 @@ export default function ShopByMobilePriceDisplay({
<div className={css.wrapper}>
<div className={css.rewdTopLayer}>
<span>
{patncNm
? patncNm + " " + $L("Price") + " "
: patnrName + " " + $L("Price") + " "}
{cleanPatncNm
? cleanPatncNm + " " + $L("Price") + " "
: cleanPatnrName + " " + $L("Price") + " "}
</span>
<span className={css.partnerPrc}>
{TYPE_CASE.case5 || TYPE_CASE.case8
@@ -95,9 +107,9 @@ export default function ShopByMobilePriceDisplay({
return (
<div className={css.wrapper}>
<span className={css.name}>
{patncNm
? patncNm + " " + $L("Price")
: patnrName + " " + $L("Price")}
{cleanPatncNm
? cleanPatncNm + " " + $L("Price")
: cleanPatnrName + " " + $L("Price")}
</span>
<div className={css.btmLayer}>
<span className={classNames(css.price, css.case01)}>
@@ -111,9 +123,9 @@ export default function ShopByMobilePriceDisplay({
<div className={css.wrapper}>
<div className={css.topLayer}>
<span className={css.name}>
{patncNm
? patncNm + " " + $L("Price")
: patnrName + $L("Price")}
{cleanPatncNm
? cleanPatncNm + " " + $L("Price")
: cleanPatnrName + $L("Price")}
</span>
</div>
{discountRate && Number(discountRate.replace("%", "")) > 4 && (
@@ -140,9 +152,9 @@ export default function ShopByMobilePriceDisplay({
<div className={css.wrapper}>
<div className={css.topLayer}>
<span className={css.name}>
{patncNm
? patncNm + " " + $L("Price")
: patnrName + " " + $L("Price")}
{cleanPatncNm
? cleanPatncNm + " " + $L("Price")
: cleanPatnrName + " " + $L("Price")}
</span>
</div>
{discountRate && Number(discountRate.replace("%", "")) > 4 && (
@@ -194,7 +206,7 @@ export default function ShopByMobilePriceDisplay({
return (
<div className={css.wrapper}>
<span className={css.partnerName}>
{patncNm ? patncNm + " " + $L("Price") : patnrName + $L("Price")}
{cleanPatncNm ? cleanPatncNm + " " + $L("Price") : cleanPatnrName + $L("Price")}
</span>
<span
className={css.offerInfo}
@@ -204,7 +216,8 @@ export default function ShopByMobilePriceDisplay({
);
}
}, [
patnrName,
cleanPatnrName,
cleanPatncNm,
priceInfo,
isOriginalPriceEmpty,
isDiscountedPriceEmpty,

View File

@@ -1,5 +1,5 @@
@import "../../../../../style/CommonStyle.module.less";
@import "../../../../../style/utils.module.less";
@import '../../../../../style/CommonStyle.module.less';
@import '../../../../../style/utils.module.less';
.wrapper {
height: 100%;
@@ -30,7 +30,7 @@
}
.name {
font-weight: bold;
font-size: 36px;
font-size: 30px;
color: @COLOR_WHITE;
width: 100%;
}

View File

@@ -78,35 +78,35 @@ export default function ThemeItemCard({
const { originalPrice, discountedPrice, discountRate } =
usePriceInfo(priceInfo) || {};
const mockEnergyLabel = [
{
"enrgLblExpsOrd": "0",
"enrgLblTpCd": "EL_TYPE_05",
"enrgLblCd": "MNLC",
"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",
"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"
},
{
"enrgLblExpsOrd": "0",
"enrgLblTpCd": "EL_TYPE_05",
"enrgLblCd": "MNLC",
"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",
"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"
},
{
"enrgLblExpsOrd": "0",
"enrgLblTpCd": "EL_TYPE_05",
"enrgLblCd": "MNLC",
"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",
"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"
}
];
// const mockEnergyLabel = [
// {
// "enrgLblExpsOrd": "0",
// "enrgLblTpCd": "EL_TYPE_05",
// "enrgLblCd": "MNLC",
// "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",
// "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"
// },
// {
// "enrgLblExpsOrd": "0",
// "enrgLblTpCd": "EL_TYPE_05",
// "enrgLblCd": "MNLC",
// "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",
// "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"
// },
// {
// "enrgLblExpsOrd": "0",
// "enrgLblTpCd": "EL_TYPE_05",
// "enrgLblCd": "MNLC",
// "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",
// "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"
// }
// ];
const setEnactFitZIndex = (zIndexValue) => {
const target = document.getElementById("floatLayer");
@@ -211,7 +211,7 @@ export default function ThemeItemCard({
))}
</div>
)} */}
{mockEnergyLabel && mockEnergyLabel.length > 0 && (
{/* {mockEnergyLabel && mockEnergyLabel.length > 0 && (
<div className={css.energyLabels}>
{mockEnergyLabel.map((label, labelIndex) => (
<SpottableTemp
@@ -228,7 +228,7 @@ export default function ThemeItemCard({
</SpottableTemp>
))}
</div>
)}
)} */}
</div>
</SpottableDiv>
{(() => {

View File

@@ -1425,8 +1425,6 @@ const BuyOption = ({
setTimeout(() => {
Spotlight.focus('buy-option-buy-now-button');
});
dispatch(setHidePopup());
dispatch(launchMembershipApp());
} else {
dispatch(setShowPopup(Config.ACTIVE_POPUP.qrPopup));
@@ -1525,6 +1523,7 @@ const handleCartMove = useCallback(() => {
disabled: detail.optStkQty <= 0,
imageUrl: detail.optImgUrl || null,
price: detail.priceInfo.split('|')[1],
prodOptCdCval: detail.prodOptCdCval,
})) || []),
]}
selectedIndex={selectedOptionItemIndex}

View File

@@ -72,6 +72,7 @@ const CustomDropDown = ({
const selectedOption = normalizedOptions[selectedIndex];
const selectedLabel = selectedOption?.label || placeholder;
const selectedId = selectedOption?.prodOptCdCval ? selectedOption?.prodOptCdCval : null;
const selectedImage = selectedOption?.imageUrl;
return (
@@ -93,7 +94,7 @@ const CustomDropDown = ({
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 className={styles.custom_dropdown__icon}>
<img src={iconDownArrow} alt="dropdown arrow" />
@@ -108,7 +109,6 @@ const CustomDropDown = ({
.map((option, reverseIndex) => {
const originalIndex = normalizedOptions.length - 1 - reverseIndex;
const isOptionDisabled = option.disabled;
return (
<SpottableDiv
key={originalIndex}
@@ -133,7 +133,7 @@ const CustomDropDown = ({
/>
)}
<span className={styles.custom_dropdown__optname}>
{option.label}
{option.label} {option.prodOptCdCval ? `ID : ${option.prodOptCdCval}` : ''}
</span>
{isOptionDisabled ? (
<span className={styles.custom_dropdown__lasttxt}>

View File

@@ -10,6 +10,7 @@ import ontv4u from '../../../../../assets/images/bg/ontv4u_new.png';
import Pinkfong from '../../../../../assets/images/bg/Pinkfong_new.png';
import qvc from '../../../../../assets/images/bg/qvc_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';
/**
@@ -38,6 +39,7 @@ export default function DetailPanelBackground({
11: shoplc,
16: koreaKiosk,
19: Pinkfong,
21: nbcu,
};
const detailPanelBg = useMemo(() => {
@@ -75,26 +77,32 @@ export default function DetailPanelBackground({
useEffect(() => {
// launchedFromPlayer가 true이면 배경 이미지를 로드하지 않음 (PlayerPanel 비디오 보이도록)
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);
return;
}
// 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)) {
// console.log('[DetailPanelBackground] Using preloaded image:', detailPanelBg);
console.log('[Detail-BG] ✅ DetailPanelBackground - Using preloaded image:', detailPanelBg);
setImageReady(true);
} 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)
.then(() => {
// console.log('[DetailPanelBackground] On-demand image loaded:', detailPanelBg);
console.log('[Detail-BG] ✅ DetailPanelBackground - On-demand image loaded:', detailPanelBg);
setImageReady(true);
})
.catch((e) => {
// console.error('[DetailPanelBackground] On-demand image load failed:', e);
console.error('[Detail-BG] ❌ DetailPanelBackground - On-demand image load failed:', e);
// 실패해도 이미지를 표시해야 함
setImageReady(true);
});

View File

@@ -9,6 +9,7 @@ import ontv4u from '../../../../../assets/images/bg/ontv4u_new.png';
import Pinkfong from '../../../../../assets/images/bg/Pinkfong_new.png';
import qvc from '../../../../../assets/images/bg/qvc_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';
// ==================== 로깅 함수들 ====================
@@ -65,6 +66,7 @@ export default function DetailPanelBackgroundV2({
11: shoplc, // SHOPLC
16: koreaKiosk, // KOREA KIOSK
19: Pinkfong, // PINKFONG
21: nbcu, // NBCU
}),
[]
);
@@ -200,7 +202,7 @@ export function PreloadedBackgroundImages({
launchedFromPlayer = false,
}) {
// 모든 파트너사 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가 있을 때만 배경 표시
const shouldShowBackground = !isHomePanelOnTop && selectedPatnrId;

View File

@@ -93,9 +93,6 @@ export default function THeaderCustom({
role="button"
/>
)}
{type === "theme" && themeTitle && (
<span className={css.themeTitle} dangerouslySetInnerHTML={{ __html: themeTitle }} />
)}
{kind ? (
""
) : (
@@ -107,13 +104,16 @@ export default function THeaderCustom({
}}
/>
)}
{type === "theme" && themeTitle && (
<span className={css.themeTitle} dangerouslySetInnerHTML={{ __html: `[${themeTitle}]` }} />
)}
<Marquee
marqueeOn="render"
className={css.title}
marqueeDisabled={marqueeDisabled}
aria-label={ariaLabel}
>
{prdtId && <span className={css.prdtId}>ID : {prdtId}</span>}
{(prdtId && patnrId !== "21") && <span className={css.prdtId}>ID : {prdtId}</span>}
{convertedTitle && (
<span dangerouslySetInnerHTML={{ __html: convertedTitle }} />
)}

View File

@@ -61,4 +61,5 @@
color: #eaeaea;
width: max-content;
margin-right: 20px;
margin-left: 10px;
}

View File

@@ -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 { 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 Scroller from '@enact/sandstone/Scroller';
import Spotlight from '@enact/spotlight';
import AutoScrollAreaDetail, { POSITION } from '../AutoScrollAreaDetail/AutoScrollAreaDetail';
import AutoScrollAreaDetail, {
POSITION,
} from '../AutoScrollAreaDetail/AutoScrollAreaDetail';
import css from './TScrollerDetail.module.less';
/**
@@ -206,7 +218,8 @@ const TScrollerDetail = forwardRef(
onScrollStop={_onScrollStop}
onScroll={_onScroll}
scrollMode={scrollMode || 'translate'}
focusableScrollbar={focusableScrollbar}
// focusableScrollbar={focusableScrollbar}
focusableScrollbar={false}
className={classNames(isMounted && css.tScroller, noScrollByWheel && css.preventScroll)}
direction={direction}
horizontalScrollbar={horizontalScrollbar}

View File

@@ -2,19 +2,55 @@ import React, { memo } from "react";
import IcPartnersDefault from "../../../../assets/images/ic-tab-partners-default@3x.png";
import CustomImage from "../../../components/CustomImage/CustomImage";
import TopBannerImage from "../TopBannerImage/TopBannerImage";
import css from "./Banner.module.less";
export default memo(function Banner({
brandInfo,
brandTopImgInfo,
brandTopBannerInfo,
panelPatnrId,
selectedPatnrId,
}) {
const selectedBrandInfo =
brandInfo?.find(({ patnrId }) => panelPatnrId === patnrId) || {};
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 (
<div className={css.container}>
<figure>
@@ -26,7 +62,25 @@ export default memo(function Banner({
/>
<figcaption>{patncNm}</figcaption>
</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>
);
});

View File

@@ -3,6 +3,7 @@
.container {
position: fixed;
// position: relative; // changed from fixed to relative for absolute positioning of children
.flex(@justifyCenter: flex-start, @alignCenter: flex-end);
.size(@w: 100%, @h: 108px);
margin-bottom: 58px;

View File

@@ -19,6 +19,7 @@ const Container = SpotlightContainerDecorator(
const FeaturedBestSeller = ({
brandBestSellerInfo,
brandBestSellerTitle,
handleItemFocus,
order,
shelfOrder,
@@ -28,6 +29,12 @@ const FeaturedBestSeller = ({
}) => {
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(() => {
if (handleItemFocus) handleItemFocus(spotlightId, shelfOrder);
@@ -65,7 +72,7 @@ const FeaturedBestSeller = ({
spotlightId={spotlightId}
>
<SectionTitle
title={$L(STRING_CONF.BEST_SELLER)}
title={displayTitle}
data-title="best-seller"
label="best-seller Heading 1"
/>

View File

@@ -60,6 +60,10 @@ export default function FeaturedBestSellerList({
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(
(state) => state.common.appStatus.cursorVisible
);
@@ -155,6 +159,9 @@ export default function FeaturedBestSellerList({
lgCatNm,
euEnrgLblInfos,
} = brandBestSellerInfo[index];
// console.log("[FeaturedBestSellerList] renderItem - index:", index, "patnrId:", patnrId, "rankOrd:", rankOrd, "prdtNm:", prdtNm);
const rankText =
rankOrd === 1
? rankOrd + "st,"

View File

@@ -11,6 +11,7 @@ import { useDispatch, useSelector } from "react-redux";
import { Job } from "@enact/core/util";
import Spotlight from "@enact/spotlight";
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
import { types } from "../../actions/actionTypes";
import {
@@ -23,8 +24,10 @@ import {
getBrandLiveChannelInfo,
getBrandRecommendedShowInfo,
getBrandSeriesInfo,
getBrandShopByShow,
getBrandShowroom,
getBrandTSVInfo,
getBrandTopBanner,
} from "../../actions/brandActions";
import { changeAppStatus, setHidePopup } from "../../actions/commonActions";
import {
@@ -63,6 +66,7 @@ import LiveChannels from "./LiveChannels/LiveChannels";
import QuickMenu from "./QuickMenu/QuickMenu";
import RecommendedShows from "./RecommendedShows/RecommendedShows";
import Series from "./Series/Series";
import ShopByShow from "./ShopByShow/ShopByShow";
import Showroom from "./Showroom/Showroom";
import TodaysDeals from "./TodaysDeals/TodaysDeals";
import UpComing from "./UpComing/UpComing";
@@ -90,6 +94,7 @@ const TEMPLATE_CODE_CONF = {
SERIES: "BRD00107",
CATEGORY: "BRD00108",
SHOWROOM: "BRD00109",
NBCU: "BRD00110",
};
const DISPATCH_MAP = Object.freeze({
@@ -101,10 +106,22 @@ const DISPATCH_MAP = Object.freeze({
[TEMPLATE_CODE_CONF.SERIES]: getBrandSeriesInfo,
[TEMPLATE_CODE_CONF.CATEGORY]: getBrandCategoryInfo,
[TEMPLATE_CODE_CONF.SHOWROOM]: getBrandShowroom,
[TEMPLATE_CODE_CONF.NBCU]: getBrandShopByShow,
});
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) =>
array?.some((obj) => obj?.shptmBrndOptTpCd === value) ?? false;
@@ -233,6 +250,9 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
const brandBestSellerInfo = useSelector(
(state) => state.brand.brandBestSellerData.data.brandBestSellerInfo
);
const brandBestSellerTitle = useSelector(
(state) => state.brand.brandBestSellerData.data.brandBestSellerTitle
);
const brandRecommendedShowCategoryInfo = useSelector(
(state) =>
state.brand.brandRecommendedShowInfoData.data
@@ -263,6 +283,19 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
const brandShowroomInfo = useSelector(
(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 [focusedContainerId, setFocusedContainerId] = useState(null);
@@ -293,6 +326,9 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
});
const renderedShelfCountRef = useRef(0);
// 🆕 [251210] patnrId=21 카테고리 그룹 조회 상태 추적
const fetchedCategoryGroupsRef = useRef(new Set());
const fromDetail = panelInfo?.from && panelInfo.from === "detail";
const fromGNB = panelInfo?.from && panelInfo.from === "gnb";
const fromUpcoming = panelInfo?.from && panelInfo.from === "upcoming";
@@ -412,9 +448,12 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
);
const renderPageItem = useCallback(() => {
// console.log("[FeaturedBrandsPanel] renderPageItem - sortedBrandLayoutInfo length:", sortedBrandLayoutInfo.length);
// console.log("[FeaturedBrandsPanel] renderPageItem - sortedBrandLayoutInfo items:", sortedBrandLayoutInfo.map(el => el.shptmBrndOptTpCd));
return (
<>
{sortedBrandLayoutInfo.map((el, idx) => {
// console.log("[FeaturedBrandsPanel] Processing template code:", el.shptmBrndOptTpCd);
switch (el.shptmBrndOptTpCd) {
case TEMPLATE_CODE_CONF.LIVE_CHANNELS: {
return (
@@ -485,6 +524,10 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
}
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 (
<React.Fragment key={el.shptmBrndOptTpCd}>
{hasTemplateCodeWithValue(
@@ -492,8 +535,11 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
TEMPLATE_CODE_CONF.BEST_SELLER
) &&
shouldRenderComponent(brandBestSellerInfo) && (
<>
{/* {console.log("[FeaturedBrandsPanel] Rendering FeaturedBestSeller for patnrId:", selectedPatnrId)} */}
<FeaturedBestSeller
brandBestSellerInfo={brandBestSellerInfo}
brandBestSellerTitle={brandBestSellerTitle}
handleItemFocus={handleItemFocus}
order={idx + 1}
shelfOrder={el.expsOrd}
@@ -501,6 +547,7 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
spotlightId={TEMPLATE_CODE_CONF.BEST_SELLER}
selectedPatnrId={selectedPatnrId}
/>
</>
)}
</React.Fragment>
);
@@ -650,12 +697,43 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
</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,
brandBestSellerTitle,
brandCategoryInfo,
brandCategoryProductInfo,
brandChanInfo,
@@ -668,6 +746,8 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
brandSeriesGroupInfo,
brandSeriesInfo,
brandShowroomInfo,
brandShopByShowContsList,
brandShopByShowContsInfo,
brandTsvInfo,
fromGNB,
fromQuickMenu,
@@ -711,6 +791,7 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
// effect: layout information fetching due to partner id change
useEffect(() => {
if (!fromDetail) {
// console.log("[FeaturedBrandsPanel] Layout Info Effect - patnrId:", panelInfo?.patnrId);
dispatch({ type: types.RESET_BRAND_LAYOUT_INFO });
dispatch(getBrandLayoutInfo({ patnrId: panelInfo?.patnrId }));
setIsInitialFocusOccurred(false);
@@ -720,30 +801,98 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
// effect: set selectedPatnrId and selectedPatncNm
useEffect(() => {
if (brandInfo) {
const patnrId = panelInfo?.patnrId;
const patncNm = brandInfo.find((b) => b?.patnrId === patnrId).patncNm;
if (brandInfo && panelInfo?.patnrId) {
const patnrId = panelInfo.patnrId;
const patncNm = brandInfo.find((b) => b?.patnrId === patnrId)?.patncNm;
if (patncNm) {
setSelectedPatncNm(patncNm);
if (!fromDetail) setSelectedPatnrId(patnrId);
}
}, [brandInfo, panelInfo?.patnrId]);
// Detail에서 돌아와도 patnrId가 비어 있으면 다시 설정해 API 호출이 정상 동작하도록 보완
if (!fromDetail || !selectedPatnrId) {
setSelectedPatnrId(patnrId);
}
}
}, [brandInfo, panelInfo?.patnrId, selectedPatnrId, fromDetail]);
// effect: data fetching based on brandLayoutInfo and selectedPatnrId
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) {
// console.log("[FB-PANEL-DATA-FETCH] Fetching data - patnrId:", selectedPatnrId);
Object.entries(DISPATCH_MAP) //
.forEach(([templateCode, action]) => {
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 }));
}
});
// 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();
}
}, [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(() => {
if (selectedCatCd) {
dispatch(
@@ -905,6 +1054,18 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
}
}, [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
useEffect(() => {
return () => {
@@ -959,11 +1120,16 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
/>
)}
{brandInfo && brandTopImgInfo && (
{/* 🆕 [251211] patnrId=21일 때: Banner + Category + BestSeller를 통합 Container로 관리 */}
{(selectedPatnrId === 21 || selectedPatnrId === "21") ? (
<NbcuIntegratedContainer className={css.nbcuIntegratedContainer}>
{brandInfo && (brandTopImgInfo || brandTopBannerInfo) && (
<Banner
brandInfo={brandInfo}
brandTopImgInfo={brandTopImgInfo}
brandTopBannerInfo={brandTopBannerInfo}
panelPatnrId={panelInfo?.patnrId}
selectedPatnrId={selectedPatnrId}
/>
)}
@@ -975,6 +1141,29 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
{renderPageItem()}
</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 && (
<TButton

View File

@@ -49,10 +49,13 @@ const QuickMenuItem = ({
}, [handleStopScrolling, itemIndex]);
const handleClick = useCallback(() => {
console.log("[QuickMenuItem] Click - patnrId:", patnrId, "currentPatnrId:", selectedPatnrId ?? panelInfo?.patnrId);
if (patnrId === (selectedPatnrId ?? panelInfo?.patnrId)) {
console.log("[QuickMenuItem] Already selected, returning");
return;
}
console.log("[QuickMenuItem] Switching to patnrId:", patnrId);
const from = "menu";
const name = panel_names.FEATURED_BRANDS_PANEL;

View File

@@ -5,8 +5,8 @@ import { useDispatch, useSelector } from "react-redux";
import { Job } from "@enact/core/util";
import Spotlight from "@enact/spotlight";
import { updatePanel } from "../../../../../actions/panelActions";
import { startVideoPlayer } from "../../../../../actions/playActions";
import { pushPanel, updatePanel } from "../../../../../actions/panelActions";
// import { startVideoPlayer } from "../../../../../actions/playActions";
import TItemCard, {
removeDotAndColon,
} from "../../../../../components/TItemCard/TItemCard";
@@ -113,27 +113,36 @@ export default function RecommendedShowsProductList({
);
}
let y =
index < 2
? 0
: index === 2
? scaleH(208)
: scaleH(index * 248 - 248 - 40);
// 🆕 DetailPanel로 이동 (ShopByShow 방식)
dispatch(
startVideoPlayer({
modal: false,
patnrId,
prdtId,
showId,
shptmBanrTpNm: "VOD",
thumbnail: videoThumbnail,
targetId: "spotlightId-" + prdtId,
y,
pushPanel({
name: panel_names.DETAIL_PANEL,
panelInfo: { patnrId, prdtId },
})
);
// 🔴 기존 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(() => {

View File

@@ -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>

View File

@@ -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);

View File

@@ -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;
}
}

View File

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

View File

@@ -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;
}

View File

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

View File

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

View File

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

View File

@@ -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;
}
}
}

View File

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

View File

@@ -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: "";
}
}
}
}
}
}

View File

@@ -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;

View File

@@ -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%;
}

View File

@@ -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>

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -6,7 +6,11 @@ import Spotlight from '@enact/spotlight';
import { SpotlightContainerDecorator } from '@enact/spotlight/SpotlightContainerDecorator';
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 SectionTitle from '../../../components/SectionTitle/SectionTitle';
import Tag from '../../../components/TItemCard/Tag';
@@ -15,13 +19,20 @@ import TItemCardNew from '../../../components/TItemCard/TItemCard.new';
import TScroller from '../../../components/TScroller/TScroller';
import useScrollReset from '../../../hooks/useScrollReset';
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 { SpotlightIds } from '../../../utils/SpotlightIds';
import css from './BestSeller.module.less';
const SpottableComponent = Spottable('div');
const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
const Container = SpotlightContainerDecorator(
{ enterTo: 'last-focused' },
'div'
);
const BestSeller = ({
order,
@@ -33,15 +44,26 @@ const BestSeller = ({
shelfTitle,
}) => {
const { getScrollTo, scrollLeft } = useScrollTo();
const { handleScrollReset, handleStopScrolling } = useScrollReset(scrollLeft, true);
const { handleScrollReset, handleStopScrolling } = useScrollReset(
scrollLeft,
true
);
const dispatch = useDispatch();
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 [firstChk, setFirstChk] = useState(0);
@@ -51,11 +73,14 @@ const BestSeller = ({
useEffect(() => {
setBestInfos(
bestSellerNewDatas?.filter((item) => item.recommendTpCd === 'BESTSELLER') || [] // 기본값으로 빈 배열 설정
bestSellerNewDatas?.filter(
(item) => item.recommendTpCd === 'BESTSELLER'
) || [] // 기본값으로 빈 배열 설정
);
}, [bestSellerNewDatas]);
useEffect(() => {
if (userNumber) {
if (!bestInfos || bestInfos.length === 0) {
const baseData =
bestSellerDatas?.map((item) => ({
@@ -72,7 +97,9 @@ const BestSeller = ({
foryou: true,
})) || [];
const recommendedPrdtIds = new Set(recommendedData.map((item) => item.prdtId));
const recommendedPrdtIds = new Set(
recommendedData.map((item) => item.prdtId)
);
const baseData =
bestSellerDatas?.map((item) => ({
@@ -81,7 +108,10 @@ const BestSeller = ({
})) || [];
setBestItemNewData(baseData);
}, [bestSellerDatas, bestInfos]);
} else {
setBestItemNewData(bestSellerDatas);
}
}, [bestSellerDatas, bestInfos, userNumber]);
const orderStyle = useMemo(() => ({ order: order }), [order]);
@@ -144,7 +174,10 @@ const BestSeller = ({
if (c) {
let cAriaLabel = c.getAttribute('aria-label');
if (cAriaLabel) {
const newcAriaLabel = cAriaLabel.replace('Best Seller, Heading 1,', '');
const newcAriaLabel = cAriaLabel.replace(
'Best Seller, Heading 1,',
''
);
c.setAttribute('aria-label', newcAriaLabel);
}
}

View File

@@ -53,6 +53,10 @@ export default function HomeBanner({
const bannerDataList = useSelector((state) => state.home.bannerData?.bannerInfos);
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 - 경량화된 범용 포커스 히스토리
const focusHistory = useFocusHistory({
enableLogging: true,
@@ -163,7 +167,11 @@ export default function HomeBanner({
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);
dispatch(
@@ -175,12 +183,13 @@ export default function HomeBanner({
shptmBanrTpNm: videoData.shptmBanrTpNm,
lgCatCd: videoData.lgCatCd,
chanId: videoData.brdcChnlId,
// 기본: 배너는 modal=true로 재생
modal: true,
modalContainerId: defaultFocus,
})
);
}
}, [bannerDataList, defaultFocus, dispatch]);
}, [bannerDataList, defaultFocus, dispatch, panels, isDeepLinkEntry]);
const renderItem = useCallback(
(index, isHorizontal) => {

View File

@@ -1,31 +1,52 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
// 디버그 모드 설정 - true일 때만 console.log 출력
const DEBUG_MODE = false;
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import classNames from 'classnames';
import { useDispatch, useSelector } from 'react-redux';
import {
useDispatch,
useSelector,
} from 'react-redux';
import Spotlight from '@enact/spotlight';
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
import SpotlightContainerDecorator
from '@enact/spotlight/SpotlightContainerDecorator';
import Spottable from '@enact/spotlight/Spottable';
import { getContainerId } from '@enact/spotlight/src/container';
import btnPlay from '../../../../assets/images/btn/btn-play-thumb-nor.png';
import defaultLogoImg from '../../../../assets/images/ic-tab-partners-default@3x.png';
import emptyHorImage from '../../../../assets/images/img-home-banner-empty-hor.png';
import emptyVerImage from '../../../../assets/images/img-home-banner-empty-ver.png';
import defaultImageItem from '../../../../assets/images/img-thumb-empty-product@3x.png';
import defaultLogoImg
from '../../../../assets/images/ic-tab-partners-default@3x.png';
import emptyHorImage
from '../../../../assets/images/img-home-banner-empty-hor.png';
import emptyVerImage
from '../../../../assets/images/img-home-banner-empty-ver.png';
import defaultImageItem
from '../../../../assets/images/img-thumb-empty-product@3x.png';
import liveShow from '../../../../assets/images/tag-liveshow.png';
import { changeAppStatus } from '../../../actions/commonActions';
import { updateHomeInfo, setVideoTransitionLock } from '../../../actions/homeActions';
import { sendLogTopContents, sendLogTotalRecommend } from '../../../actions/logActions';
import { pushPanel, navigateFromRandomUnit, SOURCE_MENUS } from '../../../actions/panelActions';
import {
setVideoTransitionLock,
updateHomeInfo,
} from '../../../actions/homeActions';
import {
sendLogTopContents,
sendLogTotalRecommend,
} from '../../../actions/logActions';
import {
navigateFromRandomUnit,
pushPanel,
SOURCE_MENUS,
} from '../../../actions/panelActions';
import {
finishVideoPreview,
hideModalVideo,
startVideoPlayer,
startVideoPlayerNew,
hideModalVideo,
} from '../../../actions/playActions';
import CustomImage from '../../../components/CustomImage/CustomImage';
import usePriceInfo from '../../../hooks/usePriceInfo';
@@ -36,11 +57,19 @@ import {
LOG_TP_NO,
panel_names,
} from '../../../utils/Config';
import { selectIsPlaying } from '../../../utils/playerState/playerStateSelectors';
import { $L, formatGMTString } from '../../../utils/helperMethods';
import {
$L,
formatGMTString,
} from '../../../utils/helperMethods';
import {
selectIsPlaying,
} from '../../../utils/playerState/playerStateSelectors';
import { TEMPLATE_CODE_CONF } from '../HomePanel';
import css from './RandomUnit.module.less';
// 디버그 모드 설정 - true일 때만 console.log 출력
const DEBUG_MODE = false;
const SpottableComponent = Spottable('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 countryCode = useSelector((state) => state.common.httpHeader.cntry_cd);
const foryouInfos = useSelector((state) => state.foryou.recommendInfo.recommendProduct);
// 현재 재생 중인 비디오 배너 ID 가져오기
const currentVideoBannerId = useSelector((state) => {
@@ -490,6 +520,14 @@ export default function RandomUnit({
};
break;
case 'DSP00510':
linkInfo = {
name: panel_names.JUST_FOR_YOU_TEST_PANEL,
panelInfo: {
},
};
break;
default:
linkInfo = {
name: panel_names.HOME_PANEL,
@@ -579,6 +617,15 @@ export default function RandomUnit({
// 비디오 클릭
const videoClick = useCallback(() => {
// 🔽 비디오가 다른 배너에서 modal=true로 이미 재생 중이면 클릭 무시
if (playerPanelInfo?.modal === true && currentVideoBannerId && currentVideoBannerId !== spotlightId) {
console.log('[RandomUnit] videoClick 무시: 다른 배너에서 modal=true로 재생 중', {
currentVideoBannerId,
clickedBannerId: spotlightId,
});
return;
}
const lastFocusedTargetId = getContainerId(Spotlight.getCurrent());
const currentSpot = Spotlight.getCurrent();
if (lastFocusedTargetId) {
@@ -636,6 +683,7 @@ export default function RandomUnit({
sendBannerLog,
onBlur,
playerPanelInfo?.modal,
currentVideoBannerId,
dispatch,
handleStartVideo,
]);
@@ -644,6 +692,29 @@ export default function RandomUnit({
const { originalPrice, discountedPrice, discountRate, offerInfo } =
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(() => {
let _nowMenu = nowMenu;
@@ -999,7 +1070,72 @@ export default function RandomUnit({
/>
</div>
</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>
</>
);

View File

@@ -233,6 +233,165 @@
left: -4px;
}
}
&.justforyou {
background-image: url(../../../../assets/images/img-home-banner-jfy-ver@3x.png);
background-size: 486px 858px;
background-position: left top;
border-radius: 10px;
padding: 75px 51px 0;
&.ru {
.productInfo {
.justforyouTitle {
font-size: 58px;
line-height: 60px;
font-family: @arialFontBold;
}
}
}
&.de {
.productInfo {
.justforyouTitle {
font-size: 74px !important;
line-height: 63px !important;
letter-spacing: -1px !important;
}
}
}
.productInfo {
margin-bottom: 31px;
.justforyouTitle {
.size(@w:100%,@h:132px);
font-family: Arial;
font-weight: bold;
font-size: 80px;
word-break: break-word;
font-stretch: normal;
color: #151515;
text-align: center;
line-height: 76px;
font-family: @arialFontBold;
}
.textBox {
.size(@w: 100%, @h: 80px);
margin-top: 71px;
.elip(@clamp:2);
font-weight: bold;
font-size: 30px;
color: @COLOR_GRAY06;
line-height: 1.27;
margin-bottom: 6px;
}
.accBox {
width: 100%;
text-align: center;
font-weight: bold;
font-size: 42px;
color: @PRIMARY_COLOR_RED;
line-height: 1.14;
display: inline-block;
.elip(@clamp:1);
> strong {
width: 260px;
font-size: 30px;
line-height: 1.27;
display: block;
.elip(@clamp:2);
}
.saleAccBox {
font-weight: normal;
font-size: 24px;
color: @COLOR_GRAY04;
vertical-align: middle;
text-decoration: line-through;
margin-left: 9px;
display: inline-block;
}
}
}
.itemImgBox {
> img {
.size(@w: 356px, @h: 356px);
border-radius: 12px;
border:6px solid #DCB9A1;
box-sizing: content-box;
}
}
&.isHorizontal {
background-image: url(../../../../assets/images/img-home-banner-jfy-hor@3x.png);
background-size: 744px 420px;
background-position: center center;
display: flex;
padding: 0 30px 0 0;
border-radius: 10px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
-o-border-radius: 10px;
> div {
flex: none;
}
&.ru {
.productInfo {
.justforyouTitle {
font-size: 58px;
line-height: 60px;
font-family: @arialFontBold;
}
}
}
&.de {
.productInfo {
.justforyouTitle {
font-size: 59px !important;
line-height: 63px !important;
letter-spacing: -2px !important;
}
}
}
.productInfo {
margin-bottom: 0;
.justforyouTitle {
.size(@w:305px,@h:114px);
margin-top: 53px;
margin-left: 49px;
font-size: 66px;
word-break: break-word;
color: #151515;
text-align: left;
line-height: 57px;
font-family: @arialBlack;
}
.textBox {
.size(@w: 294px, @h: 80px);
margin: 67px 0 5px 50px;
text-align: left;
}
.accBox {
.size(@w: 320px, @h: 50px);
margin-left: 50px;
text-align: left;
display: block;
.elip(@clamp:1);
}
.saleAccBox {
color: #767676;
display: block;
text-align: left;
margin: 5px 0 0 55px;
text-decoration: line-through;
}
}
.itemImgBox {
.position(@position: absolute, @top: 47px, @left: 389px);
.size(@w: 326px, @h: 326px);
> img {
.size(@w: inherit, @h: inherit);
border-radius: 12px;
border:6px solid #DCB9A1;
box-sizing: content-box;
}
}
}
}
}
.arrow {

Some files were not shown because too many files have changed in this diff Show More