merge: OptionalTerms 250613
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 111 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 634 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 431 KiB |
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useRef } from "react";
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
||||
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
@@ -21,6 +21,8 @@ import {
|
||||
setDeepLink,
|
||||
setGNBMenu,
|
||||
setSecondLayerInfo,
|
||||
setShowPopup,
|
||||
getTermsAgreeYn,
|
||||
} from "../actions/commonActions";
|
||||
import { getShoptimeTerms } from "../actions/empActions";
|
||||
import { getHomeMenu, getHomeTerms } from "../actions/homeActions";
|
||||
@@ -119,6 +121,25 @@ function AppBase(props) {
|
||||
(state) => state.common.appStatus.cursorVisible
|
||||
);
|
||||
const introTermsAgree = useSelector((state) => state.common.introTermsAgree);
|
||||
const optionalTermsAgree = useSelector((state) => state.common.optionalTermsAgree);
|
||||
// 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]);
|
||||
|
||||
useEffect(() => {
|
||||
if (termsData?.data?.terms) {
|
||||
dispatch(getTermsAgreeYn());
|
||||
}
|
||||
}, [termsData, dispatch]);
|
||||
|
||||
const introTermsAgreeRef = usePrevious(introTermsAgree);
|
||||
const logEnable = useSelector((state) => state.localSettings.logEnable);
|
||||
@@ -326,7 +347,7 @@ function AppBase(props) {
|
||||
dispatch(
|
||||
getHomeTerms({
|
||||
mbrNo: loginUserData.userNumber,
|
||||
trmsTpCdList: "MST00401, MST00402",
|
||||
trmsTpCdList: "MST00401, MST00402, MST00405", // 선택약관 추가 25.06
|
||||
})
|
||||
);
|
||||
|
||||
@@ -347,19 +368,38 @@ function AppBase(props) {
|
||||
}
|
||||
}, [webOSVersion, deviceId]);
|
||||
|
||||
// useEffect(() => {
|
||||
// setTimeout(() => {
|
||||
// console.log("App.js optionalTermsTest 팝업 표시");
|
||||
// dispatch(setShowPopup({ activePopup: "optionalTermsTest" }));
|
||||
// }, 3000);
|
||||
// }, [dispatch]);
|
||||
|
||||
|
||||
// 약관 동의 및 선택 약관 팝업 처리
|
||||
useEffect(() => {
|
||||
console.log("App.js introTermsAgree", introTermsAgree);
|
||||
if (introTermsAgree !== undefined) {
|
||||
if (introTermsAgree === undefined) {
|
||||
// 약관 동의 여부 확인 전에는 아무것도 하지 않음
|
||||
return;
|
||||
}
|
||||
|
||||
if (introTermsAgree) {
|
||||
initService(true);
|
||||
// 필수 약관에 동의한 경우
|
||||
if (shouldShowOptionalTermsPopup) {
|
||||
// 선택 약관 팝업을 띄워야 하는 경우
|
||||
dispatch(setShowPopup({ activePopup: "optionalTermsTest" }));
|
||||
} else {
|
||||
// 선택 약관 팝업이 필요 없는 경우, 바로 서비스 초기화
|
||||
initService(true);
|
||||
}
|
||||
} else {
|
||||
// 필수 약관에 동의하지 않은 경우
|
||||
dispatch(
|
||||
pushPanel({ name: Config.panel_names.INTRO_PANEL, panelInfo: {} })
|
||||
);
|
||||
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
|
||||
}
|
||||
}
|
||||
}, [introTermsAgree, dispatch]);
|
||||
}, [introTermsAgree, shouldShowOptionalTermsPopup, dispatch, initService]);
|
||||
|
||||
useEffect(() => {
|
||||
const launchParmas = getLaunchParams();
|
||||
|
||||
@@ -17,14 +17,17 @@ export const types = {
|
||||
CHANGE_APP_STATUS: "CHANGE_APP_STATUS",
|
||||
SEND_BROADCAST: "SEND_BROADCAST",
|
||||
CHANGE_LOCAL_SETTINGS: "CHANGE_LOCAL_SETTINGS",
|
||||
GNB_OPENED: "GNB_OPENED",
|
||||
SET_SHOW_POPUP: "SET_SHOW_POPUP",
|
||||
GNB_OPENED: "GNB_OPENED", SET_SHOW_POPUP: "SET_SHOW_POPUP",
|
||||
SET_SHOW_SECONDARY_POPUP: "SET_SHOW_SECONDARY_POPUP",
|
||||
SET_HIDE_POPUP: "SET_HIDE_POPUP",
|
||||
SET_HIDE_SECONDARY_POPUP: "SET_HIDE_SECONDARY_POPUP",
|
||||
SHOW_OPTIONAL_TERMS_CONFIRM_POPUP: "SHOW_OPTIONAL_TERMS_CONFIRM_POPUP",
|
||||
HIDE_OPTIONAL_TERMS_CONFIRM_POPUP: "HIDE_OPTIONAL_TERMS_CONFIRM_POPUP",
|
||||
TOGGLE_OPTIONAL_TERMS_CONFIRM: "TOGGLE_OPTIONAL_TERMS_CONFIRM",
|
||||
SET_EXIT_APP: "SET_EXIT_APP",
|
||||
GET_LOGIN_USER_DATA: "GET_LOGIN_USER_DATA",
|
||||
GET_TERMS_AGREE_YN: "GET_TERMS_AGREE_YN",
|
||||
LAUNCH_MEMBERSHIP_APP: "LAUNCH_MEMBERSHIP_APP",
|
||||
SET_FOCUS: "SET_FOCUS",
|
||||
SET_GNB_MENU: "SET_GNB_MENU",
|
||||
SET_SYSTEM_NOTICE: "SET_SYSTEM_NOTICE",
|
||||
@@ -120,6 +123,9 @@ export const types = {
|
||||
GET_MY_RECENTLY_VIEWED_INFO: "GET_MY_RECENTLY_VIEWED_INFO",
|
||||
CLEAR_RECENTLY_VIEWED_INFO: "CLEAR_RECENTLY_VIEWED_INFO",
|
||||
CLEAR_FAVORITES: "CLEAR_FAVORITES",
|
||||
SET_MYPAGE_TERMS_AGREE: "SET_MYPAGE_TERMS_AGREE",
|
||||
SET_MYPAGE_TERMS_AGREE_SUCCESS: "SET_MYPAGE_TERMS_AGREE_SUCCESS",
|
||||
SET_MYPAGE_TERMS_AGREE_FAIL: "SET_MYPAGE_TERMS_AGREE_FAIL",
|
||||
|
||||
// onSale actions
|
||||
GET_HOME_ON_SALE_INFO: "GET_HOME_ON_SALE_INFO",
|
||||
@@ -200,4 +206,7 @@ export const types = {
|
||||
|
||||
// pinCode actions
|
||||
GET_MY_INFO_CARD_PINCODE_CHECK: "GET_MY_INFO_CARD_PINCODE_CHECK",
|
||||
|
||||
// new actions
|
||||
CANCEL_FOCUS_ELEMENT: "CANCEL_FOCUS_ELEMENT",
|
||||
};
|
||||
|
||||
@@ -29,10 +29,13 @@ export const gnbOpened = (status) => ({
|
||||
payload: status,
|
||||
});
|
||||
|
||||
export const setShowPopup = (popupType, payload = {}) => ({
|
||||
export const setShowPopup = (config) => {
|
||||
const payload = typeof config === 'string' ? { activePopup: config } : config;
|
||||
return {
|
||||
type: types.SET_SHOW_POPUP,
|
||||
payload: { activePopup: popupType, ...payload },
|
||||
});
|
||||
payload,
|
||||
};
|
||||
};
|
||||
|
||||
export const setShowSecondaryPopup = (popupType, payload = {}) => ({
|
||||
type: types.SET_SHOW_SECONDARY_POPUP,
|
||||
@@ -47,6 +50,19 @@ export const setHideSecondaryPopup = () => ({
|
||||
type: types.SET_HIDE_SECONDARY_POPUP,
|
||||
});
|
||||
|
||||
export const showOptionalTermsConfirmPopup = () => ({
|
||||
type: types.SHOW_OPTIONAL_TERMS_CONFIRM_POPUP,
|
||||
});
|
||||
|
||||
export const hideOptionalTermsConfirmPopup = () => ({
|
||||
type: types.HIDE_OPTIONAL_TERMS_CONFIRM_POPUP,
|
||||
});
|
||||
|
||||
export const toggleOptionalTermsConfirm = (selected) => ({
|
||||
type: types.TOGGLE_OPTIONAL_TERMS_CONFIRM,
|
||||
payload: selected,
|
||||
});
|
||||
|
||||
export const setExitApp = () => (dispatch, getState) => {
|
||||
dispatch({ type: types.SET_EXIT_APP });
|
||||
|
||||
@@ -272,6 +288,13 @@ export const getDeviceId = (onComplete) => (dispatch, getState) => {
|
||||
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
|
||||
})));
|
||||
|
||||
const termsAgreeFlag = terms.reduce((acc, term) => {
|
||||
switch (term.trmsTpCd) {
|
||||
case "MST00401":
|
||||
@@ -290,6 +313,10 @@ export const getTermsAgreeYn = () => (dispatch, getState) => {
|
||||
acc.paymentTerms = term.trmsAgrFlag;
|
||||
break;
|
||||
|
||||
case "MST00405":
|
||||
acc.optionalTerms = term.trmsAgrFlag;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -211,6 +211,43 @@ export const setMyTermsWithdraw =
|
||||
);
|
||||
};
|
||||
|
||||
// MyPage 약관 동의 (IF-LGSP-031)
|
||||
export const setMyPageTermsAgree =
|
||||
(params, callback) => (dispatch, getState) => {
|
||||
const { termsList, notTermsList } = params;
|
||||
|
||||
const onSuccess = (response) => {
|
||||
console.log("setMyPageTermsAgree onSuccess ", response.data);
|
||||
|
||||
dispatch({
|
||||
type: types.SET_MYPAGE_TERMS_AGREE_SUCCESS,
|
||||
payload: response.data,
|
||||
retCode: response.data.retCode,
|
||||
});
|
||||
|
||||
if (callback) callback(response.data);
|
||||
};
|
||||
|
||||
const onFail = (error) => {
|
||||
console.error("setMyPageTermsAgree onFail ", error);
|
||||
dispatch({
|
||||
type: types.SET_MYPAGE_TERMS_AGREE_FAIL,
|
||||
payload: error,
|
||||
});
|
||||
};
|
||||
|
||||
TAxios(
|
||||
dispatch,
|
||||
getState,
|
||||
"post",
|
||||
URLS.SET_MYPAGE_TERMS_AGREE,
|
||||
{},
|
||||
{ termsList, notTermsList },
|
||||
onSuccess,
|
||||
onFail
|
||||
);
|
||||
};
|
||||
|
||||
// MyPage Upcoming Alert 정보 변경 조회 (IF-LGSP-050)
|
||||
export const getMyUpcomingChangeInfo = () => (dispatch, getState) => {
|
||||
const onSuccess = (response) => {
|
||||
|
||||
@@ -128,7 +128,7 @@ export const URLS = {
|
||||
|
||||
// emp controller
|
||||
GET_SHOPTIME_TERMS: "/lgsp/v1/emp/shoptime/terms.lge",
|
||||
|
||||
SET_MYPAGE_TERMS_AGREE: "/lgsp/v1/mypage/terms/agree.lge",
|
||||
// pinCode controller
|
||||
GET_MY_INFO_CARD_PINCODE_CHECK: "/lgsp/v1/myinfo/card/pincode/check.lge",
|
||||
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
// src/components/Optional/OptionalTermsConfirm.jsx
|
||||
|
||||
import React, { useEffect, useCallback } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import classNames from "classnames";
|
||||
|
||||
import TPopUp from "../TPopUp/TPopUp";
|
||||
import TCheckBoxSquare from "../TCheckBox/TCheckBoxSquare";
|
||||
import TButton from "../TButton/TButton";
|
||||
import { $L } from "../../utils/helperMethods";
|
||||
import css from "./OptionalTermsConfirm.module.less";
|
||||
|
||||
export default function OptionalTermsConfirm({
|
||||
className,
|
||||
open = false,
|
||||
onAgree,
|
||||
onNotNow,
|
||||
onToggleOptionalTerms,
|
||||
optionalTermsSelected = false,
|
||||
spotlightId = "optional-terms-confirm",
|
||||
onClose,
|
||||
...rest
|
||||
}) {
|
||||
|
||||
useEffect(() => {
|
||||
console.log("OptionalTermsConfirm", optionalTermsSelected);
|
||||
}, [optionalTermsSelected]);
|
||||
|
||||
const handleAgree = useCallback(() => {
|
||||
if (onAgree) {
|
||||
onAgree();
|
||||
}
|
||||
}, [onAgree]);
|
||||
|
||||
const handleNotNow = useCallback(() => {
|
||||
if (onNotNow) {
|
||||
onNotNow();
|
||||
}
|
||||
}, [onNotNow]);
|
||||
|
||||
const handleTermsToggle = useCallback(({ selected }) => {
|
||||
if (onToggleOptionalTerms) {
|
||||
onToggleOptionalTerms(selected);
|
||||
}
|
||||
}, [onToggleOptionalTerms]);
|
||||
return (
|
||||
<TPopUp
|
||||
kind="optionalTermsConfirmPopup"
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
className={classNames(css.optionalTermsPopup, className)}
|
||||
spotlightId={spotlightId}
|
||||
hasButton={false}
|
||||
hasOnClose={false}
|
||||
type="normal"
|
||||
{...rest}
|
||||
>
|
||||
<div className={css.popupContent}>
|
||||
<div className={css.description}>
|
||||
{$L("Get recommendations, special offers, and ads tailored just for you.")}
|
||||
</div>
|
||||
|
||||
<div className={css.rightSection}>
|
||||
<div className={css.termsSection}>
|
||||
<div className={css.termsButton}>
|
||||
<TCheckBoxSquare
|
||||
className={css.checkbox}
|
||||
selected={optionalTermsSelected}
|
||||
onToggle={handleTermsToggle}
|
||||
spotlightId="optional-terms-checkbox"
|
||||
ariaLabel={$L("Optional Terms")}
|
||||
/>
|
||||
<div className={css.termsText}>
|
||||
{$L("Optional Terms")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={css.buttonSection}>
|
||||
<TButton
|
||||
className={css.agreeButton}
|
||||
onClick={handleAgree}
|
||||
spotlightId="agree-button"
|
||||
type="agree"
|
||||
disabled={!optionalTermsSelected}
|
||||
ariaLabel={$L("Agree")}
|
||||
>
|
||||
{$L("Agree")}
|
||||
</TButton>
|
||||
|
||||
<TButton
|
||||
className={css.notNowButton}
|
||||
onClick={handleNotNow}
|
||||
spotlightId="not-now-button"
|
||||
type="normal"
|
||||
color="gray"
|
||||
ariaLabel={$L("Not Now")}
|
||||
>
|
||||
{$L("Not Now")}
|
||||
</TButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TPopUp>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
@import "../../style/CommonStyle.module.less";
|
||||
@import "../../style/utils.module.less";
|
||||
|
||||
// Optional Terms Confirm 팝업 전용 전역 스타일 - 화면 하단 위치 조정
|
||||
:global {
|
||||
// 팝업이 화면 하단에 나타나도록 조정
|
||||
.src_components_Optional_OptionalTermsConfirm_optionalTermsPopup {
|
||||
position: fixed !important;
|
||||
left: 120px !important; // GNB 너비만큼 오른쪽으로 이동
|
||||
bottom: 0 !important;
|
||||
top: auto !important;
|
||||
right: auto !important;
|
||||
width: calc(100vw - 120px) !important; // GNB 너비를 제외한 나머지 영역
|
||||
height: auto !important;
|
||||
transform: none !important;
|
||||
margin: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
background: #E6EBF0;
|
||||
box-shadow: 0px 20px 12px rgba(0, 0, 0, 0.30);
|
||||
}
|
||||
|
||||
// 팝업 컨테이너들의 위치 조정
|
||||
.enact_ui_Transition_Transition_transition.enact_sandstone_Popup_Popup_popupTransitionContainer {
|
||||
align-items: flex-end !important;
|
||||
justify-content: flex-start !important;
|
||||
padding-left: 120px !important; // GNB 너비만큼 패딩
|
||||
}
|
||||
}
|
||||
|
||||
.optionalTermsPopup {
|
||||
background: #E6EBF0;
|
||||
box-shadow: 0px 20px 12px rgba(0, 0, 0, 0.30);
|
||||
// TPopUp의 기본 스타일을 하단 팝업으로 오버라이드
|
||||
.popupContent {
|
||||
width: 100%;
|
||||
height: 140px;
|
||||
padding: 25px 140px;
|
||||
.flex(@alignCenter: center, @justifyCenter: flex-start);
|
||||
gap: 20px;
|
||||
|
||||
.description {
|
||||
.flex(@alignCenter: center, @justifyCenter: flex-end);
|
||||
color: black;
|
||||
font-size: 26px;
|
||||
font-family: 'LG Smart UI', sans-serif;
|
||||
font-weight: 400;
|
||||
word-wrap: break-word;
|
||||
line-height: 1.3;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.rightSection {
|
||||
flex: 1 1 0;
|
||||
.flex(@alignCenter: center, @justifyCenter: space-between);
|
||||
|
||||
.termsSection {
|
||||
.flex(@direction: column, @alignCenter: center, @justifyCenter: flex-start);
|
||||
gap: 10px;
|
||||
|
||||
.termsButton {
|
||||
.flex(@alignCenter: center, @justifyCenter: space-between);
|
||||
width: 320px;
|
||||
height: 60px;
|
||||
padding: 0 30px;
|
||||
background: white;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #CFCFCF;
|
||||
|
||||
.checkbox {
|
||||
width: 27px;
|
||||
height: 27px;
|
||||
border-radius: 50%;
|
||||
border: 1.82px solid black;
|
||||
}
|
||||
|
||||
.termsText {
|
||||
color: #1A1A1A;
|
||||
font-size: 22px;
|
||||
font-family: 'LG Smart UI', sans-serif;
|
||||
font-weight: 700;
|
||||
line-height: 35px;
|
||||
word-wrap: break-word;
|
||||
flex: 1;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.buttonSection {
|
||||
.flex(@alignCenter: center, @justifyCenter: center);
|
||||
gap: 12px;
|
||||
|
||||
.agreeButton {
|
||||
width: 160px;
|
||||
height: 50px;
|
||||
max-width: 450px;
|
||||
min-width: 150px;
|
||||
background: #C70850;
|
||||
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.50);
|
||||
border-radius: 12px;
|
||||
|
||||
> div {
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
font-family: 'LG Smart UI', sans-serif;
|
||||
font-weight: 700;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: #777D8A;
|
||||
box-shadow: none;
|
||||
|
||||
> div {
|
||||
color: #E6E6E6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notNowButton {
|
||||
width: 160px;
|
||||
height: 50px;
|
||||
max-width: 450px;
|
||||
min-width: 150px;
|
||||
background: #777D8A;
|
||||
border-radius: 12px;
|
||||
|
||||
> div {
|
||||
text-align: center;
|
||||
color: #E6E6E6;
|
||||
font-size: 20px;
|
||||
font-family: 'LG Smart UI', sans-serif;
|
||||
font-weight: 700;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #8A8F9C;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background: #8A8F9C;
|
||||
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.30);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 반응형 대응
|
||||
@media (max-width: 1366px) {
|
||||
.optionalTermsPopup {
|
||||
.popupContent {
|
||||
padding: 20px 100px;
|
||||
|
||||
.description {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.rightSection {
|
||||
.termsSection {
|
||||
.termsButton {
|
||||
width: 280px;
|
||||
height: 55px;
|
||||
|
||||
.termsText {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.buttonSection {
|
||||
.agreeButton,
|
||||
.notNowButton {
|
||||
width: 140px;
|
||||
height: 45px;
|
||||
|
||||
> div {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.optionalTermsPopup {
|
||||
.popupContent {
|
||||
height: 120px;
|
||||
padding: 15px 60px;
|
||||
gap: 15px;
|
||||
|
||||
.description {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.rightSection {
|
||||
.termsSection {
|
||||
.termsButton {
|
||||
width: 250px;
|
||||
height: 50px;
|
||||
padding: 0 20px;
|
||||
|
||||
.checkbox {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.termsText {
|
||||
font-size: 18px;
|
||||
line-height: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.buttonSection {
|
||||
gap: 8px;
|
||||
|
||||
.agreeButton,
|
||||
.notNowButton {
|
||||
width: 120px;
|
||||
height: 40px;
|
||||
|
||||
> div {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
// src/components/Optional/OptionalTermsConfirmTest.jsx
|
||||
|
||||
import React, { useEffect, useCallback, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import TPopUp from '../TPopUp/TPopUp';
|
||||
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 './OptionalTermsConfirmTest.module.less';
|
||||
|
||||
const OptionalTermsConfirmTest = ({ open }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
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")
|
||||
);
|
||||
|
||||
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);
|
||||
}, []);
|
||||
|
||||
const handleAgree = useCallback(() => {
|
||||
if (isChecked) {
|
||||
// 약관 동의할 항목들 (string array)
|
||||
const termsList = ["TID0000222", "TID0000223", "TID0000232"];
|
||||
|
||||
// 동의하지 않을 항목들 (빈 배열)
|
||||
const notTermsList = [];
|
||||
|
||||
console.log('약관 동의 API 호출 파라미터:', { termsList, notTermsList });
|
||||
|
||||
const callback = (response) => {
|
||||
if (response.retCode === "000" || response.retCode === 0) {
|
||||
console.log('약관 동의 성공:', response);
|
||||
} else {
|
||||
console.error('약관 동의 실패:', response);
|
||||
}
|
||||
};
|
||||
|
||||
dispatch(setMyPageTermsAgree({ termsList, notTermsList }, callback));
|
||||
dispatch(setHidePopup());
|
||||
} else {
|
||||
setIsWarningPopupVisible(true);
|
||||
}
|
||||
}, [isChecked, dispatch]);
|
||||
|
||||
const handleCloseWarningPopup = useCallback(() => {
|
||||
setIsWarningPopupVisible(false);
|
||||
}, []);
|
||||
|
||||
const handleDontAskAgain = () => {
|
||||
console.log("Don't Ask Again 처리 필요");
|
||||
dispatch(setHidePopup());
|
||||
};
|
||||
|
||||
if (isTermsPopupVisible) {
|
||||
return (
|
||||
<TPopUp
|
||||
kind="introTermsPopup"
|
||||
open
|
||||
onClose={handleCloseTermsPopup}
|
||||
onClick={handleCloseTermsPopup}
|
||||
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>
|
||||
)}
|
||||
</TPopUp>
|
||||
);
|
||||
}
|
||||
|
||||
if (isWarningPopupVisible) {
|
||||
return (
|
||||
<TPopUp
|
||||
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 (
|
||||
<TPopUp
|
||||
kind="introTermsPopup"
|
||||
open={open}
|
||||
onClose={handleMainPopupClose}
|
||||
spotlightId="optional-terms-test-popup"
|
||||
className={css.testPopup}
|
||||
type="none"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '100px',
|
||||
left: '270px',
|
||||
zIndex: 9999
|
||||
}}
|
||||
>
|
||||
<div className={css.contentContainer}>
|
||||
<div className={css.mainContent}>
|
||||
<div className={css.checkboxSection}>
|
||||
<div className={css.checkboxArea}>
|
||||
<TCheckBoxSquare
|
||||
selected={isChecked}
|
||||
onToggle={handleCheckboxToggle}
|
||||
spotlightId="optional-checkbox"
|
||||
className={css.checkbox}
|
||||
/>
|
||||
<TButton
|
||||
className={css.termBox}
|
||||
onClick={handleViewTermsClick}
|
||||
spotlightId="optional-terms-button"
|
||||
type="terms"
|
||||
ariaLabel={$L("View Optional Terms")}
|
||||
>
|
||||
<div className={css.termTitle}>Optional Terms</div>
|
||||
</TButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={css.descriptionSection}>
|
||||
<div className={css.description}>
|
||||
Get recommendations, special offers, and ads tailored just for you.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={css.buttonSection}>
|
||||
<div className={css.buttonGroup}>
|
||||
<TButton
|
||||
onClick={handleAgree}
|
||||
spotlightId="agree-button"
|
||||
className={css.agreeButton}
|
||||
>
|
||||
{$L('Agree')}
|
||||
</TButton>
|
||||
<TButton
|
||||
onClick={handleMainPopupClose}
|
||||
spotlightId="not-now-button"
|
||||
className={css.notNowButton}
|
||||
>
|
||||
{$L('Not Now')}
|
||||
</TButton>
|
||||
<TButton
|
||||
onClick={handleDontAskAgain}
|
||||
spotlightId="dont-ask-button"
|
||||
className={css.dontAskButton}
|
||||
>
|
||||
{$L("Don't Ask Again")}
|
||||
</TButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TPopUp>
|
||||
);
|
||||
};
|
||||
|
||||
export default OptionalTermsConfirmTest;
|
||||
@@ -0,0 +1,191 @@
|
||||
// src/components/Optional/OptionalTermsConfirmTest.module.less
|
||||
.testPopup {
|
||||
width: 958px;
|
||||
height: 310px;
|
||||
background-color: white !important;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 20px 70px rgba(2, 3, 3, 0.7) !important;
|
||||
position: absolute !important;
|
||||
top: 100px !important;
|
||||
left: 300px !important;
|
||||
|
||||
}
|
||||
|
||||
|
||||
.contentContainer {
|
||||
// width: 958px;
|
||||
// height: 310px;
|
||||
padding: 60px 57px 40px 57px;
|
||||
background: #E6EBF0;
|
||||
box-shadow: 0px 20px 12px rgba(0, 0, 0, 0.30);
|
||||
border-radius: 4px;
|
||||
display: inline-flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
box-sizing: border-box;
|
||||
|
||||
.mainContent {
|
||||
flex: 1 1 0;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
gap: 30px;
|
||||
|
||||
.checkboxSection {
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
.checkboxArea {
|
||||
min-height: 120px;
|
||||
display: inline-flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
|
||||
.checkbox {
|
||||
min-width: 45px;
|
||||
min-height: 45px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
// TButton으로 변경되어 포커스 가능
|
||||
.termBox {
|
||||
min-width: 530px;
|
||||
min-height: 120px;
|
||||
padding-left: 50px !important;
|
||||
padding-right: 50px !important;
|
||||
background: white !important;
|
||||
box-shadow: 0px 0px 30px rgba(0, 0, 0, 0.20);
|
||||
border-radius: 6px;
|
||||
outline: 1px #CFCFCF solid;
|
||||
outline-offset: -1px;
|
||||
display: flex !important;
|
||||
justify-content: space-between !important;
|
||||
align-items: center !important;
|
||||
border: none !important;
|
||||
|
||||
// 포커스 스타일 추가
|
||||
&:focus {
|
||||
outline: 3px solid #C70850 !important;
|
||||
outline-offset: 2px !important;
|
||||
}
|
||||
|
||||
.termTitle {
|
||||
color: #1A1A1A;
|
||||
font-size: 35px;
|
||||
font-family: 'LG Smart UI';
|
||||
font-weight: 700;
|
||||
line-height: 35px;
|
||||
}
|
||||
|
||||
.expandIcon {
|
||||
min-width: 37px;
|
||||
min-height: 37px;
|
||||
position: relative;
|
||||
border-radius: 100px;
|
||||
outline: 2.5px black solid;
|
||||
outline-offset: -2.5px;
|
||||
flex-shrink: 0;
|
||||
|
||||
.arrow {
|
||||
width: 14.66px;
|
||||
height: 6.99px;
|
||||
left: 16.01px;
|
||||
top: 25.83px;
|
||||
position: absolute;
|
||||
transform: rotate(-90deg);
|
||||
transform-origin: top left;
|
||||
outline: 2.5px black solid;
|
||||
outline-offset: -1.25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.descriptionSection {
|
||||
align-self: stretch;
|
||||
padding-top: 20px;
|
||||
border-top: 1px #C5C6C9 solid;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.description {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
color: black;
|
||||
font-size: 26px;
|
||||
font-family: 'LG Smart UI';
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
.buttonSection {
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
|
||||
.buttonGroup {
|
||||
align-self: stretch;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
.agreeButton, .notNowButton, .dontAskButton {
|
||||
min-width: 300px;
|
||||
min-height: 80px;
|
||||
max-width: 450px;
|
||||
flex: 1;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
> div {
|
||||
text-align: center;
|
||||
font-size: 30px;
|
||||
font-family: 'LG Smart UI';
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.agreeButton {
|
||||
background: #C70850 !important;
|
||||
box-shadow: 0px 0px 50px rgba(0, 0, 0, 0.50);
|
||||
|
||||
> div {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.notNowButton {
|
||||
background: #777D8A !important;
|
||||
|
||||
> div {
|
||||
color: #E6E6E6;
|
||||
}
|
||||
}
|
||||
|
||||
.dontAskButton {
|
||||
background: rgba(122, 128, 141, 0.30) !important;
|
||||
|
||||
> div {
|
||||
opacity: 0.30;
|
||||
color: #E6E6E6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// src/components/TCheckBox/TCheckBoxSquare.jsx
|
||||
|
||||
import React, { useCallback, useState } from "react";
|
||||
import classNames from "classnames";
|
||||
import Spottable from "@enact/spotlight/Spottable";
|
||||
import css from "./TCheckBoxSquare.module.less";
|
||||
|
||||
const SpottableComponent = Spottable("div");
|
||||
|
||||
export default function TCheckBoxSquare({
|
||||
className,
|
||||
spotlightDisabled,
|
||||
spotlightId,
|
||||
onFocus,
|
||||
onToggle,
|
||||
selected = false, // 기본값을 false로 변경
|
||||
disabled = false, // disabled prop 추가
|
||||
ariaLabel,
|
||||
...rest
|
||||
}) {
|
||||
const [focus, setFocus] = useState(false);
|
||||
|
||||
const _onClick = useCallback(() => {
|
||||
// disabled 상태일 때는 클릭 처리하지 않음
|
||||
if (disabled || !onToggle) {
|
||||
console.log('[TCheckBoxSquare] 클릭 무시됨 - disabled:', disabled, 'onToggle 존재:', !!onToggle);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[TCheckBoxSquare] 클릭 처리됨 - 현재 selected:', selected, '-> 새로운 값:', !selected);
|
||||
onToggle({ selected: !selected });
|
||||
}, [onToggle, selected, disabled]);
|
||||
|
||||
const _onFocus = useCallback(() => {
|
||||
if (disabled) return; // disabled 상태일 때는 포커스 처리하지 않음
|
||||
|
||||
setFocus(true);
|
||||
if (onFocus) {
|
||||
onFocus();
|
||||
}
|
||||
}, [onFocus, disabled]);
|
||||
|
||||
const _onBlur = useCallback(() => {
|
||||
setFocus(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SpottableComponent
|
||||
{...rest}
|
||||
className={classNames(
|
||||
className,
|
||||
css.tCheckBoxSquare,
|
||||
focus && css.focus,
|
||||
selected && css.selected,
|
||||
focus && selected && css.selectedFocus,
|
||||
disabled && css.disabled // disabled 클래스 추가
|
||||
)}
|
||||
spotlightDisabled={spotlightDisabled || disabled} // spotlightDisabled와 disabled 모두 고려
|
||||
onClick={_onClick}
|
||||
onFocus={_onFocus}
|
||||
onBlur={_onBlur}
|
||||
spotlightId={spotlightId}
|
||||
aria-label={ariaLabel}
|
||||
aria-disabled={disabled} // 접근성을 위한 aria-disabled 추가
|
||||
></SpottableComponent>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// src/components/TCheckBox/TCheckBoxSquare.module.less
|
||||
|
||||
@SQUARE_BORDER_DEFAULT: #CCCCCC;
|
||||
@SQUARE_BORDER_ACTIVE: #C70850;
|
||||
@SQUARE_BG_SELECTED: #7A808D;
|
||||
|
||||
.tCheckBoxSquare {
|
||||
min-width: 45px !important;
|
||||
min-height: 45px !important;
|
||||
width: 45px !important;
|
||||
height: 45px !important;
|
||||
border: 3px solid @SQUARE_BORDER_DEFAULT !important;
|
||||
border-radius: 8px !important;
|
||||
background-color: #fff !important;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s, border 0.15s !important;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&.focus {
|
||||
border-color: @SQUARE_BORDER_ACTIVE !important;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 18px !important;
|
||||
height: 10px !important;
|
||||
border-left: 4px solid #fff !important;
|
||||
border-bottom: 4px solid #fff !important;
|
||||
transform: translate(-50%, -70%) rotate(-45deg) scale(0);
|
||||
border-radius: 0 !important;
|
||||
transition: transform 0.2s ease-in-out !important;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
border-color: @SQUARE_BG_SELECTED !important;
|
||||
background-color: @SQUARE_BG_SELECTED !important;
|
||||
&::before {
|
||||
transform: translate(-50%, -70%) rotate(-45deg) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
&.selectedFocus {
|
||||
border-color: @SQUARE_BG_SELECTED !important;
|
||||
background-color: @SQUARE_BG_SELECTED !important;
|
||||
box-shadow: 0 0 0 4px fade(@SQUARE_BG_SELECTED, 20%) !important;
|
||||
&::before {
|
||||
transform: translate(-50%, -70%) rotate(-45deg) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.5 !important;
|
||||
cursor: not-allowed !important;
|
||||
pointer-events: none !important;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-color: @SQUARE_BORDER_DEFAULT !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,8 @@ const KINDS = [
|
||||
"orderCancelPopup",
|
||||
"cancelConfirmPopup",
|
||||
"trackPackagePopup",
|
||||
"optionalAgreement",
|
||||
"normal",
|
||||
];
|
||||
|
||||
export default function TPopUp({
|
||||
@@ -125,6 +127,30 @@ export default function TPopUp({
|
||||
return kind === "exitPopup" ? ButtonContainerNegative : 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) {
|
||||
return "No";
|
||||
}
|
||||
return button2Text;
|
||||
}, [kind, button2Text]);
|
||||
|
||||
// optionalAgreement일 경우 항상 버튼 표시
|
||||
const shouldShowButtons = useMemo(() => {
|
||||
return hasButton || kind === "optionalAgreement";
|
||||
}, [hasButton, kind]);
|
||||
|
||||
//-------------------------------------------------------
|
||||
|
||||
const _onClick = useCallback(
|
||||
(e) => {
|
||||
if (onClick) {
|
||||
@@ -205,7 +231,6 @@ export default function TPopUp({
|
||||
return true;
|
||||
}
|
||||
}, [httpHeader]);
|
||||
|
||||
return (
|
||||
<Alert
|
||||
open={open}
|
||||
@@ -217,6 +242,7 @@ export default function TPopUp({
|
||||
title && css.title,
|
||||
text && css.text,
|
||||
hasButton && css.hasButton,
|
||||
kind === "optionalTermsConfirmPopup" && css.bottomPopup,
|
||||
className ? className : null
|
||||
)}
|
||||
aria-hidden={ariaHidden}
|
||||
@@ -326,27 +352,27 @@ export default function TPopUp({
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{hasButton && (
|
||||
{shouldShowButtons && (
|
||||
<ButtonContainerComp className={css.buttonContainer}>
|
||||
{button1Text && (
|
||||
{finalButton1Text && (
|
||||
<TButton
|
||||
spotlightId="tPopupBtn1"
|
||||
onClick={_onClick}
|
||||
role="button"
|
||||
ariaLabel={button1Text}
|
||||
ariaLabel={finalButton1Text}
|
||||
onSpotlightRight={_onSpotlightRight}
|
||||
>
|
||||
{button1Text}
|
||||
{finalButton1Text}
|
||||
</TButton>
|
||||
)}
|
||||
{button2Text && (
|
||||
{finalButton2Text && (
|
||||
<TButton
|
||||
spotlightId="tPopupBtn2"
|
||||
onClick={onClose}
|
||||
role="button"
|
||||
ariaLabel={button2Text}
|
||||
ariaLabel={finalButton2Text}
|
||||
>
|
||||
{button2Text}
|
||||
{finalButton2Text}
|
||||
</TButton>
|
||||
)}
|
||||
</ButtonContainerComp>
|
||||
|
||||
@@ -673,3 +673,71 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 👇 새로운 팝업 스타일 */
|
||||
.optionalAgreement {
|
||||
.default-style();
|
||||
|
||||
// 팝업 위치를 정중앙보다 아래로 명시적으로 지정
|
||||
position: absolute !important;
|
||||
top: 80% !important; // 50%가 정중앙, 값을 키우면 아래로 이동
|
||||
left: 50% !important;
|
||||
transform: translate(-50%, -50%) !important;
|
||||
margin: 0 !important;
|
||||
|
||||
.info {
|
||||
.size(@w: 1064px, @h: 240px);
|
||||
padding: 60px 57px 40px; // 상단 60px, 좌우 57px, 하단 40px 패딩 적용
|
||||
box-sizing: border-box; // 패딩이 너비/높이에 포함되도록 설정
|
||||
|
||||
// 컨텐츠 영역
|
||||
.contentContainer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
// 텍스트 영역 스타일
|
||||
.textLayer {
|
||||
flex: 1; // 남은 공간을 모두 차지하도록 설정
|
||||
min-height: 0; // flex item이 부모를 넘어 수축할 수 있도록 허용
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden; // 내용이 넘칠 경우 숨김
|
||||
|
||||
.text {
|
||||
font-size: 30px;
|
||||
line-height: 38px;
|
||||
text-align: center;
|
||||
color: @COLOR_GRAY03;
|
||||
}
|
||||
}
|
||||
|
||||
// 버튼 컨테이너 스타일
|
||||
.buttonContainer {
|
||||
margin-top: 3%; // gap 대신 margin 사용
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 12px; // 버튼 간 간격
|
||||
flex-shrink: 0; // 컨테이너가 줄어들지 않도록 설정
|
||||
|
||||
> div {
|
||||
min-width: 300px;
|
||||
height: 80px; // 높이 80px로 수정
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 👇 '자물쇠' 추가: kind="optionalTermsConfirmPopup"일 때 적용될 위치 스타일 */
|
||||
// .bottomPopup {
|
||||
// top: auto !important;
|
||||
// bottom: 60px !important; /* 화면 하단에서 60px 위 */
|
||||
// left: 50% !important;
|
||||
// transform: translateX(-50%) !important;
|
||||
// position: fixed !important;
|
||||
// margin: 0 !important;
|
||||
// }
|
||||
|
||||
@@ -18,8 +18,7 @@ const initialState = {
|
||||
},
|
||||
broadcast: {},
|
||||
httpHeader: null,
|
||||
isGnbOpened: false,
|
||||
popup: {
|
||||
isGnbOpened: false, popup: {
|
||||
popupVisible: false,
|
||||
activePopup: null,
|
||||
secondaryPopup: null,
|
||||
@@ -28,9 +27,10 @@ const initialState = {
|
||||
secondaryData: {},
|
||||
text: null,
|
||||
data: {},
|
||||
optionalTermsConfirmSelected: false,
|
||||
},
|
||||
termsFlag: null,
|
||||
introTermsAgree: undefined,
|
||||
introTermsAgree: undefined, // Y, N
|
||||
checkoutTermsAgree: undefined,
|
||||
useLog: true,
|
||||
useMockup: true,
|
||||
@@ -70,6 +70,7 @@ const initialState = {
|
||||
|
||||
secondLayerInfo: {},
|
||||
macAddress: { wifi: "", wired: "", p2p: "" },
|
||||
connectionFailed: false,
|
||||
};
|
||||
|
||||
export const commonReducer = (state = initialState, action) => {
|
||||
@@ -132,11 +133,9 @@ export const commonReducer = (state = initialState, action) => {
|
||||
return {
|
||||
...state,
|
||||
popup: {
|
||||
...state.popup,
|
||||
popupVisible: true,
|
||||
activePopup: action.payload.activePopup,
|
||||
secondaryPopup: action.payload.secondaryPopup,
|
||||
text: action.payload.text,
|
||||
data: action.payload.data,
|
||||
...action.payload,
|
||||
},
|
||||
};
|
||||
case types.SET_SHOW_SECONDARY_POPUP:
|
||||
@@ -162,8 +161,7 @@ export const commonReducer = (state = initialState, action) => {
|
||||
secondaryPopupVisible: false,
|
||||
secondaryPopup: null,
|
||||
},
|
||||
};
|
||||
case types.SET_HIDE_SECONDARY_POPUP:
|
||||
}; case types.SET_HIDE_SECONDARY_POPUP:
|
||||
return {
|
||||
...state,
|
||||
popup: {
|
||||
@@ -172,15 +170,43 @@ export const commonReducer = (state = initialState, action) => {
|
||||
secondaryPopup: null,
|
||||
},
|
||||
};
|
||||
case types.SHOW_OPTIONAL_TERMS_CONFIRM_POPUP:
|
||||
return {
|
||||
...state,
|
||||
popup: {
|
||||
...state.popup,
|
||||
popupVisible: true,
|
||||
activePopup: "optionalTermsConfirmPopup",
|
||||
},
|
||||
};
|
||||
case types.HIDE_OPTIONAL_TERMS_CONFIRM_POPUP:
|
||||
return {
|
||||
...state,
|
||||
popup: {
|
||||
...state.popup,
|
||||
popupVisible: false,
|
||||
activePopup: null,
|
||||
optionalTermsConfirmSelected: false,
|
||||
},
|
||||
};
|
||||
case types.TOGGLE_OPTIONAL_TERMS_CONFIRM:
|
||||
return {
|
||||
...state,
|
||||
popup: {
|
||||
...state.popup,
|
||||
optionalTermsConfirmSelected: action.payload,
|
||||
},
|
||||
};
|
||||
case types.SET_EXIT_APP:
|
||||
return state;
|
||||
|
||||
case types.GET_TERMS_AGREE_YN: {
|
||||
const { privacyTerms, serviceTerms, purchaseTerms, paymentTerms } =
|
||||
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,
|
||||
@@ -189,6 +215,7 @@ export const commonReducer = (state = initialState, action) => {
|
||||
},
|
||||
introTermsAgree,
|
||||
checkoutTermsAgree,
|
||||
optionalTermsAgree,
|
||||
};
|
||||
}
|
||||
case types.REGISTER_DEVICE: {
|
||||
|
||||
@@ -18,6 +18,7 @@ const initialState = {
|
||||
recentlyViewedData: {},
|
||||
recentlyViewedSuccess: null,
|
||||
favoriteRetCode: null,
|
||||
setMyPageTermsAgreeResult: { data: null, retCode: null, error: null },
|
||||
};
|
||||
|
||||
export const myPageReducer = (state = initialState, action) => {
|
||||
@@ -139,6 +140,26 @@ export const myPageReducer = (state = initialState, action) => {
|
||||
},
|
||||
};
|
||||
|
||||
case types.SET_MYPAGE_TERMS_AGREE_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
setMyPageTermsAgreeResult: {
|
||||
data: action.payload,
|
||||
retCode: action.retCode,
|
||||
error: null,
|
||||
},
|
||||
};
|
||||
|
||||
case types.SET_MYPAGE_TERMS_AGREE_FAIL:
|
||||
return {
|
||||
...state,
|
||||
setMyPageTermsAgreeResult: {
|
||||
data: null,
|
||||
retCode: null,
|
||||
error: action.payload,
|
||||
},
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ export const panel_names = {
|
||||
THEME_CURATION_PANEL: "themeCurationPanel",
|
||||
IMAGE_PANEL: "imagepanel",
|
||||
SERVICE_UNAVAILABLE: "servicepanel",
|
||||
OPTIONAL_TERMS_PANEL: "optionaltermspanel", // 선택약관 Intro Panel
|
||||
// error
|
||||
ERROR_PANEL: "errorpanel",
|
||||
|
||||
@@ -76,13 +77,16 @@ export const ACTIVE_POPUP = {
|
||||
orderDetailPopup: "orderDetailPopup",
|
||||
orderDetailCancel: "orderDetailCancel",
|
||||
errorPopup: "errorPopup",
|
||||
returnExchangePopup: "returnExchangePopup",
|
||||
trackPackagePopup: "trackPackagePopup",
|
||||
returnExchangePopup: "returnExchangePopup", trackPackagePopup: "trackPackagePopup",
|
||||
unSupportedCountryPopup: "unSupportedCountryPopup",
|
||||
changeCountyPopup: "changeCounty",
|
||||
networkErrorPopup: "networkErrorPopup",
|
||||
endOfServicePopup: "endOfServicePopup",
|
||||
checkoutErrorPopup: "checkoutErrorPopup",
|
||||
optionalTermsConfirmPopup: "optionalTermsConfirmPopup",
|
||||
optionalTermsTest: "optionalTermsTest",
|
||||
introTermsPopup: "introTermsPopup",
|
||||
toast: "toast",
|
||||
};
|
||||
export const DEBUG_VIDEO_SUBTITLE_TEST = false;
|
||||
export const AUTO_SCROLL_DELAY = 600;
|
||||
|
||||
@@ -108,10 +108,6 @@ const hasTemplateCodeWithValue = (array, value) =>
|
||||
array?.some((obj) => obj?.shptmBrndOptTpCd === value) ?? false;
|
||||
|
||||
const shouldRenderComponent = (data) => {
|
||||
if (data === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
(Array.isArray(data) && data.length > 0) ||
|
||||
(typeof data === "object" && Object.keys(data).length > 0)
|
||||
|
||||
525
com.twin.app.shoptime/src/views/IntroPanel/IntroPanel.new.jsx
Normal file
525
com.twin.app.shoptime/src/views/IntroPanel/IntroPanel.new.jsx
Normal file
@@ -0,0 +1,525 @@
|
||||
// src: views/IntroPanel/IntroPanel.new.jsx
|
||||
|
||||
import React, { useCallback, useEffect, useState,useMemo } from "react";
|
||||
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import Region from "@enact/sandstone/Region";
|
||||
import Spotlight from "@enact/spotlight";
|
||||
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
|
||||
|
||||
import {
|
||||
setExitApp,
|
||||
setHidePopup,
|
||||
setShowPopup,
|
||||
setTermsAgreeYn,
|
||||
} from "../../actions/commonActions";
|
||||
import { registerDevice } from "../../actions/deviceActions";
|
||||
import { getWelcomeEventInfo } from "../../actions/eventActions";
|
||||
import { getHomeTerms } from "../../actions/homeActions";
|
||||
import {
|
||||
sendLogGNB,
|
||||
sendLogTerms,
|
||||
sendLogTotalRecommend,
|
||||
} from "../../actions/logActions";
|
||||
import { popPanel, pushPanel } from "../../actions/panelActions";
|
||||
import TButton, { TYPES } from "../../components/TButton/TButton";
|
||||
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 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";
|
||||
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{ enterTo: "last-focused" },
|
||||
"div"
|
||||
);
|
||||
|
||||
export default function IntroPanel({
|
||||
children,
|
||||
isTabActivated,
|
||||
handleCancel,
|
||||
spotlightId,
|
||||
...rest
|
||||
}) {
|
||||
delete rest.isTabActivated;
|
||||
delete rest.panelInfo;
|
||||
|
||||
useDebugKey({});
|
||||
const dispatch = useDispatch(); const termsData = useSelector((state) => state.home.termsData);
|
||||
const { popupVisible, activePopup, ...popupState } = useSelector(
|
||||
(state) => state.common.popup
|
||||
);
|
||||
const eventInfos = useSelector((state) => state.event.eventData);
|
||||
const regDeviceData = useSelector((state) => state.device.regDeviceData);
|
||||
const regDeviceInfoData = useSelector(
|
||||
(state) => state.device.regDeviceInfoData
|
||||
);
|
||||
|
||||
// const introTermsData = termsData?.data?.terms.filter(
|
||||
// (item) => item.trmsTpCd === "MST00401" || item.trmsTpCd === "MST00402"
|
||||
// );
|
||||
// const optionalTermsData = termsData?.data?.terms.filter(
|
||||
// (item) => item.trmsTpCd === "MST00405"
|
||||
// );
|
||||
|
||||
const introTermsData = useMemo(() => {
|
||||
return termsData?.data?.terms.filter(
|
||||
(item) => item.trmsTpCd === "MST00401" || item.trmsTpCd === "MST00402"
|
||||
) || [];
|
||||
}, [termsData]);
|
||||
|
||||
const optionalTermsData = useMemo(() => {
|
||||
return termsData?.data?.terms.filter(
|
||||
(item) => item.trmsTpCd === "MST00405"
|
||||
) || [];
|
||||
}, [termsData]);
|
||||
|
||||
const webOSVersion = useSelector(
|
||||
(state) => state.common.appStatus?.webOSVersion
|
||||
);
|
||||
// WebOS 버전별 UI 표시 모드 결정
|
||||
// 이미지 표시: 4.0, 5.0, 23, 24
|
||||
// 텍스트 표시: 4.5, 6.0, 22
|
||||
const shouldShowBenefitsView = useMemo(() => {
|
||||
if (!webOSVersion) return false;
|
||||
|
||||
const version = String(webOSVersion);
|
||||
|
||||
// 텍스트 표시 버전들
|
||||
const textVersions = ['4.5', '6.0', '22'];
|
||||
|
||||
// 이미지 표시 버전들
|
||||
const imageVersions = ['4.0', '5.0', '23', '24'];
|
||||
|
||||
// 텍스트 버전인지 확인
|
||||
const shouldShowText = textVersions.includes(version);
|
||||
|
||||
console.log('🔍 WebOS 버전별 UI 모드:');
|
||||
console.log(' - webOSVersion:', version);
|
||||
console.log(' - shouldShowText (텍스트 모드):', shouldShowText);
|
||||
console.log(' - 텍스트 버전들:', textVersions);
|
||||
console.log(' - 이미지 버전들:', imageVersions);
|
||||
|
||||
return shouldShowText;
|
||||
}, [webOSVersion]);
|
||||
|
||||
// 상태 관리
|
||||
const [currentTerms, setCurrentTerms] = useState(null);
|
||||
const [termsChecked, setTermsChecked] = useState(true); // Terms & Conditions 기본 체크
|
||||
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));
|
||||
}, []);
|
||||
// 디버깅용 WebOS 버전 로그
|
||||
useEffect(() => {
|
||||
console.log('🔍 IntroPanel WebOS 버전 정보:');
|
||||
console.log(' - webOSVersion:', webOSVersion);
|
||||
console.log(' - shouldShowBenefitsView:', shouldShowBenefitsView);
|
||||
}, [webOSVersion, shouldShowBenefitsView]);
|
||||
|
||||
// Select All 상태 업데이트
|
||||
useEffect(() => {
|
||||
setSelectAllChecked(termsChecked && privacyChecked && optionalChecked);
|
||||
}, [termsChecked, privacyChecked, optionalChecked]);
|
||||
|
||||
const handleTermsClick = useCallback(
|
||||
(trmsTpCdList) => {
|
||||
if (introTermsData) {
|
||||
const selectedTerms = introTermsData.find(
|
||||
(term) => term.trmsTpCd === trmsTpCdList
|
||||
);
|
||||
|
||||
setCurrentTerms(selectedTerms);
|
||||
dispatch(setShowPopup(Config.ACTIVE_POPUP.termsPopup));
|
||||
|
||||
const logTpNo =
|
||||
trmsTpCdList === "MST00402"
|
||||
? Config.LOG_TP_NO.TERMS.TEARMS_CONDITIONS
|
||||
: Config.LOG_TP_NO.TERMS.PRIVACY_POLICY;
|
||||
dispatch(sendLogTerms({ logTpNo }));
|
||||
}
|
||||
},
|
||||
[introTermsData, dispatch]
|
||||
);
|
||||
|
||||
const handleOptionalTermsClick = useCallback(
|
||||
(trmsTpCdList) => {
|
||||
if (optionalTermsData) {
|
||||
const selectedTerms = optionalTermsData.find(
|
||||
(term) => term.trmsTpCd === trmsTpCdList
|
||||
);
|
||||
|
||||
setCurrentTerms(selectedTerms);
|
||||
dispatch(setShowPopup(Config.ACTIVE_POPUP.termsPopup));
|
||||
|
||||
// const logTpNo =
|
||||
// trmsTpCdList === "MST00402"
|
||||
// ? Config.LOG_TP_NO.TERMS.TEARMS_CONDITIONS
|
||||
// : Config.LOG_TP_NO.TERMS.PRIVACY_POLICY;
|
||||
// dispatch(sendLogTerms({ logTpNo }));
|
||||
}
|
||||
},
|
||||
[optionalTermsData, dispatch]
|
||||
);
|
||||
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
dispatch(setHidePopup());
|
||||
}, [dispatch]); const handleAgree = useCallback(() => {
|
||||
// 필수 약관이 체크되어 있는지 확인
|
||||
if (!termsChecked || !privacyChecked) {
|
||||
// 필수 약관이 체크되지 않았을 때 알림
|
||||
// window.alert($L("Please agree to Terms & Conditions and Privacy Policy."));
|
||||
dispatch(setShowPopup(Config.ACTIVE_POPUP.alertPopup, {
|
||||
title: $L("Required Terms"),
|
||||
text: $L("Please agree to Terms & Conditions and Privacy Policy."),
|
||||
button1Text: $L("OK")
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// 약관 ID 정확하게 매핑
|
||||
const agreeTerms = [];
|
||||
|
||||
if (termsChecked) {
|
||||
agreeTerms.push("TID0000222"); // MST00402 -> TID0000222 (이용약관)
|
||||
}
|
||||
if (privacyChecked) {
|
||||
agreeTerms.push("TID0000223"); // MST00401 -> TID0000223 (개인정보처리방침)
|
||||
}
|
||||
if (optionalChecked) {
|
||||
agreeTerms.push("TID0000232"); // MST00405 -> TID0000232 (선택약관)
|
||||
}
|
||||
|
||||
console.log('최종 전송될 agreeTerms:', agreeTerms);
|
||||
|
||||
// registerDevice 호출 - 필수 + 선택 약관 모두 포함
|
||||
dispatch(registerDevice({
|
||||
agreeTerms: agreeTerms
|
||||
}));
|
||||
}, [termsChecked, privacyChecked, optionalChecked, dispatch]);
|
||||
|
||||
const handleDisagree = useCallback(() => {
|
||||
dispatch(setShowPopup(Config.ACTIVE_POPUP.exitPopup));
|
||||
dispatch(sendLogTerms({ logTpNo: Config.LOG_TP_NO.TERMS.DO_NOT_AGREE }));
|
||||
}, [dispatch]);
|
||||
|
||||
const onExit = useCallback(() => {
|
||||
dispatch(setExitApp());
|
||||
dispatch(
|
||||
sendLogTotalRecommend({
|
||||
contextName: Config.LOG_CONTEXT_NAME.SHOPTIME,
|
||||
messageId: Config.LOG_MESSAGE_ID.VIEW_CHANGE,
|
||||
visible: false,
|
||||
})
|
||||
);
|
||||
}, [dispatch]);
|
||||
|
||||
const onCancel = useCallback(() => {
|
||||
if (activePopup === null) {
|
||||
dispatch(setShowPopup(Config.ACTIVE_POPUP.exitPopup));
|
||||
}
|
||||
}, [dispatch, activePopup]);
|
||||
|
||||
// 체크박스 핸들러들
|
||||
const handleTermsToggle = useCallback(({ selected }) => {
|
||||
setTermsChecked(selected);
|
||||
}, []);
|
||||
|
||||
const handlePrivacyToggle = useCallback(({ selected }) => {
|
||||
setPrivacyChecked(selected);
|
||||
}, []);
|
||||
|
||||
const handleOptionalToggle = useCallback(({ selected }) => {
|
||||
setOptionalChecked(selected);
|
||||
}, []);
|
||||
|
||||
const handleSelectAllToggle = useCallback(({ selected }) => {
|
||||
setSelectAllChecked(selected);
|
||||
setTermsChecked(selected);
|
||||
setPrivacyChecked(selected);
|
||||
setOptionalChecked(selected);
|
||||
}, []);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
Spotlight.focus("termsCheckbox");
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
Spotlight.focus();
|
||||
}, [popupVisible]);
|
||||
|
||||
useEffect(() => {
|
||||
if (regDeviceData && regDeviceData.retCode === 0) {
|
||||
dispatch(getWelcomeEventInfo());
|
||||
}
|
||||
}, [dispatch, regDeviceData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (regDeviceInfoData && regDeviceInfoData.retCode === 0) {
|
||||
dispatch(setHidePopup());
|
||||
dispatch(popPanel(panel_names.INTRO_PANEL));
|
||||
}
|
||||
}, [dispatch, regDeviceInfoData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (eventInfos && Object.keys(eventInfos).length > 0 && webOSVersion) {
|
||||
let displayWelcomeEventPanel = false;
|
||||
|
||||
if (
|
||||
eventInfos?.welcomeEventFlag === "Y" ||
|
||||
(eventInfos?.welcomeBillCpnEventFlag === "Y" &&
|
||||
Number(webOSVersion) >= 6)
|
||||
) {
|
||||
displayWelcomeEventPanel = true;
|
||||
}
|
||||
|
||||
dispatch(popPanel(panel_names.INTRO_PANEL));
|
||||
dispatch(sendLogTerms({ logTpNo: Config.LOG_TP_NO.TERMS.AGREE }));
|
||||
|
||||
if (displayWelcomeEventPanel) {
|
||||
dispatch(
|
||||
pushPanel({
|
||||
name: panel_names.WELCOME_EVENT_PANEL,
|
||||
panelInfo: { eventInfos: eventInfos },
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [eventInfos, webOSVersion]);
|
||||
|
||||
const description = $L(
|
||||
"Check out more LIVE SHOWS now and enjoy shopping at Shop Time's special price on your TV by agreeing to the LG TV Shopping Terms and Conditions."
|
||||
);
|
||||
const title = "welcome to shoptime!";
|
||||
delete rest.isOnTop;
|
||||
|
||||
return (
|
||||
<Region title={title + description}>
|
||||
<TPanel
|
||||
className={css.panel}
|
||||
isTabActivated={false}
|
||||
handleCancel={onCancel}
|
||||
spotlightId={spotlightId}
|
||||
>
|
||||
<Container {...rest} className={css.introLayout}>
|
||||
{/* 첫 번째 영역: 헤더 섹션 */}
|
||||
<div className={css.headerSection}>
|
||||
<div className={css.titleContainer}>
|
||||
<div className={css.welcomeText}>
|
||||
{$L("Welcome to")}
|
||||
</div>
|
||||
<div className={css.brandContainer}>
|
||||
<span className={css.shopText}>{$L("Sh")}</span>
|
||||
<span className={css.oText}>{$L("o")}</span>
|
||||
<span className={css.timeText}>{$L("pTime !")}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={css.descriptionContainer}>
|
||||
<div className={css.descriptionText}>
|
||||
{description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 두 번째 영역: 약관 섹션 */}
|
||||
<div className={css.termsSection}>
|
||||
<div className={css.termsLeftPanel}>
|
||||
{/* Terms & Conditions */}
|
||||
<div className={css.termsItem}>
|
||||
<TCheckBoxSquare
|
||||
className={css.checkbox}
|
||||
selected={termsChecked}
|
||||
onToggle={handleTermsToggle}
|
||||
spotlightId="termsCheckbox"
|
||||
ariaLabel={$L("Terms & Conditions checkbox")}
|
||||
/>
|
||||
<TButton
|
||||
className={css.termsButton}
|
||||
onClick={() => handleTermsClick("MST00402")}
|
||||
spotlightId="termsButton"
|
||||
type={TYPES.terms}
|
||||
ariaLabel={$L("View Terms & Conditions")}
|
||||
>
|
||||
<span className={css.termsText}>
|
||||
{$L("Terms & Conditions")}
|
||||
</span>
|
||||
</TButton>
|
||||
</div>
|
||||
|
||||
{/* Privacy Policy */}
|
||||
<div className={css.termsItem}>
|
||||
<TCheckBoxSquare
|
||||
className={css.checkbox}
|
||||
selected={privacyChecked}
|
||||
onToggle={handlePrivacyToggle}
|
||||
spotlightId="privacyCheckbox"
|
||||
ariaLabel={$L("Privacy Policy checkbox")}
|
||||
/>
|
||||
<TButton
|
||||
className={css.termsButton}
|
||||
onClick={() => handleTermsClick("MST00401")}
|
||||
spotlightId="privacyButton"
|
||||
type={TYPES.terms}
|
||||
ariaLabel={$L("View Privacy Policy")}
|
||||
>
|
||||
<span className={css.termsText}>
|
||||
{$L("Privacy Policy")}
|
||||
</span>
|
||||
</TButton>
|
||||
</div>
|
||||
|
||||
{/* Optional Terms */}
|
||||
<div className={css.termsItem}>
|
||||
<TCheckBoxSquare
|
||||
className={css.checkbox}
|
||||
selected={optionalChecked}
|
||||
onToggle={handleOptionalToggle}
|
||||
spotlightId="optionalCheckbox"
|
||||
ariaLabel={$L("Optional Terms checkbox")}
|
||||
/>
|
||||
<TButton
|
||||
className={css.termsButton}
|
||||
onClick={() => handleOptionalTermsClick("MST00405")}
|
||||
spotlightId="optionalButton"
|
||||
type={TYPES.terms}
|
||||
ariaLabel={$L("View Optional Terms")}
|
||||
>
|
||||
<span className={css.termsText}>
|
||||
{$L("Optional Terms")}
|
||||
</span>
|
||||
</TButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className={css.termsRightPanel}>
|
||||
{shouldShowBenefitsView ? (
|
||||
// WebOS 텍스트 표시 버전 (4.5, 6.0, 22): 기존 텍스트 설명
|
||||
<div className={css.optionalDescription}>
|
||||
{$L('By checking "Optional terms", you allow Shop Time to use your activity (views, purchases, searches, etc.) to show you more relevant content, product recommendations, special offers, and ads. If you do not check, you can still use all basic Shop Time features')}
|
||||
</div>
|
||||
) : (
|
||||
// WebOS 이미지 표시 버전 (4.0, 5.0, 23, 24): OptionalTermsInfo 컴포넌트로 이미지 표시
|
||||
<OptionalTermsInfo
|
||||
displayMode="image"
|
||||
imageTitle={$L('Agree and Enjoy Special Benefits')}
|
||||
spotlightId="optional-terms-info"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 세 번째 영역: Select All */}
|
||||
<div className={css.selectAllSection}>
|
||||
<TCheckBoxSquare
|
||||
className={css.selectAllCheckbox}
|
||||
selected={selectAllChecked}
|
||||
onToggle={handleSelectAllToggle}
|
||||
spotlightId="selectAllCheckbox"
|
||||
ariaLabel={$L("Select All checkbox")}
|
||||
/>
|
||||
<span className={css.selectAllText}>
|
||||
{$L("Select All")}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 네 번째 영역: 버튼 섹션 */}
|
||||
<div className={css.buttonSection}>
|
||||
<TButton
|
||||
className={css.agreeButton}
|
||||
onClick={handleAgree}
|
||||
spotlightId="agreeButton"
|
||||
type={TYPES.agree}
|
||||
ariaLabel={$L("Agree to terms")}
|
||||
>
|
||||
{$L("Agree")}
|
||||
</TButton>
|
||||
<TButton
|
||||
className={css.disagreeButton}
|
||||
onClick={handleDisagree}
|
||||
spotlightId="disagreeButton"
|
||||
type={TYPES.agree}
|
||||
ariaLabel={$L("Do not agree to terms")}
|
||||
>
|
||||
{$L("Do Not Agree")}
|
||||
</TButton>
|
||||
</div>
|
||||
</Container>
|
||||
</TPanel>
|
||||
|
||||
{/* TERMS */}
|
||||
{activePopup === Config.ACTIVE_POPUP.termsPopup && (
|
||||
<TPopUp
|
||||
kind="introTermsPopup"
|
||||
open={popupVisible}
|
||||
onClose={onClose}
|
||||
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>
|
||||
)} {/* DO NOT AGREE */}
|
||||
{activePopup === Config.ACTIVE_POPUP.exitPopup && (
|
||||
<TPopUp
|
||||
kind="exitPopup"
|
||||
open={popupVisible}
|
||||
onExit={onExit}
|
||||
onClose={onClose}
|
||||
hasButton
|
||||
button1Text={$L("Exit")}
|
||||
button2Text={$L("Cancel")}
|
||||
hasText
|
||||
title={$L("Exit Shop Time")}
|
||||
text={$L("Are you sure you want to exit Shop Time?")}
|
||||
/>
|
||||
)} {/* ALERT POPUP (필수 약관 알림) */}
|
||||
{activePopup === Config.ACTIVE_POPUP.alertPopup && (
|
||||
<TPopUp
|
||||
kind="textPopup"
|
||||
open={popupVisible}
|
||||
onClose={onClose}
|
||||
hasButton
|
||||
button1Text={popupState.button1Text || $L("OK")}
|
||||
hasText
|
||||
title={$L("Required Terms")}
|
||||
text={$L("Please agree to Terms & Conditions and Privacy Policy.")}
|
||||
/>
|
||||
)}
|
||||
</Region>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
@import "../../style/CommonStyle.module.less";
|
||||
@import "../../style/utils.module.less";
|
||||
|
||||
.panel {
|
||||
> section {
|
||||
color: @COLOR_GRAY06;
|
||||
}
|
||||
}
|
||||
|
||||
.introLayout {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 45.5px 43px;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 40px;
|
||||
display: inline-flex;
|
||||
background-color: @BG_COLOR_03;
|
||||
|
||||
// 첫 번째 영역: 헤더 섹션 (타이틀 + 설명)
|
||||
.headerSection {
|
||||
width: 1834px;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 40px;
|
||||
display: flex;
|
||||
|
||||
.titleContainer {
|
||||
align-self: stretch;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
display: inline-flex;
|
||||
|
||||
.welcomeText {
|
||||
color: #807F81;
|
||||
font-size: 62px;
|
||||
font-family: 'LG Smart UI';
|
||||
font-weight: 400;
|
||||
line-height: 62px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.brandContainer {
|
||||
.shopText {
|
||||
color: #57585A;
|
||||
font-size: 70px;
|
||||
font-family: 'LG Smart_Korean';
|
||||
font-weight: 700;
|
||||
line-height: 75px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.oText {
|
||||
color: #C91D53;
|
||||
font-size: 70px;
|
||||
font-family: 'LG Smart_Korean';
|
||||
font-weight: 700;
|
||||
line-height: 75px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.timeText {
|
||||
color: #57585A;
|
||||
font-size: 70px;
|
||||
font-family: 'LG Smart_Korean';
|
||||
font-weight: 700;
|
||||
line-height: 75px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.descriptionContainer {
|
||||
align-self: stretch;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
display: inline-flex;
|
||||
|
||||
.descriptionText {
|
||||
width: 1012.49px;
|
||||
text-align: center;
|
||||
color: #807F81;
|
||||
font-size: 36px;
|
||||
font-family: 'LG Smart UI';
|
||||
font-weight: 400;
|
||||
line-height: 43px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 두 번째 영역: 약관 선택 섹션
|
||||
.termsSection {
|
||||
align-self: stretch;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
display: inline-flex;
|
||||
|
||||
.termsLeftPanel {
|
||||
flex: 1 1 0;
|
||||
padding-left: 60px;
|
||||
padding-right: 60px;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-end;
|
||||
gap: 20px;
|
||||
display: inline-flex;
|
||||
|
||||
.termsItem {
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
display: inline-flex; .checkbox {
|
||||
/* TCheckBoxSquare 컴포넌트가 스타일링을 담당 */
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
.termsButton {
|
||||
width: 530px;
|
||||
height: 120px;
|
||||
padding: 0 50px;
|
||||
background: @COLOR_WHITE;
|
||||
box-shadow: 0px 0px 30px rgba(0, 0, 0, 0.20);
|
||||
border-radius: 6px;
|
||||
border: 1px solid #CFCFCF;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
will-change: transform;
|
||||
|
||||
.termsText {
|
||||
// color: #207847;
|
||||
color: black;
|
||||
font-size: 35px;
|
||||
font-family: 'LG Smart UI';
|
||||
font-weight: 700;
|
||||
line-height: 35px;
|
||||
word-wrap: break-word;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
// 포커스 상태
|
||||
&:global(.spottable):global(.focus) {
|
||||
outline: 2px #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;
|
||||
}
|
||||
}
|
||||
|
||||
// 호버 효과
|
||||
&: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);
|
||||
|
||||
.termsText {
|
||||
color: #C70850;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} .termsRightPanel {
|
||||
flex: 1 1 0;
|
||||
align-self: stretch;
|
||||
padding-left: 60px;
|
||||
padding-right: 60px;
|
||||
border-left: 1px #C5C6C9 solid;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
display: flex;
|
||||
|
||||
.optionalDescription {
|
||||
width: 595px;
|
||||
color: #807F81;
|
||||
font-size: 26px;
|
||||
font-family: 'LG Smart UI';
|
||||
font-weight: 400;
|
||||
line-height: 43px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
// OptionalTermsInfo 컴포넌트 스타일
|
||||
.termsInfo {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 세 번째 영역: Select All
|
||||
.selectAllSection {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
display: inline-flex; .selectAllCheckbox {
|
||||
/* TCheckBoxSquare 컴포넌트가 스타일링을 담당 */
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
.selectAllText {
|
||||
color: @COLOR_BLACK;
|
||||
font-size: 35px;
|
||||
font-family: 'LG Smart UI';
|
||||
font-weight: 700;
|
||||
line-height: 35px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
// 네 번째 영역: 버튼 섹션
|
||||
.buttonSection {
|
||||
width: 940px;
|
||||
height: 100px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
justify-content: center;
|
||||
|
||||
.agreeButton {
|
||||
width: 450px;
|
||||
height: 100px;
|
||||
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;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
// 포커스 상태
|
||||
&:global(.spottable):global(.focus) {
|
||||
background-color: #a40640 !important;
|
||||
border-color: #a40640 !important;
|
||||
color: white !important;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
// 호버 효과
|
||||
&:hover {
|
||||
background-color: #a40640 !important;
|
||||
border-color: #a40640 !important;
|
||||
color: white !important;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
// 비활성화 상태
|
||||
&:disabled {
|
||||
background-color: @COLOR_GRAY03 !important;
|
||||
border-color: @COLOR_GRAY03 !important;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
.disagreeButton {
|
||||
width: 450px;
|
||||
height: 100px;
|
||||
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;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
// 포커스 상태
|
||||
&:global(.spottable):global(.focus) {
|
||||
background-color: #7a7a7a !important;
|
||||
border-color: #7a7a7a !important;
|
||||
color: white !important;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
// 호버 효과
|
||||
&:hover {
|
||||
background-color: #7a7a7a !important;
|
||||
border-color: #7a7a7a !important;
|
||||
color: white !important;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* intro terms popup */
|
||||
.introTermsConts {
|
||||
background-color: #f8f8f8;
|
||||
|
||||
> div:nth-child(2) {
|
||||
background-color: @COLOR_WHITE;
|
||||
border: 1px solid @COLOR_GRAY02;
|
||||
width: 980px;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.termsDesc {
|
||||
padding: 30px;
|
||||
min-height: 300px;
|
||||
color: @COLOR_GRAY03;
|
||||
font-size: 24px;
|
||||
line-height: 1.27;
|
||||
letter-spacing: normal;
|
||||
text-align: left;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.tab {
|
||||
width: 980px;
|
||||
}
|
||||
}
|
||||
|
||||
// 체크박스 커스텀 스타일
|
||||
:global(.tCheckBoxSquare) {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
|
||||
// 기본 상태
|
||||
&:before {
|
||||
content: '';
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
background: @COLOR_WHITE;
|
||||
border: 2px solid @COLOR_GRAY02;
|
||||
border-radius: 4px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
// 선택된 상태
|
||||
&.selected:before {
|
||||
background: #C91D53;
|
||||
border-color: #C91D53;
|
||||
}
|
||||
|
||||
// 선택된 상태의 체크 마크
|
||||
&.selected:after {
|
||||
content: '✓';
|
||||
color: @COLOR_WHITE;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
// 포커스 상태
|
||||
&.focus: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;
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,7 @@ import FeaturedBrandsPanel from "../FeaturedBrandsPanel/FeaturedBrandsPanel";
|
||||
import HomePanel from "../HomePanel/HomePanel";
|
||||
import HotPicksPanel from "../HotPicksPanel/HotPicksPanel";
|
||||
import ImagePanel from "../ImagePanel/ImagePanel";
|
||||
import IntroPanel from "../IntroPanel/IntroPanel";
|
||||
import IntroPanel from "../IntroPanel/IntroPanel.new";
|
||||
import LoadingPanel from "../LoadingPanel/LoadingPanel";
|
||||
import MyPagePanel from "../MyPagePanel/MyPagePanel";
|
||||
import OnSalePanel from "../OnSalePanel/OnSalePanel";
|
||||
@@ -80,6 +80,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 OptionalTermsConfirmTest from "../../components/Optional/OptionalTermsConfirmTest";
|
||||
import css from "./MainView.module.less";
|
||||
|
||||
const preloadImages = [
|
||||
@@ -110,6 +112,7 @@ const panelMap = {
|
||||
[Config.panel_names.THEME_CURATION_PANEL]: ThemeCurationPanel,
|
||||
[Config.panel_names.IMAGE_PANEL]: ImagePanel,
|
||||
[Config.panel_names.CONFIRM_PANEL]: ConfirmPanel,
|
||||
// [Config.panel_names.OPTIONAL_TERMS_PANEL]: TermsOfOptional,
|
||||
};
|
||||
|
||||
const logTpNoLiveSet = new Set([
|
||||
@@ -135,8 +138,7 @@ export default function MainView({ className, initService }) {
|
||||
const panels = useSelector((state) => state.panels.panels);
|
||||
const lastPanelAction = useSelector((state) => state.panels.lastPanelAction);
|
||||
const loadingComplete = useSelector((state) => state.common?.loadingComplete);
|
||||
const menuData = useSelector((state) => state.home.menuData?.data);
|
||||
const {
|
||||
const menuData = useSelector((state) => state.home.menuData?.data); const {
|
||||
popupVisible,
|
||||
activePopup,
|
||||
data: errorCode,
|
||||
@@ -169,6 +171,14 @@ export default function MainView({ className, initService }) {
|
||||
const [showEndOfServicePopup, setShowEndOfServicePopup] = useState(false);
|
||||
const topPanel = panels[panels.length - 1];
|
||||
|
||||
// OptionalTermsConfirm 팝업 상태 디버깅
|
||||
useEffect(() => {
|
||||
console.log('🔍 MainView 팝업 상태 변경:', {
|
||||
popupVisible,
|
||||
activePopup,
|
||||
});
|
||||
}, [popupVisible, activePopup]);
|
||||
|
||||
const isHomeOnTop = useMemo(() => {
|
||||
return (
|
||||
!mainIndex &&
|
||||
@@ -627,8 +637,8 @@ export default function MainView({ className, initService }) {
|
||||
dispatch(setShowPopup(Config.ACTIVE_POPUP.endOfServicePopup));
|
||||
}
|
||||
}, [webOSVersion]);
|
||||
|
||||
const handleErrorPopupClose = useCallback(() => {
|
||||
console.log('handleErrorPopupClose 호출됨! activePopup:', activePopup, 'popupData:', popupData);
|
||||
if (popupData?.shouldPopPanel) {
|
||||
dispatch(popPanel());
|
||||
}
|
||||
@@ -641,7 +651,7 @@ export default function MainView({ className, initService }) {
|
||||
Spotlight.focus(SpotlightIds.DETAIL_BUYNOW);
|
||||
});
|
||||
}
|
||||
}, [dispatch, popupData]);
|
||||
}, [dispatch, popupData, activePopup, topPanel?.name]);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (!isInternetConnected) {
|
||||
@@ -722,8 +732,7 @@ export default function MainView({ className, initService }) {
|
||||
popupData.retDetailCode,
|
||||
popupData.returnBindStrings
|
||||
)}
|
||||
</p>
|
||||
<TButton className={css.popupBtn} onClick={handleErrorPopupClose}>
|
||||
</p> <TButton className={css.popupBtn} onClick={handleErrorPopupClose}>
|
||||
{$L("OK")}
|
||||
</TButton>
|
||||
</div>
|
||||
@@ -770,10 +779,44 @@ export default function MainView({ className, initService }) {
|
||||
title={$L("Exit Shop Time")}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{/* {activePopup === "optionalTermsTest" && (
|
||||
<OptionalTermsConfirmTest open={popupVisible} />
|
||||
)} */}
|
||||
|
||||
{/* OptionalTermsConfirmPopup */}
|
||||
{activePopup === Config.ACTIVE_POPUP.optionalTermsConfirmPopup && (
|
||||
<OptionalTermsConfirm
|
||||
kind="optionalTermsConfirmPopup"
|
||||
open={popupVisible}
|
||||
onClose={handleErrorPopupClose}
|
||||
hasText
|
||||
hasButton
|
||||
button1Text={$L("OK")}
|
||||
button2Text={$L("Cancel")}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SystemNotification />
|
||||
{loadingComplete &&
|
||||
activePopup === Config.ACTIVE_POPUP.endOfServicePopup &&
|
||||
!skipEndOfServicePopup && <EndOfServicePopUp />}
|
||||
|
||||
{/* Pop up */}
|
||||
{activePopup === Config.ACTIVE_POPUP.toast && (
|
||||
<TPopUp
|
||||
kind="toast"
|
||||
open={popupVisible}
|
||||
onClose={handleErrorPopupClose}
|
||||
hasText
|
||||
hasButton
|
||||
button1Text={toastText}
|
||||
button2Text={$L("OK")}
|
||||
/>
|
||||
)}
|
||||
{activePopup === Config.ACTIVE_POPUP.optionalTermsTest && (
|
||||
<OptionalTermsConfirmTest open={popupVisible} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
// src/views/MyPagePanel/MyPageSub/TermsOfService/CustomTermButton.jsx
|
||||
|
||||
import React, { useCallback, useState } from "react";
|
||||
import classNames from "classnames";
|
||||
import Spotlight from "@enact/spotlight";
|
||||
import Spottable from "@enact/spotlight/Spottable";
|
||||
import css from "./CustomTermButton.module.less";
|
||||
|
||||
// 스포터블 컴포넌트 생성
|
||||
const SpottableDiv = Spottable("div");
|
||||
|
||||
/**
|
||||
* 약관 버튼 전용 커스텀 버튼 컴포넌트
|
||||
* TButton 컴포넌트를 기반으로 하되, 약관 버튼에 최적화된 레이아웃을 제공합니다.
|
||||
*/
|
||||
function CustomTermButton({
|
||||
children,
|
||||
icon,
|
||||
className,
|
||||
spotlightId,
|
||||
onClick,
|
||||
onFocus,
|
||||
onBlur,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
spotlightDisabled = false,
|
||||
disabled = false,
|
||||
selected = false,
|
||||
hovered = false,
|
||||
...rest
|
||||
}) {
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
|
||||
// 클릭 핸들러
|
||||
const handleClick = useCallback(
|
||||
(e) => {
|
||||
if (disabled) {
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (onClick) {
|
||||
onClick(e);
|
||||
}
|
||||
},
|
||||
[onClick, disabled]
|
||||
);
|
||||
|
||||
// 포커스 핸들러
|
||||
const handleFocus = useCallback(() => {
|
||||
setIsFocused(true);
|
||||
|
||||
if (onFocus) {
|
||||
onFocus();
|
||||
}
|
||||
}, [onFocus]);
|
||||
|
||||
// 블러 핸들러
|
||||
const handleBlur = useCallback(() => {
|
||||
setIsFocused(false);
|
||||
|
||||
if (onBlur) {
|
||||
onBlur();
|
||||
}
|
||||
}, [onBlur]);
|
||||
|
||||
return (
|
||||
<SpottableDiv
|
||||
{...rest}
|
||||
className={classNames(
|
||||
css.customTermButton,
|
||||
isFocused && css.focused,
|
||||
hovered && css.hovered,
|
||||
selected && css.selected,
|
||||
disabled && css.disabled,
|
||||
className
|
||||
)}
|
||||
spotlightId={spotlightId}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
onClick={handleClick}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
role="button"
|
||||
spotlightDisabled={spotlightDisabled}
|
||||
>
|
||||
<div className={css.content}>
|
||||
<div className={css.textContent}>{children}</div>
|
||||
{icon && <div className={css.iconContent}>{icon}</div>}
|
||||
</div>
|
||||
</SpottableDiv>
|
||||
);
|
||||
}
|
||||
|
||||
export default CustomTermButton;
|
||||
@@ -0,0 +1,96 @@
|
||||
// src/vies/MyPagePanel/MyPageSub/TermsOfService/CustomTermButton.module.less
|
||||
|
||||
/* 스타일 직접 정의 - 외부 의존성 제거 */
|
||||
|
||||
.customTermButton {
|
||||
// 기본 버튼 스타일
|
||||
width: 630px; // 디자인 스펙에 맞게 630px로 변경
|
||||
height: 120px;
|
||||
background-color: white;
|
||||
border-radius: 6px;
|
||||
outline: 1px #CFCFCF solid;
|
||||
outline-offset: -1px;
|
||||
box-shadow: 0px 0px 30px rgba(0, 0, 0, 0.20);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
will-change: transform;
|
||||
|
||||
// 중요: 내부 콘텐츠 레이아웃 설정
|
||||
.content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 0px; // 디자인 스펙에 맞게 50px로 변경
|
||||
}
|
||||
|
||||
// 텍스트 영역 스타일
|
||||
.textContent {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
color: #1a1a1a;
|
||||
font-size: 35px; // 디자인 스펙에 맞게 35px로 변경
|
||||
font-family: 'LG Smart UI';
|
||||
font-weight: 700;
|
||||
line-height: 35px; // 디자인 스펙에 맞게 35px로 변경
|
||||
white-space: nowrap; // 텍스트가 한 줄에 표시되도록 설정
|
||||
overflow: hidden; // 넘치는 텍스트 숨김
|
||||
text-overflow: ellipsis; // 넘치는 텍스트에 ... 표시
|
||||
transition: color 0.3s ease;
|
||||
margin-right: 10px; // 오른쪽 마진 더 줄임 (15px → 10px)
|
||||
}
|
||||
|
||||
// 아이콘 영역 스타일
|
||||
.iconContent {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
// 호버 상태
|
||||
&.hovered {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
|
||||
.textContent {
|
||||
color: #207847;
|
||||
}
|
||||
|
||||
&[data-type='OPTIONAL_TERMS'] .textContent {
|
||||
color: #c70850;
|
||||
}
|
||||
}
|
||||
|
||||
// 포커스 상태
|
||||
&.focused, &:focus {
|
||||
outline: 2px #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;
|
||||
|
||||
.textContent {
|
||||
color: #C91D53;
|
||||
}
|
||||
}
|
||||
|
||||
// 선택된 상태
|
||||
&.selected {
|
||||
background-color: rgba(201, 29, 83, 0.05);
|
||||
|
||||
.textContent {
|
||||
color: #C91D53;
|
||||
}
|
||||
}
|
||||
|
||||
// 비활성화 상태
|
||||
&.disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
// OptionalTermsInfo.clean.module.less
|
||||
// IntroPanel용 깔끔한 스타일 (global 스타일 제거)
|
||||
|
||||
.termsInfo {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// 텍스트 모드
|
||||
.textMode {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.textDescription {
|
||||
color: #1A1A1A;
|
||||
font-size: 18px;
|
||||
font-family: 'LG Smart UI', sans-serif;
|
||||
font-weight: 400;
|
||||
line-height: 1.4;
|
||||
text-align: center;
|
||||
word-wrap: break-word;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
// 이미지 모드
|
||||
.imageMode {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.imageTitle {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.imageTitleText {
|
||||
color: #C70850;
|
||||
font-size: 20px;
|
||||
font-family: 'LG Smart UI', sans-serif;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.imageContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.benefitImage {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.benefitImage:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
// 이미지 개수별 스타일
|
||||
.imageCount1 {
|
||||
.benefitImage {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
.imageCount2 {
|
||||
.benefitImage {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.imageCount3 {
|
||||
.benefitImage {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
// 반응형 디자인
|
||||
@media (max-width: 1200px) {
|
||||
.termsInfo {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.textDescription {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.imageTitleText {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.benefitImage {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.imageCount1 .benefitImage {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.imageCount2 .benefitImage {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.imageCount3 .benefitImage {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
// src/views/MyPagePanel/MyPageSub/TermsOfService/OptionalTermsInfo.jsx
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import kind from '@enact/core/kind';
|
||||
import PropTypes from 'prop-types';
|
||||
import $L from '@enact/i18n/$L';
|
||||
|
||||
import css from './OptionalTermsInfo.module.less';
|
||||
|
||||
import benefitImage1 from '/assets/images/benefits/image-benefits-1.png';
|
||||
import benefitImage2 from '/assets/images/benefits/image-benefits-2.png';
|
||||
import benefitImage3 from '/assets/images/benefits/image-benefits-3.png';
|
||||
|
||||
/**
|
||||
* OptionalTermsInfo 컴포넌트
|
||||
* WebOS 버전에 따라 텍스트 또는 이미지로 선택적 약관 정보를 표시합니다.
|
||||
*/
|
||||
const OptionalTermsInfo = kind({
|
||||
name: 'OptionalTermsInfo',
|
||||
|
||||
propTypes: {
|
||||
/**
|
||||
* 추가 CSS 클래스명
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
|
||||
/**
|
||||
* 표시 모드 - 텍스트 또는 이미지
|
||||
* @type {('text'|'image')}
|
||||
*/
|
||||
displayMode: PropTypes.oneOf(['text', 'image']),
|
||||
|
||||
/**
|
||||
* 이미지 모드일 때 사용할 이미지 소스들 (배열)
|
||||
*/
|
||||
imageSources: PropTypes.arrayOf(PropTypes.string),
|
||||
|
||||
/**
|
||||
* 이미지 모드일 때 표시할 제목
|
||||
*/
|
||||
imageTitle: PropTypes.string,
|
||||
|
||||
/**
|
||||
* 텍스트 모드일 때 표시할 설명 텍스트
|
||||
*/
|
||||
textDescription: PropTypes.string,
|
||||
|
||||
/**
|
||||
* Spotlight ID
|
||||
*/
|
||||
spotlightId: PropTypes.string
|
||||
},
|
||||
|
||||
defaultProps: {
|
||||
displayMode: 'text',
|
||||
imageSources: [
|
||||
benefitImage1,
|
||||
benefitImage2,
|
||||
benefitImage3
|
||||
],
|
||||
imageTitle: $L('Agree and Enjoy Special Benefits'),
|
||||
textDescription: $L('By checking Optional terms, you allow Shop Time to use your activity (views, purchases, searches, etc.) to show you more relevant content, product recommendations, special offers, and ads. If you do not check, you can still use all basic Shop Time features')
|
||||
},
|
||||
|
||||
styles: {
|
||||
css,
|
||||
className: 'termsInfo'
|
||||
},
|
||||
|
||||
computed: {
|
||||
className: ({ className, styler }) => styler.append(className)
|
||||
},
|
||||
|
||||
render: ({
|
||||
displayMode,
|
||||
imageSources,
|
||||
imageTitle,
|
||||
textDescription,
|
||||
spotlightId,
|
||||
...rest
|
||||
}) => {
|
||||
const imageCount = imageSources ? imageSources.length : 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
{...rest}
|
||||
data-spotlight-id={spotlightId}
|
||||
>
|
||||
{displayMode === 'text' ? (
|
||||
// 텍스트 모드
|
||||
<div className={css.textMode}>
|
||||
<p className={css.textDescription}>
|
||||
{textDescription}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
// 이미지 모드
|
||||
<div className={css.imageMode}>
|
||||
<div className={`${css.imageContainer} ${css[`imageCount${imageCount}`]}`}>
|
||||
{imageSources && imageSources.map((src, index) => (
|
||||
<img
|
||||
key={index}
|
||||
className={css.benefitImage}
|
||||
src={src}
|
||||
alt={$L(`Benefit image ${index + 1}`)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default memo(OptionalTermsInfo);
|
||||
export { OptionalTermsInfo };
|
||||
@@ -0,0 +1,115 @@
|
||||
// src/views/MyPagePanel/MyPageSub/TermsOfService/OptionalTermsInfo.module.less
|
||||
|
||||
.termsInfo {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
min-height: 360px;
|
||||
}
|
||||
|
||||
// 텍스트 모드
|
||||
.textMode {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.textDescription {
|
||||
color: #1A1A1A;
|
||||
font-size: 18px;
|
||||
font-family: 'LG Smart UI', sans-serif;
|
||||
font-weight: 400;
|
||||
line-height: 1.4;
|
||||
text-align: center;
|
||||
word-wrap: break-word;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
// 이미지 모드
|
||||
.imageMode {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.imageTitle {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.imageTitleText {
|
||||
color: #C70850;
|
||||
font-size: 20px;
|
||||
font-family: 'LG Smart UI', sans-serif;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.imageContainer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.benefitImage {
|
||||
height: 95%;
|
||||
width: 30%;
|
||||
object-fit: cover;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.benefitImage:hover {
|
||||
transform: scale(1.08);
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.imageCount1 .benefitImage,
|
||||
.imageCount2 .benefitImage,
|
||||
.imageCount3 .benefitImage {
|
||||
height: 95%;
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
// 반응형 디자인
|
||||
@media (max-width: 1200px) {
|
||||
.termsInfo {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.textDescription {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.imageTitleText {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.benefitImage,
|
||||
.imageCount1 .benefitImage,
|
||||
.imageCount2 .benefitImage,
|
||||
.imageCount3 .benefitImage {
|
||||
width: 30%;
|
||||
height: 95%;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,362 @@
|
||||
|
||||
@import "../../../../style/CommonStyle.module.less";
|
||||
@import "../../../../style/utils.module.less";
|
||||
|
||||
.panel {
|
||||
> section {
|
||||
color: @COLOR_GRAY06;
|
||||
}
|
||||
}
|
||||
|
||||
.termsOptionalLayout {
|
||||
.flex(@direction: column);
|
||||
background-color: @BG_COLOR_03;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
|
||||
.title {
|
||||
font-size: 60px;
|
||||
padding: 44px 201px;
|
||||
white-space: pre-wrap;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.txtPoint {
|
||||
font-weight: bold;
|
||||
font-size: 74px;
|
||||
color: #57585a;
|
||||
}
|
||||
|
||||
.pointColor {
|
||||
color: #c91d53;
|
||||
}
|
||||
|
||||
.description {
|
||||
width: 1200px;
|
||||
white-space: pre-wrap;
|
||||
line-height: normal;
|
||||
font-size: 36px;
|
||||
padding: 0 10px;
|
||||
padding-top: 14px;
|
||||
}
|
||||
|
||||
.termsItemsLayer {
|
||||
display: flex;
|
||||
margin: 113px 0 120px 0;
|
||||
.termsButton {
|
||||
> div {
|
||||
padding-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottomBtnLayer {
|
||||
margin-top: 50px;
|
||||
.flex();
|
||||
}
|
||||
}
|
||||
|
||||
/* TermsOfOptional 전용 스타일들 - IntroPanel 패턴 따라서 추가 */
|
||||
.welcomeSection {
|
||||
width: 100%;
|
||||
max-width: 1834px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4vh;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
.welcomeTitle {
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.welcomeText {
|
||||
color: #807f81;
|
||||
font-size: clamp(48px, 3.23vw, 62px);
|
||||
font-family: 'LG Smart UI';
|
||||
font-weight: 400;
|
||||
line-height: 1;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.brandTitle {
|
||||
font-size: clamp(54px, 3.65vw, 70px);
|
||||
font-family: 'LG Smart_Korean';
|
||||
font-weight: 700;
|
||||
line-height: 1.07;
|
||||
word-break: break-word;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.brandShop { color: #57585a; }
|
||||
.brandO { color: #c91d53; }
|
||||
.brandPTime { color: #57585a; }
|
||||
|
||||
.welcomeDescription {
|
||||
width: 100%;
|
||||
max-width: 1012.49px;
|
||||
text-align: center;
|
||||
color: #807f81;
|
||||
font-size: clamp(24px, 1.875vw, 36px);
|
||||
font-family: 'LG Smart UI';
|
||||
font-weight: 400;
|
||||
line-height: 1.19;
|
||||
word-break: break-word;
|
||||
margin-top: 1vh;
|
||||
}
|
||||
|
||||
.termsSection {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: stretch;
|
||||
padding: 0;
|
||||
margin: 50px 0;
|
||||
}
|
||||
|
||||
.mandatoryTerms {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 20px;
|
||||
padding-right: 60px;
|
||||
}
|
||||
|
||||
.termItem {
|
||||
height: 120px;
|
||||
display: inline-flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.termCheckbox,
|
||||
.selectAllCheckbox {
|
||||
width: 45px !important;
|
||||
height: 45px !important;
|
||||
min-width: 45px !important;
|
||||
min-height: 45px !important;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
|
||||
&.tCheckBox {
|
||||
border-radius: 0 !important;
|
||||
border: 3px solid #C70850 !important;
|
||||
background: #fff !important;
|
||||
transition: background 0.15s, border 0.15s;
|
||||
}
|
||||
|
||||
&.tCheckBox.selected {
|
||||
border: 3px solid #7A808D !important;
|
||||
background: #7A808D !important;
|
||||
}
|
||||
|
||||
&.tCheckBox::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 18px !important;
|
||||
height: 10px !important;
|
||||
border-left: 4px solid #fff !important;
|
||||
border-bottom: 4px solid #fff !important;
|
||||
transform: translate(-50%, -70%) rotate(-45deg) scale(0);
|
||||
border-radius: 0 !important;
|
||||
transition: transform 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
&.tCheckBox.selected::before {
|
||||
transform: translate(-50%, -70%) rotate(-45deg) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.termBox {
|
||||
width: 530px;
|
||||
height: 120px;
|
||||
padding: 0 50px;
|
||||
background: white;
|
||||
box-shadow: 0px 0px 30px rgba(0, 0, 0, 0.20);
|
||||
border-radius: 6px;
|
||||
outline: 1px #CFCFCF solid;
|
||||
outline-offset: -1px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
will-change: transform;
|
||||
|
||||
&:focus {
|
||||
outline: 2px #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;
|
||||
}
|
||||
|
||||
&.hovered {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
transform: translate3d(0, -2px, 0);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.termName {
|
||||
color: #1a1a1a;
|
||||
font-size: 35px;
|
||||
font-family: 'LG Smart UI';
|
||||
font-weight: 700;
|
||||
line-height: 35px;
|
||||
word-wrap: break-word;
|
||||
transition: color 0.3s ease;
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
margin-right: 20px;
|
||||
|
||||
&.titleHovered {
|
||||
color: #c70850;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.termNameMandatory {
|
||||
color: #207847 !important;
|
||||
}
|
||||
|
||||
.expandIconContainer {
|
||||
width: 37px;
|
||||
height: 37px;
|
||||
position: relative;
|
||||
border-radius: 100px;
|
||||
outline: 2.5px black solid;
|
||||
outline-offset: -2.5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
.selectAllSection {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.selectAllText {
|
||||
color: #1a1a1a;
|
||||
font-size: 35px;
|
||||
font-family: 'LG Smart UI';
|
||||
font-weight: 700;
|
||||
line-height: 35px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.agreeButton {
|
||||
width: 450px;
|
||||
height: 100px;
|
||||
padding-left: 50px;
|
||||
padding-right: 50px;
|
||||
background-color: #C70850 !important;
|
||||
border: 2px solid #C70850 !important;
|
||||
color: white !important;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 35px;
|
||||
font-family: 'LG Smart UI';
|
||||
font-weight: 700;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&.spott-focus {
|
||||
background-color: #a40640 !important;
|
||||
border-color: #a40640 !important;
|
||||
color: white !important;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
.disagreeButton {
|
||||
width: 450px;
|
||||
height: 100px;
|
||||
padding-left: 50px;
|
||||
padding-right: 50px;
|
||||
background-color: #999999 !important;
|
||||
border: 2px solid #999999 !important;
|
||||
color: white !important;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 35px;
|
||||
font-family: 'LG Smart UI';
|
||||
font-weight: 700;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&.spott-focus {
|
||||
background-color: #7a7a7a !important;
|
||||
border-color: #7a7a7a !important;
|
||||
color: white !important;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
background-color: #c5c6c9;
|
||||
margin: 0 16px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* IntroPanel과 동일한 약관 팝업 스타일 */
|
||||
.introTermsConts {
|
||||
background-color: #f8f8f8;
|
||||
|
||||
> div:nth-child(2) {
|
||||
background-color: @COLOR_WHITE;
|
||||
.border-solid(1px,@COLOR_GRAY02);
|
||||
width: 980px;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.termsDesc {
|
||||
padding: 30px;
|
||||
min-height: 300px;
|
||||
color: @COLOR_GRAY03;
|
||||
font-size: 24px;
|
||||
line-height: 1.27;
|
||||
letter-spacing: normal;
|
||||
text-align: left;
|
||||
.flex( @direction:column, @justifyCenter:flex-start,@alignCenter:flex-start);
|
||||
}
|
||||
}
|
||||
|
||||
.tab {
|
||||
width: 980px;
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
// src/views/MyPagePanel/MyPageSub/TermsOfService/TermsOfOptional.jsx
|
||||
|
||||
/**
|
||||
* ShopTime 애플리케이션의 선택적 약관 동의 컴포넌트 (IntroPanel 기반 단순화 버전)
|
||||
*
|
||||
* @component TermsOfOptional
|
||||
* @description
|
||||
* webOS TV 환경에서 선택적 약관 정보를 표시하고 사용자의 동의 여부를 관리합니다.
|
||||
* IntroPanel과 동일한 구조로 모달 문제 없이 안전하게 동작합니다.
|
||||
*/
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import Spotlight from "@enact/spotlight";
|
||||
|
||||
import {
|
||||
setHidePopup,
|
||||
setShowPopup,
|
||||
setExitApp
|
||||
} from "../../../../actions/commonActions";
|
||||
import { registerDevice } from "../../../../actions/deviceActions";
|
||||
import { sendLogTerms } from "../../../../actions/logActions";
|
||||
import { popPanel } from "../../../../actions/panelActions";
|
||||
|
||||
import TBody from "../../../../components/TBody/TBody";
|
||||
import TPanel from "../../../../components/TPanel/TPanel";
|
||||
import TButton from "../../../../components/TButton/TButton";
|
||||
import TCheckBoxSquare from "../../../../components/TCheckBox/TCheckBoxSquare";
|
||||
import TPopUp from "../../../../components/TPopUp/TPopUp";
|
||||
|
||||
import * as Config from "../../../../utils/Config";
|
||||
import { $L } from "../../../../utils/helperMethods";
|
||||
import css from "./TermsOfOptionalSimple.module.less";
|
||||
|
||||
// 약관 타입 정의
|
||||
const TERMS_TYPE = {
|
||||
TERMS_CONDITIONS: 'TERMS_CONDITIONS',
|
||||
PRIVACY_POLICY: 'PRIVACY_POLICY',
|
||||
OPTIONAL_TERMS: 'OPTIONAL_TERMS'
|
||||
};
|
||||
|
||||
// 약관 ID 매핑
|
||||
const TERMS_ID_MAP = {
|
||||
[TERMS_TYPE.TERMS_CONDITIONS]: "TID0000222",
|
||||
[TERMS_TYPE.PRIVACY_POLICY]: "TID0000223",
|
||||
[TERMS_TYPE.OPTIONAL_TERMS]: "TID0000232"
|
||||
};
|
||||
|
||||
// 약관 표시명
|
||||
const TERMS_DISPLAY_NAMES = {
|
||||
[TERMS_TYPE.TERMS_CONDITIONS]: "이용약관 동의 (필수)",
|
||||
[TERMS_TYPE.PRIVACY_POLICY]: "개인정보처리방침 동의 (필수)",
|
||||
[TERMS_TYPE.OPTIONAL_TERMS]: "선택약관 동의"
|
||||
};
|
||||
|
||||
// 필수 약관 여부 확인
|
||||
const isMandatoryTerms = (type) => {
|
||||
return type === TERMS_TYPE.TERMS_CONDITIONS || type === TERMS_TYPE.PRIVACY_POLICY;
|
||||
};
|
||||
|
||||
// 초기 체크박스 상태
|
||||
const getInitialCheckboxStates = () => ({
|
||||
[TERMS_TYPE.TERMS_CONDITIONS]: false,
|
||||
[TERMS_TYPE.PRIVACY_POLICY]: false,
|
||||
[TERMS_TYPE.OPTIONAL_TERMS]: false
|
||||
});
|
||||
|
||||
export default function TermsOfOptional({
|
||||
title,
|
||||
isModalMode,
|
||||
closeModalCallback,
|
||||
panelInfo,
|
||||
spotlightId = "termsOfOptionalPanel",
|
||||
initialAgreeState,
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// Redux state
|
||||
const commonPopupState = useSelector((state) => state.common?.popup || {});
|
||||
const { popupVisible = false, activePopup = null } = commonPopupState;
|
||||
const deviceState = useSelector((state) => state.device);
|
||||
|
||||
// 컴포넌트 상태
|
||||
const [checkboxStates, setCheckboxStates] = useState(() => getInitialCheckboxStates());
|
||||
const [isAllChecked, setIsAllChecked] = useState(false);
|
||||
const [spotlightDisabled, setSpotlightDisabled] = useState(false);
|
||||
|
||||
// 체크박스 개별 토글 핸들러
|
||||
const handleCheckboxToggle = useCallback((type) => ({ selected }) => {
|
||||
console.log(`[CHECKBOX-CLICK] ${type} 체크박스 클릭됨:`, selected);
|
||||
|
||||
setCheckboxStates(prev => {
|
||||
const newStates = { ...prev, [type]: selected };
|
||||
const allChecked = Object.values(newStates).every(state => state);
|
||||
setIsAllChecked(allChecked);
|
||||
console.log(`[CHECKBOX-CLICK] 새로운 전체 상태:`, newStates);
|
||||
return newStates;
|
||||
});
|
||||
}, []);
|
||||
|
||||
// 전체 선택 토글 핸들러
|
||||
const handleSelectAllToggle = useCallback(({ selected }) => {
|
||||
console.log(`[SELECT-ALL-CLICK] 전체 선택 클릭됨:`, selected);
|
||||
|
||||
setIsAllChecked(selected);
|
||||
setCheckboxStates(prev => {
|
||||
const newStates = Object.keys(prev).reduce((acc, key) => ({
|
||||
...acc,
|
||||
[key]: selected
|
||||
}), {});
|
||||
console.log(`[SELECT-ALL-CLICK] 새로운 전체 상태:`, newStates);
|
||||
return newStates;
|
||||
});
|
||||
}, []);
|
||||
|
||||
// 약관 내용 보기 핸들러 (간단한 알림 팝업으로 대체)
|
||||
const handleTermsView = useCallback((type) => {
|
||||
const termName = TERMS_DISPLAY_NAMES[type];
|
||||
const termId = TERMS_ID_MAP[type];
|
||||
|
||||
dispatch(setShowPopup(Config.ACTIVE_POPUP.alertPopup, {
|
||||
title: termName,
|
||||
text: `${termName}\n\n약관 ID: ${termId}\n\n실제 약관 내용은 서버에서 로드됩니다.\n현재는 단순화된 버전입니다.`,
|
||||
button1Text: $L("확인")
|
||||
}));
|
||||
}, [dispatch]);
|
||||
|
||||
// 팝업 닫기 핸들러
|
||||
const handleClosePopup = useCallback(() => {
|
||||
console.log('[TermsOfOptional] handleClosePopup triggered');
|
||||
dispatch(setHidePopup());
|
||||
}, [dispatch]);
|
||||
|
||||
// 앱 종료 핸들러
|
||||
const onExit = useCallback(() => {
|
||||
console.log('[TermsOfOptional] onExit triggered');
|
||||
dispatch(setHidePopup());
|
||||
dispatch(setExitApp());
|
||||
}, [dispatch]);
|
||||
|
||||
// 팝업 취소 핸들러
|
||||
const onClose = useCallback(() => {
|
||||
console.log('[TermsOfOptional] onClose triggered');
|
||||
dispatch(setHidePopup());
|
||||
}, [dispatch]);
|
||||
|
||||
// 뒤로가기 핸들러
|
||||
const handleBack = useCallback(() => {
|
||||
if (isModalMode && closeModalCallback) {
|
||||
console.log('[TermsOfOptional] Modal mode back pressed');
|
||||
closeModalCallback();
|
||||
} else if (!isModalMode && panelInfo && panelInfo.name) {
|
||||
console.log('[TermsOfOptional] Panel mode back pressed');
|
||||
dispatch(popPanel(panelInfo.name));
|
||||
} else {
|
||||
console.log('[TermsOfOptional] Back pressed - showing exit popup');
|
||||
dispatch(setShowPopup(Config.ACTIVE_POPUP.exitPopup));
|
||||
}
|
||||
}, [isModalMode, closeModalCallback, dispatch, panelInfo]);
|
||||
|
||||
// 동의 버튼 핸들러
|
||||
const handleAgree = useCallback(() => {
|
||||
// 필수 약관 검증
|
||||
const hasRequiredTerms = checkboxStates[TERMS_TYPE.TERMS_CONDITIONS] &&
|
||||
checkboxStates[TERMS_TYPE.PRIVACY_POLICY];
|
||||
|
||||
if (!hasRequiredTerms) {
|
||||
dispatch(setShowPopup(Config.ACTIVE_POPUP.alertPopup, {
|
||||
title: $L("Notice"),
|
||||
text: $L("Please agree to all required terms."),
|
||||
button1Text: $L("OK")
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// 동의한 약관 ID 배열 구성
|
||||
const agreeTerms = [];
|
||||
if (checkboxStates[TERMS_TYPE.TERMS_CONDITIONS]) {
|
||||
agreeTerms.push(TERMS_ID_MAP[TERMS_TYPE.TERMS_CONDITIONS]);
|
||||
}
|
||||
if (checkboxStates[TERMS_TYPE.PRIVACY_POLICY]) {
|
||||
agreeTerms.push(TERMS_ID_MAP[TERMS_TYPE.PRIVACY_POLICY]);
|
||||
}
|
||||
if (checkboxStates[TERMS_TYPE.OPTIONAL_TERMS]) {
|
||||
agreeTerms.push(TERMS_ID_MAP[TERMS_TYPE.OPTIONAL_TERMS]);
|
||||
}
|
||||
|
||||
console.log('최종 전송될 agreeTerms:', agreeTerms);
|
||||
|
||||
// registerDevice 호출
|
||||
dispatch(registerDevice({ agreeTerms }));
|
||||
}, [checkboxStates, dispatch]);
|
||||
|
||||
// registerDevice 성공 감지
|
||||
useEffect(() => {
|
||||
console.log('📊 deviceState 변화 감지:', deviceState);
|
||||
|
||||
if (deviceState?.regDeviceData &&
|
||||
(deviceState.regDeviceData.retCode === 0 || deviceState.regDeviceData.retCode === "000")) {
|
||||
console.log('✅ registerDevice 성공 감지');
|
||||
|
||||
setTimeout(() => {
|
||||
if (isModalMode && closeModalCallback) {
|
||||
console.log('📱 Modal mode - closeModalCallback 호출');
|
||||
closeModalCallback(true);
|
||||
} else {
|
||||
console.log('📋 Panel mode - popPanel 호출');
|
||||
dispatch(popPanel());
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}, [deviceState, isModalMode, closeModalCallback, dispatch]);
|
||||
|
||||
// 비동의 버튼 핸들러
|
||||
const handleDoNotAgree = useCallback(() => {
|
||||
console.log('[TermsOfOptional] Do Not Agree pressed');
|
||||
|
||||
dispatch(sendLogTerms({ logTpNo: Config.LOG_TP_NO.TERMS.DO_NOT_AGREE }));
|
||||
|
||||
dispatch(setShowPopup(Config.ACTIVE_POPUP.exitPopup, {
|
||||
title: $L(""),
|
||||
text: (
|
||||
<div>
|
||||
{$L("Are you sure you want to opt out?")}
|
||||
<br />
|
||||
<p style={{ color: "black", fontWeight: "bold" }}>
|
||||
{$L("The service will not be available anymore after opting out.")}
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
button1Text: $L("Yes"),
|
||||
button2Text: $L("No"),
|
||||
onButton1Press: onExit,
|
||||
onButton2Press: onClose
|
||||
}));
|
||||
}, [dispatch, onExit, onClose]);
|
||||
|
||||
// 컴포넌트 마운트 시 포커스 설정
|
||||
useEffect(() => {
|
||||
setSpotlightDisabled(false);
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
Spotlight.set('checkbox-all');
|
||||
}, 200);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
// Exit Popup 포커스 관리
|
||||
useEffect(() => {
|
||||
if (activePopup === Config.ACTIVE_POPUP.exitPopup && popupVisible) {
|
||||
const timer = setTimeout(() => {
|
||||
const focusAttempts = [
|
||||
() => Spotlight.set('tPopupBtn2'),
|
||||
() => Spotlight.set('tPopupBtn1'),
|
||||
];
|
||||
|
||||
for (const attempt of focusAttempts) {
|
||||
if (attempt()) {
|
||||
console.log('포커스 설정 성공');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [activePopup, popupVisible]);
|
||||
|
||||
return (
|
||||
<TPanel
|
||||
title={title || $L("Terms and Conditions")}
|
||||
handleCancel={handleBack}
|
||||
className={css.panel}
|
||||
spotlightId={spotlightId}
|
||||
noCloseButton={isModalMode}
|
||||
>
|
||||
<TBody className={css.introLayout}>
|
||||
{/* Welcome Section */}
|
||||
<div className={css.title}>
|
||||
<span className={css.welcomeText}>{$L("Welcome to")}</span>
|
||||
<span className={css.txtPoint}>
|
||||
<span className={css.brandShop}>Sh</span>
|
||||
<span className={css.pointColor}>o</span>
|
||||
<span className={css.brandPTime}>p Time !</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className={css.description}>
|
||||
{$L("Check out more LIVE SHOWS now and enjoy shopping at Shop Time's special price on your TV by agreeing to the LG TV Shopping Terms and Conditions.")}
|
||||
</div>
|
||||
|
||||
{/* Terms Section */}
|
||||
<div className={css.termsItemsLayer}>
|
||||
{Object.entries(TERMS_TYPE).map(([key, type]) => (
|
||||
<div key={type} className={css.termItem}>
|
||||
<TCheckBoxSquare
|
||||
id={`checkbox-${type.toLowerCase()}`}
|
||||
data-spotlight-id={`checkbox-${type.toLowerCase()}`}
|
||||
selected={checkboxStates[type]}
|
||||
onToggle={handleCheckboxToggle(type)}
|
||||
className={css.termCheckbox}
|
||||
disabled={spotlightDisabled}
|
||||
spotlightDisabled={spotlightDisabled}
|
||||
tabIndex={2}
|
||||
aria-label={TERMS_DISPLAY_NAMES[type]}
|
||||
role="checkbox"
|
||||
aria-checked={checkboxStates[type]}
|
||||
/>
|
||||
<div className={css.termsButton}>
|
||||
<TButton
|
||||
data-spotlight-id={`termbox-${type.toLowerCase()}`}
|
||||
onClick={() => handleTermsView(type)}
|
||||
className={css.termBox}
|
||||
spotlightDisabled={spotlightDisabled}
|
||||
>
|
||||
<span className={`${css.termName} ${isMandatoryTerms(type) ? css.termNameMandatory : ''}`}>
|
||||
{TERMS_DISPLAY_NAMES[type]}
|
||||
</span>
|
||||
<div className={css.expandIconContainer}>
|
||||
<svg width="37" height="37" viewBox="0 0 37 37" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="2.5" y="2.5" width="32" height="32" rx="16" stroke="black" strokeWidth="2.5"/>
|
||||
<path d="M16.0059 25.8301L22.74 19.1108C22.8205 19.0307 22.8844 18.9355 22.928 18.8306C22.9716 18.7258 22.994 18.6133 22.994 18.4998C22.994 18.3862 22.9716 18.2738 22.928 18.1689C22.8844 18.0641 22.8205 17.9689 22.74 17.8888L16.0059 11.1701" stroke="black" strokeWidth="2.5" strokeLinecap="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
</TButton>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Select All Section */}
|
||||
<div className={css.selectAllSection}>
|
||||
<TCheckBoxSquare
|
||||
id="checkbox-all"
|
||||
data-spotlight-id="checkbox-all"
|
||||
selected={isAllChecked}
|
||||
onToggle={handleSelectAllToggle}
|
||||
className={css.selectAllCheckbox}
|
||||
disabled={spotlightDisabled}
|
||||
spotlightDisabled={spotlightDisabled}
|
||||
tabIndex={1}
|
||||
/>
|
||||
<span className={css.selectAllText}>{$L("Select All")}</span>
|
||||
</div>
|
||||
|
||||
{/* Bottom Button Layer */}
|
||||
<div className={css.bottomBtnLayer}>
|
||||
<TButton
|
||||
onClick={handleAgree}
|
||||
className={css.agreeButton}
|
||||
spotlightId={spotlightId + "_agreeButton"}
|
||||
>
|
||||
{$L("Agree")}
|
||||
</TButton>
|
||||
|
||||
<TButton
|
||||
onClick={handleDoNotAgree}
|
||||
className={css.disagreeButton}
|
||||
spotlightId={spotlightId + "_disagreeButton"}
|
||||
>
|
||||
{$L("Do Not Agree")}
|
||||
</TButton>
|
||||
</div>
|
||||
|
||||
{/* Alert Popup */}
|
||||
{activePopup === Config.ACTIVE_POPUP.alertPopup && popupVisible && (
|
||||
<TPopUp
|
||||
kind="alertPopup"
|
||||
open={popupVisible}
|
||||
onClose={handleClosePopup}
|
||||
onButton1Press={handleClosePopup}
|
||||
hasButton
|
||||
button1Text={commonPopupState.button1Text || $L("OK")}
|
||||
hasText
|
||||
title={commonPopupState.title}
|
||||
text={commonPopupState.text}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Exit Popup */}
|
||||
{activePopup === Config.ACTIVE_POPUP.exitPopup && popupVisible && (
|
||||
<TPopUp
|
||||
kind="exitPopup"
|
||||
open={popupVisible}
|
||||
onExit={onExit}
|
||||
onClose={onClose}
|
||||
hasButton
|
||||
button1Text={$L("Yes")}
|
||||
button2Text={$L("No")}
|
||||
hasText
|
||||
title={$L("")}
|
||||
text={
|
||||
<div>
|
||||
{$L("Are you sure you want to opt out?")}
|
||||
<br />
|
||||
<p style={{ color: "black", fontWeight: "bold" }}>
|
||||
{$L("The service will not be available anymore after opting out.")}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</TBody>
|
||||
</TPanel>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
// src/views/MyPagePanel/MyPageSub/TermsOfService/TermsOfOptional.module.less
|
||||
// IntroPanel 기반으로 단순화된 스타일 - 모달 관련 문제 없음
|
||||
|
||||
@import "../../../../style/CommonStyle.module.less";
|
||||
@import "../../../../style/utils.module.less";
|
||||
|
||||
.panel {
|
||||
> section {
|
||||
color: @COLOR_GRAY06;
|
||||
}
|
||||
}
|
||||
|
||||
.introLayout {
|
||||
.flex(@direction: column);
|
||||
background-color: @BG_COLOR_03;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
|
||||
.title {
|
||||
font-size: 60px;
|
||||
padding: 44px 201px;
|
||||
white-space: pre-wrap;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.welcomeText {
|
||||
color: #807f81;
|
||||
font-size: clamp(48px, 3.23vw, 62px);
|
||||
font-family: 'LG Smart UI';
|
||||
font-weight: 400;
|
||||
line-height: 1;
|
||||
word-break: break-word;
|
||||
margin-right: 14px;
|
||||
}
|
||||
|
||||
.txtPoint {
|
||||
font-weight: bold;
|
||||
font-size: 74px;
|
||||
color: #57585a;
|
||||
}
|
||||
|
||||
.brandShop {
|
||||
color: #57585a;
|
||||
}
|
||||
|
||||
.pointColor {
|
||||
color: #c91d53;
|
||||
}
|
||||
|
||||
.brandPTime {
|
||||
color: #57585a;
|
||||
}
|
||||
|
||||
.description {
|
||||
width: 1200px;
|
||||
white-space: pre-wrap;
|
||||
line-height: normal;
|
||||
font-size: 36px;
|
||||
padding: 0 10px;
|
||||
padding-top: 14px;
|
||||
color: #807f81;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.termsItemsLayer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 113px 0 60px 0;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
|
||||
.termItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.termsButton {
|
||||
flex: 1;
|
||||
|
||||
> div {
|
||||
padding-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selectAllSection {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.bottomBtnLayer {
|
||||
margin-top: 50px;
|
||||
.flex();
|
||||
gap: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
// 체크박스 스타일
|
||||
.termCheckbox,
|
||||
.selectAllCheckbox {
|
||||
width: 45px !important;
|
||||
height: 45px !important;
|
||||
min-width: 45px !important;
|
||||
min-height: 45px !important;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
|
||||
&.tCheckBox {
|
||||
border-radius: 0 !important;
|
||||
border: 3px solid #C70850 !important;
|
||||
background: #fff !important;
|
||||
transition: background 0.15s, border 0.15s;
|
||||
}
|
||||
|
||||
&.tCheckBox.selected {
|
||||
border: 3px solid #7A808D !important;
|
||||
background: #7A808D !important;
|
||||
}
|
||||
|
||||
&.tCheckBox::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 18px !important;
|
||||
height: 10px !important;
|
||||
border-left: 4px solid #fff !important;
|
||||
border-bottom: 4px solid #fff !important;
|
||||
transform: translate(-50%, -70%) rotate(-45deg) scale(0);
|
||||
border-radius: 0 !important;
|
||||
transition: transform 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
&.tCheckBox.selected::before {
|
||||
transform: translate(-50%, -70%) rotate(-45deg) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 약관 박스 스타일
|
||||
.termBox {
|
||||
width: 100%;
|
||||
min-width: 500px;
|
||||
height: 80px;
|
||||
padding: 0 30px;
|
||||
background: white;
|
||||
box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.15);
|
||||
border-radius: 6px;
|
||||
outline: 1px #CFCFCF solid;
|
||||
outline-offset: -1px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:focus {
|
||||
outline: 2px #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;
|
||||
}
|
||||
|
||||
&: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);
|
||||
}
|
||||
}
|
||||
|
||||
.termName {
|
||||
color: #1a1a1a;
|
||||
font-size: 30px;
|
||||
font-family: 'LG Smart UI';
|
||||
font-weight: 700;
|
||||
line-height: 30px;
|
||||
word-wrap: break-word;
|
||||
transition: color 0.3s ease;
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
margin-right: 20px;
|
||||
|
||||
&.titleHovered {
|
||||
color: #c70850;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.termNameMandatory {
|
||||
color: #207847 !important;
|
||||
}
|
||||
|
||||
.expandIconContainer {
|
||||
width: 37px;
|
||||
height: 37px;
|
||||
position: relative;
|
||||
border-radius: 100px;
|
||||
outline: 2.5px black solid;
|
||||
outline-offset: -2.5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.selectAllText {
|
||||
color: #1a1a1a;
|
||||
font-size: 35px;
|
||||
font-family: 'LG Smart UI';
|
||||
font-weight: 700;
|
||||
line-height: 35px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
// 버튼 스타일
|
||||
.agreeButton {
|
||||
width: 450px;
|
||||
height: 100px;
|
||||
padding-left: 50px;
|
||||
padding-right: 50px;
|
||||
background-color: #C70850 !important;
|
||||
border: 2px solid #C70850 !important;
|
||||
color: white !important;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 35px;
|
||||
font-family: 'LG Smart UI';
|
||||
font-weight: 700;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&.spott-focus {
|
||||
background-color: #a40640 !important;
|
||||
border-color: #a40640 !important;
|
||||
color: white !important;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: #8f0533 !important;
|
||||
border-color: #8f0533 !important;
|
||||
color: white !important;
|
||||
}
|
||||
}
|
||||
|
||||
.disagreeButton {
|
||||
width: 450px;
|
||||
height: 100px;
|
||||
padding-left: 50px;
|
||||
padding-right: 50px;
|
||||
background-color: #999999 !important;
|
||||
border: 2px solid #999999 !important;
|
||||
color: white !important;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 35px;
|
||||
font-family: 'LG Smart UI';
|
||||
font-weight: 700;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&.spott-focus {
|
||||
background-color: #7a7a7a !important;
|
||||
border-color: #7a7a7a !important;
|
||||
color: white !important;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: #666666 !important;
|
||||
border-color: #666666 !important;
|
||||
color: white !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Spotlight 포커스 스타일
|
||||
[data-spotlight-id]:focus {
|
||||
outline: 2px solid #C91D53;
|
||||
outline-offset: 2px;
|
||||
box-shadow: 0 0 8px rgba(201, 29, 83, 0.5);
|
||||
}
|
||||
|
||||
.termBox[data-spotlight-id]:focus {
|
||||
background-color: rgba(201, 29, 83, 0.1);
|
||||
transform: translateY(-2px);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.termCheckbox[data-spotlight-id]:focus {
|
||||
transform: scale(1.05);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
// IntroPanel 스타일 호환성을 위한 추가 클래스들
|
||||
.introTermsConts {
|
||||
background-color: #f8f8f8;
|
||||
|
||||
> div:nth-child(2) {
|
||||
background-color: @COLOR_WHITE;
|
||||
.border-solid(1px,@COLOR_GRAY02);
|
||||
width: 980px;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.termsDesc {
|
||||
padding: 30px;
|
||||
min-height: 300px;
|
||||
color: @COLOR_GRAY03;
|
||||
font-size: 24px;
|
||||
line-height: 1.27;
|
||||
letter-spacing: normal;
|
||||
text-align: left;
|
||||
.flex( @direction:column, @justifyCenter:flex-start,@alignCenter:flex-start);
|
||||
}
|
||||
}
|
||||
|
||||
.tab {
|
||||
width: 980px;
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import React, {
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import Spotlight from "@enact/spotlight";
|
||||
import { Job } from "@enact/core/util";
|
||||
|
||||
import {
|
||||
changeLocalSettings,
|
||||
@@ -16,8 +17,13 @@ import {
|
||||
setExitApp,
|
||||
setHidePopup,
|
||||
setShowPopup,
|
||||
getTermsAgreeYn,
|
||||
} from "../../../../actions/commonActions";
|
||||
import { setMyTermsWithdraw } from "../../../../actions/myPageActions";
|
||||
import {
|
||||
setMyTermsWithdraw,
|
||||
setMyPageTermsAgree,
|
||||
} from "../../../../actions/myPageActions";
|
||||
import { getHomeTerms } from "../../../../actions/homeActions";
|
||||
import TBody from "../../../../components/TBody/TBody";
|
||||
import TButton, { TYPES } from "../../../../components/TButton/TButton";
|
||||
import TButtonScroller from "../../../../components/TButtonScroller/TButtonScroller";
|
||||
@@ -28,6 +34,7 @@ import { initialLocalSettings } from "../../../../reducers/localSettingsReducer"
|
||||
import * as Config from "../../../../utils/Config";
|
||||
import { $L, scaleH, scaleW } from "../../../../utils/helperMethods";
|
||||
import css from "./TermsOfService.module.less";
|
||||
import TCheckBoxSquare from "../../../../components/TCheckBox/TCheckBoxSquare";
|
||||
|
||||
export default function TermsOfService({ title, cbScrollTo }) {
|
||||
const [selectedTab, setSelectedTab] = useState(0);
|
||||
@@ -36,8 +43,15 @@ export default function TermsOfService({ title, cbScrollTo }) {
|
||||
const [trmsCdList, setTrmsCdList] = useState([]);
|
||||
const [closePopUp, setClosePopUp] = useState(false);
|
||||
const [resetScroll, setResetScroll] = useState(false);
|
||||
const [agreePopup, setAgreePopup] = useState(false);
|
||||
const [isOptionalChecked, setIsOptionalChecked] = useState(false);
|
||||
const [optionalDisagreePopupOpen, setOptionalDisagreePopupOpen] =
|
||||
useState(false);
|
||||
const [showCheckboxAlert, setShowCheckboxAlert] = useState(false);
|
||||
|
||||
|
||||
const { popupVisible } = useSelector((state) => state.common?.popup);
|
||||
const { optionalTermsAgree } = useSelector((state) => state.common);
|
||||
const termsData = useSelector((state) => state.home.termsData);
|
||||
const empTermsData = useSelector((state) => state.emp.empTermsData);
|
||||
const webOSVersion = useSelector(
|
||||
@@ -48,6 +62,8 @@ export default function TermsOfService({ title, cbScrollTo }) {
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const focusJob = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const newTabList = [];
|
||||
const tempList = [];
|
||||
@@ -90,6 +106,12 @@ export default function TermsOfService({ title, cbScrollTo }) {
|
||||
setSpotlightDisabled(false);
|
||||
}, [termsData, empTermsData, webOSVersion]);
|
||||
|
||||
useEffect(() => {
|
||||
if (termsData && termsData.data && termsData.data.terms) {
|
||||
dispatch(getTermsAgreeYn());
|
||||
}
|
||||
}, [termsData, dispatch]);
|
||||
|
||||
const handleItemClick = useCallback(
|
||||
({ index }) => {
|
||||
setSelectedTab(index);
|
||||
@@ -99,6 +121,29 @@ export default function TermsOfService({ title, cbScrollTo }) {
|
||||
[trmsTpCd]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (focusJob.current) {
|
||||
focusJob.current.stop();
|
||||
}
|
||||
if (
|
||||
termsList.length > 0 &&
|
||||
termsList[selectedTab]?.trmsTpCd === "MST00405"
|
||||
) {
|
||||
focusJob.current = new Job(() => {
|
||||
const focusTarget = optionalTermsAgree
|
||||
? "optional-disagree-button"
|
||||
: "optional-agree-checkbox";
|
||||
Spotlight.focus(focusTarget);
|
||||
});
|
||||
focusJob.current.startAfter(100);
|
||||
}
|
||||
return () => {
|
||||
if (focusJob.current) {
|
||||
focusJob.current.stop();
|
||||
}
|
||||
};
|
||||
}, [selectedTab, termsList, optionalTermsAgree]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!spotlightDisabled) {
|
||||
Spotlight.focus("tab-0");
|
||||
@@ -154,6 +199,86 @@ export default function TermsOfService({ title, cbScrollTo }) {
|
||||
dispatch(setHidePopup());
|
||||
}, [dispatch]);
|
||||
|
||||
const handleOptionalCheckboxToggle = useCallback(
|
||||
({ selected }) => {
|
||||
if (optionalTermsAgree) return;
|
||||
setIsOptionalChecked(selected);
|
||||
},
|
||||
[optionalTermsAgree]
|
||||
);
|
||||
|
||||
const handleOptionalAgree = useCallback(() => {
|
||||
console.log(
|
||||
"handleOptionalAgree called with isChecked:",
|
||||
isOptionalChecked
|
||||
);
|
||||
if (isOptionalChecked) {
|
||||
const firstThreeTermIds = termsList
|
||||
.slice(0, 3)
|
||||
.map((term) => term.termsId)
|
||||
.filter(Boolean);
|
||||
|
||||
if (firstThreeTermIds.length > 0) {
|
||||
const payload = { termsList: firstThreeTermIds, notTermsList: [] };
|
||||
console.log("Dispatching setMyPageTermsAgree with payload:", payload);
|
||||
dispatch(
|
||||
setMyPageTermsAgree(payload, (response) => {
|
||||
console.log("setMyPageTermsAgree callback response:", response);
|
||||
if (response.retCode === "000" || response.retCode === 0) {
|
||||
console.log("Optional terms agreement successful.");
|
||||
// 약관 동의의 후 약관 정보 조회
|
||||
dispatch(getHomeTerms({ trmsTpCdList: ["MST00401", "MST00402", "MST00405"] }));
|
||||
setAgreePopup(true);
|
||||
} else {
|
||||
console.error("Optional terms agreement failed:", response);
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
console.log("No terms found to agree to.");
|
||||
}
|
||||
} else {
|
||||
console.log("Checkbox not checked, not proceeding with agreement.");
|
||||
setShowCheckboxAlert(true);
|
||||
}
|
||||
}, [dispatch, termsList, isOptionalChecked]);
|
||||
|
||||
const handleOptionalDisagree = useCallback(() => {
|
||||
setOptionalDisagreePopupOpen(true);
|
||||
}, []);
|
||||
|
||||
const confirmOptionalDisagree = useCallback(() => {
|
||||
const optionalTerm = termsList.find(
|
||||
(term) => term.trmsTpCd === "MST00405"
|
||||
);
|
||||
if (optionalTerm && optionalTerm.termsId) {
|
||||
const payload = {
|
||||
mandatoryIncludeYn: "N",
|
||||
termsList: [optionalTerm.termsId],
|
||||
};
|
||||
console.log("Dispatching setMyTermsWithdraw with payload:", payload);
|
||||
// 선택약관 철회
|
||||
dispatch(setMyTermsWithdraw(payload, (response) => {
|
||||
console.log("setMyTermsWithdraw callback response:", response);
|
||||
if (response.retCode === "000" || response.retCode === 0) {
|
||||
console.log("Optional terms withdrawal successful.");
|
||||
// 약관 철회 후 약관 정보 조회
|
||||
dispatch(getHomeTerms({ trmsTpCdList: ["MST00401", "MST00402", "MST00405"] }));
|
||||
setIsOptionalChecked(false);
|
||||
} else {
|
||||
console.error("Optional terms withdrawal failed:", response);
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
console.log("No optional term found to disagree.");
|
||||
}
|
||||
setOptionalDisagreePopupOpen(false);
|
||||
}, [termsList, dispatch]);
|
||||
|
||||
const onCancelDisagree = useCallback(() => {
|
||||
// setDisagreeConfirmOpen(false);
|
||||
}, []);
|
||||
|
||||
const termsAriaLabel = useMemo(() => {
|
||||
if (!termsList || !termsList[selectedTab]) return "";
|
||||
|
||||
@@ -179,7 +304,11 @@ export default function TermsOfService({ title, cbScrollTo }) {
|
||||
selectedIndex={selectedTab && selectedTab}
|
||||
/>
|
||||
<TButtonScroller
|
||||
boxHeight={scaleH(714)}
|
||||
boxHeight={
|
||||
termsList[selectedTab]?.trmsTpCd === "MST00405"
|
||||
? scaleH(469)
|
||||
: scaleH(714)
|
||||
}
|
||||
width={scaleW(1680)}
|
||||
resetScrollPosition={resetScroll}
|
||||
>
|
||||
@@ -193,6 +322,49 @@ export default function TermsOfService({ title, cbScrollTo }) {
|
||||
</div>
|
||||
</TButtonScroller>
|
||||
|
||||
{termsList[selectedTab]?.trmsTpCd === "MST00405" ? (
|
||||
<div className={css.optionalContainer}>
|
||||
<div className={css.checkboxContainer}>
|
||||
<TCheckBoxSquare
|
||||
selected={isOptionalChecked}
|
||||
onToggle={handleOptionalCheckboxToggle}
|
||||
spotlightId="optional-agree-checkbox"
|
||||
spotlightDisabled={spotlightDisabled || optionalTermsAgree}
|
||||
disabled={optionalTermsAgree}
|
||||
/>
|
||||
<div
|
||||
className={`${css.checkboxLabel} ${
|
||||
optionalTermsAgree ? css.disabledLabel : ""
|
||||
}`}
|
||||
>
|
||||
{$L(
|
||||
"I agree to receive Personalized Recommendations and Advertisements"
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={css.buttonContainer}>
|
||||
<TButton
|
||||
onClick={handleOptionalAgree}
|
||||
className={css.agreeButton}
|
||||
spotlightId="optional-agree-button"
|
||||
spotlightDisabled={
|
||||
spotlightDisabled || optionalTermsAgree
|
||||
}
|
||||
disabled={optionalTermsAgree}
|
||||
>
|
||||
{$L("Agree")}
|
||||
</TButton>
|
||||
<TButton
|
||||
onClick={handleOptionalDisagree}
|
||||
className={css.disagreeButton}
|
||||
spotlightDisabled={spotlightDisabled}
|
||||
spotlightId="optional-disagree-button"
|
||||
>
|
||||
{$L("Do Not Agree")}
|
||||
</TButton>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className={css.buttonBox}>
|
||||
{empTermsData && (
|
||||
<div className={css.termText}>
|
||||
@@ -230,6 +402,7 @@ export default function TermsOfService({ title, cbScrollTo }) {
|
||||
: $L("LG Account")}
|
||||
</TButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* TODO 추후 폰트 변경 */}
|
||||
@@ -265,6 +438,38 @@ export default function TermsOfService({ title, cbScrollTo }) {
|
||||
"Thank you for using the Shop Time, and we hope to see you again. The app will close in 3 seconds."
|
||||
)}
|
||||
/>
|
||||
|
||||
<TPopUp
|
||||
kind="textPopup"
|
||||
open={agreePopup}
|
||||
onClose={() => setAgreePopup(false)}
|
||||
hasButton
|
||||
button1Text={$L("OK")}
|
||||
hasText
|
||||
title={$L("Agreement Complete")}
|
||||
text={$L("Your agreement has been processed.")}
|
||||
/>
|
||||
|
||||
{/* 새로 추가된 optionalAgreement 팝업 */}
|
||||
<TPopUp
|
||||
kind="optionalAgreement"
|
||||
open={optionalDisagreePopupOpen}
|
||||
onClose={() => setOptionalDisagreePopupOpen(false)}
|
||||
onClick={confirmOptionalDisagree}
|
||||
hasText
|
||||
text={$L("Are you sure you want to disagree with the optional terms?")}
|
||||
/>
|
||||
|
||||
<TPopUp
|
||||
kind="textPopup"
|
||||
open={showCheckboxAlert}
|
||||
onClose={() => setShowCheckboxAlert(false)}
|
||||
hasButton
|
||||
button1Text={$L("OK")}
|
||||
hasText
|
||||
title={$L("Notice")}
|
||||
text={$L("Please check the box to agree to the terms.")}
|
||||
/>
|
||||
</div>
|
||||
</TBody>
|
||||
</>
|
||||
|
||||
@@ -40,3 +40,144 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.optionalContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 40px;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.checkboxContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.buttonContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.checkboxLabel {
|
||||
color: #1a1a1a;
|
||||
font-size: 35px;
|
||||
font-family: "LG Smart UI";
|
||||
font-weight: 700;
|
||||
line-height: 35px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.disabledLabel {
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
.agreeButton {
|
||||
width: 240px;
|
||||
height: 80px;
|
||||
background: #c70850;
|
||||
box-shadow: 0px 0px 50px rgba(0, 0, 0, 0.5);
|
||||
border-radius: 12px;
|
||||
color: white;
|
||||
font-size: 30px;
|
||||
font-weight: 700;
|
||||
|
||||
&:not([disabled]) {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: #a60641;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.disagreeButton {
|
||||
width: 240px;
|
||||
height: 80px;
|
||||
background: #777d8a;
|
||||
border-radius: 12px;
|
||||
color: #e6e6e6;
|
||||
font-size: 30px;
|
||||
font-weight: 700;
|
||||
|
||||
&:not([disabled]) {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: #626771;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.disagreePopup {
|
||||
:global {
|
||||
.info {
|
||||
width: 950px !important;
|
||||
padding: 60px 57px 40px 57px !important;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 35px !important;
|
||||
font-weight: 700 !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.popup_container {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
gap: 30px !important;
|
||||
}
|
||||
|
||||
.button_container {
|
||||
display: flex !important;
|
||||
flex-direction: row !important;
|
||||
justify-content: center !important;
|
||||
gap: 12px !important;
|
||||
|
||||
.button {
|
||||
width: 380px !important;
|
||||
height: 80px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.disagreePopup :global(.info) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.disagreePopupContainer {
|
||||
width: 950px;
|
||||
padding: 60px 57px 40px 57px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 30px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.disagreePopupText {
|
||||
font-size: 35px;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.disagreePopupButtonContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.disagreePopupButton {
|
||||
width: 380px;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
// src/components/TermsPopup/TermsPopup.jsx
|
||||
/**
|
||||
* ShopTime 애플리케이션의 선택적 약관 동의 팝업 컴포넌트
|
||||
* * @component TermsPopup
|
||||
* @description
|
||||
* webOS TV 환경에서 개인정보 기반 추천/광고 동의 팝업을 표시합니다.
|
||||
* 사용자는 약관 내용을 스크롤하여 확인하고 동의 또는 거부할 수 있습니다.
|
||||
* * @param {Object} props
|
||||
* @param {boolean} props.visible - 팝업 표시 여부
|
||||
* @param {Function} props.onAgree - 동의 버튼 클릭 시 콜백 함수
|
||||
* @param {Function} props.onClose - 닫기 버튼 클릭 시 콜백 함수
|
||||
* @param {Object} props.termsData - 약관 데이터 객체 (title, content)
|
||||
* @param {boolean} props.isAlreadyAgreed - 이미 동의한 약관인지 여부
|
||||
*/
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import Spotlight from "@enact/spotlight";
|
||||
|
||||
import TButton, { TYPES } from "../../../../components/TButton/TButton";
|
||||
import TButtonScroller from "../../../../components/TButtonScroller/TButtonScroller";
|
||||
import TPopUp from "../../../../components/TPopUp/TPopUp";
|
||||
import { $L, scaleH, scaleW } from "../../../../utils/helperMethods";
|
||||
import css from "./TermsPopup.module.less";
|
||||
|
||||
export default function TermsPopup({ visible, onAgree, onClose, termsData, isAlreadyAgreed = false }) {
|
||||
const [spotlightDisabled, setSpotlightDisabled] = useState(true);
|
||||
const [resetScroll, setResetScroll] = useState(false);
|
||||
|
||||
/**
|
||||
* 팝업이 표시될 때 Spotlight 활성화
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
setSpotlightDisabled(false);
|
||||
// 팝업이 열리면 스크롤을 맨 위로 리셋
|
||||
setResetScroll(true);
|
||||
// 첫 번째 버튼에 포커스 설정 (약간의 지연을 두어 렌더링 완료 후 포커스)
|
||||
setTimeout(() => {
|
||||
// 이미 동의된 약관이면 Close 버튼에, 아니면 Agree 버튼에 포커스
|
||||
const focusTargetId = isAlreadyAgreed ? "terms-close-button" : "terms-agree-button";
|
||||
Spotlight.focus(focusTargetId);
|
||||
}, 100);
|
||||
} else {
|
||||
setSpotlightDisabled(true);
|
||||
}
|
||||
}, [visible, isAlreadyAgreed]);
|
||||
|
||||
/**
|
||||
* 스크롤 리셋 완료 핸들러
|
||||
*/
|
||||
const handleResetScrollComplete = useCallback(() => {
|
||||
setResetScroll(false);
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* 동의 버튼 클릭 핸들러
|
||||
*/
|
||||
const handleAgree = useCallback(() => {
|
||||
if (onAgree) {
|
||||
onAgree();
|
||||
}
|
||||
}, [onAgree]);
|
||||
|
||||
/**
|
||||
* 닫기 버튼 클릭 핸들러
|
||||
*/
|
||||
const handleClose = useCallback(() => {
|
||||
if (onClose) {
|
||||
onClose();
|
||||
}
|
||||
}, [onClose]);
|
||||
|
||||
/**
|
||||
* 약관 내용 텍스트 - termsData가 있으면 사용, 없으면 기본값
|
||||
*/
|
||||
const displayTitle = termsData?.title || $L("Optional Terms");
|
||||
const displayContent = termsData?.content || `I agree that LG Electronics (LGE) may collect and use Basic Identifier, Commercial Information, Network Activity, Geolocation Data and Inferences, each as defined in the Shop Time Privacy Policy, for personalized and advertising purposes including to display advertisements and offers that are personalized to me or to recommend features, products or contents that might be of interest to me. I understand that I can withdraw my consent at any time by contacting LGE using the contact details [in the Shop Time Privacy Policy] or within [Shop Time application settings] and that any further information that is collected about me will not be used for personalized advertising purposes unless I provide my consent again.`;
|
||||
const termsContent = displayContent.length > 0 ? displayContent : $L("No terms content available.");
|
||||
console.log('TermsPopup 렌더링:', {
|
||||
visible,
|
||||
displayTitle,
|
||||
contentLength: displayContent.length,
|
||||
isAlreadyAgreed
|
||||
});
|
||||
|
||||
if (!visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TPopUp
|
||||
kind="termsPopup"
|
||||
open={visible}
|
||||
onClose={handleClose}
|
||||
hasButton={false}
|
||||
className={css.popup}
|
||||
>
|
||||
<div className={css.container}>
|
||||
{/* 약관 카드 */}
|
||||
<div className={css.termsCard}>
|
||||
{/* 헤더 */}
|
||||
{/* 헤더 - 항상 고정 */}
|
||||
<div className={css.header}>
|
||||
<div className={css.title}>
|
||||
{displayTitle}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 내용 영역 */}
|
||||
<div className={css.contentArea}>
|
||||
<div className={css.scrollerContainer}>
|
||||
<TButtonScroller
|
||||
boxHeight={scaleH(460)}
|
||||
width={scaleW(980)}
|
||||
resetScroll={resetScroll}
|
||||
onResetScrollComplete={handleResetScrollComplete}
|
||||
className={css.scroller}
|
||||
>
|
||||
<div className={css.termsContent}>
|
||||
{/* <div className={css.termsTitle}>
|
||||
{$L("Interest Based Recommendation/Advertisement Agreement")}
|
||||
</div> */}
|
||||
<div
|
||||
className={css.termsText}
|
||||
dangerouslySetInnerHTML={{ __html: termsContent }}
|
||||
/>
|
||||
</div>
|
||||
</TButtonScroller>
|
||||
</div>
|
||||
|
||||
{/* 스크롤바 영역 (시각적 표시용) */}
|
||||
<div className={css.scrollbarArea}>
|
||||
<div className={css.scrollbarButton}>
|
||||
<div className={css.scrollbarIcon} />
|
||||
</div>
|
||||
<div className={css.scrollbarTrack}>
|
||||
<div className={css.scrollbarThumb} />
|
||||
</div>
|
||||
<div className={css.scrollbarButton}>
|
||||
<div className={css.scrollbarIcon} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 버튼 영역 */}
|
||||
<div className={css.buttonArea}>
|
||||
<div className={css.buttonContainer}> <TButton
|
||||
id="terms-agree-button"
|
||||
onClick={handleAgree}
|
||||
type={TYPES.primary}
|
||||
className={css.agreeButton}
|
||||
spotlightDisabled={spotlightDisabled}
|
||||
disabled={isAlreadyAgreed}
|
||||
>
|
||||
{$L("Agree")}
|
||||
</TButton>
|
||||
<TButton
|
||||
id="terms-close-button"
|
||||
onClick={handleClose}
|
||||
type={TYPES.secondary}
|
||||
className={css.closeButton}
|
||||
spotlightDisabled={spotlightDisabled}
|
||||
>
|
||||
{$L("Close")}
|
||||
</TButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TPopUp>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
// src/components/TermsPopup/TermsPopup.module.less
|
||||
|
||||
// 컬러 변수 정의
|
||||
@lg-primary-color: #C70850;
|
||||
@background-color: #E6EBF0;
|
||||
@white: #FFFFFF;
|
||||
@border-color: #CCCCCC;
|
||||
@scrollbar-color: #666666;
|
||||
@scrollbar-track-color: #CCCCCC;
|
||||
@button-secondary-color: #777D8A;
|
||||
@button-text-color: #E6E6E6;
|
||||
@black: #000000;
|
||||
@button-shadow: rgba(0, 0, 0, 0.50);
|
||||
@popup-shadow: rgba(0, 0, 0, 0.30);
|
||||
|
||||
// 폰트 변수
|
||||
@font-family: 'LG Smart UI', sans-serif;
|
||||
|
||||
// 크기 변수
|
||||
@popup-padding-top: 60px;
|
||||
@popup-padding-bottom: 40px;
|
||||
@popup-padding-horizontal: 57px;
|
||||
@header-padding: 17px 31px;
|
||||
@content-padding: 31px;
|
||||
@button-width: 240px;
|
||||
@button-height: 80px;
|
||||
@button-gap: 12px;
|
||||
@content-height: 460px;
|
||||
@border-radius-small: 4px;
|
||||
@border-radius-medium: 12px;
|
||||
|
||||
// TPopUp 오버라이드 스타일
|
||||
.popup {
|
||||
// TPopUp 컴포넌트 기본 스타일 오버라이드
|
||||
:global(.enact_ui_Popup_Popup_popup) {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
padding: @popup-padding-top @popup-padding-horizontal @popup-padding-bottom @popup-padding-horizontal;
|
||||
background: @background-color;
|
||||
box-shadow: 0px 20px 12px @popup-shadow;
|
||||
border-radius: @border-radius-small;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
// 메인 컨테이너
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
gap: 40px;
|
||||
max-width: calc(100vw - 114px); // 좌우 패딩 57px * 2 = 114px 제외
|
||||
max-height: calc(100vh - 100px); // 상하 패딩 60px + 40px = 100px 제외
|
||||
}
|
||||
|
||||
// 약관 카드
|
||||
.termsCard {
|
||||
width: 100%;
|
||||
flex: 1; // 남은 공간을 모두 차지
|
||||
background: @white;
|
||||
border-radius: @border-radius-small;
|
||||
border: 1px solid @border-color;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
overflow: hidden; // 내용이 넘치지 않도록
|
||||
}
|
||||
|
||||
// 헤더 영역
|
||||
.header {
|
||||
width: 100%;
|
||||
padding: @header-padding;
|
||||
background: @white;
|
||||
border-bottom: 1px solid @border-color;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
.title {
|
||||
text-align: center;
|
||||
color: @lg-primary-color;
|
||||
font-size: 30px;
|
||||
font-family: @font-family;
|
||||
font-weight: 700;
|
||||
line-height: 36px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
// 내용 영역
|
||||
.contentArea {
|
||||
width: 100%;
|
||||
flex: 1; // 헤더를 제외한 남은 공간 차지
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
min-height: 0; // flexbox 축소 허용
|
||||
}
|
||||
|
||||
// 스크롤러 컨테이너 - 전체 영역 차지
|
||||
.scrollerContainer {
|
||||
width: 100%;
|
||||
height: 100%; // contentArea의 높이에 맞춤
|
||||
padding: @content-padding;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
min-width: 0; // flexbox 축소 허용
|
||||
}
|
||||
|
||||
// TButtonScroller 커스터마이징
|
||||
.scroller {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
// TButtonScroller 컴포넌트 스타일 오버라이드
|
||||
:global(.enact_ui_Scroller_Scroller_scroller) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
// 스크롤바를 오른쪽에 표시하고 커스텀 스타일 적용
|
||||
:global(.enact_ui_Scroller_Scroller_scrollbar) {
|
||||
position: absolute !important;
|
||||
right: 0 !important;
|
||||
top: 0 !important;
|
||||
width: 44px !important;
|
||||
height: 100% !important;
|
||||
background: transparent !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
justify-content: space-between !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
// 스크롤바 썸 (가운데 트랙 부분)
|
||||
:global(.enact_ui_Scroller_Scroller_thumb) {
|
||||
width: 4px !important;
|
||||
background: @scrollbar-color !important;
|
||||
border-radius: 0 !important;
|
||||
position: absolute !important;
|
||||
left: 20px !important; // 44px의 중앙
|
||||
}
|
||||
|
||||
// 스크롤바 트랙 영역
|
||||
:global(.enact_ui_Scroller_Scroller_track) {
|
||||
width: 4px !important;
|
||||
background: @scrollbar-track-color !important;
|
||||
position: absolute !important;
|
||||
left: 20px !important; // 44px의 중앙
|
||||
top: 44px !important; // 상단 버튼 높이만큼
|
||||
bottom: 44px !important; // 하단 버튼 높이만큼
|
||||
}
|
||||
|
||||
// 스크롤바 영역에 상하 버튼 추가 (CSS로 구현)
|
||||
:global(.enact_ui_Scroller_Scroller_scrollbar)::before {
|
||||
content: '';
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
background: #9C9898;
|
||||
box-shadow: 0px 8px 6.6px rgba(156, 152, 152, 0.34);
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
:global(.enact_ui_Scroller_Scroller_scrollbar)::after {
|
||||
content: '';
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
background: #9C9898;
|
||||
box-shadow: 0px 8px 6.6px rgba(156, 152, 152, 0.34);
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 약관 내용
|
||||
.termsContent {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
|
||||
.termsTitle {
|
||||
color: @black;
|
||||
font-size: 26px;
|
||||
font-family: @font-family;
|
||||
font-weight: 700;
|
||||
word-wrap: break-word;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.termsText {
|
||||
color: @black;
|
||||
font-size: 26px;
|
||||
font-family: @font-family;
|
||||
font-weight: 400;
|
||||
word-wrap: break-word;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
// 스크롤바 영역 제거 (TButtonScroller의 기본 스크롤바 사용)
|
||||
// .scrollbarArea 클래스 전체 제거
|
||||
|
||||
// 버튼 영역
|
||||
.buttonArea {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
padding: 0 229px; // 상하 패딩 제거, 좌우만 유지
|
||||
flex-shrink: 0; // 축소되지 않도록
|
||||
}
|
||||
|
||||
// 버튼 컨테이너
|
||||
.buttonContainer {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: @button-gap;
|
||||
}
|
||||
|
||||
// TButton 커스터마이징
|
||||
.agreeButton {
|
||||
width: @button-width;
|
||||
height: @button-height;
|
||||
max-width: 450px;
|
||||
min-width: 150px;
|
||||
border-radius: @border-radius-medium;
|
||||
|
||||
// TButton primary 타입 오버라이드
|
||||
:global(.enact_ui_Button_Button_button) {
|
||||
background: @lg-primary-color !important;
|
||||
box-shadow: 0px 0px 50px @button-shadow;
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: @border-radius-medium;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
color: @white !important;
|
||||
font-size: 30px !important;
|
||||
font-family: @font-family !important;
|
||||
font-weight: 700 !important;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0px 5px 55px @button-shadow;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0px 0px 50px @button-shadow;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 3px solid @white;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.closeButton {
|
||||
width: @button-width;
|
||||
height: @button-height;
|
||||
max-width: 450px;
|
||||
min-width: 150px;
|
||||
border-radius: @border-radius-medium;
|
||||
|
||||
// TButton secondary 타입 오버라이드
|
||||
:global(.enact_ui_Button_Button_button) {
|
||||
background: @button-secondary-color !important;
|
||||
border-radius: @border-radius-medium;
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
color: @button-text-color !important;
|
||||
font-size: 30px !important;
|
||||
font-family: @font-family !important;
|
||||
font-weight: 700 !important;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
background: lighten(@button-secondary-color, 10%) !important;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 3px solid @white;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 반응형 디자인
|
||||
@media screen and (max-width: 1280px) {
|
||||
.popup :global(.enact_ui_Popup_Popup_popup) {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: calc(100vw - 60px);
|
||||
max-height: calc(100vh - 60px);
|
||||
}
|
||||
|
||||
.buttonArea {
|
||||
padding: 0 100px;
|
||||
}
|
||||
|
||||
.header .title,
|
||||
.termsContent .termsTitle,
|
||||
.termsContent .termsText {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.agreeButton,
|
||||
.closeButton {
|
||||
width: 200px;
|
||||
height: 70px;
|
||||
|
||||
:global(.enact_ui_Button_Button_button) {
|
||||
font-size: 26px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 960px) {
|
||||
.popup :global(.enact_ui_Popup_Popup_popup) {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: calc(100vw - 40px);
|
||||
max-height: calc(100vh - 40px);
|
||||
}
|
||||
|
||||
.buttonArea {
|
||||
padding: 0 50px;
|
||||
}
|
||||
|
||||
.header .title,
|
||||
.termsContent .termsTitle,
|
||||
.termsContent .termsText {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.agreeButton,
|
||||
.closeButton {
|
||||
width: 180px;
|
||||
height: 60px;
|
||||
|
||||
:global(.enact_ui_Button_Button_button) {
|
||||
font-size: 22px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.contentArea {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.scrollerContainer {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.scrollbarArea {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// 약관 공통 상수 및 헬퍼 함수 (TermsOfService, TermsOfOptional에서 재사용)
|
||||
|
||||
// 약관 유형 상수 정의
|
||||
export const TERMS_TYPE = {
|
||||
TERMS_CONDITIONS: "TERMS_CONDITIONS",
|
||||
PRIVACY_POLICY: "PRIVACY_POLICY",
|
||||
OPTIONAL_TERMS: "OPTIONAL_TERMS",
|
||||
};
|
||||
|
||||
// 약관 ID 매핑
|
||||
export const TERMS_ID_MAP = {
|
||||
[TERMS_TYPE.TERMS_CONDITIONS]: "TID0000222",
|
||||
[TERMS_TYPE.PRIVACY_POLICY]: "TID0000223",
|
||||
[TERMS_TYPE.OPTIONAL_TERMS]: "TID0000232"
|
||||
};
|
||||
|
||||
|
||||
export const TERMS_TPCD_MAP = {
|
||||
[TERMS_TYPE.TERMS_CONDITIONS]: "MST00402",
|
||||
[TERMS_TYPE.PRIVACY_POLICY]: "MST00401",
|
||||
[TERMS_TYPE.OPTIONAL_TERMS]: "MST00405"
|
||||
};
|
||||
|
||||
export const TERMS_TYPE_LIST = [
|
||||
TERMS_TYPE.TERMS_CONDITIONS,
|
||||
TERMS_TYPE.PRIVACY_POLICY,
|
||||
TERMS_TYPE.OPTIONAL_TERMS
|
||||
];
|
||||
|
||||
// 약관 표시 이름 매핑
|
||||
export const TERMS_DISPLAY_NAMES = {
|
||||
[TERMS_TYPE.TERMS_CONDITIONS]: "Terms and Conditions",
|
||||
[TERMS_TYPE.PRIVACY_POLICY]: "Privacy Policy",
|
||||
[TERMS_TYPE.OPTIONAL_TERMS]: "Optional Terms"
|
||||
};
|
||||
|
||||
// 약관 데이터 파싱 헬퍼 함수
|
||||
export const parseTermsList = (termsData) => {
|
||||
if (!termsData) return [];
|
||||
|
||||
return termsData.map(term => ({
|
||||
...term,
|
||||
displayName: TERMS_DISPLAY_NAMES[term.trmsTpCd] || term.trmsNm
|
||||
}));
|
||||
};
|
||||
|
||||
// 체크박스 초기 상태 생성 헬퍼
|
||||
export const getInitialCheckboxStates = () => ({
|
||||
[TERMS_TYPE.TERMS_CONDITIONS]: false,
|
||||
[TERMS_TYPE.PRIVACY_POLICY]: false,
|
||||
[TERMS_TYPE.OPTIONAL_TERMS]: false
|
||||
});
|
||||
|
||||
// 필수 약관 여부 확인 헬퍼
|
||||
export const isMandatoryTerms = (termsType) => {
|
||||
return (
|
||||
termsType === TERMS_TYPE.TERMS_CONDITIONS ||
|
||||
termsType === TERMS_TYPE.PRIVACY_POLICY
|
||||
);
|
||||
};
|
||||
|
||||
// HTML 태그 제거 헬퍼
|
||||
export const stripHtmlTags = (htmlText) => {
|
||||
if (!htmlText) return "";
|
||||
return htmlText.replace(/(<([^>]+)>)/gi, "");
|
||||
};
|
||||
Reference in New Issue
Block a user