From c3e55e89f2eb2427475bb21e6b0da68ec7236c23 Mon Sep 17 00:00:00 2001 From: Shivering Ocelot Date: Mon, 26 May 2025 11:22:31 -0500 Subject: [PATCH] Signup screen and functionality --- frontend/lib/agent_screen.dart | 2 + frontend/lib/backend.dart | 22 ++++++- frontend/lib/login_screen.dart | 107 ++++++++++++++++++++++++++++++--- frontend/lib/main.dart | 2 +- frontend/pubspec.lock | 32 ++++++++++ frontend/pubspec.yaml | 1 + 6 files changed, 155 insertions(+), 11 deletions(-) diff --git a/frontend/lib/agent_screen.dart b/frontend/lib/agent_screen.dart index 7a100d8..743c8c6 100644 --- a/frontend/lib/agent_screen.dart +++ b/frontend/lib/agent_screen.dart @@ -7,6 +7,8 @@ class ConfigField { final String name; final String type; final TextEditingController controller; + // There should be an additional property to indicate whether this field is deactivated/removed/reset + // meaning it will be set as 'null' (default) rather than any string, otherwise only an empty string can be set ConfigField({required this.name, required this.type, required this.controller}); } diff --git a/frontend/lib/backend.dart b/frontend/lib/backend.dart index 60ab817..66896e2 100644 --- a/frontend/lib/backend.dart +++ b/frontend/lib/backend.dart @@ -1,7 +1,8 @@ +import 'package:http/http.dart' as http; import 'dart:html'; import 'dart:convert'; -import 'dart:async'; // Import for handling Completers +import 'dart:async'; class ChatInfo { int id; @@ -31,6 +32,23 @@ class Backend { connect(); } + static Future signup(String username, String email, String password) async { + final response = await http.post(Uri.parse("$_url/user/signup"), + headers: { + 'Content-Type': 'application/json', + }, + body: jsonEncode({ + 'username': username, + 'email': email, + 'password': password, + }) + ); + + if (response.statusCode < 200 || response.statusCode >= 300) { + throw Exception("Signup Status Code ${response.statusCode}"); + } + } + static Future auth(String username, String password) { _socket!.send(json.encode({'username': username, 'password': password})); _credentials = (username: username, password: password); @@ -43,7 +61,7 @@ class Backend { } static void connect() { - _socket = WebSocket(_url!); + _socket = WebSocket(_url! + "/ws"); _socket!.onOpen.listen((event) { print('Connected to the WebSocket server'); Backend.invalidateCache(chat: true); diff --git a/frontend/lib/login_screen.dart b/frontend/lib/login_screen.dart index edbbbd4..a23f49f 100644 --- a/frontend/lib/login_screen.dart +++ b/frontend/lib/login_screen.dart @@ -1,24 +1,52 @@ import 'package:flutter/material.dart'; +import 'backend.dart'; class LoginScreen extends StatelessWidget { final Function(String, String) onLogin; + final bool isSignupMode; // Changed to a boolean - LoginScreen({required this.onLogin}); + LoginScreen({required this.onLogin, this.isSignupMode = false}); // Default to false (login mode) final TextEditingController usernameController = TextEditingController(); final TextEditingController passwordController = TextEditingController(); + final TextEditingController emailController = TextEditingController(); + final TextEditingController confirmPasswordController = TextEditingController(); final FocusNode usernameFocusNode = FocusNode(); final FocusNode passwordFocusNode = FocusNode(); + final FocusNode emailFocusNode = FocusNode(); + final FocusNode confirmPasswordFocusNode = FocusNode(); void _handleLogin() { onLogin(usernameController.text, passwordController.text); } + void _handleSignup(BuildContext context) async { + final username = usernameController.text; + final password = passwordController.text; + final email = emailController.text; + + if (password != confirmPasswordController.text) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text("Passwords don't match")), + ); + return; + } + + try { + await Backend.signup(username, email, password); + _handleLogin(); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Could not signup: $e')), + ); + } + } + @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: Text('Login')), + appBar: AppBar(title: Text(isSignupMode ? 'Signup' : 'Login')), // Dynamic title body: Center( child: Padding( padding: const EdgeInsets.all(16.0), @@ -31,24 +59,87 @@ class LoginScreen extends StatelessWidget { decoration: InputDecoration(labelText: 'Username'), textInputAction: TextInputAction.next, onSubmitted: (_) { - FocusScope.of(context).requestFocus(passwordFocusNode); + if (!isSignupMode) { // If not in signup mode, move to password + FocusScope.of(context).requestFocus(passwordFocusNode); + } else { // In signup mode, move to email + FocusScope.of(context).requestFocus(emailFocusNode); + } }, ), SizedBox(height: 20), + if (isSignupMode) ...[ // Conditionally show email field + TextField( + controller: emailController, + focusNode: emailFocusNode, + decoration: InputDecoration(labelText: 'Email'), + keyboardType: TextInputType.emailAddress, + textInputAction: TextInputAction.next, + onSubmitted: (_) { + FocusScope.of(context).requestFocus(passwordFocusNode); + }, + ), + SizedBox(height: 20), + ], TextField( controller: passwordController, focusNode: passwordFocusNode, decoration: InputDecoration(labelText: 'Password'), obscureText: true, - textInputAction: TextInputAction.done, + textInputAction: isSignupMode ? TextInputAction.next : TextInputAction.done, // Adjust text action onSubmitted: (_) { - _handleLogin(); + if (!isSignupMode) { // If not in signup mode, handle login + _handleLogin(); + } else { // In signup mode, move to confirm password + FocusScope.of(context).requestFocus(confirmPasswordFocusNode); + } }, ), SizedBox(height: 20), + if (isSignupMode) ...[ // Conditionally show confirm password field + TextField( + controller: confirmPasswordController, + focusNode: confirmPasswordFocusNode, + decoration: InputDecoration(labelText: 'Confirm Password'), + obscureText: true, + textInputAction: TextInputAction.done, + onSubmitted: (_) { + _handleSignup(context); + }, + ), + SizedBox(height: 20), + ], ElevatedButton( - onPressed: _handleLogin, - child: Text('Login'), + onPressed: () { + if (!isSignupMode) { // If not in signup mode, handle login + _handleLogin(); + } else { // In signup mode, handle signup + _handleSignup(context); + } + }, + child: Text(isSignupMode ? 'Signup' : 'Login'), // Dynamic button text + ), + SizedBox(height: 20), + TextButton( + onPressed: () { + if (!isSignupMode) { // If in login mode, navigate to signup + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => LoginScreen( + onLogin: onLogin, + isSignupMode: true, // Push with signup mode + ), + ), + ); + } else { // If in signup mode, pop back to login + Navigator.of(context).pop(); + } + }, + child: Text( + isSignupMode + ? 'Already have an account? Login' // Text for signup mode + : 'Don\'t have an account? Sign Up', // Text for login mode + style: TextStyle(color: Colors.blue), + ), ), ], ), @@ -56,4 +147,4 @@ class LoginScreen extends StatelessWidget { ), ); } -} +} \ No newline at end of file diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart index 7f51af8..5dfe801 100644 --- a/frontend/lib/main.dart +++ b/frontend/lib/main.dart @@ -30,7 +30,7 @@ void main() { print("Main"); Config.loadConfig(); // Load the configuration before running the app - Backend.setup('${Config.backendUrl}/ws'); + Backend.setup(Config.backendUrl!); runApp(HubApp()); } diff --git a/frontend/pubspec.lock b/frontend/pubspec.lock index 6942cc2..62cc073 100644 --- a/frontend/pubspec.lock +++ b/frontend/pubspec.lock @@ -136,6 +136,22 @@ packages: description: flutter source: sdk version: "0.0.0" + http: + dependency: "direct main" + description: + name: http + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" js: dependency: transitive description: @@ -325,6 +341,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.4" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" vector_math: dependency: transitive description: @@ -341,6 +365,14 @@ packages: url: "https://pub.dev" source: hosted version: "14.3.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" win32: dependency: transitive description: diff --git a/frontend/pubspec.yaml b/frontend/pubspec.yaml index de1483f..f2dd551 100644 --- a/frontend/pubspec.yaml +++ b/frontend/pubspec.yaml @@ -37,6 +37,7 @@ dependencies: # For user credentials and sensitive data flutter_secure_storage: ^9.2.4 + http: ^1.4.0 dev_dependencies: flutter_test: