Files
shoptime/com.twin.app.shoptime/src/views/CheckOutPanel/container/InformationContainer.jsx
junghoon86.park 761796a785 [구매 옵션 노출관련 처리] #1
- option노출부분에서의 hasMockOption부분 제거
 - checkout넘어갔을시에 목데이터 노출되는부분 변경.
2025-11-10 10:59:15 +09:00

368 lines
12 KiB
JavaScript

import React, {
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import {
useDispatch,
useSelector,
} from 'react-redux';
import Spotlight from '@enact/spotlight';
import SpotlightContainerDecorator
from '@enact/spotlight/SpotlightContainerDecorator';
import Spottable from '@enact/spotlight/Spottable';
import {
setHidePopup,
setShowPopup,
} from '../../../actions/commonActions';
import { sendLogTotalRecommend } from '../../../actions/logActions';
import { popPanel } from '../../../actions/panelActions';
import TButton from '../../../components/TButton/TButton';
import TPopUp from '../../../components/TPopUp/TPopUp';
import TQRCode from '../../../components/TQRCode/TQRCode';
import useScrollTo from '../../../hooks/useScrollTo';
import useScrollTopByDistance from '../../../hooks/useScrollTopByDistance';
import { BUYNOW_CONFIG } from '../../../utils/BuyNowConfig';
import * as Config from '../../../utils/Config';
import {
$L,
getQRCodeUrl,
} from '../../../utils/helperMethods';
import BillingAddressCard from '../components/BillingAddressCard';
import PaymentCard from '../components/PaymentCard';
import ShippingAddressCard from '../components/ShippingAddressCard';
import Subject from '../components/Subject';
import css from './InformationContainer.module.less';
const Container = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
const BtnSpot = Spottable('p');
const getBtnNmByIndex = (index) => {
switch (index) {
case 0:
return 'SHIPPING ADDRESS';
case 1:
return 'BILLING ADDRESS';
case 2:
return 'PATMENT METHOD';
default:
return '';
}
};
export default function InformationContainer({
toggleOfferSideBar,
toggleOrderSideBar,
scrollTopBody,
doSendLogMyInfoEdit,
}) {
const dispatch = useDispatch();
// 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
);
const auctFinalPriceChgDt = useSelector(
(state) => state.checkout?.checkoutData?.productList?.[0].auctFinalPriceChgDt
);
const totDcAmt = useSelector((state) => state.checkout?.checkoutTotalData?.totDcAmt);
const reduxServerHOST = useSelector((state) => state.common.appStatus.serverHOST);
const reduxServerType = useSelector((state) => state.localSettings.serverType);
const reduxNowMenu = useSelector((state) => state.common.menu.nowMenu);
const reduxEntryMenu = useSelector((state) => state.common.menu.entryMenu);
const reduxDeviceInfo = useSelector((state) => state.device.deviceInfo);
// Mock Mode: Provide fallback values for Redux selectors
const serverHOST = BUYNOW_CONFIG.isMockMode()
? reduxServerHOST || 'sdp-us.shoptime.com'
: reduxServerHOST;
const serverType = BUYNOW_CONFIG.isMockMode() ? reduxServerType || 'system' : reduxServerType;
const nowMenu = BUYNOW_CONFIG.isMockMode() ? reduxNowMenu || 'MOCK_NOW_MENU' : reduxNowMenu;
const entryMenu = BUYNOW_CONFIG.isMockMode()
? reduxEntryMenu || 'MOCK_ENTRY_MENU'
: reduxEntryMenu;
const deviceInfo = BUYNOW_CONFIG.isMockMode()
? reduxDeviceInfo || { dvcIndex: 1 }
: reduxDeviceInfo;
// Check if reduxCheckoutData has actual data (productList)
const hasValidCheckoutData =
reduxCheckoutData?.productList &&
Array.isArray(reduxCheckoutData.productList) &&
reduxCheckoutData.productList.length > 0;
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: [
{
dlvrAddrSno: 'MOCK_ADDR_1',
dlvrOdrFnm: 'Mock',
dlvrOdrLnm: 'Receiver',
dlvrZpcd: '12345',
dlvrStatNm: 'CA',
dlvrCityNm: 'Los Angeles',
dlvrDtlAddr: '123 Mock Street, Suite 100',
dlvrCtpt: '555-1234',
dlvrEmalAddr: 'mock@example.com',
},
],
billingAddressList: [
{
bilAddrSno: 'MOCK_ADDR_2',
bilOdrFnm: 'Mock',
bilOdrLnm: 'Payer',
bilZpcd: '12346',
bilStatNm: 'CA',
bilCityNm: 'Los Angeles',
bilDtlAddr: '456 Mock Avenue, Suite 200',
bilCtpt: '5555678',
bilEmalAddr: 'payer@example.com',
},
],
cardInfo: [
{
cardSno: 'MOCK_CARD_1',
cardNm: 'Mock Card',
cardNo: '****-****-****-1234',
cardType: 'CREDIT',
},
],
}
: null;
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) {
dispatch(popPanel());
}
}, [checkoutData, dispatch]);
useEffect(() => {
if (checkoutData && checkoutData.productList) {
try {
const { patnrId, prdtId, prodQty } = checkoutData.productList[0];
const prdtOpt = checkoutData.productList[0].prdtOpt;
const prodOptCdCval =
Array.isArray(prdtOpt) && prdtOpt.length > 0 ? prdtOpt[0].prodOptCdCval : '';
const params = {
patnrId: patnrId,
prdtId: prdtId,
prodOptCdCval: prodOptCdCval || '',
prodQty: prodQty,
};
setPrdtData(params);
} catch (error) {
console.error('[CheckOutPanel] InformationContainer prdtData useEffect - ERROR:', error);
}
}
}, [checkoutData]);
const checkoutUrlObj = useMemo(() => {
try {
// Guard: ensure checkoutData and productList exist
if (!checkoutData?.productList?.[0]) {
console.error('[CheckOutPanel] InformationContainer checkoutUrl useMemo - No product data');
return null;
}
const { patnrId, prdtId } = checkoutData.productList[0];
const url = getQRCodeUrl({
serverHOST,
serverType,
prdtData,
entryMenu,
nowMenu,
prdtId,
patnrId,
index: deviceInfo?.dvcIndex,
});
return url;
} catch (error) {
console.error('[CheckOutPanel] InformationContainer checkoutUrl useMemo - ERROR:', error);
return null;
}
}, [serverHOST, serverType, entryMenu, nowMenu, deviceInfo]);
// Extract the actual URL string from the URL object
const checkoutUrl = checkoutUrlObj?.checkoutUrl || null;
const handleFocus = useCallback(() => {
const c = Spotlight.getCurrent();
const target = c.getAttribute('data-spotlight-id');
const targetValue = '[data-spotlight-id="' + target + '"]';
if (cursorVisible) {
return;
}
scrollTopByDistance(`[data-marker="scroll-marker"]`, targetValue, scrollTopBody, 100);
}, []);
const handleButtonClick = useCallback(
(index) => {
const btnNm = getBtnNmByIndex(index);
if (btnNm && doSendLogMyInfoEdit) {
doSendLogMyInfoEdit(btnNm);
}
setTab(index);
dispatch(setShowPopup(Config.ACTIVE_POPUP.qrPopup));
dispatch(
sendLogTotalRecommend({
buttonTitle: `${btnNm} ADD/EDIT`,
buttonId: `checkout_info_${btnNm.toLowerCase().replace(/\s+/g, '_')}`,
contextName: Config.LOG_CONTEXT_NAME.CHECKOUT,
messageId: Config.LOG_MESSAGE_ID.BUTTONCLICK,
})
);
},
[dispatch, doSendLogMyInfoEdit]
);
const handleCancel = useCallback(() => {
dispatch(setHidePopup());
}, [dispatch]);
const handleDone = useCallback(() => {
dispatch(setHidePopup());
dispatch(popPanel());
}, [dispatch]);
return (
<>
<Container className={css.container}>
<div data-marker="scroll-marker" />
<Subject title="ORDER ITEMS" />
<div className={css.markBtn}>
<BtnSpot
onClick={toggleOrderSideBar}
scrollTopBody={scrollTopBody}
spotlightId="checkout-btn-first"
onFocus={handleFocus}
>
{checkoutData?.productList?.length || 0} ITEMS
</BtnSpot>
</div>
<div className={css.listBox}>
<Subject title="SHIPPING ADDRESS" />
<TButton
className={css.editBtn}
spotlightId="shipping-add-btn"
onFocus={handleFocus}
onClick={() => handleButtonClick(0)}
>
ADD/EDIT
</TButton>
{checkoutData?.shippingAddressList && (
<ShippingAddressCard list={checkoutData.shippingAddressList} onFocus={handleFocus} />
)}
</div>
<div className={css.listBox}>
<Subject title="BILLING ADDRESS" />
<TButton
className={css.editBtn}
spotlightId="billing-add-btn"
onFocus={handleFocus}
onClick={() => handleButtonClick(1)}
>
ADD/EDIT
</TButton>
{/* BillingAddressCard disabled due to infinite render loop in Mock Mode */}
{/* <div style={{ padding: '10px', textAlign: 'center', color: '#999' }}>
Mock Billing Address
</div> */}
{checkoutData?.billingAddressList && (
<BillingAddressCard list={checkoutData.billingAddressList} onFocus={handleFocus} />
)}
</div>
<div className={css.listBox}>
<Subject title="PATMENT METHOD" />
<TButton
className={css.editBtn}
spotlightId="payment-add-btn"
onFocus={handleFocus}
onClick={() => handleButtonClick(2)}
>
ADD/EDIT
</TButton>
{checkoutData?.cardInfo && <PaymentCard list={checkoutData.cardInfo} />}
</div>
<div className={css.listBox}>
<Subject title="OFFERS & PROMOTION" />
<div className={css.markBtn}>
<BtnSpot
onClick={toggleOfferSideBar}
scrollTopBody={scrollTopBody}
spotlightId="checkout-btn-fifth"
onFocus={handleFocus}
>
{auctProdYn === 'Y' && !auctFinalPriceChgDt ? ' - ' : totDcAmt ? totDcAmt : '-'}
</BtnSpot>
</div>
</div>
</Container>
{/* QR Code */}
{activePopup === Config.ACTIVE_POPUP.qrPopup && (
<TPopUp
kind="qrPopup"
open={popupVisible}
onClose={handleCancel}
className={css.checkoutQRPopup}
>
<div className={css.popupContainer}>
<div className={css.header}>
<h3>{$L('QR CODE')}</h3>
</div>
<div className={css.qrcodeContainer}>
{checkoutUrl && (
<div className={css.qrcode}>
<TQRCode text={checkoutUrl} isBillingProductVisible width="360" height="360" />
</div>
)}
<h3>
{$L(
'Please update your information and complete the payment on your mobile. By clicking the OK button, you will be redirected to the product details page'
)}
</h3>
<TButton className={css.popupBtn} onClick={handleDone}>
{$L('OK')}
</TButton>
</div>
</div>
</TPopUp>
)}
</>
);
}