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
GET_HOME_TERMS: "GET_HOME_TERMS",
SET_TERMS_ID_MAP: "SET_TERMS_ID_MAP",
GET_HOME_MENU: "GET_HOME_MENU",
GET_HOME_LAYOUT: "GET_HOME_LAYOUT",
GET_HOME_MAIN_CONTENTS: "GET_HOME_MAIN_CONTENTS",

View File

@@ -1,5 +1,5 @@
import { URLS } from "../api/apiConfig";
import { TAxios } from "../api/TAxios";
import { TAxios,TAxiosPromise } from "../api/TAxios";
import { types } from "./actionTypes";
import { changeAppStatus, getTermsAgreeYn } from "./commonActions";
@@ -15,6 +15,26 @@ export const getHomeTerms = (props) => (dispatch, getState) => {
type: types.GET_HOME_TERMS,
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(() => {
dispatch(getTermsAgreeYn());
}, 0);
@@ -58,6 +78,26 @@ export const fetchCurrentUserHomeTerms = () => (dispatch, getState) => {
type: types.GET_HOME_TERMS, // 기존 GET_HOME_TERMS 타입을 재사용
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 후속 처리
setTimeout(() => {
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
export const getHomeMenu = () => (dispatch, getState) => {
const onSuccess = (response) => {

View File

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

View File

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

View File

@@ -96,6 +96,7 @@ export default function HomeBanner({
}, [dispatch]);
const termsData = useSelector((state) => state.home.termsData);
const termsIdMap = useSelector((state) => state.home.termsIdMap);
const optionalTermsData = useSelector((state) =>
state.home.termsData?.data?.terms.find(
(term) => term.trmsTpCd === "MST00405",
@@ -128,35 +129,76 @@ export default function HomeBanner({
}, [termsData, termsLoading, isGnbOpened]);
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 = [];
console.log("OptionalTermsConfirm -약관 동의 API 호출 파라미터:", {
termsList,
notTermsList,
});
if (process.env.NODE_ENV === "development") {
console.log("[HomeBanner] 현재 termsIdMap:", termsIdMap);
console.log("[HomeBanner] 약관 동의 API 호출 파라미터:", {
termsList,
notTermsList,
});
}
const callback = (response) => {
if (response.retCode === "000" || response.retCode === 0) {
console.log("약관 동의 성공:", response);
// 약관 정보 갱신
if (process.env.NODE_ENV === "development") {
console.log("[HomeBanner] 약관 동의 성공:", response);
}
dispatch(fetchCurrentUserHomeTerms());
} else {
console.error("약관 동의 실패:", response);
if (process.env.NODE_ENV === "development") {
console.error("[HomeBanner] 약관 동의 실패:", response);
}
}
};
console.log("OptionalTermsConfirm - 약관 동의 API 호출 payload:", {
termsList,
notTermsList,
});
if (process.env.NODE_ENV === "development") {
console.log("[HomeBanner] 약관 동의 API 호출 payload:", {
termsList,
notTermsList,
});
}
dispatch(setMyPageTermsAgree({ termsList, notTermsList }, callback));
}, [dispatch]);
}, [dispatch, termsIdMap]);
const handleOptionalTermsClick = useCallback(() => {
console.log("약관 자세히 보기 클릭");
console.log("[HomeBanner] 약관 자세히 보기 클릭");
setIsOptionalConfirmVisible(false);
setIsOptionalTermsVisible(true);
// 약관 상세 팝업을 띄우는 로직 추가
@@ -165,10 +207,10 @@ export default function HomeBanner({
const handleOptionalAgreeClick = useCallback(() => {
handleOptionalAgree();
setIsOptionalConfirmVisible(false);
}, []);
}, [handleOptionalAgree]);
const handleOptionalDeclineClick = useCallback(() => {
console.log("거절/다음에 하기 버튼 클릭");
console.log("[HomeBanner] 거절/다음에 하기 버튼 클릭");
setIsOptionalConfirmVisible(false);
// 거절 처리 로직 추가
}, []);
@@ -182,10 +224,10 @@ export default function HomeBanner({
// 선택약관 팝업 Agree
const handleTermsPopupAgree = useCallback(() => {
console.log("handleTermsPopupAgree");
console.log("[HomeBanner] handleTermsPopupAgree");
handleOptionalAgree();
setIsOptionalTermsVisible(false);
}, []);
}, [handleOptionalAgree]);
//------------------------------------------------------------------------------
const _handleShelfFocus = useCallback(() => {

View File

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