diff --git a/data/lib/repositories/auth_repository.dart b/data/lib/repositories/auth_repository_impl.dart similarity index 52% rename from data/lib/repositories/auth_repository.dart rename to data/lib/repositories/auth_repository_impl.dart index ca43040..5b0e59f 100644 --- a/data/lib/repositories/auth_repository.dart +++ b/data/lib/repositories/auth_repository_impl.dart @@ -66,4 +66,61 @@ class AuthRepositoryImpl implements AuthRepository { 'Logout failed with status code: ${response.statusCode}'); } } + + Future forgetPassword(String email) async { + final url = Uri.parse('http://localhost:3000/api/auth/forget-password'); + try { + final response = await client.post( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode({'email': email}), + ); + + if (response.statusCode != 200) { + // Handle error responses from the server + final responseData = jsonDecode(response.body); + final errorMessage = + responseData['message'] ?? 'Failed to send reset email'; + throw ServerException(errorMessage); + } + // If statusCode is 200, assume the request was successful + } catch (error) { + // Re-throw network or parsing errors as custom exceptions + throw Exception('An error occurred. Please try again.'); + } + } + + Future resetPasswordVerify(String token) async { + final url = Uri.parse( + 'http://localhost:3000/api/auth/reset-password-verify/$token'); + final response = await client.get( + url, + headers: {'Content-Type': 'application/json'}, + ); + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + return data['message']; + } else { + throw ServerException( + 'Password reset verification failed with status code: ${response.statusCode}'); + } + } + + Future resetPassword(String token, String newPassword) async { + final url = + Uri.parse('http://localhost:3000/api/auth/reset-password/$token'); + final response = await client.post( + url, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode({'password': newPassword}), + ); + + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + return data['message']; + } else { + throw ServerException( + 'Password reset failed with status code: ${response.statusCode}'); + } + } } diff --git a/domain/lib/repositories_abstract/auth_repository.dart b/domain/lib/repositories_abstract/auth_repository.dart index d395f82..95f11f6 100644 --- a/domain/lib/repositories_abstract/auth_repository.dart +++ b/domain/lib/repositories_abstract/auth_repository.dart @@ -6,4 +6,7 @@ abstract class AuthRepository { Future signup(String username, String email, String password); Future login(String username, String password); Future logout(); + Future forgetPassword(String email); + Future resetPasswordVerify(String token); + Future resetPassword(String token, String newPassword); } diff --git a/domain/lib/usecases/auth_usecase.dart b/domain/lib/usecases/auth_usecase.dart index 4b59e4c..740c8cf 100644 --- a/domain/lib/usecases/auth_usecase.dart +++ b/domain/lib/usecases/auth_usecase.dart @@ -30,4 +30,16 @@ class AuthUseCase { final token = await tokenProvider.getToken(); return token != null; } + + Future forgetPassword(String email) async { + return await repository.forgetPassword(email); + } + + Future resetPasswordVerify(String token) async { + return await repository.resetPasswordVerify(token); + } + + Future resetPassword(String token, String newPassword) async { + return await repository.resetPassword(token, newPassword); + } } diff --git a/firebase.json b/firebase.json index fa1973b..73e1ffd 100644 --- a/firebase.json +++ b/firebase.json @@ -4,6 +4,23 @@ "ignore": [ "firebase.json", "**/node_modules/**" + ], + "rewrites": [ + { + "source": "**", + "destination": "/index.html" + } + ], + "headers": [ + { + "source": "/apple-app-site-association", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } ] } } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 7f05b94..9755d00 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -4,6 +4,8 @@ PODS: - FlutterMacOS - amplify_secure_storage (0.0.1): - Flutter + - app_links (0.0.2): + - Flutter - app_tracking_transparency (0.0.1): - Flutter - device_info (0.0.1): @@ -37,6 +39,7 @@ PODS: DEPENDENCIES: - amplify_auth_cognito (from `.symlinks/plugins/amplify_auth_cognito/darwin`) - amplify_secure_storage (from `.symlinks/plugins/amplify_secure_storage/ios`) + - app_links (from `.symlinks/plugins/app_links/ios`) - app_tracking_transparency (from `.symlinks/plugins/app_tracking_transparency/ios`) - device_info (from `.symlinks/plugins/device_info/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) @@ -60,6 +63,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/amplify_auth_cognito/darwin" amplify_secure_storage: :path: ".symlinks/plugins/amplify_secure_storage/ios" + app_links: + :path: ".symlinks/plugins/app_links/ios" app_tracking_transparency: :path: ".symlinks/plugins/app_tracking_transparency/ios" device_info: @@ -90,6 +95,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: amplify_auth_cognito: 76b5a54f05f66f69966b468b8121a0dd33a32c70 amplify_secure_storage: be403397a8ba39f5e0f2a05303b3ca03b53b9ee9 + app_links: e7a6750a915a9e161c58d91bc610e8cd1d4d0ad0 app_tracking_transparency: e169b653478da7bb15a6c61209015378ca73e375 device_info: d7d233b645a32c40dfdc212de5cf646ca482f175 device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 7b6030e..e041f2f 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -45,6 +45,7 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 99A3D7612CD6FBC600B93DAF /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; D51570C31D4B8C71E4A0A233 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; F0101E625D9607551D7077EF /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -112,6 +113,7 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( + 99A3D7612CD6FBC600B93DAF /* Runner.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, @@ -357,6 +359,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 2410201826; @@ -490,6 +493,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 2410201826; @@ -517,6 +521,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 2410201826; diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements new file mode 100644 index 0000000..3a64624 --- /dev/null +++ b/ios/Runner/Runner.entitlements @@ -0,0 +1,11 @@ + + + + + com.apple.developer.associated-domains + + applinks:compositesai.com + webcredentials:compositesai.com + + + diff --git a/lib/injection_container.dart b/lib/injection_container.dart index 28fb02e..9da08ef 100644 --- a/lib/injection_container.dart +++ b/lib/injection_container.dart @@ -2,7 +2,7 @@ import 'package:data/data_sources/authenticated_http_client.dart'; import 'package:data/data_sources/function_tools_data_source.dart'; import 'package:data/data_sources/open_ai_data_source.dart'; import 'package:data/providers/token_provider_impl.dart'; -import 'package:data/repositories/auth_repository.dart'; +import 'package:data/repositories/auth_repository_impl.dart'; import 'package:data/repositories/chat_repository_impl.dart'; import 'package:data/repositories/chat_session_repository_imp.dart'; import 'package:data/repositories/user_repository.dart'; @@ -17,7 +17,9 @@ import 'package:get_it/get_it.dart'; import 'package:http/http.dart' as http; import 'package:swiftcomp/presentation/settings/providers/feature_flag_provider.dart'; import 'package:swiftcomp/presentation/settings/viewModels/feature_flag_view_model.dart'; +import 'package:swiftcomp/presentation/settings/viewModels/forget_password_view_model.dart'; import 'package:swiftcomp/presentation/settings/viewModels/login_view_model.dart'; +import 'package:swiftcomp/presentation/settings/viewModels/reset_password_view_model.dart'; import 'package:swiftcomp/presentation/settings/viewModels/settings_view_model.dart'; import 'package:swiftcomp/presentation/settings/viewModels/signup_view_model.dart'; import 'package:swiftcomp/presentation/settings/viewModels/user_profile_view_model.dart'; @@ -42,6 +44,8 @@ void initInjection() { () => FeatureFlagViewModel(featureFlagProvider: sl())); sl.registerFactory(() => UserProfileViewModel(authUseCase: sl(), userUseCase: sl())); + sl.registerFactory(() => ResetPasswordViewModel(authUseCase: sl())); + sl.registerFactory(() => ForgetPasswordViewModel(authUseCase: sl())); // Providers sl.registerLazySingleton(() => TokenProviderImpl()); diff --git a/lib/main.dart b/lib/main.dart index 4309e2e..ce4a027 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,9 @@ +import 'dart:async'; + import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; import 'package:amplify_flutter/amplify_flutter.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; @@ -59,7 +62,6 @@ class _MyAppState extends State { @override void initState() { super.initState(); - _configureAmplify(); } diff --git a/lib/presentation/bottom_navigator.dart b/lib/presentation/bottom_navigator.dart index 95660a1..67df181 100644 --- a/lib/presentation/bottom_navigator.dart +++ b/lib/presentation/bottom_navigator.dart @@ -1,7 +1,10 @@ +import 'package:app_links/app_links.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:swiftcomp/presentation/chat/viewModels/chat_view_model.dart'; import 'package:swiftcomp/presentation/settings/providers/feature_flag_provider.dart'; +import 'package:swiftcomp/presentation/settings/views/reset_password_page.dart'; import 'package:swiftcomp/presentation/tools/page/tool_page.dart'; import 'chat/views/chat_screen.dart'; @@ -15,6 +18,7 @@ class BottomNavigator extends StatefulWidget { } class _BottomNavigatorState extends State { + late final AppLinks _appLinks; final PageController _controller = PageController( initialPage: 0, ); @@ -26,6 +30,11 @@ class _BottomNavigatorState extends State { @override void initState() { super.initState(); + if (kIsWeb) { + _handleWebLink(); + } else { + _setupAppLinks(); + } } @override @@ -41,7 +50,11 @@ class _BottomNavigatorState extends State { body: PageView( controller: _controller, physics: const NeverScrollableScrollPhysics(), - children: [if (isChatEnabled) ChatScreen(), ToolPage(), SettingsPage()], + children: [ + if (isChatEnabled) ChatScreen(), + ToolPage(), + SettingsPage() + ], ), bottomNavigationBar: BottomNavigationBar( backgroundColor: Color.fromRGBO(51, 66, 78, 1), @@ -78,4 +91,50 @@ class _BottomNavigatorState extends State { ), label: title); } + + void _handleWebLink() { + final uri = Uri.base; + final pathSegments = uri.pathSegments; + if (uri.host == 'compositesai.com' || uri.host == 'localhost') { + if (uri.pathSegments.isNotEmpty && pathSegments[0] == 'reset-password') { + final token = pathSegments[1]; + WidgetsBinding.instance.addPostFrameCallback((_) { + _navigateToResetPasswordPage(token); + }); + } + } + } + + void _setupAppLinks() async { + _appLinks = AppLinks(); + + _appLinks.getInitialLink().then((uri) { + if (uri != null) { + print(uri); + } + }); + + _appLinks.uriLinkStream.listen((uri) { + print(uri); + final token = + uri.pathSegments.length > 1 && uri.pathSegments[0] == 'reset-password' + ? uri.pathSegments[1] + : uri.queryParameters['token']; + print(token); + if (token != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _navigateToResetPasswordPage(token); + }); + } + }); + } + + void _navigateToResetPasswordPage(String token) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ResetPasswordPage(token: token), + ), + ); + } } diff --git a/lib/presentation/settings/login/forget_password_page.dart b/lib/presentation/settings/login/forget_password_page_old.dart similarity index 94% rename from lib/presentation/settings/login/forget_password_page.dart rename to lib/presentation/settings/login/forget_password_page_old.dart index 0887037..b72fe6a 100644 --- a/lib/presentation/settings/login/forget_password_page.dart +++ b/lib/presentation/settings/login/forget_password_page_old.dart @@ -5,14 +5,14 @@ import 'package:swiftcomp/presentation/settings/login/login_button.dart'; import 'package:swiftcomp/presentation/settings/login/login_input.dart'; import 'package:swiftcomp/util/string_util.dart'; -class ForgetPasswordPage extends StatefulWidget { - const ForgetPasswordPage({Key? key}) : super(key: key); +class ForgetPasswordPageOld extends StatefulWidget { + const ForgetPasswordPageOld({Key? key}) : super(key: key); @override - _ForgetPasswordPageState createState() => _ForgetPasswordPageState(); + _ForgetPasswordPageOldState createState() => _ForgetPasswordPageOldState(); } -class _ForgetPasswordPageState extends State { +class _ForgetPasswordPageOldState extends State { bool confirmEnable = false; bool resetEnable = false; String? email; diff --git a/lib/presentation/settings/login/old_login_page.dart b/lib/presentation/settings/login/old_login_page.dart index e5d017e..88077d0 100644 --- a/lib/presentation/settings/login/old_login_page.dart +++ b/lib/presentation/settings/login/old_login_page.dart @@ -9,7 +9,7 @@ import 'package:swiftcomp/presentation/settings/login/registration_page.dart'; import 'package:swiftcomp/util/string_util.dart'; import 'email_confimation_screen.dart'; -import 'forget_password_page.dart'; +import 'forget_password_page_old.dart'; class LoginPage extends StatefulWidget { const LoginPage({Key? key}) : super(key: key); @@ -80,7 +80,7 @@ class _LoginPageState extends State { enable: true, onPressed: () { Navigator.push(context, - MaterialPageRoute(builder: (context) => const ForgetPasswordPage())); + MaterialPageRoute(builder: (context) => const ForgetPasswordPageOld())); }, )), ], diff --git a/lib/presentation/settings/viewModels/forget_password_view_model.dart b/lib/presentation/settings/viewModels/forget_password_view_model.dart new file mode 100644 index 0000000..2720b4b --- /dev/null +++ b/lib/presentation/settings/viewModels/forget_password_view_model.dart @@ -0,0 +1,25 @@ +import 'package:domain/usecases/auth_usecase.dart'; +import 'package:flutter/material.dart'; + +class ForgetPasswordViewModel extends ChangeNotifier { + bool isLoading = false; + String errorMessage = ''; + AuthUseCase authUseCase; + + ForgetPasswordViewModel({required this.authUseCase}); + + Future forgetPassword(String email) async { + isLoading = true; + errorMessage = ''; + notifyListeners(); + + try { + await authUseCase.forgetPassword(email); + isLoading = false; + } catch (error) { + errorMessage = error.toString(); + isLoading = false; + } + notifyListeners(); + } +} diff --git a/lib/presentation/settings/viewModels/reset_password_view_model.dart b/lib/presentation/settings/viewModels/reset_password_view_model.dart new file mode 100644 index 0000000..bedb606 --- /dev/null +++ b/lib/presentation/settings/viewModels/reset_password_view_model.dart @@ -0,0 +1,45 @@ +import 'package:domain/usecases/auth_usecase.dart'; +import 'package:flutter/material.dart'; + +class ResetPasswordViewModel extends ChangeNotifier { + final AuthUseCase authUseCase; + bool isTokenValid = false; + bool isLoading = false; + String errorMessage = ''; + + ResetPasswordViewModel({required this.authUseCase}); + + Future verifyToken(String token) async { + isLoading = true; + errorMessage = ''; + notifyListeners(); + + try { + await authUseCase.resetPasswordVerify(token); + isTokenValid = true; + } catch (error) { + isTokenValid = false; + errorMessage = 'Invalid or expired token'; + } finally { + isLoading = false; + notifyListeners(); + } + } + + Future resetPassword(String token, String newPassword) async { + isLoading = true; + errorMessage = ''; + notifyListeners(); + + try { + await authUseCase.resetPassword(token, newPassword); + return 'Password reset successful!'; + } catch (error) { + errorMessage = 'Failed to reset password. Please try again.'; + return null; + } finally { + isLoading = false; + notifyListeners(); + } + } +} diff --git a/lib/presentation/settings/viewModels/settings_view_model.dart b/lib/presentation/settings/viewModels/settings_view_model.dart index 661cd81..8a9ef73 100644 --- a/lib/presentation/settings/viewModels/settings_view_model.dart +++ b/lib/presentation/settings/viewModels/settings_view_model.dart @@ -67,9 +67,13 @@ class SettingsViewModel extends ChangeNotifier { } Future fetchUser() async { - user = await userUserCase.fetchMe(); - print(user); - notifyListeners(); + try { + user = await userUserCase.fetchMe(); + print(user); + notifyListeners(); + } catch (e) { + isLoggedIn = false; + } } void fetchFeatureFlags() { diff --git a/lib/presentation/settings/views/forget_password_page.dart b/lib/presentation/settings/views/forget_password_page.dart new file mode 100644 index 0000000..d8d0a1a --- /dev/null +++ b/lib/presentation/settings/views/forget_password_page.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../viewModels/forget_password_view_model.dart'; // Adjust the import path as necessary +import '../../../injection_container.dart'; // Import your service locator to inject dependencies + +class ForgetPasswordPage extends StatefulWidget { + @override + _ForgetPasswordPageState createState() => _ForgetPasswordPageState(); +} + +class _ForgetPasswordPageState extends State { + final _emailController = TextEditingController(); + final _formKey = GlobalKey(); + + @override + Widget build(BuildContext context) { + return ChangeNotifierProvider( + create: (_) => sl(), // Use your service locator + child: Scaffold( + appBar: AppBar( + title: Text("Forget Password"), + backgroundColor: + Colors.grey[800], // Customize the AppBar color as needed + ), + body: Consumer( + builder: (context, viewModel, child) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Form( + key: _formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Email Input Field + TextFormField( + controller: _emailController, + decoration: InputDecoration( + labelText: "Email", + hintText: "Input Email", + border: UnderlineInputBorder(), + ), + keyboardType: TextInputType.emailAddress, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter your email'; + } + if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) { + return 'Please enter a valid email'; + } + return null; + }, + ), + SizedBox(height: 20), + + // Reset Password Button + ElevatedButton( + onPressed: viewModel.isLoading + ? null + : () async { + if (_formKey.currentState!.validate()) { + await viewModel + .forgetPassword(_emailController.text); + if (viewModel.errorMessage.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: + Text('Password reset email sent.')), + ); + Navigator.pop( + context); // Go back after successful reset + } + } + }, + child: viewModel.isLoading + ? CircularProgressIndicator( + valueColor: + AlwaysStoppedAnimation(Colors.white), + ) + : Text("Reset Password"), + ), + + // Error Message + if (viewModel.errorMessage.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 16.0), + child: Text( + viewModel.errorMessage, + style: TextStyle(color: Colors.red), + ), + ), + ], + ), + ), + ); + }, + ), + ), + ); + } + + @override + void dispose() { + _emailController.dispose(); + super.dispose(); + } +} diff --git a/lib/presentation/settings/views/login_page.dart b/lib/presentation/settings/views/login_page.dart index 2bb289f..0599984 100644 --- a/lib/presentation/settings/views/login_page.dart +++ b/lib/presentation/settings/views/login_page.dart @@ -5,6 +5,7 @@ import 'package:swiftcomp/presentation/settings/views/sigup_page.dart'; import '../../../../injection_container.dart'; import '../viewModels/login_view_model.dart'; +import 'forget_password_page.dart'; class NewLoginPage extends StatefulWidget { const NewLoginPage({Key? key}) : super(key: key); @@ -27,7 +28,10 @@ class _LoginPageState extends State { if (accessToken != null) { // Login successful ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Login Successful!'), duration: Duration(seconds: 2),), + SnackBar( + content: Text('Login Successful!'), + duration: Duration(seconds: 2), + ), ); Navigator.pop(context, "Log in Success"); } else if (viewModel.errorMessage != null) { @@ -83,8 +87,20 @@ class _LoginPageState extends State { viewModel.isLoading ? CircularProgressIndicator() : ElevatedButton( - onPressed: () => _login(viewModel), - child: Text('Login'), + onPressed: () => _login(viewModel), + child: Text('Login'), + ), + SizedBox(height: 50.0), + + ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ForgetPasswordPage()), + ); + }, + child: Text('Forget Password'), ), SizedBox(height: 50.0), diff --git a/lib/presentation/settings/views/reset_password_page.dart b/lib/presentation/settings/views/reset_password_page.dart new file mode 100644 index 0000000..392cddb --- /dev/null +++ b/lib/presentation/settings/views/reset_password_page.dart @@ -0,0 +1,101 @@ +import 'package:data/repositories/auth_repository_impl.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../../../injection_container.dart'; +import '../viewModels/reset_password_view_model.dart'; + +class ResetPasswordPage extends StatelessWidget { + final String token; + final _passwordController = TextEditingController(); + final _formKey = GlobalKey(); + + ResetPasswordPage({required this.token}); + + @override + Widget build(BuildContext context) { + return ChangeNotifierProvider( + create: (context) { + final viewModel = sl(); + Future.microtask( + () => viewModel.verifyToken(token)); // Call verifyToken on load + return viewModel; + }, + child: Scaffold( + appBar: AppBar(title: Text("Reset Password")), + body: Consumer( + builder: (context, viewModel, child) { + if (viewModel.isLoading) { + return Center(child: CircularProgressIndicator()); + } + + if (viewModel.errorMessage.isNotEmpty) { + return Center( + child: Text( + viewModel.errorMessage, + style: TextStyle(color: Colors.red, fontSize: 18), + ), + ); + } + + if (viewModel.isTokenValid) { + return Form( + key: _formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextFormField( + controller: _passwordController, + decoration: InputDecoration(labelText: "New Password"), + obscureText: true, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a new password'; + } + return null; + }, + ), + SizedBox(height: 20), + ElevatedButton( + onPressed: () async { + if (_formKey.currentState!.validate()) { + final message = await viewModel.resetPassword( + token, + _passwordController.text, + ); + + if (message != null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(message)), + ); + Navigator.pop(context); // Go back after successful reset + } + } + }, + child: Text("Reset Password"), + ), + if (viewModel.errorMessage.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 16.0), + child: Text( + viewModel.errorMessage, + style: TextStyle(color: Colors.red), + ), + ), + ], + ), + ); + } + + return Center( + child: ElevatedButton( + onPressed: () => viewModel.verifyToken(token), + child: Text("Verify Token"), + ), + ); + }, + ), + ), + ); + } +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index fad28e9..43bb687 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -97,6 +97,38 @@ packages: url: "https://pub.dev" source: hosted version: "6.4.1" + app_links: + dependency: "direct main" + description: + name: app_links + sha256: ad1a6d598e7e39b46a34f746f9a8b011ee147e4c275d407fa457e7a62f84dd99 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + app_links_linux: + dependency: transitive + description: + name: app_links_linux + sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + app_links_platform_interface: + dependency: transitive + description: + name: app_links_platform_interface + sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + app_links_web: + dependency: transitive + description: + name: app_links_web + sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555 + url: "https://pub.dev" + source: hosted + version: "1.0.4" app_tracking_transparency: dependency: "direct main" description: @@ -515,6 +547,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.1" + gtk: + dependency: transitive + description: + name: gtk + sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c + url: "https://pub.dev" + source: hosted + version: "2.1.0" http: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 4e9cce3..5f0cf74 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -48,6 +48,7 @@ dependencies: get_it: ^8.0.0 flutter_dotenv: ^5.0.2 flutter_secure_storage: ^9.2.2 + app_links: ^6.2.0 amplify_flutter: ^1.8.0 amplify_auth_cognito: ^1.8.0 diff --git a/web/apple-app-site-association b/web/apple-app-site-association new file mode 100644 index 0000000..837a5f1 --- /dev/null +++ b/web/apple-app-site-association @@ -0,0 +1,14 @@ +{ + "applinks": { + "apps": [], + "details": [ + { + "appID": "F694X76A5X.com.cdmHUB.SwiftComp", + "paths": [ + "*", + "/*" + ] + } + ] + } +}