Files
shoptime/com.twin.app.shoptime/src/views/IntroPanel/IntroPanel.new.jsx

835 lines
27 KiB
JavaScript

// src: views/IntroPanel/IntroPanel.new.jsx
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} 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 { fetchCurrentUserHomeTermsPromise } 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 TNewPopUp from "../../components/TPopUp/TNewPopUp";
// import OptionalTermsInfo from "../MyPagePanel/MyPageSub/TermsOfService/OptionalTermsInfo";
import useDebugKey from "../../hooks/useDebugKey";
import useSafeFocusState from "../../hooks/useSafeFocusState";
import useTermsStateMachine from "../../hooks/useTermsStateMachine";
import useRightPanelContent from "../../hooks/useRightPanelContent";
import * as Config from "../../utils/Config";
import { panel_names } from "../../utils/Config";
import { $L } from "../../utils/helperMethods";
import css from "./IntroPanel.new.module.less";
import { types } from "../../actions/actionTypes";
import { focusById } from "../../utils/spotlight-utils";
const Container = SpotlightContainerDecorator(
{ enterTo: "last-focused" },
"div",
);
export default function IntroPanel({
// children,
// isTabActivated,
// handleCancel,
spotlightId,
...rest
}) {
delete rest.isTabActivated;
delete rest.panelInfo;
useDebugKey({});
const dispatch = useDispatch();
// const blurTimeout = useRef(null);
const termsData = useSelector((state) => state.home.termsData);
const termsIdMap = useSelector((state) => state.home.termsIdMap);
const { popupVisible, activePopup, ...popupState } = useSelector(
(state) => state.common.popup,
);
// const eventInfos = useSelector((state) => state.event.eventData);
const regDeviceData = useSelector((state) => state.device.regDeviceData);
// const regDeviceInfoData = useSelector(
// (state) => state.device.regDeviceInfoData
// );
// registerDevice API 호출 중 여부
const [isProcessing, setIsProcessing] = useState(false);
const [showExitMessagePopup, setShowExitMessagePopup] = useState(false);
// race condition 방지를 위한 안전장치
const processingTimeoutRef = useRef(null);
// const [isRequiredFocused, setIsRequiredFocused] = useState(false);
const { focusedItem, setFocusAsync, clearFocusAsync } = useSafeFocusState();
const { state: termsState, updateStateAsync } = useTermsStateMachine();
const {
termsChecked,
privacyChecked,
optionalChecked,
selectAllChecked,
error: termsError, // 훅의 에러 상태를 가져옴
} = termsState;
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" || item.trmsTpCd === "MST00406",
) || []
);
}, [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);
if (process.env.NODE_ENV === "development") {
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);
useEffect(() => {
dispatch({ type: types.REGISTER_DEVICE_RESET });
dispatch(sendLogGNB(Config.LOG_MENU.TERMS_CONDITIONS));
}, [dispatch]);
// 컴포넌트 마운트 시 현재 Redux 상태 로깅
// useEffect(() => {
// console.log('🔍 IntroPanel 마운트 시 Redux 상태:');
// console.log(' - regDeviceData:', regDeviceData);
// console.log(' - regDeviceInfoData:', regDeviceInfoData);
// console.log(' - eventInfos:', eventInfos);
// console.log(' - termsData:', termsData);
// }, []);
// 디버깅용 WebOS 버전 로그
useEffect(() => {
if (process.env.NODE_ENV === "development") {
console.log("🔍 IntroPanel WebOS 버전 정보:");
console.log(" - webOSVersion:", webOSVersion);
console.log(" - shouldShowBenefitsView:", shouldShowBenefitsView);
}
}, [webOSVersion, shouldShowBenefitsView]);
useEffect(() => {
if (showExitMessagePopup) {
const timer = setTimeout(() => {
dispatch(setExitApp());
dispatch(
sendLogTotalRecommend({
contextName: Config.LOG_CONTEXT_NAME.SHOPTIME,
messageId: Config.LOG_MESSAGE_ID.VIEW_CHANGE,
visible: false,
}),
);
}, 3000);
return () => clearTimeout(timer);
}
}, [showExitMessagePopup, dispatch]);
// 컴포넌트 마운트 후 0.5초 뒤에 selectAllCheckbox으로 강제 포커스
useEffect(() => {
const focusTimer = setTimeout(() => {
focusById("selectAllCheckbox");
}, 500);
return () => {
clearTimeout(focusTimer);
};
}, []);
// [추가] useTermsStateMachine의 에러 상태를 감지하여 팝업으로 표시
useEffect(() => {
if (termsError) {
dispatch(setShowPopup(Config.ACTIVE_POPUP.alertPopup, {
title: $L("Error"),
text: termsError.message,
button1Text: $L("OK")
}));
}
}, [termsError, dispatch]);
// 약관 팝업 동의여부에 따른 이벤트 핸들러
const handleTermsAgree = useCallback(() => {
if (!currentTerms) {
return;
}
const termType = currentTerms.trmsTpCd;
if (termType === "MST00402") {
updateStateAsync({ termsChecked: true });
} else if (termType === "MST00401") {
updateStateAsync({ privacyChecked: true });
} else if (termType === "MST00405") {
// Optional Terms
updateStateAsync({ optionalChecked: true });
}
// 팝업 닫기
dispatch(setHidePopup());
}, [currentTerms, dispatch, updateStateAsync]);
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(() => {
console.log("[IntroPanel] handleAgree isProcessing=", isProcessing);
if (isProcessing){
return;
}
// 필수 약관이 체크되어 있는지 확인
// if (!termsChecked || !privacyChecked) {
// // 필수 약관이 체크되지 않았을 때 알림
// // window.alert($L("Please agree to Terms & Conditions and Privacy Policy."));
// dispatch(setShowPopup(Config.ACTIVE_POPUP.alertPopup, {
// title: $L("Required Terms"),
// text: $L("Please agree to Terms & Conditions and Privacy Policy."),
// button1Text: $L("OK")
// }));
// return;
// }
setIsProcessing(true);
// 안전장치: 30초 후 자동으로 isProcessing 해제
processingTimeoutRef.current = setTimeout(() => {
console.warn("[IntroPanel] handleAgree 타임아웃 - isProcessing 강제 해제");
setIsProcessing(false);
}, 30000);
// 약관 동의 처리 시작 시 로딩 상태로 설정
dispatch({ type: types.GET_TERMS_AGREE_YN_START });
// Redux에서 가져온 termsIdMap을 사용하여 동적으로 약관 ID 매핑
const agreeTerms = [];
if (termsChecked && termsIdMap["MST00402"]) {
agreeTerms.push(termsIdMap["MST00402"]); // 이용약관
}
if (privacyChecked && termsIdMap["MST00401"]) {
agreeTerms.push(termsIdMap["MST00401"]); // 개인정보처리방침
}
if (optionalChecked && termsIdMap["MST00405"]) {
agreeTerms.push(termsIdMap["MST00405"]); // 선택약관
}
if (process.env.NODE_ENV === "development") {
console.log("[IntroPanel] 현재 termsIdMap:", termsIdMap);
console.log("[IntroPanel] 최종 전송될 agreeTerms:", agreeTerms);
}
dispatch(
registerDevice(
{ agreeTerms: agreeTerms },
(newRegDeviceData) => {
if (newRegDeviceData && newRegDeviceData.retCode === 0) {
dispatch(
getWelcomeEventInfo((eventInfos) => {
if (
eventInfos &&
Object.keys(eventInfos.data).length > 0 &&
webOSVersion
) {
let displayWelcomeEventPanel = false;
if (
eventInfos.data?.welcomeEventFlag === "Y" ||
(eventInfos.data?.welcomeBillCpnEventFlag === "Y" &&
Number(webOSVersion) >= 6)
) {
displayWelcomeEventPanel = true;
}
dispatch(
sendLogTerms({ logTpNo: Config.LOG_TP_NO.TERMS.AGREE }),
);
if (displayWelcomeEventPanel) {
dispatch(
pushPanel({
name: panel_names.WELCOME_EVENT_PANEL,
panelInfo: { eventInfos: eventInfos.data },
}),
);
}
}
dispatch(popPanel(panel_names.INTRO_PANEL));
setIsProcessing(false);
clearTimeout(processingTimeoutRef.current); // 타임아웃 정리
}),
);
} else {
dispatch(
setShowPopup(Config.ACTIVE_POPUP.alertPopup, {
title: $L("Error"),
text: $L("Device registration failed. Please try again."),
button1Text: $L("OK"),
}),
);
setIsProcessing(false);
clearTimeout(processingTimeoutRef.current); // 타임아웃 정리
}
},
() => {
dispatch(
setShowPopup(Config.ACTIVE_POPUP.alertPopup, {
title: $L("Error"),
text: $L("Device registration failed. Please try again."),
button1Text: $L("OK"),
}),
);
setIsProcessing(false);
clearTimeout(processingTimeoutRef.current); // 타임아웃 정리
},
),
);
}, [
termsChecked,
privacyChecked,
optionalChecked,
dispatch,
isProcessing,
webOSVersion,
termsIdMap,
]);
// 실패 감지를 위한 useEffect 추가
useEffect(() => {
// isProcessing이 true일 때만 실패 체크 (= handleAgree 클릭 후에만)
if (isProcessing && regDeviceData && regDeviceData.retCode !== 0) {
if (process.env.NODE_ENV === "development") {
console.error(
`[IntroPanel] registerDevice 실패: isProcessing=${isProcessing}, retCode=${regDeviceData.retCode}`,
regDeviceData,
);
}
dispatch(
setShowPopup(Config.ACTIVE_POPUP.alertPopup, {
title: $L("Error"),
text: $L("Device registration failed. Please try again."),
button1Text: $L("OK"),
}),
);
setIsProcessing(false);
clearTimeout(processingTimeoutRef.current); // 타임아웃 정리
}
}, [regDeviceData, dispatch, isProcessing]); // isProcessing 의존성 추가
// 컴포넌트 언마운트 시 타임아웃 정리
useEffect(() => {
return () => {
if (processingTimeoutRef.current) {
clearTimeout(processingTimeoutRef.current);
}
};
}, []);
const handleDisagree = useCallback(() => {
dispatch(setShowPopup(Config.ACTIVE_POPUP.exitPopup));
dispatch(sendLogTerms({ logTpNo: Config.LOG_TP_NO.TERMS.DO_NOT_AGREE }));
}, [dispatch]);
const onExit = useCallback(() => {
dispatch(setHidePopup());
setShowExitMessagePopup(true);
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 handleFocus = useCallback(
(item) => {
setFocusAsync(item);
},
[setFocusAsync]
);
const handleBlur = useCallback(() => {
clearFocusAsync(0);
}, [clearFocusAsync]);
// 체크박스 핸들러들
const handleTermsToggle = useCallback(
async ({ selected }) => {
try {
const newState = await updateStateAsync({ termsChecked: selected });
if (newState.termsChecked && newState.privacyChecked && newState.optionalChecked) {
setTimeout(() => Spotlight.focus("agreeButton"), 100);
}
} catch (error) {
if (process.env.NODE_ENV === "development") {
console.error("Toggle failed:", error);
}
}
},
[updateStateAsync]
);
const handlePrivacyToggle = useCallback(
async ({ selected }) => {
try {
const newState = await updateStateAsync({ privacyChecked: selected });
if (newState.termsChecked && newState.privacyChecked && newState.optionalChecked) {
setTimeout(() => Spotlight.focus("agreeButton"), 100);
}
} catch (error) {
if (process.env.NODE_ENV === "development") {
console.error("Toggle failed:", error);
}
}
},
[updateStateAsync]
);
const handleOptionalToggle = useCallback(
async ({ selected }) => {
try {
const newState = await updateStateAsync({ optionalChecked: selected });
if (newState.termsChecked && newState.privacyChecked && newState.optionalChecked) {
setTimeout(() => Spotlight.focus("agreeButton"), 100);
}
} catch (error) {
if (process.env.NODE_ENV === "development") {
console.error("Toggle failed:", error);
}
}
},
[updateStateAsync]
);
const handleSelectAllToggle = useCallback(
async ({ selected }) => {
try {
const newState = await updateStateAsync({ selectAllChecked: selected });
if (newState.selectAllChecked) {
setTimeout(() => Spotlight.focus("agreeButton"), 100);
}
} catch (error) {
if (process.env.NODE_ENV === "development") {
console.error("Toggle failed:", error);
}
}
},
[updateStateAsync]
);
const handleTermsClickMST00402 = useCallback(
() => handleTermsClick("MST00402"),
[handleTermsClick],
);
const handleTermsClickMST00401 = useCallback(
() => handleTermsClick("MST00401"),
[handleTermsClick],
);
const handleOptionalTermsClickMST00405 = useCallback(
() => handleOptionalTermsClick("MST00405"),
[handleOptionalTermsClick],
);
const handleFocusTermsCheckbox = useCallback(
() => handleFocus("termsCheckbox"),
[handleFocus],
);
const handleFocusTermsButton = useCallback(
() => handleFocus("termsButton"),
[handleFocus],
);
const handleFocusPrivacyCheckbox = useCallback(
() => handleFocus("privacyCheckbox"),
[handleFocus],
);
const handleFocusPrivacyButton = useCallback(
() => handleFocus("privacyButton"),
[handleFocus],
);
const handleFocusOptionalCheckbox = useCallback(
() => handleFocus("optionalCheckbox"),
[handleFocus],
);
const handleFocusOptionalButton = useCallback(
() => handleFocus("optionalButton"),
[handleFocus],
);
const handleFocusSelectAllCheckbox = useCallback(
() => handleFocus("selectAllCheckbox"),
[handleFocus],
);
const handleFocusAgreeButton = useCallback(
() => handleFocus("agreeButton"),
[handleFocus],
);
const handleFocusDisagreeButton = useCallback(
() => handleFocus("disagreeButton"),
[handleFocus],
);
const rightPanelContent = useRightPanelContent(
focusedItem,
termsChecked,
privacyChecked,
shouldShowBenefitsView
);
useEffect(() => {
Spotlight.focus();
}, [popupVisible]);
// useEffect(() => {
// if (regDeviceInfoData && regDeviceInfoData.retCode === 0) {
// dispatch(setHidePopup());
// dispatch(popPanel(panel_names.INTRO_PANEL));
// }
// }, [dispatch, regDeviceInfoData]);
const description = $L(
"Check out more LIVE SHOWS now and enjoy shopping at Shop Time's special price on your TV by agreeing to the LG TV Shopping Terms and Conditions.",
);
const title = "welcome to shoptime!";
delete rest.isOnTop;
// [추가] 약관 종류에 따라 팝업 제목을 반환하는 헬퍼 함수
const getTermsPopupTitle = (terms) => {
if (!terms) return "";
switch (terms.trmsTpCd) {
case "MST00401":
return $L("Privacy Policy");
case "MST00402":
return $L("Terms & Conditions");
case "MST00405":
return $L("Optional Terms");
default:
return "";
}
};
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.customeCheckbox}
selected={termsChecked}
onToggle={handleTermsToggle}
onFocus={handleFocusTermsCheckbox}
onBlur={handleBlur}
spotlightId="termsCheckbox"
ariaLabel={$L("Terms & Conditions checkbox")}
/>
<TButton
className={css.termsButton}
onClick={handleTermsClickMST00402}
onFocus={handleFocusTermsButton}
onBlur={handleBlur}
spotlightId="termsButton"
type={TYPES.terms}
ariaLabel={$L("View Terms & Conditions")}
>
<span className={`${css.termsText} ${css.required}`}>
{$L("Terms & Conditions")}
</span>
</TButton>
</div>
{/* Privacy Policy */}
<div className={css.termsItem}>
<TCheckBoxSquare
className={css.customeCheckbox}
selected={privacyChecked}
onToggle={handlePrivacyToggle}
onFocus={handleFocusPrivacyCheckbox}
onBlur={handleBlur}
spotlightId="privacyCheckbox"
ariaLabel={$L("Privacy Policy checkbox")}
/>
<TButton
className={css.termsButton}
onClick={handleTermsClickMST00401}
onFocus={handleFocusPrivacyButton}
onBlur={handleBlur}
spotlightId="privacyButton"
type={TYPES.terms}
ariaLabel={$L("View Privacy Policy")}
>
<span className={`${css.termsText} ${css.required}`}>
{$L("Privacy Policy")}
</span>
</TButton>
</div>
{/* Optional Terms */}
<div className={css.termsItem}>
<TCheckBoxSquare
className={css.customeCheckbox}
selected={optionalChecked}
onToggle={handleOptionalToggle}
onFocus={handleFocusOptionalCheckbox}
onBlur={handleBlur}
spotlightId="optionalCheckbox"
ariaLabel={$L("Optional Terms checkbox")}
/>
<TButton
className={css.termsButton}
onClick={handleOptionalTermsClickMST00405}
onFocus={handleFocusOptionalButton}
onBlur={handleBlur}
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}>{rightPanelContent}</div>
</div>
{/* 세 번째 영역: Select All */}
<div className={css.selectAllSection}>
<TCheckBoxSquare
className={css.selectAllCheckbox}
selected={selectAllChecked}
onToggle={handleSelectAllToggle}
onFocus={handleFocusSelectAllCheckbox}
onBlur={handleBlur}
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}
onFocus={handleFocusAgreeButton}
onBlur={handleBlur}
spotlightId="agreeButton"
type={TYPES.agree}
ariaLabel={$L("Agree to terms")}
disabled={!termsChecked || !privacyChecked}
spotlightDisabled={!termsChecked || !privacyChecked}
>
{$L("Agree")}
</TButton>
<TButton
className={css.disagreeButton}
onClick={handleDisagree}
onFocus={handleFocusDisagreeButton}
onBlur={handleBlur}
spotlightId="disagreeButton"
type={TYPES.agree}
ariaLabel={$L("Do not agree to terms")}
>
{$L("Do Not Agree")}
</TButton>
</div>
</Container>
</TPanel>
{/* 약관 보기 팝업 */}
{activePopup === Config.ACTIVE_POPUP.termsPopup && (
<>
{/*
<TPopUp
kind="introTermsPopup"
contentType={CONTENT_TYPES.TERMS}
open={popupVisible}
onClose={onClose}
onTermsAgree={handleTermsAgree}
termsData={currentTerms}
hasButton
button1Text={$L("Agree")}
button2Text={$L("Close")}
spotlightId="tPopupBtn1"
/>
*/}
{/* TermsPopup 호출은 완전히 삭제 */}
{/* TNewPopUp을 사용한 새로운 팝업 구현 */}
<TNewPopUp
open={popupVisible}
kind="figmaTermsPopup"
title={getTermsPopupTitle(currentTerms)}
text={currentTerms?.trmsCntt || ""}
onClose={onClose} // Close 버튼 핸들러 연결
/>
</>
)}
{/* 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={popupState.title || ""}
text={popupState.text || ""}
/>
)}
{/* Final Exit Message Popup */}
{showExitMessagePopup && (
<TPopUp
kind="textPopup"
open={showExitMessagePopup}
hasText
title={$L("Exit Shop Time")}
text={$L(
"Thank you for using the Shop Time, and we hope to see you again. The app will close in 3 seconds.",
)}
/>
)}
</Region>
);
}