1626 lines
58 KiB
JavaScript
1626 lines
58 KiB
JavaScript
import React, {
|
||
useCallback,
|
||
useEffect,
|
||
useMemo,
|
||
useRef,
|
||
useState,
|
||
} from 'react';
|
||
|
||
import {
|
||
useDispatch,
|
||
useSelector,
|
||
} from 'react-redux';
|
||
|
||
import Spotlight from '@enact/spotlight';
|
||
import SpotlightContainerDecorator
|
||
from '@enact/spotlight/SpotlightContainerDecorator';
|
||
|
||
import {
|
||
addToCart,
|
||
insertMyinfoCart,
|
||
} from '../../../actions/cartActions';
|
||
import { getMyInfoCheckoutInfo } from '../../../actions/checkoutActions';
|
||
import {
|
||
changeAppStatus,
|
||
setHidePopup,
|
||
setShowPopup,
|
||
showError,
|
||
} from '../../../actions/commonActions';
|
||
import { getProductCouponSearch } from '../../../actions/couponActions';
|
||
import {
|
||
sendLogPaymentEntry,
|
||
sendLogTotalRecommend,
|
||
} from '../../../actions/logActions';
|
||
import { finishMediaPreview } from '../../../actions/mediaActions';
|
||
import {
|
||
popPanel,
|
||
pushPanel,
|
||
} from '../../../actions/panelActions';
|
||
import {
|
||
clearAllVideoTimers,
|
||
finishVideoPreview,
|
||
} from '../../../actions/playActions';
|
||
import {
|
||
getProductOption,
|
||
getProductOptionId,
|
||
} from '../../../actions/productActions';
|
||
import { clearAllToasts } from '../../../actions/toastActions';
|
||
import TButton from '../../../components/TButton/TButton';
|
||
import TPopUp from '../../../components/TPopUp/TPopUp';
|
||
import { launchMembershipApp } from '../../../lunaSend';
|
||
import { BUYNOW_CONFIG } from '../../../utils/BuyNowConfig';
|
||
import {
|
||
createMockProductOptionData,
|
||
} from '../../../utils/BuyNowDataManipulator';
|
||
import * as Config from '../../../utils/Config';
|
||
import { $L } from '../../../utils/helperMethods';
|
||
import { getSafeProductPrice } from '../../../utils/mockDataSafetyUtils';
|
||
import FavoriteBtn from '../components/FavoriteBtn';
|
||
import styles from './BuyOption.module.less';
|
||
import BuyOptionPriceBlock from './BuyOptionPriceBlock';
|
||
// import CustomDropDown from './CustomDropDown';
|
||
import CustomDropDown from './CustomDropDown.new';
|
||
import ProductQuantity from './ProductQuantity';
|
||
|
||
const Container = SpotlightContainerDecorator(
|
||
{
|
||
spotlightRestrict: 'self-only',
|
||
leaveFor: {}, // arrow key로 빠져나가지 않도록 설정
|
||
},
|
||
'div'
|
||
);
|
||
|
||
const BuyOption = ({
|
||
patncNm,
|
||
productInfo: propsProductInfo,
|
||
isSpotlight,
|
||
selectedPatnrId: propsSelectedPatnrId,
|
||
selectedPrdtId: propsSelectedPrdtId,
|
||
selectedIndex,
|
||
logMenu,
|
||
type,
|
||
}) => {
|
||
// DEBUG_LOG 설정 - 이 값이 true일 때만 console.log가 실행됨
|
||
const DEBUG_LOG = true;
|
||
|
||
// 🚀 컴포넌트 마운트 시 props 확인
|
||
useEffect(() => {
|
||
console.log('[BuyOption] 🚀 COMPONENT MOUNTED - Props received:', {
|
||
propsProductInfo: !!propsProductInfo,
|
||
propsSelectedPatnrId,
|
||
propsSelectedPrdtId,
|
||
patncNm,
|
||
type,
|
||
});
|
||
}, []);
|
||
|
||
// console.log 오버라이드를 위한 ref
|
||
const originalConsoleLog = useRef(console.log);
|
||
|
||
// 컴포넌트 마운트 시 console.log 오버라이드
|
||
useEffect(() => {
|
||
// 원래 함수 저장
|
||
originalConsoleLog.current = console.log;
|
||
|
||
// console.log 오버라이드 - 이 컴포넌트에서만 DEBUG_LOG 조건을 체크하도록 설정
|
||
console.log = (...args) => {
|
||
if (DEBUG_LOG) {
|
||
originalConsoleLog.current.apply(console, args);
|
||
}
|
||
};
|
||
|
||
return () => {
|
||
// console.log 원래 함수로 복원
|
||
console.log = originalConsoleLog.current;
|
||
};
|
||
}, []); // 의존성 배열이 비어있어 컴포넌트 마운트 시 한 번만 실행
|
||
|
||
const dispatch = useDispatch();
|
||
|
||
// Redux 상태 (props가 있으면 props 우선, 없으면 Redux에서)
|
||
const { userId, userNumber } = useSelector(
|
||
(state) => state.common.appStatus.loginUserData
|
||
);
|
||
const reduxProductInfo = useSelector((state) => state.main.productData);
|
||
const productInfo = propsProductInfo || reduxProductInfo;
|
||
const reduxProductOptionInfos = useSelector((state) => state.product.prdtOptInfo);
|
||
const productData = useSelector((state) => state.main.productData);
|
||
|
||
// 🔴 Redux productData 상태 확인
|
||
useEffect(() => {
|
||
console.log('[BuyOption] 🔴 REDUX PRODUCT DATA STATUS:', {
|
||
hasReduxProductInfo: !!reduxProductInfo,
|
||
hasProductData: !!productData,
|
||
reduxProductInfoFull: reduxProductInfo,
|
||
productDataFull: productData,
|
||
});
|
||
}, [reduxProductInfo, productData]);
|
||
const { partnerCoupon } = useSelector(
|
||
(state) => state.coupon.productCouponSearchData || {}
|
||
);
|
||
const { popupVisible, activePopup } = useSelector(
|
||
(state) => state.common.popup
|
||
);
|
||
const nowMenu = useSelector((state) => state.common.menu.nowMenu);
|
||
const webOSVersion = useSelector(
|
||
(state) => state.common.appStatus.webOSVersion
|
||
);
|
||
|
||
// 옵션 선택 상태 관리 (SingleOption과 동일한 구조)
|
||
const [selectedBtnOptIdx, setSelectedBtnOptIdx] = useState(0);
|
||
const [selectedOptionItemIndex, setSelectedOptionItemIndex] = useState(0);
|
||
const [selectedOptions, setSelectedOptions] = useState();
|
||
const [isOptionValue, setIsOptionValue] = useState(false);
|
||
const [isOptionSelect, setIsOptionSelect] = useState(false);
|
||
const [quantity, setQuantity] = useState(1);
|
||
const [favoriteFlag, setFavoriteFlag] = useState(productInfo?.favorYn);
|
||
const [hasProductOptionArray, setHasProductOptionArray] = useState(true);
|
||
const [selectedOptionInfo, setSelectedOptionInfo] = useState();
|
||
|
||
const [selectFirstOptionIndex, setSelectFirstOptionIndex] = useState(0);
|
||
const [selectSecondOptionIndex, setSelectSecondOptionIndex] = useState(0);
|
||
|
||
// 상품 정보 (props 우선, 없으면 productInfo에서)
|
||
const selectedPatnrId = propsSelectedPatnrId || productInfo?.patnrId;
|
||
const selectedPrdtId = propsSelectedPrdtId || productInfo?.prdtId;
|
||
const isMockMode = BUYNOW_CONFIG.isMockMode();
|
||
|
||
// 🔴 selectedPatnrId, selectedPrdtId 변경 감지
|
||
useEffect(() => {
|
||
console.log('[BuyOption] 🔴 SELECTED IDS CHANGED:', {
|
||
selectedPatnrId,
|
||
selectedPrdtId,
|
||
propsSelectedPatnrId,
|
||
propsSelectedPrdtId,
|
||
});
|
||
}, [selectedPatnrId, selectedPrdtId, propsSelectedPatnrId, propsSelectedPrdtId]);
|
||
|
||
// 🔍 Redux 상태와 계산된 값 확인
|
||
useEffect(() => {
|
||
console.log('[BuyOption] 🔍 REDUX STATE & CALCULATED VALUES:', {
|
||
isMockMode,
|
||
propsSelectedPatnrId,
|
||
propsSelectedPrdtId,
|
||
propsProductInfo: !!propsProductInfo,
|
||
reduxProductInfo: !!reduxProductInfo,
|
||
productInfo: !!productInfo,
|
||
reduxProductInfoPatnrId: productInfo?.patnrId,
|
||
reduxProductInfoPrdtId: productInfo?.prdtId,
|
||
finalSelectedPatnrId: selectedPatnrId,
|
||
finalSelectedPrdtId: selectedPrdtId,
|
||
reduxProductOptionInfos: reduxProductOptionInfos,
|
||
fullProductInfo: productInfo,
|
||
});
|
||
}, [selectedPatnrId, selectedPrdtId, isMockMode, productInfo, reduxProductOptionInfos, reduxProductInfo, propsProductInfo]);
|
||
|
||
// Mock Mode에서 옵션 데이터 처리
|
||
const productOptionInfos = useMemo(() => {
|
||
console.log('[BuyOption] productOptionInfos useMemo - isMockMode:', isMockMode);
|
||
console.log('[BuyOption] productOptionInfos useMemo - reduxProductOptionInfos:', reduxProductOptionInfos);
|
||
|
||
// API Mode: 기존 로직 100% 유지
|
||
if (!isMockMode) {
|
||
console.log('[BuyOption] API Mode - using reduxProductOptionInfos');
|
||
return reduxProductOptionInfos;
|
||
}
|
||
|
||
// Mock Mode: 옵션 데이터가 없거나 배열이 아니면 Mock 데이터 생성
|
||
const isValidReduxData = Array.isArray(reduxProductOptionInfos) && reduxProductOptionInfos.length > 0;
|
||
|
||
if (!isValidReduxData) {
|
||
console.log('[BuyOption] Mock Mode - generating mock option data (reduxData invalid)');
|
||
const mockOptionData = createMockProductOptionData(productData);
|
||
console.log('[BuyOption] Mock Mode - createMockProductOptionData result:', mockOptionData);
|
||
// Mock 옵션 데이터는 이미 배열 구조로 반환됨
|
||
return mockOptionData || [];
|
||
}
|
||
|
||
// Mock Mode이고 유효한 옵션 데이터가 있으면 그대로 사용
|
||
console.log('[BuyOption] Mock Mode - using existing valid reduxProductOptionInfos');
|
||
return reduxProductOptionInfos;
|
||
}, [reduxProductOptionInfos, productData, productInfo, isMockMode]); // logInfo 생성 (SingleOption과 동일한 로직, productData 우선 사용)
|
||
const hasMockOptions =
|
||
isMockMode && Array.isArray(productOptionInfos) && productOptionInfos.length > 0;
|
||
const cleanupVideoAndPlayerPanels = useCallback(() => {
|
||
clearAllVideoTimers();
|
||
|
||
dispatch((dispatchFn, getState) => {
|
||
const getPanels = () => getState().panels?.panels || [];
|
||
const popIfTopMatches = (predicate, action) => {
|
||
const panels = getPanels();
|
||
if (!panels.length) {
|
||
return;
|
||
}
|
||
const topPanel = panels[panels.length - 1];
|
||
if (predicate(topPanel)) {
|
||
action();
|
||
}
|
||
};
|
||
|
||
popIfTopMatches(
|
||
(panel) =>
|
||
panel?.name === Config.panel_names.MEDIA_PANEL &&
|
||
panel.panelInfo?.modal,
|
||
() => dispatchFn(finishMediaPreview())
|
||
);
|
||
|
||
popIfTopMatches(
|
||
(panel) =>
|
||
panel &&
|
||
(panel.name === Config.panel_names.PLAYER_PANEL ||
|
||
panel.name === Config.panel_names.PLAYER_PANEL_NEW) &&
|
||
panel.panelInfo?.modal,
|
||
() => dispatchFn(finishVideoPreview())
|
||
);
|
||
});
|
||
}, [dispatch]);
|
||
|
||
const selectDefaultOptionForMock = useCallback(() => {
|
||
if (!isMockMode) {
|
||
return selectedOptions;
|
||
}
|
||
|
||
if (selectedOptions) {
|
||
return selectedOptions;
|
||
}
|
||
|
||
const firstGroup = Array.isArray(productOptionInfos)
|
||
? productOptionInfos[0]
|
||
: null;
|
||
const firstDetail = firstGroup?.prdtOptDtl?.[0];
|
||
|
||
if (firstDetail) {
|
||
const hasSecondDropdown = (firstGroup?.prdtOptDtl?.length || 0) > 1;
|
||
const hasMultipleGroups = (productOptionInfos?.length || 0) > 1;
|
||
|
||
setSelectedOptions((prev) => prev || firstDetail);
|
||
setIsOptionValue(true);
|
||
setIsOptionSelect(true);
|
||
setSelectedBtnOptIdx((prev) => prev || 0);
|
||
setSelectedOptionItemIndex((prev) =>
|
||
hasSecondDropdown ? (prev > 0 ? prev : 1) : 0
|
||
);
|
||
setSelectSecondOptionIndex((prev) =>
|
||
hasSecondDropdown ? (prev > 0 ? prev : 1) : 0
|
||
);
|
||
if (hasMultipleGroups) {
|
||
setSelectFirstOptionIndex((prev) => (prev > 0 ? prev : 1));
|
||
}
|
||
return firstDetail;
|
||
}
|
||
|
||
const fallbackLabel =
|
||
productInfo?.optNm || productInfo?.prdtNm || 'Mock Option';
|
||
const fallbackPriceInfo =
|
||
productInfo?.priceInfo || '999.99|999.99|0|0%|0';
|
||
|
||
const fallbackOption = {
|
||
prodOptCdCval: 'MOCK_DEFAULT_OPT',
|
||
prodOptCval: fallbackLabel,
|
||
optNm: fallbackLabel,
|
||
optPrc: productInfo?.price3 || productInfo?.price2 || '0.00',
|
||
priceInfo: fallbackPriceInfo,
|
||
};
|
||
|
||
setSelectedOptions((prev) => prev || fallbackOption);
|
||
setIsOptionValue(true);
|
||
setIsOptionSelect(true);
|
||
setSelectedBtnOptIdx((prev) => prev || 0);
|
||
setSelectedOptionItemIndex((prev) => (prev > 0 ? prev : 0));
|
||
setSelectSecondOptionIndex((prev) => (prev > 0 ? prev : 0));
|
||
return fallbackOption;
|
||
}, [
|
||
isMockMode,
|
||
selectedOptions,
|
||
productOptionInfos,
|
||
productInfo,
|
||
]);
|
||
const logInfo = useMemo(() => {
|
||
if (productData) {
|
||
// productData가 있으면 SingleOption과 동일하게 처리
|
||
let couponInfo;
|
||
|
||
if (partnerCoupon && partnerCoupon.length > 0) {
|
||
couponInfo = partnerCoupon[0];
|
||
}
|
||
|
||
const { catCd, catNm, patncNm, patnrId, prdtId, prdtNm, priceInfo } =
|
||
productData;
|
||
const { cpnSno, cpnTtl } = couponInfo || {};
|
||
const prodOptSno =
|
||
(productOptionInfos &&
|
||
productOptionInfos.length > 0 &&
|
||
productOptionInfos[0]?.prodOptSno) ||
|
||
'';
|
||
const prodOptTpCdCval =
|
||
(productOptionInfos &&
|
||
productOptionInfos.length > 0 &&
|
||
productOptionInfos[0]?.prodOptTpCdCval) ||
|
||
'';
|
||
|
||
return {
|
||
cpnSno: String(cpnSno) || '',
|
||
cpnTtl: cpnTtl || '',
|
||
dcAftrPrc: priceInfo.split('|')[1],
|
||
dcBefPrc: priceInfo.split('|')[0],
|
||
lgCatCd: catCd || '',
|
||
lgCatNm: catNm || '',
|
||
patncNm,
|
||
patnrId,
|
||
prodId: prdtId,
|
||
prodNm: prdtNm,
|
||
prodOptSno: prodOptSno,
|
||
prodOptTpCdCval: prodOptTpCdCval,
|
||
qty: String(quantity),
|
||
};
|
||
} else if (productInfo) {
|
||
// productData가 없으면 productInfo 사용
|
||
let couponInfo;
|
||
|
||
if (partnerCoupon && partnerCoupon.length > 0) {
|
||
couponInfo = partnerCoupon[0];
|
||
}
|
||
|
||
const { catCd, catNm, patncNm, patnrId, prdtId, prdtNm, priceInfo } =
|
||
productInfo;
|
||
const { cpnSno, cpnTtl } = couponInfo || {};
|
||
const prodOptSno =
|
||
(productOptionInfos &&
|
||
productOptionInfos.length > 0 &&
|
||
productOptionInfos[0]?.prodOptSno) ||
|
||
'';
|
||
const prodOptTpCdCval =
|
||
(productOptionInfos &&
|
||
productOptionInfos.length > 0 &&
|
||
productOptionInfos[0]?.prodOptTpCdCval) ||
|
||
'';
|
||
|
||
return {
|
||
cpnSno: String(cpnSno) || '',
|
||
cpnTtl: cpnTtl || '',
|
||
dcAftrPrc: priceInfo.split('|')[1],
|
||
dcBefPrc: priceInfo.split('|')[0],
|
||
lgCatCd: catCd || '',
|
||
lgCatNm: catNm || '',
|
||
patncNm,
|
||
patnrId,
|
||
prodId: prdtId,
|
||
prodNm: prdtNm,
|
||
prodOptSno: prodOptSno,
|
||
prodOptTpCdCval: prodOptTpCdCval,
|
||
qty: String(quantity),
|
||
};
|
||
}
|
||
|
||
return {};
|
||
}, [partnerCoupon, productData, productInfo, productOptionInfos, quantity]);
|
||
|
||
// 옵션 리셋 로직 (SingleOption과 동일)
|
||
useEffect(() => {
|
||
if (type !== 'theme') {
|
||
return;
|
||
}
|
||
|
||
setSelectedOptions();
|
||
setIsOptionValue(false);
|
||
setSelectedOptionItemIndex(0);
|
||
setIsOptionSelect(false);
|
||
setQuantity(1);
|
||
setSelectedBtnOptIdx(0);
|
||
}, [selectedIndex, productOptionInfos, type]);
|
||
|
||
// 옵션 자동 선택 로직 (SingleOption과 동일)
|
||
// Mock Mode: 항상 첫 번째 옵션을 자동으로 선택
|
||
useEffect(() => {
|
||
console.log('[BuyOption] autoSelect useEffect - productOptionInfos:', productOptionInfos);
|
||
console.log('[BuyOption] autoSelect useEffect - selectedBtnOptIdx:', selectedBtnOptIdx);
|
||
console.log('[BuyOption] autoSelect useEffect - isOptionValue:', isOptionValue);
|
||
|
||
if (!productOptionInfos || productOptionInfos.length === 0) {
|
||
console.log('[BuyOption] autoSelect - productOptionInfos is empty, returning');
|
||
return;
|
||
}
|
||
|
||
const currentOptionGroup = productOptionInfos[selectedBtnOptIdx];
|
||
if (!currentOptionGroup) {
|
||
console.log('[BuyOption] autoSelect - currentOptionGroup is not found at index:', selectedBtnOptIdx);
|
||
return;
|
||
}
|
||
|
||
const optionDetails = currentOptionGroup.prdtOptDtl;
|
||
console.log('[BuyOption] autoSelect - optionDetails:', optionDetails);
|
||
|
||
if (!optionDetails || optionDetails.length === 0) {
|
||
console.log('[BuyOption] autoSelect - optionDetails is empty');
|
||
return;
|
||
}
|
||
|
||
// 이미 선택되었으면 스킵
|
||
if (isOptionValue) {
|
||
console.log('[BuyOption] autoSelect - already selected, skipping');
|
||
return;
|
||
}
|
||
|
||
// Mock Mode: 첫 번째 옵션 자동 선택
|
||
if (BUYNOW_CONFIG.isMockMode()) {
|
||
console.log('[BuyOption] Mock Mode - Auto selecting first option:', optionDetails[0]);
|
||
setSelectedOptions(optionDetails[0]);
|
||
setIsOptionValue(true);
|
||
}
|
||
// API Mode: 옵션이 1개일 때만 자동 선택
|
||
else if (optionDetails.length === 1) {
|
||
console.log('[BuyOption] API Mode - Auto selecting only option:', optionDetails[0]);
|
||
setSelectedOptions(optionDetails[0]);
|
||
setIsOptionValue(true);
|
||
}
|
||
}, [productOptionInfos, selectedBtnOptIdx]);
|
||
|
||
// 필수 데이터 로드 (SingleOption과 동일)
|
||
// Mock Mode: API 호출 스킵 (Mock 데이터만 사용)
|
||
// 🚀 처음 마운트될 때와 선택된 ID가 변경될 때마다 호출
|
||
useEffect(() => {
|
||
// 디버그 로그: useEffect 진입
|
||
console.log('%c[BuyOption] 📍 DATA LOADING useEffect - Dependencies changed', 'background: red; color: white; font-weight: bold; padding: 5px;', {
|
||
isMockMode,
|
||
selectedPatnrId,
|
||
selectedPrdtId,
|
||
userNumber,
|
||
hasSelectedPatnrId: !!selectedPatnrId,
|
||
hasSelectedPrdtId: !!selectedPrdtId,
|
||
});
|
||
|
||
// API Mode: 실제 API 호출
|
||
if (!isMockMode) {
|
||
// ⚠️ 필수 값 검증
|
||
if (!selectedPatnrId || !selectedPrdtId) {
|
||
console.warn('[BuyOption] ⚠️ [getProductOption] MISSING REQUIRED PARAMS', {
|
||
selectedPatnrId,
|
||
selectedPrdtId,
|
||
reason: 'selectedPatnrId or selectedPrdtId is missing',
|
||
});
|
||
return; // API 호출하지 않음
|
||
}
|
||
|
||
console.log('[BuyOption] 🔄 [getProductOption] API CALL - Dispatching getProductOption', {
|
||
patnrId: selectedPatnrId,
|
||
prdtId: selectedPrdtId,
|
||
});
|
||
|
||
dispatch(
|
||
getProductOption({
|
||
patnrId: selectedPatnrId,
|
||
prdtId: selectedPrdtId,
|
||
})
|
||
);
|
||
|
||
console.log('[BuyOption] 🔄 [getProductCouponSearch] API CALL - Dispatching getProductCouponSearch', {
|
||
patnrId: selectedPatnrId,
|
||
prdtId: selectedPrdtId,
|
||
mbrNo: userNumber,
|
||
});
|
||
|
||
dispatch(
|
||
getProductCouponSearch({
|
||
patnrId: selectedPatnrId,
|
||
prdtId: selectedPrdtId,
|
||
mbrNo: userNumber,
|
||
})
|
||
);
|
||
} else {
|
||
// Mock Mode 상태 로그
|
||
console.log('[BuyOption] 🚫 Mock Mode Active - Skipping API calls');
|
||
console.log('[BuyOption] 🚫 [getProductOption] SKIPPED - Mock Mode enabled');
|
||
console.log('[BuyOption] 🚫 [getProductCouponSearch] SKIPPED - Mock Mode enabled');
|
||
}
|
||
// Mock Mode: API 호출 하지 않음
|
||
}, [dispatch, selectedPatnrId, selectedPrdtId, userNumber, isMockMode]);
|
||
|
||
// 포커스 관리 로직 (SingleOption과 유사)
|
||
useEffect(() => {
|
||
const shouldHandleOptionFocus =
|
||
productInfo?.optProdYn === 'Y' || hasMockOptions;
|
||
|
||
if (!shouldHandleOptionFocus) {
|
||
Spotlight.focus('buy-option-buy-now-button');
|
||
return;
|
||
}
|
||
|
||
if (productOptionInfos && productOptionInfos.length > 1) {
|
||
Spotlight.focus('buy-option-first-dropdown');
|
||
} else if (
|
||
productOptionInfos &&
|
||
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl &&
|
||
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl.length > 1
|
||
) {
|
||
Spotlight.focus('buy-option-second-dropdown');
|
||
} else {
|
||
Spotlight.focus('buy-option-buy-now-button');
|
||
}
|
||
}, [
|
||
productOptionInfos,
|
||
productInfo?.optProdYn,
|
||
hasMockOptions,
|
||
selectedBtnOptIdx,
|
||
]);
|
||
|
||
// checkOutValidate 콜백 함수 (SingleOption과 동일한 로직)
|
||
function checkOutValidate(response) {
|
||
console.log('%c[BuyOption] 🔴 checkOutValidate CALLED', 'background: red; color: white; font-weight: bold; padding: 5px;', {
|
||
hasResponse: !!response,
|
||
retCode: response?.retCode,
|
||
retMsg: response?.retMsg,
|
||
hasCardInfo: !!response?.data?.cardInfo,
|
||
hasBillingAddress: response?.data?.billingAddressList?.length > 0,
|
||
hasShippingAddress: response?.data?.shippingAddressList?.length > 0,
|
||
hasProductList: response?.data?.productList?.length > 0,
|
||
});
|
||
|
||
if (response) {
|
||
if (response.retCode === 0) {
|
||
// 🔍 조건 체크
|
||
const isCardInfoNull = response.data.cardInfo === null;
|
||
const isBillingAddressEmpty = response.data.billingAddressList.length === 0;
|
||
const isShippingAddressEmpty = response.data.shippingAddressList.length === 0;
|
||
|
||
console.log('%c[BuyOption] 🔍 Address & Card Validation:', 'background: blue; color: white; font-weight: bold; padding: 5px;', {
|
||
isCardInfoNull,
|
||
isBillingAddressEmpty,
|
||
isShippingAddressEmpty,
|
||
needsQRPopup: isCardInfoNull || isBillingAddressEmpty || isShippingAddressEmpty,
|
||
});
|
||
|
||
if (
|
||
isCardInfoNull ||
|
||
isBillingAddressEmpty ||
|
||
isShippingAddressEmpty
|
||
) {
|
||
console.log('%c[BuyOption] 🟡 Missing card/address - Showing QR Popup', 'background: orange; color: white; font-weight: bold; padding: 5px;');
|
||
dispatch(setShowPopup(Config.ACTIVE_POPUP.qrPopup));
|
||
dispatch(changeAppStatus({ isLoading: false }));
|
||
return;
|
||
} else {
|
||
console.log('%c[BuyOption] ✅ All address & card data present - Proceeding to CheckOutPanel', 'background: green; color: white; font-weight: bold; padding: 5px;');
|
||
const { mbrId, prdtId, prodSno } = response.data.productList[0];
|
||
const cartTpSno = `${mbrId}_${prdtId}_${prodSno}`;
|
||
|
||
// CheckOutPanel 이동 전에 ProductVideoV2 타이머 및 MediaPanel/PlayerPanel 정리
|
||
console.log('[BuyOption] API Mode - Cleaning up media panels before checkout');
|
||
cleanupVideoAndPlayerPanels();
|
||
|
||
// 🔴 CRITICAL: 기존 CheckOutPanel 있으면 제거 후 새로 push (API Mode)
|
||
dispatch((dispatchFn, getState) => {
|
||
const panels = getState().panels?.panels || [];
|
||
const checkoutPanelExists = panels.some(p => p.name === Config.panel_names.CHECKOUT_PANEL);
|
||
|
||
console.log('[BuyOption] 📊 API Mode - Current panels:', panels.map(p => p.name));
|
||
|
||
// 1️⃣ DetailPanel 제거 (STANDALONE_PANEL이므로 다른 패널 제거 필수)
|
||
console.log('[BuyOption] 🗑️ API Mode - Removing DetailPanel');
|
||
dispatchFn(popPanel(Config.panel_names.DETAIL_PANEL));
|
||
|
||
// 2️⃣ 기존 CheckOutPanel 제거 (있으면)
|
||
if (checkoutPanelExists) {
|
||
console.log('[BuyOption] 🗑️ API Mode - Removing existing CheckOutPanel');
|
||
dispatchFn(popPanel(Config.panel_names.CHECKOUT_PANEL));
|
||
}
|
||
|
||
// 3️⃣ 새로운 CheckOutPanel push
|
||
console.log('[BuyOption] ➕ API Mode - Pushing new CheckOutPanel');
|
||
dispatchFn(pushPanel({
|
||
name: Config.panel_names.CHECKOUT_PANEL,
|
||
panelInfo: { logInfo: { ...logInfo, cartTpSno } },
|
||
}));
|
||
|
||
// 🚀 CheckOutPanel 이동 후 BuyOption Toast 닫기
|
||
console.log('[BuyOption] 🔥 Closing BuyOption Toast after CheckOutPanel push');
|
||
dispatchFn(clearAllToasts());
|
||
});
|
||
|
||
dispatch(sendLogPaymentEntry({ ...logInfo, cartTpSno }));
|
||
}
|
||
} else if (response.retCode === 1001) {
|
||
dispatch(setShowPopup(Config.ACTIVE_POPUP.qrPopup));
|
||
dispatch(changeAppStatus({ isLoading: false }));
|
||
} else {
|
||
dispatch(
|
||
showError(
|
||
response.retCode,
|
||
response.retMsg,
|
||
false,
|
||
response.retDetailCode,
|
||
response.returnBindStrings
|
||
)
|
||
);
|
||
dispatch(changeAppStatus({ isLoading: false }));
|
||
return;
|
||
}
|
||
}
|
||
|
||
Spotlight.focus('buy-option-buy-now-button');
|
||
}
|
||
|
||
const handleBuyNowClick = useCallback((e) => {
|
||
e.stopPropagation()
|
||
console.log('%c🔥🔥🔥 BUY NOW CLICKED! FUNCTION CALLED! 🔥🔥🔥', 'background: red; color: white; font-size: 16px; font-weight: bold; padding: 5px;');
|
||
console.log('%cproductInfo exists:', 'background: red; color: white; padding: 3px;', !!productInfo);
|
||
console.log('%cuserNumber exists:', 'background: red; color: white; padding: 3px;', !!userNumber);
|
||
console.log('%cselectedOptions exists:', 'background: red; color: white; padding: 3px;', !!selectedOptions);
|
||
console.log('%cselectedPatnrId:', 'background: red; color: white; padding: 3px;', selectedPatnrId);
|
||
console.log('%cselectedPrdtId:', 'background: red; color: white; padding: 3px;', selectedPrdtId);
|
||
|
||
// const optionName = renderOptionName();
|
||
// const optionValueName = renderOptionValue();
|
||
|
||
if (productInfo && productInfo?.soldoutFlag === 'Y') {
|
||
console.log('%c[BuyOption] ❌ Product is sold out', 'background: red; color: white; padding: 3px;');
|
||
return;
|
||
}
|
||
if (!userNumber || userNumber === '') {
|
||
if (!isMockMode) {
|
||
console.log('%c[BuyOption] ❌ User not logged in - showing login popup', 'background: red; color: white; padding: 3px;');
|
||
dispatch(setShowPopup(Config.ACTIVE_POPUP.loginPopup));
|
||
return;
|
||
}
|
||
}
|
||
|
||
// // 옵션 선택 검증 (SingleOption과 동일)
|
||
// if (
|
||
// productOptionInfos &&
|
||
// productOptionInfos.length > 0 &&
|
||
// (optionName === "SELECT" || optionValueName === "SELECT")
|
||
// ) {
|
||
// console.log("###Test pjh 2", optionName);
|
||
// return dispatch(setShowPopup(Config.ACTIVE_POPUP.loginPopup));
|
||
// }
|
||
|
||
// Mock Mode가 아닐 때만 옵션 선택 검증
|
||
if (!isMockMode) {
|
||
//옵션 선택 안하면 구매 안돼도록.
|
||
if (
|
||
productOptionInfos &&
|
||
productOptionInfos.length > 1 &&
|
||
productInfo?.optProdYn === 'Y'
|
||
) {
|
||
|
||
if (selectFirstOptionIndex === 0) {
|
||
dispatch(
|
||
showError(
|
||
null,
|
||
"PLEASE SELECT OPTION",
|
||
false,
|
||
null,
|
||
null
|
||
)
|
||
);
|
||
|
||
return;
|
||
}
|
||
}
|
||
if (
|
||
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl &&
|
||
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl.length > 1 &&
|
||
productInfo?.optProdYn === 'Y'
|
||
) {
|
||
|
||
if (selectSecondOptionIndex === 0) {
|
||
dispatch(
|
||
showError(
|
||
null,
|
||
"PLEASE SELECT OPTION",
|
||
false,
|
||
null,
|
||
null
|
||
)
|
||
);
|
||
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 필수 데이터 검증 (API Mode만)
|
||
if (!userNumber || !selectedPatnrId || !selectedPrdtId || !quantity) {
|
||
console.error('%c[BuyOption] ❌ Missing required data:', 'background: red; color: white; padding: 3px;', {
|
||
userNumber: !!userNumber,
|
||
selectedPatnrId: !!selectedPatnrId,
|
||
selectedPrdtId: !!selectedPrdtId,
|
||
quantity: !!quantity
|
||
});
|
||
dispatch(showError(null, 'Missing required information', false, null, null));
|
||
return;
|
||
}
|
||
}
|
||
|
||
const effectivePatnrId =
|
||
selectedPatnrId || productInfo?.patnrId || 'MOCK_PARTNER';
|
||
const effectivePrdtId =
|
||
selectedPrdtId || productInfo?.prdtId || 'MOCK_PRODUCT';
|
||
const optionForUse = isMockMode
|
||
? selectDefaultOptionForMock()
|
||
: selectedOptions;
|
||
const effectiveQuantity = quantity > 0 ? quantity : 1;
|
||
|
||
// Mock Mode: 조건 완화 (userNumber 체크 제외)
|
||
const shouldProceed = isMockMode
|
||
? (effectivePatnrId && effectivePrdtId && effectiveQuantity)
|
||
: (userNumber && selectedPatnrId && selectedPrdtId && quantity);
|
||
|
||
console.log('%c[BuyOption] shouldProceed check:', 'background: purple; color: white; padding: 3px;', {
|
||
shouldProceed,
|
||
isMockMode,
|
||
userNumber: !!userNumber,
|
||
selectedPatnrId: !!effectivePatnrId,
|
||
selectedPrdtId: !!effectivePrdtId,
|
||
quantity
|
||
});
|
||
|
||
if (shouldProceed) {
|
||
const { prodOptCval, priceInfo } = optionForUse || {};
|
||
const { patncNm, brndNm, catNm, prdtNm, prdtId } = productInfo;
|
||
|
||
console.log('[BuyOption] handleClickOrder - productInfo:', productInfo);
|
||
console.log('[BuyOption] handleClickOrder - selectedOptions:', optionForUse);
|
||
console.log('[BuyOption] handleClickOrder - priceInfo:', priceInfo);
|
||
console.log('[BuyOption] handleClickOrder - logInfo:', logInfo);
|
||
|
||
// productInfo에서 직접 price2, price5 추출 (가장 신뢰할 수 있는 소스)
|
||
// price2 = 상품 가격 ("$ 521.66" 형식)
|
||
// price5 = 쿠폰 할인 ("$ 104.33" 형식)
|
||
const extractNumericPrice = (priceStr) => {
|
||
if (typeof priceStr === 'number') return priceStr;
|
||
if (typeof priceStr === 'string') {
|
||
const cleanedValue = priceStr.replace(/,/g, '');
|
||
const numMatch = cleanedValue.match(/[\d.]+/);
|
||
return numMatch ? parseFloat(numMatch[0]) : 0;
|
||
}
|
||
return 0;
|
||
};
|
||
|
||
const price3Value = extractNumericPrice(productInfo?.price3 ? productInfo?.price3 : productInfo?.price2);
|
||
const price5Value = extractNumericPrice(productInfo?.price5);
|
||
|
||
console.log('[BuyOption] handleClickOrder - productInfo.price2:', productInfo?.price3, '-> extracted:', price3Value);
|
||
console.log('[BuyOption] handleClickOrder - productInfo.price5:', productInfo?.price5, '-> extracted:', price5Value);
|
||
|
||
// 가격 계산:
|
||
// discountPrice = price2 (상품 가격)
|
||
// regularPrice = price2 + price5 (상품 가격 + 할인액 = 원래 가격)
|
||
let discountPrice = price3Value > 0 ? price3Value : 0;
|
||
let regularPrice = price3Value + price5Value; // price2 + price5 = 원래 가격
|
||
|
||
const discountRate = priceInfo?.split('|')[4];
|
||
|
||
console.log('[BuyOption] handleClickOrder - Calculated prices - regularPrice:', regularPrice, 'discountPrice:', discountPrice);
|
||
|
||
// selectedOptions가 없고 logInfo가 있으면, logInfo의 가격도 고려
|
||
if (!optionForUse && logInfo) {
|
||
console.log('[BuyOption] handleClickOrder - selectedOptions is undefined, checking logInfo prices');
|
||
// logInfo의 dcBefPrc와 dcAftrPrc는 "$ 521.66" 형식이므로 숫자만 추출 (소수점 포함)
|
||
const dcBefPrcMatch = logInfo.dcBefPrc?.match(/[\d.]+/);
|
||
const dcAftrPrcMatch = logInfo.dcAftrPrc?.match(/[\d.]+/);
|
||
const logRegularPrice = dcBefPrcMatch ? parseFloat(dcBefPrcMatch[0]) : 0;
|
||
const logDiscountPrice = dcAftrPrcMatch ? parseFloat(dcAftrPrcMatch[0]) : logRegularPrice;
|
||
|
||
// logInfo의 가격이 있으면 우선
|
||
if (logRegularPrice > 0 || logDiscountPrice > 0) {
|
||
regularPrice = logRegularPrice > 0 ? logRegularPrice : regularPrice;
|
||
discountPrice = logDiscountPrice > 0 ? logDiscountPrice : discountPrice;
|
||
console.log('[BuyOption] handleClickOrder - Using logInfo prices - regularPrice:', regularPrice, 'discountPrice:', discountPrice);
|
||
}
|
||
}
|
||
|
||
console.log('[BuyOption] handleClickOrder - Final prices - regularPrice:', regularPrice, 'discountPrice:', discountPrice, 'discountRate:', discountRate);
|
||
|
||
dispatch(
|
||
sendLogTotalRecommend({
|
||
nowMenu: nowMenu,
|
||
productId: prdtId,
|
||
productTitle: prdtNm,
|
||
partner: patncNm,
|
||
price: discountRate ? discountPrice : regularPrice,
|
||
discount: discountRate,
|
||
brand: brndNm,
|
||
productOption: prodOptCval || optionForUse?.optNm || optionForUse?.prodOptCval || '',
|
||
category: catNm,
|
||
contextName: Config.LOG_CONTEXT_NAME.DETAILPAGE,
|
||
messageId: Config.LOG_MESSAGE_ID.BUY_NOW,
|
||
})
|
||
);
|
||
|
||
// Mock Mode: API 호출 스킵
|
||
console.log('%c[BuyOption] 🔍 CHECK BUYNOW_CONFIG.isMockMode() at BUY NOW time:', 'background: orange; color: white; font-weight: bold; padding: 5px;', {
|
||
isMockMode: BUYNOW_CONFIG.isMockMode(),
|
||
localIsMockMode: isMockMode,
|
||
});
|
||
|
||
if (!BUYNOW_CONFIG.isMockMode()) {
|
||
console.log('%c[BuyOption] 🚀 API MODE DETECTED - Calling getMyInfoCheckoutInfo', 'background: green; color: white; font-weight: bold; padding: 5px;');
|
||
console.log('[BuyOption] API 파라미터:', {
|
||
mbrNo: userNumber,
|
||
dirPurcSelYn: 'Y',
|
||
patnrId: selectedPatnrId,
|
||
prdtId: selectedPrdtId,
|
||
prodQty: String(quantity),
|
||
});
|
||
dispatch(
|
||
getMyInfoCheckoutInfo(
|
||
{
|
||
mbrNo: userNumber,
|
||
dirPurcSelYn: 'Y',
|
||
cartList: [
|
||
{
|
||
patnrId: selectedPatnrId,
|
||
prdtId: selectedPrdtId,
|
||
prodOptCdCval: optionForUse?.prodOptCdCval
|
||
? optionForUse.prodOptCdCval
|
||
: null,
|
||
prodQty: String(quantity),
|
||
prodOptTpCdCval: productOptionInfos[0]?.prodOptTpCdCval,
|
||
},
|
||
],
|
||
},
|
||
checkOutValidate
|
||
)
|
||
);
|
||
} else {
|
||
// Mock Mode: 체크아웃 페이지로 이동 (시뮬레이션)
|
||
// panelInfo의 logInfo에 선택한 상품 정보를 포함시켜 CheckOutPanel에서 사용
|
||
console.log('%c[BuyOption] 🟠 MOCK MODE DETECTED - Simulating checkout WITHOUT API', 'background: orange; color: white; font-weight: bold; padding: 5px;');
|
||
console.log('[BuyOption] logInfo:', logInfo);
|
||
console.log('[BuyOption] Dispatching pushPanel to CHECKOUT_PANEL');
|
||
|
||
// CheckOutPanel 이동 전에 ProductVideoV2 타이머 및 MediaPanel/PlayerPanel 정리
|
||
console.log('[BuyOption] Mock Mode - Cleaning up media panels before checkout');
|
||
cleanupVideoAndPlayerPanels();
|
||
|
||
// Mock 모드: 선택 상품의 정보를 panelInfo에 담아서 전달
|
||
// CheckOutPanel에서 이 정보로 Mock 상품 데이터 생성
|
||
// 이미지 URL 추출 (productInfo의 imgList 또는 thumbnailUrl 사용)
|
||
const imgUrl = productInfo?.imgList?.[0]?.imgUrl ||
|
||
productInfo?.thumbnailUrl ||
|
||
productInfo?.patncLogPath ||
|
||
'/mock/image.jpg';
|
||
|
||
const mockProductInfo = {
|
||
prdtId: effectivePrdtId,
|
||
prdtNm: productInfo?.prdtNm,
|
||
patnrId: effectivePatnrId,
|
||
patncNm: patncNm,
|
||
prodQty: effectiveQuantity,
|
||
origPrice: regularPrice || 99999, // 원래 가격 (숫자)
|
||
discountPrice: discountPrice || regularPrice || 99999, // 할인된 가격 (실제 판매 가격, 숫자)
|
||
finalPrice: discountPrice || regularPrice || 99999, // 최종 가격 (숫자)
|
||
currSign: '$',
|
||
currSignLoc: 'left',
|
||
imgUrl: imgUrl, // 상품 이미지 URL 추가
|
||
};
|
||
|
||
// Mock Mode: productInfo 최소 검증만 수행
|
||
if (!productInfo || !productInfo.prdtId) {
|
||
console.error('%c[BuyOption] ❌ Mock Mode - productInfo is invalid:', 'background: red; color: white; font-weight: bold; padding: 5px;', productInfo);
|
||
// Mock Mode에서는 기본값으로 진행 시도
|
||
console.log('%c[BuyOption] Mock Mode - Attempting to proceed with fallback data', 'background: orange; color: white; padding: 3px;');
|
||
} else {
|
||
console.log('%c[BuyOption] ✅ Mock Mode - productInfo is valid:', 'background: green; color: white; font-weight: bold; padding: 5px;', productInfo.prdtId);
|
||
}
|
||
|
||
const checkoutPanelInfo = {
|
||
logInfo: { ...logInfo, cartTpSno: `MOCK_${Date.now()}` },
|
||
productInfo: {
|
||
prdtId: productInfo?.prdtId || effectivePrdtId || 'MOCK_PRODUCT',
|
||
prdtNm: productInfo?.prdtNm || 'Mock Product',
|
||
patnrId: effectivePatnrId || '1',
|
||
patncNm: patncNm || 'Mock Partner',
|
||
// calculateOrderSummaryFromProductInfo 함수가 필요한 필드들
|
||
// productInfo에서 직접 추출한 값을 전달
|
||
price2: price3Value, // Items (상품 가격)
|
||
price5: price5Value, // Coupon Savings (할인액)
|
||
finalPrice: discountPrice,
|
||
discount: price5Value,
|
||
origPrice: regularPrice,
|
||
discountPrice: discountPrice,
|
||
prodQty: quantity,
|
||
// 추가 가격 필드들 (fallback용)
|
||
price: discountPrice,
|
||
originalPrice: regularPrice,
|
||
// 이미지 정보
|
||
...(productInfo.imgList && { imgList: productInfo.imgList }),
|
||
...(productInfo.thumbnailUrl && { thumbnailUrl: productInfo.thumbnailUrl }),
|
||
...(productInfo.imgUrls && { imgUrls: productInfo.imgUrls }),
|
||
shippingCharge: productInfo?.shippingFee || '12.99', // 배송비
|
||
shippingFee: productInfo?.shippingFee || '12.99',
|
||
currSign: '$',
|
||
currSignLoc: 'left',
|
||
},
|
||
defaultPrice: discountPrice,
|
||
};
|
||
|
||
console.log('[BuyOption] Mock Mode - checkoutPanelInfo.productInfo price fields:', {
|
||
price2: price3Value,
|
||
price5: price5Value,
|
||
finalPrice: discountPrice,
|
||
origPrice: regularPrice,
|
||
discountPrice: discountPrice,
|
||
});
|
||
|
||
console.log('[BuyOption] Mock Mode - checkoutPanelInfo:', checkoutPanelInfo);
|
||
console.log('[BuyOption] Mock Mode - regularPrice:', regularPrice, 'discountPrice:', discountPrice);
|
||
|
||
console.log('%c[BuyOption] 🔍 Determining UPDATE vs PUSH for CheckOutPanel', 'background: orange; color: white; font-weight: bold; padding: 5px;');
|
||
|
||
// Mock Mode: checkoutPanelInfo가 있으면 무조건 진행
|
||
if (!checkoutPanelInfo) {
|
||
console.error('%c[BuyOption] ❌ Mock Mode - checkoutPanelInfo is null/undefined', 'background: red; color: white; padding: 3px;');
|
||
// 최소한의 fallback 데이터로 진행 시도
|
||
const fallbackPanelInfo = {
|
||
logInfo: { cartTpSno: `MOCK_FALLBACK_${Date.now()}` },
|
||
productInfo: {
|
||
prdtId: effectivePrdtId || 'MOCK_PRODUCT',
|
||
prdtNm: 'Mock Product',
|
||
patnrId: effectivePatnrId || '1',
|
||
patncNm: 'Mock Partner',
|
||
finalPrice: 99999,
|
||
origPrice: 99999,
|
||
discountPrice: 99999,
|
||
currSign: '$',
|
||
currSignLoc: 'left',
|
||
},
|
||
defaultPrice: 99999,
|
||
};
|
||
console.log('%c[BuyOption] Mock Mode - Using fallback panelInfo', 'background: orange; color: white; padding: 3px;', fallbackPanelInfo);
|
||
|
||
// 🔴 CRITICAL: DetailPanel Pop 완료 후 CheckOutPanel Push (Promise 체인)
|
||
dispatch((dispatchFn) => {
|
||
return new Promise((resolve) => {
|
||
console.log('[BuyOption] 1️⃣ Popping DetailPanel...');
|
||
dispatchFn(popPanel(Config.panel_names.DETAIL_PANEL));
|
||
|
||
// Pop이 끝난 후 Push 실행
|
||
Promise.resolve().then(() => {
|
||
console.log('[BuyOption] 2️⃣ Pushing CheckOutPanel...');
|
||
dispatchFn(
|
||
pushPanel({
|
||
name: Config.panel_names.CHECKOUT_PANEL,
|
||
panelInfo: fallbackPanelInfo,
|
||
})
|
||
);
|
||
console.log('[BuyOption] ✅ Panel transition complete');
|
||
resolve();
|
||
});
|
||
});
|
||
});
|
||
} else {
|
||
// 정상 케이스: checkoutPanelInfo 사용
|
||
// 🔴 CRITICAL: DetailPanel Pop 완료 후 CheckOutPanel Push (Promise 체인)
|
||
dispatch((dispatchFn) => {
|
||
return new Promise((resolve) => {
|
||
console.log('[BuyOption] 1️⃣ Normal - Popping DetailPanel...');
|
||
dispatchFn(popPanel(Config.panel_names.DETAIL_PANEL));
|
||
|
||
// Pop이 끝난 후 Push 실행
|
||
Promise.resolve().then(() => {
|
||
console.log('[BuyOption] 2️⃣ Normal - Pushing CheckOutPanel...');
|
||
dispatchFn(
|
||
pushPanel({
|
||
name: Config.panel_names.CHECKOUT_PANEL,
|
||
panelInfo: checkoutPanelInfo,
|
||
})
|
||
);
|
||
console.log('[BuyOption] ✅ Normal - Panel transition complete');
|
||
resolve();
|
||
});
|
||
});
|
||
});
|
||
}
|
||
console.log('%c[BuyOption] ✅ AFTER pushPanel dispatch', 'background: orange; color: white; font-weight: bold; padding: 5px;');
|
||
|
||
// Toast 정리는 성공적으로 패널 이동 후에만 실행
|
||
// dispatch(clearAllToasts());
|
||
}
|
||
}
|
||
}, [
|
||
dispatch,
|
||
userNumber,
|
||
selectedPatnrId,
|
||
selectedPrdtId,
|
||
selectedOptions,
|
||
productInfo,
|
||
productOptionInfos,
|
||
quantity,
|
||
logInfo,
|
||
selectFirstOptionIndex,
|
||
selectSecondOptionIndex,
|
||
nowMenu,
|
||
isMockMode,
|
||
selectDefaultOptionForMock,
|
||
cleanupVideoAndPlayerPanels,
|
||
]);
|
||
|
||
// ADD TO CART 버튼 클릭 핸들러
|
||
const handleAddToCartClick = useCallback((e) => {
|
||
e.stopPropagation();
|
||
console.log('[BuyOption] ADD TO CART clicked');
|
||
|
||
const isMock = isMockMode;
|
||
|
||
// 상품 품절 체크 (Mock Mode에서는 우회)
|
||
if (productInfo && productInfo?.soldoutFlag === 'Y' && !isMock) {
|
||
return;
|
||
}
|
||
|
||
// 로그인 체크 (Mock Mode에서는 우회)
|
||
if ((!userNumber || userNumber === '') && !isMock) {
|
||
return dispatch(setShowPopup(Config.ACTIVE_POPUP.loginPopup));
|
||
}
|
||
|
||
const effectivePatnrId =
|
||
selectedPatnrId || productInfo?.patnrId || 'MOCK_PARTNER';
|
||
const effectivePrdtId =
|
||
selectedPrdtId || productInfo?.prdtId || 'MOCK_PRODUCT';
|
||
const effectiveQuantity = quantity > 0 ? quantity : 1;
|
||
const optionForUse = isMock ? selectDefaultOptionForMock() : selectedOptions;
|
||
|
||
// API Mode 조건 검증
|
||
if (
|
||
!isMock &&
|
||
(!userNumber || !selectedPatnrId || !selectedPrdtId || !quantity)
|
||
) {
|
||
return;
|
||
}
|
||
|
||
const parseNumericPrice = (value) => {
|
||
if (typeof value === 'number') return value;
|
||
if (!value) return 0;
|
||
const cleaned = String(value).replace(/,/g, '');
|
||
const match = cleaned.match(/-?[\d.]+/);
|
||
return match ? parseFloat(match[0]) : 0;
|
||
};
|
||
|
||
const priceInfoSource =
|
||
optionForUse?.priceInfo ||
|
||
productInfo?.priceInfo ||
|
||
'0|0|0|0%|0';
|
||
const priceParts = priceInfoSource.split('|');
|
||
const regularPriceValue = parseNumericPrice(priceParts[0]);
|
||
const discountPriceValue =
|
||
priceParts.length > 1
|
||
? parseNumericPrice(priceParts[1])
|
||
: regularPriceValue;
|
||
const optionLabel =
|
||
optionForUse?.optNm || optionForUse?.prodOptCval || '';
|
||
|
||
if (!isMock) {
|
||
if (
|
||
productOptionInfos &&
|
||
productOptionInfos.length > 1 &&
|
||
productInfo?.optProdYn === 'Y'
|
||
) {
|
||
|
||
if (selectFirstOptionIndex === 0) {
|
||
dispatch(
|
||
showError(
|
||
null,
|
||
"PLEASE SELECT OPTION",
|
||
false,
|
||
null,
|
||
null
|
||
)
|
||
);
|
||
|
||
return;
|
||
}
|
||
}
|
||
if (
|
||
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl &&
|
||
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl.length > 1 &&
|
||
productInfo?.optProdYn === 'Y'
|
||
) {
|
||
|
||
if (selectSecondOptionIndex === 0) {
|
||
dispatch(
|
||
showError(
|
||
null,
|
||
"PLEASE SELECT OPTION",
|
||
false,
|
||
null,
|
||
null
|
||
)
|
||
);
|
||
|
||
return;
|
||
}
|
||
}
|
||
dispatch(
|
||
insertMyinfoCart({
|
||
mbrNo: userNumber,
|
||
patnrId: selectedPatnrId,
|
||
prdtId: selectedPrdtId,
|
||
prodQty: Number(effectiveQuantity),
|
||
prdtOpt: [{
|
||
prodOptSno: productOptionInfos?.[0]?.prodOptSno || '',
|
||
prodOptCdCval: optionForUse?.prodOptCdCval || '',
|
||
prodOptTpCdCval: productOptionInfos?.[0]?.prodOptTpCdCval || '',
|
||
prodOptCval: optionLabel,
|
||
}],
|
||
|
||
})
|
||
);
|
||
|
||
const imgUrl =
|
||
productInfo?.imgUrls600?.[0] ||
|
||
productInfo?.thumbnailUrl960 ||
|
||
productInfo?.imgList?.[0]?.imgUrl ||
|
||
productInfo?.thumbnailUrl ||
|
||
productInfo?.patncLogPath ||
|
||
'/assets/images/img-thumb-empty-144@3x.png';
|
||
|
||
const productInfoForCart = {
|
||
prdtId: effectivePrdtId,
|
||
prdtNm: productInfo?.prdtNm || 'Product',
|
||
patnrId: effectivePatnrId,
|
||
patncNm: productInfo?.patncNm || 'Partner',
|
||
patncLogPath: productInfo?.patncLogPath || '',
|
||
imgUrl,
|
||
thumbnailUrl: productInfo?.thumbnailUrl960 || imgUrl,
|
||
thumbnailUrl960: productInfo?.thumbnailUrl960,
|
||
imgList: productInfo?.imgList || [{ imgUrl }],
|
||
imgUrls: productInfo?.imgUrls || [{ imgUrl }],
|
||
imgUrls600: productInfo?.imgUrls600,
|
||
price2: regularPriceValue.toFixed(2),
|
||
price3: discountPriceValue.toFixed(2),
|
||
finalPrice: discountPriceValue,
|
||
discountPrice: discountPriceValue,
|
||
origPrice: regularPriceValue,
|
||
discount: Math.max(0, regularPriceValue - discountPriceValue),
|
||
priceInfo: priceInfoSource,
|
||
prodQty: effectiveQuantity,
|
||
optNm: optionLabel,
|
||
shippingCharge: productInfo?.shippingFee || '12.99',
|
||
soldoutFlag: productInfo?.soldoutFlag || 'N',
|
||
};
|
||
|
||
const optionInfoForCart = {
|
||
name: optionLabel,
|
||
price: optionForUse?.optPrc || '0.00',
|
||
};
|
||
dispatch(setShowPopup(Config.ACTIVE_POPUP.addCartPopup));
|
||
return;
|
||
// dispatch(
|
||
// pushPanel({
|
||
// name: Config.panel_names.CART_PANEL,
|
||
// panelInfo: {
|
||
// productInfo: productInfoForCart,
|
||
// optionInfo: optionInfoForCart,
|
||
// quantity: effectiveQuantity,
|
||
// },
|
||
// })
|
||
// );
|
||
} else {
|
||
console.log('[BuyOption] Mock Mode - Adding to cart (Mock)');
|
||
|
||
const imgUrl =
|
||
productInfo?.imgUrls600?.[0] ||
|
||
productInfo?.thumbnailUrl960 ||
|
||
productInfo?.imgList?.[0]?.imgUrl ||
|
||
productInfo?.thumbnailUrl ||
|
||
productInfo?.patncLogPath ||
|
||
'/assets/images/img-thumb-empty-144@3x.png';
|
||
|
||
const mockProductInfo = {
|
||
prdtId: effectivePrdtId,
|
||
prdtNm: productInfo?.prdtNm || 'Mock Product',
|
||
patnrId: effectivePatnrId,
|
||
patncNm: productInfo?.patncNm || 'Mock Partner',
|
||
patncLogPath: productInfo?.patncLogPath || '',
|
||
imgUrl,
|
||
thumbnailUrl: productInfo?.thumbnailUrl960 || imgUrl,
|
||
thumbnailUrl960: productInfo?.thumbnailUrl960,
|
||
imgList: productInfo?.imgList || [{ imgUrl }],
|
||
imgUrls: productInfo?.imgUrls || [{ imgUrl }],
|
||
imgUrls600: productInfo?.imgUrls600,
|
||
price2: regularPriceValue.toFixed(2),
|
||
price3: discountPriceValue.toFixed(2),
|
||
finalPrice: discountPriceValue,
|
||
discountPrice: discountPriceValue,
|
||
origPrice: regularPriceValue,
|
||
discount: Math.max(0, regularPriceValue - discountPriceValue),
|
||
priceInfo: priceInfoSource,
|
||
prodQty: effectiveQuantity,
|
||
optNm: optionLabel || 'Default Option',
|
||
shippingCharge: productInfo?.shippingFee || '12.99',
|
||
shippingFee: productInfo?.shippingFee || '12.99',
|
||
soldoutFlag: productInfo?.soldoutFlag || 'N',
|
||
};
|
||
|
||
const optionInfo = {
|
||
name: optionLabel || 'Default Option',
|
||
price: optionForUse?.optPrc || discountPriceValue.toFixed(2),
|
||
};
|
||
|
||
dispatch(
|
||
pushPanel({
|
||
name: Config.panel_names.CART_PANEL,
|
||
panelInfo: {
|
||
productInfo: mockProductInfo,
|
||
optionInfo,
|
||
quantity: effectiveQuantity,
|
||
},
|
||
})
|
||
);
|
||
}
|
||
// dispatch(clearAllToasts());
|
||
}, [
|
||
dispatch,
|
||
userNumber,
|
||
selectedPatnrId,
|
||
selectedPrdtId,
|
||
selectedOptions,
|
||
productInfo,
|
||
productOptionInfos,
|
||
quantity,
|
||
isMockMode,
|
||
selectDefaultOptionForMock,
|
||
]);
|
||
|
||
const handleFirstOptionSelect = (selected) => {
|
||
const selectedIndex = selected.selected;
|
||
console.log('[BuyOption] First option selected:', selectedIndex);
|
||
|
||
// "Select Option"이 선택된 경우 (index 0)
|
||
if (selectedIndex === 0) {
|
||
// 초기 상태로 리셋
|
||
setSelectFirstOptionIndex(0);
|
||
setSelectedBtnOptIdx(0);
|
||
setSelectedOptionItemIndex(0);
|
||
setSelectedOptions(undefined);
|
||
setIsOptionValue(false);
|
||
setIsOptionSelect(false);
|
||
return;
|
||
}
|
||
|
||
// selectedBtnOptIdx는 UI 인덱스 그대로 저장 (드롭다운 표시용)
|
||
setSelectedBtnOptIdx(selectedIndex);
|
||
setSelectFirstOptionIndex(selectedIndex);
|
||
// 실제 데이터 접근 시에는 selectedIndex - 1 사용
|
||
const dataIndex = selectedIndex - 1;
|
||
setSelectedOptionItemIndex(0);
|
||
setSelectedOptions(productOptionInfos[dataIndex]?.prdtOptDtl[0]);
|
||
setIsOptionValue(false);
|
||
setIsOptionSelect(true);
|
||
};
|
||
|
||
const handleSecondOptionSelect = (selected) => {
|
||
const selectedIndex = selected.selected;
|
||
console.log('[BuyOption] Second option selected:', selectedIndex);
|
||
|
||
// selectedOptionItemIndex는 UI 인덱스 그대로 저장
|
||
setSelectedOptionItemIndex(selectedIndex);
|
||
setSelectSecondOptionIndex(selectedIndex);
|
||
// 실제 데이터 접근 시에는 -1 적용
|
||
const firstOptionDataIndex = selectedBtnOptIdx - 1; // 첫 번째 옵션의 실제 데이터 인덱스
|
||
const secondOptionDataIndex = selectedIndex - 1; // 두 번째 옵션의 실제 데이터 인덱스
|
||
|
||
setSelectedOptions(
|
||
productOptionInfos.length > 1
|
||
? productOptionInfos[firstOptionDataIndex]?.prdtOptDtl[
|
||
secondOptionDataIndex
|
||
]
|
||
: productOptionInfos[0]?.prdtOptDtl[secondOptionDataIndex]
|
||
);
|
||
dispatch(
|
||
getProductOptionId(
|
||
productOptionInfos.length > 1
|
||
? productOptionInfos[firstOptionDataIndex]?.prdtOptDtl[
|
||
secondOptionDataIndex
|
||
]?.prodOptCdCval
|
||
: productOptionInfos[0]?.prdtOptDtl[secondOptionDataIndex]
|
||
?.prodOptCdCval
|
||
)
|
||
);
|
||
setIsOptionValue(true);
|
||
};
|
||
|
||
// 수량 선택 핸들러
|
||
const handleQuantitySelect = (selected) => {
|
||
const qty = selected.selected + 1;
|
||
console.log('[BuyOption] Quantity selected:', qty);
|
||
setQuantity(qty);
|
||
};
|
||
|
||
// // 옵션명 렌더링 함수 (SingleOption과 동일)
|
||
// const renderOptionName = useCallback(() => {
|
||
// if (selectedOptions) {
|
||
// return productOptionInfos[selectedBtnOptIdx]?.optNm || null;
|
||
// }
|
||
// return $L("SELECT");
|
||
// }, [productOptionInfos, selectedOptions, selectedBtnOptIdx]);
|
||
|
||
// // 옵션값 렌더링 함수 (SingleOption과 동일)
|
||
// const renderOptionValue = useCallback(() => {
|
||
// if (
|
||
// productOptionInfos &&
|
||
// productOptionInfos[selectedBtnOptIdx] &&
|
||
// productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl &&
|
||
// productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl.length > 0 &&
|
||
// isOptionValue
|
||
// ) {
|
||
// return (
|
||
// productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl[
|
||
// selectedOptionItemIndex
|
||
// ]?.prodOptCval || null
|
||
// );
|
||
// }
|
||
// return $L("SELECT");
|
||
// }, [
|
||
// productOptionInfos,
|
||
// selectedBtnOptIdx,
|
||
// isOptionValue,
|
||
// selectedOptionItemIndex,
|
||
// ]);
|
||
|
||
// Favorite 플래그 업데이트 (SingleOption과 동일)
|
||
useEffect(() => {
|
||
setFavoriteFlag(productInfo?.favorYn ? productInfo?.favorYn : 'N');
|
||
}, [productInfo]);
|
||
|
||
// Favorite 플래그 변경 콜백 (SingleOption과 동일)
|
||
const onFavoriteFlagChanged = useCallback((ev) => {
|
||
setFavoriteFlag(ev);
|
||
}, []);
|
||
|
||
// 컴포넌트 마운트 시 상품 정보 출력
|
||
useEffect(() => {
|
||
console.log('[BuyOption]', '상품 정보:', JSON.stringify(productInfo));
|
||
}, []); // 컴포넌트 마운트 시 한 번만 출력
|
||
|
||
// hasOnClose 로직 (SingleOption과 동일)
|
||
const hasOnClose = useMemo(() => {
|
||
if (productOptionInfos && productOptionInfos.length > 0) {
|
||
return true;
|
||
}
|
||
return false;
|
||
}, [productOptionInfos, isOptionValue, isOptionSelect, selectedOptions]);
|
||
|
||
// 로그인 팝업 텍스트 로직 (SingleOption과 동일)
|
||
const loginPopupText = useMemo(() => {
|
||
if (!userNumber) {
|
||
return $L('Would you like to sign in?');
|
||
}
|
||
if (!hasOnClose) {
|
||
return $L('Please select Option');
|
||
}
|
||
return $L('Would you like to sign in?');
|
||
}, [hasOnClose, userNumber]);
|
||
|
||
// BuyOption 내에서 arrow up으로 빠져나가지 않도록 처리
|
||
const handleArrowUpWithinBuyOption = useCallback((e) => {
|
||
// e.stopPropagation();
|
||
// arrow up 이벤트를 막음 (컨테이너의 leaveFor: {}와 함께 작동)
|
||
}, []);
|
||
|
||
// 팝업 닫기 핸들러 (SingleOption과 동일)
|
||
const onClose = useCallback(
|
||
(spotlightId) => {
|
||
dispatch(setHidePopup());
|
||
|
||
let currentSpot;
|
||
if (typeof spotlightId === 'string') {
|
||
currentSpot = spotlightId;
|
||
} else {
|
||
currentSpot = 'buy-option-add-to-cart-button';
|
||
}
|
||
|
||
if (currentSpot) {
|
||
setTimeout(() => {
|
||
Spotlight.focus(currentSpot);
|
||
});
|
||
}
|
||
},
|
||
[dispatch]
|
||
);
|
||
|
||
// 로그인 팝업 열기 핸들러 (SingleOption과 동일)
|
||
const handleLoginPopUpOpen = useCallback(() => {
|
||
if (!userNumber) {
|
||
if (webOSVersion >= '6.0') {
|
||
setTimeout(() => {
|
||
Spotlight.focus('buy-option-buy-now-button');
|
||
});
|
||
dispatch(launchMembershipApp());
|
||
} else {
|
||
dispatch(setShowPopup(Config.ACTIVE_POPUP.qrPopup));
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (hasOnClose) {
|
||
dispatch(setHidePopup());
|
||
|
||
let spotlightId = 'buy-option-first-dropdown';
|
||
|
||
//옵션이 하나만 있는경우 isOptionValue === false
|
||
if (!isOptionValue) {
|
||
spotlightId = 'buy-option-second-dropdown';
|
||
}
|
||
setTimeout(() => {
|
||
Spotlight.focus(spotlightId);
|
||
}, 100);
|
||
return;
|
||
}
|
||
}, [dispatch, hasOnClose, isOptionValue, webOSVersion, userNumber]);
|
||
|
||
useEffect(()=>{
|
||
return(()=>{
|
||
dispatch(clearAllToasts());
|
||
})
|
||
},[])
|
||
|
||
const handleCartMove = useCallback(() => {
|
||
dispatch(setHidePopup());
|
||
clearAllToasts();
|
||
dispatch(
|
||
pushPanel({
|
||
name: Config.panel_names.CART_PANEL,
|
||
})
|
||
)
|
||
},[dispatch])
|
||
|
||
return (
|
||
<Container
|
||
className={styles.buy_option}
|
||
>
|
||
<div className={styles.buy_option__left_section}>
|
||
{/* 동적 옵션 렌더링 */}
|
||
{productOptionInfos &&
|
||
productOptionInfos?.length > 0 &&
|
||
productInfo?.optProdYn === 'Y' && (
|
||
<>
|
||
{/* 첫번째 옵션 (여러 옵션이 있을 때만) */}
|
||
{productOptionInfos?.length > 1 && (
|
||
<div className={styles.buy_option__option_row}>
|
||
<div className={styles.buy_option__option_label}>
|
||
<div className={styles.buy_option__label_text}>
|
||
OPTION 1
|
||
</div>
|
||
</div>
|
||
<div className={styles.buy_option__option_control}>
|
||
<CustomDropDown
|
||
options={[
|
||
'Select Option',
|
||
...(productOptionInfos?.map((option) => option.optNm) ||
|
||
[]),
|
||
]}
|
||
selectedIndex={selectedBtnOptIdx}
|
||
onSelect={handleFirstOptionSelect}
|
||
spotlightId="buy-option-first-dropdown"
|
||
onSpotlightUp={handleArrowUpWithinBuyOption}
|
||
/>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* 두번째 옵션 (옵션 상세값들) */}
|
||
{productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl &&
|
||
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl?.length >
|
||
1 && (
|
||
<div className={styles.buy_option__option_row}>
|
||
<div className={styles.buy_option__option_label}>
|
||
<div className={styles.buy_option__label_text}>
|
||
{productOptionInfos.length === 1
|
||
? 'OPTION'
|
||
: 'OPTION 2'}
|
||
</div>
|
||
</div>
|
||
<div className={styles.buy_option__option_control}>
|
||
<CustomDropDown
|
||
options={[
|
||
'Select Option',
|
||
...(productOptionInfos[
|
||
productOptionInfos.length > 1
|
||
? selectedBtnOptIdx - 1
|
||
: selectedBtnOptIdx
|
||
]?.prdtOptDtl.map((detail) => ({
|
||
label: detail.prodOptCval,
|
||
disabled: detail.optStkQty <= 0,
|
||
imageUrl: detail.optImgUrl || null,
|
||
price: detail.priceInfo.split('|')[1],
|
||
prodOptCdCval: detail.prodOptCdCval,
|
||
})) || []),
|
||
]}
|
||
selectedIndex={selectedOptionItemIndex}
|
||
onSelect={handleSecondOptionSelect}
|
||
spotlightId="buy-option-second-dropdown"
|
||
onSpotlightUp={handleArrowUpWithinBuyOption}
|
||
/>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</>
|
||
)}
|
||
|
||
{/* 수량 선택 */}
|
||
<div className={styles.buy_option__option_row}>
|
||
<div className={styles.buy_option__option_label}>
|
||
<div className={styles.buy_option__label_text}>QUANTITY</div>
|
||
</div>
|
||
<div className={styles.buy_option__option_control}>
|
||
{/* <CustomDropDown
|
||
options={["1", "2", "3", "4", "5"]}
|
||
selectedIndex={quantity - 1}
|
||
onSelect={handleQuantitySelect}
|
||
spotlightId="buy-option-quantity-dropdown"
|
||
/> */}
|
||
<ProductQuantity
|
||
onSpotlightUp={handleArrowUpWithinBuyOption}
|
||
setQuantity={setQuantity}
|
||
quantity={quantity}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div className={styles.buy_option__right_section}>
|
||
<BuyOptionPriceBlock
|
||
className={styles.buy_option__price_block}
|
||
productInfo={productInfo || productData}
|
||
selectedOptions={selectedOptions}
|
||
patncNm={patncNm || productData?.patncNm || productInfo?.patncNm}
|
||
quantity={quantity}
|
||
/>
|
||
<div className={styles.buy_option__button_section}>
|
||
<TButton
|
||
className={styles.buy_option__buy_button}
|
||
spotlightId="buy-option-buy-now-button"
|
||
size="detailButton"
|
||
onClick={handleBuyNowClick}
|
||
onSpotlightUp={handleArrowUpWithinBuyOption}
|
||
>
|
||
<span className={styles.buy_option__button_text}>BUY NOW</span>
|
||
</TButton>
|
||
<TButton
|
||
className={styles.buy_option__cart_button}
|
||
spotlightId="buy-option-add-to-cart-button"
|
||
size="detailButton"
|
||
onClick={handleAddToCartClick}
|
||
onSpotlightUp={handleArrowUpWithinBuyOption}
|
||
>
|
||
<span className={styles.buy_option__button_text}>ADD TO CART</span>
|
||
</TButton>
|
||
</div>
|
||
</div>
|
||
|
||
{/* LOGIN POPUP */}
|
||
{activePopup === Config.ACTIVE_POPUP.loginPopup && (
|
||
<TPopUp
|
||
kind="textPopup"
|
||
hasText
|
||
open={popupVisible}
|
||
text={loginPopupText}
|
||
hasButton
|
||
hasOnClose={hasOnClose}
|
||
button1Text={$L('OK')}
|
||
button2Text={$L('CANCEL')}
|
||
onClick={handleLoginPopUpOpen}
|
||
onClose={onClose}
|
||
/>
|
||
)}
|
||
{activePopup === Config.ACTIVE_POPUP.addCartPopup && (
|
||
<TPopUp
|
||
kind="textPopup"
|
||
hasText
|
||
open={popupVisible}
|
||
text={"Added to Cart"}
|
||
hasButton
|
||
hasOnClose={hasOnClose}
|
||
button1Text={$L('VIEW CART')}
|
||
button2Text={$L('CONTINUE SHOPPING')}
|
||
onClick={handleCartMove}
|
||
onClose={onClose}
|
||
/>
|
||
)}
|
||
</Container>
|
||
);
|
||
};
|
||
|
||
|
||
|
||
export default BuyOption;
|