fix: 선택약관관련 수정 250619
This commit is contained in:
@@ -44,6 +44,7 @@ import css from "./App.module.less";
|
||||
import { handleBypassLink } from "./bypassLinkHandler";
|
||||
import { handleDeepLink } from "./deepLinkHandler";
|
||||
import { sendLogTotalRecommend } from "../actions/logActions";
|
||||
import { startFocusMonitoring, stopFocusMonitoring } from '../utils/focus-monitor';
|
||||
|
||||
let foreGroundChangeTimer = null;
|
||||
|
||||
@@ -121,22 +122,21 @@ function AppBase(props) {
|
||||
(state) => state.common.appStatus.cursorVisible
|
||||
);
|
||||
const introTermsAgree = useSelector((state) => state.common.introTermsAgree);
|
||||
const optionalTermsAgree = useSelector(
|
||||
(state) => state.common.optionalTermsAgree
|
||||
);
|
||||
// const optionalTermsAgree = useSelector((state) => state.common.optionalTermsAgree);
|
||||
const termsLoading = useSelector((state) => state.common.termsLoading);
|
||||
// termsFlag 전체 상태 확인
|
||||
const termsFlag = useSelector((state) => state.common.termsFlag);
|
||||
// const termsFlag = useSelector((state) => state.common.termsFlag);
|
||||
const termsData = useSelector((state) => state.home.termsData);
|
||||
|
||||
const shouldShowOptionalTermsPopup = useMemo(() => {
|
||||
const terms = termsData?.data?.terms;
|
||||
if (!terms) {
|
||||
return false;
|
||||
}
|
||||
const optionalTerm = terms.find((term) => term.trmsTpCd === "MST00405");
|
||||
return optionalTerm ? optionalTerm.trmsPopFlag === "Y" : false;
|
||||
}, [termsData]);
|
||||
|
||||
// const shouldShowOptionalTermsPopup = useMemo(() => {
|
||||
// const terms = termsData?.data?.terms;
|
||||
// if (!terms) {
|
||||
// return false;
|
||||
// }
|
||||
// const optionalTerm = terms.find(term => term.trmsTpCd === "MST00405");
|
||||
// return optionalTerm ? optionalTerm.trmsPopFlag === 'Y' && optionalTerm.trmsAgrFlag === 'N' : false;
|
||||
// }, [termsData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (termsData?.data?.terms) {
|
||||
dispatch(getTermsAgreeYn());
|
||||
@@ -179,6 +179,23 @@ function AppBase(props) {
|
||||
}, 5000)
|
||||
);
|
||||
|
||||
// 컴포넌트에서 모니터링 시작 - 한시적 모니터링
|
||||
// useEffect(() => {
|
||||
// startFocusMonitoring();
|
||||
// return () => stopFocusMonitoring();
|
||||
// }, []);
|
||||
|
||||
// 임시 작업용 코드
|
||||
// useEffect(() => {
|
||||
// const timer = setTimeout(() => {
|
||||
// dispatch(
|
||||
// pushPanel({ name: Config.panel_names.INTRO_PANEL, panelInfo: {} })
|
||||
// );
|
||||
// }, 1500);
|
||||
|
||||
// return () => clearTimeout(timer);
|
||||
// }, [dispatch]);
|
||||
|
||||
// called by [receive httpHeader, launch, relaunch]
|
||||
const initService = useCallback(
|
||||
(haveyInit = true) => {
|
||||
@@ -370,29 +387,38 @@ function AppBase(props) {
|
||||
}
|
||||
}, [webOSVersion, deviceId]);
|
||||
|
||||
|
||||
// 테스트용 팝업 표시
|
||||
// useEffect(() => {
|
||||
// setTimeout(() => {
|
||||
// console.log("App.js optionalTermsTest 팝업 표시");
|
||||
// dispatch(setShowPopup({ activePopup: "optionalTermsTest" }));
|
||||
// dispatch(setShowPopup({ activePopup: "optionalTermsConfirmBottom" }));
|
||||
// }, 3000);
|
||||
// }, [dispatch]);
|
||||
|
||||
// 약관 동의 및 선택 약관 팝업 처리
|
||||
useEffect(() => {
|
||||
if (introTermsAgree === undefined) {
|
||||
if (introTermsAgree === undefined || termsLoading) {
|
||||
// 약관 동의 여부 확인 전에는 아무것도 하지 않음
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (introTermsAgree) {
|
||||
// 필수 약관에 동의한 경우
|
||||
if (shouldShowOptionalTermsPopup) {
|
||||
// if (shouldShowOptionalTermsPopup) {
|
||||
// 선택 약관 팝업을 띄워야 하는 경우
|
||||
dispatch(setShowPopup({ activePopup: "optionalTermsTest" }));
|
||||
} else {
|
||||
// 3초 후에 팝업을 띄우도록 설정
|
||||
// console.log("App.js optionalTermsTest 팝업 표시");
|
||||
// const timer = setTimeout(() => {
|
||||
// dispatch(setShowPopup({ activePopup: "optionalTermsConfirm" }));
|
||||
// }, 3000); // 3000 milliseconds = 3 seconds
|
||||
|
||||
// 컴포넌트 언마운트 시 타이머 클리어
|
||||
// return () => clearTimeout(timer);
|
||||
// } else {
|
||||
// 선택 약관 팝업이 필요 없는 경우, 바로 서비스 초기화
|
||||
initService(true);
|
||||
}
|
||||
// }
|
||||
} else {
|
||||
// 필수 약관에 동의하지 않은 경우
|
||||
dispatch(
|
||||
@@ -400,7 +426,7 @@ function AppBase(props) {
|
||||
);
|
||||
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
|
||||
}
|
||||
}, [introTermsAgree, shouldShowOptionalTermsPopup, dispatch, initService]);
|
||||
}, [introTermsAgree, dispatch, initService]);
|
||||
|
||||
useEffect(() => {
|
||||
const launchParmas = getLaunchParams();
|
||||
@@ -432,21 +458,19 @@ function AppBase(props) {
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<>
|
||||
{webOSVersion === "" ? null : Number(webOSVersion) < 4 ? (
|
||||
<NotSupportedVersion />
|
||||
) : (
|
||||
<MainView
|
||||
initService={initService}
|
||||
className={
|
||||
typeof window === "object" &&
|
||||
!window.PalmSystem &&
|
||||
!cursorVisible &&
|
||||
css.preventMouse
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
{webOSVersion === "" ? null : Number(webOSVersion) < 4 ? (
|
||||
<NotSupportedVersion />
|
||||
) : (
|
||||
<MainView
|
||||
initService={initService}
|
||||
className={
|
||||
typeof window === "object" &&
|
||||
!window.PalmSystem &&
|
||||
!cursorVisible &&
|
||||
css.preventMouse
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// src/actions/actionTypes.js
|
||||
|
||||
export const types = {
|
||||
// panel actions
|
||||
PUSH_PANEL: "PUSH_PANEL",
|
||||
@@ -209,4 +211,9 @@ export const types = {
|
||||
|
||||
// new actions
|
||||
CANCEL_FOCUS_ELEMENT: "CANCEL_FOCUS_ELEMENT",
|
||||
|
||||
// 약관동의 여부 확인 상태
|
||||
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",
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// src/actions/commonActions.js
|
||||
|
||||
import { Job } from "@enact/core/util";
|
||||
import Spotlight from "@enact/spotlight";
|
||||
|
||||
@@ -286,50 +288,110 @@ export const getDeviceId = (onComplete) => (dispatch, getState) => {
|
||||
};
|
||||
|
||||
export const getTermsAgreeYn = () => (dispatch, getState) => {
|
||||
const { terms } = getState().home.termsData.data;
|
||||
dispatch({ type: types.GET_TERMS_AGREE_YN_START });
|
||||
|
||||
try {
|
||||
const { terms } = getState().home.termsData.data;
|
||||
|
||||
// console.log("getTermsAgreeYn", terms);
|
||||
console.log("getTermsAgreeYn", terms.map(term => ({
|
||||
trmsId: term.trmsId,
|
||||
trmsTpCd: term.trmsTpCd,
|
||||
trmsAgrFlag: term.trmsAgrFlag
|
||||
})));
|
||||
console.log("getTermsAgreeYn", terms.map(term => ({
|
||||
trmsId: term.trmsId,
|
||||
trmsTpCd: term.trmsTpCd,
|
||||
trmsAgrFlag: term.trmsAgrFlag,
|
||||
trmsPopFlag: term.trmsPopFlag,
|
||||
})));
|
||||
|
||||
const termsAgreeFlag = terms.reduce((acc, term) => {
|
||||
switch (term.trmsTpCd) {
|
||||
case "MST00401":
|
||||
acc.privacyTerms = term.trmsAgrFlag;
|
||||
break;
|
||||
|
||||
case "MST00402":
|
||||
acc.serviceTerms = term.trmsAgrFlag;
|
||||
break;
|
||||
|
||||
case "MST00403":
|
||||
acc.purchaseTerms = term.trmsAgrFlag;
|
||||
break;
|
||||
|
||||
case "MST00404":
|
||||
acc.paymentTerms = term.trmsAgrFlag;
|
||||
break;
|
||||
|
||||
case "MST00405":
|
||||
acc.optionalTerms = term.trmsAgrFlag;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
// MST00405 선택약관 정보만 따로 출력
|
||||
const optionalTerm = terms.find(term => term.trmsTpCd === 'MST00405');
|
||||
if (optionalTerm) {
|
||||
console.log("getTermsAgreeYn MST00405 선택약관:", {
|
||||
trmsId: optionalTerm.trmsId,
|
||||
trmsTpCd: optionalTerm.trmsTpCd,
|
||||
trmsAgrFlag: optionalTerm.trmsAgrFlag,
|
||||
trmsPopFlag: optionalTerm.trmsPopFlag
|
||||
});
|
||||
} else {
|
||||
console.log("getTermsAgreeYn MST00405 선택약관을 찾을 수 없습니다.");
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
const termsAgreeFlag = terms.reduce((acc, term) => {
|
||||
switch (term.trmsTpCd) {
|
||||
case "MST00401":
|
||||
acc.privacyTerms = term.trmsAgrFlag;
|
||||
break;
|
||||
case "MST00402":
|
||||
acc.serviceTerms = term.trmsAgrFlag;
|
||||
break;
|
||||
case "MST00403":
|
||||
acc.purchaseTerms = term.trmsAgrFlag;
|
||||
break;
|
||||
case "MST00404":
|
||||
acc.paymentTerms = term.trmsAgrFlag;
|
||||
break;
|
||||
case "MST00405":
|
||||
acc.optionalTerms = term.trmsAgrFlag;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
dispatch({
|
||||
type: types.GET_TERMS_AGREE_YN,
|
||||
payload: termsAgreeFlag,
|
||||
});
|
||||
dispatch({
|
||||
type: types.GET_TERMS_AGREE_YN_SUCCESS,
|
||||
payload: termsAgreeFlag,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("getTermsAgreeYn error:", error);
|
||||
dispatch({ type: types.GET_TERMS_AGREE_YN_FAILURE });
|
||||
}
|
||||
};
|
||||
|
||||
// export const getTermsAgreeYn = () => (dispatch, getState) => {
|
||||
// const { terms } = getState().home.termsData.data;
|
||||
|
||||
// // console.log("getTermsAgreeYn", terms);
|
||||
// console.log("getTermsAgreeYn", terms.map(term => ({
|
||||
// trmsId: term.trmsId,
|
||||
// trmsTpCd: term.trmsTpCd,
|
||||
// trmsAgrFlag: term.trmsAgrFlag,
|
||||
// trmsPopFlag: term.trmsPopFlag,
|
||||
// })));
|
||||
|
||||
// const termsAgreeFlag = terms.reduce((acc, term) => {
|
||||
// switch (term.trmsTpCd) {
|
||||
// case "MST00401":
|
||||
// acc.privacyTerms = term.trmsAgrFlag;
|
||||
// break;
|
||||
|
||||
// case "MST00402":
|
||||
// acc.serviceTerms = term.trmsAgrFlag;
|
||||
// break;
|
||||
|
||||
// case "MST00403":
|
||||
// acc.purchaseTerms = term.trmsAgrFlag;
|
||||
// break;
|
||||
|
||||
// case "MST00404":
|
||||
// acc.paymentTerms = term.trmsAgrFlag;
|
||||
// break;
|
||||
|
||||
// case "MST00405":
|
||||
// acc.optionalTerms = term.trmsAgrFlag;
|
||||
// break;
|
||||
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
|
||||
// return acc;
|
||||
// }, {});
|
||||
|
||||
// dispatch({
|
||||
// type: types.GET_TERMS_AGREE_YN,
|
||||
// payload: termsAgreeFlag,
|
||||
// });
|
||||
// };
|
||||
|
||||
export const launchMembershipApp = () => (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const panels = state.panels.panels;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { runDelayedAction, setTokenRefreshing, TAxios } from "../api/TAxios";
|
||||
import * as lunaSend from "../lunaSend";
|
||||
import { types } from "./actionTypes";
|
||||
import { changeLocalSettings } from "./commonActions";
|
||||
import { fetchCurrentUserHomeTerms } from "./homeActions";
|
||||
|
||||
// IF-LGSP-000 인증코드 요청
|
||||
export const getAuthenticationCode = () => (dispatch, getState) => {
|
||||
@@ -48,6 +49,7 @@ export const registerDevice = (params) => (dispatch, getState) => {
|
||||
retCode: response.data.retCode,
|
||||
});
|
||||
dispatch(getAuthenticationCode());
|
||||
dispatch(fetchCurrentUserHomeTerms());
|
||||
};
|
||||
|
||||
const onFail = (error) => {
|
||||
|
||||
@@ -37,6 +37,54 @@ export const getHomeTerms = (props) => (dispatch, getState) => {
|
||||
);
|
||||
};
|
||||
|
||||
// 현재 로그인 사용자 기준으로 약관 정보 조회 (인자 없이 호출 가능)
|
||||
export const fetchCurrentUserHomeTerms = () => (dispatch, getState) => {
|
||||
const loginUserData = getState().common.appStatus.loginUserData;
|
||||
|
||||
if (!loginUserData || !loginUserData.userNumber) {
|
||||
console.error("fetchCurrentUserHomeTerms: userNumber (mbrNo) is not available. User might not be logged in.");
|
||||
dispatch({ type: types.GET_TERMS_AGREE_YN_FAILURE });
|
||||
return;
|
||||
}
|
||||
|
||||
const mbrNo = loginUserData.userNumber;
|
||||
const trmsTpCdList = "MST00401, MST00402, MST00405"; // 기본 약관 코드 리스트
|
||||
|
||||
const onSuccess = (response) => {
|
||||
console.log("fetchCurrentUserHomeTerms onSuccess ", response.data);
|
||||
|
||||
if (response.data.retCode === 0) {
|
||||
dispatch({
|
||||
type: types.GET_HOME_TERMS, // 기존 GET_HOME_TERMS 타입을 재사용
|
||||
payload: response.data,
|
||||
});
|
||||
// getHomeTerms와 동일하게 getTermsAgreeYn 후속 처리
|
||||
setTimeout(() => {
|
||||
dispatch(getTermsAgreeYn());
|
||||
}, 0);
|
||||
} else {
|
||||
// retCode가 0이 아닌 경우 실패로 처리
|
||||
dispatch({ type: types.GET_TERMS_AGREE_YN_FAILURE });
|
||||
}
|
||||
};
|
||||
|
||||
const onFail = (error) => {
|
||||
console.error("fetchCurrentUserHomeTerms onFail ", error);
|
||||
dispatch({ type: types.GET_TERMS_AGREE_YN_FAILURE });
|
||||
};
|
||||
|
||||
TAxios(
|
||||
dispatch,
|
||||
getState,
|
||||
"get",
|
||||
URLS.GET_HOME_TERMS, // 동일한 API 엔드포인트 사용
|
||||
{ trmsTpCdList, mbrNo },
|
||||
{},
|
||||
onSuccess,
|
||||
onFail
|
||||
);
|
||||
};
|
||||
|
||||
// 메뉴 목록 조회 IF-LGSP-044
|
||||
export const getHomeMenu = () => (dispatch, getState) => {
|
||||
const onSuccess = (response) => {
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TNewPopUp from '../TPopUp/TNewPopUp'; // TNewPopUp 컴포넌트의 정확한 경로를 확인해주세요.
|
||||
import css from './OptionalConfirm.module.less';
|
||||
|
||||
/**
|
||||
* 선택 약관 동의 팝업 컴포넌트입니다.
|
||||
* 이 컴포넌트는 약관 동의 UI를 표시하며, 내부에서 다른 팝업을 관리하지 않습니다.
|
||||
* 버튼 클릭 이벤트는 props로 전달받아 상위 컴포넌트에서 처리합니다.
|
||||
*/
|
||||
const OptionalConfirm = ({
|
||||
open,
|
||||
spotlightId,
|
||||
className,
|
||||
onOptionalTermsClick, // 약관 자세히 보기 버튼 클릭 핸들러
|
||||
onOptionalAgreeClick, // 동의 버튼 클릭 핸들러
|
||||
onOptionalDeclineClick, // 거절 또는 다음에 하기 버튼 클릭 핸들러
|
||||
customPosition,
|
||||
position,
|
||||
}) => {
|
||||
return (
|
||||
<TNewPopUp
|
||||
kind="optionalConfirm" // 이 kind에 따라 TNewPopUp 내부 UI/UX가 결정됩니다.
|
||||
open={open}
|
||||
spotlightId={spotlightId}
|
||||
spotlightRestrict="self-only" // 필요에 따라 props로 설정 가능하게 할 수 있습니다.
|
||||
className={`${css.optionalConfirmPopup} ${className || ''}`.trim()}
|
||||
onOptionalTermsClick={onOptionalTermsClick}
|
||||
onOptionalAgreeClick={onOptionalAgreeClick}
|
||||
onOptionalDeclineClick={onOptionalDeclineClick}
|
||||
customPosition={customPosition}
|
||||
position={position}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
OptionalConfirm.propTypes = {
|
||||
/** 팝업의 표시 여부 */
|
||||
open: PropTypes.bool.isRequired,
|
||||
/** Spotlight ID */
|
||||
spotlightId: PropTypes.string.isRequired,
|
||||
/** 추가적인 CSS 클래스 */
|
||||
className: PropTypes.string,
|
||||
/** 약관 자세히 보기 버튼 클릭 시 호출될 함수 */
|
||||
onOptionalTermsClick: PropTypes.func,
|
||||
/** 동의 버튼 클릭 시 호출될 함수 */
|
||||
onOptionalAgreeClick: PropTypes.func,
|
||||
/** 거절 또는 다음에 하기 버튼 클릭 시 호출될 함수 */
|
||||
onOptionalDeclineClick: PropTypes.func,
|
||||
/** 사용자 정의 위치 사용 여부 */
|
||||
customPosition: PropTypes.bool,
|
||||
/** 사용자 정의 위치 값 (customPosition이 true일 때 사용) */
|
||||
position: PropTypes.object,
|
||||
};
|
||||
|
||||
OptionalConfirm.defaultProps = {
|
||||
className: '',
|
||||
onOptionalTermsClick: () => console.log('OptionalConfirm: onOptionalTermsClick not provided'),
|
||||
onOptionalAgreeClick: () => console.log('OptionalConfirm: onOptionalAgreeClick not provided'),
|
||||
onOptionalDeclineClick: () => console.log('OptionalConfirm: onOptionalDeclineClick not provided'),
|
||||
customPosition: false,
|
||||
// position: {},
|
||||
};
|
||||
|
||||
export default OptionalConfirm;
|
||||
@@ -0,0 +1,12 @@
|
||||
.optionalConfirmPopup {
|
||||
width: 1920px;
|
||||
height: auto;
|
||||
background-color: white !important;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 20px 70px rgba(2, 3, 3, 0.7) !important;
|
||||
position: absolute !important;
|
||||
// top: 882px !important;
|
||||
left: 0 !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,10 +17,7 @@ const OptionalTermsConfirm = ({ open }) => {
|
||||
const [isChecked, setIsChecked] = useState(false);
|
||||
const [isTermsPopupVisible, setIsTermsPopupVisible] = useState(false);
|
||||
const [isWarningPopupVisible, setIsWarningPopupVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("OptionalTermsTest - in Component Rendered");
|
||||
}, []);
|
||||
|
||||
|
||||
const optionalTermsData = useSelector((state) =>
|
||||
state.home.termsData?.data?.terms.find(term => term.trmsTpCd === "MST00405")
|
||||
@@ -132,10 +129,10 @@ const OptionalTermsConfirm = ({ open }) => {
|
||||
<TPopUp
|
||||
kind="introTermsPopup"
|
||||
open={open}
|
||||
onClose={handleMainPopupClose}
|
||||
// onClose={handleMainPopupClose}
|
||||
spotlightId="optional-terms-test-popup"
|
||||
className={css.testPopup}
|
||||
type="none"
|
||||
// type="fullscreen"
|
||||
// style={{
|
||||
// position: 'absolute',
|
||||
// top: '100px',
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
// src/components/Optional/OptionalTermsConfirm.module.less
|
||||
.testPopup {
|
||||
width: 958px;
|
||||
// width: 1920px;
|
||||
height: 300px;
|
||||
background-color: white !important;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 20px 70px rgba(2, 3, 3, 0.7) !important;
|
||||
position: absolute !important;
|
||||
top: 45% !important;
|
||||
left: 450px !important;
|
||||
|
||||
}
|
||||
|
||||
left: 470px !important;
|
||||
}
|
||||
|
||||
.contentContainer {
|
||||
// width: 958px;
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
// src/components/Optional/OptionalTermsConfirm.jsx
|
||||
|
||||
import React, { useEffect, useCallback, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import TNewPopUp from '../TPopUp/TNewPopUp';
|
||||
import TButton from '../TButton/TButton';
|
||||
import TCheckBoxSquare from '../TCheckBox/TCheckBoxSquare';
|
||||
import TButtonScroller from '../TButtonScroller/TButtonScroller';
|
||||
import { $L, scaleH, scaleW } from '../../utils/helperMethods';
|
||||
import { setHidePopup } from '../../actions/commonActions';
|
||||
import { setMyPageTermsAgree } from '../../actions/myPageActions';
|
||||
import css from './OptionalTermsConfirmBottom.module.less';
|
||||
import cssPopup from '../TPopUp/TNewPopUp.module.less';
|
||||
import Spotlight from "@enact/spotlight";
|
||||
|
||||
const OptionalTermsConfirm = ({ open }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [isChecked, setIsChecked] = useState(false);
|
||||
const [isTermsPopupVisible, setIsTermsPopupVisible] = useState(false);
|
||||
const [isWarningPopupVisible, setIsWarningPopupVisible] = useState(false);
|
||||
|
||||
|
||||
const optionalTermsData = useSelector((state) =>
|
||||
state.home.termsData?.data?.terms.find(term => term.trmsTpCd === "MST00405")
|
||||
);
|
||||
|
||||
|
||||
// 포커스 복원을 위한 useEffect 추가
|
||||
useEffect(() => {
|
||||
if (open && !isTermsPopupVisible && !isWarningPopupVisible) {
|
||||
const timer = setTimeout(() => {
|
||||
Spotlight.focus("optional-terms-confirm-popup");
|
||||
}, 150); // 약간 더 긴 지연
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [open, isTermsPopupVisible, isWarningPopupVisible]);
|
||||
|
||||
const handleMainPopupClose = useCallback(() => {
|
||||
dispatch(setHidePopup());
|
||||
}, [dispatch]);
|
||||
|
||||
const handleCheckboxToggle = useCallback(({ selected }) => {
|
||||
setIsChecked(selected);
|
||||
}, []);
|
||||
|
||||
const handleViewTermsClick = useCallback(() => {
|
||||
setIsTermsPopupVisible(true);
|
||||
}, []);
|
||||
|
||||
const handleCloseTermsPopup = useCallback((e) => {
|
||||
if (e) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
setIsTermsPopupVisible(false);
|
||||
Spotlight.focus("optional-terms-confirm-popup");
|
||||
}, []);
|
||||
|
||||
const handleTermsPopupClosed = useCallback(() => {
|
||||
setTimeout(() => {
|
||||
Spotlight.focus("optional-terms-confirm-popup");
|
||||
}, 50);
|
||||
}, []);
|
||||
|
||||
const handleAgreeTest = useCallback(() => {
|
||||
console.log("handleAgreeTest");
|
||||
Spotlight.pause();
|
||||
setIsTermsPopupVisible(false);
|
||||
// 상태 업데이트 후 DOM이 완전히 렌더링될 때까지 기다린 후 포커스
|
||||
setTimeout(() => {
|
||||
Spotlight.resume();
|
||||
Spotlight.focus("optional-terms-confirm-popup");
|
||||
}, 500); // 50ms에서 100ms로 증가
|
||||
|
||||
}, []);
|
||||
|
||||
const handleAgree = useCallback(() => {
|
||||
if (isChecked) {
|
||||
// 약관 동의할 항목들 (string array)
|
||||
const termsList = ["TID0000222", "TID0000223", "TID0000232"];
|
||||
|
||||
// 동의하지 않을 항목들 (빈 배열)
|
||||
const notTermsList = [];
|
||||
|
||||
console.log('OptionalTermsConfirm -약관 동의 API 호출 파라미터:', { termsList, notTermsList });
|
||||
|
||||
const callback = (response) => {
|
||||
if (response.retCode === "000" || response.retCode === 0) {
|
||||
console.log('약관 동의 성공:', response);
|
||||
} else {
|
||||
console.error('약관 동의 실패:', response);
|
||||
}
|
||||
};
|
||||
|
||||
console.log('OptionalTermsConfirm - 약관 동의 API 호출 payload:', { termsList, notTermsList });
|
||||
dispatch(setMyPageTermsAgree({ termsList, notTermsList }, callback));
|
||||
dispatch(setHidePopup());
|
||||
} else {
|
||||
setIsWarningPopupVisible(true);
|
||||
}
|
||||
}, [isChecked, dispatch]);
|
||||
|
||||
const handleCloseWarningPopup = useCallback(() => {
|
||||
setIsWarningPopupVisible(false);
|
||||
Spotlight.focus("optional-terms-confirm-popup");
|
||||
}, []);
|
||||
|
||||
const handleDontAskAgain = () => {
|
||||
console.log("Don't Ask Again 처리 필요");
|
||||
dispatch(setHidePopup());
|
||||
};
|
||||
|
||||
if (isTermsPopupVisible) {
|
||||
return (
|
||||
<TNewPopUp
|
||||
kind="introTermsPopup"
|
||||
open
|
||||
onClose={handleTermsPopupClosed}
|
||||
onClick={handleCloseTermsPopup}
|
||||
onIntroTermsAgreeClick={handleAgreeTest}
|
||||
hasButton
|
||||
button1Text={$L("OK")}
|
||||
spotlightId="terms-viewer-popup"
|
||||
>
|
||||
{optionalTermsData && (
|
||||
<div className={css.termsViewerContent}>
|
||||
<div className={css.termsViewerTitle}>{$L("Optional Terms")}</div>
|
||||
<TButtonScroller
|
||||
boxHeight={scaleH(300)}
|
||||
width={scaleW(980)}
|
||||
className={css.termsDescription}
|
||||
>
|
||||
<div
|
||||
className={css.termsDesc}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: optionalTermsData.trmsCntt,
|
||||
}}
|
||||
/>
|
||||
</TButtonScroller>
|
||||
</div>
|
||||
)}
|
||||
</TNewPopUp>
|
||||
);
|
||||
}
|
||||
|
||||
if (isWarningPopupVisible) {
|
||||
return (
|
||||
<TNewPopUp
|
||||
kind="textPopup"
|
||||
open
|
||||
onClose={handleCloseWarningPopup}
|
||||
hasButton
|
||||
button1Text={$L("OK")}
|
||||
hasText
|
||||
title={$L("Agreement Required")}
|
||||
text={$L("Please agree to the Optional Terms.")}
|
||||
spotlightId="warning-popup"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TNewPopUp
|
||||
kind="optionalConfirm"
|
||||
open={open}
|
||||
spotlightId="optional-terms-confirm-popup"
|
||||
spotlightRestrict="self-only"
|
||||
className={css.optionalConfirmPopup}
|
||||
onOptionalTermsClick={handleViewTermsClick}
|
||||
onOptionalAgreeClick={handleAgree}
|
||||
onOptionalDeclineClick={handleDontAskAgain}
|
||||
customPosition={true}
|
||||
position={{
|
||||
position: 'absolute',
|
||||
top: '342px', // 가운데를 기준으로 한 좌표 (1080/2) - 198
|
||||
left: '0px',
|
||||
bottom: 'unset',
|
||||
transform: 'none',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default OptionalTermsConfirm;
|
||||
@@ -0,0 +1,13 @@
|
||||
// src/components/Optional/OptionalTermsConfirm.module.less
|
||||
|
||||
.optionalConfirmPopup {
|
||||
width: auto;
|
||||
height: auto;
|
||||
background-color: white !important;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 20px 70px rgba(2, 3, 3, 0.7) !important;
|
||||
position: absolute !important;
|
||||
// top: 882px !important;
|
||||
left: 0 !important;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
@SQUARE_BORDER_DEFAULT: #CCCCCC;
|
||||
@SQUARE_BORDER_ACTIVE: #C70850;
|
||||
@SQUARE_BG_SELECTED: #7A808D;
|
||||
// @SQUARE_BG_SELECTED: #C70850;
|
||||
;
|
||||
|
||||
.tCheckBoxSquare {
|
||||
min-width: 45px !important;
|
||||
@@ -21,6 +23,7 @@
|
||||
&:focus,
|
||||
&.focus {
|
||||
border-color: @SQUARE_BORDER_ACTIVE !important;
|
||||
border-width: 4px !important; // 🔥 포커스 시 굵은 테두리
|
||||
}
|
||||
|
||||
&::before {
|
||||
@@ -39,6 +42,7 @@
|
||||
|
||||
&.selected {
|
||||
border-color: @SQUARE_BG_SELECTED !important;
|
||||
border-width: 4px !important;
|
||||
background-color: @SQUARE_BG_SELECTED !important;
|
||||
&::before {
|
||||
transform: translate(-50%, -70%) rotate(-45deg) scale(1);
|
||||
@@ -46,9 +50,10 @@
|
||||
}
|
||||
|
||||
&.selectedFocus {
|
||||
border-color: @SQUARE_BG_SELECTED !important;
|
||||
border-color: @SQUARE_BORDER_ACTIVE !important;
|
||||
border-width: 4px !important;
|
||||
background-color: @SQUARE_BG_SELECTED !important;
|
||||
box-shadow: 0 0 0 4px fade(@SQUARE_BG_SELECTED, 20%) !important;
|
||||
box-shadow: 0 0 0 4px fade(@SQUARE_BORDER_ACTIVE, 20%) !important;
|
||||
&::before {
|
||||
transform: translate(-50%, -70%) rotate(-45deg) scale(1);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import React, { useCallback, useEffect, useMemo,useState } from "react";
|
||||
|
||||
import classNames from "classnames";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
@@ -31,6 +31,14 @@ const ButtonContainerNegative = SpotlightContainerDecorator(
|
||||
{ defaultElement: `[data-spotlight-id="${"tPopupBtn2"}"]` },
|
||||
"div"
|
||||
);
|
||||
const IntroTermsButtonContainer = SpotlightContainerDecorator(
|
||||
{ defaultElement: '[data-spotlight-id="introTermsAgreeBtn"]' },
|
||||
"div"
|
||||
);
|
||||
const OptionalConfirmButtonSection = SpotlightContainerDecorator(
|
||||
{ defaultElement: '[data-spotlight-id="optionalConfirmAgreeBtn"]' },
|
||||
"div"
|
||||
);
|
||||
const SpottableComponent = Spottable("li");
|
||||
const SpottableDiv = Spottable("div");
|
||||
|
||||
@@ -58,6 +66,7 @@ const KINDS = [
|
||||
"trackPackagePopup",
|
||||
"optionalAgreement",
|
||||
"normal",
|
||||
"optionalConfirm",
|
||||
];
|
||||
|
||||
// 각 kind별 클래스명 매핑
|
||||
@@ -207,6 +216,14 @@ const CLASS_MAPPINGS = {
|
||||
text: "optionalAgreementText",
|
||||
buttonContainer: "optionalAgreementButtonContainer",
|
||||
contentContainer: "optionalAgreementContentContainer"
|
||||
},
|
||||
optionalConfirm: {
|
||||
info: "optionalConfirmInfo",
|
||||
textLayer: "optionalConfirmTextLayer",
|
||||
title: "optionalConfirmTitle",
|
||||
text: "optionalConfirmText",
|
||||
buttonContainer: "optionalConfirmButtonContainer",
|
||||
contentContainer: "optionalConfirmContentContainer" // 👈 추가
|
||||
}
|
||||
};
|
||||
|
||||
@@ -250,6 +267,12 @@ export default function TNewPopUp({
|
||||
selectedIndex,
|
||||
spotlightId,
|
||||
onSpotlightRight,
|
||||
customPosition = false,
|
||||
position = {},
|
||||
onOptionalTermsClick, // Optional Terms 버튼용
|
||||
onOptionalAgreeClick, // Agree 버튼용
|
||||
onOptionalDeclineClick, // Not Now 버튼용
|
||||
onIntroTermsAgreeClick, // introTerms Agree 버튼용
|
||||
...rest
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
@@ -268,6 +291,7 @@ export default function TNewPopUp({
|
||||
};
|
||||
}
|
||||
const timerId = setTimeout(() => {
|
||||
console.log("focusTarget = ", SpotlightIds.TPOPUP);
|
||||
Spotlight.focus(SpotlightIds.TPOPUP);
|
||||
}, 0);
|
||||
|
||||
@@ -283,19 +307,74 @@ export default function TNewPopUp({
|
||||
}
|
||||
}, [kind]);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
let focusTarget = SpotlightIds.TPOPUP;
|
||||
if (kind === 'introTermsPopup') {
|
||||
focusTarget = 'introTermsAgreeBtn';
|
||||
} else if (kind === 'optionalConfirm') {
|
||||
focusTarget = 'optionalConfirmAgreeBtn';
|
||||
} else if (spotlightId) {
|
||||
focusTarget = spotlightId;
|
||||
}
|
||||
|
||||
const timerId = setTimeout(() => {
|
||||
console.log("focusTarget is ", focusTarget);
|
||||
Spotlight.focus(focusTarget);
|
||||
}, 0);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timerId);
|
||||
};
|
||||
}
|
||||
}, [kind, open, spotlightId]);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (open) {
|
||||
// const focusTarget = spotlightId || SpotlightIds.TPOPUP;
|
||||
// const timerId = setTimeout(() => {
|
||||
// Spotlight.focus(focusTarget);
|
||||
// }, 0);
|
||||
|
||||
// return () => {
|
||||
// clearTimeout(timerId);
|
||||
// };
|
||||
// }
|
||||
// }, [open, spotlightId]);
|
||||
|
||||
// 커스텀 스타일 생성
|
||||
const customStyle = useMemo(() => {
|
||||
if (!customPosition) return {};
|
||||
|
||||
return {
|
||||
position: position.position || 'fixed',
|
||||
top: position.top,
|
||||
bottom: position.bottom,
|
||||
left: position.left,
|
||||
right: position.right,
|
||||
transform: position.transform,
|
||||
zIndex: position.zIndex || 99999,
|
||||
...position.style // 추가 스타일
|
||||
};
|
||||
}, [customPosition, position]);
|
||||
|
||||
const ButtonContainerComp = useMemo(() => {
|
||||
return kind === "exitPopup" ? ButtonContainerNegative : ButtonContainer;
|
||||
if (kind === "exitPopup") {
|
||||
return ButtonContainerNegative;
|
||||
}
|
||||
if (kind === "introTermsPopup") {
|
||||
return IntroTermsButtonContainer;
|
||||
}
|
||||
if (kind === "optionalConfirm") {
|
||||
return OptionalConfirmButtonSection;
|
||||
}
|
||||
return ButtonContainer;
|
||||
}, [kind]);
|
||||
|
||||
// optionalAgreement
|
||||
// 자동으로 Yes/No 버튼 텍스트 설정
|
||||
|
||||
const finalButton1Text = useMemo(() => {
|
||||
if (kind === "optionalAgreement" && !button1Text) {
|
||||
return "Yes";
|
||||
}
|
||||
return button1Text;
|
||||
}, [kind, button1Text]);
|
||||
|
||||
|
||||
const finalButton2Text = useMemo(() => {
|
||||
if (kind === "optionalAgreement" && !button2Text) {
|
||||
@@ -309,6 +388,18 @@ export default function TNewPopUp({
|
||||
return hasButton || kind === "optionalAgreement";
|
||||
}, [hasButton, kind]);
|
||||
|
||||
|
||||
const finalButton1Text = useMemo(() => {
|
||||
if (kind === "optionalAgreement" && !button1Text) {
|
||||
return "Yes";
|
||||
}
|
||||
if (kind === "introTermsPopup" && !button1Text) {
|
||||
return "Close"; // 기존 버튼을 Close로 변경
|
||||
}
|
||||
return button1Text;
|
||||
}, [kind, button1Text]);
|
||||
|
||||
|
||||
//-------------------------------------------------------
|
||||
|
||||
const _onClick = useCallback(
|
||||
@@ -354,29 +445,67 @@ export default function TNewPopUp({
|
||||
[optionClick]
|
||||
);
|
||||
|
||||
const _onSpotlightRight = useCallback(
|
||||
(e) => {
|
||||
if (onSpotlightRight) onSpotlightRight(e);
|
||||
},
|
||||
[onSpotlightRight]
|
||||
);
|
||||
// 핸들러 함수들 추가
|
||||
const _onOptionalTermsClick = useCallback(() => {
|
||||
if (onOptionalTermsClick) {
|
||||
onOptionalTermsClick();
|
||||
}
|
||||
}, [onOptionalTermsClick]);
|
||||
|
||||
if (!open) {
|
||||
return null;
|
||||
const _onOptionalAgreeClick = useCallback(() => {
|
||||
if (onOptionalAgreeClick) {
|
||||
onOptionalAgreeClick();
|
||||
}
|
||||
}, [onOptionalAgreeClick]);
|
||||
|
||||
const _onOptionalDeclineClick = useCallback(() => {
|
||||
if (onOptionalDeclineClick) {
|
||||
onOptionalDeclineClick();
|
||||
}
|
||||
}, [onOptionalDeclineClick]);
|
||||
|
||||
const _onIntroTermsAgreeClick = useCallback(() => {
|
||||
if (onIntroTermsAgreeClick) {
|
||||
onIntroTermsAgreeClick();
|
||||
}
|
||||
}, [onIntroTermsAgreeClick]);
|
||||
|
||||
const _onSpotlightRight = useCallback(
|
||||
(e) => {
|
||||
if (onSpotlightRight) onSpotlightRight(e);
|
||||
},
|
||||
[onSpotlightRight]
|
||||
);
|
||||
const alertStyle = useMemo(() => {
|
||||
if (kind === 'optionalConfirm') {
|
||||
return {
|
||||
bottom: 'unset',
|
||||
transform: 'none'
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}, [kind]);
|
||||
|
||||
if (!open) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Alert
|
||||
{...rest}
|
||||
type={type}
|
||||
open={open}
|
||||
className={classNames(css.tNewPopUp, css[kind], className)}
|
||||
onClose={onClose}
|
||||
onClose={onClose}
|
||||
style={alertStyle}
|
||||
>
|
||||
<Container
|
||||
className={getClassName(kind, "info")}
|
||||
spotlightId={SpotlightIds.TPOPUP}
|
||||
spotlightDisabled={rest.spotlightDisabled}
|
||||
style={customPosition ? customStyle : undefined} // 👈 커스텀 스타일 적용
|
||||
>
|
||||
{hasOnClose && (
|
||||
<TButton
|
||||
@@ -476,9 +605,11 @@ export default function TNewPopUp({
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
|
||||
{/* 다른 종류들의 children */}
|
||||
{kind !== "optionalAgreement" && children}
|
||||
{kind !== "optionalAgreement" && kind !== "optionalConfirm" && children}
|
||||
|
||||
{hasIndicator && (
|
||||
<>
|
||||
@@ -502,6 +633,17 @@ export default function TNewPopUp({
|
||||
)}
|
||||
{shouldShowButtons && (
|
||||
<ButtonContainerComp className={getClassName(kind, "buttonContainer")}>
|
||||
{/* introTermsPopup일 때 Agree 버튼 먼저 표시 */}
|
||||
{kind === "introTermsPopup" && (
|
||||
<TButton
|
||||
spotlightId="introTermsAgreeBtn"
|
||||
onClick={_onIntroTermsAgreeClick}
|
||||
role="button"
|
||||
ariaLabel="Agree"
|
||||
>
|
||||
Agree
|
||||
</TButton>
|
||||
)}
|
||||
{finalButton1Text && (
|
||||
<TButton
|
||||
spotlightId="tPopupBtn1"
|
||||
@@ -524,6 +666,51 @@ export default function TNewPopUp({
|
||||
</TButton>
|
||||
)}
|
||||
</ButtonContainerComp>
|
||||
)}
|
||||
|
||||
{kind === "optionalConfirm" && (
|
||||
<div className={getClassName(kind, "optionalConfirmContentContainer")}>
|
||||
<div className={getClassName(kind, "optionalConfirmTextSection")}>
|
||||
Get recommendations, special offers, and ads tailored just for you.
|
||||
</div>
|
||||
<OptionalConfirmButtonSection className={getClassName(kind, "optionalConfirmButtonSection")}>
|
||||
<div className={getClassName(kind, "optionalConfirmLeftButtonSection")}>
|
||||
<TButton
|
||||
className={css.optionalTermsButton}
|
||||
onClick={_onOptionalTermsClick}
|
||||
spotlightId="optionalConfirmTermsBtn"
|
||||
type="terms"
|
||||
ariaLabel="Optional Terms"
|
||||
size="small"
|
||||
>
|
||||
<div className={css.optionalTermsTitle}>Optional Terms</div>
|
||||
</TButton>
|
||||
</div>
|
||||
<div className={getClassName(kind, "optionalConfirmRightButtonSection")}>
|
||||
<TButton
|
||||
className={getClassName(kind, "optionalConfirmButton")}
|
||||
onClick={_onOptionalAgreeClick}
|
||||
role="button"
|
||||
ariaLabel="Agree"
|
||||
spotlightId="optionalConfirmAgreeBtn"
|
||||
size="small"
|
||||
>
|
||||
Agree
|
||||
</TButton>
|
||||
<TButton
|
||||
className={getClassName(kind, "optionalConfirmButton")}
|
||||
onClick={_onOptionalDeclineClick}
|
||||
role="button"
|
||||
ariaLabel="Not Now"
|
||||
spotlightId="optionalConfirmDeclineBtn"
|
||||
size="small"
|
||||
>
|
||||
Not Now
|
||||
</TButton>
|
||||
|
||||
</div>
|
||||
</OptionalConfirmButtonSection>
|
||||
</div>
|
||||
)}
|
||||
</Container>
|
||||
</Alert>
|
||||
|
||||
@@ -1,6 +1,42 @@
|
||||
// src/components/TPopUp/TNewPopUp.module.less
|
||||
|
||||
@import "../../style/CommonStyle.module.less";
|
||||
@import "../../style/utils.module.less";
|
||||
|
||||
// 👇 TPopUp.module.less에서 가져온 floatLayer 스타일을 optionalConfirm용으로 수정
|
||||
// [id="floatLayer"] {
|
||||
// // optionalConfirm일 때만 다른 위치 적용
|
||||
// > div:not([id]) > div > div:nth-child(2) {
|
||||
// &:has(.src_components_TPopUp_TNewPopUp_optionalConfirm) {
|
||||
// bottom: unset !important;
|
||||
// transform: none !important;
|
||||
// overflow: unset;
|
||||
|
||||
// > div {
|
||||
// overflow: unset;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // 다른 팝업들은 기존 TPopUp 방식 유지
|
||||
// &:not(:has(.src_components_TPopUp_TNewPopUp_optionalConfirm)) {
|
||||
// bottom: 50%;
|
||||
// transform: translateY(50%);
|
||||
// overflow: unset;
|
||||
|
||||
// > div {
|
||||
// overflow: unset;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// html body [id="floatLayer"] > div:not([id]) > div > div:nth-child(2) {
|
||||
// &:has(.src_components_TPopUp_TNewPopUp_optionalConfirm) {
|
||||
// bottom: unset !important;
|
||||
// transform: none !important;
|
||||
// }
|
||||
// }
|
||||
|
||||
.tNewPopUp {
|
||||
//enact popup reset
|
||||
margin: 0 auto !important;
|
||||
@@ -73,6 +109,7 @@
|
||||
margin-top: 30px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
> div {
|
||||
min-width: 300px;
|
||||
height: 78px;
|
||||
@@ -809,6 +846,7 @@
|
||||
|
||||
.optionalAgreementInfo {
|
||||
.size(@w: 1064px, @h: 240px);
|
||||
top: 80% !important;
|
||||
padding: 60px 57px 40px; // 상단 60px, 좌우 57px, 하단 40px 패딩 적용
|
||||
box-sizing: border-box; // 패딩이 너비/높이에 포함되도록 설정
|
||||
background-color: @BG_COLOR_01;
|
||||
@@ -859,4 +897,150 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.optionalConfirm {
|
||||
.default-style();
|
||||
|
||||
bottom: unset !important;
|
||||
transform: none !important;
|
||||
top: 20% !important;
|
||||
|
||||
// 기존 위치 스타일들...
|
||||
|
||||
.optionalConfirmInfo {
|
||||
width: 100vw;
|
||||
height: 198px;
|
||||
background-color: #E6EBF0;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0px 20px 12px rgba(0, 0, 0, 0.30);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
gap: 15px;
|
||||
|
||||
.optionalConfirmContentContainer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 25px 140px 25px 140px;
|
||||
box-sizing: border-box;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
|
||||
.optionalConfirmTextSection {
|
||||
// flex: 1; // 나머지 높이를 모두 차지
|
||||
height: 30px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
// border : 1px solid red;
|
||||
}
|
||||
|
||||
.optionalConfirmButtonSection {
|
||||
height: 60px;
|
||||
// margin-top: 15px; // gap 대신 margin으로 간격 처리
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
flex-shrink: 0; // 줄어들지 않도록 고정
|
||||
// border : 1px solid blue;
|
||||
|
||||
.optionalConfirmLeftButtonSection {
|
||||
width: 320px;
|
||||
height: 60px; // 부모 높이(60px) 모두 사용
|
||||
display: flex;
|
||||
align-items: center; // 수직 중앙 정렬
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.optionalTermsButton {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
min-width: unset !important;
|
||||
max-width: unset !important;
|
||||
padding: 0 20px !important;
|
||||
margin: 0 !important;
|
||||
background: white !important;
|
||||
border: 1px solid #CFCFCF !important;
|
||||
border-radius: 4px !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: space-between !important; // 👈 flex-start → space-between 변경
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box !important;
|
||||
|
||||
// 포커스 스타일
|
||||
&:focus {
|
||||
outline: 2px solid #C70850 !important;
|
||||
outline-offset: 1px !important;
|
||||
}
|
||||
|
||||
.optionalTermsTitle {
|
||||
height: 100%;
|
||||
color: #1A1A1A;
|
||||
font-size: 22px;
|
||||
font-family: 'LG Smart UI';
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
// 👈 '>' 아이콘 스타일 추가
|
||||
.optionalTermsIcon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #1A1A1A;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
margin-left: 8px;
|
||||
|
||||
&::after {
|
||||
content: '>';
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #1A1A1A;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.optionalConfirmRightButtonSection {
|
||||
// width: 332px;
|
||||
height: 100%; // 부모 높이(60px) 모두 사용
|
||||
display: flex;
|
||||
align-items: center; // 수직 중앙 정렬
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
|
||||
.optionalConfirmButton {
|
||||
width: 160px;
|
||||
height: 60px;
|
||||
flex-shrink: 0; // 크기 고정
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// optionalConfirm일 때만 기존 위치 스타일 무력화
|
||||
|
||||
// :global([id="floatLayer"]) :global(> div:not([id])) :global(> div) :global(> div:nth-child(2)) {
|
||||
// .tNewPopUp.optionalConfirm & {
|
||||
// bottom: unset !important;
|
||||
// transform: none !important;
|
||||
// top: unset !important;
|
||||
// position: fixed !important;
|
||||
// bottom: 0 !important;
|
||||
// left: 50% !important;
|
||||
// transform: translateX(-50%) !important;
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import React, { useCallback, useEffect, useMemo } from "react";
|
||||
|
||||
import classNames from "classnames";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
@@ -14,7 +14,9 @@ import { SpotlightIds } from "../../utils/SpotlightIds";
|
||||
import CustomImage from "../CustomImage/CustomImage";
|
||||
import TButton from "../TButton/TButton";
|
||||
import css from "../TPopUp/TPopUp.module.less";
|
||||
import { $L } from "../../utils/helperMethods";
|
||||
import { $L, scaleH, scaleW } from "../../utils/helperMethods";
|
||||
import TButtonTab from "../TButtonTab/TButtonTab";
|
||||
import TButtonScroller from "../TButtonScroller/TButtonScroller";
|
||||
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{ enterTo: "default-element" },
|
||||
@@ -31,6 +33,10 @@ const ButtonContainerNegative = SpotlightContainerDecorator(
|
||||
{ defaultElement: `[data-spotlight-id="${"tPopupBtn2"}"]` },
|
||||
"div"
|
||||
);
|
||||
const IntroTermsButtonContainer = SpotlightContainerDecorator(
|
||||
{ defaultElement: '[data-spotlight-id="tPopupBtn1"]' },
|
||||
"div"
|
||||
);
|
||||
const SpottableComponent = Spottable("li");
|
||||
const SpottableDiv = Spottable("div");
|
||||
|
||||
@@ -57,9 +63,16 @@ const KINDS = [
|
||||
"cancelConfirmPopup",
|
||||
"trackPackagePopup",
|
||||
"optionalAgreement",
|
||||
"wideLeftPopup",
|
||||
"normal",
|
||||
];
|
||||
|
||||
export const CONTENT_TYPES = {
|
||||
TERMS: 'terms',
|
||||
DEFAULT: 'default'
|
||||
};
|
||||
|
||||
|
||||
export default function TPopUp({
|
||||
kind,
|
||||
children,
|
||||
@@ -90,32 +103,61 @@ export default function TPopUp({
|
||||
selectedIndex,
|
||||
spotlightId,
|
||||
onSpotlightRight,
|
||||
contentType = CONTENT_TYPES.DEFAULT,
|
||||
termsData,
|
||||
onTermsAgree,
|
||||
...rest
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
const popupVisible = useSelector((state) => state.common.popup.popupVisible);
|
||||
const httpHeader = useSelector((state) => state.common.httpHeader);
|
||||
const httpHeader = useSelector((state) => state.common.httpHeader);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (popupVisible) {
|
||||
// if (spotlightId) {
|
||||
// const timerId = setTimeout(() => {
|
||||
// Spotlight.focus(spotlightId);
|
||||
// }, 0);
|
||||
|
||||
// return () => {
|
||||
// clearTimeout(timerId);
|
||||
// };
|
||||
// }
|
||||
// const timerId = setTimeout(() => {
|
||||
// Spotlight.focus(SpotlightIds.TPOPUP);
|
||||
// }, 0);
|
||||
|
||||
// return () => {
|
||||
// clearTimeout(timerId);
|
||||
// };
|
||||
// }
|
||||
// }, [spotlightId, popupVisible]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (popupVisible) {
|
||||
let focusTarget;
|
||||
if (spotlightId) {
|
||||
const timerId = setTimeout(() => {
|
||||
Spotlight.focus(spotlightId);
|
||||
}, 0);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timerId);
|
||||
};
|
||||
// console.log("focusTarget-1", spotlightId);
|
||||
focusTarget = spotlightId;
|
||||
} else if (kind === "introTermsPopup" || contentType === CONTENT_TYPES.TERMS) {
|
||||
// console.log("focusTarget-2", "tPopupBtn1");
|
||||
focusTarget = "tPopupBtn1";
|
||||
} else {
|
||||
// console.log("focusTarget-3 and kind and contentType", SpotlightIds.TPOPUP, kind, contentType);
|
||||
focusTarget = SpotlightIds.TPOPUP;
|
||||
}
|
||||
const timerId = setTimeout(() => {
|
||||
Spotlight.focus(SpotlightIds.TPOPUP);
|
||||
}, 0);
|
||||
|
||||
const timerId = setTimeout(() => {
|
||||
console.log("focusTarget", focusTarget);
|
||||
Spotlight.focus(focusTarget);
|
||||
}, 200);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timerId);
|
||||
};
|
||||
}
|
||||
}, [spotlightId, popupVisible]);
|
||||
}, [popupVisible, spotlightId, kind, contentType]);
|
||||
|
||||
useEffect(() => {
|
||||
if (KINDS.indexOf(kind) < 0) {
|
||||
@@ -123,9 +165,23 @@ export default function TPopUp({
|
||||
}
|
||||
}, [kind]);
|
||||
|
||||
const ButtonContainerComp = useMemo(() => {
|
||||
return kind === "exitPopup" ? ButtonContainerNegative : ButtonContainer;
|
||||
useEffect(() => {
|
||||
if (kind === "introTermsPopup") {
|
||||
console.log("introTermsPopup");
|
||||
// Spotlight.focus("introTermsAgreeBtn");
|
||||
}
|
||||
}, [kind]);
|
||||
|
||||
const ButtonContainerComp = useMemo(() => {
|
||||
if (kind === "exitPopup") {
|
||||
return ButtonContainerNegative;
|
||||
}
|
||||
if (kind === "introTermsPopup") {
|
||||
return IntroTermsButtonContainer;
|
||||
}
|
||||
return ButtonContainer;
|
||||
}, [kind]);
|
||||
|
||||
|
||||
// optionalAgreement
|
||||
// 자동으로 Yes/No 버튼 텍스트 설정
|
||||
@@ -146,19 +202,35 @@ export default function TPopUp({
|
||||
|
||||
// optionalAgreement일 경우 항상 버튼 표시
|
||||
const shouldShowButtons = useMemo(() => {
|
||||
return hasButton || kind === "optionalAgreement";
|
||||
return hasButton || kind === "optionalAgreement" || kind === "introTermsPopup";
|
||||
}, [hasButton, kind]);
|
||||
|
||||
//-------------------------------------------------------
|
||||
|
||||
// const _onClick = useCallback(
|
||||
// (e) => {
|
||||
// if (onClick) {
|
||||
// onClick(e);
|
||||
// } else if (kind === "exitPopup") _onExit();
|
||||
// else _onClose();
|
||||
// },
|
||||
// [onClick, kind, _onExit, _onClose]
|
||||
// );
|
||||
|
||||
// onClick 핸들러 수정 - 약관 동의 처리 추가
|
||||
const _onClick = useCallback(
|
||||
(e) => {
|
||||
if (onClick) {
|
||||
if (contentType === CONTENT_TYPES.TERMS && onTermsAgree) {
|
||||
onTermsAgree(e);
|
||||
} else if (onClick) {
|
||||
onClick(e);
|
||||
} else if (kind === "exitPopup") _onExit();
|
||||
else _onClose();
|
||||
} else if (kind === "exitPopup") {
|
||||
_onExit();
|
||||
} else {
|
||||
_onClose();
|
||||
}
|
||||
},
|
||||
[onClick, kind, _onExit, _onClose]
|
||||
[contentType, onTermsAgree, onClick, kind, _onExit, _onClose]
|
||||
);
|
||||
|
||||
const _optionClick = useCallback(
|
||||
@@ -231,6 +303,72 @@ export default function TPopUp({
|
||||
return true;
|
||||
}
|
||||
}, [httpHeader]);
|
||||
|
||||
// 약관 제목 매핑 함수
|
||||
const getTermsTitle = useCallback((trmsTpCd) => {
|
||||
switch(trmsTpCd) {
|
||||
case "MST00401":
|
||||
return $L("Privacy Policy");
|
||||
case "MST00402":
|
||||
return $L("Terms & Conditions");
|
||||
case "MST00405":
|
||||
return $L("Optional Terms");
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 약관 상세보기 팝업시 렌더링 함수
|
||||
const renderTermsContent = useCallback(() => {
|
||||
if (!termsData) return null;
|
||||
|
||||
return (
|
||||
<div className={css.introTermsConts}>
|
||||
<TButtonTab
|
||||
className={css.tab}
|
||||
selectedIndex={0}
|
||||
contents={[getTermsTitle(termsData.trmsTpCd)]}
|
||||
spotlightDisabled={true}
|
||||
role="button"
|
||||
/>
|
||||
<TButtonScroller
|
||||
boxHeight={scaleH(300)}
|
||||
width={scaleW(980)}
|
||||
className={css.termsDescription}
|
||||
>
|
||||
<div
|
||||
className={css.termsDesc}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: termsData.trmsCntt,
|
||||
}}
|
||||
/>
|
||||
</TButtonScroller>
|
||||
</div>
|
||||
);
|
||||
}, [termsData, getTermsTitle]);
|
||||
|
||||
|
||||
// 약관 동의 핸들러
|
||||
const handleTermsAgree = useCallback((e) => {
|
||||
if (onTermsAgree) {
|
||||
onTermsAgree(e);
|
||||
}
|
||||
if (onClick) {
|
||||
onClick(e);
|
||||
}
|
||||
}, [onTermsAgree, onClick]);
|
||||
|
||||
// 컨텐츠 타입별 렌더링 함수
|
||||
const renderContent = useMemo(() => {
|
||||
switch(contentType) {
|
||||
case CONTENT_TYPES.TERMS:
|
||||
return renderTermsContent();
|
||||
default:
|
||||
return children;
|
||||
}
|
||||
}, [contentType, children, renderTermsContent]);
|
||||
|
||||
|
||||
return (
|
||||
<Alert
|
||||
open={open}
|
||||
@@ -331,7 +469,7 @@ export default function TPopUp({
|
||||
})}
|
||||
</OptionContainer>
|
||||
)}
|
||||
{children}
|
||||
{renderContent}
|
||||
{hasIndicator && (
|
||||
<>
|
||||
{currentPage !== 0 && (
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
// kind
|
||||
|
||||
.introTermsPopup {
|
||||
.default-style();
|
||||
.default-style();
|
||||
|
||||
.info {
|
||||
.size(@w: 1100px , @h: 564px);
|
||||
@@ -82,6 +82,11 @@
|
||||
> div {
|
||||
min-width: 300px;
|
||||
height: 78px;
|
||||
|
||||
// 첫 번째 버튼(Agree)의 오른쪽에만 마진 추가
|
||||
&:first-child {
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -741,3 +746,69 @@
|
||||
// position: fixed !important;
|
||||
// margin: 0 !important;
|
||||
// }
|
||||
|
||||
// wideLeftPopup 전용 스타일 - 다른 팝업과 완전히 분리
|
||||
.tPopUp.wideLeftPopup {
|
||||
// 팝업 전체 위치 설정
|
||||
// position: absolute !important;
|
||||
// left: 0px !important;
|
||||
// top: 50% !important;
|
||||
// width: 100% !important;
|
||||
// // transform: translateY(-50%) !important;
|
||||
// margin: 0 !important;
|
||||
|
||||
.info {
|
||||
// 실제 팝업 컨텐츠 크기 설정
|
||||
.size(@w: 1920px, @h: 110px);
|
||||
// .position(absolute, 20%, auto, auto, 0);
|
||||
|
||||
background-color: @BG_COLOR_01;
|
||||
color: @COLOR_GRAY03;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-weight: normal;
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
padding: 60px 57px 40px;
|
||||
|
||||
.textLayer {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.title {
|
||||
text-align: left;
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
line-height: normal;
|
||||
color: @COLOR_BLACK;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.text {
|
||||
color: @COLOR_GRAY03;
|
||||
font-size: 30px;
|
||||
line-height: 38px;
|
||||
text-align: center;
|
||||
min-height: 100px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.buttonContainer {
|
||||
margin-top: 30px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
|
||||
> div {
|
||||
min-width: 300px;
|
||||
height: 80px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// src/reducers/commonReducer.js
|
||||
|
||||
import { types } from "../actions/actionTypes";
|
||||
import { LOG_MENU } from "../utils/Config";
|
||||
|
||||
@@ -9,7 +11,7 @@ const initialState = {
|
||||
serverHOST: "", //"US.nextlgsdp.com",
|
||||
mbr_no: "", //X-User-Number : "US2401051532595"
|
||||
deviceId: "", //d87cedca-84e7-c05e-613d-39739bb7941f
|
||||
cursorVisible: false,
|
||||
cursorVisible: false,
|
||||
loginUserData: {},
|
||||
toast: false,
|
||||
toastText: null,
|
||||
@@ -30,6 +32,7 @@ const initialState = {
|
||||
optionalTermsConfirmSelected: false,
|
||||
},
|
||||
termsFlag: null,
|
||||
termsLoading: false, // 25.06.16 추가
|
||||
introTermsAgree: undefined, // Y, N
|
||||
checkoutTermsAgree: undefined,
|
||||
useLog: true,
|
||||
@@ -200,16 +203,26 @@ export const commonReducer = (state = initialState, action) => {
|
||||
case types.SET_EXIT_APP:
|
||||
return state;
|
||||
|
||||
case types.GET_TERMS_AGREE_YN: {
|
||||
const { privacyTerms, serviceTerms, purchaseTerms, paymentTerms,optionalTerms } =
|
||||
// 25.06.16 추가
|
||||
|
||||
case types.GET_TERMS_AGREE_YN_START: {
|
||||
return {
|
||||
...state,
|
||||
termsLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
case types.GET_TERMS_AGREE_YN_SUCCESS: {
|
||||
const { privacyTerms, serviceTerms, purchaseTerms, paymentTerms, optionalTerms } =
|
||||
action.payload;
|
||||
|
||||
const introTermsAgree = privacyTerms === "Y" && serviceTerms === "Y";
|
||||
const checkoutTermsAgree = purchaseTerms === "Y" && paymentTerms === "Y";
|
||||
const optionalTermsAgree = optionalTerms == "Y" ;
|
||||
const optionalTermsAgree = optionalTerms == "Y";
|
||||
|
||||
return {
|
||||
...state,
|
||||
termsLoading: false,
|
||||
termsFlag: {
|
||||
...action.payload,
|
||||
},
|
||||
@@ -218,6 +231,31 @@ export const commonReducer = (state = initialState, action) => {
|
||||
optionalTermsAgree,
|
||||
};
|
||||
}
|
||||
|
||||
case types.GET_TERMS_AGREE_YN_FAILURE: {
|
||||
return {
|
||||
...state,
|
||||
termsLoading: false,
|
||||
};
|
||||
}
|
||||
// case types.GET_TERMS_AGREE_YN: {
|
||||
// const { privacyTerms, serviceTerms, purchaseTerms, paymentTerms,optionalTerms } =
|
||||
// action.payload;
|
||||
|
||||
// const introTermsAgree = privacyTerms === "Y" && serviceTerms === "Y";
|
||||
// const checkoutTermsAgree = purchaseTerms === "Y" && paymentTerms === "Y";
|
||||
// const optionalTermsAgree = optionalTerms == "Y" ;
|
||||
|
||||
// return {
|
||||
// ...state,
|
||||
// termsFlag: {
|
||||
// ...action.payload,
|
||||
// },
|
||||
// introTermsAgree,
|
||||
// checkoutTermsAgree,
|
||||
// optionalTermsAgree,
|
||||
// };
|
||||
// }
|
||||
case types.REGISTER_DEVICE: {
|
||||
if (action.payload && action.payload.dvcIndex) {
|
||||
return {
|
||||
|
||||
@@ -84,9 +84,11 @@ export const ACTIVE_POPUP = {
|
||||
endOfServicePopup: "endOfServicePopup",
|
||||
checkoutErrorPopup: "checkoutErrorPopup",
|
||||
optionalTermsConfirmPopup: "optionalTermsConfirmPopup",
|
||||
optionalTermsTest: "optionalTermsTest",
|
||||
optionalTermsConfirm: "optionalTermsConfirm",
|
||||
optionalTermsConfirmBottom: "optionalTermsConfirmBottom",
|
||||
introTermsPopup: "introTermsPopup",
|
||||
toast: "toast",
|
||||
optionalConfirm: "optionalConfirm",
|
||||
};
|
||||
export const DEBUG_VIDEO_SUBTITLE_TEST = false;
|
||||
export const AUTO_SCROLL_DELAY = 600;
|
||||
|
||||
205
com.twin.app.shoptime/src/utils/focus-monitor.js
Normal file
205
com.twin.app.shoptime/src/utils/focus-monitor.js
Normal file
@@ -0,0 +1,205 @@
|
||||
// focus-monitor.js
|
||||
import Spotlight from '@enact/spotlight';
|
||||
|
||||
// 포커스 모니터링 클래스
|
||||
class FocusMonitor {
|
||||
constructor() {
|
||||
this.isListening = false;
|
||||
this.focusHistory = [];
|
||||
}
|
||||
|
||||
// 포커스 모니터링 시작
|
||||
startMonitoring() {
|
||||
if (this.isListening) return;
|
||||
|
||||
console.log("[focus] 포커스 모니터링 시작");
|
||||
this.isListening = true;
|
||||
|
||||
// DOM 이벤트로 포커스 모니터링
|
||||
// capture: true로 설정하여 모든 포커스 이벤트 캐치
|
||||
document.addEventListener('focusin', this.handleDOMFocus, true);
|
||||
document.addEventListener('focusout', this.handleDOMBlur, true);
|
||||
|
||||
// 키보드 이벤트로 spotlight 네비게이션 감지
|
||||
document.addEventListener('keydown', this.handleKeydown, true);
|
||||
}
|
||||
|
||||
// 포커스 모니터링 중지
|
||||
stopMonitoring() {
|
||||
if (!this.isListening) return;
|
||||
|
||||
console.log("[focus] 포커스 모니터링 중지");
|
||||
this.isListening = false;
|
||||
|
||||
// 이벤트 리스너 제거
|
||||
document.removeEventListener('focusin', this.handleDOMFocus, true);
|
||||
document.removeEventListener('focusout', this.handleDOMBlur, true);
|
||||
document.removeEventListener('keydown', this.handleKeydown, true);
|
||||
}
|
||||
|
||||
// 키보드 이벤트 핸들러 (spotlight 네비게이션 감지)
|
||||
handleKeydown = (event) => {
|
||||
// spotlight 네비게이션 키들
|
||||
const spotlightKeys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Enter'];
|
||||
|
||||
if (spotlightKeys.includes(event.key)) {
|
||||
console.log(`[focus] 🎮 SPOTLIGHT KEY: ${event.key}`);
|
||||
|
||||
// 키 이벤트 후 잠시 대기하여 포커스 변화 감지
|
||||
setTimeout(() => {
|
||||
const current = this.getCurrentSpotlightFocus();
|
||||
if (current) {
|
||||
console.log(`[focus] 🎯 SPOTLIGHT NAVIGATION RESULT:`, current);
|
||||
}
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
|
||||
// DOM 포커스 이벤트 핸들러
|
||||
handleDOMFocus = (event) => {
|
||||
const target = event.target;
|
||||
const focusInfo = this.getElementInfo(target);
|
||||
|
||||
// spotlight 요소인지 확인
|
||||
const isSpottable = this.isSpottableElement(target);
|
||||
const logPrefix = isSpottable ? "🎯 SPOTLIGHT FOCUS" : "🔍 DOM FOCUS";
|
||||
|
||||
console.log(`[focus] ${logPrefix}:`, focusInfo);
|
||||
|
||||
this.focusHistory.push({
|
||||
type: isSpottable ? 'spotlight-focus' : 'dom-focus',
|
||||
timestamp: Date.now(),
|
||||
target: focusInfo,
|
||||
event: event
|
||||
});
|
||||
}
|
||||
|
||||
// DOM 블러 이벤트 핸들러
|
||||
handleDOMBlur = (event) => {
|
||||
const target = event.target;
|
||||
const blurInfo = this.getElementInfo(target);
|
||||
|
||||
// spotlight 요소인지 확인
|
||||
const isSpottable = this.isSpottableElement(target);
|
||||
const logPrefix = isSpottable ? "💨 SPOTLIGHT BLUR" : "🌫️ DOM BLUR";
|
||||
|
||||
console.log(`[focus] ${logPrefix}:`, blurInfo);
|
||||
|
||||
this.focusHistory.push({
|
||||
type: isSpottable ? 'spotlight-blur' : 'dom-blur',
|
||||
timestamp: Date.now(),
|
||||
target: blurInfo,
|
||||
event: event
|
||||
});
|
||||
}
|
||||
|
||||
// 요소가 spotlight 요소인지 확인
|
||||
isSpottableElement(element) {
|
||||
if (!element) return false;
|
||||
|
||||
// spotlight 관련 속성들 확인
|
||||
return element.hasAttribute('data-spotlight-id') ||
|
||||
element.hasAttribute('data-spotlight-container') ||
|
||||
element.classList.contains('spottable') ||
|
||||
element.getAttribute('tabindex') === '0' ||
|
||||
// Enact 컴포넌트들은 보통 특정 클래스명을 가짐
|
||||
element.className.includes('Button_') ||
|
||||
element.className.includes('Input_') ||
|
||||
element.className.includes('Item_');
|
||||
}
|
||||
|
||||
// 현재 spotlight 포커스 가져오기
|
||||
getCurrentSpotlightFocus() {
|
||||
try {
|
||||
const current = Spotlight.getCurrent();
|
||||
return this.getElementInfo(current);
|
||||
} catch (e) {
|
||||
// getCurrent가 없는 경우 document.activeElement 사용
|
||||
return this.getElementInfo(document.activeElement);
|
||||
}
|
||||
}
|
||||
|
||||
// 요소 정보 추출
|
||||
getElementInfo(element) {
|
||||
if (!element) return { element: null, info: 'No element' };
|
||||
|
||||
const info = {
|
||||
tagName: element.tagName,
|
||||
id: element.id || '',
|
||||
className: element.className || '',
|
||||
spotlightId: element.getAttribute('data-spotlight-id') || '',
|
||||
spotlightContainer: element.getAttribute('data-spotlight-container') || '',
|
||||
textContent: element.textContent ? element.textContent.substring(0, 50) : '',
|
||||
tabIndex: element.tabIndex,
|
||||
isSpottable: this.isSpottableElement(element),
|
||||
element: element
|
||||
};
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
// 현재 포커스된 요소 정보 가져오기
|
||||
getCurrentFocus() {
|
||||
const activeElement = document.activeElement;
|
||||
const spotlightCurrent = this.getCurrentSpotlightFocus();
|
||||
|
||||
console.log("[focus] 현재 포커스 상태:");
|
||||
console.log("[focus] - DOM activeElement:", this.getElementInfo(activeElement));
|
||||
console.log("[focus] - Spotlight current:", spotlightCurrent);
|
||||
|
||||
return {
|
||||
dom: this.getElementInfo(activeElement),
|
||||
spotlight: spotlightCurrent
|
||||
};
|
||||
}
|
||||
|
||||
// 포커스 히스토리 출력
|
||||
printHistory(limit = 10) {
|
||||
console.log(`[focus] 📋 포커스 히스토리 (최근 ${limit}개):`);
|
||||
this.focusHistory
|
||||
.slice(-limit)
|
||||
.forEach((entry, index) => {
|
||||
const time = new Date(entry.timestamp).toLocaleTimeString();
|
||||
console.log(`[focus] ${index + 1}. [${time}] ${entry.type}:`, entry.target);
|
||||
});
|
||||
}
|
||||
|
||||
// 히스토리 초기화
|
||||
clearHistory() {
|
||||
this.focusHistory = [];
|
||||
console.log("[focus] 포커스 히스토리가 초기화되었습니다.");
|
||||
}
|
||||
|
||||
// spotlight 상태 확인
|
||||
getSpotlightStatus() {
|
||||
try {
|
||||
const current = Spotlight.getCurrent();
|
||||
const paused = Spotlight.isPaused ? Spotlight.isPaused() : false;
|
||||
|
||||
console.log("[focus] 🔍 Spotlight 상태:");
|
||||
console.log("[focus] - Current:", this.getElementInfo(current));
|
||||
console.log("[focus] - Paused:", paused);
|
||||
|
||||
return {
|
||||
current: this.getElementInfo(current),
|
||||
paused: paused
|
||||
};
|
||||
} catch (e) {
|
||||
console.log("[focus] ⚠️ Spotlight 상태를 가져올 수 없습니다:", e.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 싱글톤 인스턴스 생성
|
||||
const focusMonitor = new FocusMonitor();
|
||||
|
||||
// 편의 함수들 export
|
||||
export const startFocusMonitoring = () => focusMonitor.startMonitoring();
|
||||
export const stopFocusMonitoring = () => focusMonitor.stopMonitoring();
|
||||
export const getCurrentFocus = () => focusMonitor.getCurrentFocus();
|
||||
export const printFocusHistory = (limit) => focusMonitor.printHistory(limit);
|
||||
export const clearFocusHistory = () => focusMonitor.clearHistory();
|
||||
export const getSpotlightStatus = () => focusMonitor.getSpotlightStatus();
|
||||
|
||||
export default focusMonitor;
|
||||
240
com.twin.app.shoptime/src/utils/spotlight-utils.js
Normal file
240
com.twin.app.shoptime/src/utils/spotlight-utils.js
Normal file
@@ -0,0 +1,240 @@
|
||||
// spotlight-utils.js
|
||||
import {getTargetByContainer} from '@enact/spotlight/src/target';
|
||||
import {getTargetBySelector} from '@enact/spotlight/src/target';
|
||||
import {getContainerConfig} from '@enact/spotlight/src/container';
|
||||
import {isContainer, getContainerId} from '@enact/spotlight/src/container';
|
||||
import {getContainersForNode} from '@enact/spotlight/src/container';
|
||||
import {isNavigable} from '@enact/spotlight/src/container';
|
||||
import {setLastContainer} from '@enact/spotlight/src/container';
|
||||
import Spotlight from '@enact/spotlight';
|
||||
|
||||
// lodash 없이 last 함수 직접 구현
|
||||
const last = (array) => {
|
||||
return array && array.length > 0 ? array[array.length - 1] : undefined;
|
||||
};
|
||||
|
||||
// focusElement 함수는 spotlight 내부 함수이므로, 직접 구현하거나 spotlight의 focus를 사용
|
||||
const focusElement = (target, containerIds) => {
|
||||
console.log("focusElement called with:", target, containerIds);
|
||||
if (target && typeof target.focus === 'function') {
|
||||
try {
|
||||
target.focus();
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error("Focus failed:", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Predicts which element would be focused by Spotlight.focus() without actually
|
||||
* changing the focus.
|
||||
*
|
||||
* @param {String|Node} [elem] The spotlight ID or selector for either a spottable
|
||||
* component or a spotlight container, or spottable node. If not supplied, the default
|
||||
* container's target will be used.
|
||||
* @returns {Node|null} The DOM element that would be focused, or null if no
|
||||
* navigable target is found.
|
||||
*/
|
||||
export const getPredictedFocus = (elem) => {
|
||||
let target = elem;
|
||||
|
||||
if (!elem) {
|
||||
target = getTargetByContainer();
|
||||
} else if (typeof elem === 'string') {
|
||||
if (getContainerConfig(elem)) {
|
||||
// String is a container ID
|
||||
target = getTargetByContainer(elem);
|
||||
} else if (/^[\w\d-]+$/.test(elem)) {
|
||||
// Support component IDs consisting of alphanumeric, dash, or underscore
|
||||
target = getTargetBySelector(`[data-spotlight-id=${elem}]`);
|
||||
} else {
|
||||
// Treat as a CSS selector
|
||||
target = getTargetBySelector(elem);
|
||||
}
|
||||
} else if (isContainer(elem)) {
|
||||
// elem is a container element
|
||||
target = getTargetByContainer(getContainerId(elem));
|
||||
}
|
||||
|
||||
// Check navigability without attempting to focus
|
||||
const nextContainerIds = getContainersForNode(target);
|
||||
const nextContainerId = last(nextContainerIds);
|
||||
|
||||
if (isNavigable(target, nextContainerId, true)) {
|
||||
return target;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
// 메인 focus 함수
|
||||
export const focus = (elem) => {
|
||||
console.log("focus test", elem);
|
||||
var target = elem;
|
||||
var wasContainerId = false;
|
||||
|
||||
if (!elem) {
|
||||
// elem이 없으면 기본 컨테이너의 타겟을 가져옴
|
||||
target = getTargetByContainer();
|
||||
} else if (typeof elem === 'string') {
|
||||
if (getContainerConfig(elem)) {
|
||||
// 문자열이 컨테이너 ID인 경우
|
||||
target = getTargetByContainer(elem);
|
||||
wasContainerId = true;
|
||||
} else if (/^[\w\d-]+$/.test(elem)) {
|
||||
// 알파벳, 숫자, 대시, 언더스코어로 구성된 컴포넌트 ID 지원
|
||||
target = getTargetBySelector("[data-spotlight-id=".concat(elem, "]"));
|
||||
} else {
|
||||
// CSS 셀렉터로 처리
|
||||
target = getTargetBySelector(elem);
|
||||
}
|
||||
} else if (isContainer(elem)) {
|
||||
// elem이 컨테이너 요소인 경우
|
||||
target = getTargetByContainer(getContainerId(elem));
|
||||
}
|
||||
|
||||
// 타겟 노드의 컨테이너들을 가져옴
|
||||
var nextContainerIds = getContainersForNode(target);
|
||||
var nextContainerId = last(nextContainerIds); // 마지막 컨테이너 ID
|
||||
|
||||
if (isNavigable(target, nextContainerId, true)) {
|
||||
// 네비게이션 가능한 경우 포커스 설정
|
||||
var focused = focusElement(target, nextContainerIds);
|
||||
|
||||
if (!focused && wasContainerId) {
|
||||
// 포커스 실패했지만 컨테이너 ID였던 경우 마지막 컨테이너로 설정
|
||||
setLastContainer(elem);
|
||||
}
|
||||
|
||||
return focused;
|
||||
} else if (wasContainerId) {
|
||||
// 제공된 컨테이너 내에서 spottable 타겟을 찾지 못한 경우
|
||||
// 내용이 변경될 때 자체적으로 포커스할 수 있도록 활성 컨테이너로 설정
|
||||
setLastContainer(elem);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// spotlight-utils.js에 추가할 함수
|
||||
|
||||
/**
|
||||
* spotlightId로 직접 포커스를 설정하는 함수
|
||||
*
|
||||
* @param {String} spotlightId - data-spotlight-id 속성값
|
||||
* @param {Boolean} force - 강제 포커스 여부 (기본값: false)
|
||||
* @returns {Boolean} 포커스 성공 여부
|
||||
*/
|
||||
export const focusById = (spotlightId, force = false) => {
|
||||
// spotlightId 유효성 검사
|
||||
if (!spotlightId || typeof spotlightId !== 'string') {
|
||||
console.error('[focusById] spotlightId는 반드시 문자열이어야 합니다.');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// data-spotlight-id 속성을 가진 요소 직접 검색
|
||||
const targetElement = document.querySelector(`[data-spotlight-id="${spotlightId}"]`);
|
||||
|
||||
if (!targetElement) {
|
||||
console.warn(`[focusById] spotlightId "${spotlightId}"를 가진 요소를 찾을 수 없습니다.`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 요소가 현재 보이고 활성화되어 있는지 확인
|
||||
if (!isElementVisible(targetElement)) {
|
||||
console.warn(`[focusById] 요소 "${spotlightId}"가 보이지 않거나 비활성화되어 있습니다.`);
|
||||
if (!force) return false;
|
||||
}
|
||||
|
||||
// Spotlight의 isSpottable로 포커스 가능 여부 확인
|
||||
if (typeof Spotlight !== 'undefined' && Spotlight.isSpottable) {
|
||||
if (!Spotlight.isSpottable(targetElement) && !force) {
|
||||
console.warn(`[focusById] 요소 "${spotlightId}"가 현재 spottable하지 않습니다.`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 직접 DOM 포커스 시도
|
||||
if (force) {
|
||||
// 강제 모드: DOM focus() 직접 호출
|
||||
console.log(`[focusById] 강제 포커스 모드: "${spotlightId}"`);
|
||||
targetElement.focus();
|
||||
return true;
|
||||
} else {
|
||||
// 일반 모드: Spotlight 시스템 사용
|
||||
console.log(`[focusById] Spotlight 포커스: "${spotlightId}"`);
|
||||
|
||||
// Spotlight.focus() 사용 (선택자 형태로 전달)
|
||||
const focusResult = focus(`[data-spotlight-id="${spotlightId}"]`);
|
||||
|
||||
if (!focusResult) {
|
||||
// Spotlight 포커스 실패 시 직접 포커스 시도
|
||||
console.log(`[focusById] Spotlight 포커스 실패, 직접 포커스 시도: "${spotlightId}"`);
|
||||
targetElement.focus();
|
||||
return document.activeElement === targetElement;
|
||||
}
|
||||
|
||||
return focusResult;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[focusById] 포커스 설정 중 오류 발생: "${spotlightId}"`, error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 요소가 보이고 포커스 가능한 상태인지 확인하는 헬퍼 함수
|
||||
*
|
||||
* @param {Element} element - 확인할 DOM 요소
|
||||
* @returns {Boolean} 요소의 가시성 및 활성화 상태
|
||||
*/
|
||||
const isElementVisible = (element) => {
|
||||
if (!element) return false;
|
||||
|
||||
// 요소가 DOM에 연결되어 있는지 확인
|
||||
if (!element.isConnected) return false;
|
||||
|
||||
// disabled 속성 확인
|
||||
if (element.disabled) return false;
|
||||
|
||||
// display: none 또는 visibility: hidden 확인
|
||||
const style = window.getComputedStyle(element);
|
||||
if (style.display === 'none' || style.visibility === 'hidden') return false;
|
||||
|
||||
// opacity가 0인지 확인
|
||||
if (parseFloat(style.opacity) === 0) return false;
|
||||
|
||||
// 요소의 크기가 0인지 확인
|
||||
const rect = element.getBoundingClientRect();
|
||||
if (rect.width === 0 && rect.height === 0) return false;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* 현재 포커스된 요소의 spotlightId를 반환하는 헬퍼 함수
|
||||
*
|
||||
* @returns {String|null} 현재 포커스된 요소의 spotlightId 또는 null
|
||||
*/
|
||||
export const getCurrentSpotlightId = () => {
|
||||
const current = document.activeElement;
|
||||
if (current && current.hasAttribute('data-spotlight-id')) {
|
||||
return current.getAttribute('data-spotlight-id');
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* 특정 spotlightId를 가진 요소가 현재 포커스되어 있는지 확인하는 함수
|
||||
*
|
||||
* @param {String} spotlightId - 확인할 spotlightId
|
||||
* @returns {Boolean} 해당 요소가 현재 포커스되어 있는지 여부
|
||||
*/
|
||||
export const isCurrentlyFocused = (spotlightId) => {
|
||||
return getCurrentSpotlightId() === spotlightId;
|
||||
};
|
||||
@@ -403,6 +403,7 @@ const FeaturedBrandsPanel = ({ isOnTop, panelInfo, spotlightId }) => {
|
||||
(containerId) => setFocusedContainerId(containerId),
|
||||
[]
|
||||
);
|
||||
|
||||
const renderPageItem = useCallback(() => {
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
||||
// src/views/HomePanel/HomeBanner/HomeBanner.jsx
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import Spotlight from "@enact/spotlight";
|
||||
import { SpotlightContainerDecorator } from "@enact/spotlight/SpotlightContainerDecorator";
|
||||
import Spottable from "@enact/spotlight/Spottable";
|
||||
|
||||
import { setDefaultFocus } from "../../../actions/homeActions";
|
||||
import { $L, scaleH, scaleW } from '../../../utils/helperMethods';
|
||||
import { setDefaultFocus, setShowPopup } from "../../../actions/homeActions";
|
||||
import { changeAppStatus } from "../../../actions/commonActions";
|
||||
import { setMyPageTermsAgree } from '../../../actions/myPageActions';
|
||||
import { pushPanel } from "../../../actions/panelActions";
|
||||
import CustomImage from "../../../components/CustomImage/CustomImage";
|
||||
import css from "./HomeBanner.module.less";
|
||||
import Random from "./RandomUnit";
|
||||
import Rolling from "./RollingUnit";
|
||||
import TNewPopUp from "../../../components/TPopUp/TNewPopUp";
|
||||
import TButtonScroller from "../../../components/TButtonScroller/TButtonScroller";
|
||||
import OptionalConfirm from "../../../components/Optional/OptionalConfirm";
|
||||
import * as Config from "../../../utils/Config";
|
||||
|
||||
const SpottableComponent = Spottable("div");
|
||||
const Container = SpotlightContainerDecorator(
|
||||
@@ -49,6 +58,84 @@ export default function HomeBanner({
|
||||
}
|
||||
}, [handleItemFocus]);
|
||||
|
||||
const termsData = useSelector((state) => state.home.termsData);
|
||||
const optionalTermsData = useSelector((state) =>
|
||||
state.home.termsData?.data?.terms.find(term => term.trmsTpCd === "MST00405")
|
||||
);
|
||||
const termsLoading = useSelector((state) => state.common.termsLoading);
|
||||
// 선택약관 동의여부
|
||||
const introTermsAgree = useSelector((state) => state.common.introTermsAgree);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// 팝업표시 상태
|
||||
const [isOptionalConfirmVisible, setIsOptionalConfirmVisible] = useState(false);
|
||||
const [isOptionalTermsVisible, setIsOptionalTermsVisible] = useState(false);
|
||||
|
||||
// 선택약관 팝업 표시 여부
|
||||
const shouldShowOptionalTermsPopup = useMemo(() => {
|
||||
const terms = termsData?.data?.terms;
|
||||
if (!terms) {
|
||||
return false;
|
||||
}
|
||||
const optionalTerm = terms.find(term => term.trmsTpCd === "MST00405");
|
||||
return optionalTerm ? optionalTerm.trmsPopFlag === 'Y' && optionalTerm.trmsAgrFlag === 'N' : false;
|
||||
}, [termsData]);
|
||||
|
||||
const handleOptionalAgree = useCallback(() => {
|
||||
console.log('handleAgree Click');
|
||||
|
||||
// 약관 동의할 항목들 (string array)
|
||||
const termsList = ["TID0000222", "TID0000223", "TID0000232"];
|
||||
// 동의하지 않을 항목들 (빈 배열)
|
||||
const notTermsList = [];
|
||||
console.log('OptionalTermsConfirm -약관 동의 API 호출 파라미터:', { termsList, notTermsList });
|
||||
const callback = (response) => {
|
||||
if (response.retCode === "000" || response.retCode === 0) {
|
||||
console.log('약관 동의 성공:', response);
|
||||
} else {
|
||||
console.error('약관 동의 실패:', response);
|
||||
}
|
||||
};
|
||||
|
||||
console.log('OptionalTermsConfirm - 약관 동의 API 호출 payload:', { termsList, notTermsList });
|
||||
dispatch(setMyPageTermsAgree({ termsList, notTermsList }, callback));
|
||||
|
||||
}, []);
|
||||
|
||||
const handleOptionalTermsClick = useCallback(() => {
|
||||
console.log('약관 자세히 보기 클릭');
|
||||
setIsOptionalConfirmVisible(false);
|
||||
setIsOptionalTermsVisible(true);
|
||||
// 약관 상세 팝업을 띄우는 로직 추가
|
||||
}, []);
|
||||
|
||||
const handleOptionalAgreeClick = useCallback(() => {
|
||||
handleOptionalAgree();
|
||||
setIsOptionalConfirmVisible(false);
|
||||
}, []);
|
||||
|
||||
const handleOptionalDeclineClick = useCallback(() => {
|
||||
console.log('거절/다음에 하기 버튼 클릭');
|
||||
setIsOptionalConfirmVisible(false);
|
||||
// 거절 처리 로직 추가
|
||||
}, []);
|
||||
|
||||
// 선택약관 팝업 Close
|
||||
const handleTermsPopupClosed = useCallback(() => {
|
||||
setIsOptionalTermsVisible(false);
|
||||
setIsOptionalConfirmVisible(true);
|
||||
Spotlight.focus("optional-confirm-popup");
|
||||
}, []);
|
||||
|
||||
// 선택약관 팝업 Agree
|
||||
const handleTermsPopupAgree = useCallback(() => {
|
||||
console.log("handleTermsPopupAgree");
|
||||
handleOptionalAgree();
|
||||
setIsOptionalTermsVisible(false);
|
||||
}, []);
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
const _handleShelfFocus = useCallback(() => {
|
||||
if (handleShelfFocus) {
|
||||
handleShelfFocus();
|
||||
@@ -97,6 +184,36 @@ export default function HomeBanner({
|
||||
}
|
||||
}, [defaultFocus, dispatch, popupVisible]);
|
||||
|
||||
// 테스트용 팝업 표시
|
||||
// useEffect(() => {
|
||||
// setTimeout(() => {
|
||||
// console.log("App.js optionalTermsTest 팝업 표시");
|
||||
// setIsOptionalConfirmVisible(true);
|
||||
// // setIsOptionalTermsVisible(true);
|
||||
// }, 3000);
|
||||
// }, []);
|
||||
|
||||
// 약관 동의 및 선택 약관 팝업 처리
|
||||
useEffect(() => {
|
||||
if (termsLoading) {
|
||||
// 약관 데이터 로딩 중에는 아무것도 하지 않음
|
||||
return;
|
||||
}
|
||||
// 선택 약관 팝업을 띄워야 하는 경우
|
||||
if (shouldShowOptionalTermsPopup) {
|
||||
// 3초 후에 팝업을 띄우도록 설정
|
||||
console.log("shouldShowOptionalTermsPopup", shouldShowOptionalTermsPopup);
|
||||
console.log("App.js optionalTermsConfirm 팝업 표시");
|
||||
const timer = setTimeout(() => {
|
||||
setIsOptionalConfirmVisible(true);
|
||||
// dispatch(setShowPopup({ activePopup: "optionalTermsConfirm" }));
|
||||
}, 3000); // 3000 milliseconds = 3 seconds
|
||||
|
||||
// 컴포넌트 언마운트 시 타이머 클리어
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [shouldShowOptionalTermsPopup, termsLoading]);
|
||||
|
||||
const renderItem = useCallback(
|
||||
(index, isHorizontal) => {
|
||||
const data = bannerDataList?.[index] ?? {};
|
||||
@@ -187,12 +304,58 @@ export default function HomeBanner({
|
||||
}, [selectTemplate, renderItem]);
|
||||
|
||||
return (
|
||||
<Container
|
||||
className={css.container}
|
||||
spotlightId={spotlightId}
|
||||
data-wheel-point={true}
|
||||
>
|
||||
<div className={css.homeTemplateBox}>{renderLayout()}</div>
|
||||
</Container>
|
||||
<>
|
||||
<Container
|
||||
className={css.container}
|
||||
spotlightId={spotlightId}
|
||||
data-wheel-point={true}
|
||||
>
|
||||
<div className={css.homeTemplateBox}>{renderLayout()}</div>
|
||||
</Container>
|
||||
{/* 선택약관 동의 팝업 */}
|
||||
<OptionalConfirm
|
||||
open={isOptionalConfirmVisible}
|
||||
spotlightId="optional-confirm-popup"
|
||||
onOptionalTermsClick={handleOptionalTermsClick}
|
||||
onOptionalAgreeClick={handleOptionalAgreeClick}
|
||||
onOptionalDeclineClick={handleOptionalDeclineClick}
|
||||
customPosition={true}
|
||||
position={{
|
||||
position: 'absolute',
|
||||
top: '342px', // 가운데를 기준으로 한 좌표 (1080/2) - 198
|
||||
left: '0px',
|
||||
bottom: 'unset',
|
||||
transform: 'none',
|
||||
}}
|
||||
/>
|
||||
{/* 선택약관 자세히 보기 팝업 */}
|
||||
<TNewPopUp
|
||||
kind="introTermsPopup"
|
||||
open={isOptionalTermsVisible}
|
||||
// onClose={handleTermsPopupClosed}
|
||||
onClick={handleTermsPopupClosed}
|
||||
onIntroTermsAgreeClick={handleTermsPopupAgree}
|
||||
hasButton
|
||||
button1Text={$L("Close")}
|
||||
>
|
||||
{optionalTermsData && (
|
||||
<div className={css.termsViewerContent}>
|
||||
<div className={css.termsViewerTitle}>{$L("Optional Terms")}</div>
|
||||
<TButtonScroller
|
||||
boxHeight={scaleH(300)}
|
||||
width={scaleW(980)}
|
||||
className={css.termsDescription}
|
||||
>
|
||||
<div
|
||||
className={css.termsDesc}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: optionalTermsData.trmsCntt,
|
||||
}}
|
||||
/>
|
||||
</TButtonScroller>
|
||||
</div>
|
||||
)}
|
||||
</TNewPopUp>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// src: views/IntroPanel/IntroPanel.new.jsx
|
||||
|
||||
import React, { useCallback, useEffect, useState,useMemo } from "react";
|
||||
import React, { useCallback, useEffect, useState, useMemo } from "react";
|
||||
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
} from "../../actions/commonActions";
|
||||
import { registerDevice } from "../../actions/deviceActions";
|
||||
import { getWelcomeEventInfo } from "../../actions/eventActions";
|
||||
import { getHomeTerms } from "../../actions/homeActions";
|
||||
import { fetchCurrentUserHomeTerms } from "../../actions/homeActions";
|
||||
import {
|
||||
sendLogGNB,
|
||||
sendLogTerms,
|
||||
@@ -28,13 +28,17 @@ import TButtonScroller from "../../components/TButtonScroller/TButtonScroller";
|
||||
import TButtonTab from "../../components/TButtonTab/TButtonTab";
|
||||
import TCheckBoxSquare from "../../components/TCheckBox/TCheckBoxSquare";
|
||||
import TPanel from "../../components/TPanel/TPanel";
|
||||
import TPopUp from "../../components/TPopUp/TPopUp";
|
||||
import TPopUp, { CONTENT_TYPES } from "../../components/TPopUp/TPopUp";
|
||||
// import TNewPopUp from "../../components/TPopUp/TNewPopUp";
|
||||
import OptionalTermsInfo from "../MyPagePanel/MyPageSub/TermsOfService/OptionalTermsInfo";
|
||||
import useDebugKey from "../../hooks/useDebugKey";
|
||||
import * as Config from "../../utils/Config";
|
||||
import { panel_names } from "../../utils/Config";
|
||||
import { $L, scaleH, scaleW } from "../../utils/helperMethods";
|
||||
import css from "./IntroPanel.new.module.less";
|
||||
import { types } from "../../actions/actionTypes";
|
||||
import { focusById } from "../../utils/spotlight-utils";
|
||||
|
||||
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{ enterTo: "last-focused" },
|
||||
@@ -62,6 +66,10 @@ export default function IntroPanel({
|
||||
(state) => state.device.regDeviceInfoData
|
||||
);
|
||||
|
||||
// registerDevice API 호출 중 여부
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
const [showExitMessagePopup, setShowExitMessagePopup] = useState(false);
|
||||
|
||||
// const introTermsData = termsData?.data?.terms.filter(
|
||||
// (item) => item.trmsTpCd === "MST00401" || item.trmsTpCd === "MST00402"
|
||||
// );
|
||||
@@ -116,9 +124,20 @@ export default function IntroPanel({
|
||||
const [privacyChecked, setPrivacyChecked] = useState(true); // Privacy Policy 기본 체크
|
||||
const [optionalChecked, setOptionalChecked] = useState(false); // Optional Terms 기본 체크 안됨
|
||||
const [selectAllChecked, setSelectAllChecked] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(sendLogGNB(Config.LOG_MENU.TERMS_CONDITIONS));
|
||||
}, []);
|
||||
|
||||
// 컴포넌트 마운트 시 현재 Redux 상태 로깅
|
||||
useEffect(() => {
|
||||
console.log('🔍 IntroPanel 마운트 시 Redux 상태:');
|
||||
console.log(' - regDeviceData:', regDeviceData);
|
||||
console.log(' - regDeviceInfoData:', regDeviceInfoData);
|
||||
console.log(' - eventInfos:', eventInfos);
|
||||
console.log(' - termsData:', termsData);
|
||||
}, []);
|
||||
|
||||
// 디버깅용 WebOS 버전 로그
|
||||
useEffect(() => {
|
||||
console.log('🔍 IntroPanel WebOS 버전 정보:');
|
||||
@@ -126,11 +145,69 @@ export default function IntroPanel({
|
||||
console.log(' - shouldShowBenefitsView:', shouldShowBenefitsView);
|
||||
}, [webOSVersion, shouldShowBenefitsView]);
|
||||
|
||||
useEffect(() => {
|
||||
if (showExitMessagePopup) {
|
||||
const timer = setTimeout(() => {
|
||||
dispatch(setExitApp());
|
||||
dispatch(
|
||||
sendLogTotalRecommend({
|
||||
contextName: Config.LOG_CONTEXT_NAME.SHOPTIME,
|
||||
messageId: Config.LOG_MESSAGE_ID.VIEW_CHANGE,
|
||||
visible: false,
|
||||
})
|
||||
);
|
||||
}, 3000);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [showExitMessagePopup, dispatch]);
|
||||
|
||||
// Select All 상태 업데이트
|
||||
useEffect(() => {
|
||||
setSelectAllChecked(termsChecked && privacyChecked && optionalChecked);
|
||||
}, [termsChecked, privacyChecked, optionalChecked]);
|
||||
|
||||
// 컴포넌트 마운트 후 1.5초 뒤에 agreeButton으로 강제 포커스
|
||||
useEffect(() => {
|
||||
// 1.5초(1500ms) 후에 실행될 타이머 설정
|
||||
const focusTimer = setTimeout(() => {
|
||||
console.log('[Focus] 1.5초 후 agreeButton으로 강제 포커스 시도');
|
||||
|
||||
// focusById 함수를 사용하여 강제 포커스 (force: true)
|
||||
const focusSuccess = focusById('agreeButton', true);
|
||||
|
||||
if (focusSuccess) {
|
||||
console.log('[Focus] agreeButton 포커스 성공');
|
||||
} else {
|
||||
console.warn('[Focus] agreeButton 포커스 실패');
|
||||
}
|
||||
}, 1500);
|
||||
|
||||
// 컴포넌트 언마운트 시 타이머 정리
|
||||
return () => {
|
||||
clearTimeout(focusTimer);
|
||||
console.log('[Focus] agreeButton 포커스 타이머 정리됨');
|
||||
};
|
||||
}, []); // 빈 dependency 배열로 컴포넌트 마운트 시에만 실행
|
||||
|
||||
// 약관 팝업 동의여부에 따른 이벤트 핸들러
|
||||
const handleTermsAgree = useCallback(() => {
|
||||
if (!currentTerms) {
|
||||
return;
|
||||
}
|
||||
const termType = currentTerms.trmsTpCd;
|
||||
if (termType === "MST00402") {
|
||||
setTermsChecked(true);
|
||||
} else if (termType === "MST00401") {
|
||||
setPrivacyChecked(true);
|
||||
} else if (termType === "MST00405") {
|
||||
// Optional Terms
|
||||
setOptionalChecked(true);
|
||||
}
|
||||
// 팝업 닫기
|
||||
dispatch(setHidePopup());
|
||||
}, [currentTerms, dispatch]);
|
||||
|
||||
|
||||
const handleTermsClick = useCallback(
|
||||
(trmsTpCdList) => {
|
||||
if (introTermsData) {
|
||||
@@ -174,7 +251,12 @@ export default function IntroPanel({
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
dispatch(setHidePopup());
|
||||
}, [dispatch]); const handleAgree = useCallback(() => {
|
||||
}, [dispatch]);
|
||||
|
||||
const handleAgree = useCallback(() => {
|
||||
|
||||
if(isProcessing) return;
|
||||
|
||||
// 필수 약관이 체크되어 있는지 확인
|
||||
if (!termsChecked || !privacyChecked) {
|
||||
// 필수 약관이 체크되지 않았을 때 알림
|
||||
@@ -187,6 +269,10 @@ export default function IntroPanel({
|
||||
return;
|
||||
}
|
||||
|
||||
setIsProcessing(true);
|
||||
// 약관 동의 처리 시작 시 로딩 상태로 설정
|
||||
dispatch({ type: types.GET_TERMS_AGREE_YN_START });
|
||||
|
||||
// 약관 ID 정확하게 매핑
|
||||
const agreeTerms = [];
|
||||
|
||||
@@ -205,15 +291,33 @@ export default function IntroPanel({
|
||||
// registerDevice 호출 - 필수 + 선택 약관 모두 포함
|
||||
dispatch(registerDevice({
|
||||
agreeTerms: agreeTerms
|
||||
}));
|
||||
}));
|
||||
// dispatch(fetchCurrentUserHomeTerms()); // 중복호출 방지
|
||||
setIsProcessing(false);
|
||||
}, [termsChecked, privacyChecked, optionalChecked, dispatch]);
|
||||
|
||||
// 실패 감지를 위한 useEffect 추가
|
||||
useEffect(() => {
|
||||
// isProcessing이 true일 때만 실패 체크 (= handleAgree 클릭 후에만)
|
||||
if (isProcessing && regDeviceData && regDeviceData.retCode !== 0) {
|
||||
console.error('registerDevice 실패:', regDeviceData);
|
||||
dispatch(setShowPopup(Config.ACTIVE_POPUP.alertPopup, {
|
||||
title: $L("Error"),
|
||||
text: $L("Device registration failed. Please try again."),
|
||||
button1Text: $L("OK")
|
||||
}));
|
||||
setIsProcessing(false);
|
||||
}
|
||||
}, [regDeviceData, dispatch, isProcessing]); // isProcessing 의존성 추가
|
||||
|
||||
const handleDisagree = useCallback(() => {
|
||||
dispatch(setShowPopup(Config.ACTIVE_POPUP.exitPopup));
|
||||
dispatch(sendLogTerms({ logTpNo: Config.LOG_TP_NO.TERMS.DO_NOT_AGREE }));
|
||||
}, [dispatch]);
|
||||
|
||||
const onExit = useCallback(() => {
|
||||
const onExit = useCallback(() => {
|
||||
dispatch(setHidePopup());
|
||||
setShowExitMessagePopup(true);
|
||||
dispatch(setExitApp());
|
||||
dispatch(
|
||||
sendLogTotalRecommend({
|
||||
@@ -252,7 +356,7 @@ export default function IntroPanel({
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
Spotlight.focus("termsCheckbox");
|
||||
Spotlight.focus("selectAllCheckbox");
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -283,7 +387,7 @@ export default function IntroPanel({
|
||||
) {
|
||||
displayWelcomeEventPanel = true;
|
||||
}
|
||||
|
||||
setIsProcessing(false);
|
||||
dispatch(popPanel(panel_names.INTRO_PANEL));
|
||||
dispatch(sendLogTerms({ logTpNo: Config.LOG_TP_NO.TERMS.AGREE }));
|
||||
|
||||
@@ -338,7 +442,7 @@ export default function IntroPanel({
|
||||
{/* Terms & Conditions */}
|
||||
<div className={css.termsItem}>
|
||||
<TCheckBoxSquare
|
||||
className={css.checkbox}
|
||||
className={css.customeCheckbox}
|
||||
selected={termsChecked}
|
||||
onToggle={handleTermsToggle}
|
||||
spotlightId="termsCheckbox"
|
||||
@@ -360,7 +464,7 @@ export default function IntroPanel({
|
||||
{/* Privacy Policy */}
|
||||
<div className={css.termsItem}>
|
||||
<TCheckBoxSquare
|
||||
className={css.checkbox}
|
||||
className={css.customeCheckbox}
|
||||
selected={privacyChecked}
|
||||
onToggle={handlePrivacyToggle}
|
||||
spotlightId="privacyCheckbox"
|
||||
@@ -382,7 +486,7 @@ export default function IntroPanel({
|
||||
{/* Optional Terms */}
|
||||
<div className={css.termsItem}>
|
||||
<TCheckBoxSquare
|
||||
className={css.checkbox}
|
||||
className={css.customeCheckbox}
|
||||
selected={optionalChecked}
|
||||
onToggle={handleOptionalToggle}
|
||||
spotlightId="optionalCheckbox"
|
||||
@@ -456,43 +560,20 @@ export default function IntroPanel({
|
||||
</Container>
|
||||
</TPanel>
|
||||
|
||||
{/* TERMS */}
|
||||
{/* 약관 보기 팝업 */}
|
||||
{activePopup === Config.ACTIVE_POPUP.termsPopup && (
|
||||
<TPopUp
|
||||
<TPopUp
|
||||
kind="introTermsPopup"
|
||||
contentType={CONTENT_TYPES.TERMS} // 추가
|
||||
open={popupVisible}
|
||||
onClose={onClose}
|
||||
onTermsAgree={handleTermsAgree} // onClick에서 변경
|
||||
termsData={currentTerms} // 추가
|
||||
hasButton
|
||||
button1Text={$L("OK")}
|
||||
>
|
||||
{currentTerms && (
|
||||
<div className={css.introTermsConts}>
|
||||
<TButtonTab
|
||||
className={css.tab}
|
||||
selectedIndex={0}
|
||||
contents={
|
||||
currentTerms.trmsTpCd === "MST00401"
|
||||
? [$L("Privacy Policy")]
|
||||
: [$L("Terms & Conditions")]
|
||||
}
|
||||
spotlightDisabled={true}
|
||||
role="button"
|
||||
/>
|
||||
<TButtonScroller
|
||||
boxHeight={scaleH(300)}
|
||||
width={scaleW(980)}
|
||||
className={css.termsDescription}
|
||||
>
|
||||
<div
|
||||
className={css.termsDesc}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: currentTerms && currentTerms.trmsCntt,
|
||||
}}
|
||||
/>
|
||||
</TButtonScroller>
|
||||
</div>
|
||||
)}
|
||||
</TPopUp>
|
||||
button1Text={$L("Agree")}
|
||||
button2Text={$L("Close")}
|
||||
spotlightId="tPopupBtn1"
|
||||
/>
|
||||
)} {/* DO NOT AGREE */}
|
||||
{activePopup === Config.ACTIVE_POPUP.exitPopup && (
|
||||
<TPopUp
|
||||
@@ -520,6 +601,17 @@ export default function IntroPanel({
|
||||
text={$L("Please agree to Terms & Conditions and Privacy Policy.")}
|
||||
/>
|
||||
)}
|
||||
{/* Final Exit Message Popup */}
|
||||
{showExitMessagePopup && (
|
||||
<TPopUp
|
||||
kind="textPopup"
|
||||
open={showExitMessagePopup}
|
||||
hasText
|
||||
title={$L("Exit Shop Time")}
|
||||
text={$L("Thank you for using the Shop Time, and we hope to see you again. The app will close in 3 seconds.")}
|
||||
/>
|
||||
)}
|
||||
</Region>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -146,24 +146,29 @@
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
포커스 상태
|
||||
&.focused {
|
||||
outline: 2px #C91D53 solid !important;
|
||||
// ✅ 포커스 상태 (화살표 키 네비게이션용)
|
||||
&.focused,
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
outline: 4px #C91D53 solid !important;
|
||||
outline-offset: 2px !important;
|
||||
background-color: rgba(201, 29, 83, 0.1) !important;
|
||||
transform: translateY(-2px) !important;
|
||||
box-shadow: 0 4px 12px rgba(201, 29, 83, 0.3) !important;
|
||||
|
||||
.termsText {
|
||||
color: #C70850;
|
||||
// color: #C70850 !important;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 호버 효과
|
||||
// ✅ 호버 효과 (마우스용)
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
transform: translate3d(0, -2px, 0);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
outline: 2px #C91D53 solid;
|
||||
outline-offset: 2px;
|
||||
background-color: rgba(201, 29, 83, 0.1);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(201, 29, 83, 0.3);
|
||||
|
||||
.termsText {
|
||||
color: #C70850;
|
||||
@@ -238,14 +243,14 @@
|
||||
.agreeButton {
|
||||
width: 450px;
|
||||
height: 100px;
|
||||
background-color: #C70850 !important;
|
||||
border: 2px solid #C70850 !important;
|
||||
// background-color: #C70850 !important;
|
||||
// border: 2px solid #C70850 !important;
|
||||
color: white !important;
|
||||
border-radius: 12px;
|
||||
font-size: 35px;
|
||||
font-family: 'LG Smart UI';
|
||||
font-weight: 700;
|
||||
line-height: 35px;
|
||||
line-height: 2rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
@@ -277,14 +282,14 @@
|
||||
.disagreeButton {
|
||||
width: 450px;
|
||||
height: 100px;
|
||||
background-color: #999999 !important;
|
||||
border: 2px solid #999999 !important;
|
||||
// background-color: #999999 !important;
|
||||
// border: 2px solid #999999 !important;
|
||||
color: white !important;
|
||||
border-radius: 12px;
|
||||
font-size: 35px;
|
||||
font-family: 'LG Smart UI';
|
||||
font-weight: 700;
|
||||
line-height: 35px;
|
||||
line-height: 2rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
@@ -337,29 +342,117 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 로컬 체크박스 스타일로 변경
|
||||
// ✅ 로컬 체크박스 스타일 (전체 교체)
|
||||
// .customCheckbox {
|
||||
// width: 45px;
|
||||
// height: 45px;
|
||||
// position: relative;
|
||||
|
||||
// // 기본 상자 스타일
|
||||
// &:before {
|
||||
// content: '';
|
||||
// width: 42px;
|
||||
// height: 42px;
|
||||
// background-color: @COLOR_WHITE;
|
||||
// border: 2px solid @COLOR_GRAY02;
|
||||
// border-radius: 4px;
|
||||
// display: block;
|
||||
// transition: background-color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
// }
|
||||
|
||||
// // --- 상태별 스타일 정의 ---
|
||||
|
||||
// // [상태 2] 선택됨: 선택되면 배경을 진한 붉은색으로 만듭니다.
|
||||
// &.selected:before {
|
||||
// background-color: #C91D53;
|
||||
// border-color: #C91D53;
|
||||
// }
|
||||
|
||||
// // [상태 3] 포커스 받았지만, 선택은 안됨: 이때만 배경을 연한 붉은색으로 만듭니다.
|
||||
// &.focused:not(.selected):before {
|
||||
// background-color: rgba(201, 29, 83, 0.1);
|
||||
// }
|
||||
|
||||
// // [상태 1] 포커스 받음: 어떤 상태든 포커스를 받으면 굵고 붉은 테두리와 그림자 효과를 줍니다.
|
||||
// // 이 규칙을 다른 상태들보다 아래에 두어 우선순위를 높입니다.
|
||||
// &.focused:before {
|
||||
// border: 4px solid #C91D53;
|
||||
// box-shadow: 0 0 10px rgba(199, 8, 80, 0.3);
|
||||
// }
|
||||
|
||||
// // [상태 4] 선택됨 (체크마크): 선택되었을 때만 체크마크를 표시합니다.
|
||||
// &.selected:after {
|
||||
// content: '✓';
|
||||
// color: @COLOR_WHITE;
|
||||
// font-size: 24px;
|
||||
// font-weight: bold;
|
||||
// position: absolute;
|
||||
// top: 50%;
|
||||
// left: 50%;
|
||||
// transform: translate(-50%, -50%);
|
||||
// }
|
||||
|
||||
// // [상태 5] 비활성화됨
|
||||
// &.disabled:before {
|
||||
// background-color: @COLOR_GRAY01;
|
||||
// border-color: @COLOR_GRAY02;
|
||||
// opacity: 0.5;
|
||||
// }
|
||||
|
||||
// // [상태 6] 비활성화 상태에서 포커스 받음
|
||||
// &.disabled.focused:before {
|
||||
// background-color: #C91D53;
|
||||
// border-color: #C91D53; // 비활성화 포커스는 배경색만 변경하므로, 테두리는 포커스 기본 스타일을 따름
|
||||
// opacity: 1;
|
||||
// }
|
||||
// }
|
||||
|
||||
// ✅ 로컬 체크박스 스타일 (전체 교체)
|
||||
.customCheckbox {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
position: relative;
|
||||
|
||||
// 기본 상자 스타일
|
||||
&:before {
|
||||
content: '';
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
background: @COLOR_WHITE;
|
||||
background-color: @COLOR_WHITE;
|
||||
border: 2px solid @COLOR_GRAY02;
|
||||
border-radius: 4px;
|
||||
display: block;
|
||||
transition: background-color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
// 선택된 상태
|
||||
|
||||
// ✅ 선택된 상태: 배경색과 테두리 모두 붉은색 + 굵은 테두리
|
||||
&.selected:before {
|
||||
background: #C91D53;
|
||||
border-color: #C91D53;
|
||||
background-color: #C91D53 !important;
|
||||
border: 4px solid #C91D53 !important; // 굵은 테두리로 변경
|
||||
box-shadow: 0 0 8px rgba(201, 29, 83, 0.3); // 약간의 그림자 효과
|
||||
}
|
||||
|
||||
// 선택된 상태의 체크 마크
|
||||
// 포커스 받았지만 선택 안됨
|
||||
&.focused:not(.selected):before {
|
||||
background-color: rgba(201, 29, 83, 0.1);
|
||||
border: 4px solid #C91D53 !important;
|
||||
box-shadow: 0 0 10px rgba(199, 8, 80, 0.3) !important;
|
||||
}
|
||||
|
||||
// 비활성화됨
|
||||
&.disabled:before {
|
||||
background-color: @COLOR_GRAY01;
|
||||
border-color: @COLOR_GRAY02;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
// 포커스 받음 (선택된 상태가 아닌 경우에만 적용)
|
||||
&.focused:not(.selected):before {
|
||||
border: 4px solid #C91D53 !important;
|
||||
box-shadow: 0 0 10px rgba(199, 8, 80, 0.3) !important;
|
||||
}
|
||||
|
||||
// 체크마크
|
||||
&.selected:after {
|
||||
content: '✓';
|
||||
color: @COLOR_WHITE;
|
||||
@@ -370,16 +463,4 @@
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
&.focused:before {
|
||||
border-color: #C91D53;
|
||||
box-shadow: 0 0 10px rgba(199, 8, 80, 0.3);
|
||||
}
|
||||
|
||||
// 비활성화 상태
|
||||
&.disabled:before {
|
||||
background: @COLOR_GRAY01;
|
||||
border-color: @COLOR_GRAY02;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,7 @@ import SystemNotification from "../../components/SystemNotification/SystemNotifi
|
||||
import TabLayout from "../../components/TabLayout/TabLayout";
|
||||
import TButton from "../../components/TButton/TButton";
|
||||
import TPopUp from "../../components/TPopUp/TPopUp";
|
||||
import TNewPopUp from "../../components/TPopUp/TNewPopUp";
|
||||
import usePrevious from "../../hooks/usePrevious";
|
||||
import * as Config from "../../utils/Config";
|
||||
import { panel_names } from "../../utils/Config";
|
||||
@@ -80,8 +81,8 @@ import ThemeCurationPanel from "../ThemeCurationPanel/ThemeCurationPanel";
|
||||
import TrendingNowPanel from "../TrendingNowPanel/TrendingNowPanel";
|
||||
import VideoTestPanel from "../VideoTestPanel/VideoTestPanel";
|
||||
import WelcomeEventPanel from "../WelcomeEventPanel/WelcomeEventPanel";
|
||||
import TermsOfOptional from "../MyPagePanel/MyPageSub/TermsOfService/TermsOfOptionalSimple"; // 선택약관 반영 인트로
|
||||
import OptionalTermsConfirm from "../../components/Optional/OptionalTermsConfirm";
|
||||
import OptionalTermsConfirmBottom from "../../components/Optional/OptionalTermsConfirmBottom";
|
||||
import css from "./MainView.module.less";
|
||||
|
||||
const preloadImages = [
|
||||
@@ -800,9 +801,23 @@ export default function MainView({ className, initService }) {
|
||||
/>
|
||||
)}
|
||||
{/* OptionalTermsConfirmPopup */}
|
||||
{activePopup === Config.ACTIVE_POPUP.optionalTermsTest && (
|
||||
{activePopup === Config.ACTIVE_POPUP.optionalTermsConfirm && (
|
||||
<OptionalTermsConfirm open={popupVisible} />
|
||||
)}
|
||||
{activePopup === Config.ACTIVE_POPUP.optionalTermsConfirmBottom && (
|
||||
<OptionalTermsConfirmBottom open={popupVisible} />
|
||||
)}
|
||||
{/* {activePopup === Config.ACTIVE_POPUP.optionalConfirm && (
|
||||
<TNewPopUp
|
||||
kind="optionalConfirm"
|
||||
open={true}
|
||||
text="Get recommendations, special offers, and ads tailored just for you."
|
||||
button1Text="Agree"
|
||||
button2Text="Not Now"
|
||||
onClose={handleClose}
|
||||
onClick={handleAgree}
|
||||
/>
|
||||
)} */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
setMyTermsWithdraw,
|
||||
setMyPageTermsAgree,
|
||||
} from "../../../../actions/myPageActions";
|
||||
import { getHomeTerms } from "../../../../actions/homeActions";
|
||||
import { fetchCurrentUserHomeTerms } from "../../../../actions/homeActions";
|
||||
import TBody from "../../../../components/TBody/TBody";
|
||||
import TButton, { TYPES } from "../../../../components/TButton/TButton";
|
||||
import TButtonScroller from "../../../../components/TButtonScroller/TButtonScroller";
|
||||
@@ -64,8 +64,8 @@ export default function TermsOfService({ title, cbScrollTo }) {
|
||||
|
||||
const focusJob = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getHomeTerms({ trmsTpCdList: ["MST00401", "MST00402", "MST00405"] }));
|
||||
useEffect(() => {
|
||||
dispatch(fetchCurrentUserHomeTerms());
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -186,7 +186,7 @@ export default function TermsOfService({ title, cbScrollTo }) {
|
||||
}
|
||||
|
||||
// 약관 철회
|
||||
const onExit = useCallback(() => {
|
||||
const onExit = useCallback(() => {
|
||||
dispatch(setHidePopup());
|
||||
dispatch(
|
||||
setMyTermsWithdraw(
|
||||
@@ -231,7 +231,7 @@ export default function TermsOfService({ title, cbScrollTo }) {
|
||||
if (response.retCode === "000" || response.retCode === 0) {
|
||||
console.log("Optional terms agreement successful.");
|
||||
// 약관 동의의 후 약관 정보 조회
|
||||
dispatch(getHomeTerms({ trmsTpCdList: ["MST00401", "MST00402", "MST00405"] }));
|
||||
dispatch(fetchCurrentUserHomeTerms());
|
||||
setAgreePopup(true);
|
||||
} else {
|
||||
console.error("Optional terms agreement failed:", response);
|
||||
@@ -267,7 +267,7 @@ export default function TermsOfService({ title, cbScrollTo }) {
|
||||
if (response.retCode === "000" || response.retCode === 0) {
|
||||
console.log("Optional terms withdrawal successful.");
|
||||
// 약관 철회 후 약관 정보 조회
|
||||
dispatch(getHomeTerms({ trmsTpCdList: ["MST00401", "MST00402", "MST00405"] }));
|
||||
dispatch(fetchCurrentUserHomeTerms());
|
||||
setIsOptionalChecked(false);
|
||||
} else {
|
||||
console.error("Optional terms withdrawal failed:", response);
|
||||
@@ -463,7 +463,7 @@ export default function TermsOfService({ title, cbScrollTo }) {
|
||||
hasText
|
||||
text={$L("Are you sure you want to disagree with the optional terms?")}
|
||||
/>
|
||||
|
||||
{/* 필수약관 체크 확인 팝업 */}
|
||||
<TPopUp
|
||||
kind="textPopup"
|
||||
open={showCheckboxAlert}
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
.agreeButton {
|
||||
width: 240px;
|
||||
height: 80px;
|
||||
background: #c70850;
|
||||
background: #777d8a;
|
||||
box-shadow: 0px 0px 50px rgba(0, 0, 0, 0.5);
|
||||
border-radius: 12px;
|
||||
color: white;
|
||||
|
||||
Reference in New Issue
Block a user