[251115] fix: ProductVideo.v3.jsx 전체화면 전환

🕐 커밋 시간: 2025. 11. 15. 14:47:47

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

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/actions/deviceActions.js
  ~ com.twin.app.shoptime/src/actions/homeActions.js
  ~ com.twin.app.shoptime/src/actions/mainActions.js
  ~ com.twin.app.shoptime/src/actions/productActions.js
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.jsx
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v3.jsx
  ~ com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx

🔧 함수 변경 내용:
  📄 com.twin.app.shoptime/src/actions/homeActions.js (javascript):
    🔄 Modified: checkEnterThroughGNB()
     Deleted: clearPersistentVideoInfo()
  📄 com.twin.app.shoptime/src/actions/productActions.js (javascript):
    🔄 Modified: createGetThunk(), getVideoIndicatorFocus()
     Deleted: fetchAllReviewsWithSequentialPaging()
  📄 com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx (javascript):
    🔄 Modified: extractProductMeta()
  📄 com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.jsx (javascript):
    🔄 Modified: Spottable()
  📄 com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v3.jsx (javascript):
    🔄 Modified: Spottable()
  📄 com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.v3.jsx (javascript):
    🔄 Modified: normalizeModalStyle()

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
This commit is contained in:
2025-11-15 14:47:48 +09:00
parent e474ac3ef2
commit 98dde0d6a0
8 changed files with 426 additions and 451 deletions

View File

@@ -1,14 +1,9 @@
import { URLS } from "../api/apiConfig"; import { URLS } from '../api/apiConfig';
import { import { runDelayedAction, setTokenRefreshing, TAxios, TAxiosAdvancedPromise } from '../api/TAxios';
runDelayedAction, import * as lunaSend from '../lunaSend';
setTokenRefreshing, import { types } from './actionTypes';
TAxios, import { changeLocalSettings } from './commonActions';
TAxiosAdvancedPromise, import { fetchCurrentUserHomeTerms } from './homeActions';
} 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 MAX_RETRY_COUNT = 3;
const RETRY_DELAY = 2000; // 2 seconds const RETRY_DELAY = 2000; // 2 seconds
@@ -17,7 +12,7 @@ const RETRY_DELAY = 2000; // 2 seconds
export const getAuthenticationCode = () => (dispatch, getState) => { export const getAuthenticationCode = () => (dispatch, getState) => {
setTokenRefreshing(true); setTokenRefreshing(true);
const onSuccess = (response) => { const onSuccess = (response) => {
console.log("getAuthenticationCode onSuccess: ", response.data); console.log('getAuthenticationCode onSuccess: ', response.data);
const accessToken = response.data.data.accessToken; const accessToken = response.data.data.accessToken;
const refreshToken = response.data.data.refreshToken ?? null; const refreshToken = response.data.data.refreshToken ?? null;
@@ -27,21 +22,11 @@ export const getAuthenticationCode = () => (dispatch, getState) => {
}; };
const onFail = (error) => { const onFail = (error) => {
console.error("getAuthenticationCode onFail: ", error); console.error('getAuthenticationCode onFail: ', error);
setTokenRefreshing(false); setTokenRefreshing(false);
}; };
TAxios( TAxios(dispatch, getState, 'get', URLS.GET_AUTHENTICATION_CODE, {}, {}, onSuccess, onFail, true);
dispatch,
getState,
"get",
URLS.GET_AUTHENTICATION_CODE,
{},
{},
onSuccess,
onFail,
true
);
}; };
// IF-LGSP-001 디바이스 등록 및 약관 동의 // IF-LGSP-001 디바이스 등록 및 약관 동의
@@ -50,7 +35,7 @@ export const registerDevice =
const { agreeTerms } = params; const { agreeTerms } = params;
const onSuccess = (response) => { const onSuccess = (response) => {
console.log("registerDevice onSuccess: ", response.data); console.log('registerDevice onSuccess: ', response.data);
dispatch({ dispatch({
type: types.REGISTER_DEVICE, type: types.REGISTER_DEVICE,
@@ -65,7 +50,7 @@ export const registerDevice =
}; };
const onFail = (error) => { const onFail = (error) => {
console.error("registerDevice onFail: ", error); console.error('registerDevice onFail: ', error);
if (onFailCallback) { if (onFailCallback) {
onFailCallback(error); onFailCallback(error);
} }
@@ -74,7 +59,7 @@ export const registerDevice =
TAxios( TAxios(
dispatch, dispatch,
getState, getState,
"post", 'post',
URLS.REGISTER_DEVICE, URLS.REGISTER_DEVICE,
{}, {},
{ agreeTerms }, { agreeTerms },
@@ -89,7 +74,7 @@ export const registerDeviceInfo = (params) => (dispatch, getState) => {
const { evntTpCd, evntId, evntApplcnFlag, entryMenu, mbphNo } = params; const { evntTpCd, evntId, evntApplcnFlag, entryMenu, mbphNo } = params;
const onSuccess = (response) => { const onSuccess = (response) => {
console.log("registerDeviceInfo onSuccess: ", response.data); console.log('registerDeviceInfo onSuccess: ', response.data);
dispatch({ dispatch({
type: types.REGISTER_DEVICE_INFO, type: types.REGISTER_DEVICE_INFO,
@@ -99,13 +84,13 @@ export const registerDeviceInfo = (params) => (dispatch, getState) => {
}; };
const onFail = (error) => { const onFail = (error) => {
console.error("registerDeviceInfo onFail: ", error); console.error('registerDeviceInfo onFail: ', error);
}; };
TAxios( TAxios(
dispatch, dispatch,
getState, getState,
"post", 'post',
URLS.REGISTER_DEVICE_INFO, URLS.REGISTER_DEVICE_INFO,
{}, {},
{ evntTpCd, evntId, evntApplcnFlag, entryMenu, mbphNo }, { evntTpCd, evntId, evntApplcnFlag, entryMenu, mbphNo },
@@ -117,7 +102,7 @@ export const registerDeviceInfo = (params) => (dispatch, getState) => {
// 디바이스 부가 정보 조회 IF-LGSP-003 // 디바이스 부가 정보 조회 IF-LGSP-003
export const getDeviceAdditionInfo = () => (dispatch, getState) => { export const getDeviceAdditionInfo = () => (dispatch, getState) => {
const onSuccess = (response) => { const onSuccess = (response) => {
console.log("getDeviceAdditionInfo onSuccess: ", response.data); console.log('getDeviceAdditionInfo onSuccess: ', response.data);
dispatch({ dispatch({
type: types.GET_DEVICE_INFO, type: types.GET_DEVICE_INFO,
@@ -126,26 +111,17 @@ export const getDeviceAdditionInfo = () => (dispatch, getState) => {
}; };
const onFail = (error) => { const onFail = (error) => {
console.error("getDeviceAdditionInfo onFail: ", error); console.error('getDeviceAdditionInfo onFail: ', error);
}; };
TAxios( TAxios(dispatch, getState, 'get', URLS.GET_DEVICE_INFO, {}, {}, onSuccess, onFail);
dispatch,
getState,
"get",
URLS.GET_DEVICE_INFO,
{},
{},
onSuccess,
onFail
);
}; };
// 인증번호 재요청 IF-LGSP-096 // 인증번호 재요청 IF-LGSP-096
export const getReAuthenticationCode = () => (dispatch, getState) => { export const getReAuthenticationCode = () => (dispatch, getState) => {
setTokenRefreshing(true); setTokenRefreshing(true);
const onSuccess = (response) => { const onSuccess = (response) => {
console.log("getReAuthenticationCode onSuccess: ", response.data); // console.log("getReAuthenticationCode onSuccess: ", response.data);
const accessToken = response.data.data.accessToken; const accessToken = response.data.data.accessToken;
dispatch(changeLocalSettings({ accessToken })); dispatch(changeLocalSettings({ accessToken }));
setTokenRefreshing(false); setTokenRefreshing(false);
@@ -153,14 +129,14 @@ export const getReAuthenticationCode = () => (dispatch, getState) => {
}; };
const onFail = (error) => { const onFail = (error) => {
console.error("getReAuthenticationCode onFail: ", error); console.error('getReAuthenticationCode onFail: ', error);
setTokenRefreshing(false); setTokenRefreshing(false);
}; };
TAxios( TAxios(
dispatch, dispatch,
getState, getState,
"get", 'get',
URLS.GET_RE_AUTHENTICATION_CODE, URLS.GET_RE_AUTHENTICATION_CODE,
{}, {},
{}, {},

View File

@@ -1,62 +1,67 @@
import { URLS } from "../api/apiConfig"; import { URLS } from '../api/apiConfig';
import { TAxios,TAxiosPromise } from "../api/TAxios"; import { TAxios, TAxiosPromise } from '../api/TAxios';
import { types } from "./actionTypes"; import { types } from './actionTypes';
import { changeAppStatus, getTermsAgreeYn } from "./commonActions"; import { changeAppStatus, getTermsAgreeYn } from './commonActions';
import { collectBannerPositions } from "../utils/domUtils"; import { collectBannerPositions } from '../utils/domUtils';
// 약관 정보 조회 IF-LGSP-005 // 약관 정보 조회 IF-LGSP-005
export const getHomeTerms = (props) => (dispatch, getState) => { export const getHomeTerms = (props) => (dispatch, getState) => {
const { trmsTpCdList, mbrNo } = props; const { trmsTpCdList, mbrNo } = props;
const onSuccess = (response) => { const onSuccess = (response) => {
console.log("getHomeTerms onSuccess ", response.data); console.log('getHomeTerms onSuccess ', response.data);
if (response.data.retCode === 0) { if (response.data.retCode === 0) {
dispatch({ dispatch({
type: types.GET_HOME_TERMS, type: types.GET_HOME_TERMS,
payload: response.data, payload: response.data,
}); });
// 약관 ID 매핑을 별도로 생성하여 저장 // 약관 ID 매핑을 별도로 생성하여 저장
// Chromium68 호환성을 위해 Optional Chaining 제거 // Chromium68 호환성을 위해 Optional Chaining 제거
if (response.data && response.data.data && response.data.data.terms) { if (response.data && response.data.data && response.data.data.terms) {
const termsIdMap = {}; const termsIdMap = {};
let hasOptionalTerms = false; // MST00405 존재 여부 확인 let hasOptionalTerms = false; // MST00405 존재 여부 확인
response.data.data.terms.forEach(term => { response.data.data.terms.forEach((term) => {
if (term.trmsTpCd && term.trmsId) { if (term.trmsTpCd && term.trmsId) {
termsIdMap[term.trmsTpCd] = term.trmsId; termsIdMap[term.trmsTpCd] = term.trmsId;
} }
// MST00405 선택약관 존재 여부 확인 // MST00405 선택약관 존재 여부 확인
if (term.trmsTpCd === "MST00405") { if (term.trmsTpCd === 'MST00405') {
hasOptionalTerms = true; hasOptionalTerms = true;
} }
}); });
dispatch({ dispatch({
type: types.SET_TERMS_ID_MAP, type: types.SET_TERMS_ID_MAP,
payload: termsIdMap, payload: termsIdMap,
}); });
// 선택약관 존재 여부 상태 설정 // 선택약관 존재 여부 상태 설정
// TODO: 테스트용 - 임시로 false 강제 설정 // TODO: 테스트용 - 임시로 false 강제 설정
const forceDisableOptionalTerms = false; // 테스트 완료 후 false로 변경 const forceDisableOptionalTerms = false; // 테스트 완료 후 false로 변경
const finalOptionalTermsValue = forceDisableOptionalTerms ? false : hasOptionalTerms; const finalOptionalTermsValue = forceDisableOptionalTerms ? false : hasOptionalTerms;
dispatch({ dispatch({
type: types.SET_OPTIONAL_TERMS_AVAILABILITY, type: types.SET_OPTIONAL_TERMS_AVAILABILITY,
payload: finalOptionalTermsValue, payload: finalOptionalTermsValue,
}); });
console.log("[optionalTermsAvailable] 실제값:", hasOptionalTerms, "강제설정값:", finalOptionalTermsValue); console.log(
'[optionalTermsAvailable] 실제값:',
if (process.env.NODE_ENV === "development") { hasOptionalTerms,
console.log("약관 ID 매핑 생성:", termsIdMap); '강제설정값:',
console.log("선택약관 존재 여부:", hasOptionalTerms); finalOptionalTermsValue
);
if (process.env.NODE_ENV === 'development') {
console.log('약관 ID 매핑 생성:', termsIdMap);
console.log('선택약관 존재 여부:', hasOptionalTerms);
} }
} }
setTimeout(() => { setTimeout(() => {
dispatch(getTermsAgreeYn()); dispatch(getTermsAgreeYn());
}, 0); }, 0);
@@ -64,13 +69,13 @@ export const getHomeTerms = (props) => (dispatch, getState) => {
}; };
const onFail = (error) => { const onFail = (error) => {
console.error("getHomeTerms onFail ", error); console.error('getHomeTerms onFail ', error);
}; };
TAxios( TAxios(
dispatch, dispatch,
getState, getState,
"get", 'get',
URLS.GET_HOME_TERMS, URLS.GET_HOME_TERMS,
{ trmsTpCdList, mbrNo }, { trmsTpCdList, mbrNo },
{}, {},
@@ -82,64 +87,71 @@ export const getHomeTerms = (props) => (dispatch, getState) => {
// 현재 로그인 사용자 기준으로 약관 정보 조회 (인자 없이 호출 가능) // 현재 로그인 사용자 기준으로 약관 정보 조회 (인자 없이 호출 가능)
export const fetchCurrentUserHomeTerms = () => (dispatch, getState) => { export const fetchCurrentUserHomeTerms = () => (dispatch, getState) => {
const loginUserData = getState().common.appStatus.loginUserData; const loginUserData = getState().common.appStatus.loginUserData;
if (!loginUserData || !loginUserData.userNumber) { 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 }); dispatch({ type: types.GET_TERMS_AGREE_YN_FAILURE });
return; return;
} }
const mbrNo = loginUserData.userNumber; const mbrNo = loginUserData.userNumber;
const trmsTpCdList = "MST00401, MST00402, MST00405"; // 기본 약관 코드 리스트 const trmsTpCdList = 'MST00401, MST00402, MST00405'; // 기본 약관 코드 리스트
const onSuccess = (response) => { const onSuccess = (response) => {
console.log("fetchCurrentUserHomeTerms onSuccess ", response.data); console.log('fetchCurrentUserHomeTerms onSuccess ', response.data);
if (response.data.retCode === 0) { if (response.data.retCode === 0) {
dispatch({ dispatch({
type: types.GET_HOME_TERMS, // 기존 GET_HOME_TERMS 타입을 재사용 type: types.GET_HOME_TERMS, // 기존 GET_HOME_TERMS 타입을 재사용
payload: response.data, payload: response.data,
}); });
// 약관 ID 매핑을 별도로 생성하여 저장 // 약관 ID 매핑을 별도로 생성하여 저장
// Chromium68 호환성을 위해 Optional Chaining 제거 // Chromium68 호환성을 위해 Optional Chaining 제거
if (response.data && response.data.data && response.data.data.terms) { if (response.data && response.data.data && response.data.data.terms) {
const termsIdMap = {}; const termsIdMap = {};
let hasOptionalTerms = false; // MST00405 존재 여부 확인 let hasOptionalTerms = false; // MST00405 존재 여부 확인
response.data.data.terms.forEach(term => { response.data.data.terms.forEach((term) => {
if (term.trmsTpCd && term.trmsId) { if (term.trmsTpCd && term.trmsId) {
termsIdMap[term.trmsTpCd] = term.trmsId; termsIdMap[term.trmsTpCd] = term.trmsId;
} }
// MST00405 선택약관 존재 여부 확인 // MST00405 선택약관 존재 여부 확인
if (term.trmsTpCd === "MST00405") { if (term.trmsTpCd === 'MST00405') {
hasOptionalTerms = true; hasOptionalTerms = true;
} }
}); });
dispatch({ dispatch({
type: types.SET_TERMS_ID_MAP, type: types.SET_TERMS_ID_MAP,
payload: termsIdMap, payload: termsIdMap,
}); });
// 선택약관 존재 여부 상태 설정 // 선택약관 존재 여부 상태 설정
// TODO: 테스트용 - 임시로 false 강제 설정 // TODO: 테스트용 - 임시로 false 강제 설정
const forceDisableOptionalTerms = false; // 테스트 완료 후 false로 변경 const forceDisableOptionalTerms = false; // 테스트 완료 후 false로 변경
const finalOptionalTermsValue = forceDisableOptionalTerms ? false : hasOptionalTerms; const finalOptionalTermsValue = forceDisableOptionalTerms ? false : hasOptionalTerms;
dispatch({ dispatch({
type: types.SET_OPTIONAL_TERMS_AVAILABILITY, type: types.SET_OPTIONAL_TERMS_AVAILABILITY,
payload: finalOptionalTermsValue, payload: finalOptionalTermsValue,
}); });
console.log("[optionalTermsAvailable] 실제값:", hasOptionalTerms, "강제설정값:", finalOptionalTermsValue); console.log(
'[optionalTermsAvailable] 실제값:',
hasOptionalTerms,
'강제설정값:',
finalOptionalTermsValue
);
if (process.env.NODE_ENV === "development") { if (process.env.NODE_ENV === 'development') {
console.log("약관 ID 매핑 생성:", termsIdMap); console.log('약관 ID 매핑 생성:', termsIdMap);
console.log("선택약관 존재 여부:", hasOptionalTerms); console.log('선택약관 존재 여부:', hasOptionalTerms);
} }
} }
// getHomeTerms와 동일하게 getTermsAgreeYn 후속 처리 // getHomeTerms와 동일하게 getTermsAgreeYn 후속 처리
setTimeout(() => { setTimeout(() => {
dispatch(getTermsAgreeYn()); dispatch(getTermsAgreeYn());
@@ -149,129 +161,129 @@ export const fetchCurrentUserHomeTerms = () => (dispatch, getState) => {
dispatch({ type: types.GET_TERMS_AGREE_YN_FAILURE }); dispatch({ type: types.GET_TERMS_AGREE_YN_FAILURE });
} }
}; };
const onFail = (error) => { const onFail = (error) => {
console.error("fetchCurrentUserHomeTerms onFail ", error); console.error('fetchCurrentUserHomeTerms onFail ', error);
dispatch({ type: types.GET_TERMS_AGREE_YN_FAILURE }); dispatch({ type: types.GET_TERMS_AGREE_YN_FAILURE });
}; };
TAxios( TAxios(
dispatch, dispatch,
getState, getState,
"get", 'get',
URLS.GET_HOME_TERMS, // 동일한 API 엔드포인트 사용 URLS.GET_HOME_TERMS, // 동일한 API 엔드포인트 사용
{ trmsTpCdList, mbrNo }, { trmsTpCdList, mbrNo },
{}, {},
onSuccess, onSuccess,
onFail onFail
); );
}; };
// 기존 TAxios 패턴과 일치하는 안전한 Redux Action // 기존 TAxios 패턴과 일치하는 안전한 Redux Action
export const fetchCurrentUserHomeTermsSafe = () => async (dispatch, getState) => { export const fetchCurrentUserHomeTermsSafe = () => async (dispatch, getState) => {
const loginUserData = getState().common.appStatus.loginUserData; const loginUserData = getState().common.appStatus.loginUserData;
if (!loginUserData || !loginUserData.userNumber) { 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 }); dispatch({ type: types.GET_TERMS_AGREE_YN_FAILURE });
return { success: false, message: "사용자 정보가 없습니다." }; return { success: false, message: '사용자 정보가 없습니다.' };
} }
const mbrNo = loginUserData.userNumber; const mbrNo = loginUserData.userNumber;
const trmsTpCdList = "MST00401, MST00402, MST00405"; const trmsTpCdList = 'MST00401, MST00402, MST00405';
console.log("Fetching home terms for user:", mbrNo); console.log('Fetching home terms for user:', mbrNo);
// 안전한 API 호출 (기존 TAxios 패턴과 동일) // 안전한 API 호출 (기존 TAxios 패턴과 동일)
const result = await TAxiosPromise( const result = await TAxiosPromise(dispatch, getState, 'get', URLS.GET_HOME_TERMS, {
dispatch, trmsTpCdList,
getState, mbrNo,
"get", });
URLS.GET_HOME_TERMS,
{ trmsTpCdList, mbrNo }
);
// 네트워크 에러인 경우 // 네트워크 에러인 경우
if (!result.success) { 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 }); dispatch({ type: types.GET_TERMS_AGREE_YN_FAILURE });
return { success: false, message: "네트워크 오류가 발생했습니다." }; return { success: false, message: '네트워크 오류가 발생했습니다.' };
} }
// 기존 TAxios처럼 특별한 retCode들은 TAxios 내부에서 이미 처리됨 // 기존 TAxios처럼 특별한 retCode들은 TAxios 내부에서 이미 처리됨
// (401, 402, 501, 602, 603, 604 등은 TAxios에서 알아서 처리하고 onSuccess가 호출되지 않음) // (401, 402, 501, 602, 603, 604 등은 TAxios에서 알아서 처리하고 onSuccess가 호출되지 않음)
console.log("fetchCurrentUserHomeTerms response:", result.data); console.log('fetchCurrentUserHomeTerms response:', result.data);
// 정상적으로 onSuccess가 호출된 경우에만 여기까지 옴 // 정상적으로 onSuccess가 호출된 경우에만 여기까지 옴
if (result.data && result.data.retCode === 0) { if (result.data && result.data.retCode === 0) {
dispatch({ dispatch({
type: types.GET_HOME_TERMS, type: types.GET_HOME_TERMS,
payload: result.data, payload: result.data,
}); });
// 약관 ID 매핑을 별도로 생성하여 저장 // 약관 ID 매핑을 별도로 생성하여 저장
// Chromium68 호환성을 위해 Optional Chaining 제거 // Chromium68 호환성을 위해 Optional Chaining 제거
if (result.data && result.data.data && result.data.data.terms) { if (result.data && result.data.data && result.data.data.terms) {
const termsIdMap = {}; const termsIdMap = {};
let hasOptionalTerms = false; // MST00405 존재 여부 확인 let hasOptionalTerms = false; // MST00405 존재 여부 확인
result.data.data.terms.forEach(term => { result.data.data.terms.forEach((term) => {
if (term.trmsTpCd && term.trmsId) { if (term.trmsTpCd && term.trmsId) {
termsIdMap[term.trmsTpCd] = term.trmsId; termsIdMap[term.trmsTpCd] = term.trmsId;
} }
// MST00405 선택약관 존재 여부 확인 // MST00405 선택약관 존재 여부 확인
if (term.trmsTpCd === "MST00405") { if (term.trmsTpCd === 'MST00405') {
hasOptionalTerms = true; hasOptionalTerms = true;
} }
}); });
dispatch({ dispatch({
type: types.SET_TERMS_ID_MAP, type: types.SET_TERMS_ID_MAP,
payload: termsIdMap, payload: termsIdMap,
}); });
// 선택약관 존재 여부 상태 설정 2025-07-03 // 선택약관 존재 여부 상태 설정 2025-07-03
// TODO: 테스트용 - 임시로 false 강제 설정 // TODO: 테스트용 - 임시로 false 강제 설정
const forceDisableOptionalTerms = false; // 테스트 완료 후 false로 변경 const forceDisableOptionalTerms = false; // 테스트 완료 후 false로 변경
const finalOptionalTermsValue = forceDisableOptionalTerms ? false : hasOptionalTerms; const finalOptionalTermsValue = forceDisableOptionalTerms ? false : hasOptionalTerms;
dispatch({ dispatch({
type: types.SET_OPTIONAL_TERMS_AVAILABILITY, type: types.SET_OPTIONAL_TERMS_AVAILABILITY,
payload: finalOptionalTermsValue, payload: finalOptionalTermsValue,
}); });
if (process.env.NODE_ENV === "development") { if (process.env.NODE_ENV === 'development') {
console.log("약관 ID 매핑 생성:", termsIdMap); console.log('약관 ID 매핑 생성:', termsIdMap);
console.log("선택약관 존재 여부 - 실제값:", hasOptionalTerms, "강제설정값:", finalOptionalTermsValue); console.log(
'선택약관 존재 여부 - 실제값:',
hasOptionalTerms,
'강제설정값:',
finalOptionalTermsValue
);
} }
} }
// 후속 액션 호출 (기존과 동일) // 후속 액션 호출 (기존과 동일)
setTimeout(() => { setTimeout(() => {
dispatch(getTermsAgreeYn()); dispatch(getTermsAgreeYn());
}, 0); }, 0);
return { success: true, data: result.data }; return { success: true, data: result.data };
} else { } else {
// retCode가 0이 아닌 일반적인 API 에러 // retCode가 0이 아닌 일반적인 API 에러
// Chromium68 호환성을 위해 Optional Chaining 제거 // 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 }); dispatch({ type: types.GET_TERMS_AGREE_YN_FAILURE });
return { return {
success: false, success: false,
message: (result.data && result.data.retMsg) || "서버 오류가 발생했습니다." message: (result.data && result.data.retMsg) || '서버 오류가 발생했습니다.',
}; };
} }
}; };
// 메뉴 목록 조회 IF-LGSP-044 // 메뉴 목록 조회 IF-LGSP-044
export const getHomeMenu = () => (dispatch, getState) => { export const getHomeMenu = () => (dispatch, getState) => {
const onSuccess = (response) => { const onSuccess = (response) => {
console.log("getHomeMenu onSuccess ", response.data); // console.log("getHomeMenu onSuccess ", response.data);
dispatch({ dispatch({
type: types.GET_HOME_MENU, type: types.GET_HOME_MENU,
@@ -280,29 +292,20 @@ export const getHomeMenu = () => (dispatch, getState) => {
}; };
const onFail = (error) => { const onFail = (error) => {
console.error("getHomeMenu onFail ", error); console.error('getHomeMenu onFail ', error);
}; };
TAxios( TAxios(dispatch, getState, 'get', URLS.GET_HOME_MENU, {}, {}, onSuccess, onFail);
dispatch,
getState,
"get",
URLS.GET_HOME_MENU,
{},
{},
onSuccess,
onFail
);
}; };
// 테마 전시 정보 상세 조회 IF-LGSP-060 // 테마 전시 정보 상세 조회 IF-LGSP-060
export const getThemeCurationDetailInfo = (params) => (dispatch, getState) => { export const getThemeCurationDetailInfo = (params) => (dispatch, getState) => {
const { patnrId, curationId, bgImgNo } = params; const { patnrId, curationId, bgImgNo } = params;
dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: "wait" } })); dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } }));
const onSuccess = (response) => { const onSuccess = (response) => {
console.log("getThemeCurationDetailInfo onSuccess", response.data); console.log('getThemeCurationDetailInfo onSuccess', response.data);
dispatch({ dispatch({
type: types.GET_THEME_CURATION_DETAIL_INFO, type: types.GET_THEME_CURATION_DETAIL_INFO,
@@ -313,14 +316,14 @@ export const getThemeCurationDetailInfo = (params) => (dispatch, getState) => {
}; };
const onFail = (error) => { const onFail = (error) => {
console.error("getThemeCurationDetailInfo onFail", error); console.error('getThemeCurationDetailInfo onFail', error);
dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
}; };
TAxios( TAxios(
dispatch, dispatch,
getState, getState,
"get", 'get',
URLS.GET_THEME_CURATION_DETAIL_INFO, URLS.GET_THEME_CURATION_DETAIL_INFO,
{ patnrId, curationId, bgImgNo }, { patnrId, curationId, bgImgNo },
{}, {},
@@ -332,10 +335,10 @@ export const getThemeCurationDetailInfo = (params) => (dispatch, getState) => {
export const getThemeHotelDetailInfo = (params) => (dispatch, getState) => { export const getThemeHotelDetailInfo = (params) => (dispatch, getState) => {
const { patnrId, curationId } = params; const { patnrId, curationId } = params;
dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: "wait" } })); dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } }));
const onSuccess = (response) => { const onSuccess = (response) => {
console.log("getThemeHotelDetailInfo onSuccess", response.data); console.log('getThemeHotelDetailInfo onSuccess', response.data);
dispatch({ dispatch({
type: types.GET_THEME_HOTEL_DETAIL_INFO, type: types.GET_THEME_HOTEL_DETAIL_INFO,
@@ -346,14 +349,14 @@ export const getThemeHotelDetailInfo = (params) => (dispatch, getState) => {
}; };
const onFail = (error) => { const onFail = (error) => {
console.error("getThemeHotelDetailInfo onFail", error); console.error('getThemeHotelDetailInfo onFail', error);
dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
}; };
TAxios( TAxios(
dispatch, dispatch,
getState, getState,
"get", 'get',
URLS.GET_THEME_HOTEL_DETAIL_INFO, URLS.GET_THEME_HOTEL_DETAIL_INFO,
{ patnrId, curationId }, { patnrId, curationId },
{}, {},
@@ -364,7 +367,7 @@ export const getThemeHotelDetailInfo = (params) => (dispatch, getState) => {
// HOME LAYOUT 정보 조회 IF-LGSP-300 // HOME LAYOUT 정보 조회 IF-LGSP-300
export const getHomeLayout = () => (dispatch, getState) => { export const getHomeLayout = () => (dispatch, getState) => {
const onSuccess = (response) => { const onSuccess = (response) => {
console.log("getHomeLayout onSuccess", response.data); console.log('getHomeLayout onSuccess', response.data);
dispatch({ dispatch({
type: types.GET_HOME_LAYOUT, type: types.GET_HOME_LAYOUT,
@@ -374,57 +377,39 @@ export const getHomeLayout = () => (dispatch, getState) => {
}; };
const onFail = (error) => { const onFail = (error) => {
console.error("getHomeLayout onFail", error); console.error('getHomeLayout onFail', error);
dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
}; };
TAxios( TAxios(dispatch, getState, 'get', URLS.GET_HOME_LAYOUT, {}, {}, onSuccess, onFail);
dispatch,
getState,
"get",
URLS.GET_HOME_LAYOUT,
{},
{},
onSuccess,
onFail
);
}; };
// HOME Main Contents Banner 정보 조회 IF-LGSP-301 // HOME Main Contents Banner 정보 조회 IF-LGSP-301
export const getHomeMainContents = () => (dispatch, getState) => { export const getHomeMainContents = () => (dispatch, getState) => {
const onSuccess = (response) => { const onSuccess = (response) => {
console.log("getHomeMainContents onSuccess", response.data); console.log('getHomeMainContents onSuccess', response.data);
dispatch({ dispatch({
type: types.GET_HOME_MAIN_CONTENTS, type: types.GET_HOME_MAIN_CONTENTS,
payload: response.data.data, payload: response.data.data,
status: "fulfilled", status: 'fulfilled',
}); });
dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
}; };
const onFail = (error) => { const onFail = (error) => {
console.error("getHomeMainContents onFail", error); console.error('getHomeMainContents onFail', error);
dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
}; };
TAxios( TAxios(dispatch, getState, 'get', URLS.GET_HOME_MAIN_CONTENTS, {}, {}, onSuccess, onFail);
dispatch,
getState,
"get",
URLS.GET_HOME_MAIN_CONTENTS,
{},
{},
onSuccess,
onFail
);
}; };
// Theme 전시 정보 조회 : IF-LGSP-045 // Theme 전시 정보 조회 : IF-LGSP-045
export const getThemeCurationInfo = () => (dispatch, getState) => { export const getThemeCurationInfo = () => (dispatch, getState) => {
const onSuccess = (response) => { const onSuccess = (response) => {
console.log("getThemeCurationInfo onSuccess", response.data); console.log('getThemeCurationInfo onSuccess', response.data);
dispatch({ dispatch({
type: types.GET_THEME_CURATION_INFO, type: types.GET_THEME_CURATION_INFO,
@@ -435,30 +420,21 @@ export const getThemeCurationInfo = () => (dispatch, getState) => {
}; };
const onFail = (error) => { const onFail = (error) => {
console.error("getThemeCurationInfo onFail", error); console.error('getThemeCurationInfo onFail', error);
dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
}; };
TAxios( TAxios(dispatch, getState, 'get', URLS.GET_THEME_CURATION_INFO, {}, {}, onSuccess, onFail);
dispatch,
getState,
"get",
URLS.GET_THEME_CURATION_INFO,
{},
{},
onSuccess,
onFail
);
}; };
// 테마 메뉴(=테마 페이지) 선반 조회 : IF-LGSP-095 // 테마 메뉴(=테마 페이지) 선반 조회 : IF-LGSP-095
export const getThemeMenuShelfInfo = (props) => (dispatch, getState) => { export const getThemeMenuShelfInfo = (props) => (dispatch, getState) => {
const { curationId } = props; const { curationId } = props;
dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: "wait" } })); dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } }));
const onSuccess = (response) => { const onSuccess = (response) => {
console.log("getThemeMenuShelfInfo onSuccess", response.data); console.log('getThemeMenuShelfInfo onSuccess', response.data);
dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
dispatch({ dispatch({
type: types.GET_THEME_MENU_SHELF_INFO, type: types.GET_THEME_MENU_SHELF_INFO,
@@ -467,14 +443,14 @@ export const getThemeMenuShelfInfo = (props) => (dispatch, getState) => {
}; };
const onFail = (error) => { const onFail = (error) => {
console.error("getThemeMenuShelfInfo onFail", error); console.error('getThemeMenuShelfInfo onFail', error);
dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
}; };
TAxios( TAxios(
dispatch, dispatch,
getState, getState,
"get", 'get',
URLS.GET_THEME_MENU_SHELF_INFO, URLS.GET_THEME_MENU_SHELF_INFO,
{ curationId }, { curationId },
{}, {},
@@ -514,8 +490,8 @@ export const checkEnterThroughGNB = (boolean) => ({
export const setBannerIndex = (bannerId, index) => { export const setBannerIndex = (bannerId, index) => {
if (!bannerId) { if (!bannerId) {
console.warn("setBannerIndex called with undefined bannerId"); console.warn('setBannerIndex called with undefined bannerId');
return { type: "NO_OP" }; return { type: 'NO_OP' };
} }
return { return {
type: types.SET_BANNER_INDEX, type: types.SET_BANNER_INDEX,
@@ -568,11 +544,11 @@ export const collectAndSaveBannerPositions = (bannerIds) => async (dispatch) =>
try { try {
const positions = await collectBannerPositions(bannerIds); const positions = await collectBannerPositions(bannerIds);
dispatch(setBannerPositions(positions)); dispatch(setBannerPositions(positions));
if (process.env.NODE_ENV === "development") { if (process.env.NODE_ENV === 'development') {
console.log("[homeActions] 배너 위치 수집 완료:", positions); console.log('[homeActions] 배너 위치 수집 완료:', positions);
} }
} catch (error) { } catch (error) {
console.error("[homeActions] 배너 위치 수집 실패:", error); console.error('[homeActions] 배너 위치 수집 실패:', error);
} }
}; };

View File

@@ -1,18 +1,10 @@
import { URLS } from '../api/apiConfig'; import { URLS } from '../api/apiConfig';
import { TAxios } from '../api/TAxios'; import { TAxios } from '../api/TAxios';
import { convertUtcToLocal } from '../components/MediaPlayer/util'; import { convertUtcToLocal } from '../components/MediaPlayer/util';
import { import { CATEGORY_DATA_MAX_RESULTS_LIMIT, LOG_CONTEXT_NAME, LOG_MESSAGE_ID } from '../utils/Config';
CATEGORY_DATA_MAX_RESULTS_LIMIT,
LOG_CONTEXT_NAME,
LOG_MESSAGE_ID,
} from '../utils/Config';
import * as HelperMethods from '../utils/helperMethods'; import * as HelperMethods from '../utils/helperMethods';
import { types } from './actionTypes'; import { types } from './actionTypes';
import { import { addReservation, changeAppStatus, deleteReservation } from './commonActions';
addReservation,
changeAppStatus,
deleteReservation,
} from './commonActions';
//IF-LGSP-007 //IF-LGSP-007
export const getMainLiveShow = (props) => (dispatch, getState) => { export const getMainLiveShow = (props) => (dispatch, getState) => {
@@ -233,7 +225,7 @@ export const getSubCategory =
getState, getState,
'get', 'get',
URLS.GET_SUB_CATEGORY, URLS.GET_SUB_CATEGORY,
{ lgCatCd, patnrIdList, pageSize, pageNo, tabType, filterType,recommendIncFlag }, { lgCatCd, patnrIdList, pageSize, pageNo, tabType, filterType, recommendIncFlag },
{}, {},
onSuccess, onSuccess,
onFail onFail
@@ -435,7 +427,7 @@ export const getMainLiveShowNowProduct =
({ patnrId, showId, lstChgDt }) => ({ patnrId, showId, lstChgDt }) =>
(dispatch, getState) => { (dispatch, getState) => {
const onSuccess = (response) => { const onSuccess = (response) => {
console.log('getMainLiveShowNowProduct onSuccess', response.data); // console.log('getMainLiveShowNowProduct onSuccess', response.data);
dispatch({ dispatch({
type: types.GET_MAIN_LIVE_SHOW_NOW_PRODUCT, type: types.GET_MAIN_LIVE_SHOW_NOW_PRODUCT,

View File

@@ -169,15 +169,15 @@ export const getProductOption = createGetThunk({
// IF-LGSP-101용 API 응답에서 reviewList + reviewDetail 추출 // IF-LGSP-101용 API 응답에서 reviewList + reviewDetail 추출
const extractReviewListApiData = (apiResponse) => { const extractReviewListApiData = (apiResponse) => {
try { try {
console.log('[UserReviewList] 📥 extractReviewListApiData 호출 - 원본 응답:', apiResponse); // console.log('[UserReviewList] 📥 extractReviewListApiData 호출 - 원본 응답:', apiResponse);
// ⭐ 핵심: retCode가 0인지 먼저 확인 (HTTP 200이어도 API 에러일 수 있음) // ⭐ 핵심: retCode가 0인지 먼저 확인 (HTTP 200이어도 API 에러일 수 있음)
if (apiResponse && apiResponse.retCode !== 0) { if (apiResponse && apiResponse.retCode !== 0) {
console.error('[UserReviewList] ❌ API 에러 - retCode !== 0:', { // console.error('[UserReviewList] ❌ API 에러 - retCode !== 0:', {
retCode: apiResponse.retCode, // retCode: apiResponse.retCode,
retMsg: apiResponse.retMsg, // retMsg: apiResponse.retMsg,
fullResponse: apiResponse // fullResponse: apiResponse
}); // });
return null; return null;
} }
@@ -193,59 +193,67 @@ const extractReviewListApiData = (apiResponse) => {
const reviewDetail = apiData.reviewDetail || {}; const reviewDetail = apiData.reviewDetail || {};
// reviewDetail.reviewList에 실제 데이터가 있으면 사용 // 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; reviewList = reviewDetail.reviewList;
console.log('[UserReviewList] 🔄 reviewDetail.reviewList에서 데이터 추출됨'); // console.log('[UserReviewList] 🔄 reviewDetail.reviewList에서 데이터 추출됨');
} }
data = { data = {
reviewList: reviewList, reviewList: reviewList,
reviewDetail: reviewDetail reviewDetail: reviewDetail,
}; };
console.log('[UserReviewList] 📊 apiResponse.data 경로에서 추출:', { // console.log('[UserReviewList] 📊 apiResponse.data 경로에서 추출:', {
reviewListLength: data.reviewList.length, // reviewListLength: data.reviewList.length,
reviewDetailKeys: Object.keys(data.reviewDetail), // reviewDetailKeys: Object.keys(data.reviewDetail),
reviewDetail: data.reviewDetail, // reviewDetail: data.reviewDetail,
reviewListSample: data.reviewList.length > 0 ? data.reviewList[0] : 'empty' // reviewListSample: data.reviewList.length > 0 ? data.reviewList[0] : 'empty'
}); // });
} else if (apiResponse) { } else if (apiResponse) {
// 직접 경로에서 추출 // 직접 경로에서 추출
let reviewList = apiResponse.reviewList || []; let reviewList = apiResponse.reviewList || [];
const reviewDetail = apiResponse.reviewDetail || {}; const reviewDetail = apiResponse.reviewDetail || {};
// reviewDetail.reviewList에 실제 데이터가 있으면 사용 // 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; reviewList = reviewDetail.reviewList;
console.log('[UserReviewList] 🔄 reviewDetail.reviewList에서 데이터 추출됨'); // console.log('[UserReviewList] 🔄 reviewDetail.reviewList에서 데이터 추출됨');
} }
data = { data = {
reviewList: reviewList, reviewList: reviewList,
reviewDetail: reviewDetail reviewDetail: reviewDetail,
}; };
console.log('[UserReviewList] 📊 직접 경로에서 추출:', { // console.log('[UserReviewList] 📊 직접 경로에서 추출:', {
reviewListLength: data.reviewList.length, // reviewListLength: data.reviewList.length,
reviewDetailKeys: Object.keys(data.reviewDetail), // reviewDetailKeys: Object.keys(data.reviewDetail),
reviewDetail: data.reviewDetail, // reviewDetail: data.reviewDetail,
reviewListSample: data.reviewList.length > 0 ? data.reviewList[0] : 'empty' // reviewListSample: data.reviewList.length > 0 ? data.reviewList[0] : 'empty'
}); // });
} }
if (!data || (!data.reviewList && !data.reviewDetail)) { if (!data || (!data.reviewList && !data.reviewDetail)) {
console.warn('[UserReviewList] ⚠️ reviewList와 reviewDetail 모두 없음:', apiResponse); // console.warn('[UserReviewList] ⚠️ reviewList와 reviewDetail 모두 없음:', apiResponse);
return null; return null;
} }
console.log('[UserReviewList] ✅ 추출 완료:', { // console.log('[UserReviewList] ✅ 추출 완료:', {
reviewListLength: data.reviewList.length, // reviewListLength: data.reviewList.length,
reviewDetail: data.reviewDetail // reviewDetail: data.reviewDetail
}); // });
return data; return data;
} catch (error) { } catch (error) {
console.error('[UserReviewList] ❌ extractReviewListApiData 에러:', error); // console.error('[UserReviewList] ❌ extractReviewListApiData 에러:', error);
return null; return null;
} }
}; };
@@ -367,7 +375,12 @@ export const getVideoIndicatorFocus = (focused) => (dispatch) => {
// 순차 페이징으로 모든 리뷰 데이터를 수집하는 함수 (TV 앱 성능 최적화) // 순차 페이징으로 모든 리뷰 데이터를 수집하는 함수 (TV 앱 성능 최적화)
// Option 2: 순차 페칭 (메모리 효율, 서버 부하 감소) // 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 MAX_RETRIES = 2; // 최대 2회 재시도 (총 3회 시도)
const { const {
prdtId, prdtId,
@@ -377,15 +390,15 @@ const fetchAllReviewsWithSequentialPaging = async (dispatch, getState, requestPa
pageSize = 100, // 최대값으로 설정하여 페이징 횟수 최소화 pageSize = 100, // 최대값으로 설정하여 페이징 횟수 최소화
} = requestParams; } = requestParams;
console.log('[UserReviewList] 🚀 순차 페이징 시작:', { // console.log('[UserReviewList] 🚀 순차 페이징 시작:', {
prdtId, // prdtId,
patnrId, // patnrId,
filterTpCd, // filterTpCd,
filterTpVal, // filterTpVal,
pageSize, // pageSize,
retryCount, // retryCount,
isRetry: retryCount > 0 // isRetry: retryCount > 0
}); // });
let allReviews = []; let allReviews = [];
let currentReviewDetail = null; let currentReviewDetail = null;
@@ -401,13 +414,13 @@ const fetchAllReviewsWithSequentialPaging = async (dispatch, getState, requestPa
filterTpCd, filterTpCd,
pageSize, pageSize,
pageNo, pageNo,
cntryCd: 'US' cntryCd: 'US',
}; };
// filterTpCd가 'ALL'이 아니면 filterTpVal 추가 // filterTpCd가 'ALL'이 아니면 filterTpVal 추가
if (filterTpCd !== 'ALL') { if (filterTpCd !== 'ALL') {
if (!filterTpVal) { if (!filterTpVal) {
console.warn('[UserReviewList] ⚠️ filterTpCd가 ALL이 아니면 filterTpVal은 필수입니다'); // console.warn('[UserReviewList] ⚠️ filterTpCd가 ALL이 아니면 filterTpVal은 필수입니다');
} }
params.filterTpVal = filterTpVal; params.filterTpVal = filterTpVal;
} }
@@ -416,13 +429,13 @@ const fetchAllReviewsWithSequentialPaging = async (dispatch, getState, requestPa
// ⭐ 타임아웃 추가: TAxios의 콜백이 호출되지 않는 경우를 대비 (모든 오류 상황 처리) // ⭐ 타임아웃 추가: TAxios의 콜백이 호출되지 않는 경우를 대비 (모든 오류 상황 처리)
const REQUEST_TIMEOUT = 5000; // 5초 타임아웃 (재인증, 팝업 등 오류 상황 처리 포함) const REQUEST_TIMEOUT = 5000; // 5초 타임아웃 (재인증, 팝업 등 오류 상황 처리 포함)
console.log(`[UserReviewList] 🔄 API 요청 시작 (page ${pageNo}):`, { // console.log(`[UserReviewList] 🔄 API 요청 시작 (page ${pageNo}):`, {
prdtId, // prdtId,
patnrId, // patnrId,
filterTpCd, // filterTpCd,
pageSize, // pageSize,
pageNo // pageNo
}); // });
const response = await Promise.race([ const response = await Promise.race([
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
@@ -430,80 +443,89 @@ const fetchAllReviewsWithSequentialPaging = async (dispatch, getState, requestPa
const onSuccess = (res) => { const onSuccess = (res) => {
if (callbackCalled) { if (callbackCalled) {
console.warn(`[UserReviewList] ⚠️ onSuccess 중복 호출 (page ${pageNo})`); // console.warn(`[UserReviewList] ⚠️ onSuccess 중복 호출 (page ${pageNo})`);
return; return;
} }
callbackCalled = true; callbackCalled = true;
console.log(`[UserReviewList] ✅ API 응답 수신 (page ${pageNo}):`, { // console.log(`[UserReviewList] ✅ API 응답 수신 (page ${pageNo}):`, {
status: res?.status, // status: res?.status,
statusText: res?.statusText, // statusText: res?.statusText,
retCode: res?.data?.retCode, // retCode: res?.data?.retCode,
dataExists: !!res?.data, // dataExists: !!res?.data,
reviewDetailExists: !!res?.data?.data?.reviewDetail // reviewDetailExists: !!res?.data?.data?.reviewDetail
}); // });
resolve(res); resolve(res);
}; };
const onFail = (err) => { const onFail = (err) => {
if (callbackCalled) { if (callbackCalled) {
console.warn(`[UserReviewList] ⚠️ onFail 중복 호출 (page ${pageNo})`); // console.warn(`[UserReviewList] ⚠️ onFail 중복 호출 (page ${pageNo})`);
return; return;
} }
callbackCalled = true; callbackCalled = true;
console.error(`[UserReviewList] ❌ API 콜백 에러 발생 (page ${pageNo}):`, { // console.error(`[UserReviewList] ❌ API 콜백 에러 발생 (page ${pageNo}):`, {
errorMessage: err?.message, // errorMessage: err?.message,
errorStatus: err?.response?.status, // errorStatus: err?.response?.status,
errorStatusText: err?.response?.statusText, // errorStatusText: err?.response?.statusText,
errorRetCode: err?.data?.retCode, // errorRetCode: err?.data?.retCode,
errorRetMsg: err?.data?.retMsg, // errorRetMsg: err?.data?.retMsg,
errorType: typeof err // errorType: typeof err
}); // });
reject(err); reject(err);
}; };
// API 호출 // API 호출
console.log(`[UserReviewList] 📡 TAxios 호출 (page ${pageNo})`); // console.log(`[UserReviewList] 📡 TAxios 호출 (page ${pageNo})`);
TAxios(dispatch, getState, 'get', URLS.GET_USER_REVIEW_LIST, params, {}, onSuccess, onFail); TAxios(
dispatch,
getState,
'get',
URLS.GET_USER_REVIEW_LIST,
params,
{},
onSuccess,
onFail
);
}), }),
// 타임아웃 Promise (onFail이 호출되지 않은 경우에 대비) // 타임아웃 Promise (onFail이 호출되지 않은 경우에 대비)
new Promise((_, reject) => new Promise((_, reject) =>
setTimeout(() => { setTimeout(() => {
const timeoutError = new Error(`API request timeout without callback (page ${pageNo})`); const timeoutError = new Error(`API request timeout without callback (page ${pageNo})`);
console.error(`[UserReviewList] ⏱️ API 응답 타임아웃 (page ${pageNo}):`, { // console.error(`[UserReviewList] ⏱️ API 응답 타임아웃 (page ${pageNo}):`, {
timeout: REQUEST_TIMEOUT, // timeout: REQUEST_TIMEOUT,
prdtId, // prdtId,
patnrId, // patnrId,
pageNo, // pageNo,
reason: '5초 이내 onSuccess/onFail 콜백이 호출되지 않음' // reason: '5초 이내 onSuccess/onFail 콜백이 호출되지 않음'
}); // });
reject(timeoutError); reject(timeoutError);
}, REQUEST_TIMEOUT) }, REQUEST_TIMEOUT)
) ),
]); ]);
// ⭐ 핵심: HTTP 200이어도 response.data.retCode를 반드시 확인해야 함 // ⭐ 핵심: HTTP 200이어도 response.data.retCode를 반드시 확인해야 함
const retCode = response?.data?.retCode; const retCode = response?.data?.retCode;
console.log(`[UserReviewList] 📄 페이지 ${pageNo} 응답 상태 확인:`, { // console.log(`[UserReviewList] 📄 페이지 ${pageNo} 응답 상태 확인:`, {
pageNo, // pageNo,
httpStatus: response?.status, // httpStatus: response?.status,
retCode: retCode, // retCode: retCode,
retMsg: response?.data?.retMsg, // retMsg: response?.data?.retMsg,
reviewListLength: response?.data?.data?.reviewDetail?.reviewList?.length || 0, // reviewListLength: response?.data?.data?.reviewDetail?.reviewList?.length || 0,
totRvwCnt: response?.data?.data?.reviewDetail?.totRvwCnt // totRvwCnt: response?.data?.data?.reviewDetail?.totRvwCnt
}); // });
// retCode가 0이 아니면 API 에러 (HTTP 200이어도 실제 데이터 없을 수 있음) // retCode가 0이 아니면 API 에러 (HTTP 200이어도 실제 데이터 없을 수 있음)
if (retCode !== 0) { if (retCode !== 0) {
console.error(`[UserReviewList] ❌ API 에러 - retCode !== 0 (page ${pageNo}):`, { // console.error(`[UserReviewList] ❌ API 에러 - retCode !== 0 (page ${pageNo}):`, {
retCode, // retCode,
retMsg: response?.data?.retMsg, // retMsg: response?.data?.retMsg,
pageNo, // pageNo,
prdtId, // prdtId,
totalCollected: allReviews.length // totalCollected: allReviews.length
}); // });
throw new Error(`API Error: retCode=${retCode}, message=${response?.data?.retMsg}`); 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); const reviewData = extractReviewListApiData(response.data);
if (!reviewData || !reviewData.reviewList) { if (!reviewData || !reviewData.reviewList) {
console.warn('[UserReviewList] ⚠️ 리뷰 데이터 추출 실패, 페이징 종료'); // console.warn('[UserReviewList] ⚠️ 리뷰 데이터 추출 실패, 페이징 종료');
break; break;
} }
@@ -523,12 +545,12 @@ const fetchAllReviewsWithSequentialPaging = async (dispatch, getState, requestPa
// 5. 현재 페이지의 리뷰들을 전체 리스트에 추가 // 5. 현재 페이지의 리뷰들을 전체 리스트에 추가
allReviews = allReviews.concat(reviewData.reviewList); allReviews = allReviews.concat(reviewData.reviewList);
console.log(`[UserReviewList] ✅ 페이지 ${pageNo} 수집 완료:`, { // console.log(`[UserReviewList] ✅ 페이지 ${pageNo} 수집 완료:`, {
pageNo, // pageNo,
currentPageCount: reviewData.reviewList.length, // currentPageCount: reviewData.reviewList.length,
totalCollected: allReviews.length, // totalCollected: allReviews.length,
totRvwCnt: currentReviewDetail?.totRvwCnt // totRvwCnt: currentReviewDetail?.totRvwCnt
}); // });
// 6. 페이징 종료 조건 확인 // 6. 페이징 종료 조건 확인
// rvwListCnt < pageSize이면 마지막 페이지 // rvwListCnt < pageSize이면 마지막 페이지
@@ -538,24 +560,24 @@ const fetchAllReviewsWithSequentialPaging = async (dispatch, getState, requestPa
if (receivedCount < pageSize || allReviews.length >= totalReviews) { if (receivedCount < pageSize || allReviews.length >= totalReviews) {
hasMore = false; hasMore = false;
console.log('[UserReviewList] 📊 페이징 종료:', { // console.log('[UserReviewList] 📊 페이징 종료:', {
reason: receivedCount < pageSize ? '받은 개수 < pageSize' : '수집된 개수 >= 총 개수', // reason: receivedCount < pageSize ? '받은 개수 < pageSize' : '수집된 개수 >= 총 개수',
receivedCount, // receivedCount,
pageSize, // pageSize,
totalCollected: allReviews.length, // totalCollected: allReviews.length,
totalReviews // totalReviews
}); // });
} else { } else {
pageNo++; pageNo++;
} }
} }
// 7. 모든 리뷰 수집 완료, Redux에 디스패치 // 7. 모든 리뷰 수집 완료, Redux에 디스패치
console.log('[UserReviewList] 🎉 모든 리뷰 수집 완료:', { // console.log('[UserReviewList] 🎉 모든 리뷰 수집 완료:', {
totalCollected: allReviews.length, // totalCollected: allReviews.length,
totRvwCnt: currentReviewDetail?.totRvwCnt, // totRvwCnt: currentReviewDetail?.totRvwCnt,
pages: pageNo - 1 // pages: pageNo - 1
}); // });
// Redux 디스패치를 위한 최종 데이터 구성 // Redux 디스패치를 위한 최종 데이터 구성
const isAllFilter = filterTpCd === 'ALL'; const isAllFilter = filterTpCd === 'ALL';
@@ -565,59 +587,61 @@ const fetchAllReviewsWithSequentialPaging = async (dispatch, getState, requestPa
reviewList: allReviews, reviewList: allReviews,
reviewDetail: currentReviewDetail, reviewDetail: currentReviewDetail,
prdtId, prdtId,
...(isAllFilter ? {} : { filterTpCd, filterTpVal }) ...(isAllFilter ? {} : { filterTpCd, filterTpVal }),
}; };
const action = { const action = {
type: actionType, type: actionType,
payload: finalPayload payload: finalPayload,
}; };
console.log('[UserReviewList] 📦 Redux 디스패치:', { // console.log('[UserReviewList] 📦 Redux 디스패치:', {
actionType, // actionType,
totalReviews: allReviews.length, // totalReviews: allReviews.length,
totRvwCnt: currentReviewDetail?.totRvwCnt, // totRvwCnt: currentReviewDetail?.totRvwCnt,
prdtId // prdtId
}); // });
dispatch(action); dispatch(action);
return finalPayload; return finalPayload;
} catch (error) { } 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 httpStatus = error?.response?.status;
const apiRetCode = error?.response?.data?.retCode; const apiRetCode = error?.response?.data?.retCode;
const apiRetMsg = error?.response?.data?.retMsg; const apiRetMsg = error?.response?.data?.retMsg;
console.error('[fetchAllReviewsWithSequentialPaging] ❌ 에러 발생:', { // console.error('[fetchAllReviewsWithSequentialPaging] ❌ 에러 발생:', {
errorMessage: errorMessage, // errorMessage: errorMessage,
errorType: typeof error, // errorType: typeof error,
httpStatus: httpStatus, // httpStatus: httpStatus,
apiRetCode: apiRetCode, // apiRetCode: apiRetCode,
apiRetMsg: apiRetMsg, // apiRetMsg: apiRetMsg,
prdtId, // prdtId,
patnrId, // patnrId,
pageNo, // pageNo,
currentCollected: allReviews.length, // currentCollected: allReviews.length,
retryCount, // retryCount,
maxRetries: MAX_RETRIES // 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) { if (isTimeoutError && retryCount < MAX_RETRIES) {
console.log(`[fetchAllReviewsWithSequentialPaging] 🔄 타임아웃으로 인한 재시도 (${retryCount + 1}/${MAX_RETRIES}):`, { // console.log(`[fetchAllReviewsWithSequentialPaging] 🔄 타임아웃으로 인한 재시도 (${retryCount + 1}/${MAX_RETRIES}):`, {
prdtId, // prdtId,
patnrId, // patnrId,
pageNo, // pageNo,
retryCount, // retryCount,
delayMs: 1000 * (retryCount + 1) // delayMs: 1000 * (retryCount + 1)
}); // });
// 지수 백오프: 1초, 2초 대기 후 재시도 // 지수 백오프: 1초, 2초 대기 후 재시도
const delayMs = 1000 * (retryCount + 1); 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); return fetchAllReviewsWithSequentialPaging(dispatch, getState, requestParams, retryCount + 1);
@@ -630,51 +654,47 @@ const fetchAllReviewsWithSequentialPaging = async (dispatch, getState, requestPa
// User Review List 추가 조회 IF-LGSP-101 (순차 페이징으로 모든 데이터 수집) // User Review List 추가 조회 IF-LGSP-101 (순차 페이징으로 모든 데이터 수집)
export const getUserReviewList = (requestParams) => async (dispatch, getState) => { export const getUserReviewList = (requestParams) => async (dispatch, getState) => {
const { const { prdtId, patnrId, filterTpCd = 'ALL', filterTpVal } = requestParams;
prdtId,
patnrId,
filterTpCd = 'ALL',
filterTpVal
} = requestParams;
console.log('[getUserReviewList] 🚀 getUserReviewList 호출됨 (순차 페이징 사용):', { // console.log('[getUserReviewList] 🚀 getUserReviewList 호출됨 (순차 페이징 사용):', {
prdtId, // prdtId,
patnrId, // patnrId,
filterTpCd, // filterTpCd,
filterTpVal, // filterTpVal,
timestamp: new Date().toISOString() // timestamp: new Date().toISOString()
}); // });
try { try {
// fetchAllReviewsWithSequentialPaging 함수를 호출하여 모든 리뷰 수집 // fetchAllReviewsWithSequentialPaging 함수를 호출하여 모든 리뷰 수집
const result = await fetchAllReviewsWithSequentialPaging(dispatch, getState, requestParams); const result = await fetchAllReviewsWithSequentialPaging(dispatch, getState, requestParams);
console.log('[getUserReviewList] ✅ 모든 리뷰 수집 완료:', { // console.log('[getUserReviewList] ✅ 모든 리뷰 수집 완료:', {
totalReviews: result.reviewList.length, // totalReviews: result.reviewList.length,
totRvwCnt: result.reviewDetail?.totRvwCnt, // totRvwCnt: result.reviewDetail?.totRvwCnt,
prdtId, // prdtId,
filterTpCd, // filterTpCd,
filterTpVal // filterTpVal
}); // });
} catch (error) { } 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 httpStatus = error?.response?.status;
const apiRetCode = error?.response?.data?.retCode; const apiRetCode = error?.response?.data?.retCode;
const apiRetMsg = error?.response?.data?.retMsg; const apiRetMsg = error?.response?.data?.retMsg;
console.error('[getUserReviewList] ❌ 순차 페이징 중 에러 발생:', { // console.error('[getUserReviewList] ❌ 순차 페이징 중 에러 발생:', {
errorMessage: errorMessage, // errorMessage: errorMessage,
errorType: typeof error, // errorType: typeof error,
httpStatus: httpStatus, // httpStatus: httpStatus,
apiRetCode: apiRetCode, // apiRetCode: apiRetCode,
apiRetMsg: apiRetMsg, // apiRetMsg: apiRetMsg,
prdtId, // prdtId,
patnrId, // patnrId,
filterTpCd, // filterTpCd,
filterTpVal, // filterTpVal,
stack: error?.stack // stack: error?.stack
}); // });
// Redux 상태에 에러 정보 저장 (선택사항) // Redux 상태에 에러 정보 저장 (선택사항)
// dispatch({ // dispatch({
@@ -708,7 +728,7 @@ const extractReviewFiltersApiData = (apiResponse) => {
console.error('[ReviewFilters] ❌ API 에러 - retCode !== 0:', { console.error('[ReviewFilters] ❌ API 에러 - retCode !== 0:', {
retCode: retCode, retCode: retCode,
retMsg: apiResponse?.retMsg, retMsg: apiResponse?.retMsg,
fullResponse: apiResponse fullResponse: apiResponse,
}); });
return null; return null;
} }
@@ -721,7 +741,7 @@ const extractReviewFiltersApiData = (apiResponse) => {
prdtId: reviewFilterInfos.prdtId, prdtId: reviewFilterInfos.prdtId,
hasFilters: !!reviewFilterInfos.filters, hasFilters: !!reviewFilterInfos.filters,
filtersLength: reviewFilterInfos.filters ? reviewFilterInfos.filters.length : 0, filtersLength: reviewFilterInfos.filters ? reviewFilterInfos.filters.length : 0,
reviewFilterInfosKeys: Object.keys(reviewFilterInfos) reviewFilterInfosKeys: Object.keys(reviewFilterInfos),
}); });
data = reviewFilterInfos; data = reviewFilterInfos;
@@ -734,7 +754,7 @@ const extractReviewFiltersApiData = (apiResponse) => {
console.log('[ReviewFilters] ✅ 추출 완료:', { console.log('[ReviewFilters] ✅ 추출 완료:', {
patnrId: data.patnrId, patnrId: data.patnrId,
prdtId: data.prdtId, prdtId: data.prdtId,
filtersLength: data.filters.length filtersLength: data.filters.length,
}); });
return data; return data;
@@ -746,16 +766,13 @@ const extractReviewFiltersApiData = (apiResponse) => {
// Review Filters 조회 IF-LGSP-100 // Review Filters 조회 IF-LGSP-100
export const getReviewFilters = (requestParams) => (dispatch, getState) => { export const getReviewFilters = (requestParams) => (dispatch, getState) => {
const { const { prdtId, patnrId } = requestParams;
prdtId,
patnrId
} = requestParams;
const params = { const params = {
prdtId, prdtId,
patnrId, patnrId,
// 우선순위 1: cntryCd 기본값 'US' 설정 (TV 환경에서는 자동으로 header로 전달됨) // 우선순위 1: cntryCd 기본값 'US' 설정 (TV 환경에서는 자동으로 header로 전달됨)
cntryCd: 'US' cntryCd: 'US',
}; };
const body = {}; const body = {};
@@ -765,7 +782,7 @@ export const getReviewFilters = (requestParams) => (dispatch, getState) => {
params, params,
body, body,
url: URLS.GET_REVIEW_FILTERS, url: URLS.GET_REVIEW_FILTERS,
timestamp: new Date().toISOString() timestamp: new Date().toISOString(),
}); });
const onSuccess = (response) => { const onSuccess = (response) => {
@@ -777,8 +794,8 @@ export const getReviewFilters = (requestParams) => (dispatch, getState) => {
httpStatus: response?.status, httpStatus: response?.status,
retCode: retCode, retCode: retCode,
retMsg: retMsg, retMsg: retMsg,
hasData: !!(response?.data?.data), hasData: !!response?.data?.data,
dataExists: !!response?.data dataExists: !!response?.data,
}); });
// retCode !== 0이면 extractReviewFiltersApiData에서 처리하고 null 반환됨 // retCode !== 0이면 extractReviewFiltersApiData에서 처리하고 null 반환됨
@@ -788,7 +805,7 @@ export const getReviewFilters = (requestParams) => (dispatch, getState) => {
console.warn('[ReviewFilters] ⚠️ 필터 데이터 추출 실패:', { console.warn('[ReviewFilters] ⚠️ 필터 데이터 추출 실패:', {
retCode: retCode, retCode: retCode,
retMsg: retMsg, retMsg: retMsg,
reason: retCode !== 0 ? 'retCode !== 0' : 'filters 데이터 없음' reason: retCode !== 0 ? 'retCode !== 0' : 'filters 데이터 없음',
}); });
return; // 실패 시 dispatch하지 않음 return; // 실패 시 dispatch하지 않음
} }
@@ -796,7 +813,7 @@ export const getReviewFilters = (requestParams) => (dispatch, getState) => {
console.log('[ReviewFilters] 📊 필터 데이터 추출 성공:', { console.log('[ReviewFilters] 📊 필터 데이터 추출 성공:', {
patnrId: filtersData.patnrId, patnrId: filtersData.patnrId,
prdtId: filtersData.prdtId, prdtId: filtersData.prdtId,
filtersLength: filtersData.filters ? filtersData.filters.length : 0 filtersLength: filtersData.filters ? filtersData.filters.length : 0,
}); });
const action = { const action = {
@@ -804,7 +821,7 @@ export const getReviewFilters = (requestParams) => (dispatch, getState) => {
payload: { payload: {
...filtersData, ...filtersData,
prdtId: prdtId, prdtId: prdtId,
patnrId: patnrId patnrId: patnrId,
}, },
}; };
@@ -812,7 +829,7 @@ export const getReviewFilters = (requestParams) => (dispatch, getState) => {
actionType: types.GET_REVIEW_FILTERS, actionType: types.GET_REVIEW_FILTERS,
patnrId: patnrId, patnrId: patnrId,
prdtId: prdtId, prdtId: prdtId,
filtersLength: filtersData.filters ? filtersData.filters.length : 0 filtersLength: filtersData.filters ? filtersData.filters.length : 0,
}); });
dispatch(action); dispatch(action);
@@ -839,6 +856,6 @@ export const getReviewFilters = (requestParams) => (dispatch, getState) => {
// All Star 필터 해제 - API 호출 없이 상태만 초기화 // All Star 필터 해제 - API 호출 없이 상태만 초기화
export const clearReviewFilter = () => (dispatch) => { export const clearReviewFilter = () => (dispatch) => {
dispatch({ dispatch({
type: types.CLEAR_REVIEW_FILTER type: types.CLEAR_REVIEW_FILTER,
}); });
}; };

View File

@@ -628,15 +628,15 @@ export default function ProductAllSection({
[setOpenThemeItemOverlay] [setOpenThemeItemOverlay]
); );
const handleProductDetailsClick = useCallback( const handleProductDetailsClick = useCallback(() => {
() => scrollToSection('scroll-marker-product-details'), dispatch(minimizeModalMedia());
[scrollToSection] scrollToSection('scroll-marker-product-details');
); }, [scrollToSection, dispatch]);
const handleYouMayAlsoLikeClick = useCallback( const handleYouMayAlsoLikeClick = useCallback(() => {
() => scrollToSection('scroll-marker-you-may-also-like'), dispatch(minimizeModalMedia());
[scrollToSection] scrollToSection('scroll-marker-you-may-also-like');
); }, [scrollToSection, dispatch]);
const scrollPositionRef = useRef(0); const scrollPositionRef = useRef(0);
const prevScrollPositionRef = useRef(0); // 이전 스크롤 위치 추적 const prevScrollPositionRef = useRef(0); // 이전 스크롤 위치 추적
const prevScrollTopRef = useRef(0); // HomePanel 스타일 스크롤 위치 추적 const prevScrollTopRef = useRef(0); // HomePanel 스타일 스크롤 위치 추적
@@ -644,6 +644,8 @@ export default function ProductAllSection({
const mediaMinimizedRef = useRef(false); const mediaMinimizedRef = useRef(false);
const handleArrowClickAlternative = useCallback(() => { const handleArrowClickAlternative = useCallback(() => {
dispatch(minimizeModalMedia());
const currentHeight = scrollPositionRef.current; const currentHeight = scrollPositionRef.current;
const scrollAmount = 200; const scrollAmount = 200;
@@ -659,7 +661,7 @@ export default function ProductAllSection({
if (isAtBottom) { if (isAtBottom) {
setIsBottom(isAtBottom); setIsBottom(isAtBottom);
} }
}, [documentHeight, scrollTop]); }, [documentHeight, scrollTop, dispatch]);
const handleScroll = useCallback( const handleScroll = useCallback(
(e) => { (e) => {

View File

@@ -54,7 +54,7 @@ export default function ProductVideo({
// autoPlay 기능: 컴포넌트 마운트 시 자동으로 비디오 재생 // autoPlay 기능: 컴포넌트 마운트 시 자동으로 비디오 재생
useEffect(() => { useEffect(() => {
if (autoPlay && canPlayVideo && !hasAutoPlayed && productInfo) { if (autoPlay && canPlayVideo && !hasAutoPlayed && productInfo) {
console.log('[ProductVideo] Auto-playing video'); // console.log('[ProductVideo] Auto-playing video');
setHasAutoPlayed(true); setHasAutoPlayed(true);
// 짧은 딸레이 후 재생 시작 (컴포넌트 마운트 완료 후) // 짧은 딸레이 후 재생 시작 (컴포넌트 마운트 완료 후)
@@ -113,7 +113,7 @@ export default function ProductVideo({
const videoContainerOnFocus = useCallback(() => { const videoContainerOnFocus = useCallback(() => {
if (canPlayVideo) { if (canPlayVideo) {
setFocused(true); setFocused(true);
console.log('[ProductVideo] Calling restoreModalMedia'); // console.log('[ProductVideo] Calling restoreModalMedia');
// ProductVideo에 포커스가 돌아오면 비디오 복원 // ProductVideo에 포커스가 돌아오면 비디오 복원
// dispatch(restoreModalMedia()); // dispatch(restoreModalMedia());
@@ -125,7 +125,7 @@ export default function ProductVideo({
}, [canPlayVideo, dispatch, onFocus]); }, [canPlayVideo, dispatch, onFocus]);
const videoContainerOnBlur = useCallback(() => { const videoContainerOnBlur = useCallback(() => {
console.log('[ProductVideo] onBlur called - canPlayVideo:', canPlayVideo); // console.log('[ProductVideo] onBlur called - canPlayVideo:', canPlayVideo);
// if (canPlayVideo) { // if (canPlayVideo) {
// setFocused(false); // setFocused(false);
// // 포커스를 잃으면 모달 MediaPanel을 최소화하여 1px 상태로 유지 // // 포커스를 잃으면 모달 MediaPanel을 최소화하여 1px 상태로 유지
@@ -149,10 +149,10 @@ export default function ProductVideo({
// MediaPanel 비디오 클릭 핸들러 + 모달 토글 기능 // MediaPanel 비디오 클릭 핸들러 + 모달 토글 기능
const handleVideoClick = useCallback(() => { const handleVideoClick = useCallback(() => {
console.log('[ProductVideo] ========== handleVideoClick 호출 =========='); // console.log('[ProductVideo] ========== handleVideoClick 호출 ==========');
console.log('[ProductVideo] canPlayVideo:', canPlayVideo); // console.log('[ProductVideo] canPlayVideo:', canPlayVideo);
console.log('[ProductVideo] panels.length:', panels.length); // console.log('[ProductVideo] panels.length:', panels.length);
console.log('[ProductVideo] All panels:', JSON.stringify(panels, null, 2)); // console.log('[ProductVideo] All panels:', JSON.stringify(panels, null, 2));
if (canPlayVideo) { if (canPlayVideo) {
const currentTopPanel = panels[panels.length - 1]; const currentTopPanel = panels[panels.length - 1];
@@ -163,19 +163,19 @@ export default function ProductVideo({
currentTopPanel.name === panel_names.MEDIA_PANEL && currentTopPanel.name === panel_names.MEDIA_PANEL &&
currentTopPanel.panelInfo.modal === true; currentTopPanel.panelInfo.modal === true;
console.log('[ProductVideo] currentTopPanel:', JSON.stringify(currentTopPanel, null, 2)); // console.log('[ProductVideo] currentTopPanel:', JSON.stringify(currentTopPanel, null, 2));
console.log('[ProductVideo] isCurrentlyPlayingModal:', isCurrentlyPlayingModal); // console.log('[ProductVideo] isCurrentlyPlayingModal:', isCurrentlyPlayingModal);
// modal로 재생 중이면 전체화면으로 전환 // modal로 재생 중이면 전체화면으로 전환
if (isCurrentlyPlayingModal) { if (isCurrentlyPlayingModal) {
console.log( // console.log(
'[ProductVideo] *** Switching to fullscreen mode via switchMediaToFullscreen ***' // '[ProductVideo] *** Switching to fullscreen mode via switchMediaToFullscreen ***'
); // );
dispatch(switchMediaToFullscreen()); dispatch(switchMediaToFullscreen());
setModalState(false); setModalState(false);
} else { } else {
console.log('[ProductVideo] *** Starting modal MediaPanel ***'); // console.log('[ProductVideo] *** Starting modal MediaPanel ***');
console.log('[ProductVideo] productInfo:', JSON.stringify(productInfo, null, 2)); // console.log('[ProductVideo] productInfo:', JSON.stringify(productInfo, null, 2));
// 처음 재생 시작 - modal=true로 시작 // 처음 재생 시작 - modal=true로 시작
dispatch( dispatch(
startMediaPlayer({ startMediaPlayer({

View File

@@ -58,17 +58,17 @@ export default function ProductVideo({
topPanel.panelInfo.modal === true && topPanel.panelInfo.modal === true &&
prevModalStateRef.current === false 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가 자동으로 포커스를 빼앗지 않도록 // VideoPlayer의 controlsHandleAbove가 자동으로 포커스를 빼앗지 않도록
// 약간의 딜레이 후에 강제로 포커스 설정 // 약간의 딜레이 후에 강제로 포커스 설정
setTimeout(() => { 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"]'); const element = document.querySelector('[data-spotlight-id="product-video-player"]');
if (element) { if (element) {
// Spotlight 내부 포커스 강제 설정 // Spotlight 내부 포커스 강제 설정
Spotlight.focus('product-video-player'); Spotlight.focus('product-video-player');
console.log('[ProductVideo] Focus set to product-video-player'); // console.log('[ProductVideo] Focus set to product-video-player');
} }
}, 50); }, 50);
@@ -77,7 +77,7 @@ export default function ProductVideo({
// MediaPanel이 닫혔을 때 modalState를 true로 복원 // MediaPanel이 닫혔을 때 modalState를 true로 복원
if (!topPanel || topPanel.name !== panel_names.MEDIA_PANEL) { 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); setModalState(true);
prevModalStateRef.current = null; prevModalStateRef.current = null;
} }
@@ -91,11 +91,11 @@ export default function ProductVideo({
topPanel?.name === panel_names.MEDIA_PANEL && topPanel?.panelInfo?.modal === true; topPanel?.name === panel_names.MEDIA_PANEL && topPanel?.panelInfo?.modal === true;
if (autoPlay && canPlayVideo && !hasAutoPlayed && productInfo && !isMediaPanelAlreadyPlaying) { if (autoPlay && canPlayVideo && !hasAutoPlayed && productInfo && !isMediaPanelAlreadyPlaying) {
console.log('[ProductVideo]-LoadingVideo 🎯 AutoPlay 시작:', { // console.log('[ProductVideo]-LoadingVideo 🎯 AutoPlay 시작:', {
prdtId: productInfo?.prdtId, // prdtId: productInfo?.prdtId,
prdtNm: productInfo?.prdtNm, // prdtNm: productInfo?.prdtNm,
prdtMediaUrl: productInfo?.prdtMediaUrl?.substring(0, 50), // prdtMediaUrl: productInfo?.prdtMediaUrl?.substring(0, 50),
}); // });
setHasAutoPlayed(true); setHasAutoPlayed(true);
// 짧은 딜레이 후 재생 시작 (컴포넌트 마운트 완료 후) // 짧은 딜레이 후 재생 시작 (컴포넌트 마운트 완료 후)
@@ -160,7 +160,7 @@ export default function ProductVideo({
const videoContainerOnFocus = useCallback(() => { const videoContainerOnFocus = useCallback(() => {
if (canPlayVideo) { if (canPlayVideo) {
setFocused(true); setFocused(true);
console.log('[ProductVideo] Calling restoreModalMedia'); // console.log('[ProductVideo] Calling restoreModalMedia');
// ProductVideo에 포커스가 돌아오면 비디오 복원 // ProductVideo에 포커스가 돌아오면 비디오 복원
// dispatch(restoreModalMedia()); // dispatch(restoreModalMedia());
@@ -172,7 +172,7 @@ export default function ProductVideo({
}, [canPlayVideo, dispatch, onFocus]); }, [canPlayVideo, dispatch, onFocus]);
const videoContainerOnBlur = useCallback(() => { const videoContainerOnBlur = useCallback(() => {
console.log('[ProductVideo] onBlur called - canPlayVideo:', canPlayVideo); // console.log('[ProductVideo] onBlur called - canPlayVideo:', canPlayVideo);
// if (canPlayVideo) { // if (canPlayVideo) {
// setFocused(false); // setFocused(false);
// // 포커스를 잃으면 모달 MediaPanel을 최소화하여 1px 상태로 유지 // // 포커스를 잃으면 모달 MediaPanel을 최소화하여 1px 상태로 유지
@@ -197,10 +197,10 @@ export default function ProductVideo({
// MediaPanel 비디오 클릭 핸들러 + 모달 토글 기능 // MediaPanel 비디오 클릭 핸들러 + 모달 토글 기능
const handleVideoClick = useCallback(() => { const handleVideoClick = useCallback(() => {
console.log('[ProductVideo] ========== handleVideoClick 호출 =========='); // console.log('[ProductVideo] ========== handleVideoClick 호출 ==========');
console.log('[ProductVideo] canPlayVideo:', canPlayVideo); // console.log('[ProductVideo] canPlayVideo:', canPlayVideo);
console.log('[ProductVideo] panels.length:', panels.length); // console.log('[ProductVideo] panels.length:', panels.length);
console.log('[ProductVideo] All panels:', JSON.stringify(panels, null, 2)); // console.log('[ProductVideo] All panels:', JSON.stringify(panels, null, 2));
if (canPlayVideo) { if (canPlayVideo) {
const currentTopPanel = panels[panels.length - 1]; const currentTopPanel = panels[panels.length - 1];
@@ -211,19 +211,31 @@ export default function ProductVideo({
currentTopPanel.name === panel_names.MEDIA_PANEL && currentTopPanel.name === panel_names.MEDIA_PANEL &&
currentTopPanel.panelInfo.modal === true; currentTopPanel.panelInfo.modal === true;
console.log('[ProductVideo] currentTopPanel:', JSON.stringify(currentTopPanel, null, 2)); // console.log('[ProductVideo] currentTopPanel:', JSON.stringify(currentTopPanel, null, 2));
console.log('[ProductVideo] isCurrentlyPlayingModal:', isCurrentlyPlayingModal); // console.log('[ProductVideo] isCurrentlyPlayingModal:', isCurrentlyPlayingModal);
// modal로 재생 중이면 전체화면으로 전환 // modal로 재생 중이면 전체화면으로 전환
if (isCurrentlyPlayingModal) { if (isCurrentlyPlayingModal) {
console.log( // console.log(
'[ProductVideo] *** Switching to fullscreen mode via switchMediaToFullscreen ***' // '[ProductVideo] *** Switching to fullscreen mode via switchMediaToFullscreen ***'
); // );
dispatch(switchMediaToFullscreen()); dispatch(switchMediaToFullscreen());
setModalState(false); 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 { } else {
console.log('[ProductVideo] *** Starting modal MediaPanel ***'); // console.log('[ProductVideo] *** Starting modal MediaPanel ***');
console.log('[ProductVideo] productInfo:', JSON.stringify(productInfo, null, 2)); // console.log('[ProductVideo] productInfo:', JSON.stringify(productInfo, null, 2));
// 처음 재생 시작 - modal=true로 시작 // 처음 재생 시작 - modal=true로 시작
setIsVideoPlaying(true); // 비디오 재생 flag 설정 setIsVideoPlaying(true); // 비디오 재생 flag 설정
if (onVideoPlaying) { if (onVideoPlaying) {

View File

@@ -2237,7 +2237,7 @@ const MediaPanel = React.forwardRef(
modalScale={panelInfo.modal ? modalScale : 1} modalScale={panelInfo.modal ? modalScale : 1}
modalClassName={panelInfo.modal && panelInfo.modalClassName} modalClassName={panelInfo.modal && panelInfo.modalClassName}
spotlightId={ spotlightId={
panelInfo.modal ? undefined : panelInfo.modalContainerId || spotlightId panelInfo.modal ? 'modal-video-player' : panelInfo.modalContainerId || spotlightId
} }
handleIndicatorDownClick={handleIndicatorDownClick} handleIndicatorDownClick={handleIndicatorDownClick}
handleIndicatorUpClick={handleIndicatorUpClick} handleIndicatorUpClick={handleIndicatorUpClick}