diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx b/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx index 77f4a530..44ccd1f6 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx @@ -1,60 +1,37 @@ -import React, { - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import classNames from 'classnames'; // import { throttle } from 'lodash'; import { PropTypes } from 'prop-types'; -import { - useDispatch, - useSelector, -} from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import Spotlight from '@enact/spotlight'; /* eslint-disable react/jsx-no-bind */ // src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx -import SpotlightContainerDecorator - from '@enact/spotlight/SpotlightContainerDecorator'; +import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator'; import Spottable from '@enact/spotlight/Spottable'; import couponImg from '../../../../assets/images/icons/coupon.png'; // import Spottable from '@enact/spotlight/Spottable'; //image -import arrowDown - from '../../../../assets/images/icons/ic_arrow_down_3x_new.png'; -import indicatorDefaultImage - from '../../../../assets/images/img-thumb-empty-144@3x.png'; -import { - setHidePopup, - setShowPopup, -} from '../../../actions/commonActions.js'; +import arrowDown from '../../../../assets/images/icons/ic_arrow_down_3x_new.png'; +import indicatorDefaultImage from '../../../../assets/images/img-thumb-empty-144@3x.png'; +import { setHidePopup, setShowPopup } from '../../../actions/commonActions.js'; import { getProductCouponDownload, getProductCouponSearch, getProductCouponTotDownload, } from '../../../actions/couponActions.js'; // import { pushPanel } from '../../../actions/panelActions'; -import { - minimizeModalMedia, - restoreModalMedia, -} from '../../../actions/mediaActions'; +import { minimizeModalMedia, restoreModalMedia } from '../../../actions/mediaActions'; import { pauseFullscreenVideo } from '../../../actions/playActions'; import { resetShowAllReviews } from '../../../actions/productActions'; -import { - clearAllToasts, - removeToast, - showToast, -} from '../../../actions/toastActions'; +import { clearAllToasts, removeToast, showToast } from '../../../actions/toastActions'; import CustomImage from '../../../components/CustomImage/CustomImage.jsx'; // ProductInfoSection imports import TButton from '../../../components/TButton/TButton'; import TPopUp from '../../../components/TPopUp/TPopUp.jsx'; -import TVirtualGridList - from '../../../components/TVirtualGridList/TVirtualGridList.jsx'; +import TVirtualGridList from '../../../components/TVirtualGridList/TVirtualGridList.jsx'; import useReviews from '../../../hooks/useReviews/useReviews'; import useScrollTo from '../../../hooks/useScrollTo'; import { BUYNOW_CONFIG } from '../../../utils/BuyNowConfig'; @@ -86,19 +63,13 @@ import StarRating from '../components/StarRating'; // ProductContentSection imports import TScrollerDetail from '../components/TScroller/TScrollerDetail'; import DetailPanelSkeleton from '../DetailPanelSkeleton/DetailPanelSkeleton'; -import ProductDescription - from '../ProductContentSection/ProductDescription/ProductDescription'; -import ProductDetail - from '../ProductContentSection/ProductDetail/ProductDetail.new'; -import { - ProductVideoV2, -} from '../ProductContentSection/ProductVideo/ProductVideo.v2.jsx'; -import ProductVideo - from '../ProductContentSection/ProductVideo/ProductVideo.v3'; +import ProductDescription from '../ProductContentSection/ProductDescription/ProductDescription'; +import ProductDetail from '../ProductContentSection/ProductDetail/ProductDetail.new'; +import { ProductVideoV2 } from '../ProductContentSection/ProductVideo/ProductVideo.v2.jsx'; +import ProductVideo from '../ProductContentSection/ProductVideo/ProductVideo.v3'; import UserReviews from '../ProductContentSection/UserReviews/UserReviews'; // import ViewAllReviewsButton from '../ProductContentSection/UserReviews/ViewAllReviewsButton'; -import YouMayAlsoLike - from '../ProductContentSection/YouMayAlsoLike/YouMayAlsoLike'; +import YouMayAlsoLike from '../ProductContentSection/YouMayAlsoLike/YouMayAlsoLike'; import QRCode from '../ProductInfoSection/QRCode/QRCode'; import ProductOverview from '../ProductOverview/ProductOverview'; // CSS imports @@ -139,7 +110,7 @@ const HorizontalContainer = SpotlightContainerDecorator( 'div' ); -const SpottableComponent = Spottable("div"); +const SpottableComponent = Spottable('div'); const getProductData = curry((productType, themeProductInfo, productInfo) => pipe( @@ -192,15 +163,9 @@ export default function ProductAllSection({ // YouMayLike 데이터는 API 응답 시간이 걸리므로 직접 구독 const youmaylikeData = useSelector((state) => state.main.youmaylikeData); //coupon - const { partnerCoupon } = useSelector( - (state) => state.coupon.productCouponSearchData - ); - const { userNumber } = useSelector( - (state) => state.common.appStatus.loginUserData - ); - const { popupVisible, activePopup } = useSelector( - (state) => state.common.popup - ); + const { partnerCoupon } = useSelector((state) => state.coupon.productCouponSearchData); + const { userNumber } = useSelector((state) => state.common.appStatus.loginUserData); + const { popupVisible, activePopup } = useSelector((state) => state.common.popup); // ProductVideo 버전 관리 (1: 기존 modal 방식, 2: 내장 방식 , 3: 비디오 생략) const [productVideoVersion, setProductVideoVersion] = useState(1); // 비디오 재생 여부 flag (재생 전에는 minimize/restore 로직 비활성화) @@ -235,10 +200,10 @@ export default function ProductAllSection({ const [selectedCoupon, setSelectedCoupon] = useState(); const [downloadCouponArr, setDownloadCouponArr] = useState([]); const [couponTypes, setCouponTypes] = useState(null); - const [couponCodes, setCouponCodes] = useState(""); + const [couponCodes, setCouponCodes] = useState(''); const [focused, setFocused] = useState(false); - useEffect(()=>{ + useEffect(() => { dispatch( getProductCouponSearch({ patnrId: selectedPatnrId, @@ -246,18 +211,18 @@ export default function ProductAllSection({ mbrNo: userNumber, }) ); - },[dispatch]) - + }, [dispatch]); + const getCouponCode = () => { const snoArray = []; for (let i = 0; i < selectedCoupon.length; i++) { - if (selectedCoupon[i].downloadYn === "Y") { + if (selectedCoupon[i].downloadYn === 'Y') { snoArray.push(selectedCoupon[i].cpnSno); } } - setCouponCodes(snoArray.join(", ")); + setCouponCodes(snoArray.join(', ')); }; useEffect(() => { @@ -269,7 +234,7 @@ export default function ProductAllSection({ useEffect(() => { const newPromotions = []; if (partnerCoupon && partnerCoupon.length > 0) { - newPromotions.push($L("SPECIAL PROMOTION")); + newPromotions.push($L('SPECIAL PROMOTION')); } setPromotions(newPromotions); }, [partnerCoupon]); @@ -280,7 +245,7 @@ export default function ProductAllSection({ dispatch(setShowPopup(Config.ACTIVE_POPUP.loginPopup)); return; } - if (promotion === "SPECIAL PROMOTION") { + if (promotion === 'SPECIAL PROMOTION') { setSelectedCoupon(partnerCoupon); } setCouponTypes(idx); @@ -291,9 +256,7 @@ export default function ProductAllSection({ const handleCouponTotDownload = useCallback(() => { if (selectedCoupon && userNumber) { - const couponCodesArray = couponCodes - .split(",") - .map((code) => parseInt(code.trim())); + const couponCodesArray = couponCodes.split(',').map((code) => parseInt(code.trim())); setDownloadCouponArr((prevArr) => [...prevArr, ...couponCodesArray]); dispatch( @@ -302,7 +265,7 @@ export default function ProductAllSection({ cpnSnoAll: couponCodes, }) ); - dispatch(showToast({message : "Your coupon download is complete."})); + dispatch(showToast({ message: 'Your coupon download is complete.' })); } }, [dispatch, selectedCoupon, userNumber, couponCodes]); @@ -311,7 +274,7 @@ export default function ProductAllSection({ dispatch(setHidePopup()); let currentSpot; - if (typeof spotlightId === "string") { + if (typeof spotlightId === 'string') { currentSpot = spotlightId; } else { currentSpot = SpotlightIds.DETAIL_BUYNOW; @@ -324,7 +287,7 @@ export default function ProductAllSection({ } }, [dispatch] - ) + ); const renderItem = useCallback( ({ index, ...rest }) => { @@ -343,8 +306,8 @@ export default function ProductAllSection({ duplDwldYn, } = selectedCoupon[index]; - const couponAplyStartDate = cpnAplyStrtDtt.split(" ")[0]; - const couponAplyEndDate = cpnAplyEndDtt.split(" ")[0]; + const couponAplyStartDate = cpnAplyStrtDtt.split(' ')[0]; + const couponAplyEndDate = cpnAplyEndDtt.split(' ')[0]; const onFocus = (index) => { setSelectedCouponIndex(index); @@ -359,16 +322,14 @@ export default function ProductAllSection({ if ( downloadCouponArr.length > 0 && downloadCouponArr.includes(cpnSno) && - (downloadYn === "N" || (duplDwldYn === "Y" && downloadYn === "Y")) + (downloadYn === 'N' || (duplDwldYn === 'Y' && downloadYn === 'Y')) ) { return; } setDownloadCouponArr((prevArr) => [...prevArr, cpnSno]); - dispatch( - getProductCouponDownload({ mbrNo: userNumber, cpnSno: cpnSno }) - ); - dispatch(showToast({message : "Your coupon download is complete."})); - }; + dispatch(getProductCouponDownload({ mbrNo: userNumber, cpnSno: cpnSno })); + dispatch(showToast({ message: 'Your coupon download is complete.' })); + }; return ( onFocus(index)} onBlur={onBlur} - onClick={downloadYn === "N" ? null : handleDownloadClick} + onClick={downloadYn === 'N' ? null : handleDownloadClick} >
- {shptmDcTpCd === "CPN00401" && ( + {shptmDcTpCd === 'CPN00401' && ( {`${currSign}${dcAmt}`} )} - {shptmDcTpCd === "CPN00402" && ( + {shptmDcTpCd === 'CPN00402' && ( {`${cpnDctrt}%`} )} @@ -403,25 +358,21 @@ export default function ProductAllSection({
- {$L( - "Purchase over ${cpnAplyMinPurcAmt} (up to ${cpnAplyMaxDcAmt} off)" - ) - .replace("{cpnAplyMinPurcAmt}", cpnAplyMinPurcAmt) - .replace("{cpnAplyMaxDcAmt}", cpnAplyMaxDcAmt)} + {$L('Purchase over ${cpnAplyMinPurcAmt} (up to ${cpnAplyMaxDcAmt} off)') + .replace('{cpnAplyMinPurcAmt}', cpnAplyMinPurcAmt) + .replace('{cpnAplyMaxDcAmt}', cpnAplyMaxDcAmt)} {couponAplyStartDate}~{couponAplyEndDate}
- {downloadYn !== "N" && ( + {downloadYn !== 'N' && (
0 && - downloadCouponArr.includes(cpnSno) && - css.disable, - duplDwldYn === "N" && downloadYn === "Y" && css.disable, + downloadCouponArr.length > 0 && downloadCouponArr.includes(cpnSno) && css.disable, + duplDwldYn === 'N' && downloadYn === 'Y' && css.disable, !downloadCouponArr.includes(cpnSno) && index === selectedCouponIndex && focused && @@ -429,13 +380,9 @@ export default function ProductAllSection({ )} aria-label="Download Button" > - {downloadCouponArr.length > 0 && - downloadCouponArr.includes(cpnSno) - ? - $L("DOWNLOAD COMPLETED") - : - $L("DOWNLOAD") - } + {downloadCouponArr.length > 0 && downloadCouponArr.includes(cpnSno) + ? $L('DOWNLOAD COMPLETED') + : $L('DOWNLOAD')}
)}
@@ -666,13 +613,13 @@ export default function ProductAllSection({ //닫히도록 const handleCloseToast = useCallback(() => { - // 팝업이 열려있으면 닫지 않음 - if (popupVisible) { - return; // 팝업이 활성이면 무시 - } - dispatch(clearAllToasts()); - setOpenToast(false); -}, [dispatch, popupVisible]); + // 팝업이 열려있으면 닫지 않음 + if (popupVisible) { + return; // 팝업이 활성이면 무시 + } + dispatch(clearAllToasts()); + setOpenToast(false); + }, [dispatch, popupVisible]); // 스크롤 컨테이너의 클릭 이벤트 추적용 로깅 const handleScrollContainerClick = useCallback((e) => { @@ -901,18 +848,20 @@ export default function ProductAllSection({ ); // BUY NOW, ADD TO CART 버튼에서 arrow up 시: 항상 헤더 뒤로가기 버튼으로 - const handleSpotlightUpFromBuyButtons = useCallback((e) => { - e.stopPropagation(); - if(promotions && promotions.length > 0){ - Spotlight.focus('detail-coupon-button'); - } else { - // 쿠폰 버튼이 없을 때 뒤로가기 버튼으로 포커스 이동 (특정 Chrome 버전에서의 포커스 문제 해결) - setTimeout(() => { - Spotlight.focus('spotlightId_backBtn'); - }, 0); - } - }, [promotions]); - + const handleSpotlightUpFromBuyButtons = useCallback( + (e) => { + e.stopPropagation(); + if (promotions && promotions.length > 0) { + Spotlight.focus('detail-coupon-button'); + } else { + // 쿠폰 버튼이 없을 때 뒤로가기 버튼으로 포커스 이동 (특정 Chrome 버전에서의 포커스 문제 해결) + setTimeout(() => { + Spotlight.focus('spotlightId_backBtn'); + }, 0); + } + }, + [promotions] + ); const handleSpotlightUpFromCouponButtons = useCallback((e) => { e.stopPropagation(); @@ -923,6 +872,28 @@ export default function ProductAllSection({ e.stopPropagation(); }, []); + // SHOP BY MOBILE에서 아래로 이동 시 제품 정보 영역으로 포커스 이동 + const handleSpotlightDownFromShopByMobile = useCallback((e) => { + e.stopPropagation(); + e.preventDefault(); + + if (!Spotlight.focus('product-details-button')) { + Spotlight.focus('user-reviews-button'); + } + + // 포커스가 비거나 다른 곳으로 튀는 경우를 방어 + setTimeout(() => { + const current = Spotlight.getCurrent(); + const currentId = current?.dataset?.spotlightId; + if ( + !current || + (currentId !== 'product-details-button' && currentId !== 'user-reviews-button') + ) { + Spotlight.focus('product-details-button') || Spotlight.focus('user-reviews-button'); + } + }, 0); + }, []); + const onFavoriteFlagChanged = useCallback( (newFavoriteFlag) => setFavoriteOverride(newFavoriteFlag), [] @@ -1216,8 +1187,6 @@ export default function ProductAllSection({ }; }, []); - - // 초기 로딩 중에는 Skeleton 표시 if (isInitialLoading) { return ( @@ -1226,9 +1195,6 @@ export default function ProductAllSection({
); } - - - return ( @@ -1276,31 +1242,32 @@ export default function ProductAllSection({ - {userNumber && promotions.map((promotion, idx) => { - return( -
-
-
SPECIAL PROMOTION
-
Coupon only applicable to this product!
-
- { - handleCouponClick(idx, promotion); - }} - onSpotlightUp={handleSpotlightUpFromCouponButtons} - onSpotlightDown={handleSpotlightDown} - size="detail_very_small" - > -
- COUPON + {userNumber && + promotions.map((promotion, idx) => { + return ( +
+
+
SPECIAL PROMOTION
+
+ Coupon only applicable to this product! +
- - -
- ) - })} + { + handleCouponClick(idx, promotion); + }} + onSpotlightUp={handleSpotlightUpFromCouponButtons} + onSpotlightDown={handleSpotlightDown} + size="detail_very_small" + > +
COUPON
+ +
+
+ ); + })} {isBillingProductVisible && (
{$L('SHOP BY MOBILE')}
@@ -1608,8 +1576,8 @@ export default function ProductAllSection({ onClick={handleCouponTotDownload} onClose={onClose} hasButton - button1Text={$L("DOWNLOAD ALL")} - button2Text={$L("CLOSE")} + button1Text={$L('DOWNLOAD ALL')} + button2Text={$L('CLOSE')} > {selectedCoupon && selectedCoupon.length > 0 && ( @@ -1625,9 +1593,7 @@ export default function ProductAllSection({ /> )} -
{`1/${selectedCoupon?.length}`}
+
{`1/${selectedCoupon?.length}`}
)} diff --git a/com.twin.app.shoptime/src/views/DetailPanel/components/FavoriteBtn.jsx b/com.twin.app.shoptime/src/views/DetailPanel/components/FavoriteBtn.jsx index 152e84de..94e0620c 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/components/FavoriteBtn.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/components/FavoriteBtn.jsx @@ -66,16 +66,39 @@ export default function FavoriteBtn({ onFavoriteFlagChanged(favoriteFlag === 'Y' ? 'N' : 'Y'); }, [dispatch, favoriteFlag, onFavoriteFlagChanged]); - const handleFavoriteKeyDown = useCallback((e) => { - if (e.key === 'Enter') { - e.preventDefault(); - handleFavoriteClick(); - } - }, [handleFavoriteClick]); + const handleFavoriteKeyDown = useCallback( + (e) => { + if (e.key === 'Enter') { + e.preventDefault(); + handleFavoriteClick(); + } + }, + [handleFavoriteClick] + ); - const handleSpotlightDown = useCallback((e) => { - e.stopPropagation(); - }, []); + const handleSpotlightDown = useCallback( + (e) => { + e.stopPropagation(); + if (kind === 'item_detail') { + e.preventDefault(); + if (!Spotlight.focus('product-details-button')) { + Spotlight.focus('user-reviews-button'); + } + // 포커스가 비거나 다른 곳으로 튀는 경우를 방어 + setTimeout(() => { + const current = Spotlight.getCurrent(); + const currentId = current?.dataset?.spotlightId; + if ( + !current || + (currentId !== 'product-details-button' && currentId !== 'user-reviews-button') + ) { + Spotlight.focus('product-details-button') || Spotlight.focus('user-reviews-button'); + } + }, 0); + } + }, + [kind] + ); return (