[251101] fix: BuyNow Mock Mocde

🕐 커밋 시간: 2025. 11. 01. 16:14:38

📊 변경 통계:
  • 총 파일: 13개
  • 추가: +504줄
  • 삭제: -170줄

📁 추가된 파일:
  + com.twin.app.shoptime/src/utils/BuyNowConfig.js
  + com.twin.app.shoptime/src/utils/BuyNowDataManipulator.js

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/hooks/useReviews/useReviews.js
  ~ com.twin.app.shoptime/src/views/CheckOutPanel/CheckOutPanel.jsx
  ~ com.twin.app.shoptime/src/views/CheckOutPanel/container/FixedSideBar.jsx
  ~ com.twin.app.shoptime/src/views/CheckOutPanel/container/InformationContainer.jsx
  ~ com.twin.app.shoptime/src/views/CheckOutPanel/container/OrderItemsSideBar.jsx
  ~ com.twin.app.shoptime/src/views/CheckOutPanel/container/SummaryCotainer.jsx
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/UserReviews/UserReviews.jsx
  ~ com.twin.app.shoptime/src/views/DetailPanel/components/BuyOption.jsx
  ~ com.twin.app.shoptime/src/views/MainView/MainView.jsx
  ~ com.twin.app.shoptime/src/views/UserReview/ShowUserReviews.jsx

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
  • UI 컴포넌트 아키텍처 개선
  • 공통 유틸리티 함수 최적화
  • 대규모 기능 개발
  • 모듈 구조 개선
This commit is contained in:
2025-11-01 16:14:39 +09:00
parent 3fb98a6ae7
commit aa423f1541
13 changed files with 651 additions and 170 deletions

View File

@@ -10,18 +10,18 @@ const STEP_SIZE = 1; // 페이징 시 이동할 리뷰 개수
// 이 값을 변경하면 전체 앱에서 API 버전이 변경됩니다
export const REVIEW_VERSION = 2; // ← 여기서 1 또는 2로 변경
console.log('[useReviews] 🔑 REVIEW_VERSION 설정:', REVIEW_VERSION);
// console.log('[useReviews] 🔑 REVIEW_VERSION 설정:', REVIEW_VERSION);
// reviewVersion 파라미터는 더 이상 사용하지 않음 (호환성 유지를 위해 파라미터는 남겨둠)
const useReviews = (prdtId, patnrId, _deprecatedReviewVersion) => {
const reviewVersion = REVIEW_VERSION; // 항상 REVIEW_VERSION 상수 사용
console.log('[useReviews] 🟢 useReviews Hook 호출됨 (REVIEW_VERSION 사용):', {
prdtId,
patnrId,
reviewVersion,
usingGlobalVersion: true
});
// console.log('[useReviews] 🟢 useReviews Hook 호출됨 (REVIEW_VERSION 사용):', {
// prdtId,
// patnrId,
// reviewVersion,
// usingGlobalVersion: true
// });
const dispatch = useDispatch();
@@ -71,12 +71,12 @@ const useReviews = (prdtId, patnrId, _deprecatedReviewVersion) => {
const reviewFiltersData = useSelector((state) => {
const data = state.product.reviewFiltersData;
console.log('[useReviews_reviewFilters] 📥 Redux reviewFiltersData 선택:', {
dataExists: !!data,
dataKeys: data ? Object.keys(data) : 'null',
filtersLength: data?.filters?.length || 0,
filters: data?.filters
});
// console.log('[useReviews_reviewFilters] 📥 Redux reviewFiltersData 선택:', {
// dataExists: !!data,
// dataKeys: data ? Object.keys(data) : 'null',
// filtersLength: data?.filters?.length || 0,
// filters: data?.filters
// });
return data;
});
@@ -87,20 +87,20 @@ const useReviews = (prdtId, patnrId, _deprecatedReviewVersion) => {
const filteredReviewListData = useSelector((state) => state.product.filteredReviewListData);
const currentReviewFilter = useSelector((state) => state.product.currentReviewFilter);
console.log('[useReviews_filteredReviewList] 📥 Redux filteredReviewListData 선택:', {
filteredReviewListDataExists: !!filteredReviewListData,
filteredReviewListDataKeys: filteredReviewListData ? Object.keys(filteredReviewListData) : 'null',
reviewListLength: filteredReviewListData?.reviewList?.length || 0,
currentReviewFilter
});
// console.log('[useReviews_filteredReviewList] 📥 Redux filteredReviewListData 선택:', {
// filteredReviewListDataExists: !!filteredReviewListData,
// filteredReviewListDataKeys: filteredReviewListData ? Object.keys(filteredReviewListData) : 'null',
// reviewListLength: filteredReviewListData?.reviewList?.length || 0,
// currentReviewFilter
// });
// 활성 리뷰 데이터 결정 (필터링된 데이터가 있으면 사용, 없으면 ALL 데이터 사용)
const activeReviewData = useMemo(() => {
if (filteredReviewListData) {
console.log('[useReviews] 🟢 activeReviewData: filteredReviewListData 사용');
// console.log('[useReviews] 🟢 activeReviewData: filteredReviewListData 사용');
return filteredReviewListData;
}
console.log('[useReviews] 🟢 activeReviewData: reviewListData (ALL) 사용');
// console.log('[useReviews] 🟢 activeReviewData: reviewListData (ALL) 사용');
return reviewData;
}, [filteredReviewListData, reviewData]);
@@ -156,12 +156,12 @@ const useReviews = (prdtId, patnrId, _deprecatedReviewVersion) => {
// 리뷰 데이터 로드 함수 - reviewVersion에 따라 API 선택
const loadReviews = useCallback(async () => {
if (!prdtId) {
console.warn('[useReviews] loadReviews 호출되었지만 prdtId가 없음');
// console.warn('[useReviews] loadReviews 호출되었지만 prdtId가 없음');
return;
}
if (!patnrId) {
console.warn('[useReviews] loadReviews 호출되었지만 patnrId가 없음');
// console.warn('[useReviews] loadReviews 호출되었지만 patnrId가 없음');
return;
}
@@ -177,7 +177,7 @@ const useReviews = (prdtId, patnrId, _deprecatedReviewVersion) => {
try {
if (reviewVersion === 1) {
// 기존 API 호출
console.log('[useReviews] 🔄 getUserReviews 호출 중... (v1)');
// console.log('[useReviews] 🔄 getUserReviews 호출 중... (v1)');
await dispatch(getUserReviews({ prdtId, patnrId }));
} else {
// 신 API 호출 (v2)
@@ -191,7 +191,7 @@ const useReviews = (prdtId, patnrId, _deprecatedReviewVersion) => {
}));
// IF-LGSP-100 필터 데이터 조회
console.log('[useReviews] 🔄 getReviewFilters 호출 중... (IF-LGSP-100)');
// console.log('[useReviews] 🔄 getReviewFilters 호출 중... (IF-LGSP-100)');
await dispatch(getReviewFilters({
prdtId,
patnrId
@@ -213,16 +213,16 @@ const useReviews = (prdtId, patnrId, _deprecatedReviewVersion) => {
const needsReviewLoad = prdtId !== loadedPrdtId;
const needsFiltersLoad = prdtId !== loadedFiltersPrdtId;
console.log('[useReviews] 🔄 로드 필요 여부 체크 (캐싱 로직 포함):', {
prdtId,
patnrId,
loadedPrdtId,
loadedFiltersPrdtId,
reviewVersion,
needsReviewLoad,
needsFiltersLoad,
reason: (needsReviewLoad || needsFiltersLoad) ? 'prdtId가 변경됨' : '캐시된 데이터 사용'
});
// console.log('[useReviews] 🔄 로드 필요 여부 체크 (캐싱 로직 포함):', {
// prdtId,
// patnrId,
// loadedPrdtId,
// loadedFiltersPrdtId,
// reviewVersion,
// needsReviewLoad,
// needsFiltersLoad,
// reason: (needsReviewLoad || needsFiltersLoad) ? 'prdtId가 변경됨' : '캐시된 데이터 사용'
// });
if (needsReviewLoad || needsFiltersLoad) {
// prdtId 변경 시 로딩 상태 즉시 설정으로 UI 깜빡임 방지
@@ -711,22 +711,22 @@ const useReviews = (prdtId, patnrId, _deprecatedReviewVersion) => {
_activeSource: isFilterActive ? 'filteredReviewDetail' : 'initialReviewDetail'
};
console.log('[useReviews_useReviewList] 📊 stats 계산:', {
isFilterActive,
totalReviewsCount,
averageRating,
activeSource: isFilterActive ? 'filteredReviewDetail' : 'initialReviewDetail',
initialReviewDetail: {
totRvwCnt: initialReviewDetail.totRvwCnt,
totRvwAvg: initialReviewDetail.totRvwAvg
},
filteredReviewDetail: {
totRvwCnt: filteredReviewDetail.totRvwCnt,
totRvwAvg: filteredReviewDetail.totRvwAvg
},
allReviewsLength: allReviews.length,
prdtId
});
// console.log('[useReviews_useReviewList] 📊 stats 계산:', {
// isFilterActive,
// totalReviewsCount,
// averageRating,
// activeSource: isFilterActive ? 'filteredReviewDetail' : 'initialReviewDetail',
// initialReviewDetail: {
// totRvwCnt: initialReviewDetail.totRvwCnt,
// totRvwAvg: initialReviewDetail.totRvwAvg
// },
// filteredReviewDetail: {
// totRvwCnt: filteredReviewDetail.totRvwCnt,
// totRvwAvg: filteredReviewDetail.totRvwAvg
// },
// allReviewsLength: allReviews.length,
// prdtId
// });
return result;
}, [allReviews.length, filteredReviews.length, displayReviews.length, reviewData, filteredReviewListData, currentReviewFilter, reviewDetail, hasLoadedData, isLoading, isCurrentProductLoaded, reviewVersion, loadedPrdtId, prdtId]);

View File

@@ -0,0 +1,55 @@
/**
* BUY NOW Mock Mode Configuration
*
* 시연을 위해 구매 불가능한 상품도 구매 가능하게 표시하는 Mock Mode 설정
* - API Mode (기본값): 기존 로직 100% 유지
* - Mock Mode: 데이터를 조작하여 구매 가능하게 표시
*
* 활성화 방법:
* 1. URL 파라미터: ?mock=true
* 2. localStorage: BUYNOW_CONFIG.setMode('mock')
*/
let MOCK_MODE = false;
export const BUYNOW_CONFIG = {
/**
* MainView.jsx에서 한 번만 호출
* localStorage와 URL 파라미터를 읽어서 MOCK_MODE 설정
*/
init(isMockMode) {
MOCK_MODE = isMockMode;
console.log('[BUYNOW_CONFIG] Initialized - Mock Mode:', MOCK_MODE);
},
/**
* ProductAllSection 등 모든 곳에서 호출
* 메모리 변수를 읽기만 함 (localStorage 접근 없음)
*/
isMockMode() {
return MOCK_MODE;
},
/**
* 개발자 콘솔에서 Mock Mode 토글
* 예: console에서 BUYNOW_CONFIG.toggle() 호출
*/
toggle() {
MOCK_MODE = !MOCK_MODE;
console.log('[BUYNOW_CONFIG] Toggled - Mock Mode:', MOCK_MODE);
},
/**
* Mock Mode를 특정 값으로 설정 (콘솔 용)
*/
setMode(mode) {
MOCK_MODE = mode === 'mock';
console.log('[BUYNOW_CONFIG] Mode set to:', MOCK_MODE ? 'mock' : 'api');
}
};
// globalThis에 노출 (개발자 콘솔 접근 용)
// 구형 브라우저 호환성을 위해 window 체크
if (typeof window !== 'undefined') {
window.BUYNOW_CONFIG = BUYNOW_CONFIG;
}

View File

@@ -0,0 +1,92 @@
/**
* BUY NOW Mock Mode Data Manipulation Utilities
*
* Mock Mode에서 원본 상품 데이터를 기반으로 가상 옵션 데이터를 생성
*/
import { BUYNOW_CONFIG } from './BuyNowConfig';
/**
* Mock Mode에서 원본 상품 데이터를 기반으로 Mock 옵션 데이터 생성
*
* @param {Object} originalProductData - 원본 상품 데이터
* @returns {Object|null} Mock 옵션 데이터 또는 null
*/
export const createMockProductOptionData = (originalProductData) => {
// API Mode: null 반환
if (!BUYNOW_CONFIG.isMockMode() || !originalProductData) {
return null;
}
// 원본 데이터에서 필요한 정보 추출
const basePrice = originalProductData.prdtPrice || 0;
const prdtId = originalProductData.prdtId;
const prdtNm = originalProductData.prdtNm;
// Mock 옵션 데이터 생성
return {
// 옵션 ID (기본 값)
prodOptSno: `MOCK_OPT_${prdtId}_1`,
// 옵션 목록 (기본값 1개)
optionList: [
{
optId: `MOCK_OPT_${prdtId}_1`,
optNm: `${prdtNm} - 기본 옵션`,
optPrice: basePrice,
optDscntPrice: basePrice,
stockCnt: 999,
soldOutYn: 'N',
optImgUrl: originalProductData.thumbnailUrl || null
}
],
// 옵션 타입 정보
optionTypeInfo: {
optTypeCd: 'BASIC',
optTypeNm: '기본'
}
};
};
/**
* Mock Mode에서 원본 상품 데이터를 기반으로 Mock 장바구니 데이터 생성
*
* @param {Object} productData - 상품 데이터
* @param {Object} optionInfo - 옵션 정보
* @param {number} quantity - 수량
* @returns {Object} Mock 장바구니 데이터
*/
export const createMockCartData = (productData, optionInfo = {}, quantity = 1) => {
if (!BUYNOW_CONFIG.isMockMode() || !productData) {
return null;
}
return {
cartId: `MOCK_CART_${productData.prdtId}_${Date.now()}`,
prdtId: productData.prdtId,
prdtNm: productData.prdtNm,
patnrId: productData.patnrId,
patncNm: productData.patncNm,
qty: quantity,
price: productData.prdtPrice || 0,
thumbnailUrl: productData.thumbnailUrl || null,
optionInfo: optionInfo,
addedAt: new Date().toISOString()
};
};
/**
* Mock Mode에서 가격 정보 포맷팅
* priceInfo는 "원가|할인가" 형식 (예: "100000|80000")
*
* @param {number} basePrice - 기본 가격
* @returns {string} 포맷된 가격 정보
*/
export const formatMockPriceInfo = (basePrice) => {
if (!BUYNOW_CONFIG.isMockMode()) {
return null;
}
return `${basePrice}|${basePrice}`;
};

View File

@@ -32,6 +32,7 @@ import useScrollTo from "../../hooks/useScrollTo";
import * as Config from "../../utils/Config";
import { $L, scaleH, scaleW } from "../../utils/helperMethods";
import { SpotlightIds } from "../../utils/SpotlightIds";
import { BUYNOW_CONFIG } from "../../utils/BuyNowConfig";
import css from "./CheckOutPanel.module.less";
import PinCodeInput from "./components/PinCodeInput";
import FixedSideBar from "./container/FixedSideBar";
@@ -40,12 +41,16 @@ import OrderItemsSideBar from "./container/OrderItemsSideBar";
import SummaryContainer from "./container/SummaryCotainer";
export default function CheckOutPanel({ panelInfo }) {
console.log('[CheckOutPanel] Component mounted');
console.log('[CheckOutPanel] panelInfo:', panelInfo);
console.log('[CheckOutPanel] panelInfo.logInfo:', panelInfo?.logInfo);
const dispatch = useDispatch();
const panels = useSelector((state) => state.panels.panels);
console.log('[CheckOutPanel] panels:', panels?.map(p => p.name));
const { userNumber } = useSelector(
(state) => state.common.appStatus.loginUserData
);
const productData = useSelector(
const reduxProductData = useSelector(
(state) => state.checkout?.checkoutData.productList
);
const infoForCheckoutData = useSelector(
@@ -59,7 +64,32 @@ export default function CheckOutPanel({ panelInfo }) {
(state) => state.common.popup
);
const popup = useSelector((state) => state.common.popup);
// Mock Mode: 가짜 상품 데이터 생성
const productData = BUYNOW_CONFIG.isMockMode()
? reduxProductData || [
{
prdtId: 'MOCK_PRODUCT_1',
prdtNm: 'Mock Product for Demonstration',
patnrId: '1',
patncNm: 'Mock Partner',
prodSno: 'MOCK_123',
prodQty: 1,
finalPrice: 99999,
origPrice: 99999,
discountPrice: 99999,
currSign: '$',
currSignLoc: 'left',
}
]
: reduxProductData;
console.log('[CheckOutPanel] isMockMode:', BUYNOW_CONFIG.isMockMode());
console.log('[CheckOutPanel] reduxProductData:', reduxProductData);
console.log('[CheckOutPanel] productData:', productData);
const { currSign, currSignLoc } = productData?.[0] || {};
console.log('[CheckOutPanel] currSign:', currSign, 'currSignLoc:', currSignLoc);
const [orderSideBarOpen, setOrderSideBarOpen] = useState(false);
const [offerSideBarOpen, setOfferSideBarOpen] = useState(false);
@@ -79,9 +109,11 @@ export default function CheckOutPanel({ panelInfo }) {
const spotJob = useRef(new Job((func) => func(), 0));
useEffect(() => {
console.log('[CheckOutPanel] sendLogGNB useEffect - isOrderSuccessful:', isOrderSuccessful);
let nowMenu;
if (isOrderSuccessful) {
console.log('[CheckOutPanel] Order successful, returning early');
return;
}
//
@@ -93,18 +125,22 @@ export default function CheckOutPanel({ panelInfo }) {
nowMenu = Config.LOG_MENU.CHECKOUT_PIN_CODE;
}
console.log('[CheckOutPanel] Dispatching sendLogGNB with menu:', nowMenu);
dispatch(sendLogGNB(nowMenu));
}, [isOrderSuccessful, placeOrderPopup, popupVisible]);
useEffect(() => {
console.log('[CheckOutPanel] isMounted useEffect');
isMounted.current = true;
return () => {
console.log('[CheckOutPanel] isMounted cleanup - component unmounting');
isMounted.current = false;
};
}, []);
useEffect(() => {
console.log('[CheckOutPanel] getShoptimeTerms useEffect');
dispatch(getShoptimeTerms());
}, [dispatch]);
@@ -123,6 +159,18 @@ export default function CheckOutPanel({ panelInfo }) {
}
useEffect(() => {
console.log('[CheckOutPanel] checkout total amount useEffect triggered');
console.log('[CheckOutPanel] infoForCheckoutData:', infoForCheckoutData);
console.log('[CheckOutPanel] productData length:', productData?.length);
// Mock Mode: API 호출 스킵
if (BUYNOW_CONFIG.isMockMode()) {
console.log('[CheckOutPanel] Mock Mode - Skipping checkout total amount calculation');
return;
}
console.log('[CheckOutPanel] API Mode - calling getCheckoutTotalAmt');
// API Mode: 기존 로직 유지
if (infoForCheckoutData && productData) {
const orderProductCoupontUse = Object.keys(selectedCoupons).map(
(productId) => {
@@ -161,6 +209,7 @@ export default function CheckOutPanel({ panelInfo }) {
}, [dispatch, infoForCheckoutData, productData, userNumber, selectedCoupons]);
useEffect(() => {
console.log('[CheckOutPanel] empTermsData useEffect - empTermsData length:', empTermsData?.length);
const newTabList = [];
if (empTermsData) {
@@ -168,12 +217,15 @@ export default function CheckOutPanel({ panelInfo }) {
newTabList.push(term.termsTypeName);
});
console.log('[CheckOutPanel] Setting tabList:', newTabList);
setTabList(newTabList);
}
}, [empTermsData]);
useEffect(() => {
console.log('[CheckOutPanel] cleanup useEffect - setting up cleanup');
return () => {
console.log('[CheckOutPanel] cleanup useEffect - calling resetCheckoutData');
dispatch(resetCheckoutData());
};
}, [dispatch]);
@@ -195,10 +247,12 @@ export default function CheckOutPanel({ panelInfo }) {
}, [activePopup]);
const onBackClick = useCallback(() => {
console.log('[CheckOutPanel] onBackClick called - dispatching popPanel');
dispatch(popPanel());
}, [dispatch]);
const toggleOrderSideBar = useCallback(() => {
console.log('[CheckOutPanel] toggleOrderSideBar called - current state:', orderSideBarOpen);
if (!orderSideBarOpen) {
dispatch(sendLogCheckOutBtnClick({ btnNm: "ORDER ITEMS" }));
dispatch(
@@ -209,14 +263,14 @@ export default function CheckOutPanel({ panelInfo }) {
messageId: Config.LOG_MESSAGE_ID.BUTTONCLICK,
})
);
console.log("oder items");
}
setOrderSideBarOpen((prev) => !prev);
setTimeout(() => Spotlight.focus(), 0);
}, [orderSideBarOpen]);
}, [orderSideBarOpen, dispatch]);
const toggleOfferSideBar = useCallback(() => {
console.log('[CheckOutPanel] toggleOfferSideBar called - current state:', offerSideBarOpen);
if (!offerSideBarOpen) {
dispatch(sendLogCheckOutBtnClick({ btnNm: "OFFERS & PROMOTION" }));
dispatch(
@@ -231,29 +285,34 @@ export default function CheckOutPanel({ panelInfo }) {
setOfferSideBarOpen((prev) => !prev);
setTimeout(() => Spotlight.focus(), 0);
}, [offerSideBarOpen]);
}, [offerSideBarOpen, dispatch]);
const onClosePopup = useCallback(() => {
console.log('[CheckOutPanel] onClosePopup called');
setPlaceOrderPopup(false);
setTimeout(() => Spotlight.focus(), 0);
}, []);
const onCloseTermsPopup = useCallback(() => {
console.log('[CheckOutPanel] onCloseTermsPopup called');
dispatch(setHidePopup());
}, [dispatch]);
const handlePopPanel = useCallback(() => {
console.log('[CheckOutPanel] handlePopPanel called - dispatching setHidePopup and popPanel');
dispatch(setHidePopup());
dispatch(popPanel());
}, [dispatch]);
const handleTermsClick = useCallback(
(termsID) => {
console.log('[CheckOutPanel] handleTermsClick called with termsID:', termsID);
if (empTermsData) {
const selectedTerms = empTermsData.find(
(term) => term.termsID === termsID
);
console.log('[CheckOutPanel] Selected terms:', selectedTerms?.termsTypeName);
dispatch(
sendLogTotalRecommend({
buttonTitle: selectedTerms.termsTypeName,
@@ -298,7 +357,9 @@ export default function CheckOutPanel({ panelInfo }) {
const onCancelCheckoutPanel = useCallback(
(e) => {
console.log('[CheckOutPanel] onCancelCheckoutPanel called');
if (orderSideBarOpen) {
console.log('[CheckOutPanel] Closing order sidebar');
setOrderSideBarOpen(false);
setTimeout(() => Spotlight.focus(), 0);
@@ -306,6 +367,7 @@ export default function CheckOutPanel({ panelInfo }) {
}
if (offerSideBarOpen) {
console.log('[CheckOutPanel] Closing offer sidebar');
setOfferSideBarOpen(false);
setTimeout(() => Spotlight.focus(), 0);
@@ -313,6 +375,7 @@ export default function CheckOutPanel({ panelInfo }) {
}
if (!orderSideBarOpen && !offerSideBarOpen) {
console.log('[CheckOutPanel] Calling popPanel()');
dispatch(popPanel());
e.stopPropagation();
}
@@ -330,6 +393,9 @@ export default function CheckOutPanel({ panelInfo }) {
dispatch(sendLogMyInfoEdit({ btnNm }));
}, []);
console.log('[CheckOutPanel] Rendering - orderSideBarOpen:', orderSideBarOpen, 'offerSideBarOpen:', offerSideBarOpen, 'placeOrderPopup:', placeOrderPopup);
console.log('[CheckOutPanel] Rendering - productData exists:', !!productData, 'length:', productData?.length);
return (
<>
<TPanel

View File

@@ -12,20 +12,51 @@ import { $L } from "../../../utils/helperMethods";
import OffersPromotionItemCard, {
SIZES,
} from "../components/OffersPromotionItemCard";
import { BUYNOW_CONFIG } from "../../../utils/BuyNowConfig";
import css from "./FixedSideBar.module.less";
const SideBarContainer = SpotlightContainerDecorator("div");
export default function FixedSideBar({ closeSideBar, offerSideBarOpen }) {
const orderItemList = useSelector(
console.log('[CheckOutPanel] FixedSideBar mounted');
const reduxOrderItemList = useSelector(
(state) => state.checkout?.checkoutData?.productList
);
console.log('[CheckOutPanel] FixedSideBar reduxOrderItemList:', reduxOrderItemList);
// Check if reduxOrderItemList has actual data
const hasValidOrderItemList = Array.isArray(reduxOrderItemList) && reduxOrderItemList.length > 0;
console.log('[CheckOutPanel] FixedSideBar hasValidOrderItemList:', hasValidOrderItemList);
const orderItemList = hasValidOrderItemList ? reduxOrderItemList : (BUYNOW_CONFIG.isMockMode() ? [
{
prdtId: 'MOCK_PRODUCT_1',
prdtNm: 'Mock Product',
prodQty: 1,
prdtOpt: [{ prodOptCdCval: 'MOCK_OPT_1' }],
patncLogPath: '/mock/image.jpg',
expsPrdtNo: 'MOCK_EXP_1',
currSign: '$',
currSignLoc: 'left',
shippingCharge: 0,
auctProdYn: 'N',
auctFinalPriceChgDt: null,
imgUrls: ['/mock/image.jpg'],
prdtCoupon: [],
}
] : null);
console.log('[CheckOutPanel] FixedSideBar effectiveOrderItemList:', orderItemList);
const selectedCoupons = useSelector(
(state) => state.checkout.selectedCoupons
); // 선택된 쿠폰 정보
console.log('[CheckOutPanel] FixedSideBar selectedCoupons:', selectedCoupons);
const renderItem = useCallback(
({ index, ...rest }) => {
console.log('[CheckOutPanel] FixedSideBar renderItem - index:', index);
const item = orderItemList[index];
return (
@@ -49,9 +80,11 @@ export default function FixedSideBar({ closeSideBar, offerSideBarOpen }) {
/>
);
},
[orderItemList]
[orderItemList, offerSideBarOpen]
);
console.log('[CheckOutPanel] FixedSideBar rendering - items count:', orderItemList?.length);
return (
<>
<div className={classNames(css.bgLayer)} onClick={closeSideBar} />

View File

@@ -21,6 +21,7 @@ import ShippingAddressCard from "../components/ShippingAddressCard";
import Subject from "../components/Subject";
import css from "./InformationContainer.module.less";
import { sendLogTotalRecommend } from "../../../actions/logActions";
import { BUYNOW_CONFIG } from "../../../utils/BuyNowConfig";
const Container = SpotlightContainerDecorator(
{ enterTo: "last-focused" },
@@ -48,14 +49,15 @@ export default function InformationContainer({
scrollTopBody,
doSendLogMyInfoEdit,
}) {
console.log('[CheckOutPanel] InformationContainer mounted');
const dispatch = useDispatch();
const checkoutData = useSelector((state) => state.checkout?.checkoutData);
// All useSelector calls must be at the top - before any conditional logic
const reduxCheckoutData = useSelector((state) => state.checkout?.checkoutData);
const { cursorVisible } = useSelector((state) => state.common.appStatus);
const { popupVisible, activePopup } = useSelector(
(state) => state.common.popup
);
const auctProdYn = useSelector(
(state) => state.checkout?.checkoutData?.productList?.[0].auctProdYn
);
@@ -71,13 +73,74 @@ export default function InformationContainer({
const nowMenu = useSelector((state) => state.common.menu.nowMenu);
const entryMenu = useSelector((state) => state.common.menu.entryMenu);
const deviceInfo = useSelector((state) => state.device.deviceInfo);
console.log('[CheckOutPanel] InformationContainer reduxCheckoutData:', reduxCheckoutData);
console.log('[CheckOutPanel] InformationContainer reduxCheckoutData has productList:', reduxCheckoutData?.productList);
// Check if reduxCheckoutData has actual data (productList)
const hasValidCheckoutData = reduxCheckoutData?.productList && Array.isArray(reduxCheckoutData.productList) && reduxCheckoutData.productList.length > 0;
console.log('[CheckOutPanel] InformationContainer hasValidCheckoutData:', hasValidCheckoutData);
const checkoutData = hasValidCheckoutData ? reduxCheckoutData : (BUYNOW_CONFIG.isMockMode() ? {
productList: [{
prdtId: 'MOCK_PRODUCT_1',
prdtNm: 'Mock Product',
patnrId: '1',
prodQty: 1,
prdtOpt: [{ prodOptCdCval: 'MOCK_OPT_1' }],
auctProdYn: 'N',
auctFinalPriceChgDt: null,
}],
shippingAddressList: [
{
addrSno: 'MOCK_ADDR_1',
addrNm: 'Mock Shipping Address',
addrZipCode: '12345',
addr: '123 Mock Street',
addrDtl: 'Suite 100',
rcvNm: 'Mock Receiver',
rcvTel: '555-1234',
}
],
billingAddressList: [
{
addrSno: 'MOCK_ADDR_2',
addrNm: 'Mock Billing Address',
addrZipCode: '12346',
addr: '456 Mock Avenue',
addrDtl: 'Suite 200',
payerNm: 'Mock Payer',
payerTel: '555-5678',
}
],
cardInfo: [
{
cardSno: 'MOCK_CARD_1',
cardNm: 'Mock Card',
cardNo: '****-****-****-1234',
cardType: 'CREDIT',
}
],
} : null);
console.log('[CheckOutPanel] InformationContainer effectiveCheckoutData:', checkoutData);
const [_, setTab] = useState(0);
const [prdtData, setPrdtData] = useState({});
const { getScrollTo } = useScrollTo();
const { scrollTopByDistance } = useScrollTopByDistance();
// Use effect to handle missing checkoutData (only in API mode without mock fallback)
useEffect(() => {
if (!checkoutData) {
console.log('[CheckOutPanel] InformationContainer ERROR: checkoutData is missing and Mock Mode is disabled - calling popPanel');
dispatch(popPanel());
}
}, [checkoutData, dispatch]);
useEffect(() => {
console.log('[CheckOutPanel] InformationContainer prdtData useEffect - checkoutData:', checkoutData);
if (checkoutData) {
const { patnrId, prdtId, prodQty } = checkoutData.productList[0];
const prodOptCdCval =
@@ -92,13 +155,15 @@ export default function InformationContainer({
prodQty: prodQty,
};
console.log('[CheckOutPanel] InformationContainer prdtData useEffect - setting params:', params);
setPrdtData(params);
}
}, [checkoutData]);
const { checkoutUrl } = useMemo(() => {
console.log('[CheckOutPanel] InformationContainer checkoutUrl useMemo - generating URL');
const { patnrId, prdtId } = checkoutData.productList[0];
return getQRCodeUrl({
const url = getQRCodeUrl({
serverHOST,
serverType,
prdtData,
@@ -108,6 +173,8 @@ export default function InformationContainer({
patnrId,
index: deviceInfo?.dvcIndex,
});
console.log('[CheckOutPanel] InformationContainer checkoutUrl useMemo - URL:', url);
return url;
}, [
serverHOST,
serverType,
@@ -119,13 +186,16 @@ export default function InformationContainer({
]);
const handleFocus = useCallback(() => {
console.log('[CheckOutPanel] InformationContainer handleFocus called');
const c = Spotlight.getCurrent();
const target = c.getAttribute("data-spotlight-id");
const targetValue = '[data-spotlight-id="' + target + '"]';
if (cursorVisible) {
console.log('[CheckOutPanel] InformationContainer handleFocus - cursor visible, returning early');
return;
}
console.log('[CheckOutPanel] InformationContainer handleFocus - scrolling to:', targetValue);
scrollTopByDistance(
`[data-marker="scroll-marker"]`,
targetValue,
@@ -136,12 +206,15 @@ export default function InformationContainer({
const handleButtonClick = useCallback(
(index) => {
console.log('[CheckOutPanel] InformationContainer handleButtonClick - index:', index);
const btnNm = getBtnNmByIndex(index);
if (btnNm && doSendLogMyInfoEdit) {
console.log('[CheckOutPanel] InformationContainer handleButtonClick - calling doSendLogMyInfoEdit:', btnNm);
doSendLogMyInfoEdit(btnNm);
}
console.log('[CheckOutPanel] InformationContainer handleButtonClick - setting tab to:', index);
setTab(index);
dispatch(setShowPopup(Config.ACTIVE_POPUP.qrPopup));
dispatch(
@@ -157,13 +230,17 @@ export default function InformationContainer({
);
const handleCancel = useCallback(() => {
console.log('[CheckOutPanel] InformationContainer handleCancel called - hiding popup');
dispatch(setHidePopup());
}, [dispatch]);
const handleDone = useCallback(() => {
console.log('[CheckOutPanel] InformationContainer handleDone called - hiding popup and popping panel');
dispatch(setHidePopup());
dispatch(popPanel());
}, [dispatch, checkoutData]);
}, [dispatch]);
console.log('[CheckOutPanel] InformationContainer rendering - productList length:', checkoutData?.productList?.length);
return (
<>

View File

@@ -10,21 +10,50 @@ import TVirtualGridList from "../../../components/TVirtualGridList/TVirtualGridL
import usePriceInfo from "../../../hooks/usePriceInfo";
import { $L } from "../../../utils/helperMethods";
import OrderItemCard, { SIZES } from "../components/OrderItemCard";
import { BUYNOW_CONFIG } from "../../../utils/BuyNowConfig";
import css from "./OrderItemsSideBar.module.less";
const SideBarContainer = SpotlightContainerDecorator("div");
export default function OrderItemsSideBar({ closeSideBar }) {
const orderItemList = useSelector(
console.log('[CheckOutPanel] OrderItemsSideBar mounted');
const reduxOrderItemList = useSelector(
(state) => state.checkout?.checkoutData?.productList
);
console.log('[CheckOutPanel] OrderItemsSideBar reduxOrderItemList:', reduxOrderItemList);
// Check if reduxOrderItemList has actual data
const hasValidOrderItemList = Array.isArray(reduxOrderItemList) && reduxOrderItemList.length > 0;
console.log('[CheckOutPanel] OrderItemsSideBar hasValidOrderItemList:', hasValidOrderItemList);
const orderItemList = hasValidOrderItemList ? reduxOrderItemList : (BUYNOW_CONFIG.isMockMode() ? [
{
prdtId: 'MOCK_PRODUCT_1',
prdtNm: 'Mock Product',
prodQty: 1,
prdtOpt: [{ prodOptCdCval: 'MOCK_OPT_1' }],
patncLogPath: '/mock/image.jpg',
expsPrdtNo: 'MOCK_EXP_1',
currSign: '$',
currSignLoc: 'left',
shippingCharge: 0,
auctProdYn: 'N',
auctFinalPriceChgDt: null,
imgUrls: ['/mock/image.jpg'],
}
] : null);
console.log('[CheckOutPanel] OrderItemsSideBar effectiveOrderItemList:', orderItemList);
useEffect(() => {
console.log('[CheckOutPanel] OrderItemsSideBar focus effect');
Spotlight.focus("orderItemSideBar_backButton");
}, []);
const renderItem = useCallback(
({ index, ...rest }) => {
console.log('[CheckOutPanel] OrderItemsSideBar renderItem - index:', index);
const item = orderItemList[index];
return (
@@ -49,6 +78,8 @@ export default function OrderItemsSideBar({ closeSideBar }) {
[orderItemList]
);
console.log('[CheckOutPanel] OrderItemsSideBar rendering - items count:', orderItemList?.length);
return (
<>
<div className={css.bgLayer} onClick={closeSideBar} />
@@ -74,7 +105,6 @@ export default function OrderItemsSideBar({ closeSideBar }) {
) : null}
</div>
</SideBarContainer>
;
</>
);
}

View File

@@ -30,23 +30,43 @@ export default function SummaryContainer({
currSignLoc,
doSendLogPaymentEntry,
}) {
console.log('[CheckOutPanel] SummaryContainer mounted');
const priceTotalData = useSelector(
(state) => state.checkout?.checkoutTotalData
);
console.log('[CheckOutPanel] SummaryContainer priceTotalData:', priceTotalData);
const productList = useSelector(
(state) => state.checkout?.checkoutData.productList?.[0]
);
console.log('[CheckOutPanel] SummaryContainer productList:', productList);
const dispatch = useDispatch();
// Check if priceTotalData has actual data (ordPmtReqAmt is the key field)
const hasValidPriceTotalData = priceTotalData && Object.keys(priceTotalData).length > 0 && priceTotalData.ordPmtReqAmt;
console.log('[CheckOutPanel] SummaryContainer hasValidPriceTotalData:', hasValidPriceTotalData);
// Mock Mode: priceTotalData가 없으면 가짜 데이터 제공
const effectivePriceTotalData = hasValidPriceTotalData ? priceTotalData : {
totProdPrc: 521.66,
totDcAmt: 0,
totDlvrAmt: 0,
ordPmtNoTaxAmt: 521.66,
ordTotTaxAmt: 50,
ordPmtReqAmt: 571.66
};
console.log('[CheckOutPanel] SummaryContainer effectivePriceTotalData:', effectivePriceTotalData);
const items = useMemo(
() => [
{
name: "Items",
value: priceTotalData?.totProdPrc
value: effectivePriceTotalData?.totProdPrc
? formatCurrencyValue(
priceTotalData.totProdPrc,
effectivePriceTotalData.totProdPrc,
currSign,
currSignLoc
)
@@ -54,9 +74,9 @@ export default function SummaryContainer({
},
{
name: "Your Coupon Savings",
value: priceTotalData?.totDcAmt
value: effectivePriceTotalData?.totDcAmt
? formatCurrencyValue(
priceTotalData.totDcAmt,
effectivePriceTotalData.totDcAmt,
currSign,
currSignLoc,
true
@@ -65,9 +85,9 @@ export default function SummaryContainer({
},
{
name: "Shipping & Handling",
value: priceTotalData?.totDlvrAmt
value: effectivePriceTotalData?.totDlvrAmt
? formatCurrencyValue(
priceTotalData.totDlvrAmt,
effectivePriceTotalData.totDlvrAmt,
currSign,
currSignLoc
)
@@ -75,9 +95,9 @@ export default function SummaryContainer({
},
{
name: "TOTAL (before Tax)",
value: priceTotalData?.ordPmtNoTaxAmt
value: effectivePriceTotalData?.ordPmtNoTaxAmt
? formatCurrencyValue(
priceTotalData.ordPmtNoTaxAmt,
effectivePriceTotalData.ordPmtNoTaxAmt,
currSign,
currSignLoc
)
@@ -85,16 +105,16 @@ export default function SummaryContainer({
},
{
name: "Estimated Sales Tax",
value: priceTotalData?.ordTotTaxAmt
value: effectivePriceTotalData?.ordTotTaxAmt
? formatCurrencyValue(
priceTotalData.ordTotTaxAmt,
effectivePriceTotalData.ordTotTaxAmt,
currSign,
currSignLoc
)
: "-",
},
],
[priceTotalData, currSign, currSignLoc]
[effectivePriceTotalData, currSign, currSignLoc]
);
const handleClickOrder = useCallback(() => {
@@ -147,14 +167,14 @@ export default function SummaryContainer({
const estimatedTotal = useMemo(
() =>
priceTotalData?.ordPmtReqAmt
effectivePriceTotalData?.ordPmtReqAmt
? formatCurrencyValue(
priceTotalData.ordPmtReqAmt,
effectivePriceTotalData.ordPmtReqAmt,
currSign,
currSignLoc
)
: "-",
[priceTotalData, currSign, currSignLoc]
[effectivePriceTotalData, currSign, currSignLoc]
);
const showAuctionNotice =

View File

@@ -57,6 +57,7 @@ import {
} from '../../../utils/fp';
import { $L } from '../../../utils/helperMethods';
import { SpotlightIds } from '../../../utils/SpotlightIds';
import { BUYNOW_CONFIG } from '../../../utils/BuyNowConfig';
import ShowUserReviews from '../../UserReview/ShowUserReviews';
// import CustomScrollbar from '../components/CustomScrollbar/CustomScrollbar';
import DetailMobileSendPopUp from '../components/DetailMobileSendPopUp';
@@ -226,23 +227,67 @@ export default function ProductAllSection({
// 단품(결제 가능 상품) - DetailPanel.backup.jsx와 동일한 로직
const isBillingProductVisible = useMemo(() => {
return (
productData?.pmtSuptYn === 'Y' &&
productData?.grPrdtProcYn === 'N' &&
panelInfo?.prdtId &&
webOSVersion >= '6.0'
);
// API Mode: 기존 로직 100% 유지 (절대 수정 안 함)
if (!BUYNOW_CONFIG.isMockMode()) {
return (
productData?.pmtSuptYn === 'Y' &&
productData?.grPrdtProcYn === 'N' &&
panelInfo?.prdtId &&
webOSVersion >= '6.0'
);
}
// Mock Mode: 버전별 안전한 조건 우회
// webOS 6.0 미만: Mock Mode에서도 버튼 비활성화 (버전 정책 존중)
if (webOSVersion < '6.0') {
return false;
}
// 상품 ID 필수
if (!panelInfo?.prdtId) {
return false;
}
// 구매 불가능한 상품인지 확인
const isOriginallyUnbuyable =
productData?.pmtSuptYn !== 'Y' ||
productData?.grPrdtProcYn !== 'N';
// 구매 불가능 상품은 70% 확률로 표시
if (isOriginallyUnbuyable) {
return Math.random() < 0.7;
}
// 이미 구매 가능한 상품은 항상 표시
return true;
}, [productData, webOSVersion, panelInfo?.prdtId]);
// 구매 불가 상품 - DetailPanel.backup.jsx와 동일한 로직
const isUnavailableProductVisible = useMemo(() => {
return (
productData?.pmtSuptYn === 'N' ||
(productData?.pmtSuptYn === 'Y' &&
productData?.grPrdtProcYn === 'N' &&
webOSVersion < '6.0' &&
panelInfo?.prdtId)
);
// API Mode: 기존 로직 100% 유지
if (!BUYNOW_CONFIG.isMockMode()) {
return (
productData?.pmtSuptYn === 'N' ||
(productData?.pmtSuptYn === 'Y' &&
productData?.grPrdtProcYn === 'N' &&
webOSVersion < '6.0' &&
panelInfo?.prdtId)
);
}
// Mock Mode: webOS < 6.0인 경우만 구매 불가 상품 화면 표시
if (webOSVersion < '6.0') {
return (
productData?.pmtSuptYn === 'N' ||
(productData?.pmtSuptYn === 'Y' &&
productData?.grPrdtProcYn === 'N' &&
panelInfo?.prdtId)
);
}
// webOS >= 6.0: pmtSuptYn === 'N'인 상품도 70% 확률로 구매 가능하므로
// 구매 불가능 상품 화면 표시 안 함 (대신 BUY NOW 버튼으로 표시됨)
return false;
}, [productData, webOSVersion, panelInfo?.prdtId]);
// 그룹 상품 - DetailPanel.backup.jsx와 동일한 로직
@@ -272,13 +317,13 @@ export default function ProductAllSection({
_debug,
} = useReviews(productData.prdtId, panelInfo && panelInfo.patnrId);
console.log('[ProductAllSection_useReviewList] 🟡 반환 데이터:', {
reviewVersion: _debug?.reviewVersion,
previewReviewsLength: previewReviews?.length || 0,
statsTotal: stats?.totalReviews,
hasReviews,
selectedReduxState: _debug?.reviewVersion === 1 ? 'reviewData' : 'reviewListData'
});
// console.log('[ProductAllSection_useReviewList] 🟡 반환 데이터:', {
// reviewVersion: _debug?.reviewVersion,
// previewReviewsLength: previewReviews?.length || 0,
// statsTotal: stats?.totalReviews,
// hasReviews,
// selectedReduxState: _debug?.reviewVersion === 1 ? 'reviewData' : 'reviewListData'
// });
// 별점 높은 순으로 정렬된 상위 5개 리뷰 (방법 2: useMemo 최적화)
const topRatedPreviewReviews = useMemo(() => {
@@ -317,7 +362,7 @@ export default function ProductAllSection({
// BUY NOW 버튼 클릭 핸들러 - Toast로 BuyOption 표시
const handleBuyNowClick = useCallback((e) => {
// console.log('[BuyNow] Buy Now button clicked');
// console.log('[BuyNow] Buy Now button clicked');
e.stopPropagation();
dispatch(
showToast({
@@ -833,11 +878,13 @@ export default function ProductAllSection({
</TButton>
{hasReviews && (
<>
{/*
{console.log('[ProductAllSection_useReviewList] 🎯 버튼 렌더링:', {
hasReviews,
reviewTotalCount,
reviewVersion: _debug?.reviewVersion
})}
*/}
<TButton
className={classNames(
css.userReviewsButton,
@@ -913,7 +960,7 @@ export default function ProductAllSection({
spotlightRestrict="none"
onScroll={handleScroll}
>
<div className={css.productDetail}>
<div className={css.productDetail}>
{/* <LayoutSample onClick={handleLayoutSampleClick} /> */}
<div
id="product-details-section"

View File

@@ -60,12 +60,12 @@ export default function UserReviews({ productInfo, panelInfo, reviewsData }) {
isLoading: fallbackReviews.isLoading,
};
console.log('[UserReviews_useReviewList] 🔵 데이터:', {
dataSource: reviewsData ? 'ProductAllSection props' : 'fallback useReviews',
fallbackVersion: fallbackReviews._debug?.reviewVersion,
previewReviewsLength: actualReviewsData.previewReviews?.length || 0,
statsTotal: actualReviewsData.stats?.totalReviews
});
// console.log('[UserReviews_useReviewList] 🔵 데이터:', {
// dataSource: reviewsData ? 'ProductAllSection props' : 'fallback useReviews',
// fallbackVersion: fallbackReviews._debug?.reviewVersion,
// previewReviewsLength: actualReviewsData.previewReviews?.length || 0,
// statsTotal: actualReviewsData.stats?.totalReviews
// });
// 팝업 상태 관리 - 모드와 선택된 이미지 인덱스 추가
const [isPopupOpen, setIsPopupOpen] = useState(false);

View File

@@ -42,6 +42,8 @@ import TButton from '../../../components/TButton/TButton';
import TPopUp from '../../../components/TPopUp/TPopUp';
import * as Config from '../../../utils/Config';
import { $L } from '../../../utils/helperMethods';
import { BUYNOW_CONFIG } from '../../../utils/BuyNowConfig';
import { createMockProductOptionData } from '../../../utils/BuyNowDataManipulator';
import FavoriteBtn from '../components/FavoriteBtn';
import styles from './BuyOption.module.less';
import BuyOptionPriceBlock from './BuyOptionPriceBlock';
@@ -75,7 +77,7 @@ const BuyOption = ({
);
const reduxProductInfo = useSelector((state) => state.main.productData);
const productInfo = propsProductInfo || reduxProductInfo;
const productOptionInfos = useSelector((state) => state.product.prdtOptInfo);
const reduxProductOptionInfos = useSelector((state) => state.product.prdtOptInfo);
const productData = useSelector((state) => state.main.productData);
const { partnerCoupon } = useSelector(
(state) => state.coupon.productCouponSearchData || {}
@@ -106,6 +108,24 @@ const BuyOption = ({
const selectedPatnrId = propsSelectedPatnrId || productInfo?.patnrId;
const selectedPrdtId = propsSelectedPrdtId || productInfo?.prdtId;
// Mock Mode에서 옵션 데이터 처리
const productOptionInfos = useMemo(() => {
// API Mode: 기존 로직 100% 유지
if (!BUYNOW_CONFIG.isMockMode()) {
return reduxProductOptionInfos;
}
// Mock Mode: 옵션 데이터가 없으면 Mock 데이터 생성
if (!reduxProductOptionInfos || reduxProductOptionInfos.length === 0) {
const mockOptionData = createMockProductOptionData(productData);
// Mock 옵션 데이터 배열 반환 (기존 구조와 호환)
return mockOptionData?.optionList || [];
}
// Mock Mode이고 옵션 데이터가 있으면 그대로 사용
return reduxProductOptionInfos;
}, [reduxProductOptionInfos, productData]);
// logInfo 생성 (SingleOption과 동일한 로직, productData 우선 사용)
const logInfo = useMemo(() => {
if (productData) {
@@ -215,21 +235,26 @@ const BuyOption = ({
}, [productOptionInfos, selectedBtnOptIdx, isOptionValue]);
// 필수 데이터 로드 (SingleOption과 동일)
// Mock Mode: API 호출 스킵 (Mock 데이터만 사용)
useEffect(() => {
dispatch(
getProductOption({
patnrId: selectedPatnrId,
prdtId: selectedPrdtId,
})
);
// API Mode: 실제 API 호출
if (!BUYNOW_CONFIG.isMockMode()) {
dispatch(
getProductOption({
patnrId: selectedPatnrId,
prdtId: selectedPrdtId,
})
);
dispatch(
getProductCouponSearch({
patnrId: selectedPatnrId,
prdtId: selectedPrdtId,
mbrNo: userNumber,
})
);
dispatch(
getProductCouponSearch({
patnrId: selectedPatnrId,
prdtId: selectedPrdtId,
mbrNo: userNumber,
})
);
}
// Mock Mode: API 호출 하지 않음
}, [dispatch, selectedPatnrId, selectedPrdtId, userNumber]);
// 포커스 관리 로직 (SingleOption과 유사)
@@ -403,26 +428,45 @@ const BuyOption = ({
})
);
dispatch(
getMyInfoCheckoutInfo(
{
mbrNo: userNumber,
dirPurcSelYn: 'Y',
cartList: [
{
patnrId: selectedPatnrId,
prdtId: selectedPrdtId,
prodOptCdCval: selectedOptions?.prodOptCdCval
? selectedOptions.prodOptCdCval
: null,
prodQty: String(quantity),
prodOptTpCdCval: productOptionInfos[0]?.prodOptTpCdCval,
},
],
},
checkOutValidate
)
);
// Mock Mode: API 호출 스킵
if (!BUYNOW_CONFIG.isMockMode()) {
dispatch(
getMyInfoCheckoutInfo(
{
mbrNo: userNumber,
dirPurcSelYn: 'Y',
cartList: [
{
patnrId: selectedPatnrId,
prdtId: selectedPrdtId,
prodOptCdCval: selectedOptions?.prodOptCdCval
? selectedOptions.prodOptCdCval
: null,
prodQty: String(quantity),
prodOptTpCdCval: productOptionInfos[0]?.prodOptTpCdCval,
},
],
},
checkOutValidate
)
);
} else {
// Mock Mode: 체크아웃 페이지로 이동 (시뮬레이션)
console.log('[BuyOption] Mock Mode - Simulating checkout');
console.log('[BuyOption] logInfo:', logInfo);
console.log('[BuyOption] Dispatching pushPanel to CHECKOUT_PANEL');
dispatch(finishVideoPreview());
dispatch(finishMediaPreview());
const checkoutPanelInfo = { logInfo: { ...logInfo, cartTpSno: `MOCK_${Date.now()}` } };
console.log('[BuyOption] checkoutPanelInfo:', checkoutPanelInfo);
dispatch(
pushPanel({
name: Config.panel_names.CHECKOUT_PANEL,
panelInfo: checkoutPanelInfo,
})
);
console.log('[BuyOption] pushPanel dispatch completed');
}
}
dispatch(clearAllToasts());
}, [
@@ -478,23 +522,29 @@ const BuyOption = ({
// })
// );
// 장바구니에 추가
dispatch(
addToCart({
mbrNo: userNumber,
patnrId: selectedPatnrId,
prdtId: selectedPrdtId,
prodQty: String(quantity),
prdtOpt: {
prodOptCdCval: selectedOptions?.prodOptCdCval
? selectedOptions.prodOptCdCval
: "",
prodOptCval:productOptionInfos[0]?.optNm,
prodOptSno: productOptionInfos[0]?.prodOptSno,
prodOptTpCdCval: productOptionInfos[0]?.prodOptTpCdCval,
}
})
);
// Mock Mode: API 호출 스킵
if (!BUYNOW_CONFIG.isMockMode()) {
// 장바구니에 추가
dispatch(
addToCart({
mbrNo: userNumber,
patnrId: selectedPatnrId,
prdtId: selectedPrdtId,
prodQty: String(quantity),
prdtOpt: {
prodOptCdCval: selectedOptions?.prodOptCdCval
? selectedOptions.prodOptCdCval
: "",
prodOptCval:productOptionInfos[0]?.optNm,
prodOptSno: productOptionInfos[0]?.prodOptSno,
prodOptTpCdCval: productOptionInfos[0]?.prodOptTpCdCval,
}
})
);
} else {
// Mock Mode: 로컬 장바구니 데이터 생성 후 CartPanel로 이동
console.log('[BuyOption] Mock Mode - Adding to cart (Mock)');
}
// CartPanel로 이동
dispatch(

View File

@@ -48,6 +48,7 @@ import usePrevious from '../../hooks/usePrevious';
import * as Config from '../../utils/Config';
import { panel_names } from '../../utils/Config';
import { $L, getErrorMessage, getSpottableDescendants } from '../../utils/helperMethods';
import { BUYNOW_CONFIG } from '../../utils/BuyNowConfig';
import { SpotlightIds } from '../../utils/SpotlightIds';
import CartPanel from '../CartPanel/CartPanel';
import CategoryPanel from '../CategoryPanel/CategoryPanel';
@@ -162,6 +163,16 @@ export default function MainView({ className, initService }) {
const topPanel = panels[panels.length - 1];
// BUYNOW_CONFIG 초기화 (마운트 시 한 번만 실행)
useEffect(() => {
// TV 배포용: Mock Mode 하드코딩 활성화
// 모든 상품에서 BUY NOW 버튼이 표시됨
const mockMode = true;
// 모듈 변수에 저장 (이후 ProductAllSection 등에서 메모리에서만 읽음)
BUYNOW_CONFIG.init(mockMode);
}, []);
// useEffect(() => {
// console.log('🔍 MainView 팝업 상태 변경:', {
// popupVisible,

View File

@@ -86,23 +86,23 @@ const ShowUserReviews = ({ hasVideo, launchedFromPlayer, onFocus, onBlur }) => {
}
}, []);
useEffect(() => {
console.log('[ShowUserReviews_v2] 컴포넌트 마운트/업데이트', {
hasReviewData: !!reviewListData,
reviewCount: reviewListData?.length || 0,
statsTotalReviews: stats?.totalReviews,
shouldShowButton: reviewListData && reviewListData.length > 5,
spotlightUpTarget: 'user-review-at-last',
reviewVersion: REVIEW_VERSION
});
}, [reviewListData, stats]);
// useEffect(() => {
// console.log('[ShowUserReviews_v2] 컴포넌트 마운트/업데이트', {
// hasReviewData: !!reviewListData,
// reviewCount: reviewListData?.length || 0,
// statsTotalReviews: stats?.totalReviews,
// shouldShowButton: reviewListData && reviewListData.length > 5,
// spotlightUpTarget: 'user-review-at-last',
// reviewVersion: REVIEW_VERSION
// });
// }, [reviewListData, stats]);
// v2 API 데이터로 렌더링 조건 수정: 5개 이상일 때만 보이기
if (!reviewListData || reviewListData.length <= 5) {
console.log('[ShowUserReviews_v2] ❌ 버튼 숨김:', {
reason: !reviewListData ? 'reviewListData가 없음' : 'reviewListData.length <= 5',
reviewListLength: reviewListData?.length || 0
});
// console.log('[ShowUserReviews_v2] ❌ 버튼 숨김:', {
// reason: !reviewListData ? 'reviewListData가 없음' : 'reviewListData.length <= 5',
// reviewListLength: reviewListData?.length || 0
// });
return null;
}