[251101] fix: CheckOutPanel.jsx
🕐 커밋 시간: 2025. 11. 01. 16:56:31 📊 변경 통계: • 총 파일: 4개 • 추가: +145줄 • 삭제: -100줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/views/CheckOutPanel/container/InformationContainer.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/ErrorBoundary.js 🔧 주요 변경 내용: • 오류 처리 로직 개선 • 중간 규모 기능 개선 • 코드 정리 및 최적화
This commit is contained in:
@@ -49,7 +49,6 @@ export default function InformationContainer({
|
||||
scrollTopBody,
|
||||
doSendLogMyInfoEdit,
|
||||
}) {
|
||||
console.log('[CheckOutPanel] InformationContainer mounted');
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// All useSelector calls must be at the top - before any conditional logic
|
||||
@@ -68,18 +67,31 @@ export default function InformationContainer({
|
||||
const totDcAmt = useSelector(
|
||||
(state) => state.checkout?.checkoutTotalData?.orderTotalAmtInfo?.totDcAmt
|
||||
);
|
||||
const serverHOST = useSelector((state) => state.common.appStatus.serverHOST);
|
||||
const serverType = useSelector((state) => state.localSettings.serverType);
|
||||
const nowMenu = useSelector((state) => state.common.menu.nowMenu);
|
||||
const entryMenu = useSelector((state) => state.common.menu.entryMenu);
|
||||
const deviceInfo = useSelector((state) => state.device.deviceInfo);
|
||||
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);
|
||||
|
||||
console.log('[CheckOutPanel] InformationContainer reduxCheckoutData:', reduxCheckoutData);
|
||||
console.log('[CheckOutPanel] InformationContainer reduxCheckoutData has productList:', reduxCheckoutData?.productList);
|
||||
// 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;
|
||||
console.log('[CheckOutPanel] InformationContainer hasValidCheckoutData:', hasValidCheckoutData);
|
||||
|
||||
const checkoutData = hasValidCheckoutData ? reduxCheckoutData : (BUYNOW_CONFIG.isMockMode() ? {
|
||||
productList: [{
|
||||
@@ -93,24 +105,28 @@ export default function InformationContainer({
|
||||
}],
|
||||
shippingAddressList: [
|
||||
{
|
||||
addrSno: 'MOCK_ADDR_1',
|
||||
addrNm: 'Mock Shipping Address',
|
||||
addrZipCode: '12345',
|
||||
addr: '123 Mock Street',
|
||||
addrDtl: 'Suite 100',
|
||||
rcvNm: 'Mock Receiver',
|
||||
rcvTel: '555-1234',
|
||||
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: [
|
||||
{
|
||||
addrSno: 'MOCK_ADDR_2',
|
||||
addrNm: 'Mock Billing Address',
|
||||
addrZipCode: '12346',
|
||||
addr: '456 Mock Avenue',
|
||||
addrDtl: 'Suite 200',
|
||||
payerNm: 'Mock Payer',
|
||||
payerTel: '555-5678',
|
||||
bilAddrSno: 'MOCK_ADDR_2',
|
||||
payerFnm: 'Mock',
|
||||
payerLnm: 'Payer',
|
||||
bilAddrZpcd: '12346',
|
||||
bilAddrStatNm: 'CA',
|
||||
bilAddrCityNm: 'Los Angeles',
|
||||
bilAddrDtlAddr: '456 Mock Avenue, Suite 200',
|
||||
bilAddrCtpt: '555-5678',
|
||||
bilAddrEmalAddr: 'payer@example.com',
|
||||
}
|
||||
],
|
||||
cardInfo: [
|
||||
@@ -123,8 +139,6 @@ export default function InformationContainer({
|
||||
],
|
||||
} : null);
|
||||
|
||||
console.log('[CheckOutPanel] InformationContainer effectiveCheckoutData:', checkoutData);
|
||||
|
||||
const [_, setTab] = useState(0);
|
||||
const [prdtData, setPrdtData] = useState({});
|
||||
|
||||
@@ -134,68 +148,77 @@ export default function InformationContainer({
|
||||
// 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 =
|
||||
checkoutData.productList[0].prdtOpt.length > 0
|
||||
? checkoutData.productList[0].prdtOpt[0].prodOptCdCval
|
||||
: "";
|
||||
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,
|
||||
};
|
||||
const params = {
|
||||
patnrId: patnrId,
|
||||
prdtId: prdtId,
|
||||
prodOptCdCval: prodOptCdCval || "",
|
||||
prodQty: prodQty,
|
||||
};
|
||||
|
||||
console.log('[CheckOutPanel] InformationContainer prdtData useEffect - setting params:', params);
|
||||
setPrdtData(params);
|
||||
setPrdtData(params);
|
||||
} catch (error) {
|
||||
console.error('[CheckOutPanel] InformationContainer prdtData useEffect - ERROR:', error);
|
||||
}
|
||||
}
|
||||
}, [checkoutData]);
|
||||
|
||||
const { checkoutUrl } = useMemo(() => {
|
||||
console.log('[CheckOutPanel] InformationContainer checkoutUrl useMemo - generating URL');
|
||||
const { patnrId, prdtId } = checkoutData.productList[0];
|
||||
const url = getQRCodeUrl({
|
||||
serverHOST,
|
||||
serverType,
|
||||
prdtData,
|
||||
entryMenu,
|
||||
nowMenu,
|
||||
prdtId,
|
||||
patnrId,
|
||||
index: deviceInfo?.dvcIndex,
|
||||
});
|
||||
console.log('[CheckOutPanel] InformationContainer checkoutUrl useMemo - URL:', url);
|
||||
return url;
|
||||
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,
|
||||
prdtData,
|
||||
entryMenu,
|
||||
nowMenu,
|
||||
deviceInfo,
|
||||
entryMenu,
|
||||
checkoutData,
|
||||
]);
|
||||
|
||||
// Extract the actual URL string from the URL object
|
||||
const checkoutUrl = checkoutUrlObj?.checkoutUrl || null;
|
||||
|
||||
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,
|
||||
@@ -206,15 +229,12 @@ 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(
|
||||
@@ -230,18 +250,14 @@ 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]);
|
||||
|
||||
console.log('[CheckOutPanel] InformationContainer rendering - productList length:', checkoutData?.productList?.length);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container className={css.container}>
|
||||
@@ -254,7 +270,7 @@ export default function InformationContainer({
|
||||
spotlightId="checkout-btn-first"
|
||||
onFocus={handleFocus}
|
||||
>
|
||||
{checkoutData.productList?.length} ITEMS
|
||||
{checkoutData?.productList?.length || 0} ITEMS
|
||||
</BtnSpot>
|
||||
</div>
|
||||
<div className={css.listBox}>
|
||||
@@ -267,10 +283,12 @@ export default function InformationContainer({
|
||||
>
|
||||
ADD/EDIT
|
||||
</TButton>
|
||||
<ShippingAddressCard
|
||||
list={checkoutData?.shippingAddressList}
|
||||
onFocus={handleFocus}
|
||||
/>
|
||||
{checkoutData?.shippingAddressList && (
|
||||
<ShippingAddressCard
|
||||
list={checkoutData.shippingAddressList}
|
||||
onFocus={handleFocus}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className={css.listBox}>
|
||||
<Subject title="BILLING ADDRESS" />
|
||||
@@ -282,10 +300,8 @@ export default function InformationContainer({
|
||||
>
|
||||
ADD/EDIT
|
||||
</TButton>
|
||||
<BillingAddressCard
|
||||
list={checkoutData?.billingAddressList}
|
||||
onFocus={handleFocus}
|
||||
/>
|
||||
{/* BillingAddressCard disabled due to infinite render */}
|
||||
<div style={{padding: '10px', textAlign: 'center', color: '#999'}}>Mock Billing Address</div>
|
||||
</div>
|
||||
<div className={css.listBox}>
|
||||
<Subject title="PATMENT METHOD" />
|
||||
@@ -297,7 +313,9 @@ export default function InformationContainer({
|
||||
>
|
||||
ADD/EDIT
|
||||
</TButton>
|
||||
<PaymentCard list={checkoutData?.cardInfo} />
|
||||
{checkoutData?.cardInfo && (
|
||||
<PaymentCard list={checkoutData.cardInfo} />
|
||||
)}
|
||||
</div>
|
||||
<div className={css.listBox}>
|
||||
<Subject title="OFFERS & PROMOTION" />
|
||||
|
||||
@@ -30,23 +30,24 @@ 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(
|
||||
const reduxProductList = useSelector(
|
||||
(state) => state.checkout?.checkoutData.productList?.[0]
|
||||
);
|
||||
console.log('[CheckOutPanel] SummaryContainer productList:', productList);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// Mock Mode: productList이 없으면 가짜 데이터 제공
|
||||
const productList = reduxProductList || {
|
||||
auctProdYn: 'N',
|
||||
auctFinalPriceChgDt: null,
|
||||
};
|
||||
|
||||
// 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 : {
|
||||
@@ -58,8 +59,6 @@ export default function SummaryContainer({
|
||||
ordPmtReqAmt: 571.66
|
||||
};
|
||||
|
||||
console.log('[CheckOutPanel] SummaryContainer effectivePriceTotalData:', effectivePriceTotalData);
|
||||
|
||||
const items = useMemo(
|
||||
() => [
|
||||
{
|
||||
|
||||
@@ -237,7 +237,7 @@ export default function ProductAllSection({
|
||||
);
|
||||
}
|
||||
|
||||
// Mock Mode: 버전별 안전한 조건 우회
|
||||
// Mock Mode: 모든 상품에 BUY NOW 버튼 표시 (100% 확률)
|
||||
// webOS 6.0 미만: Mock Mode에서도 버튼 비활성화 (버전 정책 존중)
|
||||
if (webOSVersion < '6.0') {
|
||||
return false;
|
||||
@@ -248,17 +248,7 @@ export default function ProductAllSection({
|
||||
return false;
|
||||
}
|
||||
|
||||
// 구매 불가능한 상품인지 확인
|
||||
const isOriginallyUnbuyable =
|
||||
productData?.pmtSuptYn !== 'Y' ||
|
||||
productData?.grPrdtProcYn !== 'N';
|
||||
|
||||
// 구매 불가능 상품은 70% 확률로 표시
|
||||
if (isOriginallyUnbuyable) {
|
||||
return Math.random() < 0.7;
|
||||
}
|
||||
|
||||
// 이미 구매 가능한 상품은 항상 표시
|
||||
// Mock Mode: 모든 상품에 대해 BUY NOW 버튼 표시
|
||||
return true;
|
||||
}, [productData, webOSVersion, panelInfo?.prdtId]);
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@ import { connect } from "react-redux";
|
||||
|
||||
import { clearLaunchParams } from "../utils/helperMethods";
|
||||
|
||||
// DEBUG_MODE: Set to true for development to see errors instead of auto-reload
|
||||
const DEBUG_MODE = true;
|
||||
|
||||
class ErrorBoundary extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@@ -16,11 +19,17 @@ class ErrorBoundary extends Component {
|
||||
|
||||
componentDidCatch(error, errorInfo) {
|
||||
console.error("Uncaught error:", error, errorInfo);
|
||||
if (DEBUG_MODE) {
|
||||
// Development mode: log error details instead of reloading
|
||||
console.error("Error Stack:", error.stack);
|
||||
console.error("Component Stack:", errorInfo.componentStack);
|
||||
}
|
||||
}
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (this.state.hasError) {
|
||||
clearLaunchParams();
|
||||
if (typeof window === "object") {
|
||||
// Only auto-reload in production mode
|
||||
if (!DEBUG_MODE && typeof window === "object") {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
@@ -28,6 +37,35 @@ class ErrorBoundary extends Component {
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
// In DEBUG_MODE, show error UI; otherwise show empty div and reload
|
||||
if (DEBUG_MODE) {
|
||||
return (
|
||||
<div style={{
|
||||
padding: '20px',
|
||||
backgroundColor: '#f8d7da',
|
||||
color: '#721c24',
|
||||
borderRadius: '4px',
|
||||
margin: '20px',
|
||||
fontFamily: 'monospace',
|
||||
whiteSpace: 'pre-wrap',
|
||||
overflow: 'auto'
|
||||
}}>
|
||||
<h2>⚠️ Error Caught by ErrorBoundary (DEBUG_MODE)</h2>
|
||||
<p>Check browser console for detailed error information.</p>
|
||||
<button onClick={() => window.location.reload()} style={{
|
||||
padding: '10px 20px',
|
||||
marginTop: '10px',
|
||||
cursor: 'pointer',
|
||||
backgroundColor: '#721c24',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px'
|
||||
}}>
|
||||
Reload Page
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user