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 { createDebugHelpers } from '../utils/debug'; import { setHidePopup, setShowPopup } from './commonActions'; import { ACTIVE_POPUP } from '../utils/Config'; // 디버그 헬퍼 설정 const DEBUG_MODE = false; const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE); // 약관 정보 조회 IF-LGSP-005 export const getHomeTerms = (props) => (dispatch, getState) => { const { trmsTpCdList, mbrNo } = props; const onSuccess = (response) => { dlog('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) => { if (term.trmsTpCd && term.trmsId) { termsIdMap[term.trmsTpCd] = term.trmsId; } // 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, }); dlog( '[optionalTermsAvailable] 실제값:', hasOptionalTerms, '강제설정값:', finalOptionalTermsValue ); if (process.env.NODE_ENV === 'development') { dlog('약관 ID 매핑 생성:', termsIdMap); dlog('선택약관 존재 여부:', hasOptionalTerms); } } setTimeout(() => { dispatch(getTermsAgreeYn()); }, 0); } }; const onFail = (error) => { derror('getHomeTerms onFail ', error); // TODO: 임시 디버그용 팝업 (재현 후 제거하세요) const retCode = error?.data?.retCode ?? error?.retCode ?? 'unknown'; dispatch( setShowPopup(ACTIVE_POPUP.toast, { button1Text: `getHomeTerms onFail retCode=${retCode}`, button2Text: 'OK', }) ); setTimeout(() => dispatch(setHidePopup()), 1500); // 약관 미동의(retCode 501)로 GET_HOME_TERMS가 실패하면 // introTermsAgree를 명시적으로 false로 내려 앱이 IntroPanel을 띄우도록 한다. if (retCode === 501) { dispatch({ type: types.GET_TERMS_AGREE_YN_SUCCESS, payload: { privacyTerms: 'N', serviceTerms: 'N', purchaseTerms: 'N', paymentTerms: 'N', optionalTerms: 'N', }, }); } // 실패 시 로딩 패널을 반드시 내려 백화 상태를 방지 dispatch( changeAppStatus({ showLoadingPanel: { show: false }, }) ); }; TAxios( dispatch, getState, 'get', URLS.GET_HOME_TERMS, { trmsTpCdList, mbrNo }, {}, onSuccess, onFail ); }; // 현재 로그인 사용자 기준으로 약관 정보 조회 (인자 없이 호출 가능) export const fetchCurrentUserHomeTerms = () => (dispatch, getState) => { const loginUserData = getState().common.appStatus.loginUserData; if (!loginUserData || !loginUserData.userNumber) { derror( '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 onSuccess = (response) => { dlog('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) => { if (term.trmsTpCd && term.trmsId) { termsIdMap[term.trmsTpCd] = term.trmsId; } // 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, }); dlog( '[optionalTermsAvailable] 실제값:', hasOptionalTerms, '강제설정값:', finalOptionalTermsValue ); if (process.env.NODE_ENV === 'development') { dlog('약관 ID 매핑 생성:', termsIdMap); dlog('선택약관 존재 여부:', hasOptionalTerms); } } // getHomeTerms와 동일하게 getTermsAgreeYn 후속 처리 setTimeout(() => { dispatch(getTermsAgreeYn()); }, 0); } else { // retCode가 0이 아닌 경우 실패로 처리 dispatch({ type: types.GET_TERMS_AGREE_YN_FAILURE }); } }; const onFail = (error) => { derror('fetchCurrentUserHomeTerms onFail ', error); dispatch({ type: types.GET_TERMS_AGREE_YN_FAILURE }); }; TAxios( dispatch, getState, '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) { derror('fetchCurrentUserHomeTerms: userNumber is not available'); dispatch({ type: types.GET_TERMS_AGREE_YN_FAILURE }); return { success: false, message: '사용자 정보가 없습니다.' }; } const mbrNo = loginUserData.userNumber; const trmsTpCdList = 'MST00401, MST00402, MST00405'; dlog('Fetching home terms for user:', mbrNo); // 안전한 API 호출 (기존 TAxios 패턴과 동일) const result = await TAxiosPromise(dispatch, getState, 'get', URLS.GET_HOME_TERMS, { trmsTpCdList, mbrNo, }); // 네트워크 에러인 경우 if (!result.success) { derror('fetchCurrentUserHomeTerms network error:', result.error); dispatch({ type: types.GET_TERMS_AGREE_YN_FAILURE }); return { success: false, message: '네트워크 오류가 발생했습니다.' }; } // 기존 TAxios처럼 특별한 retCode들은 TAxios 내부에서 이미 처리됨 // (401, 402, 501, 602, 603, 604 등은 TAxios에서 알아서 처리하고 onSuccess가 호출되지 않음) dlog('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) => { if (term.trmsTpCd && term.trmsId) { termsIdMap[term.trmsTpCd] = term.trmsId; } // 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') { dlog('약관 ID 매핑 생성:', termsIdMap); dlog( '선택약관 존재 여부 - 실제값:', hasOptionalTerms, '강제설정값:', finalOptionalTermsValue ); } } // 후속 액션 호출 (기존과 동일) setTimeout(() => { dispatch(getTermsAgreeYn()); }, 0); return { success: true, data: result.data }; } else { // retCode가 0이 아닌 일반적인 API 에러 // Chromium68 호환성을 위해 Optional Chaining 제거 derror('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) || '서버 오류가 발생했습니다.', }; } }; // 메뉴 목록 조회 IF-LGSP-044 export const getHomeMenu = () => (dispatch, getState) => { const onSuccess = (response) => { // dlog("getHomeMenu onSuccess ", response.data); dispatch({ type: types.GET_HOME_MENU, payload: response.data, }); }; const onFail = (error) => { derror('getHomeMenu onFail ', error); }; 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' } })); const onSuccess = (response) => { dlog('getThemeCurationDetailInfo onSuccess', response.data); dispatch({ type: types.GET_THEME_CURATION_DETAIL_INFO, payload: response.data.data, }); dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); }; const onFail = (error) => { derror('getThemeCurationDetailInfo onFail', error); dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); }; TAxios( dispatch, getState, 'get', URLS.GET_THEME_CURATION_DETAIL_INFO, { patnrId, curationId, bgImgNo }, {}, onSuccess, onFail ); }; // 테마호텔 정보 상세 조회 IF-LGSP-085 export const getThemeHotelDetailInfo = (params) => (dispatch, getState) => { const { patnrId, curationId } = params; dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } })); const onSuccess = (response) => { dlog('getThemeHotelDetailInfo onSuccess', response.data); dispatch({ type: types.GET_THEME_HOTEL_DETAIL_INFO, payload: response.data.data, }); dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); }; const onFail = (error) => { derror('getThemeHotelDetailInfo onFail', error); dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); }; TAxios( dispatch, getState, 'get', URLS.GET_THEME_HOTEL_DETAIL_INFO, { patnrId, curationId }, {}, onSuccess, onFail ); }; // HOME LAYOUT 정보 조회 IF-LGSP-300 export const getHomeLayout = () => (dispatch, getState) => { const onSuccess = (response) => { dlog('getHomeLayout onSuccess', response.data); dispatch({ type: types.GET_HOME_LAYOUT, payload: response.data.data, }); dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); }; const onFail = (error) => { derror('getHomeLayout onFail', error); dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); }; 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) => { dlog('getHomeMainContents onSuccess', response.data); dispatch({ type: types.GET_HOME_MAIN_CONTENTS, payload: response.data.data, status: 'fulfilled', }); dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); }; const onFail = (error) => { derror('getHomeMainContents onFail', error); dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); }; TAxios(dispatch, getState, 'get', URLS.GET_HOME_MAIN_CONTENTS, {}, {}, onSuccess, onFail); }; // Theme 전시 정보 조회 : IF-LGSP-045 export const getThemeCurationInfo = () => (dispatch, getState) => { const onSuccess = (response) => { dlog('getThemeCurationInfo onSuccess', response.data); dispatch({ type: types.GET_THEME_CURATION_INFO, payload: response.data.data, retCode: response.data.retCode, }); dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); }; const onFail = (error) => { derror('getThemeCurationInfo onFail', error); dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); }; 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' } })); const onSuccess = (response) => { dlog('getThemeMenuShelfInfo onSuccess', response.data); dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); dispatch({ type: types.GET_THEME_MENU_SHELF_INFO, payload: response.data.data, }); }; const onFail = (error) => { derror('getThemeMenuShelfInfo onFail', error); dispatch(changeAppStatus({ showLoadingPanel: { show: false } })); }; TAxios( dispatch, getState, 'get', URLS.GET_THEME_MENU_SHELF_INFO, { curationId }, {}, onSuccess, onFail ); }; export const clearThemeMenuShelfInfo = () => ({ type: types.CLEAR_THEME_MENU_SHELF_INFO, }); export const resetHomeInfo = () => ({ type: types.RESET_HOME_INFO, }); export const updateHomeInfo = (homeInfo) => { return { type: types.UPDATE_HOME_INFO, payload: homeInfo, }; }; export const clearThemeDetail = () => ({ type: types.CLEAR_THEME_DETAIL, }); export const setDefaultFocus = (focus) => ({ type: types.SET_DEFAULT_FOCUS, payload: focus, }); export const setVideoTransitionLock = (isLocked) => ({ type: types.SET_VIDEO_TRANSITION_LOCK, payload: Boolean(isLocked), }); export const checkEnterThroughGNB = (boolean) => ({ type: types.CHECK_ENTER_THROUGH_GNB, payload: boolean, }); export const setBannerIndex = (bannerId, index) => { if (!bannerId) { dwarn('setBannerIndex called with undefined bannerId'); return { type: 'NO_OP' }; } return { type: types.SET_BANNER_INDEX, payload: { bannerId, index }, }; }; // 🔽 [추가] 새로운 배너 위치 및 영구재생 관련 액션들 /** * 모든 배너의 위치 정보를 설정합니다. * @param {Object} positions - 배너별 위치 정보 맵 */ export const setBannerPositions = (positions) => ({ type: types.SET_BANNER_POSITIONS, payload: positions, }); /** * 특정 배너의 위치 정보를 업데이트합니다. * @param {string} bannerId - 배너 ID * @param {Object} position - 위치 정보 */ export const updateBannerPosition = (bannerId, position) => ({ type: types.UPDATE_BANNER_POSITION, payload: { bannerId, position }, }); /** * 영구재생 비디오 정보를 설정합니다. * @param {Object} videoInfo - 비디오 정보 */ export const setPersistentVideoInfo = (videoInfo) => ({ type: types.SET_PERSISTENT_VIDEO_INFO, payload: videoInfo, }); /** * 영구재생 비디오 정보를 클리어합니다. */ export const clearPersistentVideoInfo = () => ({ type: types.CLEAR_PERSISTENT_VIDEO_INFO, }); /** * 모든 배너의 현재 DOM 위치를 수집하여 Redux 스토어에 저장합니다. * @param {Array} bannerIds - 수집할 배너 ID 배열 */ export const collectAndSaveBannerPositions = (bannerIds) => async (dispatch) => { try { const positions = await collectBannerPositions(bannerIds); dispatch(setBannerPositions(positions)); if (process.env.NODE_ENV === 'development') { dlog('[homeActions] 배너 위치 수집 완료:', positions); } } catch (error) { derror('[homeActions] 배너 위치 수집 실패:', error); } };