[251101] fix: CheckOutPanel Mock-2
🕐 커밋 시간: 2025. 11. 01. 21:01:06 📊 변경 통계: • 총 파일: 10개 • 추가: +617줄 • 삭제: -142줄 📁 추가된 파일: + com.twin.app.shoptime/src/utils/mockDataSafetyUtils.js 📝 수정된 파일: ~ com.twin.app.shoptime/src/utils/BuyNowDataManipulator.js ~ com.twin.app.shoptime/src/views/CheckOutPanel/CheckOutPanel.jsx ~ com.twin.app.shoptime/src/views/CheckOutPanel/container/OrderItemsSideBar.jsx ~ com.twin.app.shoptime/src/views/CheckOutPanel/container/SummaryContainerMock.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.module.less ~ com.twin.app.shoptime/src/views/DetailPanel/components/BuyOption.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/components/FavoriteBtn.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/components/FavoriteBtn.module.less ~ com.twin.app.shoptime/src/views/ErrorBoundary.js 🔧 주요 변경 내용: • 공통 유틸리티 함수 최적화 • UI 컴포넌트 아키텍처 개선 • 오류 처리 로직 개선 • 대규모 기능 개발 • 모듈 구조 개선
This commit is contained in:
@@ -19,34 +19,35 @@ export const createMockProductOptionData = (originalProductData) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 원본 데이터에서 필요한 정보 추출
|
// 원본 데이터에서 필요한 정보 추출
|
||||||
const basePrice = originalProductData.prdtPrice || 0;
|
const prdtId = originalProductData.prdtId || 'MOCK_PRODUCT';
|
||||||
const prdtId = originalProductData.prdtId;
|
const prdtNm = originalProductData.prdtNm || 'Mock Product';
|
||||||
const prdtNm = originalProductData.prdtNm;
|
|
||||||
|
|
||||||
// Mock 옵션 데이터 생성
|
// 실제 상품 가격 정보 사용 (originalProductData의 priceInfo 또는 기본값)
|
||||||
return {
|
// priceInfo 포맷: "원가|할인가|할인액|할인율|할인율숫자"
|
||||||
// 옵션 ID (기본 값)
|
const priceInfo = originalProductData.priceInfo || '99999|99999|0|0%|0';
|
||||||
prodOptSno: `MOCK_OPT_${prdtId}_1`,
|
|
||||||
|
|
||||||
// 옵션 목록 (기본값 1개)
|
console.log('[BuyNowDataManipulator] createMockProductOptionData - priceInfo:', priceInfo);
|
||||||
optionList: [
|
|
||||||
{
|
// 옵션 상세 객체
|
||||||
optId: `MOCK_OPT_${prdtId}_1`,
|
const optionDetail = {
|
||||||
optNm: `${prdtNm} - 기본 옵션`,
|
prodOptCval: `${prdtNm}`,
|
||||||
optPrice: basePrice,
|
priceInfo: priceInfo,
|
||||||
optDscntPrice: basePrice,
|
|
||||||
stockCnt: 999,
|
stockCnt: 999,
|
||||||
soldOutYn: 'N',
|
soldOutYn: 'N',
|
||||||
optImgUrl: originalProductData.thumbnailUrl || null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
// 옵션 타입 정보
|
|
||||||
optionTypeInfo: {
|
|
||||||
optTypeCd: 'BASIC',
|
|
||||||
optTypeNm: '기본'
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Mock 옵션 데이터 생성 (BuyOption에서 기대하는 구조)
|
||||||
|
// 배열 구조로 반환 - BuyOption에서 productOptionInfos[0]에 접근
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
// 옵션 ID (기본 값)
|
||||||
|
prodOptSno: `MOCK_OPT_${prdtId}_1`,
|
||||||
|
prodOptTpCdCval: 'BASIC',
|
||||||
|
|
||||||
|
// 옵션 상세 목록 (기본값 1개) - prdtOptDtl 배열 구조
|
||||||
|
prdtOptDtl: [optionDetail],
|
||||||
|
}
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
326
com.twin.app.shoptime/src/utils/mockDataSafetyUtils.js
Normal file
326
com.twin.app.shoptime/src/utils/mockDataSafetyUtils.js
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
/**
|
||||||
|
* Mock 모드에서 데이터가 없거나 불완전할 때 안전하게 처리하는 유틸리티
|
||||||
|
* 목적: 표시만 하는 것이므로 데이터가 없으면 기본값으로 우회
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 상품 정보의 유효성 체크 - 표시할 데이터가 있는지 확인
|
||||||
|
* @param {Object} product - 상품 객체
|
||||||
|
* @returns {boolean} 표시 가능 여부
|
||||||
|
*/
|
||||||
|
export const isProductDisplayable = (product) => {
|
||||||
|
return product && (product.prdtNm || product.prdtId);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 가격 정보를 안전하게 추출 (없으면 기본값 반환)
|
||||||
|
* @param {Object} product - 상품 객체
|
||||||
|
* @returns {Object} { price, originalPrice, discount, currSign, currSignLoc }
|
||||||
|
*/
|
||||||
|
export const getSafeProductPrice = (product) => {
|
||||||
|
if (!product) {
|
||||||
|
return {
|
||||||
|
price: 0,
|
||||||
|
originalPrice: 0,
|
||||||
|
discount: 0,
|
||||||
|
currSign: '$',
|
||||||
|
currSignLoc: 'left',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// priceInfo가 있으면 파싱하여 사용 (BuyOption에서 전달된 원본 상품 데이터)
|
||||||
|
if (product.priceInfo) {
|
||||||
|
const priceParts = product.priceInfo.split('|');
|
||||||
|
if (priceParts.length >= 5) {
|
||||||
|
const originalPrice = parseFloat(priceParts[0]) || 0;
|
||||||
|
const price = parseFloat(priceParts[1]) || originalPrice; // 할인가격
|
||||||
|
const discountRate = parseFloat(priceParts[2]) || 0;
|
||||||
|
const discountAmount = parseFloat(priceParts[3]) || 0;
|
||||||
|
const currSign = priceParts[4] || '$';
|
||||||
|
|
||||||
|
return {
|
||||||
|
price,
|
||||||
|
originalPrice,
|
||||||
|
discount: discountAmount > 0 ? discountAmount : Math.max(0, originalPrice - price),
|
||||||
|
currSign,
|
||||||
|
currSignLoc: 'left', // 기본값
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback: finalPrice, discountPrice 등 기존 방식
|
||||||
|
const price = parseFloat(product.finalPrice || product.discountPrice || 0);
|
||||||
|
const originalPrice = parseFloat(product.origPrice || product.originalPrice || price);
|
||||||
|
const discount = Math.max(0, originalPrice - price);
|
||||||
|
|
||||||
|
return {
|
||||||
|
price,
|
||||||
|
originalPrice,
|
||||||
|
discount,
|
||||||
|
currSign: product.currSign || '$',
|
||||||
|
currSignLoc: product.currSignLoc || 'left',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 상품명을 안전하게 추출
|
||||||
|
* @param {Object} product - 상품 객체
|
||||||
|
* @returns {string} 상품명 또는 기본값
|
||||||
|
*/
|
||||||
|
export const getSafeProductName = (product) => {
|
||||||
|
return product?.prdtNm || product?.prdtId || 'Product';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 파트너명을 안전하게 추출
|
||||||
|
* @param {Object} product - 상품 객체
|
||||||
|
* @returns {string} 파트너명 또는 기본값
|
||||||
|
*/
|
||||||
|
export const getSafePartnerName = (product) => {
|
||||||
|
return product?.patncNm || product?.patnrId || 'Partner';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 수량을 안전하게 추출
|
||||||
|
* @param {Object} product - 상품 객체
|
||||||
|
* @returns {number} 수량
|
||||||
|
*/
|
||||||
|
export const getSafeQuantity = (product) => {
|
||||||
|
const qty = product?.prodQty;
|
||||||
|
return typeof qty === 'number' && qty > 0 ? qty : 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 배송료를 안전하게 추출
|
||||||
|
* @param {Object} product - 상품 객체
|
||||||
|
* @returns {number} 배송료
|
||||||
|
*/
|
||||||
|
export const getSafeShippingCharge = (product) => {
|
||||||
|
const shipping = product?.shippingCharge;
|
||||||
|
return typeof shipping === 'number' ? shipping : 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 특정 필드가 'N'으로 설정되어 있는지 체크 (비활성화 항목)
|
||||||
|
* @param {string} value - 값
|
||||||
|
* @returns {boolean} 'N'인 경우 true
|
||||||
|
*/
|
||||||
|
export const isDisabled = (value) => {
|
||||||
|
return value === 'N' || value === false || !value;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 옵션 정보를 안전하게 추출
|
||||||
|
* @param {Object} product - 상품 객체
|
||||||
|
* @returns {Array} 옵션 배열
|
||||||
|
*/
|
||||||
|
export const getSafeProductOptions = (product) => {
|
||||||
|
const options = product?.prdtOpt;
|
||||||
|
return Array.isArray(options) && options.length > 0
|
||||||
|
? options
|
||||||
|
: [{ prodOptCdCval: 'N/A', optNm: 'No Options' }];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 이미지 URL을 안전하게 추출
|
||||||
|
* @param {Object} product - 상품 객체
|
||||||
|
* @returns {string} 이미지 URL 또는 기본값
|
||||||
|
*/
|
||||||
|
export const getSafeImageUrl = (product) => {
|
||||||
|
// 1순위: imgUrls 배열 (productInfo의 imgUrls)
|
||||||
|
const imgUrls = product?.imgUrls;
|
||||||
|
if (Array.isArray(imgUrls) && imgUrls.length > 0) {
|
||||||
|
return imgUrls[0];
|
||||||
|
}
|
||||||
|
// 2순위: imgUrl 직접 필드
|
||||||
|
if (product?.imgUrl) {
|
||||||
|
return product.imgUrl;
|
||||||
|
}
|
||||||
|
// 3순위: thumbnailUrl (productInfo의 썸네일)
|
||||||
|
if (product?.thumbnailUrl) {
|
||||||
|
return product.thumbnailUrl;
|
||||||
|
}
|
||||||
|
// 4순위: thumbnailUrl960 (productInfo의 960px 썸네일)
|
||||||
|
if (product?.thumbnailUrl960) {
|
||||||
|
return product.thumbnailUrl960;
|
||||||
|
}
|
||||||
|
// 5순위: imgUrls 배열의 imgUrl 필드 (기존 방식)
|
||||||
|
const imgUrlsWithImgUrl = product?.imgUrls;
|
||||||
|
if (Array.isArray(imgUrlsWithImgUrl) && imgUrlsWithImgUrl.length > 0) {
|
||||||
|
return imgUrlsWithImgUrl[0]?.imgUrl || '/mock/image.jpg';
|
||||||
|
}
|
||||||
|
// 6순위: patncLogPath (파트너 로고)
|
||||||
|
if (product?.patncLogPath) {
|
||||||
|
return product.patncLogPath;
|
||||||
|
}
|
||||||
|
// 7순위: 기본 이미지
|
||||||
|
return '/mock/image.jpg';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 경매 상품 여부 안전 확인
|
||||||
|
* @param {Object} product - 상품 객체
|
||||||
|
* @returns {boolean} 경매 상품 여부
|
||||||
|
*/
|
||||||
|
export const isAuctionProduct = (product) => {
|
||||||
|
return product?.auctProdYn === 'Y';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 상품 데이터를 표시용으로 정규화 (모든 필드를 안전한 값으로 변환)
|
||||||
|
* @param {Object} product - 상품 객체
|
||||||
|
* @returns {Object} 정규화된 상품 객체
|
||||||
|
*/
|
||||||
|
export const normalizeProductDataForDisplay = (product) => {
|
||||||
|
if (!isProductDisplayable(product)) {
|
||||||
|
// 표시 불가능하면 빈 제품으로 반환
|
||||||
|
return {
|
||||||
|
prdtId: 'N/A',
|
||||||
|
prdtNm: 'Product',
|
||||||
|
patnrId: 'N/A',
|
||||||
|
patncNm: 'Partner',
|
||||||
|
prodQty: 1,
|
||||||
|
price: 0,
|
||||||
|
originalPrice: 0,
|
||||||
|
discount: 0,
|
||||||
|
currSign: '$',
|
||||||
|
currSignLoc: 'left',
|
||||||
|
shippingCharge: 0,
|
||||||
|
imgUrl: '/mock/image.jpg',
|
||||||
|
options: [],
|
||||||
|
auctProdYn: 'N',
|
||||||
|
isValid: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const priceInfo = getSafeProductPrice(product);
|
||||||
|
|
||||||
|
return {
|
||||||
|
prdtId: product.prdtId || 'N/A',
|
||||||
|
prdtNm: getSafeProductName(product),
|
||||||
|
patnrId: product.patnrId || 'N/A',
|
||||||
|
patncNm: getSafePartnerName(product),
|
||||||
|
prodQty: getSafeQuantity(product),
|
||||||
|
price: priceInfo.price,
|
||||||
|
originalPrice: priceInfo.originalPrice,
|
||||||
|
discount: priceInfo.discount,
|
||||||
|
currSign: priceInfo.currSign,
|
||||||
|
currSignLoc: priceInfo.currSignLoc,
|
||||||
|
shippingCharge: getSafeShippingCharge(product),
|
||||||
|
imgUrl: getSafeImageUrl(product),
|
||||||
|
options: getSafeProductOptions(product),
|
||||||
|
auctProdYn: product.auctProdYn || 'N',
|
||||||
|
isValid: true,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 배열의 첫 번째 상품을 안전하게 추출
|
||||||
|
* @param {Array} productArray - 상품 배열
|
||||||
|
* @returns {Object} 첫 번째 상품 또는 빈 객체
|
||||||
|
*/
|
||||||
|
export const getSafeFirstProduct = (productArray) => {
|
||||||
|
if (Array.isArray(productArray) && productArray.length > 0) {
|
||||||
|
return productArray[0];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 통화 기호 정보 추출
|
||||||
|
* @param {Object} product - 상품 객체
|
||||||
|
* @returns {Object} { currSign, currSignLoc }
|
||||||
|
*/
|
||||||
|
export const getSafeCurrencyInfo = (product) => {
|
||||||
|
return {
|
||||||
|
currSign: product?.currSign || '$',
|
||||||
|
currSignLoc: product?.currSignLoc || 'left',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock Mode: productInfo에서 가격 정보를 추출하여 ORDER SUMMARY용 데이터 생성
|
||||||
|
* @param {Object} productInfo - 원본 productInfo 객체
|
||||||
|
* @returns {Object} ORDER SUMMARY용 가격 데이터
|
||||||
|
*/
|
||||||
|
export const calculateOrderSummaryFromProductInfo = (productInfo) => {
|
||||||
|
console.log('[calculateOrderSummaryFromProductInfo] Input productInfo:', productInfo);
|
||||||
|
|
||||||
|
if (!productInfo) {
|
||||||
|
console.log('[calculateOrderSummaryFromProductInfo] No productInfo, using defaults');
|
||||||
|
return {
|
||||||
|
items: 0,
|
||||||
|
couponSavings: 0,
|
||||||
|
shipping: 0,
|
||||||
|
subtotal: 0,
|
||||||
|
tax: 0,
|
||||||
|
total: 0,
|
||||||
|
currency: { currSign: '$', currSignLoc: 'left' }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Items (상품 가격) - price2 사용
|
||||||
|
const itemsPrice = parseFloat(
|
||||||
|
productInfo.price2?.replace(/[^\d.]/g, '') ||
|
||||||
|
productInfo.finalPrice ||
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. Your Coupon Savings (쿠폰 할인) - price5를 할인액으로 사용
|
||||||
|
const couponSavings = parseFloat(
|
||||||
|
productInfo.price5?.replace(/[^\d.]/g, '') ||
|
||||||
|
productInfo.discount ||
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. Shipping & Handling (배송비) - shippingCharge 사용
|
||||||
|
const shippingCharge = parseFloat(
|
||||||
|
productInfo.shippingCharge?.replace(/[^\d.]/g, '') ||
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4. Subtotal (세전 총계) = itemsPrice - couponSavings + shippingCharge
|
||||||
|
const subtotal = Math.max(0, itemsPrice - couponSavings + shippingCharge);
|
||||||
|
|
||||||
|
// 5. Tax (세금) = subtotal의 10%
|
||||||
|
const tax = Math.round((subtotal * 0.1) * 100) / 100;
|
||||||
|
|
||||||
|
// 6. Total (총계) = subtotal + tax
|
||||||
|
const total = Math.round((subtotal + tax) * 100) / 100;
|
||||||
|
|
||||||
|
// 통화 정보
|
||||||
|
const currency = {
|
||||||
|
currSign: productInfo.currSign || '$',
|
||||||
|
currSignLoc: productInfo.currSignLoc || 'left'
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
items: itemsPrice,
|
||||||
|
couponSavings: couponSavings,
|
||||||
|
shipping: shippingCharge,
|
||||||
|
subtotal: subtotal,
|
||||||
|
tax: tax,
|
||||||
|
total: total,
|
||||||
|
currency: currency
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('[calculateOrderSummaryFromProductInfo] Calculated result:', result);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
isProductDisplayable,
|
||||||
|
getSafeProductPrice,
|
||||||
|
getSafeProductName,
|
||||||
|
getSafePartnerName,
|
||||||
|
getSafeQuantity,
|
||||||
|
getSafeShippingCharge,
|
||||||
|
isDisabled,
|
||||||
|
getSafeProductOptions,
|
||||||
|
getSafeImageUrl,
|
||||||
|
isAuctionProduct,
|
||||||
|
normalizeProductDataForDisplay,
|
||||||
|
getSafeFirstProduct,
|
||||||
|
getSafeCurrencyInfo,
|
||||||
|
calculateOrderSummaryFromProductInfo,
|
||||||
|
};
|
||||||
@@ -30,6 +30,11 @@ import * as Config from '../../utils/Config';
|
|||||||
import { $L, scaleH, scaleW } from '../../utils/helperMethods';
|
import { $L, scaleH, scaleW } from '../../utils/helperMethods';
|
||||||
import { SpotlightIds } from '../../utils/SpotlightIds';
|
import { SpotlightIds } from '../../utils/SpotlightIds';
|
||||||
import { BUYNOW_CONFIG } from '../../utils/BuyNowConfig';
|
import { BUYNOW_CONFIG } from '../../utils/BuyNowConfig';
|
||||||
|
import {
|
||||||
|
normalizeProductDataForDisplay,
|
||||||
|
getSafeFirstProduct,
|
||||||
|
getSafeCurrencyInfo,
|
||||||
|
} from '../../utils/mockDataSafetyUtils';
|
||||||
import css from './CheckOutPanel.module.less';
|
import css from './CheckOutPanel.module.less';
|
||||||
import PinCodeInput from './components/PinCodeInput';
|
import PinCodeInput from './components/PinCodeInput';
|
||||||
import FixedSideBar from './container/FixedSideBar';
|
import FixedSideBar from './container/FixedSideBar';
|
||||||
@@ -57,15 +62,27 @@ export default function CheckOutPanel({ panelInfo }) {
|
|||||||
const { popupVisible, activePopup } = useSelector((state) => state.common.popup);
|
const { popupVisible, activePopup } = useSelector((state) => state.common.popup);
|
||||||
const popup = useSelector((state) => state.common.popup);
|
const popup = useSelector((state) => state.common.popup);
|
||||||
|
|
||||||
// Mock Mode: 가짜 상품 데이터 생성
|
// Mock Mode: panelInfo.mockProductInfo 또는 Redux에서 상품 데이터 사용
|
||||||
const productData = BUYNOW_CONFIG.isMockMode()
|
const productData = BUYNOW_CONFIG.isMockMode()
|
||||||
? reduxProductData || [
|
? (() => {
|
||||||
|
// 1순위: BuyOption에서 전달된 productInfo
|
||||||
|
if (panelInfo?.productInfo) {
|
||||||
|
console.log('[CheckOutPanel] Using panelInfo.productInfo:', panelInfo.productInfo);
|
||||||
|
return [panelInfo.productInfo];
|
||||||
|
}
|
||||||
|
// 2순위: Redux에서 가져온 상품 데이터
|
||||||
|
if (reduxProductData && reduxProductData.length > 0) {
|
||||||
|
console.log('[CheckOutPanel] Using reduxProductData:', reduxProductData);
|
||||||
|
return reduxProductData;
|
||||||
|
}
|
||||||
|
// 3순위: 기본 Hardcoded Mock 데이터
|
||||||
|
console.log('[CheckOutPanel] Using default hardcoded mock data');
|
||||||
|
return [
|
||||||
{
|
{
|
||||||
prdtId: 'MOCK_PRODUCT_1',
|
prdtId: 'MOCK_PRODUCT_1',
|
||||||
prdtNm: 'Mock Product for Demonstration',
|
prdtNm: 'Mock Product',
|
||||||
patnrId: '1',
|
patnrId: '1',
|
||||||
patncNm: 'Mock Partner',
|
patncNm: 'Partner',
|
||||||
prodSno: 'MOCK_123',
|
|
||||||
prodQty: 1,
|
prodQty: 1,
|
||||||
finalPrice: 99999,
|
finalPrice: 99999,
|
||||||
origPrice: 99999,
|
origPrice: 99999,
|
||||||
@@ -73,14 +90,30 @@ export default function CheckOutPanel({ panelInfo }) {
|
|||||||
currSign: '$',
|
currSign: '$',
|
||||||
currSignLoc: 'left',
|
currSignLoc: 'left',
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
|
})()
|
||||||
: reduxProductData;
|
: reduxProductData;
|
||||||
|
|
||||||
console.log('[CheckOutPanel] isMockMode:', BUYNOW_CONFIG.isMockMode());
|
console.log('[CheckOutPanel] isMockMode:', BUYNOW_CONFIG.isMockMode());
|
||||||
|
console.log('[CheckOutPanel] panelInfo:', panelInfo);
|
||||||
console.log('[CheckOutPanel] reduxProductData:', reduxProductData);
|
console.log('[CheckOutPanel] reduxProductData:', reduxProductData);
|
||||||
console.log('[CheckOutPanel] productData:', productData);
|
console.log('[CheckOutPanel] productData (raw):', productData);
|
||||||
|
console.log('[BuyOption][CheckOutPanel] 상품 정보:', productData);
|
||||||
|
|
||||||
const { currSign, currSignLoc } = productData?.[0] || {};
|
// 표시용으로 모든 상품 데이터 정규화 (없는 필드는 안전한 기본값으로)
|
||||||
|
// Mock 모드에서는 항상 정규화, API 모드에서는 그대로 사용
|
||||||
|
const normalizedProductData = productData?.map((prod) => normalizeProductDataForDisplay(prod)) || [];
|
||||||
|
const safeProductData = BUYNOW_CONFIG.isMockMode() ? normalizedProductData : productData;
|
||||||
|
|
||||||
|
console.log('[CheckOutPanel] productData (normalized):', normalizedProductData);
|
||||||
|
console.log('[CheckOutPanel] safeProductData (final):', safeProductData);
|
||||||
|
|
||||||
|
// 첫 번째 상품 정보로 통화 정보 추출
|
||||||
|
const firstProduct = getSafeFirstProduct(safeProductData);
|
||||||
|
const { currSign, currSignLoc } = firstProduct
|
||||||
|
? getSafeCurrencyInfo(firstProduct)
|
||||||
|
: { currSign: '$', currSignLoc: 'left' };
|
||||||
|
console.log('[CheckOutPanel] firstProduct:', firstProduct);
|
||||||
console.log('[CheckOutPanel] currSign:', currSign, 'currSignLoc:', currSignLoc);
|
console.log('[CheckOutPanel] currSign:', currSign, 'currSignLoc:', currSignLoc);
|
||||||
|
|
||||||
const [orderSideBarOpen, setOrderSideBarOpen] = useState(false);
|
const [orderSideBarOpen, setOrderSideBarOpen] = useState(false);
|
||||||
@@ -218,7 +251,12 @@ export default function CheckOutPanel({ panelInfo }) {
|
|||||||
console.log('[CheckOutPanel] cleanup useEffect - setting up cleanup');
|
console.log('[CheckOutPanel] cleanup useEffect - setting up cleanup');
|
||||||
return () => {
|
return () => {
|
||||||
console.log('[CheckOutPanel] cleanup useEffect - calling resetCheckoutData');
|
console.log('[CheckOutPanel] cleanup useEffect - calling resetCheckoutData');
|
||||||
|
// Mock 모드일 때는 데이터를 유지해야 다시 진입했을 때 올바른 상품 정보 로드 가능
|
||||||
|
if (!BUYNOW_CONFIG.isMockMode()) {
|
||||||
dispatch(resetCheckoutData());
|
dispatch(resetCheckoutData());
|
||||||
|
} else {
|
||||||
|
console.log('[CheckOutPanel] Mock Mode - Skipping resetCheckoutData to preserve product data');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
@@ -414,6 +452,9 @@ export default function CheckOutPanel({ panelInfo }) {
|
|||||||
currSign={currSign}
|
currSign={currSign}
|
||||||
currSignLoc={currSignLoc}
|
currSignLoc={currSignLoc}
|
||||||
doSendLogPaymentEntry={doSendLogPaymentEntry}
|
doSendLogPaymentEntry={doSendLogPaymentEntry}
|
||||||
|
productData={safeProductData}
|
||||||
|
productInfo={panelInfo?.productInfo}
|
||||||
|
defaultPrice={panelInfo?.defaultPrice}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<SummaryContainer
|
<SummaryContainer
|
||||||
@@ -456,7 +497,7 @@ export default function CheckOutPanel({ panelInfo }) {
|
|||||||
</TBody>
|
</TBody>
|
||||||
</TPanel>
|
</TPanel>
|
||||||
|
|
||||||
{orderSideBarOpen && <OrderItemsSideBar closeSideBar={toggleOrderSideBar} />}
|
{orderSideBarOpen && <OrderItemsSideBar closeSideBar={toggleOrderSideBar} productData={safeProductData} productInfo={panelInfo?.productInfo} />}
|
||||||
{offerSideBarOpen && (
|
{offerSideBarOpen && (
|
||||||
<FixedSideBar closeSideBar={toggleOfferSideBar} offerSideBarOpen={offerSideBarOpen} />
|
<FixedSideBar closeSideBar={toggleOfferSideBar} offerSideBarOpen={offerSideBarOpen} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -9,17 +9,23 @@ import TIConButton from '../../../components/TIconButton/TIconButton';
|
|||||||
import TVirtualGridList from '../../../components/TVirtualGridList/TVirtualGridList';
|
import TVirtualGridList from '../../../components/TVirtualGridList/TVirtualGridList';
|
||||||
import usePriceInfo from '../../../hooks/usePriceInfo';
|
import usePriceInfo from '../../../hooks/usePriceInfo';
|
||||||
import { $L } from '../../../utils/helperMethods';
|
import { $L } from '../../../utils/helperMethods';
|
||||||
|
import {
|
||||||
|
normalizeProductDataForDisplay,
|
||||||
|
getSafeFirstProduct,
|
||||||
|
} from '../../../utils/mockDataSafetyUtils';
|
||||||
import OrderItemCard, { SIZES } from '../components/OrderItemCard';
|
import OrderItemCard, { SIZES } from '../components/OrderItemCard';
|
||||||
import { BUYNOW_CONFIG } from '../../../utils/BuyNowConfig';
|
import { BUYNOW_CONFIG } from '../../../utils/BuyNowConfig';
|
||||||
import css from './OrderItemsSideBar.module.less';
|
import css from './OrderItemsSideBar.module.less';
|
||||||
|
|
||||||
const SideBarContainer = SpotlightContainerDecorator('div');
|
const SideBarContainer = SpotlightContainerDecorator('div');
|
||||||
|
|
||||||
export default function OrderItemsSideBar({ closeSideBar }) {
|
export default function OrderItemsSideBar({ closeSideBar, productData, productInfo }) {
|
||||||
console.log('[CheckOutPanel] OrderItemsSideBar mounted');
|
console.log('[CheckOutPanel] OrderItemsSideBar mounted');
|
||||||
|
|
||||||
const reduxOrderItemList = useSelector((state) => state.checkout?.checkoutData?.productList);
|
const reduxOrderItemList = useSelector((state) => state.checkout?.checkoutData?.productList);
|
||||||
console.log('[CheckOutPanel] OrderItemsSideBar reduxOrderItemList:', reduxOrderItemList);
|
console.log('[CheckOutPanel] OrderItemsSideBar reduxOrderItemList:', reduxOrderItemList);
|
||||||
|
console.log('[CheckOutPanel] OrderItemsSideBar productData:', productData);
|
||||||
|
console.log('[CheckOutPanel] OrderItemsSideBar productInfo:', productInfo);
|
||||||
|
|
||||||
// Check if reduxOrderItemList has actual data
|
// Check if reduxOrderItemList has actual data
|
||||||
const hasValidOrderItemList = Array.isArray(reduxOrderItemList) && reduxOrderItemList.length > 0;
|
const hasValidOrderItemList = Array.isArray(reduxOrderItemList) && reduxOrderItemList.length > 0;
|
||||||
@@ -28,8 +34,60 @@ export default function OrderItemsSideBar({ closeSideBar }) {
|
|||||||
const orderItemList = hasValidOrderItemList
|
const orderItemList = hasValidOrderItemList
|
||||||
? reduxOrderItemList
|
? reduxOrderItemList
|
||||||
: BUYNOW_CONFIG.isMockMode()
|
: BUYNOW_CONFIG.isMockMode()
|
||||||
? [
|
? (() => {
|
||||||
{
|
// Mock Mode: productInfo가 있으면 우선 사용
|
||||||
|
if (productInfo) {
|
||||||
|
console.log('[CheckOutPanel] OrderItemsSideBar Using productInfo for image');
|
||||||
|
const normalized = normalizeProductDataForDisplay(productInfo);
|
||||||
|
console.log('[CheckOutPanel] OrderItemsSideBar productInfo normalized imgUrl:', normalized.imgUrl);
|
||||||
|
|
||||||
|
return [{
|
||||||
|
prdtId: normalized.prdtId,
|
||||||
|
prdtNm: normalized.prdtNm,
|
||||||
|
prodQty: normalized.prodQty,
|
||||||
|
prdtOpt: normalized.options || [{ prodOptCdCval: 'DEFAULT_OPT', optNm: 'Default Option' }],
|
||||||
|
patncLogPath: normalized.imgUrl,
|
||||||
|
expsPrdtNo: normalized.prdtId,
|
||||||
|
currSign: normalized.currSign,
|
||||||
|
currSignLoc: normalized.currSignLoc,
|
||||||
|
shippingCharge: normalized.shippingCharge || 0,
|
||||||
|
auctProdYn: normalized.auctProdYn || 'N',
|
||||||
|
auctFinalPriceChgDt: null,
|
||||||
|
imgUrls: [{ imgUrl: normalized.imgUrl }], // productInfo에서 추출한 실제 이미지
|
||||||
|
// 표시용 추가 필드
|
||||||
|
price: normalized.price,
|
||||||
|
discount: normalized.discount,
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
// productInfo가 없으면 productData 사용
|
||||||
|
else if (productData && productData.length > 0) {
|
||||||
|
console.log('[CheckOutPanel] OrderItemsSideBar Using productData (fallback)');
|
||||||
|
return productData.map((prod) => {
|
||||||
|
const normalized = normalizeProductDataForDisplay(prod);
|
||||||
|
console.log('[CheckOutPanel] OrderItemsSideBar productData normalized imgUrl:', normalized.imgUrl);
|
||||||
|
return {
|
||||||
|
prdtId: normalized.prdtId,
|
||||||
|
prdtNm: normalized.prdtNm,
|
||||||
|
prodQty: normalized.prodQty,
|
||||||
|
prdtOpt: normalized.options || [{ prodOptCdCval: 'MOCK_OPT_1', optNm: 'Selected Option' }],
|
||||||
|
patncLogPath: normalized.imgUrl,
|
||||||
|
expsPrdtNo: normalized.prdtId,
|
||||||
|
currSign: normalized.currSign,
|
||||||
|
currSignLoc: normalized.currSignLoc,
|
||||||
|
shippingCharge: normalized.shippingCharge || 0,
|
||||||
|
auctProdYn: normalized.auctProdYn || 'N',
|
||||||
|
auctFinalPriceChgDt: null,
|
||||||
|
imgUrls: [{ imgUrl: normalized.imgUrl }], // 이미지 URL 추가
|
||||||
|
// 표시용 추가 필드
|
||||||
|
price: normalized.price,
|
||||||
|
discount: normalized.discount,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 둘 다 없으면 기본 Mock 데이터
|
||||||
|
else {
|
||||||
|
console.log('[CheckOutPanel] OrderItemsSideBar Using default mock data');
|
||||||
|
return [{
|
||||||
prdtId: 'MOCK_PRODUCT_1',
|
prdtId: 'MOCK_PRODUCT_1',
|
||||||
prdtNm: 'Mock Product',
|
prdtNm: 'Mock Product',
|
||||||
prodQty: 1,
|
prodQty: 1,
|
||||||
@@ -42,8 +100,9 @@ export default function OrderItemsSideBar({ closeSideBar }) {
|
|||||||
auctProdYn: 'N',
|
auctProdYn: 'N',
|
||||||
auctFinalPriceChgDt: null,
|
auctFinalPriceChgDt: null,
|
||||||
imgUrls: [{ imgUrl: '/mock/image.jpg' }],
|
imgUrls: [{ imgUrl: '/mock/image.jpg' }],
|
||||||
},
|
}];
|
||||||
]
|
}
|
||||||
|
})()
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
console.log('[CheckOutPanel] OrderItemsSideBar effectiveOrderItemList:', orderItemList);
|
console.log('[CheckOutPanel] OrderItemsSideBar effectiveOrderItemList:', orderItemList);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React, { useCallback, useEffect, useMemo } from 'react';
|
|||||||
import TButton from '../../../components/TButton/TButton';
|
import TButton from '../../../components/TButton/TButton';
|
||||||
import * as Config from '../../../utils/Config';
|
import * as Config from '../../../utils/Config';
|
||||||
import { $L, formatCurrencyValue } from '../../../utils/helperMethods';
|
import { $L, formatCurrencyValue } from '../../../utils/helperMethods';
|
||||||
|
import { getSafeFirstProduct, calculateOrderSummaryFromProductInfo } from '../../../utils/mockDataSafetyUtils';
|
||||||
import css from './SummaryContainer.module.less';
|
import css from './SummaryContainer.module.less';
|
||||||
|
|
||||||
export default function SummaryContainerMock({
|
export default function SummaryContainerMock({
|
||||||
@@ -12,20 +13,53 @@ export default function SummaryContainerMock({
|
|||||||
currSign,
|
currSign,
|
||||||
currSignLoc,
|
currSignLoc,
|
||||||
doSendLogPaymentEntry,
|
doSendLogPaymentEntry,
|
||||||
|
productData,
|
||||||
|
productInfo,
|
||||||
|
defaultPrice,
|
||||||
}) {
|
}) {
|
||||||
console.log('[CheckOutPanel] SummaryContainerMock - START render');
|
console.log('[CheckOutPanel] SummaryContainerMock - START render');
|
||||||
console.log('[CheckOutPanel] SummaryContainerMock - empTermsData:', empTermsData);
|
console.log('[CheckOutPanel] SummaryContainerMock - empTermsData:', empTermsData);
|
||||||
console.log('[CheckOutPanel] SummaryContainerMock - currSign:', currSign);
|
console.log('[CheckOutPanel] SummaryContainerMock - currSign:', currSign);
|
||||||
|
console.log('[CheckOutPanel] SummaryContainerMock - productData:', productData);
|
||||||
|
console.log('[CheckOutPanel] SummaryContainerMock - productInfo:', productInfo);
|
||||||
|
|
||||||
// Mock Mode: 하드코딩된 가격 데이터
|
// Mock Mode: productInfo로부터 ORDER SUMMARY용 가격 데이터 계산
|
||||||
const effectivePriceTotalData = {
|
const orderSummaryData = useMemo(() => {
|
||||||
totProdPrc: 521.66,
|
if (productInfo) {
|
||||||
totDcAmt: 0,
|
// productInfo가 있으면 직접 계산
|
||||||
totDlvrAmt: 0,
|
return calculateOrderSummaryFromProductInfo(productInfo);
|
||||||
ordPmtNoTaxAmt: 521.66,
|
} else {
|
||||||
ordTotTaxAmt: 50,
|
// productInfo가 없으면 기존 방식으로 fallback
|
||||||
ordPmtReqAmt: 571.66,
|
const selectedProduct = getSafeFirstProduct(productData);
|
||||||
|
const productPrice = parseFloat(defaultPrice || selectedProduct?.price || 0);
|
||||||
|
const productDiscount = parseFloat(selectedProduct?.discount || 0);
|
||||||
|
const tax = Math.round((productPrice * 0.1) * 100) / 100;
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: productPrice,
|
||||||
|
couponSavings: productDiscount,
|
||||||
|
shipping: 0,
|
||||||
|
subtotal: Math.max(0, productPrice - productDiscount),
|
||||||
|
tax: tax,
|
||||||
|
total: Math.max(0, productPrice - productDiscount) + tax,
|
||||||
|
currency: { currSign, currSignLoc }
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
}, [productInfo, productData, defaultPrice, currSign, currSignLoc]);
|
||||||
|
|
||||||
|
console.log('[CheckOutPanel] SummaryContainerMock - orderSummaryData:', orderSummaryData);
|
||||||
|
|
||||||
|
// 기존 호환성을 위해 effectivePriceTotalData 유지
|
||||||
|
const effectivePriceTotalData = {
|
||||||
|
totProdPrc: orderSummaryData.items,
|
||||||
|
totDcAmt: orderSummaryData.couponSavings,
|
||||||
|
totDlvrAmt: orderSummaryData.shipping,
|
||||||
|
ordPmtNoTaxAmt: orderSummaryData.subtotal,
|
||||||
|
ordTotTaxAmt: orderSummaryData.tax,
|
||||||
|
ordPmtReqAmt: orderSummaryData.total,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('[CheckOutPanel] SummaryContainerMock - effectivePriceTotalData:', effectivePriceTotalData);
|
||||||
|
|
||||||
// Mock Mode: 기본 상품 정보
|
// Mock Mode: 기본 상품 정보
|
||||||
const productList = {
|
const productList = {
|
||||||
@@ -37,38 +71,38 @@ export default function SummaryContainerMock({
|
|||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
name: 'Items',
|
name: 'Items',
|
||||||
value: formatCurrencyValue(effectivePriceTotalData.totProdPrc, currSign, currSignLoc),
|
value: formatCurrencyValue(effectivePriceTotalData.totProdPrc, orderSummaryData.currency.currSign, orderSummaryData.currency.currSignLoc),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Your Coupon Savings',
|
name: 'Your Coupon Savings',
|
||||||
value: effectivePriceTotalData.totDcAmt
|
value: effectivePriceTotalData.totDcAmt
|
||||||
? formatCurrencyValue(effectivePriceTotalData.totDcAmt, currSign, currSignLoc, true)
|
? formatCurrencyValue(effectivePriceTotalData.totDcAmt, orderSummaryData.currency.currSign, orderSummaryData.currency.currSignLoc, true)
|
||||||
: '-',
|
: '-',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Shipping & Handling',
|
name: 'Shipping & Handling',
|
||||||
value: formatCurrencyValue(effectivePriceTotalData.totDlvrAmt, currSign, currSignLoc),
|
value: formatCurrencyValue(effectivePriceTotalData.totDlvrAmt, orderSummaryData.currency.currSign, orderSummaryData.currency.currSignLoc),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'TOTAL (before Tax)',
|
name: 'TOTAL (before Tax)',
|
||||||
value: formatCurrencyValue(effectivePriceTotalData.ordPmtNoTaxAmt, currSign, currSignLoc),
|
value: formatCurrencyValue(effectivePriceTotalData.ordPmtNoTaxAmt, orderSummaryData.currency.currSign, orderSummaryData.currency.currSignLoc),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Estimated Sales Tax',
|
name: 'Estimated Sales Tax',
|
||||||
value: formatCurrencyValue(effectivePriceTotalData.ordTotTaxAmt, currSign, currSignLoc),
|
value: formatCurrencyValue(effectivePriceTotalData.ordTotTaxAmt, orderSummaryData.currency.currSign, orderSummaryData.currency.currSignLoc),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[effectivePriceTotalData, currSign, currSignLoc]
|
[effectivePriceTotalData, orderSummaryData.currency]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleClickOrder = useCallback(() => {
|
const handleClickOrder = useCallback(() => {
|
||||||
console.log('[SummaryContainerMock] Place order clicked');
|
console.log('[SummaryContainerMock] Place order clicked');
|
||||||
if (doSendLogPaymentEntry) {
|
|
||||||
doSendLogPaymentEntry();
|
doSendLogPaymentEntry();
|
||||||
}
|
|
||||||
setPlaceOrderPopup(true);
|
setPlaceOrderPopup(true);
|
||||||
}, [doSendLogPaymentEntry, setPlaceOrderPopup]);
|
}, [doSendLogPaymentEntry, setPlaceOrderPopup]);
|
||||||
|
|
||||||
|
console.log('[CheckOutPanel] SummaryContainerMock - items:', items);
|
||||||
|
|
||||||
const renderItemList = useCallback(
|
const renderItemList = useCallback(
|
||||||
() =>
|
() =>
|
||||||
items.map((item, index) => (
|
items.map((item, index) => (
|
||||||
@@ -100,8 +134,8 @@ export default function SummaryContainerMock({
|
|||||||
|
|
||||||
const estimatedTotal = useMemo(() => {
|
const estimatedTotal = useMemo(() => {
|
||||||
console.log('[CheckOutPanel] SummaryContainerMock - estimatedTotal useMemo');
|
console.log('[CheckOutPanel] SummaryContainerMock - estimatedTotal useMemo');
|
||||||
return formatCurrencyValue(effectivePriceTotalData.ordPmtReqAmt, currSign, currSignLoc);
|
return formatCurrencyValue(effectivePriceTotalData.ordPmtReqAmt, orderSummaryData.currency.currSign, orderSummaryData.currency.currSignLoc);
|
||||||
}, [effectivePriceTotalData, currSign, currSignLoc]);
|
}, [effectivePriceTotalData, orderSummaryData.currency]);
|
||||||
|
|
||||||
const showAuctionNotice = productList?.auctProdYn === 'Y' && !productList.auctFinalPriceChgDt;
|
const showAuctionNotice = productList?.auctProdYn === 'Y' && !productList.auctFinalPriceChgDt;
|
||||||
|
|
||||||
|
|||||||
@@ -533,6 +533,25 @@
|
|||||||
// 배경색과 라운드는 FavoriteBtn 내부에서 처리하므로 제거
|
// 배경색과 라운드는 FavoriteBtn 내부에서 처리하므로 제거
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FavoriteBtn 컴포넌트에 적용할 스타일
|
||||||
|
.favoriteBtn {
|
||||||
|
width: 60px !important;
|
||||||
|
height: 60px !important;
|
||||||
|
background: rgba(68, 68, 68, 0.5) !important;
|
||||||
|
border-radius: 6px !important;
|
||||||
|
border: none !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background: @PRIMARY_COLOR_RED !important;
|
||||||
|
// outline은 사용하지 않음
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 액션 버튼들 (actionButtons 참고)
|
// 액션 버튼들 (actionButtons 참고)
|
||||||
.actionButtonsWrapper {
|
.actionButtonsWrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -110,23 +110,30 @@ const BuyOption = ({
|
|||||||
|
|
||||||
// Mock Mode에서 옵션 데이터 처리
|
// Mock Mode에서 옵션 데이터 처리
|
||||||
const productOptionInfos = useMemo(() => {
|
const productOptionInfos = useMemo(() => {
|
||||||
|
console.log('[BuyOption] productOptionInfos useMemo - isMockMode:', BUYNOW_CONFIG.isMockMode());
|
||||||
|
console.log('[BuyOption] productOptionInfos useMemo - reduxProductOptionInfos:', reduxProductOptionInfos);
|
||||||
|
|
||||||
// API Mode: 기존 로직 100% 유지
|
// API Mode: 기존 로직 100% 유지
|
||||||
if (!BUYNOW_CONFIG.isMockMode()) {
|
if (!BUYNOW_CONFIG.isMockMode()) {
|
||||||
|
console.log('[BuyOption] API Mode - using reduxProductOptionInfos');
|
||||||
return reduxProductOptionInfos;
|
return reduxProductOptionInfos;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock Mode: 옵션 데이터가 없으면 Mock 데이터 생성
|
// Mock Mode: 옵션 데이터가 없거나 배열이 아니면 Mock 데이터 생성
|
||||||
if (!reduxProductOptionInfos || reduxProductOptionInfos.length === 0) {
|
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);
|
const mockOptionData = createMockProductOptionData(productData);
|
||||||
// Mock 옵션 데이터 배열 반환 (기존 구조와 호환)
|
console.log('[BuyOption] Mock Mode - createMockProductOptionData result:', mockOptionData);
|
||||||
return mockOptionData?.optionList || [];
|
// Mock 옵션 데이터는 이미 배열 구조로 반환됨
|
||||||
|
return mockOptionData || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock Mode이고 옵션 데이터가 있으면 그대로 사용
|
// Mock Mode이고 유효한 옵션 데이터가 있으면 그대로 사용
|
||||||
|
console.log('[BuyOption] Mock Mode - using existing valid reduxProductOptionInfos');
|
||||||
return reduxProductOptionInfos;
|
return reduxProductOptionInfos;
|
||||||
}, [reduxProductOptionInfos, productData]);
|
}, [reduxProductOptionInfos, productData]); // logInfo 생성 (SingleOption과 동일한 로직, productData 우선 사용)
|
||||||
|
|
||||||
// logInfo 생성 (SingleOption과 동일한 로직, productData 우선 사용)
|
|
||||||
const logInfo = useMemo(() => {
|
const logInfo = useMemo(() => {
|
||||||
if (productData) {
|
if (productData) {
|
||||||
// productData가 있으면 SingleOption과 동일하게 처리
|
// productData가 있으면 SingleOption과 동일하게 처리
|
||||||
@@ -222,17 +229,50 @@ const BuyOption = ({
|
|||||||
}, [selectedIndex, productOptionInfos, type]);
|
}, [selectedIndex, productOptionInfos, type]);
|
||||||
|
|
||||||
// 옵션 자동 선택 로직 (SingleOption과 동일)
|
// 옵션 자동 선택 로직 (SingleOption과 동일)
|
||||||
|
// Mock Mode: 항상 첫 번째 옵션을 자동으로 선택
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
console.log('[BuyOption] autoSelect useEffect - productOptionInfos:', productOptionInfos);
|
||||||
productOptionInfos &&
|
console.log('[BuyOption] autoSelect useEffect - selectedBtnOptIdx:', selectedBtnOptIdx);
|
||||||
selectedBtnOptIdx >= 0 &&
|
console.log('[BuyOption] autoSelect useEffect - isOptionValue:', isOptionValue);
|
||||||
productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl.length === 1 &&
|
|
||||||
!isOptionValue
|
if (!productOptionInfos || productOptionInfos.length === 0) {
|
||||||
) {
|
console.log('[BuyOption] autoSelect - productOptionInfos is empty, returning');
|
||||||
setSelectedOptions(productOptionInfos[selectedBtnOptIdx]?.prdtOptDtl[0]);
|
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);
|
setIsOptionValue(true);
|
||||||
}
|
}
|
||||||
}, [productOptionInfos, selectedBtnOptIdx, isOptionValue]);
|
// 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과 동일)
|
// 필수 데이터 로드 (SingleOption과 동일)
|
||||||
// Mock Mode: API 호출 스킵 (Mock 데이터만 사용)
|
// Mock Mode: API 호출 스킵 (Mock 데이터만 사용)
|
||||||
@@ -408,10 +448,27 @@ const BuyOption = ({
|
|||||||
if (userNumber && selectedPatnrId && selectedPrdtId && quantity) {
|
if (userNumber && selectedPatnrId && selectedPrdtId && quantity) {
|
||||||
const { prodOptCval, priceInfo } = selectedOptions || {};
|
const { prodOptCval, priceInfo } = selectedOptions || {};
|
||||||
const { patncNm, brndNm, catNm, prdtNm, prdtId } = productInfo;
|
const { patncNm, brndNm, catNm, prdtNm, prdtId } = productInfo;
|
||||||
const regularPrice = priceInfo?.split('|')[0];
|
console.log('[BuyOption] handleClickOrder - selectedOptions:', selectedOptions);
|
||||||
const discountPrice = priceInfo?.split('|')[1];
|
console.log('[BuyOption] handleClickOrder - priceInfo:', priceInfo);
|
||||||
|
console.log('[BuyOption] handleClickOrder - logInfo:', logInfo);
|
||||||
|
|
||||||
|
// priceInfo 파싱 및 숫자 변환
|
||||||
|
let regularPrice = parseInt(priceInfo?.split('|')[0], 10) || 0;
|
||||||
|
let discountPrice = parseInt(priceInfo?.split('|')[1], 10) || 0;
|
||||||
const discountRate = priceInfo?.split('|')[4];
|
const discountRate = priceInfo?.split('|')[4];
|
||||||
|
|
||||||
|
// selectedOptions가 없으면 logInfo에서 가격 추출
|
||||||
|
if (!selectedOptions && logInfo) {
|
||||||
|
console.log('[BuyOption] handleClickOrder - selectedOptions is undefined, using logInfo prices');
|
||||||
|
// logInfo의 dcBefPrc와 dcAftrPrc는 "$ 521.66" 형식이므로 숫자만 추출 (소수점 포함)
|
||||||
|
const dcBefPrcMatch = logInfo.dcBefPrc?.match(/[\d.]+/);
|
||||||
|
const dcAftrPrcMatch = logInfo.dcAftrPrc?.match(/[\d.]+/);
|
||||||
|
regularPrice = dcBefPrcMatch ? parseFloat(dcBefPrcMatch[0]) : 0;
|
||||||
|
discountPrice = dcAftrPrcMatch ? parseFloat(dcAftrPrcMatch[0]) : regularPrice;
|
||||||
|
console.log('[BuyOption] handleClickOrder - extracted from logInfo - dcBefPrc:', logInfo.dcBefPrc, 'dcAftrPrc:', logInfo.dcAftrPrc);
|
||||||
|
}
|
||||||
|
console.log('[BuyOption] handleClickOrder - regularPrice:', regularPrice, 'discountPrice:', discountPrice, 'discountRate:', discountRate);
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
sendLogTotalRecommend({
|
sendLogTotalRecommend({
|
||||||
nowMenu: nowMenu,
|
nowMenu: nowMenu,
|
||||||
@@ -452,13 +509,44 @@ const BuyOption = ({
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Mock Mode: 체크아웃 페이지로 이동 (시뮬레이션)
|
// Mock Mode: 체크아웃 페이지로 이동 (시뮬레이션)
|
||||||
|
// panelInfo의 logInfo에 선택한 상품 정보를 포함시켜 CheckOutPanel에서 사용
|
||||||
console.log('[BuyOption] Mock Mode - Simulating checkout');
|
console.log('[BuyOption] Mock Mode - Simulating checkout');
|
||||||
console.log('[BuyOption] logInfo:', logInfo);
|
console.log('[BuyOption] logInfo:', logInfo);
|
||||||
console.log('[BuyOption] Dispatching pushPanel to CHECKOUT_PANEL');
|
console.log('[BuyOption] Dispatching pushPanel to CHECKOUT_PANEL');
|
||||||
dispatch(finishVideoPreview());
|
dispatch(finishVideoPreview());
|
||||||
dispatch(finishMediaPreview());
|
dispatch(finishMediaPreview());
|
||||||
const checkoutPanelInfo = { logInfo: { ...logInfo, cartTpSno: `MOCK_${Date.now()}` } };
|
|
||||||
console.log('[BuyOption] checkoutPanelInfo:', checkoutPanelInfo);
|
// Mock 모드: 선택 상품의 정보를 panelInfo에 담아서 전달
|
||||||
|
// CheckOutPanel에서 이 정보로 Mock 상품 데이터 생성
|
||||||
|
// 이미지 URL 추출 (productInfo의 imgList 또는 thumbnailUrl 사용)
|
||||||
|
const imgUrl = productInfo?.imgList?.[0]?.imgUrl ||
|
||||||
|
productInfo?.thumbnailUrl ||
|
||||||
|
productInfo?.patncLogPath ||
|
||||||
|
'/mock/image.jpg';
|
||||||
|
|
||||||
|
const mockProductInfo = {
|
||||||
|
prdtId: selectedPrdtId,
|
||||||
|
prdtNm: productInfo?.prdtNm,
|
||||||
|
patnrId: selectedPatnrId,
|
||||||
|
patncNm: patncNm,
|
||||||
|
prodQty: quantity,
|
||||||
|
origPrice: regularPrice || 99999, // 원래 가격 (숫자)
|
||||||
|
discountPrice: discountPrice || regularPrice || 99999, // 할인된 가격 (실제 판매 가격, 숫자)
|
||||||
|
finalPrice: discountPrice || regularPrice || 99999, // 최종 가격 (숫자)
|
||||||
|
currSign: '$',
|
||||||
|
currSignLoc: 'left',
|
||||||
|
imgUrl: imgUrl, // 상품 이미지 URL 추가
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkoutPanelInfo = {
|
||||||
|
logInfo: { ...logInfo, cartTpSno: `MOCK_${Date.now()}` },
|
||||||
|
productInfo: productInfo,
|
||||||
|
defaultPrice: discountPrice,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('[BuyOption] Mock Mode - mockProductInfo:', mockProductInfo);
|
||||||
|
console.log('[BuyOption] Mock Mode - regularPrice(숫자):', regularPrice, 'discountPrice(숫자):', discountPrice);
|
||||||
|
console.log('[BuyOption] Mock Mode - checkoutPanelInfo:', checkoutPanelInfo);
|
||||||
dispatch(
|
dispatch(
|
||||||
pushPanel({
|
pushPanel({
|
||||||
name: Config.panel_names.CHECKOUT_PANEL,
|
name: Config.panel_names.CHECKOUT_PANEL,
|
||||||
@@ -673,6 +761,11 @@ const BuyOption = ({
|
|||||||
setFavoriteFlag(ev);
|
setFavoriteFlag(ev);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// 구매창이 뜰 때 콘솔 로그 출력 (상품 정보 포함 태그 [BuyOption] 붙임)
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('[BuyOption]', '상품 정보:', JSON.stringify(productInfo));
|
||||||
|
}, []); // 컴포넌트 마운트 시 한 번만 출력
|
||||||
|
|
||||||
// hasOnClose 로직 (SingleOption과 동일)
|
// hasOnClose 로직 (SingleOption과 동일)
|
||||||
const hasOnClose = useMemo(() => {
|
const hasOnClose = useMemo(() => {
|
||||||
if (productOptionInfos && productOptionInfos.length > 0) {
|
if (productOptionInfos && productOptionInfos.length > 0) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
} from 'react-redux';
|
} from 'react-redux';
|
||||||
|
|
||||||
import Spotlight from '@enact/spotlight';
|
import Spotlight from '@enact/spotlight';
|
||||||
|
import Spottable from '@enact/spotlight/Spottable';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
setHidePopup,
|
setHidePopup,
|
||||||
@@ -28,6 +29,8 @@ import * as Config from '../../../utils/Config';
|
|||||||
import { $L } from '../../../utils/helperMethods';
|
import { $L } from '../../../utils/helperMethods';
|
||||||
import css from './FavoriteBtn.module.less';
|
import css from './FavoriteBtn.module.less';
|
||||||
|
|
||||||
|
const SpottableDiv = Spottable('div');
|
||||||
|
|
||||||
export default function FavoriteBtn({
|
export default function FavoriteBtn({
|
||||||
selectedPatnrId,
|
selectedPatnrId,
|
||||||
selectedPrdtId,
|
selectedPrdtId,
|
||||||
@@ -80,7 +83,7 @@ export default function FavoriteBtn({
|
|||||||
}, [dispatch, favoriteFlag, onFavoriteFlagChanged]);
|
}, [dispatch, favoriteFlag, onFavoriteFlagChanged]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<SpottableDiv
|
||||||
className={classNames(
|
className={classNames(
|
||||||
css.favorBtnContainer,
|
css.favorBtnContainer,
|
||||||
kind === "item_detail" ? css.smallSize : ""
|
kind === "item_detail" ? css.smallSize : ""
|
||||||
@@ -110,6 +113,6 @@ export default function FavoriteBtn({
|
|||||||
onClick={PopUpOnClick}
|
onClick={PopUpOnClick}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</SpottableDiv>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,7 @@
|
|||||||
.favorUnableBtn {
|
.favorUnableBtn {
|
||||||
min-width: 60px;
|
min-width: 60px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
background-image: url(../../../../assets/images/icons/ic-heart-nor@3x.png);
|
background-color: rgba(68, 68, 68, 0.5); // 다른 버튼들과 동일한 배경색
|
||||||
background-color: #4f172c; // 색상 추가.
|
|
||||||
.imgElement(54px, 54px, center, center);
|
.imgElement(54px, 54px, center, center);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,12 @@ const DEBUG_MODE = true;
|
|||||||
class ErrorBoundary extends Component {
|
class ErrorBoundary extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = { hasError: false };
|
this.state = {
|
||||||
|
hasError: false,
|
||||||
|
error: null,
|
||||||
|
errorInfo: null,
|
||||||
|
errorTimestamp: null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromError(error) {
|
static getDerivedStateFromError(error) {
|
||||||
@@ -18,11 +23,43 @@ class ErrorBoundary extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidCatch(error, errorInfo) {
|
componentDidCatch(error, errorInfo) {
|
||||||
console.error("Uncaught error:", error, errorInfo);
|
const errorTimestamp = new Date().toLocaleString();
|
||||||
|
|
||||||
|
console.error("❌ Uncaught error:", error);
|
||||||
|
console.error("📋 Error Info:", errorInfo);
|
||||||
|
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
// Development mode: log error details instead of reloading
|
// Development mode: log detailed error information
|
||||||
console.error("Error Stack:", error.stack);
|
console.error("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
||||||
console.error("Component Stack:", errorInfo.componentStack);
|
console.error("🔴 ERROR BOUNDARY CAUGHT AN ERROR");
|
||||||
|
console.error("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
||||||
|
|
||||||
|
// Error message and type
|
||||||
|
console.error("⚠️ Error Type:", error.name);
|
||||||
|
console.error("📝 Error Message:", error.message);
|
||||||
|
|
||||||
|
// Error stack trace
|
||||||
|
console.error("\n📍 Stack Trace:");
|
||||||
|
console.error(error.stack);
|
||||||
|
|
||||||
|
// Component stack
|
||||||
|
console.error("\n🏗️ Component Stack:");
|
||||||
|
console.error(errorInfo.componentStack);
|
||||||
|
|
||||||
|
// Additional error details
|
||||||
|
console.error("\n📊 Error Details:");
|
||||||
|
console.error("- Time:", errorTimestamp);
|
||||||
|
console.error("- Type:", typeof error);
|
||||||
|
console.error("- Error Object:", error);
|
||||||
|
|
||||||
|
console.error("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
||||||
|
|
||||||
|
// Store error info in state for UI display
|
||||||
|
this.setState({
|
||||||
|
error,
|
||||||
|
errorInfo,
|
||||||
|
errorTimestamp
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
@@ -37,33 +74,222 @@ class ErrorBoundary extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.hasError) {
|
if (this.state.hasError) {
|
||||||
// In DEBUG_MODE, show error UI; otherwise show empty div and reload
|
// In DEBUG_MODE, show detailed error UI; otherwise show empty div and reload
|
||||||
if (DEBUG_MODE) {
|
if (DEBUG_MODE) {
|
||||||
|
const { error, errorInfo, errorTimestamp } = this.state;
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
padding: '20px',
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
width: '100vw',
|
||||||
|
height: '100vh',
|
||||||
backgroundColor: '#f8d7da',
|
backgroundColor: '#f8d7da',
|
||||||
color: '#721c24',
|
color: '#721c24',
|
||||||
borderRadius: '4px',
|
overflow: 'hidden',
|
||||||
margin: '20px',
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
zIndex: 999999,
|
||||||
|
pointerEvents: 'auto'
|
||||||
|
}}>
|
||||||
|
{/* Header - Fixed */}
|
||||||
|
<div style={{
|
||||||
|
flexShrink: 0,
|
||||||
|
padding: '30px',
|
||||||
|
borderBottom: '4px solid #721c24',
|
||||||
|
backgroundColor: '#f8d7da',
|
||||||
|
pointerEvents: 'auto'
|
||||||
|
}}>
|
||||||
|
<h1 style={{ margin: '0', fontSize: '48px', pointerEvents: 'auto' }}>
|
||||||
|
🚨 ERROR BOUNDARY - DEBUG MODE
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content - Scrollable */}
|
||||||
|
<div style={{
|
||||||
|
flex: 1,
|
||||||
|
overflow: 'auto',
|
||||||
|
padding: '30px',
|
||||||
|
fontSize: '24px',
|
||||||
|
lineHeight: '1.8',
|
||||||
fontFamily: 'monospace',
|
fontFamily: 'monospace',
|
||||||
whiteSpace: 'pre-wrap',
|
whiteSpace: 'pre-wrap',
|
||||||
overflow: 'auto'
|
userSelect: 'text',
|
||||||
|
WebkitUserSelect: 'text',
|
||||||
|
MozUserSelect: 'text',
|
||||||
|
msUserSelect: 'text',
|
||||||
|
pointerEvents: 'auto'
|
||||||
}}>
|
}}>
|
||||||
<h2>⚠️ Error Caught by ErrorBoundary (DEBUG_MODE)</h2>
|
{errorTimestamp && (
|
||||||
<p>Check browser console for detailed error information.</p>
|
<div style={{
|
||||||
<button onClick={() => window.location.reload()} style={{
|
marginBottom: '25px',
|
||||||
padding: '10px 20px',
|
backgroundColor: 'rgba(0,0,0,0.15)',
|
||||||
|
padding: '20px',
|
||||||
|
borderRadius: '6px',
|
||||||
|
fontSize: '24px',
|
||||||
|
userSelect: 'text',
|
||||||
|
WebkitUserSelect: 'text',
|
||||||
|
pointerEvents: 'auto'
|
||||||
|
}}>
|
||||||
|
<strong>⏰ Timestamp:</strong>
|
||||||
|
<div style={{ marginTop: '10px', fontSize: '22px', userSelect: 'text', pointerEvents: 'auto' }}>{errorTimestamp}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<>
|
||||||
|
<div style={{
|
||||||
|
marginBottom: '25px',
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.15)',
|
||||||
|
padding: '20px',
|
||||||
|
borderRadius: '6px',
|
||||||
|
fontSize: '24px',
|
||||||
|
userSelect: 'text',
|
||||||
|
WebkitUserSelect: 'text',
|
||||||
|
pointerEvents: 'auto'
|
||||||
|
}}>
|
||||||
|
<strong>❌ Error Type:</strong>
|
||||||
|
<div style={{ marginTop: '10px', fontSize: '22px', userSelect: 'text', pointerEvents: 'auto' }}>{error.name}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
marginBottom: '25px',
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.15)',
|
||||||
|
padding: '20px',
|
||||||
|
borderRadius: '6px',
|
||||||
|
userSelect: 'text',
|
||||||
|
WebkitUserSelect: 'text',
|
||||||
|
pointerEvents: 'auto'
|
||||||
|
}}>
|
||||||
|
<strong style={{ fontSize: '24px', pointerEvents: 'auto' }}>📝 Error Message:</strong>
|
||||||
|
<div style={{
|
||||||
marginTop: '10px',
|
marginTop: '10px',
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
fontSize: '22px',
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
padding: '15px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
color: '#d9534f',
|
||||||
|
userSelect: 'text',
|
||||||
|
WebkitUserSelect: 'text',
|
||||||
|
cursor: 'text',
|
||||||
|
pointerEvents: 'auto'
|
||||||
|
}}>
|
||||||
|
{error.message}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && error.stack && (
|
||||||
|
<div style={{
|
||||||
|
marginBottom: '25px',
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.15)',
|
||||||
|
padding: '20px',
|
||||||
|
borderRadius: '6px',
|
||||||
|
userSelect: 'text',
|
||||||
|
WebkitUserSelect: 'text',
|
||||||
|
pointerEvents: 'auto'
|
||||||
|
}}>
|
||||||
|
<strong style={{ fontSize: '24px', pointerEvents: 'auto' }}>📍 Stack Trace:</strong>
|
||||||
|
<pre style={{
|
||||||
|
margin: '15px 0 0 0',
|
||||||
|
overflow: 'auto',
|
||||||
|
maxHeight: '300px',
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
padding: '20px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontSize: '20px',
|
||||||
|
lineHeight: '1.6',
|
||||||
|
border: '2px solid #721c24',
|
||||||
|
userSelect: 'text',
|
||||||
|
WebkitUserSelect: 'text',
|
||||||
|
cursor: 'text',
|
||||||
|
pointerEvents: 'auto'
|
||||||
|
}}>
|
||||||
|
{error.stack}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{errorInfo && errorInfo.componentStack && (
|
||||||
|
<div style={{
|
||||||
|
marginBottom: '25px',
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.15)',
|
||||||
|
padding: '20px',
|
||||||
|
borderRadius: '6px',
|
||||||
|
userSelect: 'text',
|
||||||
|
WebkitUserSelect: 'text',
|
||||||
|
pointerEvents: 'auto'
|
||||||
|
}}>
|
||||||
|
<strong style={{ fontSize: '24px', pointerEvents: 'auto' }}>🏗️ Component Stack:</strong>
|
||||||
|
<pre style={{
|
||||||
|
margin: '15px 0 0 0',
|
||||||
|
overflow: 'auto',
|
||||||
|
maxHeight: '300px',
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
padding: '20px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontSize: '20px',
|
||||||
|
lineHeight: '1.6',
|
||||||
|
border: '2px solid #721c24',
|
||||||
|
userSelect: 'text',
|
||||||
|
WebkitUserSelect: 'text',
|
||||||
|
cursor: 'text',
|
||||||
|
pointerEvents: 'auto'
|
||||||
|
}}>
|
||||||
|
{errorInfo.componentStack}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
marginTop: '30px',
|
||||||
|
paddingTop: '30px',
|
||||||
|
borderTop: '3px solid #721c24',
|
||||||
|
fontSize: '20px',
|
||||||
|
userSelect: 'text',
|
||||||
|
WebkitUserSelect: 'text',
|
||||||
|
pointerEvents: 'auto'
|
||||||
|
}}>
|
||||||
|
<p style={{ marginBottom: '20px', pointerEvents: 'auto' }}>
|
||||||
|
ℹ️ Check browser DevTools Console (F12) for additional error details and network information
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Footer - Fixed */}
|
||||||
|
<div style={{
|
||||||
|
flexShrink: 0,
|
||||||
|
padding: '30px',
|
||||||
|
borderTop: '4px solid #721c24',
|
||||||
|
backgroundColor: '#f8d7da',
|
||||||
|
textAlign: 'center',
|
||||||
|
pointerEvents: 'auto'
|
||||||
|
}}>
|
||||||
|
<button
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
style={{
|
||||||
|
padding: '20px 40px',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
backgroundColor: '#721c24',
|
backgroundColor: '#721c24',
|
||||||
color: 'white',
|
color: 'white',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
borderRadius: '4px'
|
borderRadius: '8px',
|
||||||
}}>
|
fontSize: '24px',
|
||||||
Reload Page
|
fontWeight: 'bold',
|
||||||
|
transition: 'background-color 0.3s',
|
||||||
|
boxShadow: '0 4px 8px rgba(0,0,0,0.2)',
|
||||||
|
pointerEvents: 'auto'
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => e.target.style.backgroundColor = '#5a1620'}
|
||||||
|
onMouseLeave={(e) => e.target.style.backgroundColor = '#721c24'}
|
||||||
|
>
|
||||||
|
🔄 Reload Page
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <div></div>;
|
return <div></div>;
|
||||||
|
|||||||
Reference in New Issue
Block a user