1
0
forked from baron/baron-sso

내정보 페이지 사용성개선, adminFront user 정보 연동.

This commit is contained in:
Lectom C Han
2026-01-30 13:42:41 +09:00
parent 1cb5115f2a
commit 35552943d7
29 changed files with 1586 additions and 472 deletions

View File

@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../../core/services/auth_proxy_service.dart';
class ForgotPasswordScreen extends StatefulWidget {
@@ -91,7 +90,7 @@ class _ForgotPasswordScreenState extends State<ForgotPasswordScreen> {
children: [
Text(
"비밀번호를 잊으셨나요?",
style: GoogleFonts.outfit(
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
),

View File

@@ -2,7 +2,6 @@ import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:descope/descope.dart';
import 'package:go_router/go_router.dart';
import 'package:url_launcher/url_launcher_string.dart';
@@ -47,6 +46,8 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
int _linkExpireSeconds = 0;
Timer? _linkExpireTimer;
bool _verificationOnly = false;
bool _verificationApproved = false;
String _verificationMessage = '';
bool _drySendEnabled = false;
@override
@@ -352,15 +353,72 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
_onLoginSuccess(token, provider: provider);
}
bool _hasLocalSession() {
if (AuthTokenStore.getToken() != null) {
return true;
}
return AuthTokenStore.usesCookie();
}
void _markVerificationApproved(String message) {
if (!mounted) return;
setState(() {
_verificationApproved = true;
_verificationMessage = message;
});
}
Widget _buildVerificationResultView() {
return Center(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.check_circle_outline, color: Colors.green, size: 72),
const SizedBox(height: 16),
const Text(
'승인 완료',
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.green),
),
const SizedBox(height: 12),
Text(
_verificationMessage.isEmpty ? '로그인 승인에 성공했습니다.' : _verificationMessage,
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.black54),
),
const SizedBox(height: 24),
FilledButton(
onPressed: () => context.go('/'),
child: const Text('확인'),
),
],
),
),
);
}
Future<void> _verifyToken(String token) async {
debugPrint("[Auth] Starting verification for token: $token");
try {
// Use Backend to verify the token (Backend-Driven Flow)
await AuthProxyService.verifyMagicLink(token);
final res = await AuthProxyService.verifyMagicLink(token);
debugPrint("[Auth] Verification successful for token: $token");
final jwt = res['token'] ?? res['sessionJwt'];
final provider = res['provider'] as String?;
final hasLocalSession = _hasLocalSession();
if (jwt is String && jwt.isNotEmpty) {
if (hasLocalSession) {
_markVerificationApproved("승인되었습니다. 이미 로그인된 브라우저입니다.");
return;
}
_completeLoginFromToken(jwt, provider: provider);
return;
}
if (mounted) {
_showInfo("승인되었습니다. 로그인은 요청하신 창에서 완료됩니다.");
_markVerificationApproved("승인되었습니다. 로그인은 요청하신 창에서 완료됩니다.");
}
} catch (e) {
debugPrint("[Auth] Verification FAILED for token: $token. Error: $e");
@@ -382,23 +440,26 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
final jwt = res['sessionJwt'] ?? res['token'];
final status = res['status']?.toString();
debugPrint("[Auth] Code verification successful for loginId: $sanitizedLoginId");
final hasLocalSession = _hasLocalSession();
if (jwt == null && status == 'approved') {
if (mounted) {
_showInfo("승인되었습니다. 로그인은 요청하신 창에서 완료됩니다.");
}
return;
}
if (_verificationOnly) {
if (mounted) {
_showInfo("승인되었습니다. 로그인은 요청하신 창에서 완료됩니다.");
_markVerificationApproved("승인되었습니다. 로그인은 요청하신 창에서 완료됩니다.");
}
return;
}
if (jwt is String && jwt.isNotEmpty) {
if (hasLocalSession) {
_markVerificationApproved("승인되었습니다. 이미 로그인된 브라우저입니다.");
return;
}
_completeLoginFromToken(jwt, provider: res['provider'] as String?);
return;
}
if (_verificationOnly && mounted) {
_markVerificationApproved("승인되었습니다. 로그인은 요청하신 창에서 완료됩니다.");
}
} catch (e) {
debugPrint("[Auth] Code verification FAILED for loginId: $sanitizedLoginId. Error: $e");
@@ -417,23 +478,26 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
final jwt = res['sessionJwt'] ?? res['token'];
final status = res['status']?.toString();
debugPrint("[Auth] Short code verification successful");
final hasLocalSession = _hasLocalSession();
if (jwt == null && status == 'approved') {
if (mounted) {
_showInfo("승인되었습니다. 로그인은 요청하신 창에서 완료됩니다.");
}
return;
}
if (_verificationOnly) {
if (mounted) {
_showInfo("승인되었습니다. 로그인은 요청하신 창에서 완료됩니다.");
_markVerificationApproved("승인되었습니다. 로그인은 요청하신 창에서 완료됩니다.");
}
return;
}
if (jwt is String && jwt.isNotEmpty) {
if (hasLocalSession) {
_markVerificationApproved("승인되었습니다. 이미 로그인된 브라우저입니다.");
return;
}
_completeLoginFromToken(jwt, provider: res['provider'] as String?);
return;
}
if (_verificationOnly && mounted) {
_markVerificationApproved("승인되었습니다. 로그인은 요청하신 창에서 완료됩니다.");
}
} catch (e) {
debugPrint("[Auth] Short code verification FAILED. Error: $e");
@@ -784,6 +848,19 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
@override
Widget build(BuildContext context) {
if (_verificationOnly && _verificationApproved) {
return Scaffold(
appBar: AppBar(
title: const Text('로그인 승인'),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => context.go('/'),
),
),
body: _buildVerificationResultView(),
);
}
return Scaffold(
body: LayoutBuilder(
builder: (context, constraints) {
@@ -800,7 +877,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen>
children: [
Text(
"Baron 통합로그인",
style: GoogleFonts.outfit(
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
),

View File

@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
class LoginSuccessScreen extends StatelessWidget {
const LoginSuccessScreen({super.key});
@@ -18,7 +17,7 @@ class LoginSuccessScreen extends StatelessWidget {
const SizedBox(height: 24),
Text(
"로그인 완료",
style: GoogleFonts.outfit(
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
),

View File

@@ -206,7 +206,7 @@ class _QRScanScreenState extends State<QRScanScreen> {
MobileScanner(
controller: controller,
onDetect: _onDetect,
errorBuilder: (context, error, child) {
errorBuilder: (context, error) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,

View File

@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:go_router/go_router.dart';
import '../../../core/services/auth_proxy_service.dart';
@@ -150,7 +149,7 @@ class _ResetPasswordScreenState extends State<ResetPasswordScreen> {
children: [
Text(
"새로운 비밀번호 설정",
style: GoogleFonts.outfit(
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
),

View File

@@ -1,7 +1,6 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:go_router/go_router.dart';
import '../../../core/services/auth_proxy_service.dart';
@@ -332,7 +331,7 @@ class _SignupScreenState extends State<SignupScreen> {
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('서비스 이용을 위해\n약관에 동의해주세요',
style: GoogleFonts.outfit(
style: TextStyle(
fontSize: 20, fontWeight: FontWeight.bold, height: 1.3)),
const SizedBox(height: 24),
// 모두 동의 버튼
@@ -597,7 +596,7 @@ class _SignupScreenState extends State<SignupScreen> {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('본인 확인을 위해\n인증을 진행해주세요', style: GoogleFonts.outfit(fontSize: 20, fontWeight: FontWeight.bold)),
Text('본인 확인을 위해\n인증을 진행해주세요', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
// 가족사 이메일 안내 문구
Container(
@@ -713,7 +712,7 @@ class _SignupScreenState extends State<SignupScreen> {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('회원님의\n소속 정보를 알려주세요', style: GoogleFonts.outfit(fontSize: 20, fontWeight: FontWeight.bold)),
Text('회원님의\n소속 정보를 알려주세요', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
const SizedBox(height: 24),
TextFormField(
controller: _nameController,
@@ -826,7 +825,7 @@ class _SignupScreenState extends State<SignupScreen> {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('마지막으로\n비밀번호를 설정해주세요', style: GoogleFonts.outfit(fontSize: 20, fontWeight: FontWeight.bold)),
Text('마지막으로\n비밀번호를 설정해주세요', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
// 비밀번호 정책 안내 박스
Container(
@@ -918,7 +917,7 @@ class _SignupScreenState extends State<SignupScreen> {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text('회원가입', style: GoogleFonts.outfit(fontWeight: FontWeight.bold)),
title: Text('회원가입', style: TextStyle(fontWeight: FontWeight.bold)),
elevation: 0,
backgroundColor: Colors.white,
foregroundColor: Colors.black,