[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:
2025-11-01 16:56:32 +09:00
parent aa423f1541
commit 2e2cf295dc
4 changed files with 145 additions and 100 deletions

View File

@@ -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" />

View File

@@ -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(
() => [
{

View File

@@ -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]);

View File

@@ -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>;
}