diff --git a/assets/icons/user_management.svg b/assets/icons/user_management.svg new file mode 100644 index 00000000..3255117a --- /dev/null +++ b/assets/icons/user_management.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lib/pages/home/bloc/home_bloc.dart b/lib/pages/home/bloc/home_bloc.dart index 04c35295..022354fa 100644 --- a/lib/pages/home/bloc/home_bloc.dart +++ b/lib/pages/home/bloc/home_bloc.dart @@ -95,9 +95,7 @@ class HomeBloc extends Bloc { title: 'Move in', icon: Assets.moveinIcon, active: true, - onPress: (context) { - context.go(RoutesConst.rolesAndPermissions); - }, + onPress: (context) {}, color: ColorsManager.primaryColor, ), HomeItemModel( diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart index ec3139b7..f0e3c9ad 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_bloc.dart @@ -22,9 +22,20 @@ class UsersBloc extends Bloc { on(_getRolePermission); on(_getPermissions); on(searchRolePermission); + on(_sendInvitUser); + on(_validateBasicsStep); + on(isCompleteRoleFun); + } + void _validateBasicsStep(ValidateBasicsStep event, Emitter emit) { + if (formKey.currentState?.validate() ?? false) { + emit(const BasicsStepValidState()); + } else { + emit(const BasicsStepInvalidState()); + } } List users = []; + String roleSelected = ''; Future _getUsers(GetUsers event, Emitter emit) async { emit(UsersLoadingState()); @@ -106,31 +117,42 @@ class UsersBloc extends Bloc { int numberSpaces = 0; int numberRole = 0; - isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { + // isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { + // emit(UsersLoadingState()); + // isCompleteBasics = firstNameController.text.isNotEmpty && + // lastNameController.text.isNotEmpty && + // emailController.text.isNotEmpty; + // emit(ChangeStatusSteps()); + // return isCompleteBasics; + // } + + bool isCompleteBasicsFun(CheckStepStatus event, Emitter emit) { emit(UsersLoadingState()); + + final emailRegex = RegExp( + r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', + ); isCompleteBasics = firstNameController.text.isNotEmpty && lastNameController.text.isNotEmpty && - emailController.text.isNotEmpty; + emailController.text.isNotEmpty && + emailRegex.hasMatch(emailController.text); + emit(ChangeStatusSteps()); - return isCompleteBasics; + return isCompleteBasics!; } void isCompleteSpacesFun( CheckSpacesStepStatus event, Emitter emit) { emit(UsersLoadingState()); - try { - List selectedIds = getSelectedIds(updatedCommunities); - isCompleteSpaces = selectedIds.isNotEmpty; - } catch (e) { - emit(ErrorState('Error while retrieving selected IDs: $e')); - return; - } - + List selectedIds = getSelectedIds(updatedCommunities); + isCompleteSpaces = selectedIds.isNotEmpty; emit(ChangeStatusSteps()); } - bool checkRolePermissions() { - return true; + void isCompleteRoleFun(CheckRoleStepStatus event, Emitter emit) { + emit(UsersLoadingState()); + isCompleteRolePermissions = roleSelected != ''; + emit(ChangeStatusSteps()); } Future> _fetchSpacesForCommunity( @@ -249,6 +271,7 @@ class UsersBloc extends Bloc { emit(UsersLoadingState()); permissions = await UserPermissionApi().fetchPermission( event.roleUuid == "" ? roles.first.uuid : event.roleUuid); + roleSelected = event.roleUuid!; emit(RolePermissionInitial()); } catch (e) { emit(ErrorState('Error loading communities and spaces: $e')); @@ -268,17 +291,22 @@ class UsersBloc extends Bloc { return anyMatch; } - _sendInvitUser(List nodes, String searchTerm) async { - emit(UsersLoadingState()); - await UserPermissionApi().sendInviteUser( - email: emailController.text, - firstName: firstNameController.text, - jobTitle: jobTitleController.text, - lastName: lastNameController.text, - phoneNumber: phoneController.text, - roleUuid: '', - spaceUuids: selectedIds); - emit(RolePermissionInitial()); + _sendInvitUser(SendInviteUsers event, Emitter emit) async { + try { + emit(UsersLoadingState()); + List selectedIds = getSelectedIds(updatedCommunities); + await UserPermissionApi().sendInviteUser( + email: emailController.text, + firstName: firstNameController.text, + jobTitle: jobTitleController.text, + lastName: lastNameController.text, + phoneNumber: phoneController.text, + roleUuid: roleSelected, + spaceUuids: selectedIds); + emit(SaveState()); + } catch (e) { + emit(ErrorState('Error: $e')); + } } void searchRolePermission(SearchPermission event, Emitter emit) { diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart index 6f59b495..633c1ea5 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_event.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_event.dart @@ -12,12 +12,25 @@ class GetUsers extends UsersEvent { List get props => []; } +class SendInviteUsers extends UsersEvent { + const SendInviteUsers(); + @override + List get props => []; +} + class CheckSpacesStepStatus extends UsersEvent { const CheckSpacesStepStatus(); @override List get props => []; } +class CheckRoleStepStatus extends UsersEvent { + const CheckRoleStepStatus(); + @override + List get props => []; +} + + class LoadCommunityAndSpacesEvent extends UsersEvent { const LoadCommunityAndSpacesEvent(); @override @@ -85,3 +98,9 @@ class SelecteId extends UsersEvent { @override List get props => [nodes]; } + +class ValidateBasicsStep extends UsersEvent { + const ValidateBasicsStep(); + @override + List get props => []; +} diff --git a/lib/pages/roles_and_permission/users_page/bloc/users_status.dart b/lib/pages/roles_and_permission/users_page/bloc/users_status.dart index 5d1a68f6..b5fc01d7 100644 --- a/lib/pages/roles_and_permission/users_page/bloc/users_status.dart +++ b/lib/pages/roles_and_permission/users_page/bloc/users_status.dart @@ -9,6 +9,7 @@ final class UsersInitial extends UsersState { @override List get props => []; } + final class RolePermissionInitial extends UsersState { @override List get props => []; @@ -24,6 +25,11 @@ final class UsersLoadingState extends UsersState { List get props => []; } +final class SaveState extends UsersState { + @override + List get props => []; +} + final class UsersLoadedState extends UsersState { List users = []; UsersLoadedState({required this.users}); @@ -59,3 +65,15 @@ final class ChangeTapStatus extends UsersState { @override List get props => [select]; } + +final class BasicsStepValidState extends UsersState { + const BasicsStepValidState(); + @override + List get props => []; +} + +class BasicsStepInvalidState extends UsersState { + const BasicsStepInvalidState(); + @override + List get props => []; +} diff --git a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart index dc45cfdc..cae89058 100644 --- a/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart +++ b/lib/pages/roles_and_permission/users_page/view/add_user_dialog.dart @@ -88,37 +88,6 @@ class _AddNewUserDialogState extends State { child: _getFormContent(), ), const SizedBox(height: 20), - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text("Cancel"), - ), - ElevatedButton( - onPressed: () { - if (_blocRole.formKey.currentState - ?.validate() ?? - false) { - // Proceed to next step or finish - setState(() { - if (currentStep < 3) { - currentStep++; - } else { - Navigator.of(context).pop(); - } - }); - } - }, - child: Text(currentStep < 3 - ? "Next" - : "Finish"), - ), - ], - ), ], ), ), @@ -126,6 +95,53 @@ class _AddNewUserDialogState extends State { ], ), ), + const Divider(), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: const Text("Cancel"), + ), + InkWell( + onTap: () { + setState(() { + if (currentStep < 3) { + currentStep++; + if (currentStep == 2) { + _blocRole.add(const CheckStepStatus()); + } else if (currentStep == 3) { + _blocRole + .add(const CheckSpacesStepStatus()); + _blocRole + .add(const CheckSpacesStepStatus()); + } else { + _blocRole.add(const SendInviteUsers()); + } + } + }); + }, + child: Text( + currentStep < 3 ? "Next" : "Save", + style: TextStyle( + color: (_blocRole.isCompleteSpaces == false || + _blocRole.isCompleteBasics == + false || + _blocRole + .isCompleteRolePermissions == + false) && + currentStep == 3 + ? ColorsManager.grayColor + : ColorsManager.secondaryColor), + ), + ), + ], + ), + ), ], ), )); @@ -209,8 +225,12 @@ class _AddNewUserDialogState extends State { return GestureDetector( onTap: () { setState(() { + bloc.add(const CheckSpacesStepStatus()); currentStep = step; }); + Future.delayed(const Duration(milliseconds: 500), () { + bloc.add(const ValidateBasicsStep()); + }); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -270,6 +290,7 @@ class _AddNewUserDialogState extends State { setState(() { currentStep = step; bloc.add(const CheckSpacesStepStatus()); + bloc.add(const CheckStepStatus()); }); }, child: Column( diff --git a/lib/pages/roles_and_permission/users_page/view/basics_view.dart b/lib/pages/roles_and_permission/users_page/view/basics_view.dart index abf6642c..22398ed3 100644 --- a/lib/pages/roles_and_permission/users_page/view/basics_view.dart +++ b/lib/pages/roles_and_permission/users_page/view/basics_view.dart @@ -14,6 +14,9 @@ class BasicsView extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder(builder: (context, state) { final _blocRole = BlocProvider.of(context); + if (state is BasicsStepInvalidState) { + _blocRole.formKey.currentState?.validate(); + } return Form( key: _blocRole.formKey, child: ListView( @@ -184,7 +187,14 @@ class BasicsView extends StatelessWidget { ), validator: (value) { if (value == null || value.isEmpty) { - return 'Enter last name'; + return 'Enter Email Address'; + } + // Regular expression for email validation + final emailRegex = RegExp( + r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', + ); + if (!emailRegex.hasMatch(value)) { + return 'Enter a valid Email Address'; } return null; }, @@ -213,49 +223,6 @@ class BasicsView extends StatelessWidget { ), ], )), - // InternationalPhoneNumberInput( - // spaceBetweenSelectorAndTextField: 50, - // initialValue: PhoneNumber(isoCode: 'AE'), - // inputDecoration: inputTextFormDeco( - // hintText: "x xxx xxxx", - // ).copyWith( - // hintStyle: context.textTheme.bodyMedium?.copyWith( - // fontWeight: FontWeight.w400, - // fontSize: 12, - // color: ColorsManager.textGray), - // ), - // onInputChanged: (PhoneNumber number) { - // print(number.phoneNumber); - // }, - // onInputValidated: (bool value) { - // print(value); - // }, - // selectorConfig: const SelectorConfig( - // selectorType: PhoneInputSelectorType.BOTTOM_SHEET, - // useBottomSheetSafeArea: true, - // leadingPadding: 15, - // trailingSpace: false, - // setSelectorButtonAsPrefixIcon: true), - // ignoreBlank: true, - // autoValidateMode: AutovalidateMode.disabled, - - // selectorTextStyle: - // TextStyle(color: ColorsManager.blackColor), - // // initialValue: number, - // // textFieldController: controller, - // formatInput: true, - // keyboardType: const TextInputType.numberWithOptions( - // signed: true, decimal: true), - // // inputBorder: OutlineInputBorder( - // // borderSide: - // // BorderSide(color: Colors.black,)), - // onSaved: (PhoneNumber number) { - // print('On Saved: $number'); - // }, - // textStyle: - // const TextStyle(color: ColorsManager.blackColor), - // ), - IntlPhoneField( pickerDialogStyle: PickerDialogStyle(), dropdownIconPosition: IconPosition.leading, @@ -270,36 +237,12 @@ class BasicsView extends StatelessWidget { fontSize: 12, color: ColorsManager.textGray), ), - initialCountryCode: 'AE', style: TextStyle(color: Colors.black), onChanged: (phone) { print(phone.completeNumber); }, ) - - // Padding( - // padding: const EdgeInsets.all(8.0), - // child: TextFormField( - // style: const TextStyle(color: Colors.black), - // controller: _blocRole.phoneController, - // decoration: inputTextFormDeco( - // hintText: "05x xxx xxxx", - // ).copyWith( - // hintStyle: context.textTheme.bodyMedium?.copyWith( - // fontWeight: FontWeight.w400, - // fontSize: 12, - // color: ColorsManager.textGray), - // ), - // validator: (value) { - // if (value == null || value.isEmpty) { - // return 'Please enter a phone number'; - // } - // return null; - // }, - // keyboardType: TextInputType.phone, - // ), - // ), ], ), ), diff --git a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart index a8c186e8..9b7ab542 100644 --- a/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart +++ b/lib/pages/roles_and_permission/users_page/view/roles_and_permission.dart @@ -71,6 +71,7 @@ class RolesAndPermission extends StatelessWidget { const TextStyle(color: Colors.black), controller: _blocRole.firstNameController, onChanged: (value) { + _blocRole.add(SearchPermission( nodes: _blocRole.permissions, searchTerm: value)); diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index acb314d9..8b271d5e 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -389,10 +389,10 @@ class Assets { 'assets/icons/current_process_icon.svg'; static const String uncomplete_ProcessIcon = 'assets/icons/uncompleate_process_icon.svg'; - static const String wrongProcessIcon = - 'assets/icons/wrong_process_icon.svg'; - static const String arrowForward = - 'assets/icons/arrow_forward.svg'; - static const String arrowDown = - 'assets/icons/arrow_down.svg'; + static const String wrongProcessIcon = 'assets/icons/wrong_process_icon.svg'; + static const String arrowForward = 'assets/icons/arrow_forward.svg'; + static const String arrowDown = 'assets/icons/arrow_down.svg'; + + static const String userManagement = 'assets/icons/user_management.svg'; } +//user_management.svg diff --git a/lib/utils/user_drop_down_menu.dart b/lib/utils/user_drop_down_menu.dart index 3a0c4194..5bdc18fd 100644 --- a/lib/utils/user_drop_down_menu.dart +++ b/lib/utils/user_drop_down_menu.dart @@ -53,7 +53,8 @@ class _UserDropdownMenuState extends State { } Future _showPopupMenu(BuildContext context) async { - final RenderBox overlay = Overlay.of(context).context.findRenderObject() as RenderBox; + final RenderBox overlay = + Overlay.of(context).context.findRenderObject() as RenderBox; final RelativeRect position = RelativeRect.fromRect( Rect.fromLTRB( overlay.size.width, @@ -86,11 +87,13 @@ class _UserDropdownMenuState extends State { ), ), PopupMenuItem( - onTap: () {}, + onTap: () { + context.go(RoutesConst.rolesAndPermissions); + }, child: ListTile( - leading: SvgPicture.asset(Assets.settings), + leading: SvgPicture.asset(Assets.userManagement), title: Text( - "Settings", + "User Management", style: context.textTheme.bodyMedium, ), ), @@ -107,7 +110,8 @@ class _UserDropdownMenuState extends State { height: 200, width: 400, child: Padding( - padding: const EdgeInsets.only(top: 24, left: 24, right: 24), + padding: + const EdgeInsets.only(top: 24, left: 24, right: 24), child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ @@ -120,7 +124,10 @@ class _UserDropdownMenuState extends State { padding: const EdgeInsets.only(top: 16), child: Text( 'Log out of your Syncrow account', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( fontSize: 14, fontWeight: FontWeight.w400, color: Colors.black, @@ -151,11 +158,15 @@ class _UserDropdownMenuState extends State { ), Expanded( child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, children: [ Text( '${widget.user?.firstName ?? ''} ${widget.user?.lastName}', - style: Theme.of(context).textTheme.titleMedium!.copyWith( + style: Theme.of(context) + .textTheme + .titleMedium! + .copyWith( color: Colors.black, fontWeight: FontWeight.bold, fontSize: 20, @@ -163,7 +174,10 @@ class _UserDropdownMenuState extends State { ), Text( ' ${widget.user?.email}', - style: Theme.of(context).textTheme.bodySmall!.copyWith( + style: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( color: Colors.black, ), ), @@ -189,7 +203,10 @@ class _UserDropdownMenuState extends State { elevation: 1, child: Text( 'Cancel', - style: Theme.of(context).textTheme.bodyMedium!.copyWith( + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( fontSize: 12, color: Colors.black, ), @@ -211,8 +228,10 @@ class _UserDropdownMenuState extends State { elevation: 1, child: Text( 'Logout', - style: - Theme.of(context).textTheme.bodyMedium!.copyWith(fontSize: 12, color: Colors.white), + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(fontSize: 12, color: Colors.white), ), ), ),