import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; import 'package:reactive_forms/reactive_forms.dart'; import '../../../../core/router/route_names.dart'; import '../../../../core/utils/extensions.dart'; import '../../../../shared/providers/auth_provider.dart'; import '../providers/auth_providers.dart'; class LoginForm extends ConsumerStatefulWidget { const LoginForm({super.key}); @override ConsumerState createState() => _LoginFormState(); } class _LoginFormState extends ConsumerState { late final FormGroup form; @override void initState() { super.initState(); form = FormGroup({ 'email': FormControl( validators: [Validators.required, Validators.email], ), 'password': FormControl( validators: [Validators.required, Validators.minLength(8)], ), }); } @override void dispose() { form.dispose(); super.dispose(); } Future _onSubmit() async { if (!form.valid) { form.markAllAsTouched(); return; } final email = form.control('email').value as String; final password = form.control('password').value as String; final user = await ref.read(loginNotifierProvider.notifier).login( email: email, password: password, ); if (user != null && mounted) { ref.read(authStateProvider.notifier).setUser(user); } } @override Widget build(BuildContext context) { final loginState = ref.watch(loginNotifierProvider); ref.listen(loginNotifierProvider, (_, state) { if (state.hasError) { context.showSnackBar( '로그인에 실패했습니다. 이메일과 비밀번호를 확인해주세요.', isError: true, ); } }); return ReactiveForm( formGroup: form, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: [ Text( '로그인', style: context.textTheme.headlineMedium?.copyWith( fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ), const Gap(8), Text( '계정에 로그인하세요', style: context.textTheme.bodyMedium?.copyWith( color: context.colorScheme.onSurfaceVariant, ), textAlign: TextAlign.center, ), const Gap(32), ReactiveTextField( formControlName: 'email', decoration: const InputDecoration( labelText: '이메일', hintText: 'email@example.com', prefixIcon: Icon(Icons.email_outlined), ), keyboardType: TextInputType.emailAddress, validationMessages: { 'required': (_) => '이메일을 입력해주세요', 'email': (_) => '올바른 이메일 형식이 아닙니다', }, ), const Gap(16), ReactiveTextField( formControlName: 'password', decoration: const InputDecoration( labelText: '비밀번호', hintText: '8자 이상 입력', prefixIcon: Icon(Icons.lock_outlined), ), obscureText: true, validationMessages: { 'required': (_) => '비밀번호를 입력해주세요', 'minLength': (_) => '비밀번호는 8자 이상이어야 합니다', }, ), const Gap(24), FilledButton( onPressed: loginState.isLoading ? null : _onSubmit, child: loginState.isLoading ? const SizedBox( height: 20, width: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : const Text('로그인'), ), const Gap(16), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text('계정이 없으신가요?'), TextButton( onPressed: () => context.goNamed(RouteNames.register), child: const Text('회원가입'), ), ], ), ], ), ); } }