fix: IntroPanel간헐적 Agree미작동 해결, termsIdMap 추가

This commit is contained in:
djaco
2025-06-23 14:11:07 +09:00
parent 34f4795d86
commit 23f687606b
6 changed files with 231 additions and 32 deletions

View File

@@ -57,6 +57,7 @@ export const types = {
// home actions // home actions
GET_HOME_TERMS: "GET_HOME_TERMS", GET_HOME_TERMS: "GET_HOME_TERMS",
SET_TERMS_ID_MAP: "SET_TERMS_ID_MAP",
GET_HOME_MENU: "GET_HOME_MENU", GET_HOME_MENU: "GET_HOME_MENU",
GET_HOME_LAYOUT: "GET_HOME_LAYOUT", GET_HOME_LAYOUT: "GET_HOME_LAYOUT",
GET_HOME_MAIN_CONTENTS: "GET_HOME_MAIN_CONTENTS", GET_HOME_MAIN_CONTENTS: "GET_HOME_MAIN_CONTENTS",

View File

@@ -1,5 +1,5 @@
import { URLS } from "../api/apiConfig"; import { URLS } from "../api/apiConfig";
import { TAxios } from "../api/TAxios"; import { TAxios,TAxiosPromise } from "../api/TAxios";
import { types } from "./actionTypes"; import { types } from "./actionTypes";
import { changeAppStatus, getTermsAgreeYn } from "./commonActions"; import { changeAppStatus, getTermsAgreeYn } from "./commonActions";
@@ -15,6 +15,26 @@ export const getHomeTerms = (props) => (dispatch, getState) => {
type: types.GET_HOME_TERMS, type: types.GET_HOME_TERMS,
payload: response.data, payload: response.data,
}); });
// 약관 ID 매핑을 별도로 생성하여 저장
if (response.data?.data?.terms) {
const termsIdMap = {};
response.data.data.terms.forEach(term => {
if (term.trmsTpCd && term.trmsId) {
termsIdMap[term.trmsTpCd] = term.trmsId;
}
});
dispatch({
type: types.SET_TERMS_ID_MAP,
payload: termsIdMap,
});
if (process.env.NODE_ENV === "development") {
console.log("약관 ID 매핑 생성:", termsIdMap);
}
}
setTimeout(() => { setTimeout(() => {
dispatch(getTermsAgreeYn()); dispatch(getTermsAgreeYn());
}, 0); }, 0);
@@ -58,6 +78,26 @@ export const fetchCurrentUserHomeTerms = () => (dispatch, getState) => {
type: types.GET_HOME_TERMS, // 기존 GET_HOME_TERMS 타입을 재사용 type: types.GET_HOME_TERMS, // 기존 GET_HOME_TERMS 타입을 재사용
payload: response.data, payload: response.data,
}); });
// 약관 ID 매핑을 별도로 생성하여 저장
if (response.data?.data?.terms) {
const termsIdMap = {};
response.data.data.terms.forEach(term => {
if (term.trmsTpCd && term.trmsId) {
termsIdMap[term.trmsTpCd] = term.trmsId;
}
});
dispatch({
type: types.SET_TERMS_ID_MAP,
payload: termsIdMap,
});
if (process.env.NODE_ENV === "development") {
console.log("약관 ID 매핑 생성:", termsIdMap);
}
}
// getHomeTerms와 동일하게 getTermsAgreeYn 후속 처리 // getHomeTerms와 동일하게 getTermsAgreeYn 후속 처리
setTimeout(() => { setTimeout(() => {
dispatch(getTermsAgreeYn()); dispatch(getTermsAgreeYn());
@@ -85,6 +125,87 @@ export const fetchCurrentUserHomeTerms = () => (dispatch, getState) => {
); );
}; };
// 기존 TAxios 패턴과 일치하는 안전한 Redux Action
export const fetchCurrentUserHomeTermsSafe = () => async (dispatch, getState) => {
const loginUserData = getState().common.appStatus.loginUserData;
if (!loginUserData || !loginUserData.userNumber) {
console.error("fetchCurrentUserHomeTerms: userNumber is not available");
dispatch({ type: types.GET_TERMS_AGREE_YN_FAILURE });
return { success: false, message: "사용자 정보가 없습니다." };
}
const mbrNo = loginUserData.userNumber;
const trmsTpCdList = "MST00401, MST00402, MST00405";
console.log("Fetching home terms for user:", mbrNo);
// 안전한 API 호출 (기존 TAxios 패턴과 동일)
const result = await TAxiosPromise(
dispatch,
getState,
"get",
URLS.GET_HOME_TERMS,
{ trmsTpCdList, mbrNo }
);
// 네트워크 에러인 경우
if (!result.success) {
console.error("fetchCurrentUserHomeTerms network error:", result.error);
dispatch({ type: types.GET_TERMS_AGREE_YN_FAILURE });
return { success: false, message: "네트워크 오류가 발생했습니다." };
}
// 기존 TAxios처럼 특별한 retCode들은 TAxios 내부에서 이미 처리됨
// (401, 402, 501, 602, 603, 604 등은 TAxios에서 알아서 처리하고 onSuccess가 호출되지 않음)
console.log("fetchCurrentUserHomeTerms response:", result.data);
// 정상적으로 onSuccess가 호출된 경우에만 여기까지 옴
if (result.data && result.data.retCode === 0) {
dispatch({
type: types.GET_HOME_TERMS,
payload: result.data,
});
// 약관 ID 매핑을 별도로 생성하여 저장
if (result.data?.data?.terms) {
const termsIdMap = {};
result.data.data.terms.forEach(term => {
if (term.trmsTpCd && term.trmsId) {
termsIdMap[term.trmsTpCd] = term.trmsId;
}
});
dispatch({
type: types.SET_TERMS_ID_MAP,
payload: termsIdMap,
});
if (process.env.NODE_ENV === "development") {
console.log("약관 ID 매핑 생성:", termsIdMap);
}
}
// 후속 액션 호출 (기존과 동일)
setTimeout(() => {
dispatch(getTermsAgreeYn());
}, 0);
return { success: true, data: result.data };
} else {
// retCode가 0이 아닌 일반적인 API 에러
console.error("API returned non-zero retCode:", result.data?.retCode);
dispatch({ type: types.GET_TERMS_AGREE_YN_FAILURE });
return {
success: false,
message: result.data?.retMsg || "서버 오류가 발생했습니다."
};
}
};
// 메뉴 목록 조회 IF-LGSP-044 // 메뉴 목록 조회 IF-LGSP-044
export const getHomeMenu = () => (dispatch, getState) => { export const getHomeMenu = () => (dispatch, getState) => {
const onSuccess = (response) => { const onSuccess = (response) => {

View File

@@ -2,7 +2,7 @@
@import "../../style/utils.module.less"; @import "../../style/utils.module.less";
.tabs { .tabs {
.size(@w: 1680px, @h: 67px); .size(@w: 1680px, @h: 67px);
display: flex; display: flex;
position: relative; position: relative;

View File

@@ -25,6 +25,7 @@ const initialState = {
ownerId: null, ownerId: null,
isPaused: false, isPaused: false,
}, },
termsIdMap: {}, // added new property to initialState
}; };
export const homeReducer = (state = initialState, action) => { export const homeReducer = (state = initialState, action) => {
@@ -35,6 +36,12 @@ export const homeReducer = (state = initialState, action) => {
termsData: action.payload, termsData: action.payload,
}; };
case types.SET_TERMS_ID_MAP:
return {
...state,
termsIdMap: action.payload,
};
case types.GET_HOME_MENU: { case types.GET_HOME_MENU: {
let menuItems = []; let menuItems = [];

View File

@@ -96,6 +96,7 @@ export default function HomeBanner({
}, [dispatch]); }, [dispatch]);
const termsData = useSelector((state) => state.home.termsData); const termsData = useSelector((state) => state.home.termsData);
const termsIdMap = useSelector((state) => state.home.termsIdMap);
const optionalTermsData = useSelector((state) => const optionalTermsData = useSelector((state) =>
state.home.termsData?.data?.terms.find( state.home.termsData?.data?.terms.find(
(term) => term.trmsTpCd === "MST00405", (term) => term.trmsTpCd === "MST00405",
@@ -128,35 +129,76 @@ export default function HomeBanner({
}, [termsData, termsLoading, isGnbOpened]); }, [termsData, termsLoading, isGnbOpened]);
const handleOptionalAgree = useCallback(() => { const handleOptionalAgree = useCallback(() => {
console.log("handleAgree Click"); if (process.env.NODE_ENV === "development") {
console.log("[HomeBanner] handleAgree Click");
}
if (!termsIdMap || Object.keys(termsIdMap).length === 0) {
if (process.env.NODE_ENV === "development") {
console.error("[HomeBanner] termsIdMap이 없습니다:", termsIdMap);
}
return;
}
const requiredTermTypes = ["MST00401", "MST00402", "MST00405"];
const missingTerms = requiredTermTypes.filter(
(type) => !termsIdMap[type],
);
if (missingTerms.length > 0) {
if (process.env.NODE_ENV === "development") {
console.error("[HomeBanner] 누락된 약관 타입:", missingTerms);
}
return;
}
const termsList = [];
if (termsIdMap["MST00401"]) {
termsList.push(termsIdMap["MST00401"]); // 개인정보처리방침
}
if (termsIdMap["MST00402"]) {
termsList.push(termsIdMap["MST00402"]); // 이용약관
}
if (termsIdMap["MST00405"]) {
termsList.push(termsIdMap["MST00405"]); // 선택약관
}
// 약관 동의할 항목들 (string array)
const termsList = ["TID0000222", "TID0000223", "TID0000232"];
// 동의하지 않을 항목들 (빈 배열)
const notTermsList = []; const notTermsList = [];
console.log("OptionalTermsConfirm -약관 동의 API 호출 파라미터:", {
termsList, if (process.env.NODE_ENV === "development") {
notTermsList, console.log("[HomeBanner] 현재 termsIdMap:", termsIdMap);
}); console.log("[HomeBanner] 약관 동의 API 호출 파라미터:", {
termsList,
notTermsList,
});
}
const callback = (response) => { const callback = (response) => {
if (response.retCode === "000" || response.retCode === 0) { if (response.retCode === "000" || response.retCode === 0) {
console.log("약관 동의 성공:", response); if (process.env.NODE_ENV === "development") {
// 약관 정보 갱신 console.log("[HomeBanner] 약관 동의 성공:", response);
}
dispatch(fetchCurrentUserHomeTerms()); dispatch(fetchCurrentUserHomeTerms());
} else { } else {
console.error("약관 동의 실패:", response); if (process.env.NODE_ENV === "development") {
console.error("[HomeBanner] 약관 동의 실패:", response);
}
} }
}; };
console.log("OptionalTermsConfirm - 약관 동의 API 호출 payload:", { if (process.env.NODE_ENV === "development") {
termsList, console.log("[HomeBanner] 약관 동의 API 호출 payload:", {
notTermsList, termsList,
}); notTermsList,
});
}
dispatch(setMyPageTermsAgree({ termsList, notTermsList }, callback)); dispatch(setMyPageTermsAgree({ termsList, notTermsList }, callback));
}, [dispatch]); }, [dispatch, termsIdMap]);
const handleOptionalTermsClick = useCallback(() => { const handleOptionalTermsClick = useCallback(() => {
console.log("약관 자세히 보기 클릭"); console.log("[HomeBanner] 약관 자세히 보기 클릭");
setIsOptionalConfirmVisible(false); setIsOptionalConfirmVisible(false);
setIsOptionalTermsVisible(true); setIsOptionalTermsVisible(true);
// 약관 상세 팝업을 띄우는 로직 추가 // 약관 상세 팝업을 띄우는 로직 추가
@@ -165,10 +207,10 @@ export default function HomeBanner({
const handleOptionalAgreeClick = useCallback(() => { const handleOptionalAgreeClick = useCallback(() => {
handleOptionalAgree(); handleOptionalAgree();
setIsOptionalConfirmVisible(false); setIsOptionalConfirmVisible(false);
}, []); }, [handleOptionalAgree]);
const handleOptionalDeclineClick = useCallback(() => { const handleOptionalDeclineClick = useCallback(() => {
console.log("거절/다음에 하기 버튼 클릭"); console.log("[HomeBanner] 거절/다음에 하기 버튼 클릭");
setIsOptionalConfirmVisible(false); setIsOptionalConfirmVisible(false);
// 거절 처리 로직 추가 // 거절 처리 로직 추가
}, []); }, []);
@@ -182,10 +224,10 @@ export default function HomeBanner({
// 선택약관 팝업 Agree // 선택약관 팝업 Agree
const handleTermsPopupAgree = useCallback(() => { const handleTermsPopupAgree = useCallback(() => {
console.log("handleTermsPopupAgree"); console.log("[HomeBanner] handleTermsPopupAgree");
handleOptionalAgree(); handleOptionalAgree();
setIsOptionalTermsVisible(false); setIsOptionalTermsVisible(false);
}, []); }, [handleOptionalAgree]);
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
const _handleShelfFocus = useCallback(() => { const _handleShelfFocus = useCallback(() => {

View File

@@ -64,6 +64,7 @@ export default function IntroPanel({
const dispatch = useDispatch(); const dispatch = useDispatch();
// const blurTimeout = useRef(null); // const blurTimeout = useRef(null);
const termsData = useSelector((state) => state.home.termsData); const termsData = useSelector((state) => state.home.termsData);
const termsIdMap = useSelector((state) => state.home.termsIdMap);
const { popupVisible, activePopup, ...popupState } = useSelector( const { popupVisible, activePopup, ...popupState } = useSelector(
(state) => state.common.popup, (state) => state.common.popup,
); );
@@ -76,6 +77,8 @@ export default function IntroPanel({
// registerDevice API 호출 중 여부 // registerDevice API 호출 중 여부
const [isProcessing, setIsProcessing] = useState(false); const [isProcessing, setIsProcessing] = useState(false);
const [showExitMessagePopup, setShowExitMessagePopup] = useState(false); const [showExitMessagePopup, setShowExitMessagePopup] = useState(false);
// race condition 방지를 위한 안전장치
const processingTimeoutRef = useRef(null);
// const [isRequiredFocused, setIsRequiredFocused] = useState(false); // const [isRequiredFocused, setIsRequiredFocused] = useState(false);
const { focusedItem, setFocusAsync, clearFocusAsync } = useSafeFocusState(); const { focusedItem, setFocusAsync, clearFocusAsync } = useSafeFocusState();
@@ -262,7 +265,10 @@ export default function IntroPanel({
}, [dispatch]); }, [dispatch]);
const handleAgree = useCallback(() => { const handleAgree = useCallback(() => {
if (isProcessing) return; console.log("[IntroPanel] handleAgree isProcessing=", isProcessing);
if (isProcessing){
return;
}
// 필수 약관이 체크되어 있는지 확인 // 필수 약관이 체크되어 있는지 확인
// if (!termsChecked || !privacyChecked) { // if (!termsChecked || !privacyChecked) {
@@ -277,24 +283,32 @@ export default function IntroPanel({
// } // }
setIsProcessing(true); setIsProcessing(true);
// 안전장치: 30초 후 자동으로 isProcessing 해제
processingTimeoutRef.current = setTimeout(() => {
console.warn("[IntroPanel] handleAgree 타임아웃 - isProcessing 강제 해제");
setIsProcessing(false);
}, 30000);
// 약관 동의 처리 시작 시 로딩 상태로 설정 // 약관 동의 처리 시작 시 로딩 상태로 설정
dispatch({ type: types.GET_TERMS_AGREE_YN_START }); dispatch({ type: types.GET_TERMS_AGREE_YN_START });
// 약관 ID 정확하게 매핑 // Redux에서 가져온 termsIdMap을 사용하여 동적으로 약관 ID 매핑
const agreeTerms = []; const agreeTerms = [];
if (termsChecked) { if (termsChecked && termsIdMap["MST00402"]) {
agreeTerms.push("TID0000222"); // MST00402 -> TID0000222 (이용약관) agreeTerms.push(termsIdMap["MST00402"]); // 이용약관
} }
if (privacyChecked) { if (privacyChecked && termsIdMap["MST00401"]) {
agreeTerms.push("TID0000223"); // MST00401 -> TID0000223 (개인정보처리방침) agreeTerms.push(termsIdMap["MST00401"]); // 개인정보처리방침
} }
if (optionalChecked) { if (optionalChecked && termsIdMap["MST00405"]) {
agreeTerms.push("TID0000232"); // MST00405 -> TID0000232 (선택약관) agreeTerms.push(termsIdMap["MST00405"]); // 선택약관
} }
if (process.env.NODE_ENV === "development") { if (process.env.NODE_ENV === "development") {
console.log("최종 전송될 agreeTerms:", agreeTerms); console.log("[IntroPanel] 현재 termsIdMap:", termsIdMap);
console.log("[IntroPanel] 최종 전송될 agreeTerms:", agreeTerms);
} }
dispatch( dispatch(
@@ -334,6 +348,7 @@ export default function IntroPanel({
} }
dispatch(popPanel(panel_names.INTRO_PANEL)); dispatch(popPanel(panel_names.INTRO_PANEL));
setIsProcessing(false); setIsProcessing(false);
clearTimeout(processingTimeoutRef.current); // 타임아웃 정리
}), }),
); );
} else { } else {
@@ -345,6 +360,7 @@ export default function IntroPanel({
}), }),
); );
setIsProcessing(false); setIsProcessing(false);
clearTimeout(processingTimeoutRef.current); // 타임아웃 정리
} }
}, },
() => { () => {
@@ -356,6 +372,7 @@ export default function IntroPanel({
}), }),
); );
setIsProcessing(false); setIsProcessing(false);
clearTimeout(processingTimeoutRef.current); // 타임아웃 정리
}, },
), ),
); );
@@ -366,6 +383,7 @@ export default function IntroPanel({
dispatch, dispatch,
isProcessing, isProcessing,
webOSVersion, webOSVersion,
termsIdMap,
]); ]);
// 실패 감지를 위한 useEffect 추가 // 실패 감지를 위한 useEffect 추가
@@ -386,9 +404,19 @@ export default function IntroPanel({
}), }),
); );
setIsProcessing(false); setIsProcessing(false);
clearTimeout(processingTimeoutRef.current); // 타임아웃 정리
} }
}, [regDeviceData, dispatch, isProcessing]); // isProcessing 의존성 추가 }, [regDeviceData, dispatch, isProcessing]); // isProcessing 의존성 추가
// 컴포넌트 언마운트 시 타임아웃 정리
useEffect(() => {
return () => {
if (processingTimeoutRef.current) {
clearTimeout(processingTimeoutRef.current);
}
};
}, []);
const handleDisagree = useCallback(() => { const handleDisagree = useCallback(() => {
dispatch(setShowPopup(Config.ACTIVE_POPUP.exitPopup)); dispatch(setShowPopup(Config.ACTIVE_POPUP.exitPopup));
dispatch(sendLogTerms({ logTpNo: Config.LOG_TP_NO.TERMS.DO_NOT_AGREE })); dispatch(sendLogTerms({ logTpNo: Config.LOG_TP_NO.TERMS.DO_NOT_AGREE }));