import 'package:finnow_app/api/auth.dart'; import 'package:finnow_app/auth/model.dart'; import 'package:finnow_app/components/form_label.dart'; import 'package:finnow_app/components/title_text.dart'; import 'package:finnow_app/main.dart'; import 'package:finnow_app/util/debouncer.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; class RegisterPage extends StatelessWidget { const RegisterPage({super.key}); @override Widget build(BuildContext context) { return Material( child: Center( child: Container( constraints: const BoxConstraints(maxWidth: 300.0), padding: const EdgeInsets.all(10), child: const RegisterForm()))); } } class RegisterForm extends StatefulWidget { const RegisterForm({super.key}); @override State createState() => _RegisterFormState(); } class _RegisterFormState extends State { final formKey = GlobalKey(); final usernameTextController = TextEditingController(); final passwordTextController = TextEditingController(); var loading = false; final usernameAvailabilityDebouncer = Debouncer(); bool? usernameAvailable; var canCreateAccount = false; @override Widget build(BuildContext context) { return Form( key: formKey, child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ const TitleText('Create a Finnow Account'), const SizedBox(height: 20), const FormLabel('Username'), TextFormField( controller: usernameTextController, onChanged: (s) { formValuesUpdated(); usernameAvailabilityDebouncer .run(() => checkUsernameAvailability()); }, decoration: const InputDecoration( hintText: 'Enter username', border: OutlineInputBorder()), validator: usernameValidator, ), if (usernameAvailable != null && usernameAvailable == true) ...[ const Text('Username is available.', style: TextStyle(color: Colors.green)) ], if (usernameAvailable != null && usernameAvailable == false) ...[ const Text('Username is taken.', style: TextStyle(color: Colors.red)) ], const SizedBox( height: 10, ), const FormLabel('Password'), TextFormField( controller: passwordTextController, onChanged: (s) => formValuesUpdated(), obscureText: true, decoration: const InputDecoration( hintText: 'Enter password', border: OutlineInputBorder()), validator: passwordValidator), const SizedBox(height: 10), TextButton( onPressed: loading || !canCreateAccount ? null : createAccount, child: const Text('Create Account'), ), const SizedBox( height: 10, ), TextButton( onPressed: () { getIt().replace('/login'); }, child: const Text('Back to Login')) ])); } @override void dispose() { usernameTextController.dispose(); passwordTextController.dispose(); super.dispose(); } void formValuesUpdated() { final usernameValidation = usernameValidator(usernameTextController.text); final passwordValidation = passwordValidator(passwordTextController.text); setState(() { canCreateAccount = usernameValidation == null && passwordValidation == null; }); } void checkUsernameAvailability() async { final usernameText = usernameTextController.text; // Set usernameAvailable to null if the username is invalid. if (usernameValidator(usernameText) != null) { setState(() => usernameAvailable = null); return; } // Otherwise, there's a valid username, so check if it's available. final available = await getUsernameAvailability(usernameText); setState(() => usernameAvailable = available); } void createAccount() async { print('Creating account...'); if (formKey.currentState!.validate()) { setState(() => loading = true); final credentials = LoginCredentials( usernameTextController.text, passwordTextController.text); try { final token = await postRegister(credentials); getIt().state = Authenticated(token, credentials.username); } catch (e) { print(e); } finally { setState(() => loading = false); } } } }