[250930] feat: cart관련 redux추가 , BuyOption 포커스 수정
This commit is contained in:
@@ -59,6 +59,13 @@ export const types = {
|
||||
// shipping actions
|
||||
GET_MY_INFO_SHIPPING_SEARCH: "GET_MY_INFO_SHIPPING_SEARCH",
|
||||
|
||||
// cart actions
|
||||
GET_MY_INFO_CART_SEARCH: "GET_MY_INFO_CART_SEARCH",
|
||||
ADD_TO_CART: "ADD_TO_CART",
|
||||
REMOVE_FROM_CART: "REMOVE_FROM_CART",
|
||||
UPDATE_CART_ITEM: "UPDATE_CART_ITEM",
|
||||
CLEAR_CART: "CLEAR_CART",
|
||||
|
||||
// appData actions
|
||||
ADD_MAIN_INDEX: "ADD_MAIN_INDEX",
|
||||
SEND_SMS: "SEND_SMS",
|
||||
|
||||
193
com.twin.app.shoptime/src/actions/cartActions.js
Normal file
193
com.twin.app.shoptime/src/actions/cartActions.js
Normal file
@@ -0,0 +1,193 @@
|
||||
import { URLS } from "../api/apiConfig";
|
||||
import { TAxios } from "../api/TAxios";
|
||||
import { types } from "./actionTypes";
|
||||
|
||||
/**
|
||||
* 회원 장바구니 정보 조회
|
||||
* @param {Object} props - { mbrNo: 회원번호 }
|
||||
*/
|
||||
export const getMyInfoCartSearch = (props) => (dispatch, getState) => {
|
||||
const { mbrNo } = props;
|
||||
|
||||
const onSuccess = (response) => {
|
||||
console.log("getMyInfoCartSearch onSuccess: ", response.data);
|
||||
|
||||
dispatch({
|
||||
type: types.GET_MY_INFO_CART_SEARCH,
|
||||
payload: response.data.data,
|
||||
});
|
||||
};
|
||||
|
||||
const onFail = (error) => {
|
||||
console.error("getMyInfoCartSearch OnFail: ", error);
|
||||
|
||||
// 실패 시에도 빈 데이터로 초기화
|
||||
dispatch({
|
||||
type: types.GET_MY_INFO_CART_SEARCH,
|
||||
payload: { cartList: [] },
|
||||
});
|
||||
};
|
||||
|
||||
// API URL이 정의되어 있지 않은 경우 임시로 빈 데이터 반환
|
||||
if (!URLS.GET_MY_INFO_CART_SEARCH) {
|
||||
console.warn("GET_MY_INFO_CART_SEARCH URL이 정의되지 않았습니다.");
|
||||
dispatch({
|
||||
type: types.GET_MY_INFO_CART_SEARCH,
|
||||
payload: { cartList: [] },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
TAxios(
|
||||
dispatch,
|
||||
getState,
|
||||
"get",
|
||||
URLS.GET_MY_INFO_CART_SEARCH,
|
||||
{ mbrNo },
|
||||
{},
|
||||
onSuccess,
|
||||
onFail
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 장바구니에 상품 추가
|
||||
* @param {Object} props - 장바구니 상품 정보
|
||||
*/
|
||||
export const addToCart = (props) => (dispatch, getState) => {
|
||||
const { mbrNo, patnrId, prdtId, prodOptCdCval, prodQty } = props;
|
||||
|
||||
const onSuccess = (response) => {
|
||||
console.log("addToCart onSuccess: ", response.data);
|
||||
|
||||
dispatch({
|
||||
type: types.ADD_TO_CART,
|
||||
payload: response.data.data,
|
||||
});
|
||||
|
||||
// 장바구니 목록 재조회
|
||||
dispatch(getMyInfoCartSearch({ mbrNo }));
|
||||
};
|
||||
|
||||
const onFail = (error) => {
|
||||
console.error("addToCart OnFail: ", error);
|
||||
};
|
||||
|
||||
// API URL이 정의되어 있지 않은 경우 로컬 상태만 업데이트
|
||||
if (!URLS.ADD_TO_CART) {
|
||||
console.warn("ADD_TO_CART URL이 정의되지 않았습니다.");
|
||||
dispatch({
|
||||
type: types.ADD_TO_CART,
|
||||
payload: { patnrId, prdtId, prodOptCdCval, prodQty },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
TAxios(
|
||||
dispatch,
|
||||
getState,
|
||||
"post",
|
||||
URLS.ADD_TO_CART,
|
||||
{},
|
||||
{ mbrNo, patnrId, prdtId, prodOptCdCval, prodQty },
|
||||
onSuccess,
|
||||
onFail
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 장바구니에서 상품 제거
|
||||
* @param {Object} props - { mbrNo, cartSno }
|
||||
*/
|
||||
export const removeFromCart = (props) => (dispatch, getState) => {
|
||||
const { mbrNo, cartSno } = props;
|
||||
|
||||
const onSuccess = (response) => {
|
||||
console.log("removeFromCart onSuccess: ", response.data);
|
||||
|
||||
dispatch({
|
||||
type: types.REMOVE_FROM_CART,
|
||||
payload: { cartSno },
|
||||
});
|
||||
|
||||
// 장바구니 목록 재조회
|
||||
dispatch(getMyInfoCartSearch({ mbrNo }));
|
||||
};
|
||||
|
||||
const onFail = (error) => {
|
||||
console.error("removeFromCart OnFail: ", error);
|
||||
};
|
||||
|
||||
if (!URLS.REMOVE_FROM_CART) {
|
||||
console.warn("REMOVE_FROM_CART URL이 정의되지 않았습니다.");
|
||||
dispatch({
|
||||
type: types.REMOVE_FROM_CART,
|
||||
payload: { cartSno },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
TAxios(
|
||||
dispatch,
|
||||
getState,
|
||||
"delete",
|
||||
URLS.REMOVE_FROM_CART,
|
||||
{ mbrNo, cartSno },
|
||||
{},
|
||||
onSuccess,
|
||||
onFail
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 장바구니 상품 수량 업데이트
|
||||
* @param {Object} props - { mbrNo, cartSno, prodQty }
|
||||
*/
|
||||
export const updateCartItem = (props) => (dispatch, getState) => {
|
||||
const { mbrNo, cartSno, prodQty } = props;
|
||||
|
||||
const onSuccess = (response) => {
|
||||
console.log("updateCartItem onSuccess: ", response.data);
|
||||
|
||||
dispatch({
|
||||
type: types.UPDATE_CART_ITEM,
|
||||
payload: { cartSno, prodQty },
|
||||
});
|
||||
|
||||
// 장바구니 목록 재조회
|
||||
dispatch(getMyInfoCartSearch({ mbrNo }));
|
||||
};
|
||||
|
||||
const onFail = (error) => {
|
||||
console.error("updateCartItem OnFail: ", error);
|
||||
};
|
||||
|
||||
if (!URLS.UPDATE_CART_ITEM) {
|
||||
console.warn("UPDATE_CART_ITEM URL이 정의되지 않았습니다.");
|
||||
dispatch({
|
||||
type: types.UPDATE_CART_ITEM,
|
||||
payload: { cartSno, prodQty },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
TAxios(
|
||||
dispatch,
|
||||
getState,
|
||||
"put",
|
||||
URLS.UPDATE_CART_ITEM,
|
||||
{},
|
||||
{ mbrNo, cartSno, prodQty },
|
||||
onSuccess,
|
||||
onFail
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 장바구니 전체 비우기
|
||||
*/
|
||||
export const clearCart = () => (dispatch) => {
|
||||
dispatch({
|
||||
type: types.CLEAR_CART,
|
||||
});
|
||||
};
|
||||
@@ -19,6 +19,12 @@ export const URLS = {
|
||||
// card controller
|
||||
GET_MY_INFO_CARD_SEARCH: "/lgsp/v1/myinfo/card/search.lge",
|
||||
|
||||
// cart controller
|
||||
GET_MY_INFO_CART_SEARCH: "/lgsp/v1/myinfo/cart/search.lge",
|
||||
ADD_TO_CART: "/lgsp/v1/myinfo/cart/add.lge",
|
||||
REMOVE_FROM_CART: "/lgsp/v1/myinfo/cart/remove.lge",
|
||||
UPDATE_CART_ITEM: "/lgsp/v1/myinfo/cart/update.lge",
|
||||
|
||||
// shipping controller
|
||||
GET_MY_INFO_SHIPPING_SEARCH: "/lgsp/v1/myinfo/address/shipping/search.lge",
|
||||
|
||||
|
||||
@@ -61,17 +61,34 @@ export default function TToastEnhanced({
|
||||
// BuyOption 타입일 때 전역 포커스 감지
|
||||
useEffect(() => {
|
||||
if (type === 'buyOption') {
|
||||
// BuyOption이 포커스를 받았는지 추적하는 플래그
|
||||
let hasBuyOptionReceivedFocus = false;
|
||||
|
||||
const handleFocusChange = (e) => {
|
||||
// BuyOption 컨테이너 외부로 포커스가 이동했는지 확인
|
||||
if (buyOptionRef.current && !buyOptionRef.current.contains(e.target)) {
|
||||
console.log('[TToastEnhanced] Focus moved outside BuyOption toast - closing');
|
||||
// 1. BuyOption 내부로 포커스가 들어온 경우 - 플래그를 true로 설정
|
||||
if (buyOptionRef.current && buyOptionRef.current.contains(e.target)) {
|
||||
if (!hasBuyOptionReceivedFocus) {
|
||||
hasBuyOptionReceivedFocus = true;
|
||||
console.log('[TToastEnhanced] BuyOption received focus - now tracking focus leaving');
|
||||
}
|
||||
return; // 내부에 포커스가 있으면 아무것도 하지 않음
|
||||
}
|
||||
|
||||
// 2. BuyOption이 포커스를 받은 적이 있고, 현재 외부로 포커스가 이동한 경우 - Toast 닫기
|
||||
if (hasBuyOptionReceivedFocus &&
|
||||
buyOptionRef.current &&
|
||||
!buyOptionRef.current.contains(e.target)) {
|
||||
console.log('[TToastEnhanced] Focus left BuyOption after receiving focus - closing toast');
|
||||
handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
// focusin 이벤트로 포커스 변경 감지
|
||||
document.addEventListener('focusin', handleFocusChange);
|
||||
return () => document.removeEventListener('focusin', handleFocusChange);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('focusin', handleFocusChange);
|
||||
};
|
||||
}
|
||||
}, [type]);
|
||||
|
||||
|
||||
88
com.twin.app.shoptime/src/reducers/cartReducer.js
Normal file
88
com.twin.app.shoptime/src/reducers/cartReducer.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import { types } from "../actions/actionTypes";
|
||||
|
||||
/**
|
||||
* Cart Reducer 초기 상태
|
||||
*/
|
||||
const initialState = {
|
||||
// 장바구니 조회 데이터
|
||||
getMyinfoCartSearch: {
|
||||
cartList: [],
|
||||
totalCount: 0,
|
||||
},
|
||||
// 추가/수정/삭제 결과
|
||||
lastAction: null,
|
||||
error: null,
|
||||
};
|
||||
|
||||
/**
|
||||
* Cart Reducer
|
||||
* 장바구니 관련 상태를 관리합니다.
|
||||
*/
|
||||
export const cartReducer = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
// 장바구니 조회
|
||||
case types.GET_MY_INFO_CART_SEARCH:
|
||||
return {
|
||||
...state,
|
||||
getMyinfoCartSearch: action.payload || {
|
||||
cartList: [],
|
||||
totalCount: 0,
|
||||
},
|
||||
error: null,
|
||||
};
|
||||
|
||||
// 장바구니에 상품 추가
|
||||
case types.ADD_TO_CART:
|
||||
return {
|
||||
...state,
|
||||
lastAction: {
|
||||
type: "add",
|
||||
data: action.payload,
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
error: null,
|
||||
};
|
||||
|
||||
// 장바구니에서 상품 제거
|
||||
case types.REMOVE_FROM_CART:
|
||||
return {
|
||||
...state,
|
||||
lastAction: {
|
||||
type: "remove",
|
||||
data: action.payload,
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
error: null,
|
||||
};
|
||||
|
||||
// 장바구니 상품 수량 업데이트
|
||||
case types.UPDATE_CART_ITEM:
|
||||
return {
|
||||
...state,
|
||||
lastAction: {
|
||||
type: "update",
|
||||
data: action.payload,
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
error: null,
|
||||
};
|
||||
|
||||
// 장바구니 전체 비우기
|
||||
case types.CLEAR_CART:
|
||||
return {
|
||||
...state,
|
||||
getMyinfoCartSearch: {
|
||||
cartList: [],
|
||||
totalCount: 0,
|
||||
},
|
||||
lastAction: {
|
||||
type: "clear",
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
error: null,
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
@@ -10,6 +10,7 @@ import { billingReducer } from '../reducers/billingReducer';
|
||||
import { brandReducer } from '../reducers/brandReducer';
|
||||
import { cancelReducer } from '../reducers/cancelReducer';
|
||||
import { cardReducer } from '../reducers/cardReducer';
|
||||
import { cartReducer } from '../reducers/cartReducer';
|
||||
import { checkoutReducer } from '../reducers/checkoutReducer';
|
||||
import { commonReducer } from '../reducers/commonReducer';
|
||||
import { couponReducer } from '../reducers/couponReducer';
|
||||
@@ -48,6 +49,7 @@ const rootReducer = combineReducers({
|
||||
event: eventReducer,
|
||||
coupon: couponReducer,
|
||||
card: cardReducer,
|
||||
cart: cartReducer,
|
||||
billing: billingReducer,
|
||||
shipping: shippingReducer,
|
||||
checkout: checkoutReducer,
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
useSelector,
|
||||
} from 'react-redux';
|
||||
|
||||
import { getMyInfoCartSearch } from '../../actions/cartActions';
|
||||
import { popPanel } from '../../actions/panelActions';
|
||||
import TBody from '../../components/TBody/TBody';
|
||||
import THeader from '../../components/THeader/THeader';
|
||||
@@ -21,12 +22,22 @@ import CartSidebar from './CartSidebar';
|
||||
|
||||
export default function CartPanel({ spotlightId, scrollOptions = [] }) {
|
||||
const cartData = useSelector((state) => state.cart.getMyinfoCartSearch);
|
||||
const { userNumber } = useSelector(
|
||||
(state) => state.common.appStatus.loginUserData
|
||||
);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onBackClick = useCallback(() => {
|
||||
dispatch(popPanel());
|
||||
}, [dispatch]);
|
||||
|
||||
// 장바구니 데이터 로드
|
||||
useEffect(() => {
|
||||
if (userNumber) {
|
||||
dispatch(getMyInfoCartSearch({ mbrNo: userNumber }));
|
||||
}
|
||||
}, [dispatch, userNumber]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("###cartData", cartData);
|
||||
}, [cartData]);
|
||||
|
||||
@@ -14,6 +14,7 @@ import Spotlight from '@enact/spotlight';
|
||||
import SpotlightContainerDecorator
|
||||
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
|
||||
import { addToCart } from '../../../actions/cartActions';
|
||||
import { getMyInfoCheckoutInfo } from '../../../actions/checkoutActions';
|
||||
import {
|
||||
changeAppStatus,
|
||||
@@ -365,16 +366,77 @@ const BuyOption = ({
|
||||
logInfo,
|
||||
]);
|
||||
|
||||
// [임시] ADD TO CART 버튼 클릭 시 toast 닫기
|
||||
const handleAddToCartClick = () => {
|
||||
console.log("[BuyOption] ADD TO CART clicked - closing toast (임시)");
|
||||
// ADD TO CART 버튼 클릭 핸들러
|
||||
const handleAddToCartClick = useCallback(() => {
|
||||
console.log("[BuyOption] ADD TO CART clicked");
|
||||
|
||||
// 상품 품절 체크
|
||||
if (productInfo && productInfo?.soldoutFlag === "Y") {
|
||||
return;
|
||||
}
|
||||
|
||||
// 로그인 체크
|
||||
if (!userNumber || userNumber === "") {
|
||||
return dispatch(setShowPopup(Config.ACTIVE_POPUP.loginPopup));
|
||||
}
|
||||
|
||||
// 장바구니에 상품 추가
|
||||
if (userNumber && selectedPatnrId && selectedPrdtId && quantity) {
|
||||
const { prodOptCval, priceInfo } = selectedOptions || {};
|
||||
const { patncNm, brndNm, catNm, prdtNm, prdtId } = productInfo;
|
||||
const regularPrice = priceInfo?.split("|")[0];
|
||||
const discountPrice = priceInfo?.split("|")[1];
|
||||
const discountRate = priceInfo?.split("|")[4];
|
||||
|
||||
// 로그 전송
|
||||
dispatch(
|
||||
sendLogTotalRecommend({
|
||||
nowMenu: nowMenu,
|
||||
productId: prdtId,
|
||||
productTitle: prdtNm,
|
||||
partner: patncNm,
|
||||
price: discountRate ? discountPrice : regularPrice,
|
||||
discount: discountRate,
|
||||
brand: brndNm,
|
||||
productOption: prodOptCval || "",
|
||||
category: catNm,
|
||||
contextName: Config.LOG_CONTEXT_NAME.DETAILPAGE,
|
||||
messageId: Config.LOG_MESSAGE_ID.ADD_TO_CART,
|
||||
})
|
||||
);
|
||||
|
||||
// 장바구니에 추가
|
||||
dispatch(
|
||||
addToCart({
|
||||
mbrNo: userNumber,
|
||||
patnrId: selectedPatnrId,
|
||||
prdtId: selectedPrdtId,
|
||||
prodOptCdCval: selectedOptions?.prodOptCdCval || null,
|
||||
prodQty: quantity,
|
||||
prodOptTpCdCval: productOptionInfos[0]?.prodOptTpCdCval,
|
||||
})
|
||||
);
|
||||
|
||||
// CartPanel로 이동
|
||||
dispatch(
|
||||
pushPanel({
|
||||
name: Config.panel_names.CART_PANEL,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
dispatch(clearAllToasts());
|
||||
dispatch(
|
||||
pushPanel({
|
||||
name: Config.panel_names.CART_PANEL,
|
||||
})
|
||||
);
|
||||
};
|
||||
}, [
|
||||
dispatch,
|
||||
userNumber,
|
||||
selectedPatnrId,
|
||||
selectedPrdtId,
|
||||
selectedOptions,
|
||||
productInfo,
|
||||
productOptionInfos,
|
||||
quantity,
|
||||
nowMenu,
|
||||
]);
|
||||
|
||||
// 첫번째 옵션 선택 핸들러 (SingleOption과 동일)
|
||||
const handleFirstOptionSelect = (selected) => {
|
||||
|
||||
Reference in New Issue
Block a user