diff --git a/com.twin.app.shoptime/package.json b/com.twin.app.shoptime/package.json index 9e783ca7..5d5b1a6a 100644 --- a/com.twin.app.shoptime/package.json +++ b/com.twin.app.shoptime/package.json @@ -43,6 +43,7 @@ "axios": "^0.21.1", "google-libphonenumber": "^3.2.34", "ilib": "^14.3.0", + "lodash": "4.17.21", "patch-package": "^8.0.0", "postinstall-postinstall": "^2.1.0", "prop-types": "^15.6.2", @@ -52,9 +53,12 @@ "react-player": "^1.15.3", "react-redux": "^7.2.3", "redux": "^3.7.2", - "redux-thunk": "^2.3.0" + "redux-thunk": "2.3.0" }, "browserslist": [ "chrome 38" - ] + ], + "devDependencies": { + "prettier": "^3.5.3" + } } diff --git a/com.twin.app.shoptime/src/App/App.js b/com.twin.app.shoptime/src/App/App.js index 44790b9f..92759b1d 100644 --- a/com.twin.app.shoptime/src/App/App.js +++ b/com.twin.app.shoptime/src/App/App.js @@ -154,7 +154,8 @@ function AppBase(props) { const termsData = useSelector((state) => state.home.termsData); useEffect(() => { - if (termsData?.data?.terms) { + // Chromium68 호환성을 위해 Optional Chaining 제거 + if (termsData && termsData.data && termsData.data.terms) { dispatch(getTermsAgreeYn()); } }, [termsData, dispatch]); @@ -166,7 +167,8 @@ function AppBase(props) { ); // const macAddress = useSelector((state) => state.common.macAddress); - const deviceCountryCode = httpHeader?.["X-Device-Country"] || ""; + // Chromium68 호환성을 위해 Optional Chaining 제거 + const deviceCountryCode = httpHeader && httpHeader["X-Device-Country"] || ""; useEffect(() => { if (!cursorVisible && !Spotlight.getCurrent()) { @@ -214,23 +216,31 @@ function AppBase(props) { // called by [receive httpHeader, launch, relaunch] const initService = useCallback( + (haveyInit = true) => { - /* + + // console.log( + // "<<<<<<<<<<<<< appinfo >>>>>>>>>>>>{heavyInit, appinfo} ", + // haveyInit, + // appinfo + // ); + console.log( - "<<<<<<<<<<<<< appinfo >>>>>>>>>>>>{heavyInit, appinfo} ", - haveyInit, - appinfo + "[App.js] initService,httpHeaderRef.current", + httpHeaderRef.current ); - */ + console.log("[App.js] haveyInit", haveyInit); + if (httpHeaderRef.current) { if (haveyInit) { dispatch(changeAppStatus({ connectionFailed: false })); if (typeof window === "object" && window.PalmSystem) { - dispatch( - changeAppStatus({ - cursorVisible: window.PalmSystem?.cursor?.visibility, - }) - ); + dispatch( + changeAppStatus({ + // Chromium68 호환성을 위해 Optional Chaining 제거 + cursorVisible: window.PalmSystem && window.PalmSystem.cursor && window.PalmSystem.cursor.visibility, + }) + ); } dispatch(getHomeMenu()); dispatch(getMyRecommandedKeyword()); @@ -246,11 +256,12 @@ function AppBase(props) { ); // pyh TODO: edit or delete later (line 196 ~ 198) - if (launchParams?.bypass) { + // Chromium68 호환성을 위해 Optional Chaining 제거 + if (launchParams && launchParams.bypass) { dispatch(handleBypassLink(launchParams.bypass)); } - if (launchParams?.contentTarget) { - dispatch(handleDeepLink(launchParams?.contentTarget)); + if (launchParams && launchParams.contentTarget) { + dispatch(handleDeepLink(launchParams.contentTarget)); } else { dispatch( sendLogTotalRecommend({ @@ -295,10 +306,11 @@ function AppBase(props) { // set foreground flag using delay time. clearTimeout(foreGroundChangeTimer); foreGroundChangeTimer = setTimeout(() => { - console.log( - "visibility changed !!! ==> set to foreground cursorVisible", - JSON.stringify(window.PalmSystem?.cursor?.visibility) - ); // eslint-disable-line no-console + console.log( + "visibility changed !!! ==> set to foreground cursorVisible", + // Chromium68 호환성을 위해 Optional Chaining 제거 + JSON.stringify(window.PalmSystem && window.PalmSystem.cursor && window.PalmSystem.cursor.visibility) + ); // eslint-disable-line no-console if (platform.platformName !== "webos") { //for debug dispatch( @@ -311,7 +323,8 @@ function AppBase(props) { dispatch( changeAppStatus({ isAppForeground: true, - cursorVisible: window.PalmSystem?.cursor?.visibility, + // Chromium68 호환성을 위해 Optional Chaining 제거 + cursorVisible: window.PalmSystem && window.PalmSystem.cursor && window.PalmSystem.cursor.visibility, }) ); } diff --git a/com.twin.app.shoptime/src/actions/actionTypes.js b/com.twin.app.shoptime/src/actions/actionTypes.js index a8e089fb..a1d78cb1 100644 --- a/com.twin.app.shoptime/src/actions/actionTypes.js +++ b/com.twin.app.shoptime/src/actions/actionTypes.js @@ -59,6 +59,7 @@ export const types = { // home actions GET_HOME_TERMS: "GET_HOME_TERMS", SET_TERMS_ID_MAP: "SET_TERMS_ID_MAP", + SET_OPTIONAL_TERMS_AVAILABILITY: "SET_OPTIONAL_TERMS_AVAILABILITY", GET_HOME_MENU: "GET_HOME_MENU", GET_HOME_LAYOUT: "GET_HOME_LAYOUT", GET_HOME_MAIN_CONTENTS: "GET_HOME_MAIN_CONTENTS", diff --git a/com.twin.app.shoptime/src/actions/homeActions.js b/com.twin.app.shoptime/src/actions/homeActions.js index bfb8c26e..89491994 100644 --- a/com.twin.app.shoptime/src/actions/homeActions.js +++ b/com.twin.app.shoptime/src/actions/homeActions.js @@ -17,12 +17,20 @@ export const getHomeTerms = (props) => (dispatch, getState) => { }); // 약관 ID 매핑을 별도로 생성하여 저장 - if (response.data?.data?.terms) { + // 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({ @@ -30,8 +38,21 @@ export const getHomeTerms = (props) => (dispatch, getState) => { 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); } } @@ -80,12 +101,20 @@ export const fetchCurrentUserHomeTerms = () => (dispatch, getState) => { }); // 약관 ID 매핑을 별도로 생성하여 저장 - if (response.data?.data?.terms) { + // 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({ @@ -93,8 +122,20 @@ export const fetchCurrentUserHomeTerms = () => (dispatch, getState) => { 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); } } @@ -169,12 +210,20 @@ export const fetchCurrentUserHomeTermsSafe = () => async (dispatch, getState) => }); // 약관 ID 매핑을 별도로 생성하여 저장 - if (result.data?.data?.terms) { + // 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({ @@ -182,8 +231,19 @@ export const fetchCurrentUserHomeTermsSafe = () => async (dispatch, getState) => 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); } } @@ -195,11 +255,12 @@ export const fetchCurrentUserHomeTermsSafe = () => async (dispatch, getState) => return { success: true, data: result.data }; } else { // retCode가 0이 아닌 일반적인 API 에러 - console.error("API returned non-zero retCode:", result.data?.retCode); + // Chromium68 호환성을 위해 Optional Chaining 제거 + 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?.retMsg || "서버 오류가 발생했습니다." + message: (result.data && result.data.retMsg) || "서버 오류가 발생했습니다." }; } }; diff --git a/com.twin.app.shoptime/src/reducers/homeReducer.js b/com.twin.app.shoptime/src/reducers/homeReducer.js index ae55c058..c4b94b28 100644 --- a/com.twin.app.shoptime/src/reducers/homeReducer.js +++ b/com.twin.app.shoptime/src/reducers/homeReducer.js @@ -26,6 +26,7 @@ const initialState = { isPaused: false, }, termsIdMap: {}, // added new property to initialState + optionalTermsAvailable: false, // 선택약관 존재 여부 }; export const homeReducer = (state = initialState, action) => { @@ -42,6 +43,12 @@ export const homeReducer = (state = initialState, action) => { termsIdMap: action.payload, }; + case types.SET_OPTIONAL_TERMS_AVAILABILITY: + return { + ...state, + optionalTermsAvailable: action.payload, + }; + case types.GET_HOME_MENU: { let menuItems = []; diff --git a/com.twin.app.shoptime/src/utils/dataHelpers.js b/com.twin.app.shoptime/src/utils/dataHelpers.js new file mode 100644 index 00000000..4744ee60 --- /dev/null +++ b/com.twin.app.shoptime/src/utils/dataHelpers.js @@ -0,0 +1,223 @@ +// src/utils/dataHelpers.js + +/** + * @fileoverview webOS TV 데이터 처리 유틸리티 함수들 + * + * Chromium68 기반 구형 webOS TV 호환성을 위해 Optional Chaining(?.) 대신 + * lodash를 사용하여 안전한 객체 속성 접근을 제공합니다. + * + * Optional Chaining은 Chrome 80+에서만 지원되므로, + * Chromium68 환경에서는 문법 오류가 발생할 수 있습니다. + * + * @author Your Team + * @since 2.0.0 + * @requires lodash + */ + +import { get, isEmpty, isArray } from 'lodash'; + +/** + * 객체의 중첩된 속성에 안전하게 접근합니다. + * Optional Chaining(obj?.prop?.nested) 대신 사용하는 호환성 함수입니다. + * + * @param {Object|Array|null|undefined} obj - 접근할 객체 + * @param {string|Array} path - 접근할 속성 경로 (점 표기법 또는 배열) + * @param {*} [defaultValue=null] - 값이 없을 때 반환할 기본값 + * @returns {*} 찾은 값 또는 기본값 + * + * @example + * // 기존 Optional Chaining 방식 (Chromium68에서 작동 안함) + * // const city = user?.profile?.address?.city; + * + * // 호환성을 위한 새로운 방식 + * const user = { profile: { address: { city: 'Seoul' } } }; + * const city = safeGet(user, 'profile.address.city'); // 'Seoul' + * const country = safeGet(user, 'profile.address.country', 'Unknown'); // 'Unknown' + * + * // 배열 접근 + * const items = [{ name: 'Apple' }, { name: 'Banana' }]; + * const firstItem = safeGet(items, '[0].name'); // 'Apple' + * const thirdItem = safeGet(items, '[2].name', 'Not Found'); // 'Not Found' + * + * // webOS API 응답 처리 + * const deviceInfo = safeGet(webOSResponse, 'device.modelName', 'Unknown TV'); + */ +export const safeGet = (obj, path, defaultValue = null) => + get(obj, path, defaultValue); + +/** + * 데이터가 유효한 배열인지 확인합니다. + * API 응답이나 상태 데이터가 렌더링 가능한 배열 형태인지 검증할 때 사용합니다. + * + * @param {*} data - 검증할 데이터 + * @returns {boolean} 유효한 배열이면 true, 아니면 false + * + * @example + * // API 응답 검증 + * const apiResponse = { data: { products: [] } }; + * const products = safeGet(apiResponse, 'data.products', []); + * + * if (hasValidData(products)) { + * // 안전하게 배열 렌더링 + * return products.map(product => ); + * } else { + * return ; + * } + * + * // 다양한 케이스 테스트 + * hasValidData([1, 2, 3]); // true + * hasValidData([]); // false (빈 배열) + * hasValidData(null); // false + * hasValidData(undefined); // false + * hasValidData({}); // false (객체) + * hasValidData('string'); // false (문자열) + */ +export const hasValidData = (data) => + !isEmpty(data) && isArray(data); + +/** + * API 응답에서 데이터 부분을 안전하게 추출합니다. + * 일반적인 API 응답 구조에서 실제 데이터 객체를 가져올 때 사용합니다. + * + * @param {Object} response - API 응답 객체 + * @param {string} [dataPath='data'] - 데이터가 위치한 경로 + * @returns {Object} 추출된 데이터 객체 (기본값: 빈 객체) + * + * @example + * // 일반적인 API 응답 구조 + * const apiResponse = { + * status: 'success', + * data: { + * products: [...], + * pagination: { ... }, + * metadata: { ... } + * } + * }; + * + * // 데이터 추출 + * const responseData = getApiData(apiResponse); // { products: [...], pagination: {...}, ... } + * const products = safeGet(responseData, 'products', []); + * + * // 커스텀 경로 지정 + * const nestedResponse = { + * result: { + * payload: { + * items: [...] + * } + * } + * }; + * const payloadData = getApiData(nestedResponse, 'result.payload'); // { items: [...] } + * + * // webOS 서비스 응답 처리 + * const webOSServiceResponse = { + * returnValue: true, + * response: { + * deviceList: [...] + * } + * }; + * const serviceData = getApiData(webOSServiceResponse, 'response'); // { deviceList: [...] } + * + * // 오류나 빈 응답 처리 + * const emptyResponse = null; + * const safeData = getApiData(emptyResponse); // {} (빈 객체) + */ +export const getApiData = (response, dataPath = 'data') => + get(response, dataPath, {}); + +/** + * 추가 유틸리티 함수들 - webOS TV 환경에서 자주 사용되는 패턴들 + */ + +/** + * 객체에 특정 속성이 존재하는지 안전하게 확인합니다. + * + * @param {Object} obj - 확인할 객체 + * @param {string} path - 확인할 속성 경로 + * @returns {boolean} 속성이 존재하면 true + * + * @example + * const settings = { user: { preferences: { theme: 'dark' } } }; + * + * // 기존 방식 (Chromium68에서 위험) + * // const hasTheme = settings?.user?.preferences?.theme !== undefined; + * + * // 호환성 방식 + * const hasTheme = safeHas(settings, 'user.preferences.theme'); // true + * const hasLanguage = safeHas(settings, 'user.preferences.language'); // false + */ +export const safeHas = (obj, path) => { + return get(obj, path) !== undefined; +}; + +/** + * 배열의 첫 번째 요소를 안전하게 가져옵니다. + * + * @param {Array} array - 대상 배열 + * @param {*} [defaultValue=null] - 기본값 + * @returns {*} 첫 번째 요소 또는 기본값 + * + * @example + * const products = [{ id: 1, name: 'TV' }, { id: 2, name: 'Phone' }]; + * const firstProduct = safeFirst(products); // { id: 1, name: 'TV' } + * + * const emptyArray = []; + * const firstItem = safeFirst(emptyArray, { id: 0, name: 'Default' }); // { id: 0, name: 'Default' } + */ +export const safeFirst = (array, defaultValue = null) => { + return hasValidData(array) ? array[0] : defaultValue; +}; + +/** + * 문자열이 비어있지 않은지 확인합니다. + * + * @param {string} str - 확인할 문자열 + * @returns {boolean} 유효한 문자열이면 true + * + * @example + * isValidString('Hello'); // true + * isValidString(''); // false + * isValidString(null); // false + * isValidString(undefined); // false + * isValidString(' '); // false (공백만 있는 경우) + */ +export const isValidString = (str) => { + return typeof str === 'string' && str.trim().length > 0; +}; + +// 사용 예시를 위한 실제 webOS TV 컴포넌트에서의 활용 +/** + * @example 실제 컴포넌트에서의 사용법 + * + * import { safeGet, hasValidData, getApiData } from '../utils/dataHelpers'; + * + * const ProductList = ({ apiResponse }) => { + * // API 응답에서 안전하게 데이터 추출 + * const responseData = getApiData(apiResponse); + * const products = safeGet(responseData, 'products', []); + * const totalCount = safeGet(responseData, 'pagination.total', 0); + * + * // 데이터 유효성 검사 + * if (!hasValidData(products)) { + * return
상품이 없습니다.
; + * } + * + * return ( + *
+ *

상품 목록 ({totalCount}개)

+ * {products.map(product => { + * const productName = safeGet(product, 'name', '이름 없음'); + * const productPrice = safeGet(product, 'price.amount', 0); + * const productImage = safeGet(product, 'images[0].url', '/default-image.jpg'); + * + * return ( + *
+ * {productName} + *

{productName}

+ *

{productPrice}원

+ *
+ * ); + * })} + *
+ * ); + * }; + */ \ No newline at end of file diff --git a/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/HomeBanner.jsx b/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/HomeBanner.jsx index 0a3c43ff..fa9a73fd 100644 --- a/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/HomeBanner.jsx +++ b/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/HomeBanner.jsx @@ -146,11 +146,16 @@ export default function HomeBanner({ const termsData = useSelector((state) => state.home.termsData); const termsIdMap = useSelector((state) => state.home.termsIdMap); - const optionalTermsData = useSelector((state) => - state.home.termsData?.data?.terms.find( - (term) => term.trmsTpCd === "MST00405", - ), - ); + const optionalTermsAvailable = useSelector((state) => state.home.optionalTermsAvailable); + const optionalTermsData = useSelector((state) => { + // Chromium68 호환성을 위해 Optional Chaining 제거 + if (state.home.termsData && state.home.termsData.data && state.home.termsData.data.terms) { + return state.home.termsData.data.terms.find( + (term) => term.trmsTpCd === "MST00405" + ); + } + return null; + }); const termsLoading = useSelector((state) => state.common.termsLoading); const isGnbOpened = useSelector((state) => state.common.isGnbOpened); // 선택약관 동의여부 @@ -166,13 +171,21 @@ export default function HomeBanner({ // 선택약관 팝업 표시 여부 =================================================== const shouldShowOptionalTermsPopup = useMemo(() => { - console.log('[HomeBanner] Step 1: termsLoading, isGnbOpened, optionalTermsAgreed 상태 확인', { termsLoading, isGnbOpened, optionalTermsAgreed }); - if (termsLoading || isGnbOpened || optionalTermsAgreed) { - console.log('[HomeBanner] Early return: 조건 불만족 (termsLoading || isGnbOpened || optionalTermsAgreed)'); + console.log('[HomeBanner] Step 1: 상태 확인', { + termsLoading, + isGnbOpened, + optionalTermsAgreed, + optionalTermsAvailable + }); + + // optionalTermsAvailable = false면 팝업 표시 안함 + if (termsLoading || isGnbOpened || optionalTermsAgreed || !optionalTermsAvailable) { + console.log('[HomeBanner] Early return: 조건 불만족'); return false; } - const terms = termsData?.data?.terms; + // Chromium68 호환성을 위해 Optional Chaining 제거 + const terms = termsData && termsData.data && termsData.data.terms; console.log('[HomeBanner] Step 2: termsData 확인', terms); if (!terms) { console.log('[HomeBanner] Early return: terms가 존재하지 않음'); @@ -188,7 +201,7 @@ export default function HomeBanner({ console.log('[HomeBanner] Step 4: 최종 결과', result); return result; - }, [termsData, termsLoading, isGnbOpened, optionalTermsAgreed]); + }, [termsData, termsLoading, isGnbOpened, optionalTermsAgreed, optionalTermsAvailable]); // 선택약관 팝업 표시 여부 =================================================== diff --git a/com.twin.app.shoptime/src/views/IntroPanel/IntroPanel.new.jsx b/com.twin.app.shoptime/src/views/IntroPanel/IntroPanel.new.jsx index 98b71e68..1fdc13bc 100644 --- a/com.twin.app.shoptime/src/views/IntroPanel/IntroPanel.new.jsx +++ b/com.twin.app.shoptime/src/views/IntroPanel/IntroPanel.new.jsx @@ -8,6 +8,7 @@ import React, { useState, } from "react"; import { useDispatch, useSelector } from "react-redux"; +import BasicIntroPanel from "./IntroPanel.jsx"; import Region from "@enact/sandstone/Region"; import Spotlight from "@enact/spotlight"; import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator"; @@ -54,7 +55,23 @@ const Container = SpotlightContainerDecorator( const MAX_RETRY_ATTEMPTS = 5; const RETRY_DELAY_MS = 1500; // 1.5 seconds -export default function IntroPanel({ +// 조건부 IntroPanel 컴포넌트 (선택약관 존재 여부에 따라 다른 컴포넌트 렌더링) +export default function IntroPanel(props) { + const optionalTermsAvailable = useSelector((state) => state.home.optionalTermsAvailable); + + // 선택약관이 없으면 기존 IntroPanel.jsx 사용 + if (!optionalTermsAvailable) { + console.log('[IntroPanel.new] optionalTermsAvailable = false, 기존 IntroPanel 사용'); + return ; + } + + // 선택약관이 있으면 고급 IntroPanel 사용 + console.log('[IntroPanel.new] optionalTermsAvailable = true, 고급 IntroPanel 사용'); + return ; +} + +// 선택약관 포함 고급 IntroPanel 컴포넌트 +function IntroPanelWithOptional({ // children, // isTabActivated, // handleCancel, @@ -101,24 +118,32 @@ export default function IntroPanel({ } = termsState; const introTermsData = useMemo(() => { - return ( - termsData?.data?.terms.filter( - (item) => item.trmsTpCd === "MST00401" || item.trmsTpCd === "MST00402", - ) || [] - ); + // Chromium68 호환성을 위해 Optional Chaining 제거 + if (termsData && termsData.data && termsData.data.terms) { + return termsData.data.terms.filter( + (item) => item.trmsTpCd === "MST00401" || item.trmsTpCd === "MST00402" + ); + } + return []; }, [termsData]); const optionalTermsData = useMemo(() => { - return ( - termsData?.data?.terms.filter( - (item) => item.trmsTpCd === "MST00405" || item.trmsTpCd === "MST00406", - ) || [] - ); + // Chromium68 호환성을 위해 Optional Chaining 제거 + if (termsData && termsData.data && termsData.data.terms) { + return termsData.data.terms.filter( + (item) => item.trmsTpCd === "MST00405" || item.trmsTpCd === "MST00406" + ); + } + return []; }, [termsData]); - const webOSVersion = useSelector( - (state) => state.common.appStatus?.webOSVersion, - ); + const webOSVersion = useSelector((state) => { + // Chromium68 호환성을 위해 Optional Chaining 제거 + if (state.common && state.common.appStatus && state.common.appStatus.webOSVersion) { + return state.common.appStatus.webOSVersion; + } + return null; + }); // const webOSVersion = 4.5; // WebOS 버전별 UI 표시 모드 결정