diff --git a/com.twin.app.shoptime/src/actions/actionTypes.js b/com.twin.app.shoptime/src/actions/actionTypes.js index b91169c1..06660e0f 100644 --- a/com.twin.app.shoptime/src/actions/actionTypes.js +++ b/com.twin.app.shoptime/src/actions/actionTypes.js @@ -13,6 +13,7 @@ export const types = { REGISTER_DEVICE_INFO: "REGISTER_DEVICE_INFO", GET_DEVICE_INFO: "GET_DEVICE_INFO", CLEAR_REGISTER_DEVICE_INFO: "CLEAR_REGISTER_DEVICE_INFO", + REGISTER_DEVICE_RESET: "REGISTER_DEVICE_RESET", // common actions GET_HTTP_HEADER: "GET_HTTP_HEADER", @@ -216,4 +217,7 @@ export const types = { GET_TERMS_AGREE_YN_START: "GET_TERMS_AGREE_YN_START", GET_TERMS_AGREE_YN_SUCCESS: "GET_TERMS_AGREE_YN_SUCCESS", GET_TERMS_AGREE_YN_FAILURE: "GET_TERMS_AGREE_YN_FAILURE", + + // device + REQ_REG_DEVICE_INFO: "REQ_REG_DEVICE_INFO", }; diff --git a/com.twin.app.shoptime/src/components/Optional/OptionalConfirm.jsx b/com.twin.app.shoptime/src/components/Optional/OptionalConfirm.jsx index 8e6d6cc8..27be4a9d 100644 --- a/com.twin.app.shoptime/src/components/Optional/OptionalConfirm.jsx +++ b/com.twin.app.shoptime/src/components/Optional/OptionalConfirm.jsx @@ -12,6 +12,7 @@ const OptionalConfirm = ({ open, spotlightId, className, + onClose, onOptionalTermsClick, // 약관 자세히 보기 버튼 클릭 핸들러 onOptionalAgreeClick, // 동의 버튼 클릭 핸들러 onOptionalDeclineClick, // 거절 또는 다음에 하기 버튼 클릭 핸들러 @@ -25,6 +26,7 @@ const OptionalConfirm = ({ spotlightId={spotlightId} spotlightRestrict="self-only" // 필요에 따라 props로 설정 가능하게 할 수 있습니다. className={`${css.optionalConfirmPopup} ${className || ''}`.trim()} + onClose={onClose} onOptionalTermsClick={onOptionalTermsClick} onOptionalAgreeClick={onOptionalAgreeClick} onOptionalDeclineClick={onOptionalDeclineClick} @@ -41,6 +43,8 @@ OptionalConfirm.propTypes = { spotlightId: PropTypes.string.isRequired, /** 추가적인 CSS 클래스 */ className: PropTypes.string, + /** 팝업의 닫기 이벤트 핸들러 (ESC 키 등) */ + onClose: PropTypes.func, /** 약관 자세히 보기 버튼 클릭 시 호출될 함수 */ onOptionalTermsClick: PropTypes.func, /** 동의 버튼 클릭 시 호출될 함수 */ @@ -55,6 +59,7 @@ OptionalConfirm.propTypes = { OptionalConfirm.defaultProps = { className: '', + onClose: () => console.log('OptionalConfirm: onClose not provided'), onOptionalTermsClick: () => console.log('OptionalConfirm: onOptionalTermsClick not provided'), onOptionalAgreeClick: () => console.log('OptionalConfirm: onOptionalAgreeClick not provided'), onOptionalDeclineClick: () => console.log('OptionalConfirm: onOptionalDeclineClick not provided'), diff --git a/com.twin.app.shoptime/src/reducers/deviceReducer.js b/com.twin.app.shoptime/src/reducers/deviceReducer.js index 1f443e27..95daa837 100644 --- a/com.twin.app.shoptime/src/reducers/deviceReducer.js +++ b/com.twin.app.shoptime/src/reducers/deviceReducer.js @@ -1,4 +1,7 @@ import { types } from "../actions/actionTypes"; +import { + GET_TERMS_AGREE_YN_FAIL, +} from "../actions/commonActions"; const initialState = { regDeviceData: {}, @@ -25,9 +28,13 @@ export const deviceReducer = (state = initialState, action) => { case types.CLEAR_REGISTER_DEVICE_INFO: return { ...state, - regDeviceInfoData: { retCode: undefined }, + regDeviceData: null, + }; + case types.REGISTER_DEVICE_RESET: + return { + ...state, + regDeviceData: null, }; - default: return state; } diff --git a/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/HomeBanner.jsx b/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/HomeBanner.jsx index a444b789..4059f00c 100644 --- a/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/HomeBanner.jsx +++ b/com.twin.app.shoptime/src/views/HomePanel/HomeBanner/HomeBanner.jsx @@ -321,6 +321,7 @@ export default function HomeBanner({ { + dispatch({ type: types.REGISTER_DEVICE_RESET }); dispatch(sendLogGNB(Config.LOG_MENU.TERMS_CONDITIONS)); - }, []); + }, [dispatch]); // 컴포넌트 마운트 시 현재 Redux 상태 로깅 - useEffect(() => { - console.log('🔍 IntroPanel 마운트 시 Redux 상태:'); - console.log(' - regDeviceData:', regDeviceData); - console.log(' - regDeviceInfoData:', regDeviceInfoData); - console.log(' - eventInfos:', eventInfos); - console.log(' - termsData:', termsData); - }, []); + // useEffect(() => { + // console.log('🔍 IntroPanel 마운트 시 Redux 상태:'); + // console.log(' - regDeviceData:', regDeviceData); + // console.log(' - regDeviceInfoData:', regDeviceInfoData); + // console.log(' - eventInfos:', eventInfos); + // console.log(' - termsData:', termsData); + // }, []); // 디버깅용 WebOS 버전 로그 useEffect(() => { @@ -247,16 +248,16 @@ export default function IntroPanel({ if (isProcessing) return; // 필수 약관이 체크되어 있는지 확인 - if (!termsChecked || !privacyChecked) { - // 필수 약관이 체크되지 않았을 때 알림 - // window.alert($L("Please agree to Terms & Conditions and Privacy Policy.")); - dispatch(setShowPopup(Config.ACTIVE_POPUP.alertPopup, { - title: $L("Required Terms"), - text: $L("Please agree to Terms & Conditions and Privacy Policy."), - button1Text: $L("OK") - })); - return; - } + // if (!termsChecked || !privacyChecked) { + // // 필수 약관이 체크되지 않았을 때 알림 + // // window.alert($L("Please agree to Terms & Conditions and Privacy Policy.")); + // dispatch(setShowPopup(Config.ACTIVE_POPUP.alertPopup, { + // title: $L("Required Terms"), + // text: $L("Please agree to Terms & Conditions and Privacy Policy."), + // button1Text: $L("OK") + // })); + // return; + // } setIsProcessing(true); // 약관 동의 처리 시작 시 로딩 상태로 설정 @@ -352,7 +353,10 @@ export default function IntroPanel({ useEffect(() => { // isProcessing이 true일 때만 실패 체크 (= handleAgree 클릭 후에만) if (isProcessing && regDeviceData && regDeviceData.retCode !== 0) { - console.error("registerDevice 실패:", regDeviceData); + console.error( + `[IntroPanel] registerDevice 실패: isProcessing=${isProcessing}, retCode=${regDeviceData.retCode}`, + regDeviceData + ); dispatch( setShowPopup(Config.ACTIVE_POPUP.alertPopup, { title: $L("Error"), @@ -394,13 +398,11 @@ export default function IntroPanel({ blurTimeout.current = null; } setFocusedItem(item); - setIsRequiredFocused(item === 'required'); }, []); const handleBlur = useCallback(() => { blurTimeout.current = setTimeout(() => { setFocusedItem(null); - setIsRequiredFocused(false); }, 0); }, []); @@ -426,6 +428,32 @@ export default function IntroPanel({ const rightPanelContent = useMemo(() => { + const requiredItemIds = [ + "termsCheckbox", + "termsButton", + "privacyCheckbox", + "privacyButton", + ]; + if (!requiredItemIds.includes(focusedItem)) { + return ( + <> + {shouldShowBenefitsView ? ( +
+ {$L( + 'By checking "Optional terms", you allow Shop Time to use your activity (views, purchases, searches, etc.) to show you more relevant content, product recommendations, special offers, and ads. If you do not check, you can still use all basic Shop Time features' + )} +
+ ) : ( + + )} + + ); + } + const hasRequiredUnchecked = !termsChecked || !privacyChecked; // 우선순위 1: 필수 약관이 체크 안됨 → 항상 경고 메시지 @@ -433,54 +461,43 @@ export default function IntroPanel({ return (

{$L("Required Consent")}

-

{$L("(Necessary for using the service)")}

+

+ {$L("(Necessary for using the service)")} +

-

{$L("Please agree to the required Terms & Conditions and")}

-

{$L("Privacy Policy to start enjoying Shop Time")}

+

+ {$L("Please agree to the required Terms & Conditions and")} +

+

+ {$L("Privacy Policy to start enjoying Shop Time")} +

); } // 우선순위 2: 필수 약관에 포커스 있음 → 필수 약관 안내 - if (isRequiredFocused) { - return ( -
-

{$L("Required Consent")}

-

{$L("(Necessary for using the service)")}

-
- ); - } - - // 우선순위 3: 기본값 → 선택 약관 정보 return ( - <> - {shouldShowBenefitsView ? ( -
- {$L('By checking "Optional terms", you allow Shop Time to use your activity (views, purchases, searches, etc.) to show you more relevant content, product recommendations, special offers, and ads. If you do not check, you can still use all basic Shop Time features')} -
- ) : ( - - )} - +
+

{$L("Required Consent")}

+

+ {$L("(Necessary for using the service)")} +

+
); - }, [termsChecked, privacyChecked, isRequiredFocused, shouldShowBenefitsView]); + }, [termsChecked, privacyChecked, focusedItem, shouldShowBenefitsView]); useEffect(() => { Spotlight.focus(); }, [popupVisible]); - useEffect(() => { - if (regDeviceInfoData && regDeviceInfoData.retCode === 0) { - dispatch(setHidePopup()); - dispatch(popPanel(panel_names.INTRO_PANEL)); - } - }, [dispatch, regDeviceInfoData]); + // useEffect(() => { + // if (regDeviceInfoData && regDeviceInfoData.retCode === 0) { + // dispatch(setHidePopup()); + // dispatch(popPanel(panel_names.INTRO_PANEL)); + // } + // }, [dispatch, regDeviceInfoData]); const description = $L( "Check out more LIVE SHOWS now and enjoy shopping at Shop Time's special price on your TV by agreeing to the LG TV Shopping Terms and Conditions." @@ -536,7 +553,7 @@ export default function IntroPanel({ className={css.customeCheckbox} selected={termsChecked} onToggle={handleTermsToggle} - onFocus={() => handleFocus('required')} + onFocus={() => handleFocus('termsCheckbox')} onBlur={handleBlur} spotlightId="termsCheckbox" ariaLabel={$L("Terms & Conditions checkbox")} @@ -544,7 +561,7 @@ export default function IntroPanel({ handleTermsClick("MST00402")} - onFocus={() => handleFocus('required')} + onFocus={() => handleFocus('termsButton')} onBlur={handleBlur} spotlightId="termsButton" type={TYPES.terms} @@ -562,7 +579,7 @@ export default function IntroPanel({ className={css.customeCheckbox} selected={privacyChecked} onToggle={handlePrivacyToggle} - onFocus={() => handleFocus('required')} + onFocus={() => handleFocus('privacyCheckbox')} onBlur={handleBlur} spotlightId="privacyCheckbox" ariaLabel={$L("Privacy Policy checkbox")} @@ -570,7 +587,7 @@ export default function IntroPanel({ handleTermsClick("MST00401")} - onFocus={() => handleFocus('required')} + onFocus={() => handleFocus('privacyButton')} onBlur={handleBlur} spotlightId="privacyButton" type={TYPES.terms} @@ -588,7 +605,7 @@ export default function IntroPanel({ className={css.customeCheckbox} selected={optionalChecked} onToggle={handleOptionalToggle} - onFocus={() => handleFocus('optional')} + onFocus={() => handleFocus('optionalCheckbox')} onBlur={handleBlur} spotlightId="optionalCheckbox" ariaLabel={$L("Optional Terms checkbox")} @@ -596,7 +613,7 @@ export default function IntroPanel({ handleOptionalTermsClick("MST00405")} - onFocus={() => handleFocus('optional')} + onFocus={() => handleFocus('optionalButton')} onBlur={handleBlur} spotlightId="optionalButton" type={TYPES.terms} @@ -619,6 +636,8 @@ export default function IntroPanel({ className={css.selectAllCheckbox} selected={selectAllChecked} onToggle={handleSelectAllToggle} + onFocus={() => handleFocus('selectAllCheckbox')} + onBlur={handleBlur} spotlightId="selectAllCheckbox" ariaLabel={$L("Select All checkbox")} /> @@ -632,6 +651,8 @@ export default function IntroPanel({ handleFocus('agreeButton')} + onBlur={handleBlur} spotlightId="agreeButton" type={TYPES.agree} ariaLabel={$L("Agree to terms")} @@ -643,6 +664,8 @@ export default function IntroPanel({ handleFocus('disagreeButton')} + onBlur={handleBlur} spotlightId="disagreeButton" type={TYPES.agree} ariaLabel={$L("Do not agree to terms")} @@ -710,8 +733,8 @@ export default function IntroPanel({ hasButton button1Text={popupState.button1Text || $L("OK")} hasText - title={$L("Required Terms")} - text={$L("Please agree to Terms & Conditions and Privacy Policy.")} + title={popupState.title || ""} + text={popupState.text || ""} /> )} diff --git a/com.twin.app.shoptime/src/views/MyPagePanel/MyPageSub/TermsOfService/TermsOfService.jsx b/com.twin.app.shoptime/src/views/MyPagePanel/MyPageSub/TermsOfService/TermsOfService.jsx index fdf61fad..d32cbb7f 100644 --- a/com.twin.app.shoptime/src/views/MyPagePanel/MyPageSub/TermsOfService/TermsOfService.jsx +++ b/com.twin.app.shoptime/src/views/MyPagePanel/MyPageSub/TermsOfService/TermsOfService.jsx @@ -49,7 +49,7 @@ export default function TermsOfService({ title, cbScrollTo }) { const [optionalDisagreePopupOpen, setOptionalDisagreePopupOpen] = useState(false); const [showCheckboxAlert, setShowCheckboxAlert] = useState(false); - + const agreePopupTimeoutRef = useRef(null); const { popupVisible } = useSelector((state) => state.common?.popup); const { optionalTermsAgree } = useSelector((state) => state.common); @@ -60,16 +60,33 @@ export default function TermsOfService({ title, cbScrollTo }) { ); const [spotlightDisabled, setSpotlightDisabled] = useState(true); const [termsList, setTermsList] = useState([]); + const termsListRef = useRef(termsList); const dispatch = useDispatch(); const focusJob = useRef(null); - useEffect(() => { + useEffect(() => { + return () => { + if (agreePopupTimeoutRef.current) { + clearTimeout(agreePopupTimeoutRef.current); + } + }; + }, []); + + useEffect(() => { dispatch(fetchCurrentUserHomeTerms()); }, [dispatch]); useEffect(() => { + // console.log( + // "[TermsOfService] termsData:", + // JSON.stringify(termsData, null, 2) + // ); + // console.log( + // "[TermsOfService] empTermsData:", + // JSON.stringify(empTermsData, null, 2) + // ); const newTabList = []; const tempList = []; const newTrmsCdList = []; @@ -106,11 +123,19 @@ export default function TermsOfService({ title, cbScrollTo }) { setTabList(newTabList); setTermsList(tempList); + // console.log( + // "[TermsOfService] Final termsList:", + // JSON.stringify(tempList, null, 2) + // ); setTrmsCdList(newTrmsCdList); setSpotlightDisabled(false); }, [termsData, empTermsData, webOSVersion]); + useEffect(() => { + termsListRef.current = termsList; + }, [termsList]); + useEffect(() => { if (termsData && termsData.data && termsData.data.terms) { dispatch(getTermsAgreeYn()); @@ -216,39 +241,47 @@ export default function TermsOfService({ title, cbScrollTo }) { ); const handleOptionalAgree = useCallback(() => { - console.log( - "handleOptionalAgree called with isChecked:", - isOptionalChecked - ); - if (isOptionalChecked) { - const firstThreeTermIds = termsList - .slice(0, 3) - .map((term) => term.termsId) - .filter(Boolean); + // console.log( + // "handleOptionalAgree called with isChecked:", + // isOptionalChecked + // ); + // if (isOptionalChecked) { + const firstThreeTermIds = termsList + .slice(0, 3) + .map((term) => term.termsId) + .filter(Boolean); - if (firstThreeTermIds.length > 0) { - const payload = { termsList: firstThreeTermIds, notTermsList: [] }; - console.log("Dispatching setMyPageTermsAgree with payload:", payload); - dispatch( - setMyPageTermsAgree(payload, (response) => { - console.log("setMyPageTermsAgree callback response:", response); - if (response.retCode === "000" || response.retCode === 0) { - console.log("Optional terms agreement successful."); - // 약관 동의의 후 약관 정보 조회 - dispatch(fetchCurrentUserHomeTerms()); - setAgreePopup(true); - } else { - console.error("Optional terms agreement failed:", response); + if (firstThreeTermIds.length > 0) { + const payload = { termsList: firstThreeTermIds, notTermsList: [] }; + // console.log("Dispatching setMyPageTermsAgree with payload:", payload); + dispatch( + setMyPageTermsAgree(payload, (response) => { + // console.log("setMyPageTermsAgree callback response:", response); + if (response.retCode === "000" || response.retCode === 0) { + console.log("Optional terms agreement successful."); + // 약관 동의의 후 약관 정보 조회 + dispatch(fetchCurrentUserHomeTerms()); + setAgreePopup(true); + + if (agreePopupTimeoutRef.current) { + clearTimeout(agreePopupTimeoutRef.current); } - }) - ); - } else { - console.log("No terms found to agree to."); - } + + agreePopupTimeoutRef.current = setTimeout(() => { + setAgreePopup(false); + }, 3000); + } else { + console.error("Optional terms agreement failed:", response); + } + }) + ); } else { - console.log("Checkbox not checked, not proceeding with agreement."); - setShowCheckboxAlert(true); + // console.log("No terms found to agree to."); } + // } else { + // console.log("Checkbox not checked, not proceeding with agreement."); + // setShowCheckboxAlert(true); + // } }, [dispatch, termsList, isOptionalChecked]); const handleOptionalDisagree = useCallback(() => { @@ -256,32 +289,43 @@ export default function TermsOfService({ title, cbScrollTo }) { }, []); const confirmOptionalDisagree = useCallback(() => { - const optionalTerm = termsList.find( + const currentTermsList = termsListRef.current; + // console.log("optionalTermsAgree:", currentTermsList); + const optionalTerm = currentTermsList.find( (term) => term.trmsTpCd === "MST00405" ); + // console.log("optionalTerm:", JSON.stringify(optionalTerm, null, 2)); + + // if (optionalTerm) { + // console.log("optionalTerm.termsId:", optionalTerm.termsId); + // } + if (optionalTerm && optionalTerm.termsId) { const payload = { mandatoryIncludeYn: "N", termsList: [optionalTerm.termsId], }; - console.log("Dispatching setMyTermsWithdraw with payload:", payload); + // console.log("Dispatching setMyTermsWithdraw with payload:", payload); // 선택약관 철회 - dispatch(setMyTermsWithdraw(payload, (response) => { - console.log("setMyTermsWithdraw callback response:", response); - if (response.retCode === "000" || response.retCode === 0) { - console.log("Optional terms withdrawal successful."); - // 약관 철회 후 약관 정보 조회 - dispatch(fetchCurrentUserHomeTerms()); - setIsOptionalChecked(false); - } else { - console.error("Optional terms withdrawal failed:", response); - } - })); + dispatch( + setMyTermsWithdraw(payload, (response) => { + // console.log("setMyTermsWithdraw callback response:", response); + if (response.retCode === "000" || response.retCode === 0) { + console.log("Optional terms withdrawal successful."); + // 약관 철회 후 약관 정보 조회 + dispatch(fetchCurrentUserHomeTerms()); + setIsOptionalChecked(false); + } else { + console.error("Optional terms withdrawal failed:", response); + } + setOptionalDisagreePopupOpen(false); + }) + ); } else { - console.log("No optional term found to disagree."); + // console.log("No optional term found to disagree."); + setOptionalDisagreePopupOpen(false); } - setOptionalDisagreePopupOpen(false); - }, [termsList, dispatch]); + }, [dispatch]); const onCancelDisagree = useCallback(() => { // setDisagreeConfirmOpen(false); @@ -462,23 +506,27 @@ export default function TermsOfService({ title, cbScrollTo }) { /> setAgreePopup(false)} - hasButton - button1Text={$L("OK")} hasText - title={$L("Agreement Complete")} - text={$L("Your agreement has been processed.")} + title={$L("")} + text={$L( + "From now on, you can receive personalized shopping benefits." + )} /> {/* 새로 추가된 optionalAgreement 팝업 */} setOptionalDisagreePopupOpen(false)} - onClick={confirmOptionalDisagree} + onExit={confirmOptionalDisagree} + hasButton + button1Text={$L("Yes")} + button2Text={$L("No")} hasText + title={$L("")} text={$L("You will not receive personalized shopping benefits")} /> {/* 필수약관 체크 확인 팝업 */}