Files
shoptime/com.twin.app.shoptime/src/components/MobileSend/MobileSendPopUp.jsx
junghoon86.park f514e2468c [nbcu 상품에서의 모바일 샌드팝업 수정]
- 기존 처리 했던 useselector제거하고 patnrId 로 변경처리.
 - nbcu상품에서는 상품 id가 노출되지않고 patncNm가 노출되도록 수정.
2025-12-15 10:00:27 +09:00

743 lines
22 KiB
JavaScript

import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import classNames from 'classnames';
import {
AsYouTypeFormatter,
PhoneNumberFormat,
PhoneNumberUtil,
} from 'google-libphonenumber';
import { useDispatch, useSelector } from 'react-redux';
import { off, on } from '@enact/core/dispatcher';
import spotlight, { Spotlight } from '@enact/spotlight';
import { SpotlightContainerDecorator } from '@enact/spotlight/SpotlightContainerDecorator';
import { Spottable } from '@enact/spotlight/Spottable';
import defaultImage from '../../../assets/images/img-thumb-empty-144@3x.png';
import { types } from '../../actions/actionTypes';
import { clearSMS, sendSms } from '../../actions/appDataActions';
import {
changeLocalSettings,
setHidePopup,
setShowPopup,
} from '../../actions/commonActions';
import {
clearRegisterDeviceInfo,
getDeviceAdditionInfo,
registerDeviceInfo,
} from '../../actions/deviceActions';
import {
clearCurationCoupon,
setEventIssueReq,
} from '../../actions/eventActions';
import {
sendLogShopByMobile,
sendLogTotalRecommend,
} from '../../actions/logActions';
import {
ACTIVE_POPUP,
LOG_CONTEXT_NAME,
LOG_MESSAGE_ID,
LOG_TP_NO,
} from '../../utils/Config';
import {
$L,
decryptPhoneNumber,
encryptPhoneNumber,
formatLocalDateTime,
} from '../../utils/helperMethods';
import CustomImage from '../CustomImage/CustomImage';
import TButton from '../TButton/TButton';
import TPopUp from '../TPopUp/TPopUp';
import HistoryPhoneNumber from './HistoryPhoneNumber/HistoryPhoneNumber';
import css from './MobileSendPopUp.module.less';
import PhoneInputSection from './PhoneInputSection';
import SMSNumKeyPad from './SMSNumKeyPad';
const SECRET_KEY = 'fy7BTKuM9eeTQqEC9sF3Iw5qG43Aaip';
const Container = SpotlightContainerDecorator(
{ enterTo: 'last-focused' },
'div'
);
const InputContainer = SpotlightContainerDecorator(
{ enterTo: 'last-focused' },
'div'
);
const SpottableComponent = Spottable('div');
export default function MobileSendPopUp({
open,
onClose,
title,
subTitle,
productImg,
productPrice,
brandLogo,
productId,
smsTpCd,
smsText,
prdtId,
curationId,
patnrId,
evntId,
evntTpCd,
isCurationEvnt,
hotelId,
hotelNm,
hotelDtlUrl,
shopByMobileLogRef,
spotlightId,
patncNm,
}) {
const dispatch = useDispatch();
const deviceInfo = useSelector((state) => state.device.deviceInfo);
const { httpHeader } = useSelector((state) => state.common);
const regDeviceInfoRetCode = useSelector(
(state) => state.device?.regDeviceInfoData?.retCode
);
const smsRetCodeResData = useSelector(
(state) => state.appData.sendSms?.retCode
);
const { phoneNumberList } = useSelector(
(state) => state.localSettings.phoneNumbers
);
const curationCouponSuccess = useSelector(
(state) => state.event.curationCouponSuccess
);
const popupVisible = useSelector((state) => state.common.popup.popupVisible);
const nowMenu = useSelector((state) => state.common.menu.nowMenu);
const entryMenu = useSelector((state) => state.common.menu.entryMenu);
const [inputDisabled, setInputDisabled] = useState(true);
const [mobileNumber, setMobileNumber] = useState('');
const [recentSentNumber, setRecentSentNumber] = useState([]);
const [keyPadOff, setKeyPadOff] = useState(false);
const [smsRetCode, setSmsRetCode] = useState(undefined);
const agreeBtnClickedRef = useRef(false);
const deviceCountryCode = httpHeader['X-Device-Country'];
const mobileSendPopUpSpotlightId = useMemo(() => {
return !keyPadOff && recentSentNumber.length <= 0
? 'keypad-number-1'
: 'agreeAndSend';
}, [keyPadOff, recentSentNumber]);
const getMaxNum = useCallback((_deviceCountryCode) => {
if (_deviceCountryCode === 'DE' || _deviceCountryCode === 'GB') {
return 11;
} else if (_deviceCountryCode === 'KR') {
return 12;
} else return 10;
}, []);
const MSG_SUCCESS_SENT = $L('Text Send to') + ' ' + mobileNumber;
const MSG_SEND_LINK = $L('Send a purchase link for this item via SMS');
const handleClickSelect = (_phoneNumber) => {
setKeyPadOff((state) => !state);
setMobileNumber(_phoneNumber);
setTimeout(() => {
Spotlight.focus('keypad-number-1');
}, 0);
};
const handleInputClick = () => {
setKeyPadOff(false);
if (recentSentNumber && recentSentNumber.length > 0 && keyPadOff) {
setMobileNumber(recentSentNumber[0]);
}
};
const getRawPhoneNumber = useCallback(
(key) => {
let rawPhoneNumber = `${mobileNumber}${key}`.replace(/\D/g, '');
if (rawPhoneNumber.length === getMaxNum(deviceCountryCode)) {
Spotlight.focus('agreeAndSend');
}
// 테스트용: 12자리까지 허용
if (rawPhoneNumber.length > 12) {
return;
}
const phoneUtil = PhoneNumberUtil.getInstance();
const asYouTypeFormatter = new AsYouTypeFormatter(deviceCountryCode);
try {
let numberProto = phoneUtil.parse(rawPhoneNumber, deviceCountryCode);
if (
phoneUtil.isValidNumber(numberProto) &&
phoneUtil.isValidNumberForRegion(numberProto, deviceCountryCode)
) {
rawPhoneNumber = phoneUtil.format(
numberProto,
PhoneNumberFormat.NATIONAL
);
if (deviceCountryCode === 'RU' && rawPhoneNumber.startsWith('8')) {
rawPhoneNumber = rawPhoneNumber.substring(1);
}
} else {
let formattedNumber = '';
for (let i = 0; i < rawPhoneNumber.length; i++) {
formattedNumber = asYouTypeFormatter.inputDigit(
Number(rawPhoneNumber[i])
);
}
rawPhoneNumber = formattedNumber;
}
} catch (e) {
rawPhoneNumber = `${mobileNumber}${key}`;
} finally {
setMobileNumber(rawPhoneNumber);
}
},
[mobileNumber, deviceCountryCode]
);
const getBackspaceRawNumber = useCallback(() => {
let rawPhoneNumber = mobileNumber.replace(/\D/g, '').slice(0, -1);
const phoneUtil = PhoneNumberUtil.getInstance();
const asYouTypeFormatter = new AsYouTypeFormatter(deviceCountryCode);
try {
let numberProto = phoneUtil.parse(rawPhoneNumber, deviceCountryCode);
if (
phoneUtil.isValidNumber(numberProto) &&
phoneUtil.isValidNumberForRegion(numberProto, deviceCountryCode)
) {
rawPhoneNumber = phoneUtil.format(
numberProto,
PhoneNumberFormat.NATIONAL
);
} else {
let formattedNumber = '';
for (let i = 0; i < rawPhoneNumber.length; i++) {
formattedNumber = asYouTypeFormatter.inputDigit(
Number(rawPhoneNumber[i])
);
}
rawPhoneNumber = formattedNumber;
}
} catch (e) {
rawPhoneNumber = mobileNumber.slice(0, -1);
} finally {
setMobileNumber(rawPhoneNumber);
}
}, [mobileNumber, deviceCountryCode]);
const handleKeydown = useCallback(
(ev) => {
if (ev && ev.key >= 0 && ev.key <= 9) {
getRawPhoneNumber(ev.key);
} else if (ev.key === 'Backspace') {
getBackspaceRawNumber();
}
},
[mobileNumber]
);
useEffect(() => {
on('keydown', handleKeydown);
return () => {
off('keydown', handleKeydown);
};
}, [handleKeydown]);
useEffect(() => {
if (!deviceInfo) {
dispatch(getDeviceAdditionInfo());
}
}, [deviceInfo, dispatch]);
useEffect(() => {
const timer = setTimeout(() =>
setInputDisabled(mobileSendPopUpSpotlightId === 'keypad-number-1')
);
return () => clearTimeout(timer);
}, [mobileSendPopUpSpotlightId]);
useEffect(() => {
if (!agreeBtnClickedRef.current && recentSentNumber) {
if (recentSentNumber.length > 0) {
setKeyPadOff(true);
setMobileNumber(recentSentNumber[0]);
} else {
setKeyPadOff(false);
setMobileNumber('');
}
}
}, [recentSentNumber]);
const numKeypadClicked = useCallback(
(key) => {
if (key === 'clear') {
setMobileNumber('');
} else if (key == 'backspace') {
getBackspaceRawNumber();
} else {
getRawPhoneNumber(key);
}
},
[deviceCountryCode, mobileNumber]
);
const handleDelete = (selectedIndex) => {
if (recentSentNumber) {
const updateItems = recentSentNumber.filter(
(_, index) => index !== selectedIndex
);
setRecentSentNumber(updateItems);
const encryptedNumbers = updateItems.map(encryptPhoneNumber);
dispatch(
changeLocalSettings({
phoneNumbers: {
phoneNumberList: encryptedNumbers,
},
})
);
}
};
const handleAgreeSendClick = useCallback(() => {
let naturalNumber = mobileNumber.replace(/\D/g, '');
// 테스트용: 길이 체크를 더 유연하게 (10자리 또는 11자리 허용)
if (
!mobileNumber ||
naturalNumber.length < 10 ||
naturalNumber.length > 12
) {
setSmsRetCode(907);
return;
}
if (deviceCountryCode === 'KR') {
naturalNumber = '82' + naturalNumber;
}
if (recentSentNumber && recentSentNumber.length > 0) {
const updatedNumbers = [...recentSentNumber];
const existingNumberIndex = updatedNumbers.findIndex(
(existingNum) => existingNum === mobileNumber
);
if (existingNumberIndex !== -1) {
updatedNumbers.splice(existingNumberIndex, 1);
}
if (updatedNumbers.length >= 5) {
updatedNumbers.pop();
}
updatedNumbers.unshift(mobileNumber);
agreeBtnClickedRef.current = true;
setRecentSentNumber(updatedNumbers);
const encryptedNumbers = updatedNumbers.map(encryptPhoneNumber);
dispatch(
changeLocalSettings({
phoneNumbers: {
phoneNumberList: encryptedNumbers,
},
})
);
} else {
setRecentSentNumber([mobileNumber]);
dispatch(
changeLocalSettings({
phoneNumbers: {
phoneNumberList: [encryptPhoneNumber(mobileNumber)],
},
})
);
}
if (isCurationEvnt) {
dispatch(
setEventIssueReq({
evntTpCd,
evntId,
mbphNo: naturalNumber,
cntryCd: deviceCountryCode,
})
);
return;
}
if (deviceInfo && smsTpCd) {
let params = {
dvcIndex: deviceInfo.dvcIndex,
mbphNo: naturalNumber,
smsTpCd,
patnrId,
evntId,
curationId,
smsText,
prdtId,
entryMenu: nowMenu,
};
// 호텔일 경우 날려야 하는 경우
if (smsTpCd === 'APP00205') {
params = { ...params, hotelId, hotelNm, hotelDtlUrl, curationId };
}
if (smsTpCd === 'APP00204') {
params = { ...params, curationId };
}
dispatch(sendSms(params));
}
// EVT00101 & APP00207(welcome) EVT00103 & APP00209 (welcome+Prizes) : smsTpCd 값을 받지 않음
if (evntTpCd === 'EVT00101' || evntTpCd === 'EVT00103') {
dispatch(
registerDeviceInfo({
evntTpCd,
evntId,
evntApplcnFlag: 'Y',
entryMenu: 'TermsPop',
mbphNo: naturalNumber,
})
);
}
}, [
dispatch,
mobileNumber,
smsTpCd,
evntTpCd,
deviceInfo,
deviceCountryCode,
nowMenu,
]);
const _onClose = useCallback(
(e) => {
if (e.target) {
dispatch(clearSMS());
dispatch(clearCurationCoupon());
setSmsRetCode(undefined);
}
onClose();
dispatch(setShowPopup(ACTIVE_POPUP.smsPopup));
setTimeout(() => Spotlight.focus('agreeAndSend'));
},
[dispatch, smsRetCode]
);
useEffect(() => {
setSmsRetCode(smsRetCodeResData);
let timer;
if (
smsRetCodeResData === 0 ||
regDeviceInfoRetCode === 0 ||
curationCouponSuccess === 0
) {
const logParams = {
status: 'send',
nowMenu: nowMenu,
partner: patncNm ?? shopByMobileLogRef?.current?.patncNm,
productId: prdtId ?? shopByMobileLogRef?.current?.prdtId,
productTitle: title ?? shopByMobileLogRef?.current?.prdtNm,
brand: shopByMobileLogRef?.current?.brndNm,
contextName: LOG_CONTEXT_NAME.SHOPBYMOBILE,
messageId: LOG_MESSAGE_ID.SMB,
};
dispatch(sendLogTotalRecommend(logParams));
if (shopByMobileLogRef) {
let params = {
...shopByMobileLogRef.current,
locDt: formatLocalDateTime(new Date()),
logTpNo: LOG_TP_NO.SHOP_BY_MOBILE.AGREE_AND_SEND,
mbphNoFlag: 'Y',
trmsAgrFlag: 'Y',
};
dispatch(sendLogShopByMobile(params));
shopByMobileLogRef.current = null;
}
if (spotlightId) Spotlight.focus(spotlightId);
else Spotlight.focus();
return () => clearTimeout(timer);
}
}, [
shopByMobileLogRef,
smsRetCodeResData,
regDeviceInfoRetCode,
curationCouponSuccess,
spotlightId,
]);
useEffect(() => {
// retCode initialize
return () => {
dispatch(clearSMS());
dispatch(clearRegisterDeviceInfo());
dispatch(clearCurationCoupon());
dispatch(setHidePopup());
};
}, [dispatch]);
const getSmsErrorMsg = useMemo(() => {
const SMS_ERROR_502 = $L('The event information has not been registered');
const SMS_ERROR_903 = $L('You have exceeded the daily text limit.');
const SMS_ERROR_904 = $L(
'You have exceeded the text limit for this product.'
);
const SMS_ERROR_905 = $L(
'This number is currently blocked. To receive a message, please send UNSTOP to the number below. 07860 064195'
);
const SMS_ERROR_906 = $L('Sorry. This item is sold out.');
const SMS_ERROR_600 = $L('This device had received first time coupon.');
const SMS_ERROR_601 = $L('There is no coupon.');
const SMS_ERROR_900 = $L('Failed to send text to {mobileNumber}').replace(
'{mobileNumber}',
mobileNumber
);
const SMS_ERROR_907 = $L(
'Only {length} digits is permitted. Please check again'
).replace('{length}', getMaxNum(deviceCountryCode));
switch (smsRetCode) {
case 502:
return SMS_ERROR_502;
case 600:
return SMS_ERROR_600;
case 601:
return SMS_ERROR_601;
case 900:
return SMS_ERROR_900;
case 903:
return SMS_ERROR_903;
case 904:
return SMS_ERROR_904;
case 905:
return SMS_ERROR_905;
case 906:
return SMS_ERROR_906;
case 907:
return smsRetCodeResData === 907 ? SMS_ERROR_900 : SMS_ERROR_907;
default:
return SMS_ERROR_900;
}
}, [smsRetCode, mobileNumber, smsRetCodeResData]);
const getEvntErrorMsg = useMemo(() => {
if (curationCouponSuccess === 600) {
return $L('This device had received first time coupon.');
} else if (curationCouponSuccess === 601) {
return $L('There is no coupon.');
} else {
return $L('Failed to sent text to {mobileNumber}').replace(
'{mobileNumber}',
mobileNumber
);
}
}, [curationCouponSuccess, mobileNumber]);
const retCodeError =
(smsRetCode !== undefined && smsRetCode !== 0) ||
(curationCouponSuccess !== undefined && curationCouponSuccess !== 0);
useEffect(() => {
if (phoneNumberList) {
const decryptedNumbers = phoneNumberList.map(decryptPhoneNumber);
setRecentSentNumber(decryptedNumbers);
}
}, [phoneNumberList]);
return (
<>
{smsRetCode === undefined &&
open &&
regDeviceInfoRetCode === undefined &&
curationCouponSuccess === undefined && (
<TPopUp
kind={'mobileSendPopup'}
className={css.container}
open={open}
onClose={onClose}
hasText
spotlightId={mobileSendPopUpSpotlightId}
>
<div className={css.titleHead}>
<div className={css.titleHead__text}>
{title || MSG_SEND_LINK}
</div>
</div>
<div className={css.container__header}>
{productImg && (
<p className={css.container__header__productImg}>
<CustomImage
className={css.container__header__productImg__img}
src={productImg}
fallbackSrc={defaultImage}
/>
</p>
)}
<div
className={classNames(
css.container__header__textBox,
!productImg && css.container__header__textBoxOnly
)}
>
<div className={css.headerTopRow}>
{brandLogo && (
<img
className={classNames(
css.headerTopRow__brandLogo,
patnrId === '1' && css.headerTopRow__brandLogo__qvc
)}
src={brandLogo}
alt="Brand"
/>
)}
{productId && patnrId !== '21' && (
<div className={css.headerTopRow__productId}>
ID: {productId}
</div>
)}
{patnrId === '21' && (
<div className={css.headerTopRow__productId}>{patncNm}</div>
)}
</div>
{subTitle && (
<div
className={classNames(
css.container__header__subTitle,
!productImg && css.container__header__subTitleOnly
)}
>
{subTitle}
</div>
)}
{productPrice && (
<div className={css.container__header__price}>
{productPrice}
</div>
)}
</div>
</div>
<div className={css.container__smsArea}>
{/* 🔄 [BACKUP] 기존 전화번호 입력 영역 - 원상복구용 */}
<div className={css.container__smsArea__smsContainer}>
<InputContainer className={css.smsNumLayer}>
<SpottableComponent
className={css.inputNum}
onClick={handleInputClick}
spotlightDisabled={inputDisabled}
>
{deviceCountryCode && deviceCountryCode === 'RU' && (
<span className={css.rucInput}>{'+7 '}</span>
)}
<span>{mobileNumber}</span>
</SpottableComponent>
</InputContainer>
<Container className={css.flex}>
{keyPadOff && recentSentNumber.length > 0 ? (
<HistoryPhoneNumber
handleClickSelect={handleClickSelect}
handleDelete={handleDelete}
recentSentNumber={recentSentNumber}
/>
) : (
<SMSNumKeyPad onKeyDown={numKeypadClicked} />
)}
</Container>
</div>
<div className={css.instruction}>
{deviceCountryCode && (
<span
dangerouslySetInnerHTML={{
__html: `${$L(
'By clicking Agree and Send button, I agree that LGE may collect and store my cell phone number to send text messages as I requested, for data analysis and for feature-enhancement purposes. By entering my cell phone number, I agree to receive messages from LGE with information on how to purchase the product I selected. Message and data rates may apply.'
)}`,
}}
className={deviceCountryCode === 'RU' && css.instructionRu}
/>
)}
</div>
{/* 🆕 [NEW] PhoneInputSection + instruction 좌우 배치 */}
{/* <div className={css.container__smsArea__smsContainer}>
<PhoneInputSection
mobileNumber={mobileNumber}
deviceCountryCode={deviceCountryCode}
onInputClick={handleInputClick}
onKeypadClick={numKeypadClicked}
spotlightId={mobileSendPopUpSpotlightId}
inputDisabled={inputDisabled}
/>
</div>
<div className={css.container__smsArea__instruction}>
{deviceCountryCode && (
<div className={deviceCountryCode === "RU" ? css.instructionRu : ""}>
<div>
{$L("By clicking Agree and Send button, I agree that LGE may collect and store my cell phone number to send text messages as I requested, for data analysis and for feature-enhancement purposes.")}
</div>
<div style={{ height: "16px" }}></div>
<div>
{$L("By entering my cell phone number, I agree to receive messages from LGE with information on how to purchase the product I selected. Message and data rates may apply.")}
</div>
</div>
)}
</div> */}
</div>
<Container className={css.container__btnContainer}>
<TButton
onClick={handleAgreeSendClick}
spotlightId={'agreeAndSend'}
>
{$L('Agree and Send')}
</TButton>
<TButton onClick={onClose}>{$L('Cancel')}</TButton>
</Container>
</TPopUp>
)}
{retCodeError && (
<TPopUp
kind="textPopup"
open={popupVisible}
hasText
text={smsTpCd ? getSmsErrorMsg : getEvntErrorMsg}
onClick={_onClose}
hasButton
button1Text={$L('OK')}
/>
)}
{(smsRetCode === 0 ||
regDeviceInfoRetCode === 0 ||
regDeviceInfoRetCode === 0) && (
<TPopUp
kind="textPopup"
open={popupVisible}
hasText
text={MSG_SUCCESS_SENT}
onClick={onClose}
hasButton
button1Text={$L('OK')}
/>
)}
</>
);
}