import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import '../../../../core/i18n/locale_utils.dart'; import '../../../../core/services/auth_proxy_service.dart'; import '../../../../core/services/auth_token_store.dart'; class ApproveQrScreen extends StatefulWidget { final String? pendingRef; const ApproveQrScreen({super.key, this.pendingRef}); @override State createState() => _ApproveQrScreenState(); } class _ApproveQrScreenState extends State { bool _isLoading = false; String? _message; bool _success = false; bool _isCheckingSession = false; bool _redirectingToLogin = false; bool _autoApproveTriggered = false; @override void initState() { super.initState(); _bootstrapCookieSession().then((_) { _redirectIfNotLoggedIn(); _maybeAutoApprove(); }); } Future _bootstrapCookieSession() async { if (AuthTokenStore.usesCookie()) { return true; } if (_isCheckingSession) { return false; } setState(() => _isCheckingSession = true); try { await AuthProxyService.checkCookieSession(); AuthTokenStore.setCookieMode(provider: 'ory'); return true; } catch (_) { return false; } finally { if (mounted) { setState(() => _isCheckingSession = false); } } } void _redirectIfNotLoggedIn() { if (_redirectingToLogin || !mounted) return; final hasStoredToken = AuthTokenStore.getToken()?.isNotEmpty ?? false; final usesCookie = AuthTokenStore.usesCookie(); final isLoggedIn = hasStoredToken || usesCookie; if (!isLoggedIn) { _redirectingToLogin = true; WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; final target = buildLocalizedSigninPath(Uri.base); context.go('$target?notice=qr_login_required'); }); } } void _maybeAutoApprove() { if (!mounted || _autoApproveTriggered) return; if (widget.pendingRef == null || widget.pendingRef!.trim().isEmpty) { if (_message == null) { WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; setState(() { _message = 'Error: pendingRef is missing.'; }); }); } return; } final hasStoredToken = AuthTokenStore.getToken()?.isNotEmpty ?? false; final usesCookie = AuthTokenStore.usesCookie(); final isLoggedIn = hasStoredToken || usesCookie || _isCheckingSession; if (!isLoggedIn || _isLoading || _success) { return; } _autoApproveTriggered = true; _handleApprove(); } Future _handleApprove() async { if (widget.pendingRef == null) return; final storedToken = AuthTokenStore.getToken(); final usesCookie = AuthTokenStore.usesCookie(); var hasCookie = usesCookie; if (storedToken == null && !hasCookie) { hasCookie = await _bootstrapCookieSession(); } if (storedToken == null && !hasCookie) { if (mounted) { final target = buildLocalizedSigninPath(Uri.base); context.go('$target?notice=qr_login_required'); } return; } setState(() { _isLoading = true; _message = null; }); // jwt 유효성 확인 try { final token = storedToken ?? ''; await AuthProxyService.approveQrLogin( widget.pendingRef!, token: token, withCredentials: hasCookie, ); setState(() { _success = true; _message = "Login Approved! Your browser should now be logged in."; }); // Automatically go to dashboard after a short delay Future.delayed(const Duration(seconds: 1), () { if (mounted) context.go(buildLocalizedHomePath(Uri.base)); }); } catch (e) { setState(() => _message = "Error: $e"); } finally { setState(() => _isLoading = false); } } @override Widget build(BuildContext context) { final hasStoredToken = AuthTokenStore.getToken()?.isNotEmpty ?? false; final usesCookie = AuthTokenStore.usesCookie(); final isLoggedIn = hasStoredToken || usesCookie || _isCheckingSession; if (!isLoggedIn && !_redirectingToLogin) { _redirectIfNotLoggedIn(); } if (isLoggedIn && !_success && !_isLoading) { _maybeAutoApprove(); } return Scaffold( appBar: AppBar(title: const Text("QR Login Approval")), body: Center( child: Padding( padding: const EdgeInsets.all(24.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.phonelink_lock, size: 80, color: Colors.blue), const SizedBox(height: 24), const Text( "Web Login Request", style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold), ), const SizedBox(height: 16), Text( "A computer is trying to log in using this QR code.", textAlign: TextAlign.center, style: TextStyle(color: Colors.grey.shade600), ), const SizedBox(height: 40), if (_message != null) Padding( padding: const EdgeInsets.only(bottom: 20), child: Text( _message!, style: TextStyle( color: _success ? Colors.green : Colors.red, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ), ), if (_isLoading) const Padding( padding: EdgeInsets.only(bottom: 16), child: CircularProgressIndicator(), ), if (!_success && !_isLoading) Text( "Approving login request automatically...", textAlign: TextAlign.center, style: TextStyle(color: Colors.grey.shade700), ), if (!isLoggedIn && !_success) Padding( padding: const EdgeInsets.only(top: 16), child: TextButton( onPressed: () => context.go(buildLocalizedSigninPath(Uri.base)), child: const Text("Login on this device first"), ), ), if (!_success && !_isLoading && _message != null) FilledButton.icon( onPressed: !isLoggedIn ? null : () { _autoApproveTriggered = false; _handleApprove(); }, icon: const Icon(Icons.refresh), label: const Text("Retry Approval"), ), if (_success) FilledButton( onPressed: () => context.go(buildLocalizedHomePath(Uri.base)), child: const Text("Go to My Dashboard"), ), ], ), ), ), ); } }