🕐 커밋 시간: 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 컴포넌트 아키텍처 개선 • 소규모 기능 개선
328 lines
8.8 KiB
JavaScript
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; |