알림 수신 설정 푸시 권한 연동 구현
- 앱브리지에 isPushNotificationEnabled, openAppSettings 메서드 추가 - 설정 페이지에서 푸시 알림 권한 상태에 따라 알림 수신 설정 토글 표시 - 알림 수신 설정 토글 클릭 시 앱 설정 화면으로 이동 - 푸시 권한이 꺼져있으면 하위 알림 토글들(11, 21, 31, 41, 61, 62) 비활성화 - 앱이 포어그라운드로 돌아올 때 푸시 권한 상태 재확인하여 UI 업데이트 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -53,6 +53,12 @@ interface UseAppBridgeReturn {
|
|||||||
openBiometricRegistrationPopup: () => Promise<boolean>;
|
openBiometricRegistrationPopup: () => Promise<boolean>;
|
||||||
// 간편 인증 등록 팝업 닫기
|
// 간편 인증 등록 팝업 닫기
|
||||||
closeBiometricRegistrationPopup: () => Promise<void>;
|
closeBiometricRegistrationPopup: () => Promise<void>;
|
||||||
|
|
||||||
|
// 푸시 알림 권한 확인
|
||||||
|
isPushNotificationEnabled: () => Promise<boolean>;
|
||||||
|
|
||||||
|
// 앱 설정 화면 열기
|
||||||
|
openAppSettings: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAppBridge = (): UseAppBridgeReturn => {
|
export const useAppBridge = (): UseAppBridgeReturn => {
|
||||||
@@ -214,7 +220,7 @@ export const useAppBridge = (): UseAppBridgeReturn => {
|
|||||||
console.warn('Web Share API failed:', error);
|
console.warn('Web Share API failed:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 폴백: 클립보드에 복사
|
// 폴백: 클립보드에 복사
|
||||||
const shareText = `${content.title}\n${content.text}${content.url ? `\n${content.url}` : ''}`;
|
const shareText = `${content.title}\n${content.text}${content.url ? `\n${content.url}` : ''}`;
|
||||||
if (navigator.clipboard) {
|
if (navigator.clipboard) {
|
||||||
@@ -225,10 +231,25 @@ export const useAppBridge = (): UseAppBridgeReturn => {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return appBridge.safeCall(() => appBridge.shareContent(content));
|
return appBridge.safeCall(() => appBridge.shareContent(content));
|
||||||
}, [isNativeEnvironment]);
|
}, [isNativeEnvironment]);
|
||||||
|
|
||||||
|
const isPushNotificationEnabled = useCallback(async (): Promise<boolean> => {
|
||||||
|
if (!isNativeEnvironment) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const result = await appBridge.safeCall(() => appBridge.isPushNotificationEnabled(), false);
|
||||||
|
return result || false;
|
||||||
|
}, [isNativeEnvironment]);
|
||||||
|
|
||||||
|
const openAppSettings = useCallback(async (): Promise<void> => {
|
||||||
|
if (!isNativeEnvironment) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return appBridge.safeCall(() => appBridge.openAppSettings());
|
||||||
|
}, [isNativeEnvironment]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isNativeEnvironment,
|
isNativeEnvironment,
|
||||||
isAndroid,
|
isAndroid,
|
||||||
@@ -249,6 +270,8 @@ export const useAppBridge = (): UseAppBridgeReturn => {
|
|||||||
requestRefreshToken,
|
requestRefreshToken,
|
||||||
registerBiometric,
|
registerBiometric,
|
||||||
openBiometricRegistrationPopup,
|
openBiometricRegistrationPopup,
|
||||||
closeBiometricRegistrationPopup
|
closeBiometricRegistrationPopup,
|
||||||
|
isPushNotificationEnabled,
|
||||||
|
openAppSettings
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -5,15 +5,18 @@ import { LoginTypeBottomSheet } from '@/entities/alarm/ui/login-type-bottom-shee
|
|||||||
import { ServiceLanguageBottomSheet } from '@/entities/alarm/ui/service-language-bottom-sheet';
|
import { ServiceLanguageBottomSheet } from '@/entities/alarm/ui/service-language-bottom-sheet';
|
||||||
import { AppLanguage, HeaderType, LoginType } from '@/entities/common/model/types';
|
import { AppLanguage, HeaderType, LoginType } from '@/entities/common/model/types';
|
||||||
import { useStore } from '@/shared/model/store';
|
import { useStore } from '@/shared/model/store';
|
||||||
import {
|
import {
|
||||||
useSetHeaderTitle,
|
useSetHeaderTitle,
|
||||||
useSetHeaderType,
|
useSetHeaderType,
|
||||||
useSetFooterMode
|
useSetFooterMode
|
||||||
} from '@/widgets/sub-layout/use-sub-layout';
|
} from '@/widgets/sub-layout/use-sub-layout';
|
||||||
import { ChangeEvent, useEffect, useState } from 'react';
|
import { ChangeEvent, useEffect, useState } from 'react';
|
||||||
|
import { useAppBridge } from '@/hooks/useAppBridge';
|
||||||
|
|
||||||
export const SettingPage = () => {
|
export const SettingPage = () => {
|
||||||
let userInfo = useStore.getState().UserStore.userInfo;
|
let userInfo = useStore.getState().UserStore.userInfo;
|
||||||
|
const { isPushNotificationEnabled, openAppSettings } = useAppBridge();
|
||||||
|
|
||||||
useSetHeaderTitle('설정');
|
useSetHeaderTitle('설정');
|
||||||
useSetHeaderType(HeaderType.LeftArrow);
|
useSetHeaderType(HeaderType.LeftArrow);
|
||||||
useSetFooterMode(false);
|
useSetFooterMode(false);
|
||||||
@@ -26,6 +29,7 @@ export const SettingPage = () => {
|
|||||||
const {mutateAsync: appAlarmFind} = useAppAlarmFindMutation();
|
const {mutateAsync: appAlarmFind} = useAppAlarmFindMutation();
|
||||||
const {mutateAsync: appAlarmConsent} = useAppAlarmConsentMutation();
|
const {mutateAsync: appAlarmConsent} = useAppAlarmConsentMutation();
|
||||||
|
|
||||||
|
const [pushNotificationEnabled, setPushNotificationEnabled] = useState<boolean>(false);
|
||||||
const [alarmSetting, setAlarmSetting] = useState<Record<string, boolean>>({
|
const [alarmSetting, setAlarmSetting] = useState<Record<string, boolean>>({
|
||||||
'21': false,
|
'21': false,
|
||||||
'11': false,
|
'11': false,
|
||||||
@@ -39,6 +43,16 @@ export const SettingPage = () => {
|
|||||||
const onClickPrivacyPolicy = () => {
|
const onClickPrivacyPolicy = () => {
|
||||||
window.open('https://www.nicevan.co.kr/privacy-policy', '_blank');
|
window.open('https://www.nicevan.co.kr/privacy-policy', '_blank');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const checkPushNotificationStatus = () => {
|
||||||
|
isPushNotificationEnabled().then((enabled) => {
|
||||||
|
setPushNotificationEnabled(enabled);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClickPushNotificationToggle = () => {
|
||||||
|
openAppSettings();
|
||||||
|
};
|
||||||
|
|
||||||
const callAppAlarmFind = () => {
|
const callAppAlarmFind = () => {
|
||||||
if(userInfo.usrid){
|
if(userInfo.usrid){
|
||||||
@@ -87,6 +101,26 @@ export const SettingPage = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
callAppAlarmFind();
|
callAppAlarmFind();
|
||||||
|
checkPushNotificationStatus();
|
||||||
|
|
||||||
|
// 앱이 포어그라운드로 돌아올 때 푸시 알림 권한 상태 재확인
|
||||||
|
const handleVisibilityChange = () => {
|
||||||
|
if (document.visibilityState === 'visible') {
|
||||||
|
checkPushNotificationStatus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFocus = () => {
|
||||||
|
checkPushNotificationStatus();
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||||
|
window.addEventListener('focus', handleFocus);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||||
|
window.removeEventListener('focus', handleFocus);
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -95,8 +129,8 @@ export const SettingPage = () => {
|
|||||||
<div className="sub-wrap">
|
<div className="sub-wrap">
|
||||||
<div className="settings-header">
|
<div className="settings-header">
|
||||||
<div className="settings-title">알림 수신 설정</div>
|
<div className="settings-title">알림 수신 설정</div>
|
||||||
<label className="settings-switch">
|
<label className="settings-switch" onClick={onClickPushNotificationToggle}>
|
||||||
<input type="checkbox" />
|
<input type="checkbox" checked={pushNotificationEnabled} readOnly />
|
||||||
<span className="slider"></span>
|
<span className="slider"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -108,9 +142,10 @@ export const SettingPage = () => {
|
|||||||
<div className="settings-row">
|
<div className="settings-row">
|
||||||
<div className="settings-row-title">서비스 운영 및 장애</div>
|
<div className="settings-row-title">서비스 운영 및 장애</div>
|
||||||
<label className="settings-switch">
|
<label className="settings-switch">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={ alarmSetting['21'] }
|
checked={ alarmSetting['21'] }
|
||||||
|
disabled={ !pushNotificationEnabled }
|
||||||
onChange={ (e: ChangeEvent<HTMLInputElement>) => callAppAlarmConsent(e.target.checked, '21') }
|
onChange={ (e: ChangeEvent<HTMLInputElement>) => callAppAlarmConsent(e.target.checked, '21') }
|
||||||
/>
|
/>
|
||||||
<span className="slider"></span>
|
<span className="slider"></span>
|
||||||
@@ -119,9 +154,10 @@ export const SettingPage = () => {
|
|||||||
<div className="settings-row">
|
<div className="settings-row">
|
||||||
<div className="settings-row-title">무이자 혜택 및 이벤트</div>
|
<div className="settings-row-title">무이자 혜택 및 이벤트</div>
|
||||||
<label className="settings-switch">
|
<label className="settings-switch">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={ alarmSetting['11'] }
|
checked={ alarmSetting['11'] }
|
||||||
|
disabled={ !pushNotificationEnabled }
|
||||||
onChange={ (e: ChangeEvent<HTMLInputElement>) => callAppAlarmConsent(e.target.checked, '11') }
|
onChange={ (e: ChangeEvent<HTMLInputElement>) => callAppAlarmConsent(e.target.checked, '11') }
|
||||||
/>
|
/>
|
||||||
<span className="slider"></span>
|
<span className="slider"></span>
|
||||||
@@ -130,9 +166,10 @@ export const SettingPage = () => {
|
|||||||
<div className="settings-row">
|
<div className="settings-row">
|
||||||
<div className="settings-row-title">서비스 이용 및 정책 변경</div>
|
<div className="settings-row-title">서비스 이용 및 정책 변경</div>
|
||||||
<label className="settings-switch">
|
<label className="settings-switch">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={ alarmSetting['31'] }
|
checked={ alarmSetting['31'] }
|
||||||
|
disabled={ !pushNotificationEnabled }
|
||||||
onChange={ (e: ChangeEvent<HTMLInputElement>) => callAppAlarmConsent(e.target.checked, '31') }
|
onChange={ (e: ChangeEvent<HTMLInputElement>) => callAppAlarmConsent(e.target.checked, '31') }
|
||||||
/>
|
/>
|
||||||
<span className="slider"></span>
|
<span className="slider"></span>
|
||||||
@@ -141,9 +178,10 @@ export const SettingPage = () => {
|
|||||||
<div className="settings-row">
|
<div className="settings-row">
|
||||||
<div className="settings-row-title">NICE소식 및 기타 정보</div>
|
<div className="settings-row-title">NICE소식 및 기타 정보</div>
|
||||||
<label className="settings-switch">
|
<label className="settings-switch">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={ alarmSetting['41'] }
|
checked={ alarmSetting['41'] }
|
||||||
|
disabled={ !pushNotificationEnabled }
|
||||||
onChange={ (e: ChangeEvent<HTMLInputElement>) => callAppAlarmConsent(e.target.checked, '41') }
|
onChange={ (e: ChangeEvent<HTMLInputElement>) => callAppAlarmConsent(e.target.checked, '41') }
|
||||||
/>
|
/>
|
||||||
<span className="slider"></span>
|
<span className="slider"></span>
|
||||||
@@ -156,9 +194,10 @@ export const SettingPage = () => {
|
|||||||
<div className="settings-row">
|
<div className="settings-row">
|
||||||
<div className="settings-row-title">정산처리 현황</div>
|
<div className="settings-row-title">정산처리 현황</div>
|
||||||
<label className="settings-switch">
|
<label className="settings-switch">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={ alarmSetting['61'] }
|
checked={ alarmSetting['61'] }
|
||||||
|
disabled={ !pushNotificationEnabled }
|
||||||
onChange={ (e: ChangeEvent<HTMLInputElement>) => callAppAlarmConsent(e.target.checked, '61') }
|
onChange={ (e: ChangeEvent<HTMLInputElement>) => callAppAlarmConsent(e.target.checked, '61') }
|
||||||
/>
|
/>
|
||||||
<span className="slider"></span>
|
<span className="slider"></span>
|
||||||
@@ -167,9 +206,10 @@ export const SettingPage = () => {
|
|||||||
<div className="settings-row nopadding">
|
<div className="settings-row nopadding">
|
||||||
<div className="settings-row-title">정산한도 알림</div>
|
<div className="settings-row-title">정산한도 알림</div>
|
||||||
<label className="settings-switch">
|
<label className="settings-switch">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={ alarmSetting['62'] }
|
checked={ alarmSetting['62'] }
|
||||||
|
disabled={ !pushNotificationEnabled }
|
||||||
onChange={ (e: ChangeEvent<HTMLInputElement>) => callAppAlarmConsent(e.target.checked, '62') }
|
onChange={ (e: ChangeEvent<HTMLInputElement>) => callAppAlarmConsent(e.target.checked, '62') }
|
||||||
/>
|
/>
|
||||||
<span className="slider"></span>
|
<span className="slider"></span>
|
||||||
|
|||||||
@@ -65,7 +65,13 @@ export enum BridgeMessageType {
|
|||||||
GET_LANGUAGE = 'getLanguage',
|
GET_LANGUAGE = 'getLanguage',
|
||||||
|
|
||||||
// 메시지 카운트 업데이트
|
// 메시지 카운트 업데이트
|
||||||
UPDATE_MESSAGE_COUNT = 'updateMessageCount'
|
UPDATE_MESSAGE_COUNT = 'updateMessageCount',
|
||||||
|
|
||||||
|
// 푸시 알림 권한 확인
|
||||||
|
IS_PUSH_NOTIFICATION_ENABLED = 'isPushNotificationEnabled',
|
||||||
|
|
||||||
|
// 앱 설정 화면 열기
|
||||||
|
OPEN_APP_SETTINGS = 'openAppSettings'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DeviceInfo {
|
export interface DeviceInfo {
|
||||||
|
|||||||
@@ -195,6 +195,16 @@ class AppBridge {
|
|||||||
return this.sendMessage(BridgeMessageType.UPDATE_MESSAGE_COUNT, { count });
|
return this.sendMessage(BridgeMessageType.UPDATE_MESSAGE_COUNT, { count });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 푸시 알림 권한 확인
|
||||||
|
async isPushNotificationEnabled(): Promise<boolean> {
|
||||||
|
return this.sendMessage(BridgeMessageType.IS_PUSH_NOTIFICATION_ENABLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 앱 설정 화면 열기
|
||||||
|
async openAppSettings(): Promise<void> {
|
||||||
|
return this.sendMessage(BridgeMessageType.OPEN_APP_SETTINGS);
|
||||||
|
}
|
||||||
|
|
||||||
// 네이티브 환경 체크
|
// 네이티브 환경 체크
|
||||||
isNativeEnvironment(): boolean {
|
isNativeEnvironment(): boolean {
|
||||||
return !!(
|
return !!(
|
||||||
|
|||||||
Reference in New Issue
Block a user