Files
shoptime/com.twin.app.shoptime/src/components/GlobalPopup/GlobalPopup.jsx
optrader f5621b0c55 [251211] feat: FeaturedBrandsPanel , TopBannerImage Modal
🕐 커밋 시간: 2025. 12. 11. 14:04:14

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

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/components/GlobalPopup/GlobalPopup.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/TopBannerImage/TopBannerPopup.jsx
  ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/TopBannerImage/TopBannerPopup.module.less

🔧 주요 변경 내용:
  • UI 컴포넌트 아키텍처 개선
  • 소규모 기능 개선
2025-12-11 14:04:15 +09:00

328 lines
8.8 KiB
JavaScript

/**
* 글로벌 팝업 컴포넌트
* 중앙집중식 팝업 시스템의 메인 컴포넌트
* 기존 TPopUp과 완전히 호환
*/
import React, {
useCallback,
useEffect,
useMemo,
} from 'react';
import {
useDispatch,
useSelector,
} from 'react-redux';
import Spotlight from '@enact/spotlight';
import { setHidePopup } from '../../actions/commonActions';
import { getPopupConfig } from '../../constants/popupConfig';
import usePrevious from '../../hooks/usePrevious';
import TPopUp from '../TPopUp/TPopUp';
import TopBannerPopup from '../../views/FeaturedBrandsPanel/TopBannerImage/TopBannerPopup';
// 커스텀 훅: 팝업 상태 관리
const useGlobalPopupState = () => {
return useSelector(state => state.common.popup);
};
// 커스텀 훅: 팝업 닫기 핸들러
const usePopupCloseHandlers = () => {
const dispatch = useDispatch();
const { popupData } = useGlobalPopupState();
const handleClose = useCallback(() => {
dispatch(setHidePopup());
}, [dispatch]);
const handleNetworkErrorRetry = useCallback(() => {
// 네트워크 재시도 로직 구현
console.log('Retrying network connection...');
handleClose();
}, [handleClose]);
const handleExit = useCallback(() => {
// 앱 종료 로직 구현
console.log('Exiting application...');
handleClose();
}, [handleClose]);
const handlePopupClick = useCallback(() => {
// 팝업 클릭 핸들러
if (popupData?.onClick) {
popupData.onClick();
}
handleClose();
}, [popupData, handleClose]);
const handleWatchClose = useCallback(() => {
// 시청 알림 닫기
if (popupData?.onWatchClose) {
popupData.onWatchClose();
}
handleClose();
}, [popupData, handleClose]);
const handleErrorPopupClose = useCallback(() => {
// 에러 팝업 닫기
if (popupData?.onErrorClose) {
popupData.onErrorClose();
}
handleClose();
}, [popupData, handleClose]);
return {
handleClose,
handleNetworkErrorRetry,
handleExit,
handlePopupClick,
handleWatchClose,
handleErrorPopupClose
};
};
// 팝업 내용 렌더러 컴포넌트
const PopupContentRenderer = ({ activePopup, popupData, config }) => {
// 특수한 팝업 타입에 대한 커스텀 렌더링
switch (activePopup) {
case 'errorPopup':
return (
<div className="popup-container">
<p className="error-message">
{popupData && (
popupData.errorMessage ||
popupData.errorMsg ||
'An error occurred'
)}
</p>
{/* TPopUp 내부의 버튼이 처리 */}
</div>
);
case 'networkErrorPopup':
return (
<div className="network-error-content">
<p>{config.defaultText}</p>
</div>
);
case 'watchPopup':
return (
<div className="watch-alert-content">
{/* TPopUp이 처리 */}
</div>
);
default:
return null; // 대부분의 경우 TPopUp이 직접 처리
}
};
// 메인 GlobalPopup 컴포넌트
const GlobalPopup = () => {
const {
popupVisible,
activePopup,
data: popupData,
text: popupText,
secondaryPopupVisible,
secondaryPopup,
secondaryData
} = useGlobalPopupState();
const [imageDimensions, setImageDimensions] = React.useState({ width: 0, height: 0 });
const handlers = usePopupCloseHandlers();
const previousPopupVisible = usePrevious(popupVisible);
const handleImageLoad = useCallback((dimensions) => {
console.log("[GLOBAL-POPUP] Image dimensions received:", dimensions);
setImageDimensions(dimensions);
}, []);
// Spotlight 제어: 팝업 오픈/클로즈 시 포커스 트래핑
useEffect(() => {
if (popupVisible && activePopup === 'topBannerImagePopup') {
console.log("[GLOBAL-POPUP] Pausing Spotlight for modal popup");
Spotlight.pause();
return () => {
console.log("[GLOBAL-POPUP] Resuming Spotlight after modal close");
Spotlight.resume();
};
}
}, [popupVisible, activePopup]);
// 현재 팝업 설정
const currentConfig = useMemo(() => {
if (!activePopup) return null;
return getPopupConfig(activePopup);
}, [activePopup]);
// 팝업 속성 동적 계산
const popupProps = useMemo(() => {
if (!currentConfig) return {};
const baseProps = {
kind: currentConfig.kind,
// open: popupVisible, //우선 주석처리 추후 조건에 맞게 변경처리 예정.
open: false,
onClose: handlers.handleClose,
className: currentConfig.className
};
// 기본 TPopUp 속성
if (currentConfig.hasButton !== undefined) {
baseProps.hasButton = currentConfig.hasButton;
}
if (currentConfig.hasText !== undefined) {
baseProps.hasText = currentConfig.hasText;
}
if (currentConfig.button1Text) {
baseProps.button1Text = currentConfig.button1Text;
}
if (currentConfig.button2Text) {
baseProps.button2Text = currentConfig.button2Text;
}
if (currentConfig.title) {
baseProps.title = currentConfig.title;
}
// 동적 데이터
if (popupText || currentConfig.defaultText) {
baseProps.text = popupText || currentConfig.defaultText;
}
// 특수 속성
if (currentConfig.hasLogo) baseProps.hasLogo = currentConfig.hasLogo;
if (currentConfig.hasThumbnail) baseProps.hasThumbnail = currentConfig.hasThumbnail;
if (currentConfig.scrollable) baseProps.scrollable = currentConfig.scrollable;
return baseProps;
}, [currentConfig, popupVisible, popupText, handlers]);
// 특정 팝업 타입별 핸들러 매핑
const getSpecificHandler = useCallback((handlerType) => {
const handlerMap = {
'networkErrorPopup': {
onClick: handlers.handleNetworkErrorRetry,
onClose: handlers.handleExit
},
'unSupportedCountryPopup': {
onClick: handlers.handleExit,
onClose: handlers.handleExit
},
'changeCountyPopup': {
onClick: handlers.handlePopupClick,
onClose: handlers.handleClose
},
'watchPopup': {
onClose: handlers.handleWatchClose,
onClick: handlers.handlePopupClick
},
'errorPopup': {
onClose: handlers.handleErrorPopupClose
},
'toast': {
onClose: handlers.handleClose
}
};
return handlerMap[activePopup]?.[handlerType];
}, [activePopup, handlers]);
// 팝업이 없으면 렌더링하지 않음
if (!activePopup || !popupVisible) {
return null;
}
// TopBannerImagePopup 특수 처리
if (activePopup === 'topBannerImagePopup') {
// Figma 디자인 기반 고정 크기
// 너비: 1060px
// 높이: 헤더(110px) + 이미지(556px) + 푸터(138px) = 804px
const popupWidth = '1060px';
const popupHeight = '804px';
return (
<div
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: 9999
}}
onClick={handlers.handleClose}
>
<div
style={{
width: popupWidth,
height: popupHeight,
backgroundColor: 'white',
borderRadius: '12px',
overflow: 'hidden',
display: 'flex',
flexDirection: 'column'
}}
onClick={(e) => e.stopPropagation()}
>
<TopBannerPopup
title={popupData?.pupBanrImgNm || 'Popup'}
imageUrl={popupData?.pupBanrImgUrl}
imageAlt={popupData?.pupBanrImgNm || 'Popup Banner'}
onImageLoad={handleImageLoad}
/>
</div>
</div>
);
}
// 설정이 없으면 기본 팝업도 렌더링하지 않음
if (!currentConfig) {
console.warn(`No configuration found for popup type: ${activePopup}`);
return null;
}
// 특정 핸들러들 적용
const specificOnClick = getSpecificHandler('onClick');
const specificOnClose = getSpecificHandler('onClose');
return (
<>
{/* 메인 팝업 */}
<TPopUp
{...popupProps}
onClick={specificOnClick || popupProps.onClick}
onClose={specificOnClose || popupProps.onClose}
>
<PopupContentRenderer
activePopup={activePopup}
popupData={popupData}
config={currentConfig}
/>
</TPopUp>
{/* 세컨더리 팝업 (필요한 경우) */}
{secondaryPopupVisible && secondaryPopup && (
<TPopUp
kind={getPopupConfig(secondaryPopup)?.kind || 'normal'}
open={secondaryPopupVisible}
onClose={handlers.handleClose}
>
<div className="secondary-popup-content">
{secondaryData?.text && <p>{secondaryData.text}</p>}
</div>
</TPopUp>
)}
</>
);
};
export default GlobalPopup;