- 하드코딩된 한국어 텍스트를 번역 키로 변경 - 이용약관 링크를 별도 행으로 분리 - 로그아웃 확인 Dialog 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
465 lines
17 KiB
TypeScript
465 lines
17 KiB
TypeScript
import { useAppAlarmConsentMutation } from '@/entities/alarm/api/use-app-alarm-consent-mutation';
|
|
import { useAppAlarmFindMutation } from '@/entities/alarm/api/use-app-alarm-find-mutation';
|
|
import { AppAlarmConsentParams, AppAlarmConsentResponse, AppAlarmFindParams, AppAlarmFindResponse, MERCHANT_ADMIN_APP } from '@/entities/alarm/model/types';
|
|
import { LoginTypeBottomSheet } from '@/entities/alarm/ui/login-type-bottom-sheet';
|
|
import { ServiceLanguageBottomSheet } from '@/entities/alarm/ui/service-language-bottom-sheet';
|
|
import { APP_LANGUAGE, APP_LOGIN_TYPE } from '@/entities/common/model/constant';
|
|
import { AppLanguage, HeaderType, LoginType } from '@/entities/common/model/types';
|
|
import { useStore } from '@/shared/model/store';
|
|
import {
|
|
useSetHeaderTitle,
|
|
useSetHeaderType,
|
|
useSetFooterMode
|
|
} from '@/widgets/sub-layout/use-sub-layout';
|
|
import { ChangeEvent, useCallback, useEffect, useState } from 'react';
|
|
import { useAppBridge } from '@/hooks/useAppBridge';
|
|
import { useTranslation } from 'react-i18next';
|
|
import appBridge from '@/utils/appBridge';
|
|
import { Dialog } from '@/shared/ui/dialogs/dialog';
|
|
|
|
export const SettingPage = () => {
|
|
let userInfo = useStore.getState().UserStore.userInfo;
|
|
const {
|
|
isPushNotificationEnabled,
|
|
openAppSettings,
|
|
logout,
|
|
getLoginType,
|
|
getNotificationSetting,
|
|
setNotificationSetting: updateNotificationSetting,
|
|
isAndroid,
|
|
getLanguage
|
|
} = useAppBridge();
|
|
const { t, i18n } = useTranslation();
|
|
|
|
useSetHeaderTitle(t('settings.title'));
|
|
useSetHeaderType(HeaderType.LeftArrow);
|
|
useSetFooterMode(false);
|
|
|
|
const [loginTypeBottomSheetOn, setLoginTypeBottomSheetOn] = useState<boolean>(false);
|
|
const [serviceLanguageBottomSheetOn, setServiceLanguageBottomSheetOn] = useState<boolean>(false);
|
|
const [loginType, setLoginType] = useState<LoginType>(LoginType.ID);
|
|
const [appLanguage, setAppLanguage] = useState<AppLanguage>(
|
|
(i18n.language === 'en' ? AppLanguage.EN : AppLanguage.KO)
|
|
);
|
|
|
|
const {mutateAsync: appAlarmFind} = useAppAlarmFindMutation();
|
|
const {mutateAsync: appAlarmConsent} = useAppAlarmConsentMutation();
|
|
|
|
const [pushNotificationEnabled, setPushNotificationEnabled] = useState<boolean>(false);
|
|
const [notificationSetting, setNotificationSetting] = useState<boolean>(true);
|
|
const [alarmSetting, setAlarmSetting] = useState<Record<string, boolean>>({
|
|
'21': false,
|
|
'11': false,
|
|
'31': false,
|
|
'41': false,
|
|
'61': false,
|
|
'62': false,
|
|
'15': false
|
|
});
|
|
|
|
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
|
|
|
|
const onClickPolicy = () => {
|
|
window.open('https://www.nicepay.co.kr/cs/terms/policy1.do', '_blank');
|
|
};
|
|
|
|
const onClickLogout = () => {
|
|
setDialogOpen(true);
|
|
};
|
|
|
|
const checkPushNotificationStatus = useCallback(() => {
|
|
console.log('checkPushNotificationStatus');
|
|
isPushNotificationEnabled().then((enabled) => {
|
|
setPushNotificationEnabled(enabled);
|
|
});
|
|
}, [isPushNotificationEnabled]);
|
|
|
|
const loadLoginType = useCallback(() => {
|
|
getLoginType().then((type) => {
|
|
setLoginType(type as LoginType);
|
|
});
|
|
}, [getLoginType]);
|
|
|
|
const loadLanguage = useCallback(() => {
|
|
getLanguage().then((language) => {
|
|
const languageType = language === 'en' ? AppLanguage.EN : AppLanguage.KO;
|
|
setAppLanguage(languageType);
|
|
// i18n 언어도 동기화
|
|
if (i18n.language !== language) {
|
|
i18n.changeLanguage(language);
|
|
}
|
|
});
|
|
}, [getLanguage, i18n]);
|
|
|
|
const onClickPushNotificationToggle = () => {
|
|
openAppSettings();
|
|
};
|
|
|
|
// 알림 설정 로드 (Android만)
|
|
const loadNotificationSetting = useCallback(async () => {
|
|
if (!isAndroid) {
|
|
console.log('[loadNotificationSetting] Not Android, skipping');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
console.log('[loadNotificationSetting] Calling getNotificationSetting()...');
|
|
const enabled = await getNotificationSetting();
|
|
console.log('[loadNotificationSetting] Received value from bridge:', enabled);
|
|
console.log('[loadNotificationSetting] Type of enabled:', typeof enabled);
|
|
|
|
setNotificationSetting(enabled);
|
|
console.log('[loadNotificationSetting] State updated to:', enabled);
|
|
} catch (error) {
|
|
console.error('[loadNotificationSetting] Failed to load notification setting:', error);
|
|
}
|
|
}, [isAndroid, getNotificationSetting]);
|
|
|
|
// 알림 설정 토글 핸들러 (Android만)
|
|
const onClickNotificationSettingToggle = useCallback(async () => {
|
|
if (!isAndroid) {
|
|
console.log('[onClickNotificationSettingToggle] Not Android, skipping');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const newValue = !notificationSetting;
|
|
console.log('[onClickNotificationSettingToggle] to save:', newValue);
|
|
|
|
const result = await updateNotificationSetting(newValue);
|
|
console.log('[onClickNotificationSettingToggle] result:', result);
|
|
|
|
// ✅ needsPermission이 true이면 설정 화면으로 이동한 것
|
|
if (result && typeof result === 'object' && 'needsPermission' in result && result.needsPermission) {
|
|
console.log('[onClickNotificationSettingToggle] Permission needed - opened settings');
|
|
// 설정이 변경되지 않았으므로 상태 유지
|
|
return;
|
|
}
|
|
|
|
// ✅ 성공한 경우에만 상태 업데이트
|
|
if (result && typeof result === 'object' && 'enabled' in result) {
|
|
setNotificationSetting(result.enabled);
|
|
console.log('[onClickNotificationSettingToggle] State updated to:', result.enabled);
|
|
} else {
|
|
// Fallback
|
|
setNotificationSetting(newValue);
|
|
console.log('[onClickNotificationSettingToggle] State updated to (fallback):', newValue);
|
|
}
|
|
|
|
// ✅ 저장 후 바로 다시 읽어서 확인
|
|
console.log('[onClickNotificationSettingToggle] Verifying saved value...');
|
|
const verifyValue = await getNotificationSetting();
|
|
console.log('[onClickNotificationSettingToggle] Verified value:', verifyValue);
|
|
|
|
if (verifyValue !== result?.enabled && !result?.needsPermission) {
|
|
console.error('[onClickNotificationSettingToggle] WARNING: Saved value != Verified value', {
|
|
saved: result?.enabled,
|
|
verified: verifyValue
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('[onClickNotificationSettingToggle] Failed to update notification setting:', error);
|
|
}
|
|
}, [isAndroid, notificationSetting, updateNotificationSetting, getNotificationSetting]);
|
|
|
|
const responseAlarmSetting = useCallback((data: AppAlarmFindResponse) => {
|
|
let responseAlarms: Record<string, boolean> = {};
|
|
for(let i=0;i<data.alarmAgree.length;i++){
|
|
let item = data.alarmAgree[i];
|
|
if(item){
|
|
let appNotificationSubCategory = item.appNotificationSubCategory;
|
|
let appNotificationAllowed = item.appNotificationAllowed;
|
|
responseAlarms['' + appNotificationSubCategory] = appNotificationAllowed;
|
|
}
|
|
}
|
|
setAlarmSetting(prevState => ({
|
|
...prevState,
|
|
...responseAlarms
|
|
}));
|
|
}, []);
|
|
|
|
const callAppAlarmFind = useCallback(() => {
|
|
if(userInfo.usrid){
|
|
let params: AppAlarmFindParams = {
|
|
usrid: userInfo.usrid,
|
|
appCl: MERCHANT_ADMIN_APP.MERCHANT_ADMIN_APP
|
|
};
|
|
appAlarmFind(params).then((rs: AppAlarmFindResponse) => {
|
|
responseAlarmSetting(rs);
|
|
});
|
|
}
|
|
}, [userInfo.usrid, appAlarmFind, responseAlarmSetting]);
|
|
|
|
const callAppAlarmConsent = (value: boolean, alarmCode: string) => {
|
|
if(userInfo.usrid){
|
|
let params: AppAlarmConsentParams = {
|
|
usrid: userInfo.usrid,
|
|
appCl: MERCHANT_ADMIN_APP.MERCHANT_ADMIN_APP,
|
|
appNotificationSubCategory: alarmCode,
|
|
appNotificationAllowed: value
|
|
};
|
|
appAlarmConsent(params).then((_rs: AppAlarmConsentResponse) => {
|
|
setAlarmSetting({
|
|
...alarmSetting,
|
|
...{ ['' + alarmCode]: value }
|
|
});
|
|
});
|
|
}
|
|
};
|
|
|
|
const changeLanguage = async (language: string) => {
|
|
// 웹 언어 변경
|
|
i18n.changeLanguage(language);
|
|
localStorage.setItem('i18nextLng', language);
|
|
|
|
// 네이티브 환경에서 네이티브 언어도 변경
|
|
if (appBridge.isNativeEnvironment()) {
|
|
try {
|
|
await appBridge.setLanguage(language);
|
|
} catch (error) {
|
|
console.error('Failed to set native language:', error);
|
|
}
|
|
}
|
|
};
|
|
|
|
const getLoginTypeLabel = (type: LoginType): string => {
|
|
switch (type) {
|
|
case LoginType.ID:
|
|
return t('settings.loginType.idPassword');
|
|
case LoginType.BIOMETRIC:
|
|
return t('settings.loginType.biometric');
|
|
default:
|
|
return '';
|
|
}
|
|
};
|
|
|
|
const getLanguageLabel = (language: AppLanguage): string => {
|
|
switch (language) {
|
|
case AppLanguage.KO:
|
|
return '한국어';
|
|
case AppLanguage.EN:
|
|
return 'English';
|
|
default:
|
|
return '';
|
|
}
|
|
};
|
|
|
|
const handleSetLoginType = (type: string) => {
|
|
setLoginType(type as LoginType);
|
|
};
|
|
|
|
useEffect(() => {
|
|
callAppAlarmFind();
|
|
checkPushNotificationStatus();
|
|
loadLoginType();
|
|
loadNotificationSetting();
|
|
loadLanguage();
|
|
|
|
// 앱이 포어그라운드로 돌아올 때 푸시 알림 권한 상태 재확인
|
|
const handleVisibilityChange = () => {
|
|
if (document.visibilityState === 'visible') {
|
|
checkPushNotificationStatus();
|
|
loadNotificationSetting();
|
|
}
|
|
};
|
|
|
|
const handleFocus = () => {
|
|
checkPushNotificationStatus();
|
|
loadNotificationSetting();
|
|
};
|
|
|
|
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
window.addEventListener('focus', handleFocus);
|
|
|
|
return () => {
|
|
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
window.removeEventListener('focus', handleFocus);
|
|
};
|
|
}, [callAppAlarmFind, checkPushNotificationStatus, loadLoginType, loadNotificationSetting, loadLanguage]);
|
|
|
|
return (
|
|
<>
|
|
<main className="pop">
|
|
<div className="sub-wrap">
|
|
{/* ✅ Android일 때는 앱 내 설정, 아니면 시스템 권한 표시 */}
|
|
<div className="settings-header">
|
|
<div className="settings-title">{t('settings.notificationSettings')}</div>
|
|
<label className="settings-switch">
|
|
<input
|
|
type="checkbox"
|
|
checked={isAndroid ? notificationSetting : pushNotificationEnabled}
|
|
readOnly
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
if (isAndroid) {
|
|
onClickNotificationSettingToggle();
|
|
} else {
|
|
onClickPushNotificationToggle();
|
|
}
|
|
}}
|
|
/>
|
|
<span className="slider"></span>
|
|
</label>
|
|
</div>
|
|
|
|
<div className="settings-divider"></div>
|
|
|
|
<div className="settings-section">
|
|
<div className="settings-section-title">{t('settings.noticeAlarms')}</div>
|
|
<div className="settings-row">
|
|
<div className="settings-row-title">{t('settings.serviceOperation')}</div>
|
|
<label className="settings-switch">
|
|
<input
|
|
type="checkbox"
|
|
checked={ alarmSetting['21'] }
|
|
disabled={ !pushNotificationEnabled }
|
|
onChange={ (e: ChangeEvent<HTMLInputElement>) => callAppAlarmConsent(e.target.checked, '21') }
|
|
/>
|
|
<span className="slider"></span>
|
|
</label>
|
|
</div>
|
|
<div className="settings-row">
|
|
<div className="settings-row-title">{t('settings.interestFreeEvent')}</div>
|
|
<label className="settings-switch">
|
|
<input
|
|
type="checkbox"
|
|
checked={ alarmSetting['11'] }
|
|
disabled={ !pushNotificationEnabled }
|
|
onChange={ (e: ChangeEvent<HTMLInputElement>) => callAppAlarmConsent(e.target.checked, '11') }
|
|
/>
|
|
<span className="slider"></span>
|
|
</label>
|
|
</div>
|
|
<div className="settings-row">
|
|
<div className="settings-row-title">{t('settings.servicePolicyChange')}</div>
|
|
<label className="settings-switch">
|
|
<input
|
|
type="checkbox"
|
|
checked={ alarmSetting['31'] }
|
|
disabled={ !pushNotificationEnabled }
|
|
onChange={ (e: ChangeEvent<HTMLInputElement>) => callAppAlarmConsent(e.target.checked, '31') }
|
|
/>
|
|
<span className="slider"></span>
|
|
</label>
|
|
</div>
|
|
<div className="settings-row">
|
|
<div className="settings-row-title">{t('settings.niceNews')}</div>
|
|
<label className="settings-switch">
|
|
<input
|
|
type="checkbox"
|
|
checked={ alarmSetting['41'] }
|
|
disabled={ !pushNotificationEnabled }
|
|
onChange={ (e: ChangeEvent<HTMLInputElement>) => callAppAlarmConsent(e.target.checked, '41') }
|
|
/>
|
|
<span className="slider"></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="settings-section nopadding">
|
|
<div className="settings-section-title">{t('settings.settlementAlarms')}</div>
|
|
<div className="settings-row">
|
|
<div className="settings-row-title">{t('settings.settlementStatus')}</div>
|
|
<label className="settings-switch">
|
|
<input
|
|
type="checkbox"
|
|
checked={ alarmSetting['61'] }
|
|
disabled={ !pushNotificationEnabled }
|
|
onChange={ (e: ChangeEvent<HTMLInputElement>) => callAppAlarmConsent(e.target.checked, '61') }
|
|
/>
|
|
<span className="slider"></span>
|
|
</label>
|
|
</div>
|
|
<div className="settings-row nopadding">
|
|
<div className="settings-row-title">{t('settings.settlementLimit')}</div>
|
|
<label className="settings-switch">
|
|
<input
|
|
type="checkbox"
|
|
checked={ alarmSetting['62'] }
|
|
disabled={ !pushNotificationEnabled }
|
|
onChange={ (e: ChangeEvent<HTMLInputElement>) => callAppAlarmConsent(e.target.checked, '62') }
|
|
/>
|
|
<span className="slider"></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="settings-divider"></div>
|
|
|
|
<div className="settings-section nopadding">
|
|
<div className="settings-row link">
|
|
<div className="settings-row-title bd-style">{t('settings.marketingConsent')}</div>
|
|
<label className="settings-switch">
|
|
<input
|
|
type="checkbox"
|
|
checked={ alarmSetting['15'] }
|
|
onChange={ (e: ChangeEvent<HTMLInputElement>) => callAppAlarmConsent(e.target.checked, '15') }
|
|
/>
|
|
<span className="slider"></span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
className="settings-row link"
|
|
onClick={ () => setLoginTypeBottomSheetOn(true) }
|
|
>
|
|
<div className="settings-row-title bd-style">{t('settings.loginTypeSetting')}</div>
|
|
<div className="click">{ getLoginTypeLabel(loginType) }</div>
|
|
</div>
|
|
|
|
<div
|
|
className="settings-row link nopadding"
|
|
onClick={ () => setServiceLanguageBottomSheetOn(true) }
|
|
>
|
|
<div className="settings-row-title bd-style">{t('settings.serviceLanguageSetting')}</div>
|
|
<div className="click">{ getLanguageLabel(appLanguage) }</div>
|
|
</div>
|
|
|
|
<div className="settings-divider"></div>
|
|
|
|
<div className="settings-row danger" onClick={onClickPolicy}>
|
|
<div className="settings-row-title bd-style">{t('settings.termsAndPrivacy')}</div>
|
|
</div>
|
|
|
|
<div className="settings-row danger" onClick={onClickLogout}>
|
|
<div className="settings-row-title bd-style">{t('settings.logout')}</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
{ !!loginTypeBottomSheetOn &&
|
|
<LoginTypeBottomSheet
|
|
loginTypeBottomSheetOn={ loginTypeBottomSheetOn }
|
|
setLoginTypeBottomSheetOn={ setLoginTypeBottomSheetOn }
|
|
loginType={ loginType }
|
|
setLoginType={ handleSetLoginType }
|
|
onBiometricRegistered={ loadLoginType }
|
|
isAndroid={ isAndroid }
|
|
></LoginTypeBottomSheet>
|
|
}
|
|
{ !!serviceLanguageBottomSheetOn &&
|
|
<ServiceLanguageBottomSheet
|
|
serviceLanguageBottomSheetOn={ serviceLanguageBottomSheetOn }
|
|
setServiceLanguageBottomSheetOn={ setServiceLanguageBottomSheetOn }
|
|
appLanguage={ appLanguage }
|
|
setAppLanguage={ setAppLanguage }
|
|
changeLanguage={ changeLanguage }
|
|
></ServiceLanguageBottomSheet>
|
|
}
|
|
{ !!dialogOpen &&
|
|
<Dialog
|
|
open={dialogOpen}
|
|
onClose={() => setDialogOpen(false)}
|
|
message={t('settings.logoutConfirm')}
|
|
afterLeave={() => setDialogOpen(false)}
|
|
buttonLabel={[t('common.cancel'), t('common.confirm')]}
|
|
onConfirmClick={() => {
|
|
console.log(t('common.confirm'));
|
|
logout();
|
|
}}
|
|
onCancelClick={() => setDialogOpen(false)}
|
|
/>
|
|
}
|
|
</>
|
|
);
|
|
}; |