Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
69835fc
<fe> <feat> : App 컴포넌트 수정: 인증 초기화 기능 추가 및 북마크 로드 주석 처리
benidene Jul 10, 2025
cac4a6b
<fe> <feat>: RootStackNavigator 수정: 인증 상태에 따라 화면 전환 로직 추가
benidene Jul 10, 2025
d46e989
<fe> <feat>: 북마크 화면 수정: 북마크 질문 로딩 방식 변경
benidene Jul 10, 2025
8b00d17
<fe> <feat>: Header 컴포넌트 수정: 사용자 이름 로딩 방식 변경 및 인증 상태 반영
benidene Jul 10, 2025
83b0d9f
<fe> <refactor> : 북마크 기능 개선: 북마크 추가 및 제거 로직 수정
benidene Jul 10, 2025
2d4143c
<fe> <feat>: 로그아웃 및 회원 탈퇴 기능 개선
benidene Jul 10, 2025
d14acf8
<fe> <feat>: AuthHomeScreen 수정: 로그인 상태에 따른 JWT 체크 로직 개선
benidene Jul 10, 2025
89d6c64
<fe> <feat>: 구글 및 카카오 로그인 기능 개선
benidene Jul 10, 2025
7dfbd26
<fe> <refactor> : 비로그인 상태에서 제거할 저장소 키 변경
benidene Jul 10, 2025
ccec6dc
<fe> <refactor> : 로그인 상태에 따른 화면 전환 로직 추가
benidene Jul 10, 2025
fa5ff7e
<fe> <refactor> : API 요청 시 토큰 자동 갱신 로직 추가 및 헤더 생성 함수 통합
benidene Jul 10, 2025
4cc3d5e
<fe> <refactor> : encryptStorage 키 추가 및 변경
benidene Jul 10, 2025
7ea3fe8
<fe> <refactor> : 잘못된 질문 데이터 자동 갱신 로직 추가
benidene Jul 10, 2025
f9df79f
<fe> <refactor> : 북마크 상태 관리 개선 및 API 연동 추가
benidene Jul 10, 2025
87b595a
<fe> <feat> : 인증 상태 관리 스토어 추가
benidene Jul 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions front/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { StatusBar } from 'react-native';
import useThemeStorage from './src/hooks/useThemeStorage';
import { colors } from './src/constants/colors';
import { useBookmarkStore } from './src/store/useBookmarkStore';
// import { useBookmarkStore } from './src/store/useBookmarkStore';
import SplashScreen from 'react-native-splash-screen';
import { useAuthStore } from './src/store/useAuthStore';
import { initializeAuth } from './src/util/auth';

const queryClient = new QueryClient({
defaultOptions: {
Expand All @@ -19,16 +21,19 @@ const queryClient = new QueryClient({
},
});


function App(): React.JSX.Element {
const {theme} = useThemeStorage();
const { theme } = useThemeStorage();
const { setLoggedIn } = useAuthStore();

useEffect(() => {
useBookmarkStore.getState().loadBookmarks();
initializeAuth(setLoggedIn);
// 북마크 로드와 함께 인증 초기화
// useBookmarkStore.getState().loadBookmarks();

setTimeout(() => {
SplashScreen.hide();
}, 500);
}, []);
}, [setLoggedIn]);

return (
<QueryClientProvider client={queryClient}>
Expand Down
10 changes: 8 additions & 2 deletions front/src/navigation/RootStackNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import LoginScreen from '../screens/oauth/LoginScreen';
import SelectCertificationScreen from '../screens/selectCertification/SelectCertificationScreen';
import HomeStackNavigator, { HomeStackParamList } from './HomeStackNavigator';
import { NavigatorScreenParams } from '@react-navigation/native';
import { useAuthStore } from '../store/useAuthStore';

export type RootStackParamList = {
AuthHome: undefined;
Expand All @@ -16,12 +17,17 @@ export type RootStackParamList = {
const Stack = createStackNavigator<RootStackParamList>();

function RootStackNavigator() {
const { isLoggedIn } = useAuthStore();

return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="AuthHome" component={AuthHomeScreen} />
{isLoggedIn ? (
<Stack.Screen name="HomeStack" component={HomeStackNavigator} />
) : (
<Stack.Screen name="AuthHome" component={AuthHomeScreen} />
)}
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="SelectCertification" component={SelectCertificationScreen} />
<Stack.Screen name="HomeStack" component={HomeStackNavigator} />
</Stack.Navigator>
);
}
Expand Down
36 changes: 18 additions & 18 deletions front/src/screens/bookmark/BookmarkScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React from 'react';
import React, { useState, useCallback } from 'react';
import {View, Text, ScrollView} from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useQuery } from '@tanstack/react-query';
import useThemeStore, { themeMode } from '../../store/useThemeStore';
import { colors } from '../../constants/colors';
import { HomeStackParamList } from '../../navigation/HomeStackNavigator';
Expand All @@ -11,40 +10,41 @@ import { ScaledSheet } from 'react-native-size-matters';
import { fetchGet } from '../../util/api';
import { QuestionData } from '../components/QuestionItem';
import { useBookmarkStore } from '../../store/useBookmarkStore';
import { useFocusEffect } from '@react-navigation/native';

function BookmarkScreen() {
const { theme } = useThemeStore();
const styles = styling(theme);
const navigation = useNavigation<NativeStackNavigationProp<HomeStackParamList>>();
const { bookmarks } = useBookmarkStore();
const [bookmarkQuestions, setBookmarkQuestions] = useState<QuestionData[]>([]);
console.log(bookmarks);


const { data: bookmarkQuestions } = useQuery<QuestionData[]>({
queryKey: ['bookmarkQuestions', bookmarks],
queryFn: () => fetchGet('exam/book-mark/question?certificationId=1'),
staleTime: 0,
gcTime: 0,
// refetchOnWindowFocus: true,
// refetchOnMount: true,
// refetchOnReconnect: true,
});
// 북마크 질문들을 가져오는 함수
const fetchBookmarkQuestions = useCallback(async () => {
try {
const data = await fetchGet('exam/book-mark/question?certificationId=1') as QuestionData[];
setBookmarkQuestions(data);
} catch (error) {
console.error('북마크 질문을 가져오는데 실패했습니다:', error);
setBookmarkQuestions([]);
}
}, []);

const handleQuestionSelect = (index: number) => {
// 네비게이션을 사용하여 QuestionPager 화면으로 이동
navigation.navigate('QuestionPager', {
questions: bookmarkQuestions,
currentPage: index + 1,
handlePageChange: (page: number) => console.log('페이지 변경:', page),
mode: 'bookmark',
});
};

// useFocusEffect(
// React.useCallback(() => {
// refetch();
// }, [refetch])
// );
useFocusEffect(
React.useCallback(() => {
fetchBookmarkQuestions();
}, [fetchBookmarkQuestions])
);
console.log(bookmarkQuestions);

return (
Expand Down
13 changes: 7 additions & 6 deletions front/src/screens/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,24 @@ import { useNavigation } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import useThemeStore, { themeMode } from '../../store/useThemeStore';
import { colors } from '../../constants/colors';
import { getEncryptStorage, UserKey } from '../../util/encryptStorage';
import { getEncryptStorage, UserNameKey } from '../../util/encryptStorage';
import { RootStackParamList } from '../../navigation/RootStackNavigator';

import { useAuthStore } from '../../store/useAuthStore';

function Header() {
const { theme } = useThemeStore(); // 현재 테마 가져오기
const insets = useSafeAreaInsets();
const styles = styling(theme, insets); // 테마별 스타일 적용
const [userName, setUserName] = useState<string | null>(null); // 사용자 이름을 저장할 상태 변수
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>();
const { isLoggedIn } = useAuthStore();

async function fetchUserName() {
try {
const data = await getEncryptStorage(UserKey);
const data = await getEncryptStorage(UserNameKey);

if (data && data.username) {
const originalName = data.username;
if (data) {
const originalName = data;
// '_'가 포함되어 있는지 확인하고, 포함되어 있다면 '_' 앞부분만 사용
const processedName = originalName.includes('_')
? originalName.split('_')[0]
Expand All @@ -38,7 +39,7 @@ function Header() {

useEffect(() => {
fetchUserName();
}, []);
}, [isLoggedIn]);

const backToLoginScreen = () => {
navigation.reset({
Expand Down
19 changes: 10 additions & 9 deletions front/src/screens/components/QuestionItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import FontAwesomeIcons from 'react-native-vector-icons/FontAwesome';
import useThemeStore, { themeMode } from '../../store/useThemeStore';
import { colors } from '../../constants/colors';
import { extractChoiceNumber, parseContent } from '../../constants/examParser';
import { fetchPatch, fetchPost } from '../../util/api';
import { fetchPost, fetchDelete } from '../../util/api';
import ChoiceItem from './ChoiceItem';
import { UserAnswer } from '../../screens/components/QuestionPagerScreen';
import { useBookmarkStore } from '../../store/useBookmarkStore';
Expand Down Expand Up @@ -91,23 +91,24 @@ const QuestionItem = React.memo(({
}
};

const bookmarks = useBookmarkStore((state) => state.bookmarks);
const toggleBookmark = useBookmarkStore((state) => state.toggleBookmark);
const [loading, setLoading] = useState(false);

const bookmarks = useBookmarkStore((state) => state.bookmarks);
const isBookmarked = bookmarks.includes(question.questionId);

const handleStarPress = async () => {
if (loading) {return;}
setLoading(true);
try {
// 서버에 patch 요청
await fetchPatch(`exam/book-mark?questionId=${question.questionId}`);
// zustand 상태만 변경 (EncryptedStorage 저장은 store 내부에서 처리)
console.log(question.questionId);
toggleBookmark(question.questionId);
if (isBookmarked) {
// 북마크 제거 - DELETE 요청
await fetchDelete('exam/book-mark', { questionId: question.questionId });
} else {
// 북마크 추가 - POST 요청
await fetchPost('exam/book-mark', { questionId: question.questionId });
}
} catch (e) {
Alert.alert('알림', '북마크를 추가할 수 없습니다.');
Alert.alert('알림', '북마크 처리에 실패했습니다.');
} finally {
setLoading(false);
}
Expand Down
47 changes: 28 additions & 19 deletions front/src/screens/myPage/MyPageScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,52 @@ import React, { useState } from 'react';
import { View, Text, Pressable, Alert } from 'react-native';
import { ScaledSheet } from 'react-native-size-matters';
import { useMutation } from '@tanstack/react-query';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import useThemeStore, { themeMode } from '../../store/useThemeStore';
import { colors } from '../../constants/colors';
import { fetchPost, fetchDelete } from '../../util/api';
import { RootStackParamList } from '../../navigation/RootStackNavigator';
import { removeEncryptStorage, JwtKey, UserKey } from '../../util/encryptStorage';
import { removeEncryptStorage, AccessKey, UserNameKey, UserNicknameKey, CertificationKey } from '../../util/encryptStorage';
import ThemeModal from './components/ThemeModal';
import { useAuthStore } from '../../store/useAuthStore';

type FetchMethod = typeof fetchPost | typeof fetchDelete;

type ApiRequest = {
url: string;
method: FetchMethod;
data?: any;
};

interface MyPageScreenProps {

}

function MyPageScreen({}: MyPageScreenProps) {
function MyPageScreen() {
const { theme } = useThemeStore();
const styles = styling(theme);
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
const [isModalVisible, setIsModalVisible] = useState(false);
const { setLoggedIn, isLoggedIn } = useAuthStore();

const apiMutation = useMutation({
mutationFn: (request: ApiRequest) => {
return request.method(request.url, {});
return request.method(request.url, request.data);
},
onSuccess: () => {
removeEncryptStorage(JwtKey);
removeEncryptStorage(UserKey);
navigation.reset({
index: 0,
routes: [{ name: 'AuthHome' }],
});
// 저장소에서 데이터 제거
removeEncryptStorage(AccessKey);
removeEncryptStorage(UserNameKey);
removeEncryptStorage(UserNicknameKey);
removeEncryptStorage(CertificationKey);

// 로그인 상태 변경 (이렇게 하면 RootStackNavigator가 자동으로 AuthHome을 렌더링)
setLoggedIn(false);
},
onError: () => {
onError: (error) => {
console.log('로그아웃 실패', error);
},
});

const onLogout = () => {
if (!isLoggedIn) {
Alert.alert('알림', '로그인 상태가 아닙니다');
return;
}

Alert.alert('로그아웃', '로그아웃하시겠습니까?', [
{
text: '아니오',
Expand All @@ -54,13 +57,19 @@ function MyPageScreen({}: MyPageScreenProps) {
{
text: '예',
onPress: () => {
apiMutation.mutate({ url: 'user/logout', method: fetchPost });
// 빈 객체라도 body로 전송
apiMutation.mutate({ url: 'user/logout', method: fetchPost, data: {} });
},
},
]);
};

const onDelete = () => {
if (!isLoggedIn) {
Alert.alert('알림', '로그인 상태가 아닙니다');
return;
}

Alert.alert('회원 탈퇴', '정말로 회원을 탈퇴하시겠습니까?', [
{
text: '아니오',
Expand Down
29 changes: 14 additions & 15 deletions front/src/screens/oauth/AuthHomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ import SelectCertificationScreen from '../selectCertification/SelectCertificatio
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { RootStackParamList } from '../../navigation/RootStackNavigator';
import { getEncryptStorage, JwtKey } from '../../util/encryptStorage';
import { AccessKey, getEncryptStorage } from '../../util/encryptStorage';
import { useAuthStore } from '../../store/useAuthStore';

export interface LoginUserResponse {
token: string;
refresh: string;
user: {
nickname: string;
userId: number;
userName: string;
provider: string;
username: string;
}
}

Expand All @@ -26,27 +28,24 @@ function AuthHomeScreen() {
const insets = useSafeAreaInsets();
const styles = styling(theme, insets);
const [currentPage, setCurrentPage] = useState(0);
const { isLoggedIn } = useAuthStore();

const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList, 'Login'>>();

useEffect(() => {
const checkJwt = async () => {
try {
const jwt = await getEncryptStorage(JwtKey);
if (jwt) {
// 토큰이 있으면 바로 홈 화면으로 이동
// certifications 요청은 해당 화면에서 자동으로 이루어짐
navigation.navigate('HomeStack');
// certifications 요청이 실패하면 해당 화면의 오류 처리에서
// 토큰 관련 오류 처리가 자동으로 이루어질 것임
}
} catch (error) {
console.error('토큰 확인 오류:', error);
if (!isLoggedIn) {
return; // 로그아웃 상태면 체크하지 않음
}

const jwt = await getEncryptStorage(AccessKey);
if (jwt) {
navigation.navigate('HomeStack');
}
};

checkJwt();
}, [navigation]);
}, [navigation, isLoggedIn]);

const handleLoginSuccess = useCallback(() => {
setCurrentPage(1);
Expand Down
Loading
Loading