diff --git a/Frontend/lib/mih_config/mih_go_router.dart b/Frontend/lib/mih_config/mih_go_router.dart index 5909b636..ba2497ab 100644 --- a/Frontend/lib/mih_config/mih_go_router.dart +++ b/Frontend/lib/mih_config/mih_go_router.dart @@ -5,9 +5,10 @@ import 'package:mzansi_innovation_hub/mih_components/mih_objects/arguments.dart' import 'package:mzansi_innovation_hub/mih_components/mih_package_components/Example/package_test.dart'; import 'package:mzansi_innovation_hub/mih_packages/about_mih/about_mih.dart'; import 'package:mzansi_innovation_hub/mih_packages/access_review/mih_access.dart'; -import 'package:mzansi_innovation_hub/mih_packages/authentication/forgot_password.dart'; import 'package:mzansi_innovation_hub/mih_packages/calculator/mih_calculator.dart'; import 'package:mzansi_innovation_hub/mih_packages/calendar/mzansi_calendar.dart'; +import 'package:mzansi_innovation_hub/mih_packages/mih_authentication/mih_auth_forgot_password.dart'; +import 'package:mzansi_innovation_hub/mih_packages/mih_authentication/mih_auth_password_reset.dart'; import 'package:mzansi_innovation_hub/mih_packages/mih_authentication/mih_authentication.dart'; import 'package:mzansi_innovation_hub/mih_packages/mih_home/mih_home.dart'; import 'package:mzansi_innovation_hub/mih_packages/mzansi_ai/mzansi_ai.dart'; @@ -19,11 +20,12 @@ import 'package:ken_logger/ken_logger.dart'; import 'package:mzansi_innovation_hub/mih_packages/mzansi_wallet/components/mih_barcode_scanner.dart'; import 'package:mzansi_innovation_hub/mih_packages/mzansi_wallet/mih_wallet.dart'; import 'package:mzansi_innovation_hub/mih_packages/patient_profile/pat_profile/add_or_view_patient.dart'; +import 'package:mzansi_innovation_hub/mih_packages/patient_profile/pat_profile/patient_edit.dart'; import 'package:supertokens_flutter/supertokens.dart'; class MihGoRouterPaths { // External - static const String resetPasswordExternal = '/auth/reset-password'; + static const String resetPassword = '/auth/reset-password'; static const String privacyPolicyExternal = '/privacy-policy'; static const String termsOfServiceExternal = '/terms-of-service'; @@ -32,7 +34,7 @@ class MihGoRouterPaths { static const String mihAuthentication = '/mih-authentication'; static const String mihHome = '/'; static const String notifications = '/notifications'; - static const String forgotPassword = '/forgot-password'; + static const String forgotPassword = '/mih-authentication/forgot-password'; static const String aboutMih = '/about'; static const String mzansiProfileManage = '/mzansi-profile'; static const String mzansiProfileView = '/mzansi-profile/view'; @@ -62,31 +64,22 @@ class MihGoRouter { initialLocation: MihGoRouterPaths.mihHome, redirect: (BuildContext context, GoRouterState state) async { final bool isUserSignedIn = await SuperTokens.doesSessionExist(); - - // Only redirect if absolutely necessary - if (!isUserSignedIn && - state.fullPath != MihGoRouterPaths.mihAuthentication) { + final unauthenticatedPaths = [ + MihGoRouterPaths.mihAuthentication, + MihGoRouterPaths.forgotPassword, + MihGoRouterPaths.resetPassword, + ]; + if (!isUserSignedIn && !unauthenticatedPaths.contains(state.fullPath)) { return MihGoRouterPaths.mihAuthentication; } - - if (isUserSignedIn && - state.fullPath == MihGoRouterPaths.mihAuthentication) { + if (isUserSignedIn && unauthenticatedPaths.contains(state.fullPath)) { return MihGoRouterPaths.mihHome; } - return null; // Stay on current route }, routes: [ // External Routes - use `GoRoute` with `path` and `builder` // GoRoute( - // name: "resetPasswordExternal", - // path: MihGoRouterPaths.resetPasswordExternal, - // builder: (BuildContext context, GoRouterState state) { - // final token = state.queryParameters['token']; - // return ResetPassword(token: token); - // }, - // ), - // GoRoute( // name: "privacyPolicyExternal", // path: MihGoRouterPaths.privacyPolicyExternal, // builder: (BuildContext context, GoRouterState state) => @@ -100,27 +93,7 @@ class MihGoRouter { // ), // Internal Routes - handle arguments via `extra` or path parameters - // ========================== Mih Auth ================================== - // GoRoute( - // name: "mihHome", - // path: MihGoRouterPaths.authCheck, - // builder: (BuildContext context, GoRouterState state) { - // final AuthArguments? args = state.extra as AuthArguments?; - // KenLogger.success("MihGoRouter: home"); - // return AuthCheck( - // personalSelected: args?.personalSelected ?? true, - // firstBoot: args?.firstBoot ?? true, - // ); - // }, - // ), - // GoRoute( - // name: "mihAuthCheck", - // path: MihGoRouterPaths.authCheck, - // builder: (BuildContext context, GoRouterState state) { - // KenLogger.success("MihGoRouter: mihAuthCheck"); - // return MihAuthCheck(); - // }, - // ), + // ========================== MIH Auth ================================== GoRoute( name: "mihAuthentication", path: MihGoRouterPaths.mihAuthentication, @@ -134,7 +107,21 @@ class MihGoRouter { path: MihGoRouterPaths.forgotPassword, builder: (BuildContext context, GoRouterState state) { KenLogger.success("MihGoRouter: forgotPassword"); - return const ForgotPassword(); + return const MihAuthForgotPassword(); + }, + ), + GoRoute( + name: "resetPassword", + path: MihGoRouterPaths.resetPassword, + builder: (BuildContext context, GoRouterState state) { + String? token = state.uri.queryParameters['token']; + if (token == null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + context.go(MihGoRouterPaths.mihHome); + }); + return const SizedBox.shrink(); + } + return MihAuthPasswordReset(token: token); }, ), // ========================== MIH Home ================================== @@ -208,9 +195,15 @@ class MihGoRouter { KenLogger.success("MihGoRouter: businessProfileView"); final BusinessViewArguments? args = state.extra as BusinessViewArguments?; + if (args == null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + context.go(MihGoRouterPaths.mihHome); + }); + return const SizedBox.shrink(); + } return MzansiBusinessProfileView( key: UniqueKey(), - arguments: args!, + arguments: args, ); }, ), @@ -320,9 +313,15 @@ class MihGoRouter { path: MihGoRouterPaths.mihAccess, builder: (BuildContext context, GoRouterState state) { final AppUser? signedInUser = state.extra as AppUser?; + if (signedInUser == null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + context.go(MihGoRouterPaths.mihHome); + }); + return const SizedBox.shrink(); + } return MihAccess( key: UniqueKey(), - signedInUser: signedInUser!, + signedInUser: signedInUser, ); }, ), @@ -331,14 +330,38 @@ class MihGoRouter { name: "patientProfile", path: MihGoRouterPaths.patientProfile, builder: (BuildContext context, GoRouterState state) { - final PatientViewArguments args = state.extra as PatientViewArguments; - + final PatientViewArguments? args = + state.extra as PatientViewArguments?; + if (args == null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + context.go(MihGoRouterPaths.mihHome); + }); + return const SizedBox.shrink(); + } return AddOrViewPatient( key: UniqueKey(), arguments: args, ); }, ), + GoRoute( + name: "patientProfileEdit", + path: MihGoRouterPaths.patientProfileEdit, + builder: (BuildContext context, GoRouterState state) { + final PatientEditArguments? args = + state.extra as PatientEditArguments?; + if (args == null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + context.go(MihGoRouterPaths.mihHome); + }); + return const SizedBox.shrink(); + } + return EditPatient( + signedInUser: args.signedInUser, + selectedPatient: args.selectedPatient, + ); + }, + ), // ========================== Mzansi Directory ================================== GoRoute( name: "mzansiDirectory", @@ -346,7 +369,13 @@ class MihGoRouter { builder: (BuildContext context, GoRouterState state) { final MzansiDirectoryArguments? args = state.extra as MzansiDirectoryArguments?; - return MzansiDirectory(arguments: args!); + if (args == null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + context.go(MihGoRouterPaths.mihHome); + }); + return const SizedBox.shrink(); + } + return MzansiDirectory(arguments: args); }, ), // ========================== End ================================== @@ -390,17 +419,6 @@ class MihGoRouter { // return AddPatient(signedInUser: signedInUser!); // }, // ), -// GoRoute( -// name: "patientProfileEdit", -// path: MihGoRouterPaths.patientProfileEdit, -// builder: (BuildContext context, GoRouterState state) { -// final PatientEditArguments? args = state.extra as PatientEditArguments?; -// return EditPatient( -// signedInUser: args!.signedInUser, -// selectedPatient: args.selectedPatient, -// ); -// }, -// ), // // Note: You can't have two separate GoRoutes with the same path. // // 'appointments' and 'calendar' now need a distinct path, or be sub-routes. // // Here, we'll assume they should be separate. diff --git a/Frontend/lib/mih_packages/mih_authentication/mih_auth_forgot_password.dart b/Frontend/lib/mih_packages/mih_authentication/mih_auth_forgot_password.dart new file mode 100644 index 00000000..e8854ebb --- /dev/null +++ b/Frontend/lib/mih_packages/mih_authentication/mih_auth_forgot_password.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_action.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_tools.dart'; +import 'package:mzansi_innovation_hub/mih_packages/mih_authentication/package_tools/mih_forgot_password.dart'; + +class MihAuthForgotPassword extends StatefulWidget { + const MihAuthForgotPassword({super.key}); + + @override + State createState() => _MihAuthForgotPasswordState(); +} + +class _MihAuthForgotPasswordState extends State { + int _selcetedIndex = 0; + + @override + Widget build(BuildContext context) { + return MihPackage( + appActionButton: getAction(), + appTools: getTools(), + appBody: getToolBody(), + selectedbodyIndex: _selcetedIndex, + onIndexChange: (newValue) { + setState(() { + _selcetedIndex = newValue; + }); + }, + ); + } + + MihPackageAction getAction() { + return MihPackageAction( + icon: const Icon(Icons.arrow_back), + iconSize: 35, + onTap: () { + context.goNamed( + 'mihHome', + extra: true, + ); + FocusScope.of(context).unfocus(); + }, + ); + } + + MihPackageTools getTools() { + Map temp = {}; + temp[const Icon(Icons.question_mark_rounded)] = () { + setState(() { + _selcetedIndex = 0; + }); + }; + return MihPackageTools( + tools: temp, + selcetedIndex: _selcetedIndex, + ); + } + + List getToolBody() { + List toolBodies = [ + MihForgotPassword(), + ]; + return toolBodies; + } +} diff --git a/Frontend/lib/mih_packages/mih_authentication/mih_auth_password_reset.dart b/Frontend/lib/mih_packages/mih_authentication/mih_auth_password_reset.dart new file mode 100644 index 00000000..76cd7b4a --- /dev/null +++ b/Frontend/lib/mih_packages/mih_authentication/mih_auth_password_reset.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_icons.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_action.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_tools.dart'; +import 'package:mzansi_innovation_hub/mih_packages/mih_authentication/package_tools/mih_reset_password.dart'; + +class MihAuthPasswordReset extends StatefulWidget { + final String token; + const MihAuthPasswordReset({ + super.key, + required this.token, + }); + + @override + State createState() => _MihAuthPasswordResetState(); +} + +class _MihAuthPasswordResetState extends State { + int _selcetedIndex = 0; + + @override + Widget build(BuildContext context) { + return MihPackage( + appActionButton: getAction(), + appTools: getTools(), + appBody: getToolBody(), + selectedbodyIndex: _selcetedIndex, + onIndexChange: (newValue) { + setState(() { + _selcetedIndex = newValue; + }); + }, + ); + } + + Widget getAction() { + return Padding( + padding: const EdgeInsets.only(left: 5.0), + child: MihPackageAction( + icon: const Icon(MihIcons.mihLogo), + iconSize: 45, + onTap: () { + context.goNamed( + 'mihHome', + extra: true, + ); + FocusScope.of(context).unfocus(); + }, + ), + ); + } + + MihPackageTools getTools() { + Map temp = {}; + temp[const Icon(Icons.password_rounded)] = () { + setState(() { + _selcetedIndex = 0; + }); + }; + return MihPackageTools( + tools: temp, + selcetedIndex: _selcetedIndex, + ); + } + + List getToolBody() { + List toolBodies = [ + MihResetPassword( + token: widget.token, + ), + ]; + return toolBodies; + } +} diff --git a/Frontend/lib/mih_packages/mih_authentication/mih_authentication.dart b/Frontend/lib/mih_packages/mih_authentication/mih_authentication.dart index af01615e..b255f91d 100644 --- a/Frontend/lib/mih_packages/mih_authentication/mih_authentication.dart +++ b/Frontend/lib/mih_packages/mih_authentication/mih_authentication.dart @@ -68,20 +68,16 @@ class _MihAuthenticationState extends State { ); } - MihPackageAction getAction() { - return MihPackageAction( - icon: Padding( - padding: const EdgeInsets.only(left: 10.0), - child: const Icon(MihIcons.mihLogo), + Widget getAction() { + return Padding( + padding: const EdgeInsets.only(left: 5.0), + child: MihPackageAction( + icon: const Icon(MihIcons.mihLogo), + iconSize: 45, + onTap: () { + context.goNamed("aboutMih", extra: 0); + }, ), - iconSize: 45, - onTap: () { - context.goNamed("aboutMih", extra: 0); - // Navigator.of(context).pushNamed( - // '/about', - // arguments: 0, - // ); - }, ); } } diff --git a/Frontend/lib/mih_packages/mih_authentication/package_tools/mih_forgot_password.dart b/Frontend/lib/mih_packages/mih_authentication/package_tools/mih_forgot_password.dart new file mode 100644 index 00000000..d6b7c970 --- /dev/null +++ b/Frontend/lib/mih_packages/mih_authentication/package_tools/mih_forgot_password.dart @@ -0,0 +1,295 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:go_router/go_router.dart'; +import 'package:mzansi_innovation_hub/main.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_button.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_form.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_alert.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_tool_body.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_text_form_field.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_pop_up_messages/mih_error_message.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_pop_up_messages/mih_loading_circle.dart'; +import 'package:mzansi_innovation_hub/mih_config/mih_colors.dart'; +import 'package:mzansi_innovation_hub/mih_services/mih_alert_services.dart'; +import 'package:mzansi_innovation_hub/mih_services/mih_authentication_services.dart'; +import 'package:mzansi_innovation_hub/mih_services/mih_validation_services.dart'; + +class MihForgotPassword extends StatefulWidget { + const MihForgotPassword({super.key}); + + @override + State createState() => _MihForgotPasswordState(); +} + +class _MihForgotPasswordState extends State { + final emailController = TextEditingController(); + bool successfulForgotPassword = false; + final _formKey = GlobalKey(); + final FocusNode _focusNode = FocusNode(); + bool acceptWarning = false; + + Future submitPasswodReset() async { + showDialog( + context: context, + builder: (context) { + return const Mihloadingcircle(); + }, + ); + try { + var resetPassEmailSent = await MihAuthenticationServices() + .forgotPassword(emailController.text); + context.pop(); + if (resetPassEmailSent) { + setState(() { + successfulForgotPassword = true; + }); + } + } on Exception { + //loginError(); + } + } + + void prePassResteWarning() { + showDialog( + context: context, + builder: (context) { + return MihPackageAlert( + alertIcon: Icon( + Icons.warning_amber_rounded, + size: 100, + color: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + ), + alertTitle: "Password Reset Confirmation", + alertBody: Column( + //mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 25.0), + child: Text( + "Before you reset your password, please be aware that you'll receive an email with a link to confirm your identity and set a new password. Make sure to check your inbox, including spam or junk folders. If you don't receive the email within a few minutes, please try resending the reset request.", + style: TextStyle( + color: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + fontSize: 15.0, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(height: 25), + MihButton( + onPressed: () { + setState(() { + acceptWarning = true; + }); + Navigator.of(context).pop(); + validateInput(); + }, + buttonColor: MihColors.getGreenColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + width: 300, + child: Text( + "Continue", + style: TextStyle( + color: MihColors.getPrimaryColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + alertColour: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + ); + }, + ); + } + + void resetLinkSentSuccessfully() { + showDialog( + context: context, + builder: (context) { + return MihPackageAlert( + alertIcon: Icon( + Icons.check_circle_outline_rounded, + size: 150, + color: MihColors.getGreenColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + ), + alertTitle: "Successfully Sent Reset Link", + alertBody: Column( + children: [ + Text( + "We've sent a password reset link to your email address. Please check your inbox, including spam or junk folders.\n\nOnce you find the email, click on the link to reset your password.\n\nIf you don't receive the email within a few minutes, please try resending the reset request.\n\nThe reset link will expire after 2 hours", + style: TextStyle( + color: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + fontSize: 15, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 25), + Center( + child: MihButton( + onPressed: () { + context.goNamed( + 'mihHome', + extra: true, + ); + }, + buttonColor: MihColors.getGreenColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + elevation: 10, + width: 300, + child: Text( + "Dismiss", + style: TextStyle( + color: MihColors.getPrimaryColor( + MzansiInnovationHub.of(context)!.theme.mode == + "Dark"), + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + ) + ], + ), + alertColour: MihColors.getGreenColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + ); + // return const MIHSuccessMessage( + // successType: "Success", + // successMessage: + // "We've sent a password reset link to your email address. Please check your inbox, including spam or junk folders.\n\nOnce you find the email, click on the link to reset your password.\n\nIf you don't receive the email within a few minutes, please try resending the reset request.\n\nThe reset link will expire after 2 hours"); + }, + ); + } + + void validateInput() async { + if (emailController.text.isEmpty) { + showDialog( + context: context, + builder: (context) { + return const MIHErrorMessage(errorType: "Input Error"); + }, + ); + } else { + await submitPasswodReset(); + if (successfulForgotPassword) { + // Navigator.of(context).pushNamedAndRemoveUntil('/', (route) => false); + resetLinkSentSuccessfully(); + } + } + } + + @override + Widget build(BuildContext context) { + double screenWidth = MediaQuery.of(context).size.width; + return MihPackageToolBody( + borderOn: false, + bodyItem: getBody(screenWidth), + ); + } + + Widget getBody(double width) { + return KeyboardListener( + focusNode: _focusNode, + autofocus: true, + onKeyEvent: (event) async { + if (event is KeyDownEvent && + event.logicalKey == LogicalKeyboardKey.enter) { + validateInput(); + } + }, + child: SafeArea( + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Padding( + padding: MzansiInnovationHub.of(context)!.theme.screenType == + "desktop" + ? EdgeInsets.symmetric(vertical: 25, horizontal: width * 0.2) + : EdgeInsets.symmetric(vertical: 25, horizontal: width * 0.075), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + //logo + Icon( + Icons.lock, + size: 100, + color: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + ), + //spacer + const SizedBox(height: 10), + //Heading + Text( + 'Forgot Password', + style: TextStyle( + fontSize: 25, + fontWeight: FontWeight.bold, + color: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + ), + ), + const SizedBox(height: 25), + MihForm( + formKey: _formKey, + formFields: [ + MihTextFormField( + fillColor: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)!.theme.mode == + "Dark"), + inputColor: MihColors.getPrimaryColor( + MzansiInnovationHub.of(context)!.theme.mode == + "Dark"), + controller: emailController, + multiLineInput: false, + requiredText: true, + hintText: "Email", + validator: (value) { + return MihValidationServices().validateEmail(value); + }, + ), + //spacer + const SizedBox(height: 20), + Align( + alignment: Alignment.center, + child: MihButton( + onPressed: () { + if (_formKey.currentState!.validate()) { + prePassResteWarning(); + } else { + MihAlertServices().formNotFilledCompletely(context); + } + }, + buttonColor: MihColors.getGreenColor( + MzansiInnovationHub.of(context)!.theme.mode == + "Dark"), + width: 300, + child: Text( + "Reset Password", + style: TextStyle( + color: MihColors.getPrimaryColor( + MzansiInnovationHub.of(context)!.theme.mode == + "Dark"), + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/Frontend/lib/mih_packages/mih_authentication/package_tools/mih_reset_password.dart b/Frontend/lib/mih_packages/mih_authentication/package_tools/mih_reset_password.dart new file mode 100644 index 00000000..80352898 --- /dev/null +++ b/Frontend/lib/mih_packages/mih_authentication/package_tools/mih_reset_password.dart @@ -0,0 +1,271 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:go_router/go_router.dart'; +import 'package:mzansi_innovation_hub/main.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_button.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_form.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_alert.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_tool_body.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_text_form_field.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_pop_up_messages/mih_error_message.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_pop_up_messages/mih_loading_circle.dart'; +import 'package:mzansi_innovation_hub/mih_config/mih_colors.dart'; +import 'package:mzansi_innovation_hub/mih_services/mih_alert_services.dart'; +import 'package:mzansi_innovation_hub/mih_services/mih_authentication_services.dart'; +import 'package:mzansi_innovation_hub/mih_services/mih_validation_services.dart'; + +class MihResetPassword extends StatefulWidget { + final String token; + const MihResetPassword({ + super.key, + required this.token, + }); + + @override + State createState() => _MihResetPasswordState(); +} + +class _MihResetPasswordState extends State { + final passwordController = TextEditingController(); + final confirmPasswordController = TextEditingController(); + final FocusNode _focusNode = FocusNode(); + final _formKey = GlobalKey(); + + void submitFormInput() async { + if (passwordController.text != confirmPasswordController.text) { + passwordError(); + } else { + showDialog( + context: context, + builder: (context) { + return const Mihloadingcircle(); + }, + ); + bool successfulResetPassword = await MihAuthenticationServices() + .resetPassword(widget.token, passwordController.text); + context.pop(); + if (successfulResetPassword) { + resetSuccessfully(); + } else { + loginError(); + } + } + } + + void resetSuccessfully() { + showDialog( + context: context, + builder: (context) { + return MihPackageAlert( + alertIcon: Icon( + Icons.check_circle_outline_rounded, + size: 150, + color: MihColors.getGreenColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + ), + alertTitle: "Successfully Reset Password", + alertBody: Column( + children: [ + Text( + "Great news! Your password reset is complete. You can now log in to Mzansi Innovation Hub using your new password.", + style: TextStyle( + color: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + fontSize: 15, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 25), + Center( + child: MihButton( + onPressed: () { + context.goNamed( + 'mihHome', + extra: true, + ); + }, + buttonColor: MihColors.getGreenColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + elevation: 10, + width: 300, + child: Text( + "Dismiss", + style: TextStyle( + color: MihColors.getPrimaryColor( + MzansiInnovationHub.of(context)!.theme.mode == + "Dark"), + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + ) + ], + ), + alertColour: MihColors.getGreenColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + ); + // return const MIHSuccessMessage( + // successType: "Success", + // successMessage: + // "We've sent a password reset link to your email address. Please check your inbox, including spam or junk folders.\n\nOnce you find the email, click on the link to reset your password.\n\nIf you don't receive the email within a few minutes, please try resending the reset request.\n\nThe reset link will expire after 2 hours"); + }, + ); + } + + void loginError() { + showDialog( + context: context, + builder: (context) { + return const MIHErrorMessage(errorType: "Invalid Credentials"); + }, + ); + } + + void passwordError() { + showDialog( + context: context, + builder: (context) { + return const MIHErrorMessage(errorType: "Password Match"); + }, + ); + } + + @override + Widget build(BuildContext context) { + double screenWidth = MediaQuery.of(context).size.width; + return MihPackageToolBody( + borderOn: false, + bodyItem: getBody(screenWidth), + ); + } + + Widget getBody(double width) { + return KeyboardListener( + focusNode: _focusNode, + autofocus: true, + onKeyEvent: (event) async { + if (event is KeyDownEvent && + event.logicalKey == LogicalKeyboardKey.enter) { + if (_formKey.currentState!.validate()) { + submitFormInput(); + } else { + MihAlertServices().formNotFilledCompletely(context); + } + } + }, + child: SafeArea( + child: Center( + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Padding( + padding: + MzansiInnovationHub.of(context)!.theme.screenType == "desktop" + ? EdgeInsets.symmetric(horizontal: width * 0.2) + : EdgeInsets.symmetric(horizontal: width * 0.075), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + //logo + Icon( + Icons.lock, + size: 100, + color: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)!.theme.mode == "Dark"), + ), + //spacer + const SizedBox(height: 10), + //Heading + Text( + 'Reset Password', + style: TextStyle( + fontSize: 25, + fontWeight: FontWeight.bold, + color: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)!.theme.mode == + "Dark"), + ), + ), + //spacer + const SizedBox(height: 25), + MihForm( + formKey: _formKey, + formFields: [ + MihTextFormField( + fillColor: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)!.theme.mode == + "Dark"), + inputColor: MihColors.getPrimaryColor( + MzansiInnovationHub.of(context)!.theme.mode == + "Dark"), + controller: passwordController, + multiLineInput: false, + requiredText: true, + hintText: "Password", + passwordMode: true, + autofillHints: const [AutofillHints.password], + validator: (value) { + return MihValidationServices() + .validatePassword(value); + }, + ), + //spacer + const SizedBox(height: 10), + MihTextFormField( + fillColor: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)!.theme.mode == + "Dark"), + inputColor: MihColors.getPrimaryColor( + MzansiInnovationHub.of(context)!.theme.mode == + "Dark"), + controller: confirmPasswordController, + multiLineInput: false, + requiredText: true, + hintText: "Confirm Password", + passwordMode: true, + autofillHints: const [AutofillHints.password], + validator: (value) { + return MihValidationServices() + .validatePassword(value); + }, + ), + //spacer + const SizedBox(height: 25), + // sign in button + Center( + child: MihButton( + onPressed: () { + if (_formKey.currentState!.validate()) { + submitFormInput(); + } else { + MihAlertServices() + .formNotFilledCompletely(context); + } + }, + buttonColor: MihColors.getGreenColor( + MzansiInnovationHub.of(context)!.theme.mode == + "Dark"), + width: 300, + child: Text( + "Reset Password", + style: TextStyle( + color: MihColors.getPrimaryColor( + MzansiInnovationHub.of(context)!.theme.mode == + "Dark"), + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/Frontend/lib/mih_services/mih_authentication_services.dart b/Frontend/lib/mih_services/mih_authentication_services.dart index 12c2bfa1..50c747aa 100644 --- a/Frontend/lib/mih_services/mih_authentication_services.dart +++ b/Frontend/lib/mih_services/mih_authentication_services.dart @@ -114,6 +114,54 @@ class MihAuthenticationServices { } } + Future forgotPassword( + String email, + ) async { + var response = await http.post( + Uri.parse("$baseAPI/auth/user/password/reset/token"), + body: '{"formFields": [{"id": "email","value": "$email"}]}', + headers: { + 'Content-type': 'application/json', + 'Accept': 'application/json', + }, + ); + if (response.statusCode == 200) { + var userSignedin = jsonDecode(response.body); + if (userSignedin["status"] == "OK") { + return true; + } else { + return false; + } + } else { + return false; + } + } + + Future resetPassword( + String token, + String password, + ) async { + var response = await http.post( + Uri.parse("$baseAPI/auth/user/password/reset"), + body: + '{"method": "token","formFields": [{"id": "password","value": "$password"}],"token": "$token"}', + headers: { + 'Content-type': 'application/json', + 'Accept': 'application/json', + }, + ); + if (response.statusCode == 200) { + var userSignedin = jsonDecode(response.body); + if (userSignedin["status"] == "OK") { + return true; + } else { + return false; + } + } else { + return false; + } + } + void internetConnectionPopUp(BuildContext context) { showDialog( context: context,