10 Commits

Author SHA1 Message Date
9439630bad [251126] feat: Featured Brands - NBCU - 1
🕐 커밋 시간: 2025. 11. 26. 19:43:03

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

📁 추가된 파일:
  + com.twin.app.shoptime/assets/images/featuredBrands/image-nbcu.png
  + com.twin.app.shoptime/assets/images/featuredBrands/nbcu.svg
  + com.twin.app.shoptime/src/components/TabLayout/iconComponents/NbcuIcon.jsx

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

🔧 주요 변경 내용:
  • UI 컴포넌트 아키텍처 개선
2025-11-26 19:43:04 +09:00
junghoon86.park
0a2ef0e68b [영상 스타일 수정]
- 노출 이상부분 수정과 버튼 위치 및 프로그레스바 위치변경.
2025-11-26 17:17:12 +09:00
96cbd1f67e [251126] fix: Remove Lint warinings - 1
🕐 커밋 시간: 2025. 11. 26. 14:59:11

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

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/App/App.js
  ~ com.twin.app.shoptime/src/App/deepLinkHandler.js
  ~ com.twin.app.shoptime/src/actions/appDataActions.js
  ~ com.twin.app.shoptime/src/actions/billingActions.js
  ~ com.twin.app.shoptime/src/actions/brandActions.js
  ~ com.twin.app.shoptime/src/actions/cancelActions.js
  ~ com.twin.app.shoptime/src/actions/cardActions.js
  ~ com.twin.app.shoptime/src/actions/checkoutActions.js
  ~ com.twin.app.shoptime/src/actions/commonActions.js
  ~ com.twin.app.shoptime/src/actions/convertActions.js
  ~ com.twin.app.shoptime/src/actions/couponActions.js
  ~ com.twin.app.shoptime/src/views/UserReview/UserReviewPanel.jsx

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

Performance: 코드 최적화로 성능 개선 기대
2025-11-26 14:59:12 +09:00
e8464b98b6 Merge remote-tracking branch 'gitlab/develop_si' into develop_si 2025-11-26 14:17:14 +09:00
4904c6fb58 [251126] fix: Log Migration - SearchPanel.new.v2.jsx
🕐 커밋 시간: 2025. 11. 26. 14:16:12

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

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/App/App.js
  ~ com.twin.app.shoptime/src/actions/commonActions.js
  ~ com.twin.app.shoptime/src/api/TAxios.js
  ~ com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.new.v2.jsx

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

Performance: 코드 최적화로 성능 개선 기대
2025-11-26 14:16:13 +09:00
junghoon86.park
1c9db184fa [상품상세] 녹화된 영상 관련 문구 노출
- productallsection에서 disclaimer 내려주고
 - productVideo 에서 노출하는 방식으로 노출
 - 단 재생시에는 자막관련노출이 겹쳐져 재생이 종료이후 노출됨.
2025-11-26 14:14:26 +09:00
3add749c07 [251126] fix: Log Migration - DetailPanel Done
🕐 커밋 시간: 2025. 11. 26. 13:47:36

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

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

🔧 주요 변경 내용:
  • 소규모 기능 개선
2025-11-26 13:47:36 +09:00
3c3662f791 [251126] fix: Log Migration - DetailPanel sendLogDetail
🕐 커밋 시간: 2025. 11. 26. 13:17:14

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

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

🔧 주요 변경 내용:
  • 중간 규모 기능 개선
2025-11-26 13:17:14 +09:00
42eda7e0bb [251126] fix: Log Migration - DetailPanel sendLogGNB
🕐 커밋 시간: 2025. 11. 26. 12:45:16

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

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

🔧 주요 변경 내용:
  • 소규모 기능 개선
2025-11-26 12:45:17 +09:00
d795182d4c [251126] fix: Log Migration - PlayerPanel.jsx
🕐 커밋 시간: 2025. 11. 26. 10:08:34

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

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

🔧 주요 변경 내용:
  • UI 컴포넌트 아키텍처 개선
2025-11-26 10:08:35 +09:00
28 changed files with 500 additions and 165 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 100 KiB

View File

@@ -18,7 +18,6 @@ import { ThemeDecorator } from '@enact/sandstone/ThemeDecorator';
import {
changeAppStatus,
changeLocalSettings,
// cancelFocusElement,
// focusElement,
// setExitApp,
@@ -45,7 +44,7 @@ import { pushPanel } from '../actions/panelActions';
import { enqueuePanelHistory } from '../actions/panelHistoryActions';
import NotSupportedVersion from '../components/NotSupportedVersion/NotSupportedVersion';
import ToastContainer from '../components/TToast/ToastContainer';
import GlobalPopup from '../components/GlobalPopup/GlobalPopup';
import usePrevious from '../hooks/usePrevious';
import { lunaTest } from '../lunaSend/lunaTest';
import { store } from '../store/store';
@@ -280,7 +279,7 @@ const originFocus = Spotlight.focus;
const originMove = Spotlight.move;
const originSilentlyFocus = Spotlight.silentlyFocus;
let lastLoggedSpotlightId = null;
let lastLoggedBlurSpotlightId = null;
let lastLoggedBlurSpotlightId = null; // eslint-disable-line no-unused-vars
let focusLoggingSuppressed = 0;
const resolveSpotlightIdFromNode = (node) => {
@@ -407,28 +406,7 @@ Spotlight.silentlyFocus = function (...args) {
return ret;
};
const resolveSpotlightIdFromEvent = (event) => {
if (!event) return undefined;
const { detail, target } = event;
if (detail) {
if (detail.spotlightId) {
return detail.spotlightId;
}
if (detail.id) {
return detail.id;
}
if (detail.target && detail.target.dataset && detail.target.dataset.spotlightId) {
return detail.target.dataset.spotlightId;
}
}
if (target && target.dataset && target.dataset.spotlightId) {
return target.dataset.spotlightId;
}
return undefined;
};
// Spotlight Focus 추적 로그 [251115]
// DOM 이벤트 리스너로 대체
@@ -448,7 +426,7 @@ const resolveSpotlightIdFromEvent = (event) => {
// });
// }
function AppBase(props) {
function AppBase(_props /* eslint-disable-line no-unused-vars */) {
const dispatch = useDispatch();
const httpHeader = useSelector((state) => state.common.httpHeader);
const httpHeaderRef = useRef(httpHeader);
@@ -650,7 +628,7 @@ function AppBase(props) {
clearLaunchParams();
}
},
[dispatch]
[dispatch],
);
const handleRelaunchEvent = useCallback(() => {
@@ -704,7 +682,7 @@ function AppBase(props) {
if (typeof window === 'object' && window.PalmSystem) {
window.PalmSystem.activate();
}
}, [initService, introTermsAgreeRef, dispatch]);
}, [initService, introTermsAgreeRef]);
const visibilityChanged = useCallback(() => {
// console.log('document is hidden', document.hidden);
@@ -748,7 +726,7 @@ function AppBase(props) {
}, [dispatch]);
useEffect(() => {
const keyDownEvent = (event) => {
const keyDownEvent = (_event /* eslint-disable-line no-unused-vars */) => {
dispatch(changeAppStatus({ cursorVisible: false }));
Spotlight.setPointerMode(false);
};
@@ -757,7 +735,7 @@ function AppBase(props) {
let lastMoveTime = 0;
const THROTTLE_MS = 100;
const mouseMoveEvent = (event) => {
const mouseMoveEvent = (_event /* eslint-disable-line no-unused-vars */) => {
const now = Date.now();
if (now - lastMoveTime < THROTTLE_MS) {
// throttle 기간 내에는 hideCursor만 재시작
@@ -810,9 +788,7 @@ function AppBase(props) {
let userDataChanged = false;
if (JSON.stringify(loginUserDataRef.current) !== JSON.stringify(loginUserData)) {
userDataChanged = true;
}
if (!httpHeader || !deviceId) {
} else if (userDataChanged || httpHeaderRef.current === null) {
} else if (userDataChanged || httpHeaderRef.current === null) {
//계정정보 변경시 또는 초기 로딩시
if (!httpHeader) {
dispatch(

View File

@@ -1,4 +1,4 @@
import { useDispatch } from "react-redux";
import { updateHomeInfo } from "../actions/homeActions";
import { pushPanel } from "../actions/panelActions";
import {
@@ -11,7 +11,7 @@ import { SpotlightIds } from "../utils/SpotlightIds";
import { sendLogTotalRecommend } from "../actions/logActions";
//V2_진입경로코드_진입경로명_MT_노출순번
export const handleDeepLink = (contentTarget) => (dispatch, getState) => {
export const handleDeepLink = (contentTarget) => (dispatch, _getState) => {
console.log("[handleDeepLink] ~ contentTarget: ", contentTarget);
let linkTpCd; // 진입경로코드
let linkTpNm; // 진입경로명
@@ -21,7 +21,6 @@ export const handleDeepLink = (contentTarget) => (dispatch, getState) => {
let curationId; // 큐레이션아이디
let showId; // 방송아이디
let chanId; // 채널아이디
let expsOrd; // 노출순번
let grNumber; // 그룹번호
let evntId; // 이벤트아이디
let lgCatCd; // LG카테고리Code
@@ -65,7 +64,6 @@ export const handleDeepLink = (contentTarget) => (dispatch, getState) => {
// V3_진입경로코드_진입경로명_PD_파트너아이디_상품아이디_노출순번_큐레이션아이디
patnrId = tokens[4]; // 파트너아이디
prdtId = tokens[5]; // 상품아이디
expsOrd = tokens[6]; // 노출순번
curationId = tokens[7]; // 큐레이션아이디
panelName = panel_names.DETAIL_PANEL;
deeplinkPanel = "Product Detaoil";
@@ -81,7 +79,6 @@ export const handleDeepLink = (contentTarget) => (dispatch, getState) => {
// V3_진입경로코드_진입경로명_LS_파트너아이디_채널아이디_노출순번_큐레이션아이디
patnrId = tokens[4]; // 파트너아이디
chanId = tokens[5]; // 채널아이디
expsOrd = tokens[6]; // 노출순번
curationId = tokens[7]; // 큐레이션아이디
panelName = panel_names.PLAYER_PANEL;
deeplinkPanel = "Live Show";
@@ -98,7 +95,6 @@ export const handleDeepLink = (contentTarget) => (dispatch, getState) => {
// V3_진입경로코드_진입경로명_VS_파트너아이디_방송아이디_노출순번_큐레이션아이디
patnrId = tokens[4]; // 파트너아이디
showId = tokens[5]; // 방송아이디
expsOrd = tokens[6]; // 노출순번
curationId = tokens[7]; // 큐레이션아이디
panelName = panel_names.PLAYER_PANEL;
deeplinkPanel = "VOD Show";
@@ -119,7 +115,6 @@ export const handleDeepLink = (contentTarget) => (dispatch, getState) => {
patnrId = tokens[4]; // 파트너아이디
curationId = tokens[5]; // 큐레이션아이디\
prdtId = tokens[6]; // 상품아이디
expsOrd = tokens[7]; // 노출순번
grNumber = tokens[8]; // 그룹번호
panelName = panel_names.DETAIL_PANEL;
deeplinkPanel = "Theme Detail";
@@ -140,7 +135,6 @@ export const handleDeepLink = (contentTarget) => (dispatch, getState) => {
patnrId = tokens[4]; // 파트너아이디
curationId = tokens[5]; // 큐레이션아이디
expsOrd = tokens[6]; // 노출순번
panelName = panel_names.DETAIL_PANEL;
deeplinkPanel = "Hotel Detail";
panelInfo = {
@@ -157,7 +151,6 @@ export const handleDeepLink = (contentTarget) => (dispatch, getState) => {
patnrId = tokens[4]; // 파트너아이디
curationId = tokens[5]; // 큐레이션아이디
expsOrd = tokens[6]; // 노출순번
panelName = panel_names.HOT_PICKS_PANEL;
deeplinkPanel = "Hot Picks";
panelInfo = {

View File

@@ -5,7 +5,7 @@ import { createDebugHelpers } from '../utils/debug';
// 디버그 헬퍼 설정
const DEBUG_MODE = false;
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
const { dlog, derror } = createDebugHelpers(DEBUG_MODE);
export const addMainIndex = (index) => ({
type: types.ADD_MAIN_INDEX,

View File

@@ -5,7 +5,7 @@ import { createDebugHelpers } from '../utils/debug';
// 디버그 헬퍼 설정
const DEBUG_MODE = false;
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
const { dlog, derror } = createDebugHelpers(DEBUG_MODE);
// IF-LGSP-328 : 회원 Billing Address 조회
export const getMyInfoBillingSearch = (props) => (dispatch, getState) => {

View File

@@ -6,7 +6,7 @@ import { createDebugHelpers } from '../utils/debug';
// 디버그 헬퍼 설정
const DEBUG_MODE = false;
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
const { derror } = createDebugHelpers(DEBUG_MODE);
// Featured Brands 정보 조회 IF-LGSP-304
export const getBrandList = () => (dispatch, getState) => {

View File

@@ -6,7 +6,7 @@ import { createDebugHelpers } from '../utils/debug';
// 디버그 헬퍼 설정
const DEBUG_MODE = false;
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
const { dlog, derror } = createDebugHelpers(DEBUG_MODE);
// 회원 주문 취소/반품/교환 사유 조회 (IF-LGSP-347)
export const getMyinfoOrderCancelColumnsSearch = (params, callback) => (dispatch, getState) => {

View File

@@ -5,7 +5,7 @@ import { createDebugHelpers } from '../utils/debug';
// 디버그 헬퍼 설정
const DEBUG_MODE = false;
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
const { dlog, derror } = createDebugHelpers(DEBUG_MODE);
// 회원의 등록 카드 정보 조회 IF-LGSP-332
export const getMyInfoCardSearch = (props) => (dispatch, getState) => {

View File

@@ -6,7 +6,7 @@ import { createDebugHelpers } from '../utils/debug';
// 디버그 헬퍼 설정
const DEBUG_MODE = false;
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
const { dlog, derror } = createDebugHelpers(DEBUG_MODE);
// 회원 체크아웃 정보 조회 IF-LGSP-345
export const getMyInfoCheckoutInfo = (props, callback) => (dispatch, getState) => {

View File

@@ -7,9 +7,7 @@ import Spotlight from '@enact/spotlight';
import appinfo from '../../webos-meta/appinfo.json';
import appinfo35 from '../../webos-meta/appinfo35.json';
import appinfo79 from '../../webos-meta/appinfo79.json';
import { handleBypassLink } from '../App/bypassLinkHandler';
import * as lunaSend from '../lunaSend';
import { initialLocalSettings } from '../reducers/localSettingsReducer';
import * as Config from '../utils/Config';
import * as HelperMethods from '../utils/helperMethods';
import { types } from './actionTypes';
@@ -17,7 +15,7 @@ import { createDebugHelpers } from '../utils/debug';
// 디버그 헬퍼 설정
const DEBUG_MODE = false;
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
const { dlog, derror } = createDebugHelpers(DEBUG_MODE);
// =======
// import appinfo from "../../webos-meta/appinfo.json";
// import appinfo35 from "../../webos-meta/appinfo35.json";
@@ -94,7 +92,7 @@ export const toggleOptionalTermsConfirm = (selected) => ({
payload: selected,
});
export const setExitApp = () => (dispatch, getState) => {
export const setExitApp = () => (dispatch) => {
dispatch({ type: types.SET_EXIT_APP });
dlog('Exiting App...');
@@ -116,7 +114,7 @@ export const loadingComplete = (status) => ({
payload: status,
});
export const alertToast = (payload) => (dispatch, getState) => {
export const alertToast = (payload) => (dispatch) => {
if (typeof window === 'object' && !window.PalmSystem) {
dispatch(changeAppStatus({ toast: true, toastText: payload }));
} else {
@@ -124,13 +122,13 @@ export const alertToast = (payload) => (dispatch, getState) => {
}
};
export const getSystemSettings = () => (dispatch, getState) => {
export const getSystemSettings = () => (dispatch) => {
dlog('getSystemSettings ');
lunaSend.getSystemSettings(
{ category: 'caption', keys: ['captionEnable'] },
{
onSuccess: (res) => {},
onFailure: (err) => {},
onSuccess: () => {},
onFailure: () => {},
onComplete: (res) => {
dlog('getSystemSettings onComplete', res);
if (res && res.settings) {
@@ -148,7 +146,7 @@ export const getSystemSettings = () => (dispatch, getState) => {
);
};
export const getHttpHeaderForServiceRequest = (onComplete) => (dispatch, getState) => {
export const getHttpHeaderForServiceRequest = () => (dispatch, getState) => {
dlog('getHttpHeaderForServiceRequest ');
const { serverType, ricCodeSetting, languageSetting } = getState().localSettings;
lunaSend.getHttpHeaderForServiceRequest({
@@ -267,10 +265,9 @@ export const getHttpHeaderForServiceRequest = (onComplete) => (dispatch, getStat
const mbrNo = res['X-User-Number'];
lunaSend.getLoginUserData(parameters, {
onSuccess: (res) => {
const userId = res.id ?? '';
const userNumber = res.lastSignInUserNo;
const profileNick = res.profileNick || userId.split('@')[0];
onSuccess: (loginRes) => {
const userId = loginRes.id ?? '';
const profileNick = loginRes.profileNick || userId.split('@')[0];
dispatch(
getLoginUserData({
userId,
@@ -288,7 +285,7 @@ export const getHttpHeaderForServiceRequest = (onComplete) => (dispatch, getStat
});
};
export const getDeviceId = (onComplete) => (dispatch, getState) => {
export const getDeviceId = (onComplete) => (dispatch) => {
lunaSend.getDeviceId(
{ idType: ['LGUDID'] },
{
@@ -466,7 +463,7 @@ export const setFocus = (spotlightId) => ({
payload: spotlightId,
});
export const focusElement = (spotlightId) => (dispatch, getState) => {
export const focusElement = (spotlightId) => (dispatch) => {
dispatch(setFocus(spotlightId));
if (typeof window === 'object') {
@@ -488,7 +485,7 @@ export const cancelFocusElement = () => () => {
let broadcastTimer = null;
export const sendBroadCast =
({ type, moreInfo }) =>
(dispatch, getState) => {
(dispatch) => {
clearTimeout(broadcastTimer);
dispatch(changeBroadcastEvent({ type, moreInfo }));
broadcastTimer = setTimeout(() => {
@@ -545,7 +542,7 @@ export const addReservation = (data) => (dispatch) => {
});
};
export const deleteReservationCallback = (scheduleIdList) => (dispatch) => {
export const deleteReservationCallback = (scheduleIdList) => () => {
lunaSend.deleteReservationCallback(scheduleIdList, {
onSuccess: (res) => {
// dispatch(alertToast("success" + JSON.stringify(res)));
@@ -636,8 +633,8 @@ export const showError =
export const deleteOldDb8Datas = () => (dispatch) => {
for (let i = 1; i < 10; i++) {
lunaSend.deleteOldDb8(i, {
onSuccess: (res) => {},
onFailure: (err) => {},
onSuccess: () => {},
onFailure: () => {},
});
}
dispatch(changeLocalSettings({ oldDb8Deleted: true }));
@@ -683,7 +680,7 @@ let updateNetworkStateJob = new Job((dispatch, connected) => {
dispatch(changeAppStatus({ isInternetConnected: connected }));
});
export const getConnectionStatus = () => (dispatch, getState) => {
export const getConnectionStatus = () => (dispatch) => {
lunaSend.getConnectionStatus({
onSuccess: (res) => {
dlog('lunasend getConnectionStatus', res);
@@ -712,7 +709,7 @@ export const getConnectionStatus = () => (dispatch, getState) => {
};
// macAddress
export const getConnectionInfo = () => (dispatch, getState) => {
export const getConnectionInfo = () => (dispatch) => {
lunaSend.getConnectionInfo({
onSuccess: (res) => {
dlog('lunasend getConnectionStatus', res);
@@ -734,7 +731,7 @@ export const getConnectionInfo = () => (dispatch, getState) => {
});
};
export const disableNotification = () => (dispatch, getState) => {
export const disableNotification = () => {
lunaSend.disableNotification({
onSuccess: (res) => {
dlog('lunasend disable notification success', res);
@@ -748,7 +745,7 @@ export const disableNotification = () => (dispatch, getState) => {
});
};
export const enableNotification = () => (dispatch, getState) => {
export const enableNotification = () => {
lunaSend.enableNotification({
onSuccess: (res) => {
dlog('lunasend enable notification success', res);

View File

@@ -31,7 +31,7 @@ export const convertPdfToImage =
const timeoutError = new Error(
`Conversion timeout after ${timeout}ms (attempt ${attempts})`
);
dwarn(`⏱️ [EnergyLabel] Timeout on attempt ${attempts}:`, timeoutError.message);
void dwarn(`⏱️ [EnergyLabel] Timeout on attempt ${attempts}:`, timeoutError.message);
// 재시도 가능한 경우
if (attempts < maxRetries + 1) {
@@ -39,7 +39,7 @@ export const convertPdfToImage =
attemptConversion();
} else {
// 최종 실패
derror(`❌ [EnergyLabel] Final failure after ${attempts} attempts:`, pdfUrl);
void derror(`❌ [EnergyLabel] Final failure after ${attempts} attempts:`, pdfUrl);
dispatch({
type: types.CONVERT_PDF_TO_IMAGE_FAILURE,
payload: { pdfUrl, error: timeoutError },
@@ -64,17 +64,14 @@ export const convertPdfToImage =
if (retCode !== undefined && retCode !== 0 && retCode !== '0') {
const error = new Error(`API Error: retCode=${retCode}`);
dwarn(`⚠️ [EnergyLabel] API returned error on attempt ${attempts}:`, retCode);
void dwarn(`⚠️ [EnergyLabel] API returned error on attempt ${attempts}:`, retCode);
// retCode 에러도 재시도
if (attempts < maxRetries + 1) {
dlog(`🔄 [EnergyLabel] Retrying due to API error... (${attempts}/${maxRetries + 1})`);
void dlog(`🔄 [EnergyLabel] Retrying due to API error... (${attempts}/${maxRetries + 1})`);
attemptConversion();
} else {
derror(
`❌ [EnergyLabel] Final failure after ${attempts} attempts (API error):`,
pdfUrl
);
void derror(`❌ [EnergyLabel] Final failure after ${attempts} attempts (API error):`, pdfUrl);
dispatch({
type: types.CONVERT_PDF_TO_IMAGE_FAILURE,
payload: { pdfUrl, error },
@@ -111,7 +108,7 @@ export const convertPdfToImage =
imageUrl = URL.createObjectURL(blob);
}
dlog(`✅ [EnergyLabel] Conversion successful on attempt ${attempts}:`, pdfUrl);
void dlog(`✅ [EnergyLabel] Conversion successful on attempt ${attempts}:`, pdfUrl);
dispatch({
type: types.CONVERT_PDF_TO_IMAGE_SUCCESS,
payload: { pdfUrl, imageUrl },
@@ -119,16 +116,16 @@ export const convertPdfToImage =
callback && callback(null, imageUrl);
} catch (error) {
derror(`❌ [EnergyLabel] Image creation failed on attempt ${attempts}:`, error);
void derror(`❌ [EnergyLabel] Image creation failed on attempt ${attempts}:`, error);
// 이미지 생성 실패도 재시도
if (attempts < maxRetries + 1) {
dlog(
`🔄 [EnergyLabel] Retrying due to image creation error... (${attempts}/${maxRetries + 1})`
);
void dlog(
`🔄 [EnergyLabel] Retrying due to image creation error... (${attempts}/${maxRetries + 1})`
);
attemptConversion();
} else {
derror(
void derror(
`❌ [EnergyLabel] Final failure after ${attempts} attempts (image error):`,
pdfUrl
);
@@ -147,14 +144,14 @@ export const convertPdfToImage =
timeoutId = null;
}
dwarn(`⚠️ [EnergyLabel] Network error on attempt ${attempts}:`, error.message);
void dwarn(`⚠️ [EnergyLabel] Network error on attempt ${attempts}:`, error.message);
// 네트워크 에러도 재시도
if (attempts < maxRetries + 1) {
dlog(`🔄 [EnergyLabel] Retrying due to network error... (${attempts}/${maxRetries + 1})`);
void dlog(`🔄 [EnergyLabel] Retrying due to network error... (${attempts}/${maxRetries + 1})`);
attemptConversion();
} else {
derror(
void derror(
`❌ [EnergyLabel] Final failure after ${attempts} attempts (network error):`,
pdfUrl
);
@@ -188,7 +185,7 @@ export const convertPdfToImage =
* @param {Array<string>} pdfUrls - 변환할 PDF URL 배열
* @param {function} callback - 완료 후 실행할 콜백 (errors, results)
*/
export const convertMultiplePdfs = (pdfUrls, callback) => async (dispatch, getState) => {
export const convertMultiplePdfs = (pdfUrls, callback) => async (dispatch) => {
if (!pdfUrls || pdfUrls.length === 0) {
callback && callback(null, []);
return;

View File

@@ -6,7 +6,7 @@ import { createDebugHelpers } from '../utils/debug';
// 디버그 헬퍼 설정
const DEBUG_MODE = false;
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
const { dlog, derror } = createDebugHelpers(DEBUG_MODE);
// IF-LGSP-339 : 회원 다운로드 쿠폰 정보 조회
export const getProductCouponInfo = (props) => (dispatch, getState) => {

View File

@@ -348,8 +348,14 @@ export const TAxiosAdvancedPromise = (
clearTimeout(timeoutId);
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)
// 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;
// 재시도 로직
if (attempts < maxAttempts) {
if (attempts < maxAttempts && !isTokenError) {
console.log(`Retrying in ${retryDelay}ms... (${attempts}/${maxAttempts})`);
setTimeout(() => {
attemptRequest();

View File

@@ -19,7 +19,6 @@
margin-left: 130px;
margin-right: 130px;
flex: 1 0 auto;
width: 1540px;
height: 6px;
&.videoVertical {
@@ -31,10 +30,11 @@
}
.mediaSlider {
margin: 0 @slider-padding-h;
margin: 0 0 0 @slider-padding-h;
padding: @slider-padding-v 0;
height: @sand-mediaplayer-slider-height;
right: 154px;
width: 1466px;
// Add a tap area that extends to the edges of the screen, to make the slider more accessible
&::before {
content: "";

View File

@@ -7,22 +7,28 @@
position: absolute;
font-family: @baseFont;
width: 100%;
top: 22px;
right: 30px;
right: 90px;
bottom: -5px;
font-size: 24px;
font-weight: bold;
line-height: 30px;
text-align: right;
letter-spacing: -1px;
.separator {
position: absolute;
right: 110px;
right: 105px;
bottom: -5px;
}
.currentTime {
position: absolute;
right: 140px;
right: 130px;
bottom: -5px;
}
.totalTime {
position: absolute;
bottom: -5px;
right:0px;
}
> * {
color: #fff;
}

View File

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

View File

@@ -0,0 +1,42 @@
import React from "react";
import { scaleW } from "../../../utils/helperMethods";
import useConvertThemeColor from "./useConvertThemeColor";
const NbcuIcon = ({ iconType = "normal" }) => {
const themeColor = useConvertThemeColor({ iconType });
return (
<svg
width={scaleW(48)}
height={scaleW(48)}
viewBox="0 0 48 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="24" cy="24" r="22" fill={themeColor} opacity="0.1" stroke={themeColor} strokeWidth="0.5" />
<text
x="24"
y="32"
textAnchor="middle"
fill={themeColor}
fontSize="18"
fontWeight="bold"
fontFamily="Arial, sans-serif"
>
NBC
</text>
<text
x="24"
y="40"
textAnchor="middle"
fill={themeColor}
fontSize="10"
fontFamily="Arial, sans-serif"
>
U
</text>
</svg>
);
};
export default NbcuIcon;

View File

@@ -776,6 +776,7 @@ const VideoPlayerBase = class extends React.Component {
this.sliderKnobProportion = 0;
this.mediaControlsSpotlightId = props.spotlightId + '_mediaControls';
this.jumpButtonPressed = null;
this.focusTimer = null;
// Re-render-necessary State
this.state = {
@@ -1038,6 +1039,7 @@ const VideoPlayerBase = class extends React.Component {
this.stopDelayedTitleHide();
this.stopDelayedFeedbackHide();
this.stopDelayedMiniFeedbackHide();
if (this.focusTimer) clearTimeout(this.focusTimer);
this.announceJob.stop();
this.renderBottomControl.stop();
this.slider5WayPressJob.stop();
@@ -2603,11 +2605,11 @@ const VideoPlayerBase = class extends React.Component {
this.showControls();
if (this.state.lastFocusedTarget) {
setTimeout(() => {
this.focusTimer = setTimeout(() => {
Spotlight.focus(this.state.lastFocusedTarget);
});
} else {
setTimeout(() => {
this.focusTimer = setTimeout(() => {
Spotlight.focus(SpotlightIds.PLAYER_TAB_BUTTON);
});
}

View File

@@ -692,10 +692,10 @@
// display: flex;
position: relative;
align-items: center;
margin-left: 60px;
margin-right: 59px;
height: 70px;
bottom: -20px;
width:1800px;
margin-left:60px;
bottom:92px;
> *:first-child {
text-align: right;
}

View File

@@ -38,11 +38,19 @@ import {
getProductCouponSearch,
getProductCouponTotDownload,
} from '../../../actions/couponActions.js';
import {
sendLogDetail,
sendLogGNB,
sendLogProductDetail,
sendLogShopByMobile,
sendLogTotalRecommend,
} from '../../../actions/logActions';
// import { pushPanel } from '../../../actions/panelActions';
import {
minimizeModalMedia,
restoreModalMedia,
} from '../../../actions/mediaActions';
import { updatePanel } from '../../../actions/panelActions';
import { pauseFullscreenVideo } from '../../../actions/playActions';
import { resetShowAllReviews } from '../../../actions/productActions';
import {
@@ -59,7 +67,12 @@ import TVirtualGridList
import useReviews from '../../../hooks/useReviews/useReviews';
import useScrollTo from '../../../hooks/useScrollTo';
import { BUYNOW_CONFIG } from '../../../utils/BuyNowConfig';
import { panel_names } from '../../../utils/Config';
import {
LOG_CONTEXT_NAME,
LOG_MESSAGE_ID,
LOG_TP_NO,
panel_names,
} from '../../../utils/Config';
import * as Config from '../../../utils/Config.js';
import {
andThen,
@@ -76,7 +89,10 @@ import {
tap,
when,
} from '../../../utils/fp';
import { $L } from '../../../utils/helperMethods';
import {
$L,
formatGMTString,
} from '../../../utils/helperMethods';
import { SpotlightIds } from '../../../utils/SpotlightIds';
import ShowUserReviews from '../../UserReview/ShowUserReviews';
// import CustomScrollbar from '../components/CustomScrollbar/CustomScrollbar';
@@ -242,6 +258,7 @@ export default function ProductAllSection({
// Redux 상태
const webOSVersion = useSelector((state) => state.common.appStatus.webOSVersion);
const groupInfos = useSelector((state) => state.product.groupInfo);
const nowMenu = useSelector((state) => state.common.menu.nowMenu);
// YouMayLike 데이터는 API 응답 시간이 걸리므로 직접 구독
const youmaylikeData = useSelector((state) => state.main.youmaylikeData);
@@ -263,6 +280,18 @@ export default function ProductAllSection({
const [isShowQRCode, setIsShowQRCode] = useState(true);
const timerRef = useRef(null);
// sendLogGNB용 entryMenu
const entryMenuRef = useRef(null);
// 출처 정보 통합 (향후 확장성 대비)
// YouMayLike 상품이 아닐 경우 fromPanel을 초기화하여 오기 방지
const fromPanel = useMemo(() => ({
fromYouMayLike: panelInfo?.fromPanel?.fromYouMayLike || false,
// 향후 다른 출처 플래그들 추가 가능
// fromRecommendation: panelInfo?.fromPanel?.fromRecommendation || false,
// fromSearch: panelInfo?.fromPanel?.fromSearch || false,
}), [panelInfo?.fromPanel?.fromYouMayLike]);
//구매 하단 토스트 노출 확인을 위한 용도
const [openToast, setOpenToast] = useState(false);
@@ -652,6 +681,116 @@ export default function ProductAllSection({
dispatch(resetShowAllReviews());
}, []); // 빈 dependency array = 마운트 시에만 실행
// 제품 상세 버튼 클릭 핸들러 - Source의 handleIndicatorOptions와 동일한 기능
const handleIndicatorOptions = useCallback(() => {
if (productData && Object.keys(productData).length > 0) {
// sendLogDetail - 제품 상세 버튼 클릭 로깅 (Source와 동일)
const detailLogParams = {
curationId: productData?.curationId ?? "",
curationNm: productData?.curationNm ?? "",
inDt: "",
linkTpCd: panelInfo?.linkTpCd ?? "",
logTpNo: LOG_TP_NO.DETAIL.DETAIL_BUTTON_CLICK,
patncNm: productData?.patncNm ?? "",
patnrId: productData?.patnrId ?? "",
};
dispatch(sendLogDetail(detailLogParams));
// sendLogTotalRecommend - 추천 버튼 클릭 로깅 (Source와 동일)
let menuType;
if (isTravelProductVisible) {
menuType = Config.LOG_MENU.DETAIL_PAGE_TRAVEL_THEME_DETAIL;
} else if (isGroupProductVisible) {
menuType = Config.LOG_MENU.DETAIL_PAGE_GROUP_DETAIL;
} else if (isBillingProductVisible) {
menuType = Config.LOG_MENU.DETAIL_PAGE_BILLING_PRODUCT_DETAIL;
} else {
menuType = Config.LOG_MENU.DETAIL_PAGE_PRODUCT_DETAIL;
}
dispatch(sendLogTotalRecommend({
menu: menuType,
buttonTitle: "DESCRIPTION",
contextName: LOG_CONTEXT_NAME.DETAILPAGE,
messageId: LOG_MESSAGE_ID.BUTTONCLICK,
}));
}
}, [productData, panelInfo, isBillingProductVisible, isGroupProductVisible, isTravelProductVisible]);
// sendLogGNB 로깅 - Source의 DetailPanel 컴포넌트들과 동일한 패턴
useEffect(() => {
if (!entryMenuRef.current) entryMenuRef.current = nowMenu;
// BUY NOW 버튼 활성화 상태에 따른 메뉴 결정 (Source SingleProduct vs UnableProduct 패턴)
let baseMenu;
if (isTravelProductVisible) {
baseMenu = Config.LOG_MENU.DETAIL_PAGE_TRAVEL_THEME_DETAIL;
} else if (isGroupProductVisible) {
baseMenu = Config.LOG_MENU.DETAIL_PAGE_GROUP_DETAIL;
} else if (isBillingProductVisible) {
// BUY NOW 버튼 활성화 = SingleProduct
baseMenu = Config.LOG_MENU.DETAIL_PAGE_BILLING_PRODUCT_DETAIL;
} else {
// BUY NOW 버튼 비활성화 = UnableProduct
baseMenu = Config.LOG_MENU.DETAIL_PAGE_PRODUCT_DETAIL;
}
// YouMayLike에서 상품 선택 시 메뉴 변경 (Source의 isYouMayLikeOpened와 동일 패턴)
const menu = (fromPanel?.fromYouMayLike !== undefined && fromPanel?.fromYouMayLike === true)
? `${baseMenu}/${Config.LOG_MENU.DETAIL_PAGE_YOU_MAY_LIKE}`
: baseMenu;
dispatch(sendLogGNB(menu));
// sendLogGNB 전송 후 플래그 초기화 (1회 사용 후 비활성화)
if (fromPanel?.fromYouMayLike === true) {
dispatch(updatePanel({
name: panel_names.DETAIL_PANEL,
panelInfo: {
...panelInfo,
fromPanel: {
fromYouMayLike: false // 플래그 초기화
}
}
}));
}
}, [fromPanel?.fromYouMayLike, isBillingProductVisible, isUnavailableProductVisible, isGroupProductVisible, isTravelProductVisible]); // BUY NOW 상태 변경 시 재실행
// sendLogProductDetail 로깅 - Source의 productData 변경 감지와 동일한 패턴
useEffect(() => {
if (productData && Object.keys(productData).length > 0) {
const params = {
befPrice: productData?.priceInfo?.split("|")[0],
curationId: productData?.curationId ?? "",
curationNm: productData?.curationNm ?? "",
entryMenu: entryMenuRef.current,
expsOrd: "1",
inDt: formatGMTString(new Date()),
lastPrice: productData?.priceInfo?.split("|")[1],
lgCatCd: productData?.catCd ?? "",
lgCatNm: productData?.catNm ?? "",
linkTpCd: panelInfo?.linkTpCd ?? "",
logTpNo: isTravelProductVisible
? Config.LOG_TP_NO.PRODUCT.TRAVEL_DETAIL
: isGroupProductVisible
? Config.LOG_TP_NO.PRODUCT.GROUP_DETAIL
: isBillingProductVisible
? Config.LOG_TP_NO.PRODUCT.BILLING_PRODUCT_DETAIL
: Config.LOG_TP_NO.PRODUCT.PRODUCT_DETAIL,
patncNm: productData?.patncNm ?? "",
patnrId: productData?.patnrId ?? "",
prdtId: productData?.prdtId ?? "",
prdtNm: productData?.prdtNm ?? "",
revwGrd: productData?.revwGrd ?? "",
rewdAplyFlag: productData.priceInfo?.split("|")[2],
tsvFlag: productData?.todaySpclFlag ?? "",
};
return () => dispatch(sendLogProductDetail(params));
}
}, [productData, entryMenuRef.current, panelInfo?.linkTpCd, isBillingProductVisible, isGroupProductVisible, isTravelProductVisible]); // productData 변경 시 재실행
// [251115] 주석 처리: MediaPanel에서 이미 포커스 이동을 처리하므로
// ProductAllSection의 자동 포커스는 포커스 탈취를 일으킬 수 있음
// useEffect(() => {
@@ -674,6 +813,35 @@ export default function ProductAllSection({
// console.log('[BuyNow] Buy Now button clicked');
e.stopPropagation();
// 🚀 SingleOption.jsx의 sendLogTotalRecommend 로직 추가
if (productData && Object.keys(productData).length > 0) {
const { priceInfo, patncNm, prdtId, prdtNm, brndNm, catNm, showId, showNm } = productData;
const regularPrice = priceInfo?.split("|")[0];
const discountPrice = priceInfo?.split("|")[1];
const discountRate = priceInfo?.split("|")[4];
// Option 정보는 현재 선택된 옵션이 없으므로 기본값 사용
const prodOptCval = ""; // 실제로는 선택된 옵션 값이 들어가야 함
dispatch(
sendLogTotalRecommend({
nowMenu: nowMenu,
productId: prdtId,
productTitle: prdtNm,
partner: patncNm,
price: discountRate ? discountPrice : regularPrice,
discount: discountRate,
brand: brndNm,
productOption: prodOptCval,
category: catNm,
contextName: Config.LOG_CONTEXT_NAME.DETAILPAGE,
messageId: Config.LOG_MESSAGE_ID.BUY_NOW,
showId: showId ?? "",
showNm: showNm ?? "",
})
);
}
// console.log('[ProductAllSection] 🛒 BUY NOW clicked - productData:', {
// prdtId: productData?.prdtId,
// patnrId: productData?.patnrId,
@@ -705,7 +873,7 @@ export default function ProductAllSection({
setOpenToast(true);
}
},
[dispatch, productData, openToast]
[dispatch, productData, openToast, nowMenu]
);
//닫히도록
@@ -926,7 +1094,32 @@ export default function ProductAllSection({
}, [hasVideo, productVideoVersion]);
const handleShopByMobileOpen = useCallback(
pipe(() => true, setMobileSendPopupOpen),
pipe(() => {
// sendLogShopByMobile - Source와 동일한 로깅 추가
if (productData && Object.keys(productData).length > 0) {
const { priceInfo, patncNm, patnrId, prdtId, prdtNm, brndNm, catNm } = productData;
const regularPrice = priceInfo?.split("|")[0];
const discountPrice = priceInfo?.split("|")[1];
const discountRate = priceInfo?.split("|")[4];
const logParams = {
prdtId,
patnrId,
prdtNm,
patncNm,
brndNm,
catNm,
regularPrice,
discountPrice,
discountRate,
shopByMobileTime: new Date().toISOString(),
};
dispatch(sendLogShopByMobile(logParams));
}
setMobileSendPopupOpen(true); // 팝업 열기
}, setMobileSendPopupOpen),
[]
);
@@ -998,7 +1191,10 @@ export default function ProductAllSection({
const handleProductDetailsClick = useCallback(() => {
dispatch(minimizeModalMedia());
scrollToSection('scroll-marker-product-details');
}, [scrollToSection, dispatch]);
// Source의 handleIndicatorOptions와 동일한 로깅 기능 추가
handleIndicatorOptions();
}, [scrollToSection, dispatch, handleIndicatorOptions]);
const handleYouMayAlsoLikeClick = useCallback(() => {
dispatch(minimizeModalMedia());
@@ -1592,6 +1788,7 @@ export default function ProductAllSection({
onScrollToImages={handleScrollToImagesV1}
onFocus={() => {}}
data-spotlight-id="product-video-player-container"
disclaimer={productData.disclaimer}
/>
) : (
<ProductVideoV2

View File

@@ -215,3 +215,32 @@
border-radius: 0;
}
}
.notice {
width: 100%;
height: 54px;
background: #000000;
.flex(@justifyCenter:flex-start);
padding: 6px 18px 18px 18px;
position: absolute;
bottom: 0;
border-radius: 0 0 12px 12px;
.marquee {
width: 100%;
height: 100%;
}
img {
width: 18px;
height: 18px;
margin: 10px 12px 0 0;
object-fit: contain;
}
span {
line-height: normal;
letter-spacing: normal;
text-align: left;
.font(@fontFamily:@baseFont, @fontSize:20px);
color: @COLOR_GRAY04;
}
}

View File

@@ -1,17 +1,31 @@
import React, { useCallback, useMemo, useState, useEffect, useRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import {
useDispatch,
useSelector,
} from 'react-redux';
import Marquee from '@enact/sandstone/Marquee';
import Spotlight from '@enact/spotlight';
import Spottable from '@enact/spotlight/Spottable';
import playImg from '../../../../../assets/images/btn/btn-play-thumb-nor.png';
import ic_warning from '../../../../../assets/images/icons/ic-warning@3x.png';
import {
startMediaPlayer,
finishMediaPreview,
switchMediaToFullscreen,
minimizeModalMedia,
restoreModalMedia,
startMediaPlayer,
switchMediaToFullscreen,
} from '../../../../actions/mediaActions';
import CustomImage from '../../../../components/CustomImage/CustomImage';
import { panel_names } from '../../../../utils/Config';
import playImg from '../../../../../assets/images/btn/btn-play-thumb-nor.png';
import css from './ProductVideo.module.less';
const SpottableComponent = Spottable('div');
@@ -25,6 +39,7 @@ export default function ProductVideo({
autoPlay = false, // 자동 재생 여부
continuousPlay = false, // 반복 재생 여부
onFocus = null, // 외부에서 전달된 포커스 핸들러
disclaimer,
}) {
const dispatch = useDispatch();
@@ -315,6 +330,12 @@ export default function ProductVideo({
<img src={playImg} alt="재생" />
</div>
</div>
<div className={css.notice}>
<Marquee className={css.marquee} marqueeOn="render">
<img src={ic_warning} alt={disclaimer} />
<span>{disclaimer}</span>
</Marquee>
</div>
</SpottableComponent>
);
}

View File

@@ -169,6 +169,9 @@ export default function YouMayAlsoLike({
prdtId,
launchedFromPlayer: launchedFromPlayer,
bgVideoInfo: bgVideoInfo, // 백그라운드 비디오 정보 유지
fromPanel: {
fromYouMayLike: true, // YouMayLike에서 선택된 상품임을 표시
}, // 출처 정보 통합 객체
},
})
);

View File

@@ -282,6 +282,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
const watchIntervalLive = useRef(null);
const watchIntervalVod = useRef(null);
const watchIntervalMedia = useRef(null);
const timeoutRef = useRef(null);
// useEffect(() => {
// console.log("###videoLoaded", videoLoaded);
// if (nowMenu) {
@@ -291,8 +292,15 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
if (liveShowInfos && liveShowInfos.length > 0) {
const panelInfoChanId = panelInfo?.chanId;
const isLive = panelInfo?.shptmBanrTpNm === 'LIVE';
const isModal = panelInfo?.modal;
if (isLive) {
// live full 화면에서 modal 전환시 로그 전송 추가
if (isModal) {
dispatch(sendLogGNB(Config.LOG_MENU.FULL));
prevNowMenuRef.current = nowMenuRef.current;
return () => dispatch(sendLogGNB(prevNowMenuRef.current));
}
const liveShowInfo = liveShowInfos //
.find(({ chanId }) => panelInfoChanId === chanId);
@@ -303,7 +311,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
}
return {};
}, [liveShowInfos, panelInfo?.chanId, panelInfo?.shptmBanrTpNm]);
}, [liveShowInfos, panelInfo?.chanId, panelInfo?.shptmBanrTpNm, panelInfo?.modal]);
const currentVODShowInfo = useMemo(() => {
if (showDetailInfo && showDetailInfo.length > 0) {
@@ -327,7 +335,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
prevNowMenuRef.current = nowMenuRef.current;
return () => dispatch(sendLogGNB(prevNowMenuRef.current));
} else if (panelInfo?.modal) {
} else if (panelInfo?.modal && panelInfo?.shptmBanrTpNm !== 'LIVE') {
dispatch(sendLogGNB(entryMenu));
}
}, [panelInfo?.modal, panelInfo?.shptmBanrTpNm]);
@@ -619,7 +627,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
if (lastFocusedTargetId) {
// ShopNowContents가 렌더링될 때까지 대기 후 포커스 복원
setTimeout(() => {
timeoutRef.current = setTimeout(() => {
dlog('[PlayerPanel] 🔍 800ms 후 포커스 복원 시도:', lastFocusedTargetId);
Spotlight.focus(lastFocusedTargetId);
}, 800);
@@ -710,6 +718,13 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
panelInfo?.modal &&
liveLogParamsRef.current?.showId === panelInfo?.showId
) {
dlog('[PlayerPanel] 📡 LIVE Modal Log Ready and Conditions Met:', {
isModalLiveLogReady: logStatus.isModalLiveLogReady,
isOnTop,
isModal: panelInfo?.modal,
showIdMatch: liveLogParamsRef.current?.showId === panelInfo?.showId,
logParams: liveLogParamsRef.current,
});
let watchStrtDt = formatGMTString(new Date());
watchIntervalLive.current = setInterval(() => {
@@ -728,6 +743,10 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
isModalLiveLogReady: false,
}));
clearInterval(watchIntervalLive.current);
dlog('[PlayerPanel] 🚀 Dispatching LIVE Modal Log:', {
logParams: liveLogParamsRef.current,
watchStrtDt,
});
dispatch(
sendLogLive({ ...liveLogParamsRef.current, watchStrtDt }, () =>
dispatch(changeLocalSettings({ watchRecord: {} }))
@@ -1147,7 +1166,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
//딮링크로 플레이어 진입 후 이전버튼 클릭시
if (panels.length === 1) {
setTimeout(() => {
timeoutRef.current = setTimeout(() => {
Spotlight.focus(SpotlightIds.HOME_TBODY);
});
}
@@ -1685,7 +1704,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
useEffect(() => {
if (currentLiveTimeSeconds > liveTotalTime) {
setTimeout(() => {
timeoutRef.current = setTimeout(() => {
dispatch(getMainLiveShow());
setShopNowInfo('');
dispatch(
@@ -1694,8 +1713,21 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
})
);
}, 3000);
return () => {
if (timeoutRef.current) clearTimeout(timeoutRef.current);
};
}
}, [currentLiveTimeSeconds, liveTotalTime]);
}, [currentLiveTimeSeconds, liveTotalTime, dispatch, playListInfo, selectedIndex]);
useEffect(() => {
return () => {
if (timeoutRef.current) clearTimeout(timeoutRef.current);
if (watchIntervalLive.current) clearInterval(watchIntervalLive.current);
if (watchIntervalVod.current) clearInterval(watchIntervalVod.current);
if (watchIntervalMedia.current) clearInterval(watchIntervalMedia.current);
};
}, []);
const mediainfoHandler = useCallback(
(ev) => {
@@ -1994,7 +2026,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
const handlePopupClose = useCallback(() => {
dispatch(setHidePopup());
setTimeout(() => Spotlight.focus(SpotlightIds.PLAYER_SUBTITLE_BUTTON));
timeoutRef.current = setTimeout(() => Spotlight.focus(SpotlightIds.PLAYER_SUBTITLE_BUTTON));
}, [dispatch]);
const reactPlayerSubtitleConfig = useMemo(() => {
if (isSubtitleActive && currentSubtitleBlob) {
@@ -2324,7 +2356,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
})
);
Spotlight.pause();
setTimeout(() => {
timeoutRef.current = setTimeout(() => {
Spotlight.resume();
dispatch(PanelActions.popPanel());
}, VIDEO_END_ACTION_DELAY);
@@ -2332,7 +2364,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
}
if (panelInfoRef.current.shptmBanrTpNm === 'VOD') {
Spotlight.pause();
setTimeout(() => {
timeoutRef.current = setTimeout(() => {
stopExternalPlayer();
if (panelInfoRef.current.modal) {
// 모달 모드에서는 종료 후 화면을 유지하고 Back 아이콘으로 포커스 이동
@@ -2582,7 +2614,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
if (lastFocusedTargetId) {
// ShopNowContents가 렌더링될 때까지 잠시 대기 후 포커스 복원
setTimeout(() => {
timeoutRef.current = setTimeout(() => {
dlog('[PlayerPanel] 🔍 500ms 후 포커스 복원 시도:', lastFocusedTargetId);
Spotlight.focus(lastFocusedTargetId);
}, 500);
@@ -2591,6 +2623,10 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
// 한 번 처리한 복귀 플래그는 즉시 해제해 중복 영향을 막는다.
prevIsTopPanelDetailFromPlayerRef.current = false;
}
return () => {
if (timeoutRef.current) clearTimeout(timeoutRef.current);
};
}
}, [
isOnTop,

View File

@@ -3,7 +3,7 @@
.container {
position: fixed;
bottom: 40px;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
z-index: 5;
@@ -14,7 +14,7 @@
max-width: 455px;
height: 92px;
padding: 10px 10px 10px 10px;
margin-bottom: 50px;
margin-bottom: 10px;
background: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(234, 234, 234, 0.3);
border-radius: 100px;

View File

@@ -1987,35 +1987,25 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
/**
* LOG 용도,
* 검색 시 로그를 보내는 용도의 이펙트
* 우선 주석처리 (계속보내는부분에 대한 처리 필요)
*/
// useEffect(() => {
// const result = Object.values(searchDatas).reduce((acc, curr) => {
// return acc + curr.length;
// }, 0);
useEffect(() => {
const result = Object.values(searchDatas).reduce((acc, curr) => {
return acc + curr.length;
}, 0);
// if (searchQuery) {
// dispatch(
// sendLogTotalRecommend({
// query: searchQuery,
// searchType: searchPerformed ? 'query' : 'keyword',
// result: result,
// contextName: LOG_CONTEXT_NAME.SEARCH,
// messageId: LOG_MESSAGE_ID.SEARCH_ITEM,
// })
// );
// // 검색 완료 후 결과에 따른 Toast 표시
// // if (searchPerformed && searchQuery.trim()) {
// // if (result > 0) {
// // dispatch(showSearchSuccessToast(searchQuery, result));
// // } else {
// // dispatch(showSearchErrorToast(searchQuery));
// // }
// // }
// }
// // eslint-disable-next-line react-hooks/exhaustive-deps
// }, [searchDatas, searchPerformed, searchQuery]);
if (searchQuery) {
dispatch(
sendLogTotalRecommend({
query: searchQuery,
searchType: searchPerformed ? 'query' : 'keyword',
result: result,
contextName: LOG_CONTEXT_NAME.SEARCH,
messageId: LOG_MESSAGE_ID.SEARCH_ITEM,
})
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchDatas, searchPerformed, searchQuery]);
/**
* clean up 용도

View File

@@ -20,7 +20,7 @@ import css from './UserReviewPanel.module.less';
// 디버그 헬퍼 설정
const DEBUG_MODE = false;
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
const { dlog, dwarn, derror /* eslint-disable-line no-unused-vars */ } = createDebugHelpers(DEBUG_MODE);
// 버전에 따른 UI 설정
const VERSION_LABEL = REVIEW_VERSION === 1 ? '[v1 - 기존 API]' : '[v2 - 신 API]';
@@ -41,8 +41,8 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
userReviewPanelTotalPages,
goToNextUserReviewPage,
goToPrevUserReviewPage,
applyRatingFilter,
applySentimentFilter,
applyRatingFilter, // eslint-disable-line no-unused-vars
applySentimentFilter, // eslint-disable-line no-unused-vars
clearAllFilters,
currentFilter,
filterCounts,
@@ -50,10 +50,10 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
_debug,
// 🎯 API 기반 필터링 데이터
filters,
filteredReviewListData,
filteredReviewListData, // eslint-disable-line no-unused-vars
currentReviewFilter,
// 전체 리뷰 데이터 (팝업용)
allReviews,
allReviews, // eslint-disable-line no-unused-vars
filteredReviews, // ✅ 필터링된 전체 리뷰 (팝업에서 사용)
getReviewsWithImages,
extractImagesFromReviews,
@@ -226,7 +226,15 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
return sentimentMap;
}, [filters]);
// API 기반 별점 필터 핸들러
const getApiKeywordClickHandler = useCallback(
(keywordValue) => () => handleApiKeywordsFilter(keywordValue),
[handleApiKeywordsFilter]
);
const getApiSentimentClickHandler = useCallback(
(sentimentValue) => () => handleApiSentimentFilter(sentimentValue),
[handleApiSentimentFilter]
);
const handleApiRatingFilter = useCallback(
(rating) => {
if (!prdtId || !patnrId) {
@@ -621,7 +629,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
<FilterItemButton
key={keyword.filterTpVal}
text={`${keyword.filterNm} (${keyword.filterNmCnt})`}
onClick={() => handleApiKeywordsFilter(keyword.filterTpVal)}
onClick={getApiKeywordClickHandler(keyword.filterTpVal)}
spotlightId={`filter-keyword-${index}`}
ariaLabel={`Filter by ${keyword.filterNm} keyword`}
dataSpotlightUp={
@@ -693,7 +701,7 @@ const UserReviewPanel = ({ className, panelInfo, spotlightId }) => {
<FilterItemButton
key={sentiment}
text={`${sentiment.charAt(0).toUpperCase() + sentiment.slice(1)} (${count})`}
onClick={() => handleApiSentimentFilter(sentiment)}
onClick={getApiSentimentClickHandler(sentiment)}
spotlightId={`filter-sentiment-${sentiment}`}
ariaLabel={`Filter by ${sentiment} sentiment`}
dataSpotlightUp={