[251210] feat: FeaturedBrandsPanel-TopBannerImage Popup

🕐 커밋 시간: 2025. 12. 10. 16:33:37

📊 변경 통계:
  • 총 파일: 6개
  • 추가: +69줄
  • 삭제: -5줄

📁 추가된 파일:
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/TopBannerImage/TopBannerPopup.figma.jsx
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/TopBannerImage/TopBannerPopup.jsx
  + com.twin.app.shoptime/src/views/FeaturedBrandsPanel/TopBannerImage/TopBannerPopup.module.less

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

🔧 주요 변경 내용:
  • UI 컴포넌트 아키텍처 개선
  • 소규모 기능 개선
  • 모듈 구조 개선
This commit is contained in:
2025-12-10 16:33:38 +09:00
parent bf7af5aa2e
commit a2b29d219a
6 changed files with 243 additions and 5 deletions

View File

@@ -18,6 +18,7 @@ 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 = () => {
@@ -128,9 +129,16 @@ const GlobalPopup = () => {
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);
}, []);
// 현재 팝업 설정
const currentConfig = useMemo(() => {
if (!activePopup) return null;
@@ -214,6 +222,47 @@ const GlobalPopup = () => {
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
}}>
<div style={{
width: popupWidth,
height: popupHeight,
backgroundColor: 'white',
borderRadius: '12px',
overflow: 'hidden',
display: 'flex',
flexDirection: 'column'
}}>
<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}`);

View File

@@ -26,11 +26,12 @@ export default memo(function Banner({
console.log("[FB-BANNER-COMP] isNBCU:", isNBCU);
console.log("[FB-BANNER-COMP] brandTopBannerInfo:", brandTopBannerInfo);
// Top Banner 정보에서 필요한 필드 추출 (현재는 사용하지 않음)
// Top Banner 정보에서 필요한 필드 추출
const {
banrImgUrl, // 배너 이미지 URL
banrImgNm, // 배너 이미지 이름
pupBanrImgUrl, // 팝업 배너 이미지 URL
pupBanrImgNm, // 팝업 배너 이미지 이름
pupBanrTtl, // 팝업 배너 타이틀
banrNm // 배너 이름
} = brandTopBannerInfo || {};
@@ -76,6 +77,8 @@ export default memo(function Banner({
banrImgUrl={banrImgUrl}
banrImgNm={banrImgNm}
banrNm={banrNm}
pupBanrImgUrl={pupBanrImgUrl}
pupBanrImgNm={pupBanrImgNm}
spotlightId="nbcu-top-banner-image"
/>
)}

View File

@@ -1,4 +1,6 @@
import React, { memo, useCallback, useState } from "react";
import { useDispatch } from "react-redux";
import { setShowPopup } from "../../../actions/commonActions";
import CustomImage from "../../../components/CustomImage/CustomImage";
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
import css from "./TopBannerImage.module.less";
@@ -8,16 +10,26 @@ const Container = SpotlightContainerDecorator(
"div"
);
const TopBannerImage = memo(({ banrImgUrl, banrImgNm, banrNm, spotlightId }) => {
const TopBannerImage = memo(({ banrImgUrl, banrImgNm, banrNm, pupBanrImgUrl, pupBanrImgNm, spotlightId }) => {
console.log("[TOP-BANNER-IMG] Rendering with URL:", banrImgUrl);
console.log("[TOP-BANNER-IMG] spotlightId:", spotlightId);
const dispatch = useDispatch();
const [imageDimensions, setImageDimensions] = useState({ width: 0, height: 0 });
const handleClick = useCallback(() => {
console.log("[TOP-BANNER-IMG] Clicked");
// 필요시 클릭 핸들러 로직 추가
}, []);
console.log("[TOP-BANNER-IMG] Clicked - Opening popup");
if (pupBanrImgUrl) {
console.log("[TOP-BANNER-IMG] Dispatching topBannerImagePopup");
dispatch(setShowPopup({
activePopup: 'topBannerImagePopup',
data: {
pupBanrImgUrl,
pupBanrImgNm: pupBanrImgNm || banrImgNm || banrNm
}
}));
}
}, [dispatch, pupBanrImgUrl, pupBanrImgNm, banrImgNm, banrNm]);
const handleImageLoad = useCallback((e) => {
const img = e.target;

View File

@@ -0,0 +1,13 @@
<div style={{width: '100%', height: '100%', background: 'white', overflow: 'hidden', borderRadius: 12, flexDirection: 'column', justifyContent: 'center', alignItems: 'center', display: 'inline-flex'}}>
<div style={{alignSelf: 'stretch', height: 119, padding: 30, background: '#E7EBEF', justifyContent: 'flex-start', alignItems: 'center', gap: 15, display: 'inline-flex'}}>
<div style={{textAlign: 'center', color: 'black', fontSize: 42, fontFamily: 'LG Smart UI', fontWeight: '700', lineHeight: 42, wordWrap: 'break-word'}}>Wells Fargo Active Cash Credit Card</div>
</div>
<div style={{alignSelf: 'stretch', justifyContent: 'center', alignItems: 'center', display: 'inline-flex'}}>
<img style={{flex: '1 1 0', height: 555.51}} src="https://placehold.co/1060x556" />
</div>
<div style={{alignSelf: 'stretch', paddingLeft: 60, paddingRight: 60, paddingTop: 30, paddingBottom: 30, justifyContent: 'center', alignItems: 'center', gap: 10, display: 'inline-flex'}}>
<div style={{width: 300, height: 78, background: '#7A808D', borderRadius: 12, justifyContent: 'center', alignItems: 'center', gap: 10, display: 'flex'}}>
<div style={{textAlign: 'center', color: 'white', fontSize: 30, fontFamily: 'LG Smart UI', fontWeight: '700', lineHeight: 30, wordWrap: 'break-word'}}>CLOSE</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,61 @@
import React, { memo, useCallback, useState } from "react";
import { useDispatch } from "react-redux";
import { setHidePopup } from "../../../actions/commonActions";
import css from "./TopBannerPopup.module.less";
const TopBannerPopup = memo(({ title, imageUrl, imageAlt, onImageLoad }) => {
const dispatch = useDispatch();
const [imageDimensions, setImageDimensions] = useState({ width: 0, height: 0 });
const handleImageLoad = useCallback((e) => {
const img = e.target;
console.log("[TOP-BANNER-POPUP] Image loaded - dimensions:", img.naturalWidth, "x", img.naturalHeight);
const dimensions = {
width: img.naturalWidth,
height: img.naturalHeight
};
setImageDimensions(dimensions);
// 부모 컴포넌트에 크기 전달
if (onImageLoad) {
onImageLoad(dimensions);
}
}, [onImageLoad]);
const handleClose = useCallback(() => {
console.log("[TOP-BANNER-POPUP] Closing popup");
dispatch(setHidePopup());
}, [dispatch]);
return (
<div className={css.container}>
{/* Title Section */}
<div className={css.titleSection}>
<div className={css.titleText}>{title}</div>
</div>
{/* Image Section */}
<div className={css.imageSection}>
<img
src={imageUrl}
alt={imageAlt || "Popup Banner"}
className={css.popupImage}
onLoad={handleImageLoad}
/>
</div>
{/* Button Section */}
<div className={css.buttonSection}>
<button className={css.closeButton} onClick={handleClose} aria-label="Close popup">
CLOSE
</button>
</div>
</div>
);
});
TopBannerPopup.displayName = "TopBannerPopup";
export default TopBannerPopup;

View File

@@ -0,0 +1,100 @@
.container {
width: 100%;
height: 100%;
background: white;
overflow: hidden;
border-radius: 12px;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
display: flex;
}
// 헤더: 높이 110px (상단 마진 30 + 내용 50 + 하단 마진 30)
.titleSection {
flex: 0 0 110px;
background: #E7EBEF;
padding-top: 30px;
padding-bottom: 30px;
padding-left: 30px;
padding-right: 30px;
justify-content: flex-start;
align-items: center;
gap: 15px;
display: flex;
}
.titleText {
flex: 1;
text-align: center;
color: black;
font-size: 42px;
font-family: 'LG Smart UI', sans-serif;
font-weight: 700;
line-height: 42px;
word-wrap: break-word;
word-break: break-word;
}
// 이미지: 높이 556px
.imageSection {
flex: 0 0 556px;
justify-content: center;
align-items: center;
display: flex;
background: white;
overflow: hidden;
}
.popupImage {
width: 100%;
height: 100%;
object-fit: contain;
}
// 푸터: 높이 138px (상단 마진 30 + 버튼 78 + 하단 마진 30)
.buttonSection {
flex: 0 0 138px;
padding-left: 60px;
padding-right: 60px;
padding-top: 30px;
padding-bottom: 30px;
justify-content: center;
align-items: center;
gap: 10px;
display: flex;
}
.closeButton {
width: 300px;
height: 78px;
background: #7A808D;
border-radius: 12px;
border: none;
justify-content: center;
align-items: center;
gap: 10px;
display: flex;
cursor: pointer;
transition: background-color 0.2s ease;
color: white;
font-size: 30px;
font-family: 'LG Smart UI', sans-serif;
font-weight: 700;
line-height: 30px;
text-align: center;
&:hover {
background: #6a7278;
}
&:focus {
outline: 2px solid #fff;
outline-offset: 2px;
}
&:active {
background: #5a6268;
}
}