diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz
index 94a693a..8b85faa 100644
Binary files a/.yarn/install-state.gz and b/.yarn/install-state.gz differ
diff --git a/src/components/cards/QrCard.js b/src/components/cards/QrCard.js
index cec4443..148aedd 100644
--- a/src/components/cards/QrCard.js
+++ b/src/components/cards/QrCard.js
@@ -85,7 +85,7 @@ const QrCard = ({
style={{ width: '100%' }}
showsVerticalScrollIndicator={false}
>
- 임시 출입 QR
+ 출입 QR
{
방문자: {data.visitorType}
시작일: {data.startDate}
만료일: {data.expireDate}
- 승인 여부: {data.approval}
+ 승인 여부: {data.approval.replace(/\n/g, '')}
환자 번호: {data.patientNumber}
diff --git a/src/components/modals/PasswordConfirmModal.js b/src/components/modals/PasswordConfirmModal.js
index 843338c..81d6f28 100644
--- a/src/components/modals/PasswordConfirmModal.js
+++ b/src/components/modals/PasswordConfirmModal.js
@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
import { View, Text, Modal } from 'react-native';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
import { verifyPassword } from '../../apis/PasswordApi';
+import { useAuthStore } from '../../stores/authStore';
import { useModalStore } from '../../stores/modalStore';
import { styles } from './styles/PasswordConfirmModal.styles';
import NormalInput from '../textinputs/NormalInput';
@@ -9,9 +10,11 @@ import NormalButton from '../buttons/NormalButton';
import WaveHeader from '../headers/WaveHeader';
const PasswordConfirmModal = ({ navigationRef }) => {
- const { isPasswordModalVisible, pendingTab, prevTab, hidePasswordModal } = useModalStore();
+ const { isPasswordModalVisible, pendingTab, prevTab, isFromAppState, hidePasswordModal } =
+ useModalStore();
const [password, setPassword] = useState('');
const [errorText, setErrorText] = useState(''); // mediumText ErrorText
+ const setLastAuthTime = useAuthStore((state) => state.setLastAuthTime);
useEffect(() => {
if (isPasswordModalVisible) {
@@ -43,7 +46,8 @@ const PasswordConfirmModal = ({ navigationRef }) => {
try {
await verifyPassword(password);
-
+ // 인증 성공 시 인증 시각 저장
+ setLastAuthTime(Date.now());
navigationRef.current?.navigate(pendingTab);
hidePasswordModal();
} catch (error) {
@@ -53,7 +57,11 @@ const PasswordConfirmModal = ({ navigationRef }) => {
const onClosePasswordModal = () => {
hidePasswordModal();
- navigationRef.current?.navigate(prevTab);
+ if (isFromAppState) {
+ navigationRef.current?.navigate('MainPage'); // 홈으로 강제 이동
+ } else {
+ navigationRef.current?.navigate(prevTab); // 이전 탭으로 이동
+ }
};
return (
diff --git a/src/navigations/AppNavigator.js b/src/navigations/AppNavigator.js
index 2f6383c..c473f98 100644
--- a/src/navigations/AppNavigator.js
+++ b/src/navigations/AppNavigator.js
@@ -1,6 +1,6 @@
-import { useState, useEffect } from 'react';
+import { useState, useEffect, useRef } from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
-import { StatusBar } from 'react-native';
+import { StatusBar, AppState } from 'react-native';
import { NavigationContainer, DefaultTheme } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import Ionicons from 'react-native-vector-icons/Ionicons';
@@ -28,6 +28,7 @@ import AccessRequestRolePage from '../pages/AccessRequestRolePage';
const Stack = createStackNavigator();
const Tab = createBottomTabNavigator();
+const PASSWORD_AUTH_VALID_MS = 5 * 60 * 1000; // 비밀번호 재인증 시간 (5분)
// StatusBar 스타일 설정
const WHITE_TAB_SCREENS = ['MainPage', 'WelcomePage'];
@@ -106,11 +107,13 @@ export default function AppNavigator() {
clearAccessToken,
_hasHydrated, // hydration flag
} = useAuthStore();
+ const lastAuthTime = useAuthStore((state) => state.lastAuthTime);
+ const appState = useRef(AppState.currentState);
const showPasswordModal = useModalStore((state) => state.showPasswordModal);
// 현재 라우트 이름을 저장하는 state
- const [currentRouteName, setCurrentRouteName] = useState('WelcomePage');
+ const [currentRouteName, setCurrentRouteName] = useState(isLoggedIn ? 'MainPage' : 'WelcomePage');
// 알림 읽음 여부 관련
const { hasUnread, markAllAsRead } = useNoticeBadge();
@@ -143,10 +146,33 @@ export default function AppNavigator() {
// 탭 클릭 시 비밀번호 모달 호출
const handleTabPress = (e, tabName) => {
- e.preventDefault();
- showPasswordModal(tabName, currentRouteName || 'MainPage');
+ if (!lastAuthTime || Date.now() - lastAuthTime > PASSWORD_AUTH_VALID_MS) {
+ e.preventDefault();
+ showPasswordModal(tabName, currentRouteName || 'MainPage');
+ }
};
+ // 비밀번호 모달 백그라운드 -> 포그라운드 변경시 적용
+ useEffect(() => {
+ const subscription = AppState.addEventListener('change', (nextAppState) => {
+ // 포그라운드로 돌아올 때
+ if (appState.current.match(/inactive|background/) && nextAppState === 'active') {
+ // 현재 라우트가 마이페이지(혹은 마이페이지 stack 내부)라면
+ if (currentRouteName === 'MyPage' || currentRouteName === 'MyPageStack') {
+ // 인증 만료됐으면 모달 띄우기
+ if (!lastAuthTime || Date.now() - lastAuthTime > PASSWORD_AUTH_VALID_MS) {
+ showPasswordModal('MyPageStack', currentRouteName, true);
+ }
+ }
+ }
+ appState.current = nextAppState;
+ });
+
+ return () => {
+ subscription.remove();
+ };
+ }, [currentRouteName, lastAuthTime]);
+
const navTheme = {
...DefaultTheme,
colors: {
diff --git a/src/stores/authStore.js b/src/stores/authStore.js
index 0b108a6..2054f35 100644
--- a/src/stores/authStore.js
+++ b/src/stores/authStore.js
@@ -39,6 +39,10 @@ export const useAuthStore = create(
// hydration(스토리지 복원) 상태
_hasHydrated: false,
setHasHydrated: (state) => set({ _hasHydrated: state }),
+
+ //인증 시각 관리
+ lastAuthTime: 0,
+ setLastAuthTime: (time) => set({ lastAuthTime: time }),
}),
{
name: 'auth-storage',
@@ -47,6 +51,7 @@ export const useAuthStore = create(
accessToken: state.accessToken,
isLoggedIn: state.isLoggedIn,
userInfo: state.userInfo,
+ lastAuthTime: state.lastAuthTime,
}),
// 복원 완료 시 _hasHydrated를 true로 변경
onRehydrateStorage: () => (state, error) => {
diff --git a/src/stores/modalStore.js b/src/stores/modalStore.js
index ced7b53..8736957 100644
--- a/src/stores/modalStore.js
+++ b/src/stores/modalStore.js
@@ -4,15 +4,18 @@ export const useModalStore = create((set) => ({
isPasswordModalVisible: false,
pendingTab: null, // 이동 시도한 탭 이름
prevTab: '', // 이전 탭 이름
- showPasswordModal: (tabName, prevTab) =>
+ isFromAppState: false, // 모달 출현 이유
+ showPasswordModal: (tabName, prevTab, isFromAppState = false) =>
set({
isPasswordModalVisible: true,
pendingTab: tabName,
prevTab,
+ isFromAppState,
}),
hidePasswordModal: () =>
set({
isPasswordModalVisible: false,
pendingTab: null,
+ isFromAppState: false, // 닫을 때 초기화
}),
}));