diff --git a/AppNavigator.js b/AppNavigator.js index f67bdcc..20c285b 100644 --- a/AppNavigator.js +++ b/AppNavigator.js @@ -2,13 +2,11 @@ import { NavigationContainer, DefaultTheme } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import { useState, useEffect } from 'react'; import Ionicons from '@expo/vector-icons/Ionicons'; -import { StatusBar, View } from 'react-native'; -import AsyncStorage from '@react-native-async-storage/async-storage'; - -import { setLogoutCallback } from './handler/logoutHandler'; +import { StatusBar } from 'react-native'; import HomeButtonController from './components/buttons/HomeButtonController'; import LoadingOverlay from './components/loadings/LoadingOverlay'; import { getMyInfo } from './apis/MyPageApi'; +import { useAuthStore } from './stores/authStore'; // 로그인 전 페이지 import WelcomePage from './pages/WelcomePage'; @@ -24,15 +22,21 @@ import AccessListPage from './pages/AccessListPage'; import MyAccessListPage from './pages/MyAccessListPage'; import AccessRequestPage from './pages/AccessRequestPage'; import AccessRequestRolePage from './pages/AccessRequestRolePage'; - import { colors } from './constants/colors'; const Stack = createStackNavigator(); export default function AppNavigator() { - const [isLoggedIn, setIsLoggedIn] = useState(true); // 로그인 상태 + const { + isLoggedIn, + setIsLoggedIn, + accessToken, + setLoading, + loading, + setAccessToken, + clearAccessToken, + } = useAuthStore(); const [navState, setNavState] = useState(null); - const [loading, setLoading] = useState(false); // 토큰 확인 중 상태 // 앱 시작 시 토큰 유효성 확인 useEffect(() => { @@ -40,16 +44,14 @@ export default function AppNavigator() { setLoading(true); try { await new Promise((resolve) => setTimeout(resolve, 1000)); - const token = await AsyncStorage.getItem('accessToken'); - if (token) { + if (accessToken) { // 회원 정보 조회로 토큰 유효성 검증 try { await getMyInfo(); - setIsLoggedIn(true); // 토큰 유효 + setAccessToken(accessToken); // 토큰 유효 } catch (err) { //에러 발생 시 - setIsLoggedIn(false); - await AsyncStorage.removeItem('accessToken'); + clearAccessToken(); } } else { setIsLoggedIn(false); @@ -63,11 +65,6 @@ export default function AppNavigator() { checkToken(); }, []); - // 앱 시작시 logoutHandler.js에 콜백 함수 등록 - useEffect(() => { - setLogoutCallback(() => setIsLoggedIn(false)); - }, []); - const navTheme = { ...DefaultTheme, colors: { @@ -100,9 +97,7 @@ export default function AppNavigator() { component={MainPage} options={{ headerShown: false, title: '홈' }} /> - - {(props) => } - + - - {(props) => } - + { const response = await axios.get('/hospitals'); - return response.data.data; }; diff --git a/apis/AxiosInstance.js b/apis/AxiosInstance.js index 8bb45eb..b12850a 100644 --- a/apis/AxiosInstance.js +++ b/apis/AxiosInstance.js @@ -1,7 +1,6 @@ import axios from 'axios'; import Constants from 'expo-constants'; -import AsyncStorage from '@react-native-async-storage/async-storage'; -import { runLogoutCallback } from '../handler/logoutHandler'; +import { useAuthStore } from '../stores/authStore'; const BASE_URL = Constants.expoConfig.extra.BASE_URL; @@ -12,7 +11,8 @@ const instance = axios.create({ // accessToken 가져오는 함수 const getAccessToken = async () => { - return await AsyncStorage.getItem('accessToken'); + const { accessToken } = useAuthStore.getState(); + return accessToken; }; // 요청 인터셉터: 모든 요청에 accessToken 자동 첨부 @@ -23,9 +23,6 @@ instance.interceptors.request.use( config.headers = config.headers || {}; // headers가 없으면 빈 객체로 초기화 config.headers.Authorization = `Bearer ${token}`; // Authorization 헤더에 Bearer 토큰 추가 } - // 요청 정보 로그 - // console.log('[axios request] url:', config.url); - // console.log('[axios request] headers:', config.headers); return config; }, (error) => Promise.reject(error), @@ -34,9 +31,6 @@ instance.interceptors.request.use( // 응답 인터셉터: 401(accessToken 만료) → 토큰 재발급 후 재요청 instance.interceptors.response.use( (response) => { - // 응답 로그 - // console.log('[axios response] url:', response.config.url); - // console.log('[axios response] status:', response.status); return response; }, async (error) => { @@ -44,6 +38,7 @@ instance.interceptors.response.use( // 401 에러이면서 아직 재시도 하지 않은 요청만 처리 if (error.response && error.response.status === 401 && !originalRequest._retry) { originalRequest._retry = true; + const { setAccessToken, clearAccessToken } = useAuthStore.getState(); try { // 토큰 재발급 요청 const refreshResponse = await axios.post( @@ -57,21 +52,16 @@ instance.interceptors.response.use( }, }, ); - //재발급 응답 헤더 로그 - //console.log('refreshResponse.headers:', refreshResponse.headers); //authorization, Authorization 대소문자 상관없시 추출하도록함 //새 accessToken을 헤더에서 추출 - let newAccessToken = refreshResponse.headers['authorization']; - //새 accessTokem 로그 - //console.log('newAccessToken:', newAccessToken); - if (!newAccessToken) { - newAccessToken = refreshResponse.headers['Authorization']; - } + let newAccessToken = + refreshResponse.headers['authorization'] || refreshResponse.headers['Authorization']; + // newAccessToken이 있으면, 'Bearer ' 접두사 제거 후 공백 제거 후 AsyncStorage에 저장 if (newAccessToken) { const tokenValue = newAccessToken.replace('Bearer ', '').trim(); - await AsyncStorage.setItem('accessToken', tokenValue); + setAccessToken(tokenValue); // 기존 헤더는 spread로 보존, Authorization만 교체 originalRequest.headers = { @@ -90,20 +80,13 @@ instance.interceptors.response.use( // 서버가 토큰 바로 반영 안 할 때를 대비해 약간 딜레이 await new Promise((resolve) => setTimeout(resolve, 200)); - - // 재요청 전 로그 - //console.log('재요청 config:', originalRequest); - //새 accessToken으로 원래 요청을 재시도 return instance(originalRequest); } //새 accessToken을 받지 못한 경우 에러 반환 return Promise.reject(new Error('새로운 accessToken을 받지 못했습니다.')); } catch (refreshError) { - //토큰 재발급 실패 시 - await AsyncStorage.removeItem('accessToken'); - // 로그아웃 콜백 실행 - runLogoutCallback(); + clearAccessToken(); return Promise.reject(refreshError); //에러 반환 } } diff --git a/app.config.js b/app.config.js index ff3e665..23490b4 100644 --- a/app.config.js +++ b/app.config.js @@ -25,8 +25,8 @@ export default { favicon: './assets/images/logoIcon.png', }, extra: { - BASE_URL: 'http://keywe.site', // EKS 사용시 - //BASE_URL: 'http://192.168.0.181:8081', // 도커 사용시 - 본인 pc IPv4 주소로 수정하세용 + //BASE_URL: 'http://keywe.site', // EKS 사용시 + BASE_URL: 'http://192.168.0.225:8081', // 도커 사용시 - 본인 pc IPv4 주소로 수정하세용 }, }, }; diff --git a/components/accessRequest/GuardianVerificationForm.js b/components/accessRequest/GuardianVerificationForm.js index 28c658c..1bf805e 100644 --- a/components/accessRequest/GuardianVerificationForm.js +++ b/components/accessRequest/GuardianVerificationForm.js @@ -4,8 +4,10 @@ import { useState } from 'react'; import NormalInput from '../textinputs/NormalInput'; import NormalAlert from '../alerts/NormalAlert'; import { verifyPatientCode } from '../../apis/AccessRequestApi'; +import { useAuthStore } from '../../stores/authStore'; const GuardianVerificationForm = ({ onVerifiedHandler }) => { + const { setLoading } = useAuthStore(); const [patientCode, setPatientCode] = useState(''); // 환자 번호 관리 const [isVerified, setIsVerified] = useState(false); const [alertMessage, setAlertMessage] = useState(''); @@ -15,6 +17,7 @@ const GuardianVerificationForm = ({ onVerifiedHandler }) => { // 환자 번호 검증 버튼 클릭 핸들러 const handleVerifyPatient = async () => { + setLoading(true); try { await verifyPatientCode(patientCode); @@ -29,6 +32,8 @@ const GuardianVerificationForm = ({ onVerifiedHandler }) => { setAlertMessage(`일치하는 환자 정보가\n존재하지 않습니다.\n확인 후 다시 입력해 주세요.`); setShowVerifiedAlert(true); setPatientCode(''); + } finally { + setLoading(false); } }; diff --git a/components/accessRequest/PatientVerficationForm.js b/components/accessRequest/PatientVerficationForm.js index 207f08a..9eb3537 100644 --- a/components/accessRequest/PatientVerficationForm.js +++ b/components/accessRequest/PatientVerficationForm.js @@ -5,8 +5,10 @@ import { useState, useEffect } from 'react'; import NormalInput from '../textinputs/NormalInput'; import NormalAlert from '../alerts/NormalAlert'; import { getMyInfo } from '../../apis/MyPageApi'; +import { useAuthStore } from '../../stores/authStore'; const PatientVerficationForm = ({ onVerifiedHandler }) => { + const { setLoading } = useAuthStore(); const [userInfo, setUserInfo] = useState({ name: '', birth: '', contact: '' }); // 회원 정보 관리 const [isVerified, setIsVerified] = useState(false); const [alertMessage, setAlertMessage] = useState(''); @@ -17,6 +19,7 @@ const PatientVerficationForm = ({ onVerifiedHandler }) => { // 사용자 정보 불러오기 useEffect(() => { const loadInfo = async () => { + setLoading(true); try { const data = await getMyInfo(); setUserInfo({ @@ -26,6 +29,8 @@ const PatientVerficationForm = ({ onVerifiedHandler }) => { }); } catch (error) { console.log('내 정보 조회 실패:', error.response?.data || error.message); + } finally { + setLoading(false); } }; @@ -33,6 +38,7 @@ const PatientVerficationForm = ({ onVerifiedHandler }) => { }, []); const handleVerifyPatient = async () => { + setLoading(true); // TODO: 환자 번호 검증 API 연결 // 임시 검증 로직 try { @@ -53,6 +59,8 @@ const PatientVerficationForm = ({ onVerifiedHandler }) => { } catch (error) { setIsVerified(false); setShowVerifiedAlert(true); + } finally { + setLoading(false); } }; diff --git a/handler/logoutHandler.js b/handler/logoutHandler.js deleted file mode 100644 index 471cd66..0000000 --- a/handler/logoutHandler.js +++ /dev/null @@ -1,11 +0,0 @@ -// 콜백 함수 저장하는 변수 -let logoutCallback = null; - -export function setLogoutCallback(cb) { - logoutCallback = cb; -} - -// 설정된 콜백 함수를 실행하는 함수 -export function runLogoutCallback() { - if (logoutCallback) logoutCallback(); -} diff --git a/modals/PasswordConfirmModal.js b/modals/PasswordConfirmModal.js index d25784c..aaf3d9f 100644 --- a/modals/PasswordConfirmModal.js +++ b/modals/PasswordConfirmModal.js @@ -5,7 +5,6 @@ import NormalInput from '../components/textinputs/NormalInput'; import NormalButton from '../components/buttons/NormalButton'; import WaveHeader from '../components/headers/WaveHeader'; import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'; -import AsyncStorage from '@react-native-async-storage/async-storage'; import { verifyPassword } from '../apis/PasswordApi'; const PasswordConfirmModal = ({ visible = true, onCloseHandler }) => { @@ -34,8 +33,6 @@ const PasswordConfirmModal = ({ visible = true, onCloseHandler }) => { } try { - // 토큰 불러오기 - const token = await AsyncStorage.getItem('accessToken'); await verifyPassword(password); // 모달창 닫기 @@ -45,6 +42,7 @@ const PasswordConfirmModal = ({ visible = true, onCloseHandler }) => { setErrorText('비밀번호가 일치하지 않습니다. 다시 입력해주세요.'); } }; + // 모달 오류시 임시 코드 // const handleConfirm = async () => { // if (!isValidPassword(password)) { // setErrorText('8자 이상, 영문/숫자/특수문자를 포함해야 합니다.'); diff --git a/package-lock.json b/package-lock.json index 3ee9b1c..d1d6f90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,8 @@ "react-native-reanimated": "~3.16.1", "react-native-safe-area-context": "^4.12.0", "react-native-screens": "~4.4.0", - "react-native-vector-icons": "^10.2.0" + "react-native-vector-icons": "^10.2.0", + "zustand": "^5.0.4" }, "devDependencies": { "@babel/core": "^7.20.0", @@ -13213,6 +13214,35 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.4.tgz", + "integrity": "sha512-39VFTN5InDtMd28ZhjLyuTnlytDr9HfwO512Ai4I8ZABCoyAj4F1+sr7sD1jP/+p7k77Iko0Pb5NhgBFDCX0kQ==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 634721a..8102387 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "react-native-reanimated": "~3.16.1", "react-native-safe-area-context": "^4.12.0", "react-native-screens": "~4.4.0", - "react-native-vector-icons": "^10.2.0" + "react-native-vector-icons": "^10.2.0", + "zustand": "^5.0.4" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/pages/AccessRequestPage.js b/pages/AccessRequestPage.js index d584740..0eb83fc 100644 --- a/pages/AccessRequestPage.js +++ b/pages/AccessRequestPage.js @@ -4,14 +4,17 @@ import { styles } from './styles/AccessRequestPage.styles'; import NormalInput from '../components/textinputs/NormalInput'; import NormalList from '../components/lists/NormalList'; import { getHospitalList } from '../apis/AccessRequestApi'; +import { useAuthStore } from '../stores/authStore'; const AccessRequestPage = () => { + const { setLoading } = useAuthStore(); const [searchText, setSearchText] = useState(''); const [hospitalName, setHospitalName] = useState([]); // 병원 목록 불러오기 useEffect(() => { const getHospitalsName = async () => { + setLoading(true); try { const data = await getHospitalList(); console.log(data); @@ -19,9 +22,10 @@ const AccessRequestPage = () => { setHospitalName(data); } catch (error) { console.error('병원 목록 불러오기 실패:', error); + } finally { + setLoading(false); } }; - getHospitalsName(); }, []); diff --git a/pages/AccessRequestRolePage.js b/pages/AccessRequestRolePage.js index 4c81295..5772caa 100644 --- a/pages/AccessRequestRolePage.js +++ b/pages/AccessRequestRolePage.js @@ -10,8 +10,10 @@ import PatientVerficationForm from '../components/accessRequest/PatientVerficati import GuardianVerificationForm from '../components/accessRequest/GuardianVerificationForm'; import { useNavigation } from '@react-navigation/native'; import { getAvailableDates } from '../apis/AccessRequestApi'; +import { useAuthStore } from '../stores/authStore'; const AccessRequestRolePage = ({ route }) => { + const { setLoading } = useAuthStore(); const { hospitalId, hospitalName } = route.params; const [role, setRole] = useState('patient'); @@ -23,11 +25,14 @@ const AccessRequestRolePage = ({ route }) => { // 방문 가능 날짜 불러오기 useEffect(() => { const fetchAvailableDates = async () => { + setLoading(true); try { const dates = await getAvailableDates(hospitalId); setAvailableDates(dates); } catch (error) { console.error('방문 가능 날짜 불러오기 실패:', error); + } finally { + setLoading(false); } }; diff --git a/pages/ChangePasswordPage.js b/pages/ChangePasswordPage.js index f87b793..3f9217c 100644 --- a/pages/ChangePasswordPage.js +++ b/pages/ChangePasswordPage.js @@ -8,9 +8,10 @@ import { useNavigation } from '@react-navigation/native'; import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'; import NormalAlert from '../components/alerts/NormalAlert'; import { updatePassword } from '../apis/PasswordApi'; -import AsyncStorage from '@react-native-async-storage/async-storage'; +import { useAuthStore } from '../stores/authStore'; const ChangePasswordPage = () => { + const { setLoading } = useAuthStore(); const [originalPassword, setOriginalPassword] = useState(''); // 기존 비밀번호 const [newPassword, setNewPassword] = useState(''); // 새 비밀번호 const [confirmNewPassword, setConfirmNewPassword] = useState(''); // 새 비밀번호 확인 @@ -48,9 +49,8 @@ const ChangePasswordPage = () => { // 비밀번호 변경 확인 버튼 클릭 핸들러 const handleConfirmChange = async () => { setShowConfirmAlert(false); + setLoading(true); try { - // 토큰 불러오기 - const token = await AsyncStorage.getItem('accessToken'); await updatePassword({ originalPassword, newPassword }); setTimeout(() => { @@ -68,6 +68,8 @@ const ChangePasswordPage = () => { setErrorAlertMessage(message); setShowErrorAlert(true); + } finally { + setLoading(false); } }; diff --git a/pages/LoginPage.js b/pages/LoginPage.js index bdc2672..a5b852a 100644 --- a/pages/LoginPage.js +++ b/pages/LoginPage.js @@ -8,11 +8,11 @@ import GrayUnderlineButton from '../components/buttons/GrayButton'; import { useNavigation } from '@react-navigation/native'; import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'; import NormalAlert from '../components/alerts/NormalAlert'; -import AsyncStorage from '@react-native-async-storage/async-storage'; import { loginUser } from '../apis/LoginApi'; -import LoadingOverlay from '../components/loadings/LoadingOverlay'; +import { useAuthStore } from '../stores/authStore'; -const LoginPage = ({ setIsLoggedIn }) => { +const LoginPage = () => { + const { setIsLoggedIn, setLoading, setOnlyAccessToken } = useAuthStore(); //상태 변수 const [form, setForm] = useState({ email: '', // 이메일 @@ -20,7 +20,7 @@ const LoginPage = ({ setIsLoggedIn }) => { }); // Alert 관리 상태변수 - const [showAlert, setShowAlert] = useState(null); + const [showAlert, setShowAlert] = useState(false); const [showErrorAlert, setShowErrorAlert] = useState(false); //이메일 형식 검증 함수 @@ -34,7 +34,6 @@ const LoginPage = ({ setIsLoggedIn }) => { const [error, setError] = useState({}); // 에러 메시지 const [isPwValid, setIsPwValid] = useState(false); //비밀번호 유효성 - const [loading, setLoading] = useState(false); // 토큰 확인 중 상태 const handleInputChange = (field, value) => { // field : 바꿀 필드의 이름 (ex. name), value : 입력된 새로운 값 @@ -70,9 +69,9 @@ const LoginPage = ({ setIsLoggedIn }) => { try { //로그인 API 연결 const data = await loginUser(form); + //토큰 있어야만 저장하도록함 if (data && data.data.accessToken) { - //토큰 있어야만 저장하도록함 - await AsyncStorage.setItem('accessToken', data.data.accessToken); + setOnlyAccessToken(data.data.accessToken); setShowAlert(true); } else { setShowErrorAlert(true); @@ -100,7 +99,6 @@ const LoginPage = ({ setIsLoggedIn }) => { return ( <> - { + const { setLoading } = useAuthStore(); const [myAccessList, setMyAccessList] = useState([]); // Alert 관리 상태변수 @@ -16,15 +18,17 @@ const MyAccessListPage = () => { // 출입증 목록 불러오기 useEffect(() => { const getMyAccessList = async () => { + setLoading(true); try { const data = await getAccessList(); console.log(data.data.data); setMyAccessList(data.data.data); } catch (error) { console.error('출입증 목록 불러오기 실패: ', error); + } finally { + setLoading(false); } }; - getMyAccessList(); }, []); diff --git a/pages/MyPage.js b/pages/MyPage.js index 9c94625..0978053 100644 --- a/pages/MyPage.js +++ b/pages/MyPage.js @@ -8,9 +8,10 @@ import GrayButton from '../components/buttons/GrayButton'; import { useNavigation } from '@react-navigation/native'; import NormalAlert from '../components/alerts/NormalAlert'; import { deleteUser, getMyInfo, logoutUser } from '../apis/MyPageApi'; -import AsyncStorage from '@react-native-async-storage/async-storage'; +import { useAuthStore } from '../stores/authStore'; -export default function MyPage({ setIsLoggedIn }) { +export default function MyPage() { + const { accessToken, clearAccessToken } = useAuthStore(); const [isVerified, setIsVerified] = useState(false); // 비밀번호 인증 여부 const [isModalVisible, setIsModalVisible] = useState(true); @@ -28,8 +29,6 @@ export default function MyPage({ setIsLoggedIn }) { email: '', }); - const [accessToken, setAccessToken] = useState(null); // 사용자 토큰 - const navigation = useNavigation(); // 인증 완료 시, 모달 창 닫음 @@ -39,14 +38,9 @@ export default function MyPage({ setIsLoggedIn }) { // 회원 정보 불러오기 try { - // 토큰 불러오기 - const token = await AsyncStorage.getItem('accessToken'); - if (!token) { + if (!accessToken) { throw new Error('토큰이 존재하지 않습니다.'); } - - setAccessToken(token); - const data = await getMyInfo(); setUserInfo({ name: data.name, @@ -79,10 +73,8 @@ export default function MyPage({ setIsLoggedIn }) { // 로그아웃 처리 try { await logoutUser(); - // 토큰 삭제 - await AsyncStorage.removeItem('accessToken'); - // 시작 페이지로 이동되도록 로그인 상태 설정 - setIsLoggedIn(false); + // 토큰 삭제, 시작 페이지로 이동되도록 로그인 상태 설정 + clearAccessToken(); } catch (error) { console.log('내 정보 조회 실패:', error); } @@ -108,10 +100,8 @@ export default function MyPage({ setIsLoggedIn }) { // 회원 탈퇴 처리 try { await deleteUser(); - // 토큰 삭제 - await AsyncStorage.removeItem('accessToken'); - // 시작 페이지로 이동되도록 로그인 상태 설정 - setIsLoggedIn(false); + // 토큰 삭제, 시작 페이지로 이동되도록 로그인 상태 설정 + clearAccessToken(); } catch (error) { console.log('회원 탈퇴 실패:', error); } diff --git a/pages/SignUpPage.js b/pages/SignUpPage.js index dba8965..e23f139 100644 --- a/pages/SignUpPage.js +++ b/pages/SignUpPage.js @@ -9,7 +9,7 @@ import { useNavigation, useRoute } from '@react-navigation/native'; import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'; import NormalAlert from '../components/alerts/NormalAlert'; import { createMemberInfo } from '../apis/SignUpApi'; -import LoadingOverlay from '../components/loadings/LoadingOverlay'; +import { useAuthStore } from '../stores/authStore'; // 주민등록번호에서 생년월일 추출 (YYMMDD + 성별코드로 19/20세기 구분) const getBirthDateFromRRN = (rrn) => { @@ -35,6 +35,7 @@ const getBirthDateFromRRN = (rrn) => { }; const SignUpPage = () => { + const { setLoading } = useAuthStore(); const navigation = useNavigation(); const route = useRoute(); @@ -66,7 +67,6 @@ const SignUpPage = () => { const [error, setError] = useState({}); // 에러 메시지 const [isPwValid, setIsPwValid] = useState(false); //비밀번호 유효성 const [isPwMatch, setIsPwMatch] = useState(false); //비밀번호 일치성 - const [loading, setLoading] = useState(false); // 토큰 확인 중 상태 //공통 핸들러 - 입력값 변경을 처리 const handleInputChange = (field, value) => { @@ -143,7 +143,6 @@ const SignUpPage = () => { return ( <> - ({ + //토큰 상태 관리 + accessToken: null, + setAccessToken: (token) => + set({ + accessToken: token, + isLoggedIn: !!token, + }), + //토큰 값만 설정할 때 사용 + setOnlyAccessToken: (token) => + set({ + accessToken: token, + }), + clearAccessToken: () => + set({ + accessToken: null, + isLoggedIn: false, + }), + + // 로그인 상태 관리 + isLoggedIn: false, + setIsLoggedIn: (value) => set({ isLoggedIn: value }), + + // 로딩 상태 관리 + loading: false, + setLoading: (value) => set({ loading: value }), + }), + { + name: 'auth-storage', + storage: createJSONStorage(() => AsyncStorage), + whitelist: ['accessToken', 'isLoggedIn'], + }, + ), +);