import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../core/auth_repository.dart'; import '../../core/demo_accounts.dart'; import '../../core/session_controller.dart'; import '../../theme/toss_theme.dart'; class LoginPage extends ConsumerStatefulWidget { const LoginPage({super.key}); @override ConsumerState createState() => _LoginPageState(); } class _LoginPageState extends ConsumerState { final _email = TextEditingController(); final _password = TextEditingController(); String? _error; bool _busy = false; @override void dispose() { _email.dispose(); _password.dispose(); super.dispose(); } void _fillDemo() { setState(() { _email.text = DemoAccounts.aliceEmail; _password.text = DemoAccounts.password; _error = null; }); } Future _submit() async { setState(() { _busy = true; _error = null; }); try { final r = await ref.read(authRepositoryProvider).login( email: _email.text.trim(), password: _password.text, ); await ref.read(sessionProvider.notifier).applyLogin( userId: r.userId, email: _email.text.trim(), defaultContextId: r.defaultContextId, ); if (!mounted) return; context.go('/'); } catch (e) { setState(() => _error = e.toString()); } finally { if (mounted) setState(() => _busy = false); } } @override Widget build(BuildContext context) { final tt = Theme.of(context).textTheme; return Scaffold( body: SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const SizedBox(height: 32), Center( child: Container( width: 72, height: 72, decoration: BoxDecoration( color: TossColors.surface, borderRadius: BorderRadius.circular(22), boxShadow: [ BoxShadow( color: TossColors.blue.withValues(alpha: 0.15), blurRadius: 24, offset: const Offset(0, 8), ), ], ), child: const Icon(Icons.chat_bubble_rounded, size: 36, color: TossColors.blue), ), ), const SizedBox(height: 28), Text( 'IYKYKA', textAlign: TextAlign.center, style: tt.headlineLarge?.copyWith( letterSpacing: 0.5, fontWeight: FontWeight.w800, ), ), const SizedBox(height: 6), Text( 'If you know, you know.', textAlign: TextAlign.center, style: tt.bodySmall?.copyWith(color: TossColors.textSecondary), ), const SizedBox(height: 12), Text( '일상과 직장, 한 앱에서 나눠 쓰기', textAlign: TextAlign.center, style: tt.bodyMedium, ), const SizedBox(height: 40), Container( decoration: BoxDecoration( color: TossColors.surface, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.04), blurRadius: 16, offset: const Offset(0, 4), ), ], ), padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('데모로 시작하기', style: tt.titleMedium), const SizedBox(height: 8), Text( '서버 실행 후 아래를 누르면 필드가 채워집니다. ' '홈에서 「데모 회사」를 고르면 직장 샘플 대화를 볼 수 있어요.', style: tt.bodySmall?.copyWith(height: 1.4), ), const SizedBox(height: 16), SizedBox( width: double.infinity, child: OutlinedButton( onPressed: _busy ? null : _fillDemo, child: const Text('데모 계정으로 채우기'), ), ), ], ), ), const SizedBox(height: 28), TextField( controller: _email, decoration: const InputDecoration( labelText: '이메일', prefixIcon: Icon(Icons.mail_outline_rounded, color: TossColors.textSecondary), ), keyboardType: TextInputType.emailAddress, autofillHints: const [AutofillHints.email], ), const SizedBox(height: 12), TextField( controller: _password, decoration: const InputDecoration( labelText: '비밀번호', prefixIcon: Icon(Icons.lock_outline_rounded, color: TossColors.textSecondary), ), obscureText: true, autofillHints: const [AutofillHints.password], ), if (_error != null) ...[ const SizedBox(height: 12), Text( _error!, style: TextStyle(color: Theme.of(context).colorScheme.error, fontSize: 13), ), ], const SizedBox(height: 28), SizedBox( width: double.infinity, height: 54, child: FilledButton( onPressed: _busy ? null : _submit, child: _busy ? const SizedBox( height: 22, width: 22, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : const Text('로그인'), ), ), const SizedBox(height: 8), TextButton( onPressed: _busy ? null : () => context.go('/register'), child: const Text('계정이 없어요'), ), ], ), ), ), ); } }