import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/auth/auth_notifier.dart'; import '../../core/api/api_client.dart'; import '../../core/theme/app_theme.dart'; import '../../core/widgets/loading_button.dart'; class LoginScreen extends ConsumerStatefulWidget { const LoginScreen({super.key}); @override ConsumerState createState() => _LoginScreenState(); } class _LoginScreenState extends ConsumerState { final _formKey = GlobalKey(); final _userCtrl = TextEditingController(); final _passCtrl = TextEditingController(); bool _obscure = true; bool _loading = false; String? _error; @override void dispose() { _userCtrl.dispose(); _passCtrl.dispose(); super.dispose(); } Future _submit() async { if (!_formKey.currentState!.validate()) return; setState(() { _loading = true; _error = null; }); try { await ref.read(authProvider.notifier).login(_userCtrl.text.trim(), _passCtrl.text); } on ApiException catch (e) { setState(() => _error = e.message); } finally { if (mounted) setState(() => _loading = false); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColors.bg, body: SafeArea( child: Center( child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( children: [ const SizedBox(height: 24), // Logo Container( width: 72, height: 72, decoration: BoxDecoration( color: AppColors.primary, borderRadius: BorderRadius.circular(20), ), child: const Icon(Icons.bolt, color: Colors.white, size: 40), ), const SizedBox(height: 20), const Text('JARVIS', style: TextStyle(fontSize: 28, fontWeight: FontWeight.w800, color: AppColors.text)), const SizedBox(height: 4), const Text('The BRAIN', style: TextStyle(fontSize: 13, color: AppColors.textSub, letterSpacing: 1.5)), const SizedBox(height: 40), // Card Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(16), border: Border.all(color: AppColors.border), ), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Masuk ke Akun', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w700, color: AppColors.text)), const SizedBox(height: 4), const Text('Admin only', style: TextStyle(fontSize: 13, color: AppColors.textSub)), const SizedBox(height: 24), if (_error != null) ...[ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.dangerLight, borderRadius: BorderRadius.circular(8), ), child: Row( children: [ const Icon(Icons.error_outline, color: AppColors.danger, size: 16), const SizedBox(width: 8), Expanded(child: Text(_error!, style: const TextStyle(color: AppColors.danger, fontSize: 13))), ], ), ), const SizedBox(height: 16), ], const Text('Username', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: AppColors.text)), const SizedBox(height: 6), TextFormField( controller: _userCtrl, keyboardType: TextInputType.text, textInputAction: TextInputAction.next, decoration: const InputDecoration(hintText: 'Masukkan username'), validator: (v) => (v == null || v.isEmpty) ? 'Username wajib diisi' : null, ), const SizedBox(height: 16), const Text('Password', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: AppColors.text)), const SizedBox(height: 6), TextFormField( controller: _passCtrl, obscureText: _obscure, textInputAction: TextInputAction.done, onFieldSubmitted: (_) => _submit(), decoration: InputDecoration( hintText: 'Masukkan password', suffixIcon: IconButton( onPressed: () => setState(() => _obscure = !_obscure), icon: Icon(_obscure ? Icons.visibility_off_outlined : Icons.visibility_outlined, color: AppColors.textSub), ), ), validator: (v) => (v == null || v.isEmpty) ? 'Password wajib diisi' : null, ), const SizedBox(height: 24), LoadingButton( loading: _loading, label: 'Masuk', onPressed: _submit, ), ], ), ), ), const SizedBox(height: 32), ], ), ), ), ), ); } }