[251123] fix: OptionalTerms->HomePanel로 이동

🕐 커밋 시간: 2025. 11. 23. 19:18:58

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

📝 수정된 파일:
  ~ com.twin.app.shoptime/package-lock.json
  ~ com.twin.app.shoptime/src/views/HomePanel/HomeBanner/HomeBanner.jsx
  ~ com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx

🔧 함수 변경 내용:
  📄 com.twin.app.shoptime/src/views/HomePanel/HomeBanner/HomeBanner.jsx (javascript):
    🔄 Modified: SpotlightContainerDecorator()
     Deleted: callback()
  📄 com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx (javascript):
     Added: callback()

Performance: 코드 최적화로 성능 개선 기대
This commit is contained in:
2025-11-23 19:18:58 +09:00
parent c3395c205a
commit c9543e1452
3 changed files with 303 additions and 508 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -8,19 +8,8 @@ import Spotlight from '@enact/spotlight';
import { SpotlightContainerDecorator } from '@enact/spotlight/SpotlightContainerDecorator';
import Spottable from '@enact/spotlight/Spottable';
import { types } from '../../../actions/actionTypes';
import {
changeAppStatus,
handleOptionalTermsAgree as handleOptionalTermsAgreeAction,
handleOptionalTermsDecline,
setOptionalTermsPopupShown,
setOptionalTermsUserDecision,
updateOptionalTermsAgreement,
} from '../../../actions/commonActions';
import { changeAppStatus } from '../../../actions/commonActions';
import { justForYou } from '../../../actions/forYouActions';
import { fetchCurrentUserHomeTerms, setDefaultFocus } from '../../../actions/homeActions';
import { setMyPageTermsAgree } from '../../../actions/myPageActions';
import { popPanel, pushPanel } from '../../../actions/panelActions';
import {
clearAllVideoTimers,
releasePlayControl,
@@ -29,9 +18,6 @@ import {
startVideoPlayerNew,
} from '../../../actions/playActions';
import CustomImage from '../../../components/CustomImage/CustomImage';
// import TButtonScroller from "../../../components/TButtonScroller/TButtonScroller";
import OptionalConfirm from '../../../components/Optional/OptionalConfirm';
import TNewPopUp from '../../../components/TPopUp/TNewPopUp';
import { useFocusHistory } from '../../../hooks/useFocusHistory/useFocusHistory';
// [COMMENTED OUT] useVideoMove 관련 코드 주석 처리 - 향후 사용 검토 필요
// import { useVideoMove } from '../../../hooks/useVideoTransition/useVideoMove';
@@ -113,215 +99,9 @@ export default function HomeBanner({
}
}, [handleItemFocus]);
const termsData = useSelector((state) => state.home.termsData);
const termsIdMap = useSelector((state) => state.home.termsIdMap);
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);
const currentTermsFlag = useSelector((state) => state.common.termsFlag);
// 새로운 Redux 상태: 선택약관 팝업 플로우 관리
const optionalTermsPopupFlow = useSelector((state) => state.common.optionalTermsPopupFlow);
// 🔽 초기 비디오 재생 플래그 (1회만 실행되도록)
const isInitialVideoPlayRef = useRef(false);
//------------------------------------------------------------------------------
// 팝업표시 상태
const [isOptionalConfirmVisible, setIsOptionalConfirmVisible] = useState(false);
const [isOptionalTermsVisible, setIsOptionalTermsVisible] = useState(false);
const [optionalTermsAgreed, setOptionalTermsAgreed] = useState(false);
// 선택약관 팝업 표시 여부 ===================================================
const shouldShowOptionalTermsPopup = useMemo(() => {
// if (process.env.NODE_ENV === "development") {
// console.log("[HomeBanner] Step 1: 상태 확인", {
// termsLoading,
// isGnbOpened,
// optionalTermsAvailable,
// optionalTermsPopupFlow,
// });
// }
// 1. 기본 조건 확인
if (termsLoading || isGnbOpened || !optionalTermsAvailable) {
if (process.env.NODE_ENV === 'development') {
console.log('[HomeBanner] Early return: 기본 조건 불만족');
}
return false;
}
// 2. 새로운 Redux 상태 확인 (TV 환경 최적화)
if (
optionalTermsPopupFlow.popupShown ||
optionalTermsPopupFlow.userDecision ||
optionalTermsPopupFlow.agreedInSession
) {
if (process.env.NODE_ENV === 'development') {
console.log('[HomeBanner] Early return: 이미 처리됨', optionalTermsPopupFlow);
}
return false;
}
// 3. 서버 데이터 확인
const terms = termsData && termsData.data && termsData.data.terms;
// if (process.env.NODE_ENV === "development") {
// console.log("[HomeBanner] Step 2: termsData 확인", terms);
// }
if (!terms) {
if (process.env.NODE_ENV === 'development') {
console.log('[HomeBanner] Early return: terms가 존재하지 않음');
}
return false;
}
const optionalTerm = terms.find((term) => term.trmsTpCd === 'MST00405');
// if (process.env.NODE_ENV === "development") {
// console.log("[HomeBanner] Step 3: optionalTerm 검색 결과", optionalTerm);
// }
const result = optionalTerm
? optionalTerm.trmsPopFlag === 'Y' && optionalTerm.trmsAgrFlag === 'N'
: false;
// if (process.env.NODE_ENV === "development") {
// console.log("[HomeBanner] Step 4: 최종 결과", result);
// }
return result;
}, [
termsData.data?.terms,
termsLoading,
isGnbOpened,
optionalTermsAvailable,
optionalTermsPopupFlow,
]);
// 선택약관 팝업 표시 여부 ===================================================
const handleOptionalAgree = useCallback(() => {
// if (process.env.NODE_ENV === "development") {
// console.log("[HomeBanner] handleAgree Click");
// }
if (!termsIdMap || Object.keys(termsIdMap).length === 0) {
if (process.env.NODE_ENV === 'development') {
console.error('[HomeBanner] termsIdMap이 없습니다:', termsIdMap);
}
return;
}
const requiredTermTypes = ['MST00401', 'MST00402', 'MST00405'];
const missingTerms = requiredTermTypes.filter((type) => !termsIdMap[type]);
if (missingTerms.length > 0) {
if (process.env.NODE_ENV === 'development') {
console.error('[HomeBanner] 누락된 약관 타입:', missingTerms);
}
return;
}
const termsList = [];
if (termsIdMap['MST00401']) {
termsList.push(termsIdMap['MST00401']); // 개인정보처리방침
}
if (termsIdMap['MST00402']) {
termsList.push(termsIdMap['MST00402']); // 이용약관
}
if (termsIdMap['MST00405']) {
termsList.push(termsIdMap['MST00405']); // 선택약관
}
const notTermsList = [];
if (process.env.NODE_ENV === 'development') {
console.log('[HomeBanner] 현재 termsIdMap:', termsIdMap);
console.log('[HomeBanner] 약관 동의 API 호출 파라미터:', {
termsList,
notTermsList,
});
}
const callback = (response) => {
if (response.retCode === '000' || response.retCode === 0) {
if (process.env.NODE_ENV === 'development') {
console.log('[HomeBanner] 약관 동의 성공:', response);
}
// ✅ IntroPanel과 동일한 방식으로 Redux 상태 직접 업데이트 (API 호출 없이)
dispatch(updateOptionalTermsAgreement(true));
// 로컬 상태도 업데이트 (기존 로직 유지)
setOptionalTermsAgreed(true);
} else {
if (process.env.NODE_ENV === 'development') {
console.error('[HomeBanner] 약관 동의 실패:', response);
}
}
};
if (process.env.NODE_ENV === 'development') {
console.log('[HomeBanner] 약관 동의 API 호출 payload:', {
termsList,
notTermsList,
});
}
dispatch(setMyPageTermsAgree({ termsList, notTermsList }, callback));
}, [dispatch, termsIdMap]);
const handleOptionalTermsClick = useCallback(() => {
if (process.env.NODE_ENV === 'development') {
console.log('[HomeBanner] 약관 자세히 보기 클릭');
}
setIsOptionalConfirmVisible(false);
setIsOptionalTermsVisible(true);
// 약관 상세 팝업을 띄우는 로직 추가
}, []);
const handleOptionalAgreeClick = useCallback(() => {
handleOptionalAgree();
setIsOptionalConfirmVisible(false);
dispatch({
type: types.GET_TERMS_AGREE_YN_SUCCESS,
payload: {
...currentTermsFlag,
optionalTerms: 'Y',
},
});
}, [handleOptionalAgree]);
const handleOptionalDeclineClick = useCallback(() => {
if (process.env.NODE_ENV === 'development') {
console.log('[HomeBanner] 거절/다음에 하기 버튼 클릭');
}
// ✅ 거절 상태 업데이트
dispatch(updateOptionalTermsAgreement(false));
setIsOptionalConfirmVisible(false);
}, [dispatch]);
// 선택약관 팝업 Close
const handleTermsPopupClosed = useCallback(() => {
setIsOptionalTermsVisible(false);
setIsOptionalConfirmVisible(true);
Spotlight.focus('optional-confirm-popup');
}, []);
// 선택약관 팝업 Agree
const handleTermsPopupAgree = useCallback(() => {
if (process.env.NODE_ENV === 'development') {
console.log('[HomeBanner] handleTermsPopupAgree');
}
handleOptionalAgree();
setIsOptionalTermsVisible(false);
}, [handleOptionalAgree]);
//------------------------------------------------------------------------------
const _handleShelfFocus = useCallback(() => {
if (handleShelfFocus) {
@@ -402,29 +182,6 @@ export default function HomeBanner({
}
}, [bannerDataList, defaultFocus, dispatch]);
// 약관 동의 및 선택 약관 팝업 처리 (TV 환경 최적화)
useEffect(() => {
if (termsLoading) {
// 약관 데이터 로딩 중에는 아무것도 하지 않음
return;
}
// 선택 약관 팝업을 띄워야 하는 경우
if (shouldShowOptionalTermsPopup && !isOptionalConfirmVisible) {
console.log('shouldShowOptionalTermsPopup', shouldShowOptionalTermsPopup);
console.log('App.js optionalTermsConfirm 팝업 표시');
const timer = setTimeout(() => {
setIsOptionalConfirmVisible(true);
// 팝업이 실제로 표시된 후에 Redux 상태 업데이트
dispatch(setOptionalTermsPopupShown(true));
}, 1000);
// 컴포넌트 언마운트 시 타이머 클리어
return () => clearTimeout(timer);
}
}, [shouldShowOptionalTermsPopup, termsLoading, isOptionalConfirmVisible, dispatch]);
const renderItem = useCallback(
(index, isHorizontal) => {
const data = bannerDataList?.[index] ?? {};
@@ -560,33 +317,6 @@ export default function HomeBanner({
<Container className={css.container} spotlightId={spotlightId} data-wheel-point={true}>
<div className={css.homeTemplateBox}>{renderLayout()}</div>
</Container>
{/* 선택약관 동의 팝업 */}
<OptionalConfirm
open={isOptionalConfirmVisible}
spotlightId="optional-confirm-popup"
onClose={handleOptionalDeclineClick}
onOptionalTermsClick={handleOptionalTermsClick}
onOptionalAgreeClick={handleOptionalAgreeClick}
onOptionalDeclineClick={handleOptionalDeclineClick}
customPosition={true}
position={{
position: 'absolute',
top: '342px', // 가운데를 기준으로 한 좌표 (1080/2) - 198
left: '0px',
bottom: 'unset',
transform: 'none',
}}
/>
{/* 선택약관 자세히 보기 팝업 */}
<TNewPopUp
kind="figmaTermsPopup"
open={isOptionalTermsVisible}
title={$L('Optional Terms')}
text={optionalTermsData?.trmsCntt || ''}
onClose={handleTermsPopupClosed}
onAgreeClick={handleTermsPopupAgree}
showAgreeButton={true}
/>
</>
);
}

View File

@@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import classNames from 'classnames';
import { useDispatch, useSelector } from 'react-redux';
import { types } from '../../actions/actionTypes';
import { applyMiddleware } from 'redux';
import Spotlight from '@enact/spotlight';
@@ -16,6 +17,8 @@ import {
setExitApp,
setHidePopup,
setShowPopup,
setOptionalTermsPopupShown,
updateOptionalTermsAgreement,
} from '../../actions/commonActions';
import { getWelcomeEventInfo } from '../../actions/eventActions';
import {
@@ -25,6 +28,7 @@ import {
updateHomeInfo,
// <<<<<<< HEAD
} from '../../actions/homeActions';
import { setMyPageTermsAgree } from '../../actions/myPageActions';
import { sendLogGNB, sendLogTotalRecommend } from '../../actions/logActions';
import { getSubCategory, getTop20Show } from '../../actions/mainActions';
import { getHomeOnSaleInfo } from '../../actions/onSaleActions';
@@ -38,6 +42,8 @@ import {
import { getBestSeller } from '../../actions/productActions';
import TBody from '../../components/TBody/TBody';
import TButton, { TYPES } from '../../components/TButton/TButton';
import OptionalConfirm from '../../components/Optional/OptionalConfirm';
import TNewPopUp from '../../components/TPopUp/TNewPopUp';
import TPanel from '../../components/TPanel/TPanel';
import TPopUp from '../../components/TPopUp/TPopUp';
import TVerticalPagenator from '../../components/TVerticalPagenator/TVerticalPagenator';
@@ -185,6 +191,20 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
const topInfos = useSelector((state) => state.main.top20ShowData.topInfos);
const isDeepLink = useSelector((state) => state.common.deepLinkInfo.isDeepLink);
// 선택약관 관련 Redux 상태
const termsData = useSelector((state) => state.home.termsData);
const termsIdMap = useSelector((state) => state.home.termsIdMap);
const optionalTermsAvailable = useSelector((state) => state.home.optionalTermsAvailable);
const optionalTermsData = useSelector((state) => {
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 currentTermsFlag = useSelector((state) => state.common.termsFlag);
const optionalTermsPopupFlow = useSelector((state) => state.common.optionalTermsPopupFlow);
const [btnDisabled, setBtnDisabled] = useState(true);
const [arrowBottom, setArrowBottom] = useState(true);
const [firstSpot, setFirstSpot] = useState(false);
@@ -193,6 +213,10 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
const [firstLgCatCd, setFirstLgCatCd] = useState(panelInfo.currentCatCd ?? null);
const [cateCd, setCateCd] = useState(panelInfo.currentCatCd ?? null);
const [cateNm, setCateNm] = useState(panelInfo.currentCateName ?? null);
// 선택약관 팝업 상태
const [isOptionalConfirmVisible, setIsOptionalConfirmVisible] = useState(false);
const [isOptionalTermsVisible, setIsOptionalTermsVisible] = useState(false);
const [optionalTermsAgreed, setOptionalTermsAgreed] = useState(false);
const { entryMenu, nowMenu } = useSelector((state) => state.common.menu);
const [focusedContainerId, setFocusedContainerId] = useState(panelInfo.focusedContainerId);
// DetailPanel 진입 시 포커스 대상 저장
@@ -271,6 +295,162 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
const loadingComplete = useSelector((state) => state.common?.loadingComplete);
const isVideoTransitionLocked = useSelector((state) => state.home.videoTransitionLocked);
// 선택약관 동의 핸들러
const handleOptionalAgree = useCallback(() => {
if (!termsIdMap || Object.keys(termsIdMap).length === 0) {
if (process.env.NODE_ENV === 'development') {
console.error('[HomePanel] termsIdMap이 없습니다:', termsIdMap);
}
return;
}
const requiredTermTypes = ['MST00401', 'MST00402', 'MST00405'];
const missingTerms = requiredTermTypes.filter((type) => !termsIdMap[type]);
if (missingTerms.length > 0) {
if (process.env.NODE_ENV === 'development') {
console.error('[HomePanel] 누락된 약관 타입:', missingTerms);
}
return;
}
const termsList = [];
if (termsIdMap['MST00401']) {
termsList.push(termsIdMap['MST00401']); // 개인정보처리방침
}
if (termsIdMap['MST00402']) {
termsList.push(termsIdMap['MST00402']); // 이용약관
}
if (termsIdMap['MST00405']) {
termsList.push(termsIdMap['MST00405']); // 선택약관
}
const notTermsList = [];
if (process.env.NODE_ENV === 'development') {
console.log('[HomePanel] 현재 termsIdMap:', termsIdMap);
console.log('[HomePanel] 약관 동의 API 호출 파라미터:', {
termsList,
notTermsList,
});
}
const callback = (response) => {
if (response.retCode === '000' || response.retCode === 0) {
if (process.env.NODE_ENV === 'development') {
console.log('[HomePanel] 약관 동의 성공:', response);
}
dispatch(updateOptionalTermsAgreement(true));
setOptionalTermsAgreed(true);
} else {
if (process.env.NODE_ENV === 'development') {
console.error('[HomePanel] 약관 동의 실패:', response);
}
}
};
dispatch(setMyPageTermsAgree({ termsList, notTermsList }, callback));
}, [dispatch, termsIdMap]);
const handleOptionalTermsClick = useCallback(() => {
if (process.env.NODE_ENV === 'development') {
console.log('[HomePanel] 약관 자세히 보기 클릭');
}
setIsOptionalConfirmVisible(false);
setIsOptionalTermsVisible(true);
}, []);
const handleOptionalAgreeClick = useCallback(() => {
handleOptionalAgree();
setIsOptionalConfirmVisible(false);
dispatch({
type: types.GET_TERMS_AGREE_YN_SUCCESS,
payload: {
...currentTermsFlag,
optionalTerms: 'Y',
},
});
}, [handleOptionalAgree, dispatch, currentTermsFlag]);
const handleOptionalDeclineClick = useCallback(() => {
if (process.env.NODE_ENV === 'development') {
console.log('[HomePanel] 거절/다음에 하기 버튼 클릭');
}
dispatch(updateOptionalTermsAgreement(false));
setIsOptionalConfirmVisible(false);
}, [dispatch]);
const handleTermsPopupClosed = useCallback(() => {
setIsOptionalTermsVisible(false);
setIsOptionalConfirmVisible(true);
Spotlight.focus('optional-confirm-popup');
}, []);
const handleTermsPopupAgree = useCallback(() => {
if (process.env.NODE_ENV === 'development') {
console.log('[HomePanel] handleTermsPopupAgree');
}
handleOptionalAgree();
setIsOptionalTermsVisible(false);
}, [handleOptionalAgree]);
// shouldShowOptionalTermsPopup 계산
const shouldShowOptionalTermsPopup = useMemo(() => {
// 1. 기본 조건 확인
if (termsLoading || isGnbOpened || !optionalTermsAvailable) {
return false;
}
// 2. 새로운 Redux 상태 확인 (TV 환경 최적화)
if (
optionalTermsPopupFlow.popupShown ||
optionalTermsPopupFlow.userDecision ||
optionalTermsPopupFlow.agreedInSession
) {
return false;
}
// 3. 서버 데이터 확인
const terms = termsData && termsData.data && termsData.data.terms;
if (!terms) {
return false;
}
const optionalTerm = terms.find((term) => term.trmsTpCd === 'MST00405');
const result = optionalTerm
? optionalTerm.trmsPopFlag === 'Y' && optionalTerm.trmsAgrFlag === 'N'
: false;
return result;
}, [
termsData.data?.terms,
termsLoading,
isGnbOpened,
optionalTermsAvailable,
optionalTermsPopupFlow,
]);
// 선택약관 팝업 표시 처리
useEffect(() => {
if (termsLoading) {
return;
}
if (shouldShowOptionalTermsPopup && !isOptionalConfirmVisible) {
console.log('shouldShowOptionalTermsPopup', shouldShowOptionalTermsPopup);
console.log('HomePanel optionalTermsConfirm 팝업 표시');
const timer = setTimeout(() => {
setIsOptionalConfirmVisible(true);
dispatch(setOptionalTermsPopupShown(true));
}, 1000);
return () => clearTimeout(timer);
}
}, [shouldShowOptionalTermsPopup, termsLoading, isOptionalConfirmVisible, dispatch]);
const onCancel = useCallback(() => {
const currentSpot = Spotlight.getCurrent();
@@ -1159,6 +1339,33 @@ const HomePanel = ({ isOnTop, showGradientBackground = false }) => {
{(activePopup === ACTIVE_POPUP.eventPopup || activePopup === ACTIVE_POPUP.smsPopup) && (
<EventPopUpBanner />
)}
{/* 선택약관 동의 팝업 */}
<OptionalConfirm
open={isOptionalConfirmVisible}
spotlightId="optional-confirm-popup"
onClose={handleOptionalDeclineClick}
onOptionalTermsClick={handleOptionalTermsClick}
onOptionalAgreeClick={handleOptionalAgreeClick}
onOptionalDeclineClick={handleOptionalDeclineClick}
customPosition={true}
position={{
position: 'absolute',
top: '385px',
left: '0px',
bottom: 'unset',
transform: 'none',
}}
/>
{/* 선택약관 자세히 보기 팝업 */}
<TNewPopUp
kind="figmaTermsPopup"
open={isOptionalTermsVisible}
title={$L('Optional Terms')}
text={optionalTermsData?.trmsCntt || ''}
onClose={handleTermsPopupClosed}
onAgreeClick={handleTermsPopupAgree}
showAgreeButton={true}
/>
</TPanel>
</>
);