Skip to content

Commit 6f0d8bc

Browse files
EM-H20claude
andcommitted
feat: 로그아웃 기능 구현 및 로그인 상태 유지 시스템 완성
✅ **핵심 기능** - 로그아웃 시 토큰 + SharedPreferences 전체 삭제 - 온보딩 상태 초기화로 재로그인 시 온보딩 재표시 - 앱 시작 시 자동 로그인 상태 체크 (토큰 존재 여부) - Mock/Real 모드 모두 지원 🔧 **구현 세부사항** 1. **AuthRepository.logout()** 개선 - FlutterSecureStorage 토큰 삭제 - SharedPreferences 전체 삭제 (온보딩 상태 포함) - Real 모드에서만 API 로그아웃 호출 (Mock 모드 스킵) 2. **LoginController.logout()** 추가 - Repository logout() 호출 - State 초기화 - 성공/실패 boolean 반환 3. **ProfilePage 로그아웃 버튼 연동** - 로그아웃 다이얼로그에서 LoginController.logout() 호출 - 성공 시 /login 페이지로 이동 - 실패 시 에러 스낵바 표시 4. **AppRouter 인증 가드 구현** - redirect에서 토큰 존재 여부 체크 - 토큰 없으면 → 온보딩 체크 → 로그인 페이지 - 토큰 있으면 → 정상 진행 - initialLocation을 /onboarding으로 변경 📱 **사용자 플로우** 1. 앱 시작 → 온보딩 체크 2. 온보딩 안 봄 → 온보딩 페이지 3. 온보딩 봤지만 토큰 없음 → 로그인 페이지 4. 토큰 있음 → 피드 페이지 5. 로그아웃 → 토큰 + 로컬 데이터 삭제 → 로그인 페이지 🎨 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent ae743f1 commit 6f0d8bc

File tree

5 files changed

+80
-15
lines changed

5 files changed

+80
-15
lines changed

sejong_catch_frontend/lib/core/config/app_router.dart

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import '../../features/search/presentation/pages/search_page.dart';
1010
import '../../features/queue/presentation/pages/queue_page.dart';
1111
import '../../features/profile/presentation/pages/profile_page.dart';
1212
import '../../features/onboarding/presentation/pages/onboarding_page.dart';
13+
import '../services/token_storage_service.dart';
14+
import '../services/onboarding_service.dart';
1315

1416
/// 🧭 세종 캐치 앱의 GoRouter 중앙 설정
1517
///
@@ -24,7 +26,7 @@ class AppRouter {
2426
// 🎯 GoRouter 인스턴스 생성 (ProviderContainer 필요)
2527
static GoRouter createRouter(ProviderContainer container) {
2628
return GoRouter(
27-
initialLocation: AppRoutes.auth, // 앱 시작 시 로그인 페이지로 이동
29+
initialLocation: AppRoutes.onboarding, // 앱 시작 시 온보딩 → redirect에서 자동 라우팅
2830

2931
routes: [
3032
// 🏠 메인 앱 ShellRoute - BottomNavigationBar 포함
@@ -128,12 +130,26 @@ class AppRouter {
128130
return null;
129131
}
130132

131-
// TODO: 1. 인증 상태 확인 (현재는 임시로 항상 인증된 것으로 처리)
132-
// final isAuthenticated = await checkAuthStatus();
133-
// if (!isAuthenticated) {
134-
// return AppRoutes.auth;
135-
// }
133+
// 1️⃣ 인증 상태 확인 (토큰 존재 여부 체크)
134+
final tokenStorage = container.read(tokenStorageServiceProvider.notifier);
135+
final accessToken = await tokenStorage.getAccessToken();
136136

137+
// 토큰이 없으면 → 온보딩 체크
138+
if (accessToken == null || accessToken.isEmpty) {
139+
// 2️⃣ 온보딩 완료 여부 확인
140+
final onboardingService = container.read(onboardingServiceProvider);
141+
final hasSeenOnboarding = await onboardingService.hasSeenOnboarding();
142+
143+
// 온보딩 안 봤으면 → 온보딩 페이지로
144+
if (!hasSeenOnboarding) {
145+
return AppRoutes.onboarding;
146+
}
147+
148+
// 온보딩 봤지만 토큰 없음 → 로그인 페이지로
149+
return AppRoutes.auth;
150+
}
151+
152+
// ✅ 토큰 있음 → 인증된 상태, 계속 진행
137153
// TODO: 3. 권한별 접근 제한 구현 (role guard)
138154

139155
return null;

sejong_catch_frontend/lib/features/auth/data/repositories/auth_repository.g.dart

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sejong_catch_frontend/lib/features/auth/presentation/controllers/login_controller.dart

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,30 @@ class LoginController extends _$LoginController {
9898
void clearError() {
9999
state = state.copyWith(error: null);
100100
}
101+
102+
/// 로그아웃
103+
///
104+
/// **동작**:
105+
/// 1. AuthRepository.logout() 호출 (토큰 삭제 + SharedPreferences 삭제)
106+
/// 2. State를 초기 상태로 리셋
107+
/// 3. Mock/Real 모드 모두 지원
108+
///
109+
/// **반환**: 성공 시 true, 실패 시 false
110+
Future<bool> logout() async {
111+
try {
112+
// 1. Repository logout 호출 (토큰 + 로컬 데이터 삭제)
113+
final authRepository = ref.read(authRepositoryProvider.notifier);
114+
await authRepository.logout();
115+
116+
// 2. State 초기화
117+
state = const LoginState();
118+
119+
// ✅ 로그아웃 성공!
120+
return true;
121+
} catch (e) {
122+
// 에러 발생 시에도 State는 초기화 (로컬 삭제가 더 중요)
123+
state = const LoginState();
124+
return false;
125+
}
126+
}
101127
}

sejong_catch_frontend/lib/features/auth/presentation/controllers/login_controller.g.dart

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sejong_catch_frontend/lib/features/profile/presentation/pages/profile_page.dart

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter_riverpod/flutter_riverpod.dart';
33
import 'package:flutter_screenutil/flutter_screenutil.dart';
4+
import 'package:go_router/go_router.dart';
45
import 'package:sejong_catch_frontend/core/theme/app_colors.dart';
56
import 'package:sejong_catch_frontend/core/theme/app_spacing.dart';
67
import 'package:sejong_catch_frontend/core/theme/app_shadows.dart';
78
import 'package:sejong_catch_frontend/core/widgets/app_divider.dart';
9+
import 'package:sejong_catch_frontend/features/auth/presentation/controllers/login_controller.dart';
810

911
/// 👤 프로필 페이지 - 사용자 정보 및 설정
1012
///
@@ -624,14 +626,35 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
624626
),
625627
),
626628
TextButton(
627-
onPressed: () {
629+
onPressed: () async {
630+
// 다이얼로그 닫기
628631
Navigator.pop(context);
629-
ScaffoldMessenger.of(context).showSnackBar(
630-
const SnackBar(
631-
content: Text('로그아웃되었습니다'),
632-
backgroundColor: AppColors.error,
633-
),
634-
);
632+
633+
// 로그아웃 처리
634+
final loginController =
635+
ref.read(loginControllerProvider.notifier);
636+
final success = await loginController.logout();
637+
638+
if (mounted) {
639+
if (success) {
640+
// 로그아웃 성공 → 로그인 페이지로 이동
641+
context.go('/login');
642+
ScaffoldMessenger.of(context).showSnackBar(
643+
const SnackBar(
644+
content: Text('로그아웃되었습니다. 다시 만나요! 👋'),
645+
backgroundColor: AppColors.success,
646+
),
647+
);
648+
} else {
649+
// 로그아웃 실패 (드물지만 방어 코드)
650+
ScaffoldMessenger.of(context).showSnackBar(
651+
const SnackBar(
652+
content: Text('로그아웃 중 문제가 발생했어요'),
653+
backgroundColor: AppColors.error,
654+
),
655+
);
656+
}
657+
}
635658
},
636659
child: Text(
637660
'로그아웃',

0 commit comments

Comments
 (0)