diff --git a/com.twin.app.shoptime/src/actions/deviceActions.js b/com.twin.app.shoptime/src/actions/deviceActions.js index 4f4d2a97..2c997a64 100644 --- a/com.twin.app.shoptime/src/actions/deviceActions.js +++ b/com.twin.app.shoptime/src/actions/deviceActions.js @@ -1,14 +1,9 @@ -import { URLS } from "../api/apiConfig"; -import { - runDelayedAction, - setTokenRefreshing, - TAxios, - TAxiosAdvancedPromise, -} from "../api/TAxios"; -import * as lunaSend from "../lunaSend"; -import { types } from "./actionTypes"; -import { changeLocalSettings } from "./commonActions"; -import { fetchCurrentUserHomeTerms } from "./homeActions"; +import { URLS } from '../api/apiConfig'; +import { runDelayedAction, setTokenRefreshing, TAxios, TAxiosAdvancedPromise } from '../api/TAxios'; +import * as lunaSend from '../lunaSend'; +import { types } from './actionTypes'; +import { changeLocalSettings } from './commonActions'; +import { fetchCurrentUserHomeTerms } from './homeActions'; const MAX_RETRY_COUNT = 3; const RETRY_DELAY = 2000; // 2 seconds @@ -17,7 +12,7 @@ const RETRY_DELAY = 2000; // 2 seconds export const getAuthenticationCode = () => (dispatch, getState) => { setTokenRefreshing(true); const onSuccess = (response) => { - console.log("getAuthenticationCode onSuccess: ", response.data); + console.log('getAuthenticationCode onSuccess: ', response.data); const accessToken = response.data.data.accessToken; const refreshToken = response.data.data.refreshToken ?? null; @@ -27,21 +22,11 @@ export const getAuthenticationCode = () => (dispatch, getState) => { }; const onFail = (error) => { - console.error("getAuthenticationCode onFail: ", error); + console.error('getAuthenticationCode onFail: ', error); setTokenRefreshing(false); }; - TAxios( - dispatch, - getState, - "get", - URLS.GET_AUTHENTICATION_CODE, - {}, - {}, - onSuccess, - onFail, - true - ); + TAxios(dispatch, getState, 'get', URLS.GET_AUTHENTICATION_CODE, {}, {}, onSuccess, onFail, true); }; // IF-LGSP-001 디바이스 등록 및 약관 동의 @@ -50,7 +35,7 @@ export const registerDevice = const { agreeTerms } = params; const onSuccess = (response) => { - console.log("registerDevice onSuccess: ", response.data); + console.log('registerDevice onSuccess: ', response.data); dispatch({ type: types.REGISTER_DEVICE, @@ -65,7 +50,7 @@ export const registerDevice = }; const onFail = (error) => { - console.error("registerDevice onFail: ", error); + console.error('registerDevice onFail: ', error); if (onFailCallback) { onFailCallback(error); } @@ -74,7 +59,7 @@ export const registerDevice = TAxios( dispatch, getState, - "post", + 'post', URLS.REGISTER_DEVICE, {}, { agreeTerms }, @@ -89,7 +74,7 @@ export const registerDeviceInfo = (params) => (dispatch, getState) => { const { evntTpCd, evntId, evntApplcnFlag, entryMenu, mbphNo } = params; const onSuccess = (response) => { - console.log("registerDeviceInfo onSuccess: ", response.data); + console.log('registerDeviceInfo onSuccess: ', response.data); dispatch({ type: types.REGISTER_DEVICE_INFO, @@ -99,13 +84,13 @@ export const registerDeviceInfo = (params) => (dispatch, getState) => { }; const onFail = (error) => { - console.error("registerDeviceInfo onFail: ", error); + console.error('registerDeviceInfo onFail: ', error); }; TAxios( dispatch, getState, - "post", + 'post', URLS.REGISTER_DEVICE_INFO, {}, { evntTpCd, evntId, evntApplcnFlag, entryMenu, mbphNo }, @@ -117,7 +102,7 @@ export const registerDeviceInfo = (params) => (dispatch, getState) => { // 디바이스 부가 정보 조회 IF-LGSP-003 export const getDeviceAdditionInfo = () => (dispatch, getState) => { const onSuccess = (response) => { - console.log("getDeviceAdditionInfo onSuccess: ", response.data); + console.log('getDeviceAdditionInfo onSuccess: ', response.data); dispatch({ type: types.GET_DEVICE_INFO, @@ -126,26 +111,17 @@ export const getDeviceAdditionInfo = () => (dispatch, getState) => { }; const onFail = (error) => { - console.error("getDeviceAdditionInfo onFail: ", error); + console.error('getDeviceAdditionInfo onFail: ', error); }; - TAxios( - dispatch, - getState, - "get", - URLS.GET_DEVICE_INFO, - {}, - {}, - onSuccess, - onFail - ); + TAxios(dispatch, getState, 'get', URLS.GET_DEVICE_INFO, {}, {}, onSuccess, onFail); }; // 인증번호 재요청 IF-LGSP-096 export const getReAuthenticationCode = () => (dispatch, getState) => { setTokenRefreshing(true); const onSuccess = (response) => { - console.log("getReAuthenticationCode onSuccess: ", response.data); + // console.log("getReAuthenticationCode onSuccess: ", response.data); const accessToken = response.data.data.accessToken; dispatch(changeLocalSettings({ accessToken })); setTokenRefreshing(false); @@ -153,14 +129,14 @@ export const getReAuthenticationCode = () => (dispatch, getState) => { }; const onFail = (error) => { - console.error("getReAuthenticationCode onFail: ", error); + console.error('getReAuthenticationCode onFail: ', error); setTokenRefreshing(false); }; TAxios( dispatch, getState, - "get", + 'get', URLS.GET_RE_AUTHENTICATION_CODE, {}, {}, diff --git a/com.twin.app.shoptime/src/actions/homeActions.js b/com.twin.app.shoptime/src/actions/homeActions.js index 59a1b2f6..d9f02e9f 100644 --- a/com.twin.app.shoptime/src/actions/homeActions.js +++ b/com.twin.app.shoptime/src/actions/homeActions.js @@ -1,62 +1,67 @@ -import { URLS } from "../api/apiConfig"; -import { TAxios,TAxiosPromise } from "../api/TAxios"; -import { types } from "./actionTypes"; -import { changeAppStatus, getTermsAgreeYn } from "./commonActions"; -import { collectBannerPositions } from "../utils/domUtils"; +import { URLS } from '../api/apiConfig'; +import { TAxios, TAxiosPromise } from '../api/TAxios'; +import { types } from './actionTypes'; +import { changeAppStatus, getTermsAgreeYn } from './commonActions'; +import { collectBannerPositions } from '../utils/domUtils'; // 약관 정보 조회 IF-LGSP-005 export const getHomeTerms = (props) => (dispatch, getState) => { const { trmsTpCdList, mbrNo } = props; const onSuccess = (response) => { - console.log("getHomeTerms onSuccess ", response.data); - + console.log('getHomeTerms onSuccess ', response.data); + if (response.data.retCode === 0) { dispatch({ type: types.GET_HOME_TERMS, payload: response.data, }); - + // 약관 ID 매핑을 별도로 생성하여 저장 // Chromium68 호환성을 위해 Optional Chaining 제거 if (response.data && response.data.data && response.data.data.terms) { const termsIdMap = {}; let hasOptionalTerms = false; // MST00405 존재 여부 확인 - - response.data.data.terms.forEach(term => { + + response.data.data.terms.forEach((term) => { if (term.trmsTpCd && term.trmsId) { termsIdMap[term.trmsTpCd] = term.trmsId; } - + // MST00405 선택약관 존재 여부 확인 - if (term.trmsTpCd === "MST00405") { + if (term.trmsTpCd === 'MST00405') { hasOptionalTerms = true; } }); - + dispatch({ type: types.SET_TERMS_ID_MAP, payload: termsIdMap, }); - + // 선택약관 존재 여부 상태 설정 // TODO: 테스트용 - 임시로 false 강제 설정 const forceDisableOptionalTerms = false; // 테스트 완료 후 false로 변경 const finalOptionalTermsValue = forceDisableOptionalTerms ? false : hasOptionalTerms; - + dispatch({ type: types.SET_OPTIONAL_TERMS_AVAILABILITY, payload: finalOptionalTermsValue, }); - console.log("[optionalTermsAvailable] 실제값:", hasOptionalTerms, "강제설정값:", finalOptionalTermsValue); - - if (process.env.NODE_ENV === "development") { - console.log("약관 ID 매핑 생성:", termsIdMap); - console.log("선택약관 존재 여부:", hasOptionalTerms); + console.log( + '[optionalTermsAvailable] 실제값:', + hasOptionalTerms, + '강제설정값:', + finalOptionalTermsValue + ); + + if (process.env.NODE_ENV === 'development') { + console.log('약관 ID 매핑 생성:', termsIdMap); + console.log('선택약관 존재 여부:', hasOptionalTerms); } } - + setTimeout(() => { dispatch(getTermsAgreeYn()); }, 0); @@ -64,13 +69,13 @@ export const getHomeTerms = (props) => (dispatch, getState) => { }; const onFail = (error) => { - console.error("getHomeTerms onFail ", error); + console.error('getHomeTerms onFail ', error); }; TAxios( dispatch, getState, - "get", + 'get', URLS.GET_HOME_TERMS, { trmsTpCdList, mbrNo }, {}, @@ -82,64 +87,71 @@ export const getHomeTerms = (props) => (dispatch, getState) => { // 현재 로그인 사용자 기준으로 약관 정보 조회 (인자 없이 호출 가능) export const fetchCurrentUserHomeTerms = () => (dispatch, getState) => { const loginUserData = getState().common.appStatus.loginUserData; - + if (!loginUserData || !loginUserData.userNumber) { - console.error("fetchCurrentUserHomeTerms: userNumber (mbrNo) is not available. User might not be logged in."); + console.error( + 'fetchCurrentUserHomeTerms: userNumber (mbrNo) is not available. User might not be logged in.' + ); dispatch({ type: types.GET_TERMS_AGREE_YN_FAILURE }); return; } - + const mbrNo = loginUserData.userNumber; - const trmsTpCdList = "MST00401, MST00402, MST00405"; // 기본 약관 코드 리스트 - + const trmsTpCdList = 'MST00401, MST00402, MST00405'; // 기본 약관 코드 리스트 + const onSuccess = (response) => { - console.log("fetchCurrentUserHomeTerms onSuccess ", response.data); - + console.log('fetchCurrentUserHomeTerms onSuccess ', response.data); + if (response.data.retCode === 0) { dispatch({ type: types.GET_HOME_TERMS, // 기존 GET_HOME_TERMS 타입을 재사용 payload: response.data, }); - + // 약관 ID 매핑을 별도로 생성하여 저장 // Chromium68 호환성을 위해 Optional Chaining 제거 if (response.data && response.data.data && response.data.data.terms) { const termsIdMap = {}; let hasOptionalTerms = false; // MST00405 존재 여부 확인 - - response.data.data.terms.forEach(term => { + + response.data.data.terms.forEach((term) => { if (term.trmsTpCd && term.trmsId) { termsIdMap[term.trmsTpCd] = term.trmsId; } - + // MST00405 선택약관 존재 여부 확인 - if (term.trmsTpCd === "MST00405") { + if (term.trmsTpCd === 'MST00405') { hasOptionalTerms = true; } }); - + dispatch({ type: types.SET_TERMS_ID_MAP, payload: termsIdMap, }); - + // 선택약관 존재 여부 상태 설정 // TODO: 테스트용 - 임시로 false 강제 설정 const forceDisableOptionalTerms = false; // 테스트 완료 후 false로 변경 const finalOptionalTermsValue = forceDisableOptionalTerms ? false : hasOptionalTerms; - + dispatch({ type: types.SET_OPTIONAL_TERMS_AVAILABILITY, payload: finalOptionalTermsValue, }); - console.log("[optionalTermsAvailable] 실제값:", hasOptionalTerms, "강제설정값:", finalOptionalTermsValue); + console.log( + '[optionalTermsAvailable] 실제값:', + hasOptionalTerms, + '강제설정값:', + finalOptionalTermsValue + ); - if (process.env.NODE_ENV === "development") { - console.log("약관 ID 매핑 생성:", termsIdMap); - console.log("선택약관 존재 여부:", hasOptionalTerms); + if (process.env.NODE_ENV === 'development') { + console.log('약관 ID 매핑 생성:', termsIdMap); + console.log('선택약관 존재 여부:', hasOptionalTerms); } } - + // getHomeTerms와 동일하게 getTermsAgreeYn 후속 처리 setTimeout(() => { dispatch(getTermsAgreeYn()); @@ -149,129 +161,129 @@ export const fetchCurrentUserHomeTerms = () => (dispatch, getState) => { dispatch({ type: types.GET_TERMS_AGREE_YN_FAILURE }); } }; - + const onFail = (error) => { - console.error("fetchCurrentUserHomeTerms onFail ", error); + console.error('fetchCurrentUserHomeTerms onFail ', error); dispatch({ type: types.GET_TERMS_AGREE_YN_FAILURE }); }; - + TAxios( dispatch, getState, - "get", + 'get', URLS.GET_HOME_TERMS, // 동일한 API 엔드포인트 사용 { trmsTpCdList, mbrNo }, {}, onSuccess, onFail ); - }; +}; // 기존 TAxios 패턴과 일치하는 안전한 Redux Action export const fetchCurrentUserHomeTermsSafe = () => async (dispatch, getState) => { const loginUserData = getState().common.appStatus.loginUserData; - + if (!loginUserData || !loginUserData.userNumber) { - console.error("fetchCurrentUserHomeTerms: userNumber is not available"); + console.error('fetchCurrentUserHomeTerms: userNumber is not available'); dispatch({ type: types.GET_TERMS_AGREE_YN_FAILURE }); - return { success: false, message: "사용자 정보가 없습니다." }; + return { success: false, message: '사용자 정보가 없습니다.' }; } - + const mbrNo = loginUserData.userNumber; - const trmsTpCdList = "MST00401, MST00402, MST00405"; - - console.log("Fetching home terms for user:", mbrNo); - + const trmsTpCdList = 'MST00401, MST00402, MST00405'; + + console.log('Fetching home terms for user:', mbrNo); + // 안전한 API 호출 (기존 TAxios 패턴과 동일) - const result = await TAxiosPromise( - dispatch, - getState, - "get", - URLS.GET_HOME_TERMS, - { trmsTpCdList, mbrNo } - ); - + const result = await TAxiosPromise(dispatch, getState, 'get', URLS.GET_HOME_TERMS, { + trmsTpCdList, + mbrNo, + }); + // 네트워크 에러인 경우 if (!result.success) { - console.error("fetchCurrentUserHomeTerms network error:", result.error); + console.error('fetchCurrentUserHomeTerms network error:', result.error); dispatch({ type: types.GET_TERMS_AGREE_YN_FAILURE }); - return { success: false, message: "네트워크 오류가 발생했습니다." }; + return { success: false, message: '네트워크 오류가 발생했습니다.' }; } - + // 기존 TAxios처럼 특별한 retCode들은 TAxios 내부에서 이미 처리됨 // (401, 402, 501, 602, 603, 604 등은 TAxios에서 알아서 처리하고 onSuccess가 호출되지 않음) - - console.log("fetchCurrentUserHomeTerms response:", result.data); - + + console.log('fetchCurrentUserHomeTerms response:', result.data); + // 정상적으로 onSuccess가 호출된 경우에만 여기까지 옴 if (result.data && result.data.retCode === 0) { dispatch({ type: types.GET_HOME_TERMS, payload: result.data, }); - + // 약관 ID 매핑을 별도로 생성하여 저장 // Chromium68 호환성을 위해 Optional Chaining 제거 if (result.data && result.data.data && result.data.data.terms) { const termsIdMap = {}; let hasOptionalTerms = false; // MST00405 존재 여부 확인 - - result.data.data.terms.forEach(term => { + + result.data.data.terms.forEach((term) => { if (term.trmsTpCd && term.trmsId) { termsIdMap[term.trmsTpCd] = term.trmsId; } - + // MST00405 선택약관 존재 여부 확인 - if (term.trmsTpCd === "MST00405") { + if (term.trmsTpCd === 'MST00405') { hasOptionalTerms = true; } }); - + dispatch({ type: types.SET_TERMS_ID_MAP, payload: termsIdMap, }); - + // 선택약관 존재 여부 상태 설정 2025-07-03 // TODO: 테스트용 - 임시로 false 강제 설정 const forceDisableOptionalTerms = false; // 테스트 완료 후 false로 변경 const finalOptionalTermsValue = forceDisableOptionalTerms ? false : hasOptionalTerms; - + dispatch({ type: types.SET_OPTIONAL_TERMS_AVAILABILITY, payload: finalOptionalTermsValue, }); - - if (process.env.NODE_ENV === "development") { - console.log("약관 ID 매핑 생성:", termsIdMap); - console.log("선택약관 존재 여부 - 실제값:", hasOptionalTerms, "강제설정값:", finalOptionalTermsValue); + + if (process.env.NODE_ENV === 'development') { + console.log('약관 ID 매핑 생성:', termsIdMap); + console.log( + '선택약관 존재 여부 - 실제값:', + hasOptionalTerms, + '강제설정값:', + finalOptionalTermsValue + ); } } - + // 후속 액션 호출 (기존과 동일) setTimeout(() => { dispatch(getTermsAgreeYn()); }, 0); - + return { success: true, data: result.data }; } else { // retCode가 0이 아닌 일반적인 API 에러 // Chromium68 호환성을 위해 Optional Chaining 제거 - console.error("API returned non-zero retCode:", result.data && result.data.retCode); + console.error('API returned non-zero retCode:', result.data && result.data.retCode); dispatch({ type: types.GET_TERMS_AGREE_YN_FAILURE }); - return { - success: false, - message: (result.data && result.data.retMsg) || "서버 오류가 발생했습니다." + return { + success: false, + message: (result.data && result.data.retMsg) || '서버 오류가 발생했습니다.', }; } }; - - // 메뉴 목록 조회 IF-LGSP-044 export const getHomeMenu = () => (dispatch, getState) => { const onSuccess = (response) => { - console.log("getHomeMenu onSuccess ", response.data); + // console.log("getHomeMenu onSuccess ", response.data); dispatch({ type: types.GET_HOME_MENU, @@ -280,29 +292,20 @@ export const getHomeMenu = () => (dispatch, getState) => { }; const onFail = (error) => { - console.error("getHomeMenu onFail ", error); + console.error('getHomeMenu onFail ', error); }; - TAxios( - dispatch, - getState, - "get", - URLS.GET_HOME_MENU, - {}, - {}, - onSuccess, - onFail - ); + TAxios(dispatch, getState, 'get', URLS.GET_HOME_MENU, {}, {}, onSuccess, onFail); }; // 테마 전시 정보 상세 조회 IF-LGSP-060 export const getThemeCurationDetailInfo = (params) => (dispatch, getState) => { const { patnrId, curationId, bgImgNo } = params; - dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: "wait" } })); + dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } })); const onSuccess = (response) => { - console.log("getThemeCurationDetailInfo onSuccess", response.data); + console.log('getThemeCurationDetailInfo onSuccess', response.data); dispatch({ type: types.GET_THEME_CURATION_DETAIL_INFO, @@ -313,14 +316,14 @@ export const getThemeCurationDetailInfo = (params) => (dispatch, getState) => { }; const onFail = (error) => { - console.error("getThemeCurationDetailInfo onFail", error); + console.error('getThemeCurationDetailInfo onFail', error); dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); }; TAxios( dispatch, getState, - "get", + 'get', URLS.GET_THEME_CURATION_DETAIL_INFO, { patnrId, curationId, bgImgNo }, {}, @@ -332,10 +335,10 @@ export const getThemeCurationDetailInfo = (params) => (dispatch, getState) => { export const getThemeHotelDetailInfo = (params) => (dispatch, getState) => { const { patnrId, curationId } = params; - dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: "wait" } })); + dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } })); const onSuccess = (response) => { - console.log("getThemeHotelDetailInfo onSuccess", response.data); + console.log('getThemeHotelDetailInfo onSuccess', response.data); dispatch({ type: types.GET_THEME_HOTEL_DETAIL_INFO, @@ -346,14 +349,14 @@ export const getThemeHotelDetailInfo = (params) => (dispatch, getState) => { }; const onFail = (error) => { - console.error("getThemeHotelDetailInfo onFail", error); + console.error('getThemeHotelDetailInfo onFail', error); dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); }; TAxios( dispatch, getState, - "get", + 'get', URLS.GET_THEME_HOTEL_DETAIL_INFO, { patnrId, curationId }, {}, @@ -364,7 +367,7 @@ export const getThemeHotelDetailInfo = (params) => (dispatch, getState) => { // HOME LAYOUT 정보 조회 IF-LGSP-300 export const getHomeLayout = () => (dispatch, getState) => { const onSuccess = (response) => { - console.log("getHomeLayout onSuccess", response.data); + console.log('getHomeLayout onSuccess', response.data); dispatch({ type: types.GET_HOME_LAYOUT, @@ -374,57 +377,39 @@ export const getHomeLayout = () => (dispatch, getState) => { }; const onFail = (error) => { - console.error("getHomeLayout onFail", error); + console.error('getHomeLayout onFail', error); dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); }; - TAxios( - dispatch, - getState, - "get", - URLS.GET_HOME_LAYOUT, - {}, - {}, - onSuccess, - onFail - ); + TAxios(dispatch, getState, 'get', URLS.GET_HOME_LAYOUT, {}, {}, onSuccess, onFail); }; // HOME Main Contents Banner 정보 조회 IF-LGSP-301 export const getHomeMainContents = () => (dispatch, getState) => { const onSuccess = (response) => { - console.log("getHomeMainContents onSuccess", response.data); + console.log('getHomeMainContents onSuccess', response.data); dispatch({ type: types.GET_HOME_MAIN_CONTENTS, payload: response.data.data, - status: "fulfilled", + status: 'fulfilled', }); dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); }; const onFail = (error) => { - console.error("getHomeMainContents onFail", error); + console.error('getHomeMainContents onFail', error); dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); }; - TAxios( - dispatch, - getState, - "get", - URLS.GET_HOME_MAIN_CONTENTS, - {}, - {}, - onSuccess, - onFail - ); + TAxios(dispatch, getState, 'get', URLS.GET_HOME_MAIN_CONTENTS, {}, {}, onSuccess, onFail); }; // Theme 전시 정보 조회 : IF-LGSP-045 export const getThemeCurationInfo = () => (dispatch, getState) => { const onSuccess = (response) => { - console.log("getThemeCurationInfo onSuccess", response.data); + console.log('getThemeCurationInfo onSuccess', response.data); dispatch({ type: types.GET_THEME_CURATION_INFO, @@ -435,30 +420,21 @@ export const getThemeCurationInfo = () => (dispatch, getState) => { }; const onFail = (error) => { - console.error("getThemeCurationInfo onFail", error); + console.error('getThemeCurationInfo onFail', error); dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); }; - TAxios( - dispatch, - getState, - "get", - URLS.GET_THEME_CURATION_INFO, - {}, - {}, - onSuccess, - onFail - ); + TAxios(dispatch, getState, 'get', URLS.GET_THEME_CURATION_INFO, {}, {}, onSuccess, onFail); }; // 테마 메뉴(=테마 페이지) 선반 조회 : IF-LGSP-095 export const getThemeMenuShelfInfo = (props) => (dispatch, getState) => { const { curationId } = props; - dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: "wait" } })); + dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } })); const onSuccess = (response) => { - console.log("getThemeMenuShelfInfo onSuccess", response.data); + console.log('getThemeMenuShelfInfo onSuccess', response.data); dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); dispatch({ type: types.GET_THEME_MENU_SHELF_INFO, @@ -467,14 +443,14 @@ export const getThemeMenuShelfInfo = (props) => (dispatch, getState) => { }; const onFail = (error) => { - console.error("getThemeMenuShelfInfo onFail", error); + console.error('getThemeMenuShelfInfo onFail', error); dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); }; TAxios( dispatch, getState, - "get", + 'get', URLS.GET_THEME_MENU_SHELF_INFO, { curationId }, {}, @@ -514,8 +490,8 @@ export const checkEnterThroughGNB = (boolean) => ({ export const setBannerIndex = (bannerId, index) => { if (!bannerId) { - console.warn("setBannerIndex called with undefined bannerId"); - return { type: "NO_OP" }; + console.warn('setBannerIndex called with undefined bannerId'); + return { type: 'NO_OP' }; } return { type: types.SET_BANNER_INDEX, @@ -568,11 +544,11 @@ export const collectAndSaveBannerPositions = (bannerIds) => async (dispatch) => try { const positions = await collectBannerPositions(bannerIds); dispatch(setBannerPositions(positions)); - - if (process.env.NODE_ENV === "development") { - console.log("[homeActions] 배너 위치 수집 완료:", positions); + + if (process.env.NODE_ENV === 'development') { + console.log('[homeActions] 배너 위치 수집 완료:', positions); } } catch (error) { - console.error("[homeActions] 배너 위치 수집 실패:", error); + console.error('[homeActions] 배너 위치 수집 실패:', error); } }; diff --git a/com.twin.app.shoptime/src/actions/mainActions.js b/com.twin.app.shoptime/src/actions/mainActions.js index 448b4f1d..b11c1d06 100644 --- a/com.twin.app.shoptime/src/actions/mainActions.js +++ b/com.twin.app.shoptime/src/actions/mainActions.js @@ -1,18 +1,10 @@ import { URLS } from '../api/apiConfig'; import { TAxios } from '../api/TAxios'; import { convertUtcToLocal } from '../components/MediaPlayer/util'; -import { - CATEGORY_DATA_MAX_RESULTS_LIMIT, - LOG_CONTEXT_NAME, - LOG_MESSAGE_ID, -} from '../utils/Config'; +import { CATEGORY_DATA_MAX_RESULTS_LIMIT, LOG_CONTEXT_NAME, LOG_MESSAGE_ID } from '../utils/Config'; import * as HelperMethods from '../utils/helperMethods'; import { types } from './actionTypes'; -import { - addReservation, - changeAppStatus, - deleteReservation, -} from './commonActions'; +import { addReservation, changeAppStatus, deleteReservation } from './commonActions'; //IF-LGSP-007 export const getMainLiveShow = (props) => (dispatch, getState) => { @@ -233,7 +225,7 @@ export const getSubCategory = getState, 'get', URLS.GET_SUB_CATEGORY, - { lgCatCd, patnrIdList, pageSize, pageNo, tabType, filterType,recommendIncFlag }, + { lgCatCd, patnrIdList, pageSize, pageNo, tabType, filterType, recommendIncFlag }, {}, onSuccess, onFail @@ -435,7 +427,7 @@ export const getMainLiveShowNowProduct = ({ patnrId, showId, lstChgDt }) => (dispatch, getState) => { const onSuccess = (response) => { - console.log('getMainLiveShowNowProduct onSuccess', response.data); + // console.log('getMainLiveShowNowProduct onSuccess', response.data); dispatch({ type: types.GET_MAIN_LIVE_SHOW_NOW_PRODUCT, diff --git a/com.twin.app.shoptime/src/actions/productActions.js b/com.twin.app.shoptime/src/actions/productActions.js index f2435c73..d9fdc580 100644 --- a/com.twin.app.shoptime/src/actions/productActions.js +++ b/com.twin.app.shoptime/src/actions/productActions.js @@ -169,15 +169,15 @@ export const getProductOption = createGetThunk({ // IF-LGSP-101용 API 응답에서 reviewList + reviewDetail 추출 const extractReviewListApiData = (apiResponse) => { try { - console.log('[UserReviewList] 📥 extractReviewListApiData 호출 - 원본 응답:', apiResponse); + // console.log('[UserReviewList] 📥 extractReviewListApiData 호출 - 원본 응답:', apiResponse); // ⭐ 핵심: retCode가 0인지 먼저 확인 (HTTP 200이어도 API 에러일 수 있음) if (apiResponse && apiResponse.retCode !== 0) { - console.error('[UserReviewList] ❌ API 에러 - retCode !== 0:', { - retCode: apiResponse.retCode, - retMsg: apiResponse.retMsg, - fullResponse: apiResponse - }); + // console.error('[UserReviewList] ❌ API 에러 - retCode !== 0:', { + // retCode: apiResponse.retCode, + // retMsg: apiResponse.retMsg, + // fullResponse: apiResponse + // }); return null; } @@ -193,59 +193,67 @@ const extractReviewListApiData = (apiResponse) => { const reviewDetail = apiData.reviewDetail || {}; // reviewDetail.reviewList에 실제 데이터가 있으면 사용 - if (reviewDetail.reviewList && Array.isArray(reviewDetail.reviewList) && reviewList.length === 0) { + if ( + reviewDetail.reviewList && + Array.isArray(reviewDetail.reviewList) && + reviewList.length === 0 + ) { reviewList = reviewDetail.reviewList; - console.log('[UserReviewList] 🔄 reviewDetail.reviewList에서 데이터 추출됨'); + // console.log('[UserReviewList] 🔄 reviewDetail.reviewList에서 데이터 추출됨'); } data = { reviewList: reviewList, - reviewDetail: reviewDetail + reviewDetail: reviewDetail, }; - console.log('[UserReviewList] 📊 apiResponse.data 경로에서 추출:', { - reviewListLength: data.reviewList.length, - reviewDetailKeys: Object.keys(data.reviewDetail), - reviewDetail: data.reviewDetail, - reviewListSample: data.reviewList.length > 0 ? data.reviewList[0] : 'empty' - }); + // console.log('[UserReviewList] 📊 apiResponse.data 경로에서 추출:', { + // reviewListLength: data.reviewList.length, + // reviewDetailKeys: Object.keys(data.reviewDetail), + // reviewDetail: data.reviewDetail, + // reviewListSample: data.reviewList.length > 0 ? data.reviewList[0] : 'empty' + // }); } else if (apiResponse) { // 직접 경로에서 추출 let reviewList = apiResponse.reviewList || []; const reviewDetail = apiResponse.reviewDetail || {}; // reviewDetail.reviewList에 실제 데이터가 있으면 사용 - if (reviewDetail.reviewList && Array.isArray(reviewDetail.reviewList) && reviewList.length === 0) { + if ( + reviewDetail.reviewList && + Array.isArray(reviewDetail.reviewList) && + reviewList.length === 0 + ) { reviewList = reviewDetail.reviewList; - console.log('[UserReviewList] 🔄 reviewDetail.reviewList에서 데이터 추출됨'); + // console.log('[UserReviewList] 🔄 reviewDetail.reviewList에서 데이터 추출됨'); } data = { reviewList: reviewList, - reviewDetail: reviewDetail + reviewDetail: reviewDetail, }; - console.log('[UserReviewList] 📊 직접 경로에서 추출:', { - reviewListLength: data.reviewList.length, - reviewDetailKeys: Object.keys(data.reviewDetail), - reviewDetail: data.reviewDetail, - reviewListSample: data.reviewList.length > 0 ? data.reviewList[0] : 'empty' - }); + // console.log('[UserReviewList] 📊 직접 경로에서 추출:', { + // reviewListLength: data.reviewList.length, + // reviewDetailKeys: Object.keys(data.reviewDetail), + // reviewDetail: data.reviewDetail, + // reviewListSample: data.reviewList.length > 0 ? data.reviewList[0] : 'empty' + // }); } if (!data || (!data.reviewList && !data.reviewDetail)) { - console.warn('[UserReviewList] ⚠️ reviewList와 reviewDetail 모두 없음:', apiResponse); + // console.warn('[UserReviewList] ⚠️ reviewList와 reviewDetail 모두 없음:', apiResponse); return null; } - console.log('[UserReviewList] ✅ 추출 완료:', { - reviewListLength: data.reviewList.length, - reviewDetail: data.reviewDetail - }); + // console.log('[UserReviewList] ✅ 추출 완료:', { + // reviewListLength: data.reviewList.length, + // reviewDetail: data.reviewDetail + // }); return data; } catch (error) { - console.error('[UserReviewList] ❌ extractReviewListApiData 에러:', error); + // console.error('[UserReviewList] ❌ extractReviewListApiData 에러:', error); return null; } }; @@ -367,7 +375,12 @@ export const getVideoIndicatorFocus = (focused) => (dispatch) => { // 순차 페이징으로 모든 리뷰 데이터를 수집하는 함수 (TV 앱 성능 최적화) // Option 2: 순차 페칭 (메모리 효율, 서버 부하 감소) // ⭐ 재시도 로직 포함: 타임아웃/미응답 케이스 대비 -const fetchAllReviewsWithSequentialPaging = async (dispatch, getState, requestParams, retryCount = 0) => { +const fetchAllReviewsWithSequentialPaging = async ( + dispatch, + getState, + requestParams, + retryCount = 0 +) => { const MAX_RETRIES = 2; // 최대 2회 재시도 (총 3회 시도) const { prdtId, @@ -377,15 +390,15 @@ const fetchAllReviewsWithSequentialPaging = async (dispatch, getState, requestPa pageSize = 100, // 최대값으로 설정하여 페이징 횟수 최소화 } = requestParams; - console.log('[UserReviewList] 🚀 순차 페이징 시작:', { - prdtId, - patnrId, - filterTpCd, - filterTpVal, - pageSize, - retryCount, - isRetry: retryCount > 0 - }); + // console.log('[UserReviewList] 🚀 순차 페이징 시작:', { + // prdtId, + // patnrId, + // filterTpCd, + // filterTpVal, + // pageSize, + // retryCount, + // isRetry: retryCount > 0 + // }); let allReviews = []; let currentReviewDetail = null; @@ -401,13 +414,13 @@ const fetchAllReviewsWithSequentialPaging = async (dispatch, getState, requestPa filterTpCd, pageSize, pageNo, - cntryCd: 'US' + cntryCd: 'US', }; // filterTpCd가 'ALL'이 아니면 filterTpVal 추가 if (filterTpCd !== 'ALL') { if (!filterTpVal) { - console.warn('[UserReviewList] ⚠️ filterTpCd가 ALL이 아니면 filterTpVal은 필수입니다'); + // console.warn('[UserReviewList] ⚠️ filterTpCd가 ALL이 아니면 filterTpVal은 필수입니다'); } params.filterTpVal = filterTpVal; } @@ -416,13 +429,13 @@ const fetchAllReviewsWithSequentialPaging = async (dispatch, getState, requestPa // ⭐ 타임아웃 추가: TAxios의 콜백이 호출되지 않는 경우를 대비 (모든 오류 상황 처리) const REQUEST_TIMEOUT = 5000; // 5초 타임아웃 (재인증, 팝업 등 오류 상황 처리 포함) - console.log(`[UserReviewList] 🔄 API 요청 시작 (page ${pageNo}):`, { - prdtId, - patnrId, - filterTpCd, - pageSize, - pageNo - }); + // console.log(`[UserReviewList] 🔄 API 요청 시작 (page ${pageNo}):`, { + // prdtId, + // patnrId, + // filterTpCd, + // pageSize, + // pageNo + // }); const response = await Promise.race([ new Promise((resolve, reject) => { @@ -430,80 +443,89 @@ const fetchAllReviewsWithSequentialPaging = async (dispatch, getState, requestPa const onSuccess = (res) => { if (callbackCalled) { - console.warn(`[UserReviewList] ⚠️ onSuccess 중복 호출 (page ${pageNo})`); + // console.warn(`[UserReviewList] ⚠️ onSuccess 중복 호출 (page ${pageNo})`); return; } callbackCalled = true; - console.log(`[UserReviewList] ✅ API 응답 수신 (page ${pageNo}):`, { - status: res?.status, - statusText: res?.statusText, - retCode: res?.data?.retCode, - dataExists: !!res?.data, - reviewDetailExists: !!res?.data?.data?.reviewDetail - }); + // console.log(`[UserReviewList] ✅ API 응답 수신 (page ${pageNo}):`, { + // status: res?.status, + // statusText: res?.statusText, + // retCode: res?.data?.retCode, + // dataExists: !!res?.data, + // reviewDetailExists: !!res?.data?.data?.reviewDetail + // }); resolve(res); }; const onFail = (err) => { if (callbackCalled) { - console.warn(`[UserReviewList] ⚠️ onFail 중복 호출 (page ${pageNo})`); + // console.warn(`[UserReviewList] ⚠️ onFail 중복 호출 (page ${pageNo})`); return; } callbackCalled = true; - console.error(`[UserReviewList] ❌ API 콜백 에러 발생 (page ${pageNo}):`, { - errorMessage: err?.message, - errorStatus: err?.response?.status, - errorStatusText: err?.response?.statusText, - errorRetCode: err?.data?.retCode, - errorRetMsg: err?.data?.retMsg, - errorType: typeof err - }); + // console.error(`[UserReviewList] ❌ API 콜백 에러 발생 (page ${pageNo}):`, { + // errorMessage: err?.message, + // errorStatus: err?.response?.status, + // errorStatusText: err?.response?.statusText, + // errorRetCode: err?.data?.retCode, + // errorRetMsg: err?.data?.retMsg, + // errorType: typeof err + // }); reject(err); }; // API 호출 - console.log(`[UserReviewList] 📡 TAxios 호출 (page ${pageNo})`); - TAxios(dispatch, getState, 'get', URLS.GET_USER_REVIEW_LIST, params, {}, onSuccess, onFail); + // console.log(`[UserReviewList] 📡 TAxios 호출 (page ${pageNo})`); + TAxios( + dispatch, + getState, + 'get', + URLS.GET_USER_REVIEW_LIST, + params, + {}, + onSuccess, + onFail + ); }), // 타임아웃 Promise (onFail이 호출되지 않은 경우에 대비) new Promise((_, reject) => setTimeout(() => { const timeoutError = new Error(`API request timeout without callback (page ${pageNo})`); - console.error(`[UserReviewList] ⏱️ API 응답 타임아웃 (page ${pageNo}):`, { - timeout: REQUEST_TIMEOUT, - prdtId, - patnrId, - pageNo, - reason: '5초 이내 onSuccess/onFail 콜백이 호출되지 않음' - }); + // console.error(`[UserReviewList] ⏱️ API 응답 타임아웃 (page ${pageNo}):`, { + // timeout: REQUEST_TIMEOUT, + // prdtId, + // patnrId, + // pageNo, + // reason: '5초 이내 onSuccess/onFail 콜백이 호출되지 않음' + // }); reject(timeoutError); }, REQUEST_TIMEOUT) - ) + ), ]); // ⭐ 핵심: HTTP 200이어도 response.data.retCode를 반드시 확인해야 함 const retCode = response?.data?.retCode; - console.log(`[UserReviewList] 📄 페이지 ${pageNo} 응답 상태 확인:`, { - pageNo, - httpStatus: response?.status, - retCode: retCode, - retMsg: response?.data?.retMsg, - reviewListLength: response?.data?.data?.reviewDetail?.reviewList?.length || 0, - totRvwCnt: response?.data?.data?.reviewDetail?.totRvwCnt - }); + // console.log(`[UserReviewList] 📄 페이지 ${pageNo} 응답 상태 확인:`, { + // pageNo, + // httpStatus: response?.status, + // retCode: retCode, + // retMsg: response?.data?.retMsg, + // reviewListLength: response?.data?.data?.reviewDetail?.reviewList?.length || 0, + // totRvwCnt: response?.data?.data?.reviewDetail?.totRvwCnt + // }); // retCode가 0이 아니면 API 에러 (HTTP 200이어도 실제 데이터 없을 수 있음) if (retCode !== 0) { - console.error(`[UserReviewList] ❌ API 에러 - retCode !== 0 (page ${pageNo}):`, { - retCode, - retMsg: response?.data?.retMsg, - pageNo, - prdtId, - totalCollected: allReviews.length - }); + // console.error(`[UserReviewList] ❌ API 에러 - retCode !== 0 (page ${pageNo}):`, { + // retCode, + // retMsg: response?.data?.retMsg, + // pageNo, + // prdtId, + // totalCollected: allReviews.length + // }); throw new Error(`API Error: retCode=${retCode}, message=${response?.data?.retMsg}`); } @@ -511,7 +533,7 @@ const fetchAllReviewsWithSequentialPaging = async (dispatch, getState, requestPa const reviewData = extractReviewListApiData(response.data); if (!reviewData || !reviewData.reviewList) { - console.warn('[UserReviewList] ⚠️ 리뷰 데이터 추출 실패, 페이징 종료'); + // console.warn('[UserReviewList] ⚠️ 리뷰 데이터 추출 실패, 페이징 종료'); break; } @@ -523,12 +545,12 @@ const fetchAllReviewsWithSequentialPaging = async (dispatch, getState, requestPa // 5. 현재 페이지의 리뷰들을 전체 리스트에 추가 allReviews = allReviews.concat(reviewData.reviewList); - console.log(`[UserReviewList] ✅ 페이지 ${pageNo} 수집 완료:`, { - pageNo, - currentPageCount: reviewData.reviewList.length, - totalCollected: allReviews.length, - totRvwCnt: currentReviewDetail?.totRvwCnt - }); + // console.log(`[UserReviewList] ✅ 페이지 ${pageNo} 수집 완료:`, { + // pageNo, + // currentPageCount: reviewData.reviewList.length, + // totalCollected: allReviews.length, + // totRvwCnt: currentReviewDetail?.totRvwCnt + // }); // 6. 페이징 종료 조건 확인 // rvwListCnt < pageSize이면 마지막 페이지 @@ -538,24 +560,24 @@ const fetchAllReviewsWithSequentialPaging = async (dispatch, getState, requestPa if (receivedCount < pageSize || allReviews.length >= totalReviews) { hasMore = false; - console.log('[UserReviewList] 📊 페이징 종료:', { - reason: receivedCount < pageSize ? '받은 개수 < pageSize' : '수집된 개수 >= 총 개수', - receivedCount, - pageSize, - totalCollected: allReviews.length, - totalReviews - }); + // console.log('[UserReviewList] 📊 페이징 종료:', { + // reason: receivedCount < pageSize ? '받은 개수 < pageSize' : '수집된 개수 >= 총 개수', + // receivedCount, + // pageSize, + // totalCollected: allReviews.length, + // totalReviews + // }); } else { pageNo++; } } // 7. 모든 리뷰 수집 완료, Redux에 디스패치 - console.log('[UserReviewList] 🎉 모든 리뷰 수집 완료:', { - totalCollected: allReviews.length, - totRvwCnt: currentReviewDetail?.totRvwCnt, - pages: pageNo - 1 - }); + // console.log('[UserReviewList] 🎉 모든 리뷰 수집 완료:', { + // totalCollected: allReviews.length, + // totRvwCnt: currentReviewDetail?.totRvwCnt, + // pages: pageNo - 1 + // }); // Redux 디스패치를 위한 최종 데이터 구성 const isAllFilter = filterTpCd === 'ALL'; @@ -565,59 +587,61 @@ const fetchAllReviewsWithSequentialPaging = async (dispatch, getState, requestPa reviewList: allReviews, reviewDetail: currentReviewDetail, prdtId, - ...(isAllFilter ? {} : { filterTpCd, filterTpVal }) + ...(isAllFilter ? {} : { filterTpCd, filterTpVal }), }; const action = { type: actionType, - payload: finalPayload + payload: finalPayload, }; - console.log('[UserReviewList] 📦 Redux 디스패치:', { - actionType, - totalReviews: allReviews.length, - totRvwCnt: currentReviewDetail?.totRvwCnt, - prdtId - }); + // console.log('[UserReviewList] 📦 Redux 디스패치:', { + // actionType, + // totalReviews: allReviews.length, + // totRvwCnt: currentReviewDetail?.totRvwCnt, + // prdtId + // }); dispatch(action); return finalPayload; } catch (error) { // ⭐ 핵심: 다양한 형태의 에러를 안전하게 처리 - const errorMessage = error?.message || (error instanceof Error ? error.toString() : JSON.stringify(error)); + const errorMessage = + error?.message || (error instanceof Error ? error.toString() : JSON.stringify(error)); const httpStatus = error?.response?.status; const apiRetCode = error?.response?.data?.retCode; const apiRetMsg = error?.response?.data?.retMsg; - console.error('[fetchAllReviewsWithSequentialPaging] ❌ 에러 발생:', { - errorMessage: errorMessage, - errorType: typeof error, - httpStatus: httpStatus, - apiRetCode: apiRetCode, - apiRetMsg: apiRetMsg, - prdtId, - patnrId, - pageNo, - currentCollected: allReviews.length, - retryCount, - maxRetries: MAX_RETRIES - }); + // console.error('[fetchAllReviewsWithSequentialPaging] ❌ 에러 발생:', { + // errorMessage: errorMessage, + // errorType: typeof error, + // httpStatus: httpStatus, + // apiRetCode: apiRetCode, + // apiRetMsg: apiRetMsg, + // prdtId, + // patnrId, + // pageNo, + // currentCollected: allReviews.length, + // retryCount, + // maxRetries: MAX_RETRIES + // }); // ⭐ 타임아웃 에러인 경우 재시도 - const isTimeoutError = errorMessage.includes('timeout') || errorMessage.includes('without callback'); + const isTimeoutError = + errorMessage.includes('timeout') || errorMessage.includes('without callback'); if (isTimeoutError && retryCount < MAX_RETRIES) { - console.log(`[fetchAllReviewsWithSequentialPaging] 🔄 타임아웃으로 인한 재시도 (${retryCount + 1}/${MAX_RETRIES}):`, { - prdtId, - patnrId, - pageNo, - retryCount, - delayMs: 1000 * (retryCount + 1) - }); + // console.log(`[fetchAllReviewsWithSequentialPaging] 🔄 타임아웃으로 인한 재시도 (${retryCount + 1}/${MAX_RETRIES}):`, { + // prdtId, + // patnrId, + // pageNo, + // retryCount, + // delayMs: 1000 * (retryCount + 1) + // }); // 지수 백오프: 1초, 2초 대기 후 재시도 const delayMs = 1000 * (retryCount + 1); - await new Promise(resolve => setTimeout(resolve, delayMs)); + await new Promise((resolve) => setTimeout(resolve, delayMs)); // 재귀 호출로 재시도 return fetchAllReviewsWithSequentialPaging(dispatch, getState, requestParams, retryCount + 1); @@ -630,51 +654,47 @@ const fetchAllReviewsWithSequentialPaging = async (dispatch, getState, requestPa // User Review List 추가 조회 IF-LGSP-101 (순차 페이징으로 모든 데이터 수집) export const getUserReviewList = (requestParams) => async (dispatch, getState) => { - const { - prdtId, - patnrId, - filterTpCd = 'ALL', - filterTpVal - } = requestParams; + const { prdtId, patnrId, filterTpCd = 'ALL', filterTpVal } = requestParams; - console.log('[getUserReviewList] 🚀 getUserReviewList 호출됨 (순차 페이징 사용):', { - prdtId, - patnrId, - filterTpCd, - filterTpVal, - timestamp: new Date().toISOString() - }); + // console.log('[getUserReviewList] 🚀 getUserReviewList 호출됨 (순차 페이징 사용):', { + // prdtId, + // patnrId, + // filterTpCd, + // filterTpVal, + // timestamp: new Date().toISOString() + // }); try { // fetchAllReviewsWithSequentialPaging 함수를 호출하여 모든 리뷰 수집 const result = await fetchAllReviewsWithSequentialPaging(dispatch, getState, requestParams); - console.log('[getUserReviewList] ✅ 모든 리뷰 수집 완료:', { - totalReviews: result.reviewList.length, - totRvwCnt: result.reviewDetail?.totRvwCnt, - prdtId, - filterTpCd, - filterTpVal - }); + // console.log('[getUserReviewList] ✅ 모든 리뷰 수집 완료:', { + // totalReviews: result.reviewList.length, + // totRvwCnt: result.reviewDetail?.totRvwCnt, + // prdtId, + // filterTpCd, + // filterTpVal + // }); } catch (error) { // ⭐ 핵심: 다양한 형태의 에러를 안전하게 처리 - const errorMessage = error?.message || (error instanceof Error ? error.toString() : JSON.stringify(error)); + const errorMessage = + error?.message || (error instanceof Error ? error.toString() : JSON.stringify(error)); const httpStatus = error?.response?.status; const apiRetCode = error?.response?.data?.retCode; const apiRetMsg = error?.response?.data?.retMsg; - console.error('[getUserReviewList] ❌ 순차 페이징 중 에러 발생:', { - errorMessage: errorMessage, - errorType: typeof error, - httpStatus: httpStatus, - apiRetCode: apiRetCode, - apiRetMsg: apiRetMsg, - prdtId, - patnrId, - filterTpCd, - filterTpVal, - stack: error?.stack - }); + // console.error('[getUserReviewList] ❌ 순차 페이징 중 에러 발생:', { + // errorMessage: errorMessage, + // errorType: typeof error, + // httpStatus: httpStatus, + // apiRetCode: apiRetCode, + // apiRetMsg: apiRetMsg, + // prdtId, + // patnrId, + // filterTpCd, + // filterTpVal, + // stack: error?.stack + // }); // Redux 상태에 에러 정보 저장 (선택사항) // dispatch({ @@ -708,7 +728,7 @@ const extractReviewFiltersApiData = (apiResponse) => { console.error('[ReviewFilters] ❌ API 에러 - retCode !== 0:', { retCode: retCode, retMsg: apiResponse?.retMsg, - fullResponse: apiResponse + fullResponse: apiResponse, }); return null; } @@ -721,7 +741,7 @@ const extractReviewFiltersApiData = (apiResponse) => { prdtId: reviewFilterInfos.prdtId, hasFilters: !!reviewFilterInfos.filters, filtersLength: reviewFilterInfos.filters ? reviewFilterInfos.filters.length : 0, - reviewFilterInfosKeys: Object.keys(reviewFilterInfos) + reviewFilterInfosKeys: Object.keys(reviewFilterInfos), }); data = reviewFilterInfos; @@ -734,7 +754,7 @@ const extractReviewFiltersApiData = (apiResponse) => { console.log('[ReviewFilters] ✅ 추출 완료:', { patnrId: data.patnrId, prdtId: data.prdtId, - filtersLength: data.filters.length + filtersLength: data.filters.length, }); return data; @@ -746,16 +766,13 @@ const extractReviewFiltersApiData = (apiResponse) => { // Review Filters 조회 IF-LGSP-100 export const getReviewFilters = (requestParams) => (dispatch, getState) => { - const { - prdtId, - patnrId - } = requestParams; + const { prdtId, patnrId } = requestParams; const params = { prdtId, patnrId, // 우선순위 1: cntryCd 기본값 'US' 설정 (TV 환경에서는 자동으로 header로 전달됨) - cntryCd: 'US' + cntryCd: 'US', }; const body = {}; @@ -765,7 +782,7 @@ export const getReviewFilters = (requestParams) => (dispatch, getState) => { params, body, url: URLS.GET_REVIEW_FILTERS, - timestamp: new Date().toISOString() + timestamp: new Date().toISOString(), }); const onSuccess = (response) => { @@ -777,8 +794,8 @@ export const getReviewFilters = (requestParams) => (dispatch, getState) => { httpStatus: response?.status, retCode: retCode, retMsg: retMsg, - hasData: !!(response?.data?.data), - dataExists: !!response?.data + hasData: !!response?.data?.data, + dataExists: !!response?.data, }); // retCode !== 0이면 extractReviewFiltersApiData에서 처리하고 null 반환됨 @@ -788,7 +805,7 @@ export const getReviewFilters = (requestParams) => (dispatch, getState) => { console.warn('[ReviewFilters] ⚠️ 필터 데이터 추출 실패:', { retCode: retCode, retMsg: retMsg, - reason: retCode !== 0 ? 'retCode !== 0' : 'filters 데이터 없음' + reason: retCode !== 0 ? 'retCode !== 0' : 'filters 데이터 없음', }); return; // 실패 시 dispatch하지 않음 } @@ -796,7 +813,7 @@ export const getReviewFilters = (requestParams) => (dispatch, getState) => { console.log('[ReviewFilters] 📊 필터 데이터 추출 성공:', { patnrId: filtersData.patnrId, prdtId: filtersData.prdtId, - filtersLength: filtersData.filters ? filtersData.filters.length : 0 + filtersLength: filtersData.filters ? filtersData.filters.length : 0, }); const action = { @@ -804,7 +821,7 @@ export const getReviewFilters = (requestParams) => (dispatch, getState) => { payload: { ...filtersData, prdtId: prdtId, - patnrId: patnrId + patnrId: patnrId, }, }; @@ -812,7 +829,7 @@ export const getReviewFilters = (requestParams) => (dispatch, getState) => { actionType: types.GET_REVIEW_FILTERS, patnrId: patnrId, prdtId: prdtId, - filtersLength: filtersData.filters ? filtersData.filters.length : 0 + filtersLength: filtersData.filters ? filtersData.filters.length : 0, }); dispatch(action); @@ -839,6 +856,6 @@ export const getReviewFilters = (requestParams) => (dispatch, getState) => { // All Star 필터 해제 - API 호출 없이 상태만 초기화 export const clearReviewFilter = () => (dispatch) => { dispatch({ - type: types.CLEAR_REVIEW_FILTER + type: types.CLEAR_REVIEW_FILTER, }); -}; \ No newline at end of file +}; diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx b/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx index 23cd5437..75c5646c 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx @@ -628,15 +628,15 @@ export default function ProductAllSection({ [setOpenThemeItemOverlay] ); - const handleProductDetailsClick = useCallback( - () => scrollToSection('scroll-marker-product-details'), - [scrollToSection] - ); + const handleProductDetailsClick = useCallback(() => { + dispatch(minimizeModalMedia()); + scrollToSection('scroll-marker-product-details'); + }, [scrollToSection, dispatch]); - const handleYouMayAlsoLikeClick = useCallback( - () => scrollToSection('scroll-marker-you-may-also-like'), - [scrollToSection] - ); + const handleYouMayAlsoLikeClick = useCallback(() => { + dispatch(minimizeModalMedia()); + scrollToSection('scroll-marker-you-may-also-like'); + }, [scrollToSection, dispatch]); const scrollPositionRef = useRef(0); const prevScrollPositionRef = useRef(0); // 이전 스크롤 위치 추적 const prevScrollTopRef = useRef(0); // HomePanel 스타일 스크롤 위치 추적 @@ -644,6 +644,8 @@ export default function ProductAllSection({ const mediaMinimizedRef = useRef(false); const handleArrowClickAlternative = useCallback(() => { + dispatch(minimizeModalMedia()); + const currentHeight = scrollPositionRef.current; const scrollAmount = 200; @@ -659,7 +661,7 @@ export default function ProductAllSection({ if (isAtBottom) { setIsBottom(isAtBottom); } - }, [documentHeight, scrollTop]); + }, [documentHeight, scrollTop, dispatch]); const handleScroll = useCallback( (e) => { diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.jsx b/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.jsx index 4ec4a678..0f05da83 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.jsx @@ -54,7 +54,7 @@ export default function ProductVideo({ // autoPlay 기능: 컴포넌트 마운트 시 자동으로 비디오 재생 useEffect(() => { if (autoPlay && canPlayVideo && !hasAutoPlayed && productInfo) { - console.log('[ProductVideo] Auto-playing video'); + // console.log('[ProductVideo] Auto-playing video'); setHasAutoPlayed(true); // 짧은 딸레이 후 재생 시작 (컴포넌트 마운트 완료 후) @@ -113,7 +113,7 @@ export default function ProductVideo({ const videoContainerOnFocus = useCallback(() => { if (canPlayVideo) { setFocused(true); - console.log('[ProductVideo] Calling restoreModalMedia'); + // console.log('[ProductVideo] Calling restoreModalMedia'); // ProductVideo에 포커스가 돌아오면 비디오 복원 // dispatch(restoreModalMedia()); @@ -125,7 +125,7 @@ export default function ProductVideo({ }, [canPlayVideo, dispatch, onFocus]); const videoContainerOnBlur = useCallback(() => { - console.log('[ProductVideo] onBlur called - canPlayVideo:', canPlayVideo); + // console.log('[ProductVideo] onBlur called - canPlayVideo:', canPlayVideo); // if (canPlayVideo) { // setFocused(false); // // 포커스를 잃으면 모달 MediaPanel을 최소화하여 1px 상태로 유지 @@ -149,10 +149,10 @@ export default function ProductVideo({ // MediaPanel 비디오 클릭 핸들러 + 모달 토글 기능 const handleVideoClick = useCallback(() => { - console.log('[ProductVideo] ========== handleVideoClick 호출 =========='); - console.log('[ProductVideo] canPlayVideo:', canPlayVideo); - console.log('[ProductVideo] panels.length:', panels.length); - console.log('[ProductVideo] All panels:', JSON.stringify(panels, null, 2)); + // console.log('[ProductVideo] ========== handleVideoClick 호출 =========='); + // console.log('[ProductVideo] canPlayVideo:', canPlayVideo); + // console.log('[ProductVideo] panels.length:', panels.length); + // console.log('[ProductVideo] All panels:', JSON.stringify(panels, null, 2)); if (canPlayVideo) { const currentTopPanel = panels[panels.length - 1]; @@ -163,19 +163,19 @@ export default function ProductVideo({ currentTopPanel.name === panel_names.MEDIA_PANEL && currentTopPanel.panelInfo.modal === true; - console.log('[ProductVideo] currentTopPanel:', JSON.stringify(currentTopPanel, null, 2)); - console.log('[ProductVideo] isCurrentlyPlayingModal:', isCurrentlyPlayingModal); + // console.log('[ProductVideo] currentTopPanel:', JSON.stringify(currentTopPanel, null, 2)); + // console.log('[ProductVideo] isCurrentlyPlayingModal:', isCurrentlyPlayingModal); // modal로 재생 중이면 전체화면으로 전환 if (isCurrentlyPlayingModal) { - console.log( - '[ProductVideo] *** Switching to fullscreen mode via switchMediaToFullscreen ***' - ); + // console.log( + // '[ProductVideo] *** Switching to fullscreen mode via switchMediaToFullscreen ***' + // ); dispatch(switchMediaToFullscreen()); setModalState(false); } else { - console.log('[ProductVideo] *** Starting modal MediaPanel ***'); - console.log('[ProductVideo] productInfo:', JSON.stringify(productInfo, null, 2)); + // console.log('[ProductVideo] *** Starting modal MediaPanel ***'); + // console.log('[ProductVideo] productInfo:', JSON.stringify(productInfo, null, 2)); // 처음 재생 시작 - modal=true로 시작 dispatch( startMediaPlayer({ diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v3.jsx b/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v3.jsx index bca55589..32095f2e 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v3.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v3.jsx @@ -58,17 +58,17 @@ export default function ProductVideo({ topPanel.panelInfo.modal === true && prevModalStateRef.current === false ) { - console.log('[ProductVideo] MediaPanel returned to modal - restoring focus to ProductVideo'); + // console.log('[ProductVideo] MediaPanel returned to modal - restoring focus to ProductVideo'); // VideoPlayer의 controlsHandleAbove가 자동으로 포커스를 빼앗지 않도록 // 약간의 딜레이 후에 강제로 포커스 설정 setTimeout(() => { - console.log('[ProductVideo] Forcing focus to product-video-player'); + // console.log('[ProductVideo] Forcing focus to product-video-player'); const element = document.querySelector('[data-spotlight-id="product-video-player"]'); if (element) { // Spotlight 내부 포커스 강제 설정 Spotlight.focus('product-video-player'); - console.log('[ProductVideo] Focus set to product-video-player'); + // console.log('[ProductVideo] Focus set to product-video-player'); } }, 50); @@ -77,7 +77,7 @@ export default function ProductVideo({ // MediaPanel이 닫혔을 때 modalState를 true로 복원 if (!topPanel || topPanel.name !== panel_names.MEDIA_PANEL) { - console.log('[ProductVideo] MediaPanel closed - restoring modal state'); + // console.log('[ProductVideo] MediaPanel closed - restoring modal state'); setModalState(true); prevModalStateRef.current = null; } @@ -91,11 +91,11 @@ export default function ProductVideo({ topPanel?.name === panel_names.MEDIA_PANEL && topPanel?.panelInfo?.modal === true; if (autoPlay && canPlayVideo && !hasAutoPlayed && productInfo && !isMediaPanelAlreadyPlaying) { - console.log('[ProductVideo]-LoadingVideo 🎯 AutoPlay 시작:', { - prdtId: productInfo?.prdtId, - prdtNm: productInfo?.prdtNm, - prdtMediaUrl: productInfo?.prdtMediaUrl?.substring(0, 50), - }); + // console.log('[ProductVideo]-LoadingVideo 🎯 AutoPlay 시작:', { + // prdtId: productInfo?.prdtId, + // prdtNm: productInfo?.prdtNm, + // prdtMediaUrl: productInfo?.prdtMediaUrl?.substring(0, 50), + // }); setHasAutoPlayed(true); // 짧은 딜레이 후 재생 시작 (컴포넌트 마운트 완료 후) @@ -160,7 +160,7 @@ export default function ProductVideo({ const videoContainerOnFocus = useCallback(() => { if (canPlayVideo) { setFocused(true); - console.log('[ProductVideo] Calling restoreModalMedia'); + // console.log('[ProductVideo] Calling restoreModalMedia'); // ProductVideo에 포커스가 돌아오면 비디오 복원 // dispatch(restoreModalMedia()); @@ -172,7 +172,7 @@ export default function ProductVideo({ }, [canPlayVideo, dispatch, onFocus]); const videoContainerOnBlur = useCallback(() => { - console.log('[ProductVideo] onBlur called - canPlayVideo:', canPlayVideo); + // console.log('[ProductVideo] onBlur called - canPlayVideo:', canPlayVideo); // if (canPlayVideo) { // setFocused(false); // // 포커스를 잃으면 모달 MediaPanel을 최소화하여 1px 상태로 유지 @@ -197,10 +197,10 @@ export default function ProductVideo({ // MediaPanel 비디오 클릭 핸들러 + 모달 토글 기능 const handleVideoClick = useCallback(() => { - console.log('[ProductVideo] ========== handleVideoClick 호출 =========='); - console.log('[ProductVideo] canPlayVideo:', canPlayVideo); - console.log('[ProductVideo] panels.length:', panels.length); - console.log('[ProductVideo] All panels:', JSON.stringify(panels, null, 2)); + // console.log('[ProductVideo] ========== handleVideoClick 호출 =========='); + // console.log('[ProductVideo] canPlayVideo:', canPlayVideo); + // console.log('[ProductVideo] panels.length:', panels.length); + // console.log('[ProductVideo] All panels:', JSON.stringify(panels, null, 2)); if (canPlayVideo) { const currentTopPanel = panels[panels.length - 1]; @@ -211,19 +211,31 @@ export default function ProductVideo({ currentTopPanel.name === panel_names.MEDIA_PANEL && currentTopPanel.panelInfo.modal === true; - console.log('[ProductVideo] currentTopPanel:', JSON.stringify(currentTopPanel, null, 2)); - console.log('[ProductVideo] isCurrentlyPlayingModal:', isCurrentlyPlayingModal); + // console.log('[ProductVideo] currentTopPanel:', JSON.stringify(currentTopPanel, null, 2)); + // console.log('[ProductVideo] isCurrentlyPlayingModal:', isCurrentlyPlayingModal); // modal로 재생 중이면 전체화면으로 전환 if (isCurrentlyPlayingModal) { - console.log( - '[ProductVideo] *** Switching to fullscreen mode via switchMediaToFullscreen ***' - ); + // console.log( + // '[ProductVideo] *** Switching to fullscreen mode via switchMediaToFullscreen ***' + // ); dispatch(switchMediaToFullscreen()); setModalState(false); + + // 전체화면 전환 후 VideoPlayer에 포커스 설정 + setTimeout(() => { + // console.log('[ProductVideo] Focusing to fullscreen video player'); + const focusTarget = + document.querySelector('[data-spotlight-id="modal-video-player"]') || + document.querySelector('[data-spotlight-id="product-video-player"]'); + if (focusTarget) { + Spotlight.focus('modal-video-player'); + // console.log('[ProductVideo] Focus set to fullscreen video player'); + } + }, 50); } else { - console.log('[ProductVideo] *** Starting modal MediaPanel ***'); - console.log('[ProductVideo] productInfo:', JSON.stringify(productInfo, null, 2)); + // console.log('[ProductVideo] *** Starting modal MediaPanel ***'); + // console.log('[ProductVideo] productInfo:', JSON.stringify(productInfo, null, 2)); // 처음 재생 시작 - modal=true로 시작 setIsVideoPlaying(true); // 비디오 재생 flag 설정 if (onVideoPlaying) { diff --git a/com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx b/com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx index b9f1186b..ed424868 100644 --- a/com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx +++ b/com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx @@ -2237,7 +2237,7 @@ const MediaPanel = React.forwardRef( modalScale={panelInfo.modal ? modalScale : 1} modalClassName={panelInfo.modal && panelInfo.modalClassName} spotlightId={ - panelInfo.modal ? undefined : panelInfo.modalContainerId || spotlightId + panelInfo.modal ? 'modal-video-player' : panelInfo.modalContainerId || spotlightId } handleIndicatorDownClick={handleIndicatorDownClick} handleIndicatorUpClick={handleIndicatorUpClick}