diff --git a/assets/images/facebook.svg b/assets/images/facebook.svg new file mode 100644 index 00000000..929d3861 --- /dev/null +++ b/assets/images/facebook.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/google.svg b/assets/images/google.svg new file mode 100644 index 00000000..27788e84 --- /dev/null +++ b/assets/images/google.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/images/lift_line.png b/assets/images/lift_line.png new file mode 100644 index 00000000..8dacf6d7 Binary files /dev/null and b/assets/images/lift_line.png differ diff --git a/assets/images/right_line.png b/assets/images/right_line.png new file mode 100644 index 00000000..805d1c0a Binary files /dev/null and b/assets/images/right_line.png differ diff --git a/lib/main.dart b/lib/main.dart index 09a1c6ab..629a8725 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,6 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/auth/view/login_page.dart'; +import 'package:syncrow_web/pages/auth/login/view/login_page.dart'; import 'package:syncrow_web/services/locator.dart'; void main() { diff --git a/lib/pages/auth/forget_password/bloc/forget_password_bloc.dart b/lib/pages/auth/forget_password/bloc/forget_password_bloc.dart new file mode 100644 index 00000000..07b61a24 --- /dev/null +++ b/lib/pages/auth/forget_password/bloc/forget_password_bloc.dart @@ -0,0 +1,195 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/auth/forget_password/bloc/forget_password_event.dart'; +import 'package:syncrow_web/pages/auth/forget_password/bloc/forget_password_state.dart'; +import 'package:syncrow_web/services/auth_api.dart'; + +class ForgetPasswordBloc + extends Bloc { + ForgetPasswordBloc() : super(InitialForgetState()) { + on(changePassword); + on(_onStartTimer); + on(_onStopTimer); + on(_onUpdateTimer); + } + + void _onUpdateTimer(UpdateTimerEvent event, Emitter emit) { + emit(TimerState( + isButtonEnabled: event.isButtonEnabled, + remainingTime: event.remainingTime)); + } + + final TextEditingController emailController = TextEditingController(); + final TextEditingController passwordController = TextEditingController(); + final TextEditingController otp = TextEditingController(); + final formKey = GlobalKey(); + + Timer? _timer; + int _remainingTime = 0; + + Future _onStartTimer( + StartTimerEvent event, Emitter emit) async { + if (_validateInputs(emit)) return; + print("StartTimerEvent received"); + if (_timer != null && _timer!.isActive) { + print("Timer is already active"); + return; + } + _remainingTime = 60; + add(UpdateTimerEvent( + remainingTime: _remainingTime, isButtonEnabled: false)); + print("Timer started, initial remaining time: $_remainingTime"); + await AuthenticationAPI.sendOtp(email: emailController.text); + _timer = Timer.periodic(const Duration(seconds: 1), (timer) { + _remainingTime--; + print("Timer tick, remaining time: $_remainingTime"); // Debug print + if (_remainingTime <= 0) { + _timer?.cancel(); + add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true)); + print("Timer finished"); // Debug print + } else { + add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false)); + } + }); + } + + void _onStopTimer(StopTimerEvent event, Emitter emit) { + _timer?.cancel(); + emit(TimerState(isButtonEnabled: true, remainingTime: 0)); + } + + Future changePassword( + ChangePasswordEvent event, Emitter emit) async { + try { + emit(LoadingForgetState()); + await AuthenticationAPI.forgetPassword( + password: passwordController.text, email: emailController.text); + emit(SuccessForgetState()); + } catch (failure) { + print(failure); + emit(FailureForgetState(error: failure.toString())); + } + } + + String? validateEmail(String? value) { + if (value == null || value.isEmpty) { + return 'Email is required'; + } else if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) { + return 'Enter a valid email address'; + } + return null; + } + + String? validateRegion(String? value) { + if (value == null || value.isEmpty) { + return 'Please select a region'; + } + return null; + } + + String? otpValidate(String? value) { + if (value == null || value.isEmpty) { + return 'Please select a region'; + } + return null; + } + + String? passwordValidator(String? value) { + if (value == null || value.isEmpty) { + return 'Please enter your password'; + } + List validationErrors = []; + if (!RegExp(r'^(?=.*[a-z])').hasMatch(value)) { + validationErrors.add(' - one lowercase letter'); + } + if (!RegExp(r'^(?=.*[A-Z])').hasMatch(value)) { + validationErrors.add(' - one uppercase letter'); + } + if (!RegExp(r'^(?=.*\d)').hasMatch(value)) { + validationErrors.add(' - one number'); + } + if (!RegExp(r'^(?=.*[@$!%*?&])').hasMatch(value)) { + validationErrors.add(' - one special character'); + } + if (value.length < 8) { + validationErrors.add(' - minimum 8 characters'); + } + if (validationErrors.isNotEmpty) { + return 'Password must contain at least:\n${validationErrors.join('\n')}'; + } + return null; + } + + final List regions = [ + 'North America', + 'South America', + 'Europe', + 'Asia', + 'Africa', + 'Australia', + 'Antarctica', + ]; + + + bool _validateInputs(Emitter emit) { + emit(LoadingForgetState()); + final nameError = validateEmail(emailController.text); + if (nameError != null) { + emit(FailureForgetState(error:nameError )) ; + // CustomSnackBar.displaySnackBar(nameError); + return true; + } + return false; + } +} + + + + + +// +// +// _login(LoginButtonPressed event, Emitter emit) async { +// emit(LoginLoading()); +// if(isChecked) { +// try { +// if (event.username.isEmpty || event.password.isEmpty) { +// CustomSnackBar.displaySnackBar('Please enter your credentials'); +// emit(const LoginFailure(error: 'Something went wrong')); +// return; +// } +// token = await AuthenticationAPI.loginWithEmail( +// model: LoginWithEmailModel( +// email: event.username, +// password: event.password, +// ), +// ); +// } +// catch (failure) { +// emit(const LoginFailure(error: 'Something went wrong')); +// // emit(LoginFailure(error: failure.toString())); +// return; +// } +// if (token.accessTokenIsNotEmpty) { +// debugPrint('token: ${token.accessToken}'); +// FlutterSecureStorage storage = const FlutterSecureStorage(); +// await storage.write( +// key: Token.loginAccessTokenKey, value: token.accessToken); +// +// const FlutterSecureStorage().write( +// key: UserModel.userUuidKey, +// value: Token.decodeToken(token.accessToken)['uuid'].toString() +// ); +// user = UserModel.fromToken(token); +// emailController.clear(); +// passwordController.clear(); +// emit(LoginSuccess()); +// } else { +// emit(const LoginFailure(error: 'Something went wrong')); +// } +// } +// else{ +// emit(const LoginFailure(error: 'Accept terms and condition')); +// } +// } diff --git a/lib/pages/auth/forget_password/bloc/forget_password_event.dart b/lib/pages/auth/forget_password/bloc/forget_password_event.dart new file mode 100644 index 00000000..3a42ef76 --- /dev/null +++ b/lib/pages/auth/forget_password/bloc/forget_password_event.dart @@ -0,0 +1,26 @@ + +import 'package:equatable/equatable.dart'; + +abstract class ForgetPasswordEvent extends Equatable { +const ForgetPasswordEvent(); + @override + List get props => []; +} + +class GetCodeEvent extends ForgetPasswordEvent{} + +class SubmitEvent extends ForgetPasswordEvent{} + +class StartTimerEvent extends ForgetPasswordEvent{} + +class StopTimerEvent extends ForgetPasswordEvent{} + +class UpdateTimerEvent extends ForgetPasswordEvent { + final int remainingTime; + final bool isButtonEnabled; + const UpdateTimerEvent({required this.remainingTime, required this.isButtonEnabled}); +} + +class ChangePasswordEvent extends ForgetPasswordEvent{} + +class SendOtpEvent extends ForgetPasswordEvent{} \ No newline at end of file diff --git a/lib/pages/auth/forget_password/bloc/forget_password_state.dart b/lib/pages/auth/forget_password/bloc/forget_password_state.dart new file mode 100644 index 00000000..28d6116b --- /dev/null +++ b/lib/pages/auth/forget_password/bloc/forget_password_state.dart @@ -0,0 +1,35 @@ + + +import 'package:equatable/equatable.dart'; + +class ForgetPasswordState extends Equatable{ + const ForgetPasswordState(); + + @override + List get props => []; + +} + +class InitialForgetState extends ForgetPasswordState{} + +class LoadingForgetState extends ForgetPasswordState{} + +class SuccessForgetState extends ForgetPasswordState{} + +class FailureForgetState extends ForgetPasswordState { + final String error; + const FailureForgetState({required this.error}); + @override + List get props => [error]; +} + + +class TimerState extends ForgetPasswordState { + final bool isButtonEnabled ; + final int remainingTime; + + const TimerState({required this.isButtonEnabled, required this.remainingTime}); + + @override + List get props => [isButtonEnabled, remainingTime]; +} \ No newline at end of file diff --git a/lib/pages/auth/forget_password/view/forget_password_mobile_page.dart b/lib/pages/auth/forget_password/view/forget_password_mobile_page.dart new file mode 100644 index 00000000..2aab5894 --- /dev/null +++ b/lib/pages/auth/forget_password/view/forget_password_mobile_page.dart @@ -0,0 +1,11 @@ + +import 'package:flutter/material.dart'; + +class ForgetPasswordMobilePage extends StatelessWidget { + const ForgetPasswordMobilePage({super.key}); + + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} diff --git a/lib/pages/auth/forget_password/view/forget_password_page.dart b/lib/pages/auth/forget_password/view/forget_password_page.dart new file mode 100644 index 00000000..a80d515a --- /dev/null +++ b/lib/pages/auth/forget_password/view/forget_password_page.dart @@ -0,0 +1,20 @@ + +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/auth/forget_password/view/forget_password_mobile_page.dart'; +import 'package:syncrow_web/pages/auth/forget_password/view/forget_password_web_page.dart'; + +import 'package:syncrow_web/utils/responsive_layout.dart'; + + +class ForgetPasswordPage extends StatelessWidget { + const ForgetPasswordPage({super.key}); + + @override + Widget build(BuildContext context) { + return const ResponsiveLayout( + desktopBody: ForgetPasswordWebPage(), + mobileBody:ForgetPasswordMobilePage() + ); + } +} + diff --git a/lib/pages/auth/forget_password/view/forget_password_web_page.dart b/lib/pages/auth/forget_password/view/forget_password_web_page.dart new file mode 100644 index 00000000..2d65c201 --- /dev/null +++ b/lib/pages/auth/forget_password/view/forget_password_web_page.dart @@ -0,0 +1,271 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/auth/forget_password/bloc/forget_password_bloc.dart'; +import 'package:syncrow_web/pages/auth/forget_password/bloc/forget_password_event.dart'; +import 'package:syncrow_web/pages/auth/forget_password/bloc/forget_password_state.dart'; +import 'package:syncrow_web/pages/auth/login/view/login_page.dart'; +import 'package:syncrow_web/pages/common/default_button.dart'; +import 'package:syncrow_web/pages/common/first_layer.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class ForgetPasswordWebPage extends StatelessWidget { + const ForgetPasswordWebPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: BlocProvider( + create: (context) => ForgetPasswordBloc(), + child: BlocConsumer( + listener: (context, state) { + if (state is LoadingForgetState) { + } else if (state is FailureForgetState) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.error), + ), + ); + } + }, + builder: (context, state) { + if (state is LoadingForgetState) { + return const Center(child: CircularProgressIndicator()); + } else { + return _buildForm(context, state); + } + }, + ), + ), + ); + } + + Widget _buildForm(BuildContext context, ForgetPasswordState state) { + final forgetBloc = BlocProvider.of(context); + return FirstLayer( + second: Container( + margin: const EdgeInsets.all(50), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.3), + borderRadius: const BorderRadius.all(Radius.circular(20)), + ), + child: Center( + child: ListView( + shrinkWrap: true, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Spacer(), + Expanded( + flex: 2, + child: SvgPicture.asset( + Assets.loginLogo, + ), + ), + const Spacer(), + Container( + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.1), + borderRadius: const BorderRadius.all(Radius.circular(30)), + border: Border.all(color: ColorsManager.graysColor.withOpacity(0.2)), + ), + child: Form( + key: forgetBloc.formKey, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 15), + const Text( + 'Forget Password', + style: TextStyle( + color: Colors.white, + fontSize: 24, + fontWeight: FontWeight.bold), + ), + Text( + 'Please fill in your account information to retrieve your password', + style: smallTextStyle, + ), + const SizedBox(height: 30), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Country/Region", + style: smallTextStyle, + ), + SizedBox( + width: MediaQuery.of(context).size.width * 0.2, + child: DropdownButtonFormField( + validator: forgetBloc.validateRegion, + icon: const Icon( + Icons.keyboard_arrow_down_outlined, + ), + decoration: textBoxDecoration()!.copyWith( + hintText: null, + ), + hint: const Align( + alignment: Alignment.centerLeft, + child: Text( + 'Select your region/country', + textAlign: TextAlign.center, + ), + ), + isDense: true, + style: const TextStyle(color: Colors.black), + items: forgetBloc.regions.map((String region) { + return DropdownMenuItem( + value: region, + child: Text(region), + ); + }).toList(), + onChanged: (String? value) { + print(value); + }, + ), + ) + ], + ), + const SizedBox(height: 20.0), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Account", + style: smallTextStyle, + ), + SizedBox( + width: MediaQuery.sizeOf(context).width * 0.2, + child: TextFormField( + validator: forgetBloc.validateEmail, + controller: forgetBloc.emailController, + decoration: textBoxDecoration()!.copyWith(hintText: 'Enter your email'), + style: const TextStyle(color: Colors.black), + ), + ), + ], + ), + const SizedBox(height: 20.0), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text("One Time Password", + style: smallTextStyle,), + SizedBox( + width: MediaQuery.sizeOf(context).width * 0.2, + child: TextFormField( + validator: forgetBloc.otpValidate, + keyboardType: TextInputType.visiblePassword, + controller: forgetBloc.otp, + decoration: textBoxDecoration()!.copyWith( + hintText: 'Enter Code', + suffixIcon: SizedBox( + width: 100, + child: Center( + child: InkWell( + onTap: () { + BlocProvider.of(context).add(StartTimerEvent()); + }, + child: Text( + 'Get Code ${state is TimerState && !state.isButtonEnabled ? "(${state.remainingTime.toString()})" : ""}', + style: TextStyle( + color: state is TimerState && !state.isButtonEnabled + ? Colors.grey + : ColorsManager.btnColor, + ), + ), + ), + ), + ), + ), + style: const TextStyle(color: Colors.black), + ), + ), + ], + ), + const SizedBox(height: 20.0), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text("Password", + style: smallTextStyle,), + SizedBox( + width: MediaQuery.sizeOf(context).width * 0.2, + child: TextFormField( + validator: forgetBloc.passwordValidator, + keyboardType: TextInputType.visiblePassword, + controller: forgetBloc.passwordController, + decoration: textBoxDecoration()!.copyWith( + hintText: 'At least 8 characters', + ), + style: const TextStyle(color: Colors.black), + ), + ), + ], + ), + const SizedBox( + height: 10, + ), + const SizedBox(height: 20.0), + SizedBox( + width: MediaQuery.sizeOf(context).width * 0.2, + child: DefaultButton( + backgroundColor: ColorsManager.btnColor, + child: const Text('Submit'), + onPressed: () { + if (forgetBloc.formKey.currentState!.validate()) { + forgetBloc.add(ChangePasswordEvent()); + } + }, + ), + ), + SizedBox(height: 10,), + SizedBox( + width: MediaQuery.sizeOf(context).width * 0.2, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Flexible( + child: Text( + "Do you have an account? ", + style: TextStyle(color: Colors.white), + )), + InkWell( + onTap: () { + Navigator.pop(context); + }, + child: const Flexible( + child: Text( + "Sign in", + )), + ), + ], + ), + ) + ], + ), + ), + ), + ), + const Spacer(), + ], + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/auth/bloc/login_bloc.dart b/lib/pages/auth/login/bloc/login_bloc.dart similarity index 52% rename from lib/pages/auth/bloc/login_bloc.dart rename to lib/pages/auth/login/bloc/login_bloc.dart index 83443eea..080dfd01 100644 --- a/lib/pages/auth/bloc/login_bloc.dart +++ b/lib/pages/auth/login/bloc/login_bloc.dart @@ -1,58 +1,156 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:syncrow_web/pages/auth/bloc/login_event.dart'; -import 'package:syncrow_web/pages/auth/bloc/login_state.dart'; -import 'package:syncrow_web/pages/auth/model/login_with_email_model.dart'; -import 'package:syncrow_web/pages/auth/model/token.dart'; -import 'package:syncrow_web/pages/auth/model/user_model.dart'; +import 'package:syncrow_web/pages/auth/login/bloc/login_event.dart'; +import 'package:syncrow_web/pages/auth/login/bloc/login_state.dart'; +import 'package:syncrow_web/pages/auth/login/model/login_with_email_model.dart'; +import 'package:syncrow_web/pages/auth/login/model/token.dart'; +import 'package:syncrow_web/pages/auth/login/model/user_model.dart'; import 'package:syncrow_web/services/auth_api.dart'; import 'package:syncrow_web/utils/snack_bar.dart'; class LoginBloc extends Bloc { LoginBloc() : super(LoginInitial()) { on(_login); + on(checkBoxTgl); } - final TextEditingController emailController = TextEditingController(); final TextEditingController passwordController = TextEditingController(); + final formKey = GlobalKey(); + bool isChecked = false; + bool obscureText = true; + + _login(LoginButtonPressed event, Emitter emit) async { + emit(LoginLoading()); + if(isChecked) { + try { + if (event.username.isEmpty || event.password.isEmpty) { + CustomSnackBar.displaySnackBar('Please enter your credentials'); + emit(const LoginFailure(error: 'Something went wrong')); + return; + } + token = await AuthenticationAPI.loginWithEmail( + model: LoginWithEmailModel( + email: event.username, + password: event.password, + ), + ); + } + catch (failure) { + emit(const LoginFailure(error: 'Something went wrong')); + // emit(LoginFailure(error: failure.toString())); + return; + } + if (token.accessTokenIsNotEmpty) { + debugPrint('token: ${token.accessToken}'); + FlutterSecureStorage storage = const FlutterSecureStorage(); + await storage.write( + key: Token.loginAccessTokenKey, value: token.accessToken); + + const FlutterSecureStorage().write( + key: UserModel.userUuidKey, + value: Token.decodeToken(token.accessToken)['uuid'].toString() + ); + user = UserModel.fromToken(token); + emailController.clear(); + passwordController.clear(); + emit(LoginSuccess()); + } else { + emit(const LoginFailure(error: 'Something went wrong')); + } + } + else{ + emit(const LoginFailure(error: 'Accept terms and condition')); + } + } + + checkBoxTgl(CheckBoxEvent event, Emitter emit,){ + emit(LoginLoading()); + isChecked = event.newValue!; + emit(LoginInitial()); + } + + + void launchURL(String url) { + if (kDebugMode) { + print('Launching URL: $url'); + } + } + + - String fullName = ''; - String email = ''; - String forgetPasswordEmail = ''; - String signUpPassword = ''; String newPassword = ''; String maskedEmail = ''; String otpCode = ''; - final loginFormKey = GlobalKey(); - final signUpFormKey = GlobalKey(); - final checkEmailFormKey = GlobalKey(); - final createNewPasswordKey = GlobalKey(); static Token token = Token.emptyConstructor(); static UserModel? user; - bool isPasswordVisible = false; bool showValidationMessage = false; - /////////////////////////////////////VALIDATORS///////////////////////////////////// - String? passwordValidator(String? value) { - if (value != null) { - if (value.isEmpty) { - return 'Please enter your password'; - } - if (value.isNotEmpty) { - if (!RegExp(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$') - .hasMatch(value)) { - return 'Password must contain at least:\n - one uppercase letter.\n - one lowercase letter.\n - one number. \n - special character'; - } - } + + String? validatePassword(String? value) { + if (value == null || value.isEmpty) { + return 'Password is required'; + } else if (value.length < 8) { + return 'Password must be at least 8 characters'; } return null; } + String? validateEmail(String? value) { + if (value == null || value.isEmpty) { + return 'Email is required'; + } else if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) { + return 'Enter a valid email address'; + } + return null; + } + + + + + String? validateRegion(String? value) { + if (value == null || value.isEmpty) { + return 'Please select a region'; + } + return null; + } + + String? passwordValidator(String? value) { + if (value == null || value.isEmpty) { + return 'Please enter your password'; + } + + List validationErrors = []; + + if (!RegExp(r'^(?=.*[a-z])').hasMatch(value)) { + validationErrors.add(' - one lowercase letter'); + } + if (!RegExp(r'^(?=.*[A-Z])').hasMatch(value)) { + validationErrors.add(' - one uppercase letter'); + } + if (!RegExp(r'^(?=.*\d)').hasMatch(value)) { + validationErrors.add(' - one number'); + } + if (!RegExp(r'^(?=.*[@$!%*?&])').hasMatch(value)) { + validationErrors.add(' - one special character'); + } + if (value.length < 8) { + validationErrors.add(' - minimum 8 characters'); + } + + if (validationErrors.isNotEmpty) { + return 'Password must contain at least:\n' + validationErrors.join('\n'); + } + + return null; + } + + String? fullNameValidator(String? value) { if (value == null) return 'Full name is required'; @@ -80,14 +178,7 @@ class LoginBloc extends Bloc { return null; } - String? reEnterPasswordCheck(String? value) { - passwordValidator(value); - if (signUpPassword == value) { - return null; - } else { - return 'Passwords do not match'; - } - } + String? reEnterPasswordCheckForgetPass(String? value) { passwordValidator(value); @@ -136,43 +227,16 @@ class LoginBloc extends Bloc { return '$maskedLocalPart@$domainPart'; } - _login(LoginButtonPressed event, Emitter emit) async { - emit(LoginLoading()); - try { - if (event.username.isEmpty ||event.password.isEmpty) { - CustomSnackBar.displaySnackBar('Please enter your credentials'); - emit(const LoginFailure(error: 'Something went wrong')); - return; - } - token = await AuthenticationAPI.loginWithEmail( - model: LoginWithEmailModel( - email: event.username, - password: event.password, - ), - ); - } catch (failure) { - emit( LoginFailure(error: failure.toString())); - return; - } - if (token.accessTokenIsNotEmpty) { - debugPrint('token: ${token.accessToken}'); - FlutterSecureStorage storage = const FlutterSecureStorage(); - await storage.write(key: Token.loginAccessTokenKey, value: token.accessToken); - - const FlutterSecureStorage().write( - key: UserModel.userUuidKey, - value: Token.decodeToken(token.accessToken)['uuid'].toString() - ); - - user = UserModel.fromToken(token); - emailController.clear(); - passwordController.clear(); - emit(LoginSuccess()); - } else { - emit(const LoginFailure(error: 'Something went wrong')); - } - } + final List regions = [ + 'North America', + 'South America', + 'Europe', + 'Asia', + 'Africa', + 'Australia', + 'Antarctica', + ]; // signUp() async { // emit(LoginLoading()); @@ -198,4 +262,7 @@ class LoginBloc extends Bloc { // } // } + + + } diff --git a/lib/pages/auth/bloc/login_event.dart b/lib/pages/auth/login/bloc/login_event.dart similarity index 69% rename from lib/pages/auth/bloc/login_event.dart rename to lib/pages/auth/login/bloc/login_event.dart index 8c46f607..7bfdb1bc 100644 --- a/lib/pages/auth/bloc/login_event.dart +++ b/lib/pages/auth/login/bloc/login_event.dart @@ -16,3 +16,13 @@ class LoginButtonPressed extends LoginEvent { @override List get props => [username, password]; } + + +class CheckBoxEvent extends LoginEvent { + final bool? newValue; + + const CheckBoxEvent({required this.newValue,}); + + @override + List get props => [newValue!,]; +} diff --git a/lib/pages/auth/bloc/login_state.dart b/lib/pages/auth/login/bloc/login_state.dart similarity index 64% rename from lib/pages/auth/bloc/login_state.dart rename to lib/pages/auth/login/bloc/login_state.dart index 6d5cff8a..f82d12a5 100644 --- a/lib/pages/auth/bloc/login_state.dart +++ b/lib/pages/auth/login/bloc/login_state.dart @@ -21,3 +21,19 @@ class LoginFailure extends LoginState { @override List get props => [error]; } + + + +class LoginValid extends LoginState {} + +class LoginInvalid extends LoginState { + final String error; + + const LoginInvalid({required this.error}); + + @override + List get props => [error]; +} + + +// class LoginState extends LoginState {} diff --git a/lib/pages/auth/model/login_with_email_model.dart b/lib/pages/auth/login/model/login_with_email_model.dart similarity index 100% rename from lib/pages/auth/model/login_with_email_model.dart rename to lib/pages/auth/login/model/login_with_email_model.dart diff --git a/lib/pages/auth/model/signup_model.dart b/lib/pages/auth/login/model/signup_model.dart similarity index 100% rename from lib/pages/auth/model/signup_model.dart rename to lib/pages/auth/login/model/signup_model.dart diff --git a/lib/pages/auth/model/token.dart b/lib/pages/auth/login/model/token.dart similarity index 100% rename from lib/pages/auth/model/token.dart rename to lib/pages/auth/login/model/token.dart diff --git a/lib/pages/auth/model/user_model.dart b/lib/pages/auth/login/model/user_model.dart similarity index 95% rename from lib/pages/auth/model/user_model.dart rename to lib/pages/auth/login/model/user_model.dart index 5ca08d1c..44bc6b0d 100644 --- a/lib/pages/auth/model/user_model.dart +++ b/lib/pages/auth/login/model/user_model.dart @@ -1,4 +1,5 @@ -import 'package:syncrow_web/pages/auth/model/token.dart'; + +import 'package:syncrow_web/pages/auth/login/model/token.dart'; class UserModel { static String userUuidKey = 'userUuid'; diff --git a/lib/pages/auth/model/verify_code.dart b/lib/pages/auth/login/model/verify_code.dart similarity index 100% rename from lib/pages/auth/model/verify_code.dart rename to lib/pages/auth/login/model/verify_code.dart diff --git a/lib/pages/auth/login/view/login_mobile_page.dart b/lib/pages/auth/login/view/login_mobile_page.dart new file mode 100644 index 00000000..b1dff73b --- /dev/null +++ b/lib/pages/auth/login/view/login_mobile_page.dart @@ -0,0 +1,426 @@ +import 'dart:ui'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/pages/auth/forget_password/view/forget_password_page.dart'; +import 'package:syncrow_web/pages/auth/login/bloc/login_bloc.dart'; +import 'package:syncrow_web/pages/auth/login/bloc/login_event.dart'; +import 'package:syncrow_web/pages/auth/login/bloc/login_state.dart'; +import 'package:syncrow_web/pages/common/default_button.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/pages/home/view/home_page.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class LoginMobilePage extends StatelessWidget { + const LoginMobilePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: BlocProvider( + create: (context) => LoginBloc(), + child: BlocConsumer( + listener: (context, state) { + if (state is LoginSuccess) { + // Navigate to home screen after successful login + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (context) => const HomePage()), + ); + } else if (state is LoginFailure) { + // Show error message + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.error), + ), + ); + } + }, + builder: (context, state) { + if (state is LoginLoading) { + return const Center(child: CircularProgressIndicator()); + } else { + return _buildLoginForm(context); + } + }, + ), + ), + ); + } + + Widget _buildLoginForm(BuildContext context) { + final loginBloc = BlocProvider.of(context); + final TextEditingController _usernameController = TextEditingController(); + final TextEditingController _passwordController = TextEditingController(); + return Center( + child: Stack( + children: [ + Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage( + Assets.background, + ), + fit: BoxFit.cover, + ), + ), + ), + Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage(Assets.vector), + fit: BoxFit.cover, + opacity: 0.9, + ), + ), + ), + SingleChildScrollView( + child: Container( + margin: const EdgeInsets.all(50), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.3), + borderRadius: const BorderRadius.all(Radius.circular(20))), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.all(15.0), + child: SvgPicture.asset( + Assets.loginLogo, + ), + ), + Container( + margin: EdgeInsets.all(30), + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.1), + borderRadius: + const BorderRadius.all(Radius.circular(30)), + border: Border.all( + color: + ColorsManager.graysColor.withOpacity(0.2))), + child: Form( + key: loginBloc.formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 15), + const Text( + 'Login', + style: TextStyle( + color: Colors.white, + fontSize: 24, + fontWeight: FontWeight.bold), + ), + const SizedBox(height: 30), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Country/Region", + style: smallTextStyle, + ), + SizedBox( + child: DropdownButtonFormField( + validator: loginBloc.validateRegion, + icon: const Icon( + Icons.keyboard_arrow_down_outlined, + ), + decoration: textBoxDecoration()!.copyWith( + hintText: null, + ), + hint: const Align( + alignment: Alignment.centerLeft, + child: Text( + 'Select your region/country', + textAlign: TextAlign.center, + ), + ), + isDense: true, + style: const TextStyle(color: Colors.black), + items: + loginBloc.regions.map((String region) { + return DropdownMenuItem( + value: region, + child: Text(region), + ); + }).toList(), + onChanged: (String? value) { + print(value); + }, + ), + ) + ], + ), + const SizedBox(height: 20.0), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Email", + style: smallTextStyle, + ), + SizedBox( + child: TextFormField( + validator: loginBloc.validateEmail, + controller: loginBloc.emailController, + decoration: textBoxDecoration()! + .copyWith(hintText: 'Enter your email'), + style: const TextStyle(color: Colors.black), + ), + ), + ], + ), + const SizedBox(height: 20.0), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Password", + style: smallTextStyle, + ), + SizedBox( + child: TextFormField( + validator: loginBloc.validatePassword, + obscureText: loginBloc.obscureText, + keyboardType: TextInputType.visiblePassword, + controller: loginBloc.passwordController, + decoration: textBoxDecoration()!.copyWith( + hintText: 'At least 8 characters', + ), + style: const TextStyle(color: Colors.black), + ), + ), + ], + ), + const SizedBox( + height: 10, + ), + SizedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + InkWell( + onTap: () { + Navigator.of(context) + .push(MaterialPageRoute( + builder: (context) => + const ForgetPasswordPage(), + )); + }, + child: Text( + "Forgot Password?", + style: smallTextStyle, + ), + ), + ], + ), + ), + Row( + children: [ + Transform.scale( + scale: 1.2, // Adjust the scale as needed + child: Checkbox( + fillColor: MaterialStateProperty.all( + Colors.white), + activeColor: Colors.white, + value: loginBloc.isChecked, + checkColor: Colors.black, + shape: const CircleBorder(), + onChanged: (bool? newValue) { + loginBloc.add( + CheckBoxEvent(newValue: newValue)); + }, + ), + ), + SizedBox( + width: MediaQuery.sizeOf(context).width * 0.5, + child: RichText( + text: TextSpan( + text: 'Agree to ', + style: + const TextStyle(color: Colors.white), + children: [ + TextSpan( + text: '(Terms of Service)', + style: const TextStyle( + color: Colors.black), + recognizer: TapGestureRecognizer() + ..onTap = () { + loginBloc.launchURL( + 'https://example.com/terms'); + }, + ), + TextSpan( + text: ' (Legal Statement)', + style: const TextStyle( + color: Colors.black), + recognizer: TapGestureRecognizer() + ..onTap = () { + loginBloc.launchURL( + 'https://example.com/legal'); + }, + ), + TextSpan( + text: ' (Privacy Statement)', + style: const TextStyle( + color: Colors.black), + recognizer: TapGestureRecognizer() + ..onTap = () { + loginBloc.launchURL( + 'https://example.com/privacy'); + }, + ), + ], + ), + ), + ), + ], + ), + const SizedBox(height: 20.0), + SizedBox( + child: DefaultButton( + backgroundColor: loginBloc.isChecked + ? ColorsManager.btnColor + : ColorsManager.grayColor, + child: const Text('Sign in'), + onPressed: () { + if (loginBloc.formKey.currentState! + .validate()) { + loginBloc.add( + LoginButtonPressed( + username: + loginBloc.emailController.text, + password: + loginBloc.passwordController.text, + ), + ); + } + }, + ), + ), + // Padding( + // padding: const EdgeInsets.all(5.0), + // child: SizedBox( + // width: MediaQuery.sizeOf(context).width * 0.2, + // child: Row( + // mainAxisAlignment: MainAxisAlignment.center, + // crossAxisAlignment: CrossAxisAlignment.center, + // children: [ + // Expanded(child: Image.asset(Assets.liftLine)), + // Expanded( + // child: Padding( + // padding: const EdgeInsets.all(5.0), + // child: Text('Or sign in with', + // style: smallTextStyle.copyWith(fontSize: 10), + // ), + // ) + // ), + // Expanded(child: Image.asset(Assets.rightLine)), + // ], + // ), + // ), + // ), + // SizedBox( + // width: MediaQuery.sizeOf(context).width * 0.2, + // child: Row( + // crossAxisAlignment: CrossAxisAlignment.center, + // children: [ + // Expanded( + // child: Container( + // decoration: containerDecoration, + // child:InkWell( + // child: Padding( + // padding: const EdgeInsets.all(8.0), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceAround, + // crossAxisAlignment: CrossAxisAlignment.center, + // children: [ + // SvgPicture.asset( + // Assets.google, + // fit: BoxFit.cover, + // ), + // const Flexible( + // child: Text('Google', + // style: TextStyle(color: Colors.black), + // ), + // ), + // ], + // ), + // ), + // onTap: () {}, + // ), + // ), + // ), + // SizedBox(width: 10,), + // Expanded( + // child: Container( + // decoration: containerDecoration, + // child:InkWell( + // child: Padding( + // padding: const EdgeInsets.all(8.0), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceAround, + // crossAxisAlignment: CrossAxisAlignment.center, + // children: [ + // SvgPicture.asset( + // Assets.facebook, + // fit: BoxFit.cover, + // ), + // const Flexible( + // child: Text('Facebook', + // style: TextStyle(color: Colors.black), + // ), + // ), + // ], + // ), + // ), + // onTap: () {}, + // ), + // ), + // ), + // + // ], + // ), + // ), + const SizedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Flexible( + child: Text( + "Don't you have an account? ", + style: TextStyle( + color: Colors.white, fontSize: 13), + )), + Text( + "Sign up", + ), + ], + ), + ) + ], + ), + )), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/auth/view/login_page.dart b/lib/pages/auth/login/view/login_page.dart similarity index 74% rename from lib/pages/auth/view/login_page.dart rename to lib/pages/auth/login/view/login_page.dart index d5644b5e..8000aabf 100644 --- a/lib/pages/auth/view/login_page.dart +++ b/lib/pages/auth/login/view/login_page.dart @@ -1,8 +1,8 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:syncrow_web/pages/auth/view/login_mobile_page.dart'; -import 'package:syncrow_web/pages/auth/view/login_web_page.dart'; +import 'package:syncrow_web/pages/auth/login/view/login_mobile_page.dart'; +import 'package:syncrow_web/pages/auth/login/view/login_web_page.dart'; import 'package:syncrow_web/utils/responsive_layout.dart'; class LoginPage extends StatelessWidget { diff --git a/lib/pages/auth/login/view/login_web_page.dart b/lib/pages/auth/login/view/login_web_page.dart new file mode 100644 index 00000000..e74266b5 --- /dev/null +++ b/lib/pages/auth/login/view/login_web_page.dart @@ -0,0 +1,393 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/pages/auth/login/bloc/login_bloc.dart'; +import 'package:syncrow_web/pages/auth/login/bloc/login_event.dart'; +import 'package:syncrow_web/pages/auth/login/bloc/login_state.dart'; +import 'package:syncrow_web/pages/auth/forget_password/view/forget_password_page.dart'; +import 'package:syncrow_web/pages/common/default_button.dart'; +import 'package:syncrow_web/pages/common/first_layer.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/pages/home/view/home_page.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class LoginWebPage extends StatelessWidget { + const LoginWebPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: BlocProvider( + create: (context) => LoginBloc(), + child: BlocConsumer( + listener: (context, state) { + if (state is LoginSuccess) { + // Navigate to home screen after successful login + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (context) => const HomePage()), + ); + } else if (state is LoginFailure) { + // Show error message + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(state.error), + ), + ); + } + }, + builder: (context, state) { + if (state is LoginLoading) { + return const Center(child: CircularProgressIndicator()); + } else { + return _buildLoginForm(context); + } + }, + ), + ), + ); + } + + Widget _buildLoginForm(BuildContext context) { + final loginBloc = BlocProvider.of(context); + return FirstLayer( + second: Container( + margin: const EdgeInsets.all(50), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.3), + borderRadius: const BorderRadius.all(Radius.circular(20)), + ), + child: Center( + child: ListView( + shrinkWrap: true, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Spacer(), + Expanded( + flex: 2, + child: SvgPicture.asset( + Assets.loginLogo, + ), + ), + const Spacer(), + Container( + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.1), + borderRadius: const BorderRadius.all(Radius.circular(30)), + border: Border.all(color: ColorsManager.graysColor.withOpacity(0.2))), + child: Form( + key: loginBloc.formKey, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 15), + const Text( + 'Login', + style: TextStyle( + color: Colors.white, + fontSize: 24, + fontWeight: FontWeight.bold), + ), + const SizedBox(height: 30), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Country/Region", + style: smallTextStyle, + ), + SizedBox( + width: MediaQuery.of(context).size.width * 0.2, + child: DropdownButtonFormField( + validator:loginBloc.validateRegion , + icon: const Icon( + Icons.keyboard_arrow_down_outlined, + ), + decoration: textBoxDecoration()!.copyWith( + hintText: null, + ), + hint: const Align( + alignment: Alignment.centerLeft, + child: Text( + 'Select your region/country', + textAlign: TextAlign.center, + ), + ), + isDense: true, + style: const TextStyle(color: Colors.black), + items:loginBloc.regions.map((String region) { + return DropdownMenuItem( + value: region, + child: Text(region), + ); + }).toList(), + onChanged: (String? value) { + print(value); + }, + ), + ) + ], + ), + const SizedBox(height: 20.0), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text("Email", + style: smallTextStyle, + ), + SizedBox( + width: MediaQuery.sizeOf(context).width * 0.2, + child: TextFormField( + validator:loginBloc.validateEmail , + controller:loginBloc.emailController, + decoration: textBoxDecoration()!.copyWith(hintText: 'Enter your email'), + style: const TextStyle(color: Colors.black), + ), + ), + ], + ), + const SizedBox(height: 20.0), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text("Password", + style: smallTextStyle,), + SizedBox( + width: MediaQuery.sizeOf(context).width * 0.2, + child: TextFormField( + validator:loginBloc.validatePassword , + obscureText:loginBloc.obscureText, + keyboardType: TextInputType.visiblePassword, + controller:loginBloc.passwordController, + decoration: textBoxDecoration()!.copyWith( + hintText: 'At least 8 characters', + ), + style: const TextStyle(color: Colors.black), + ), + ), + ], + ), + const SizedBox( + height: 10, + ), + SizedBox( + width: MediaQuery.of(context).size.width * 0.2, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + InkWell( + onTap: () { + Navigator.of(context).push(MaterialPageRoute(builder: (context) => const ForgetPasswordPage(),)); + }, + child: Text( + "Forgot Password?", + style: smallTextStyle, + ), + ), + ], + ), + ), + Row( + children: [ + Transform.scale( + scale: 1.2, // Adjust the scale as needed + child: Checkbox( + fillColor: MaterialStateProperty.all(Colors.white), + activeColor: Colors.white, + value:loginBloc.isChecked, + checkColor: Colors.black, + shape: const CircleBorder(), + onChanged: (bool? newValue) { + loginBloc.add(CheckBoxEvent(newValue: newValue)); + }, + ), + ), + SizedBox( + width: MediaQuery.sizeOf(context).width * 0.2, + child: RichText( + text: TextSpan( + text: 'Agree to ', + style: const TextStyle(color: Colors.white), + children: [ + TextSpan( + text: '(Terms of Service)', + style: const TextStyle( + color: Colors.black), + recognizer: TapGestureRecognizer() + ..onTap = () { + loginBloc.launchURL( + 'https://example.com/terms'); + }, + ), + TextSpan( + text: ' (Legal Statement)', + style: const TextStyle(color: Colors.black), + recognizer: TapGestureRecognizer() + ..onTap = () { + loginBloc.launchURL( + 'https://example.com/legal'); + }, + ), + TextSpan( + text: ' (Privacy Statement)', + style: const TextStyle( + color: Colors.black), + recognizer: TapGestureRecognizer() + ..onTap = () { + loginBloc.launchURL( + 'https://example.com/privacy'); + }, + ), + ], + ), + ), + ), + ], + ), + const SizedBox(height: 20.0), + SizedBox( + width: MediaQuery.sizeOf(context).width * 0.2, + child: DefaultButton( + backgroundColor: loginBloc.isChecked? + ColorsManager.btnColor:ColorsManager.grayColor, + child: const Text('Sign in'), + onPressed: () { + if (loginBloc.formKey.currentState!.validate()) { + loginBloc.add(LoginButtonPressed( + username: loginBloc.emailController.text, + password: loginBloc.passwordController.text, + ), + ); + } + }, + ), + ), + // Padding( + // padding: const EdgeInsets.all(5.0), + // child: SizedBox( + // width: MediaQuery.sizeOf(context).width * 0.2, + // child: Row( + // mainAxisAlignment: MainAxisAlignment.center, + // crossAxisAlignment: CrossAxisAlignment.center, + // children: [ + // Expanded(child: Image.asset(Assets.liftLine)), + // Expanded( + // child: Padding( + // padding: const EdgeInsets.all(5.0), + // child: Text('Or sign in with', + // style: smallTextStyle.copyWith(fontSize: 10), + // ), + // ) + // ), + // Expanded(child: Image.asset(Assets.rightLine)), + // ], + // ), + // ), + // ), + // SizedBox( + // width: MediaQuery.sizeOf(context).width * 0.2, + // child: Row( + // crossAxisAlignment: CrossAxisAlignment.center, + // children: [ + // Expanded( + // child: Container( + // decoration: containerDecoration, + // child:InkWell( + // child: Padding( + // padding: const EdgeInsets.all(8.0), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceAround, + // crossAxisAlignment: CrossAxisAlignment.center, + // children: [ + // SvgPicture.asset( + // Assets.google, + // fit: BoxFit.cover, + // ), + // const Flexible( + // child: Text('Google', + // style: TextStyle(color: Colors.black), + // ), + // ), + // ], + // ), + // ), + // onTap: () {}, + // ), + // ), + // ), + // SizedBox(width: 10,), + // Expanded( + // child: Container( + // decoration: containerDecoration, + // child:InkWell( + // child: Padding( + // padding: const EdgeInsets.all(8.0), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceAround, + // crossAxisAlignment: CrossAxisAlignment.center, + // children: [ + // SvgPicture.asset( + // Assets.facebook, + // fit: BoxFit.cover, + // ), + // const Flexible( + // child: Text('Facebook', + // style: TextStyle(color: Colors.black), + // ), + // ), + // ], + // ), + // ), + // onTap: () {}, + // ), + // ), + // ), + // + // ], + // ), + // ), + SizedBox( + width: MediaQuery.sizeOf(context).width * 0.2, + child: const Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Flexible( + child: Text( + "Don't you have an account? ", + style: TextStyle(color: Colors.white), + )), + Flexible( + child: Text( + "Sign up", + )), + ], + ), + ) + ], + ), + ), + ) + ), + const Spacer(), + ], + ), + ], + )), + ), + ); + } +} diff --git a/lib/pages/auth/view/login_mobile_page.dart b/lib/pages/auth/view/login_mobile_page.dart deleted file mode 100644 index ccccb916..00000000 --- a/lib/pages/auth/view/login_mobile_page.dart +++ /dev/null @@ -1,220 +0,0 @@ -import 'dart:ui'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:syncrow_web/utils/constants/assets.dart'; -import 'package:syncrow_web/pages/auth/bloc/login_bloc.dart'; -import 'package:syncrow_web/pages/auth/bloc/login_event.dart'; -import 'package:syncrow_web/pages/auth/bloc/login_state.dart'; -import 'package:syncrow_web/pages/home/view/home_page.dart'; - -class LoginMobilePage extends StatelessWidget { - const LoginMobilePage({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - body: BlocProvider( - create: (context) => LoginBloc(), - child: BlocConsumer( - listener: (context, state) { - if (state is LoginSuccess) { - // Navigate to home screen after successful login - Navigator.pushReplacement( - context, - MaterialPageRoute(builder: (context) => const HomePage()), - ); - } else if (state is LoginFailure) { - // Show error message - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(state.error), - ), - ); - } - }, - builder: (context, state) { - if (state is LoginLoading) { - return const Center(child: CircularProgressIndicator()); - } else { - return _buildLoginForm(context); - } - }, - ), - ), - - ); - } - - Widget _buildLoginForm(BuildContext context) { - final loginBloc = BlocProvider.of(context); - final TextEditingController _usernameController = TextEditingController(); - final TextEditingController _passwordController = TextEditingController(); - return Center( - child: Stack( - children: [ - Container( - width: MediaQuery.sizeOf(context).width, - height: MediaQuery.sizeOf(context).height, - decoration: const BoxDecoration( - image: DecorationImage( - image: AssetImage( - Assets.background, - ), - fit: BoxFit.cover, - ), - ), - ), - Container( - width: MediaQuery.sizeOf(context).width, - height: MediaQuery.sizeOf(context).height, - decoration: const BoxDecoration( - image: DecorationImage( - image: AssetImage(Assets.vector), - fit: BoxFit.cover, - opacity: 0.9, - ), - ), - ), - SingleChildScrollView( - child: Container( - margin: const EdgeInsets.all(50), - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.3), - borderRadius: const BorderRadius.all(Radius.circular(20))), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.all(15.0), - child: SvgPicture.asset( - Assets.loginLogo, - ), - ), - Container( - margin: const EdgeInsets.all(15), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.3), - borderRadius: const BorderRadius.all(Radius.circular(30))), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 15), - const Text( - 'Login', - style: TextStyle( - color: Colors.white, - fontSize: 24, - fontWeight: FontWeight.bold), - ), - const SizedBox(height: 15), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const Text( - "Email", - style: TextStyle( - color: Colors.white, - fontSize: 13, - fontWeight: FontWeight.bold), - ), - SizedBox( - child: TextFormField( - decoration: InputDecoration( - labelText: 'Email', - labelStyle: TextStyle(color: Colors.white), - hintText: 'username@gmail.com', - hintStyle: TextStyle(color: Colors.grey), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8.0), - borderSide: BorderSide(color: Colors.white), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8.0), - borderSide: BorderSide(color: Colors.white), - ), - filled: true, - fillColor: Colors.white, - contentPadding: EdgeInsets.symmetric( - vertical: 16.0, horizontal: 12.0), - ), - style: TextStyle(color: Colors.black), - )), - ], - ), - const SizedBox(height: 15.0), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const Text( - "Password", - style: TextStyle( - color: Colors.white, - fontSize: 13, - fontWeight: FontWeight.bold), - ), - SizedBox( - child: TextFormField( - decoration: InputDecoration( - labelText: 'Password', - labelStyle: TextStyle(color: Colors.white), - hintText: 'Password', - hintStyle: TextStyle(color: Colors.grey), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8.0), - borderSide: BorderSide(color: Colors.white), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8.0), - borderSide: BorderSide(color: Colors.white), - ), - filled: true, - fillColor: Colors.white, - contentPadding: EdgeInsets.symmetric( - vertical: 16.0, horizontal: 12.0), - ), - style: TextStyle(color: Colors.black), - )), - const SizedBox(height: 20.0), - const Text( - "Forgot Password?", - style: TextStyle( - color: Colors.white, - fontSize: 13, - fontWeight: FontWeight.bold), - ), - ], - ), - const SizedBox(height: 20.0), - ElevatedButton( - onPressed: () async { - // Trigger login event - loginBloc.add( - LoginButtonPressed( - username: _usernameController.text, - password: _passwordController.text, - ), - ); - }, - child: const Text('Login'), - ), - ], - ), - ), - ), - ], - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/pages/auth/view/login_web_page.dart b/lib/pages/auth/view/login_web_page.dart deleted file mode 100644 index 92f24028..00000000 --- a/lib/pages/auth/view/login_web_page.dart +++ /dev/null @@ -1,194 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:syncrow_web/utils/constants/assets.dart'; -import 'package:syncrow_web/pages/auth/bloc/login_bloc.dart'; -import 'package:syncrow_web/pages/auth/bloc/login_event.dart'; -import 'package:syncrow_web/pages/auth/bloc/login_state.dart'; -import 'package:syncrow_web/pages/home/view/home_page.dart'; -import 'package:syncrow_web/utils/style.dart'; - -class LoginWebPage extends StatelessWidget { - const LoginWebPage({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - body: BlocProvider( - create: (context) => LoginBloc(), - child: BlocConsumer( - listener: (context, state) { - if (state is LoginSuccess) { - // Navigate to home screen after successful login - Navigator.pushReplacement( - context, - MaterialPageRoute(builder: (context) => const HomePage()), - ); - } else if (state is LoginFailure) { - // Show error message - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(state.error), - ), - ); - } - }, - builder: (context, state) { - if (state is LoginLoading) { - return const Center(child: CircularProgressIndicator()); - } else { - return _buildLoginForm(context); - } - }, - ), - ), - ); - } - - Widget _buildLoginForm(BuildContext context) { - final loginBloc = BlocProvider.of(context); - final TextEditingController _usernameController = TextEditingController(); - final TextEditingController _passwordController = TextEditingController(); - return Center( - child: Stack( - children: [ - Container( - width: MediaQuery.sizeOf(context).width, - height: MediaQuery.sizeOf(context).height, - child: SvgPicture.asset( - Assets.webBackground, - fit: BoxFit.cover, - ), - ), - Container( - width: MediaQuery.sizeOf(context).width, - height: MediaQuery.sizeOf(context).height, - decoration: const BoxDecoration( - image: DecorationImage( - image: AssetImage(Assets.vector), - fit: BoxFit.cover, - opacity: 0.9, - ), - ), - ), - Container( - margin: const EdgeInsets.all(50), - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.3), - borderRadius: const BorderRadius.all(Radius.circular(20)), - ), - child:Center( - child: ListView( - shrinkWrap: true, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Spacer(), - Expanded( - flex: 2, - child: SvgPicture.asset( - Assets.loginLogo, - ), - ), - const Spacer(), - Container( - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.3), - borderRadius: const BorderRadius.all(Radius.circular(30)), - ), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 50, vertical: 20), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 15), - const Text( - 'Login', - style: TextStyle( - color: Colors.white, - fontSize: 24, - fontWeight: FontWeight.bold), - ), - const SizedBox(height: 30), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const Text( - "Email", - style: TextStyle( - color: Colors.white, - fontSize: 13, - fontWeight: FontWeight.bold), - ), - SizedBox( - width: MediaQuery.sizeOf(context).width * 0.2, - child: TextFormField( - controller: _usernameController, - decoration: textBoxDecoration()!.copyWith(hintText: 'Email'), - style: const TextStyle(color: Colors.black), - ), - ), - ], - ), - const SizedBox(height: 20.0), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const Text( - "Password", - style: TextStyle( - color: Colors.white, - fontSize: 13, - fontWeight: FontWeight.bold), - ), - SizedBox( - width: MediaQuery.sizeOf(context).width * 0.2, - child: TextFormField( - controller: _passwordController, - decoration: textBoxDecoration()!.copyWith(hintText: 'Password' ,), - style: const TextStyle(color: Colors.black), - ), - ), - const SizedBox(height: 20.0), - const Text( - "Forgot Password?", - style: TextStyle( - color: Colors.white, - fontSize: 13, - fontWeight: FontWeight.bold), - ), - ], - ), - const SizedBox(height: 20.0), - ElevatedButton( - onPressed: () async { - // Trigger login event - loginBloc.add( - LoginButtonPressed( - username: _usernameController.text, - password: _passwordController.text, - ), - ); - }, - child: const Text('Login'), - ), - ], - ), - ), - ), - const Spacer(), - ], - ), - ], - )), - ), - ], - ), - ); - } -} diff --git a/lib/pages/common/default_button.dart b/lib/pages/common/default_button.dart new file mode 100644 index 00000000..277d18f8 --- /dev/null +++ b/lib/pages/common/default_button.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class DefaultButton extends StatelessWidget { + const DefaultButton({ + super.key, + this.enabled = true, + this.onPressed, + required this.child, + this.isSecondary = false, + this.isLoading = false, + this.isDone = false, + this.customTextStyle, + this.customButtonStyle, + this.backgroundColor, + this.foregroundColor, + this.borderRadius, + this.height, + this.padding, + }); + + final void Function()? onPressed; + final Widget child; + final double? height; + final bool isSecondary; + final double? borderRadius; + final bool enabled; + final double? padding; + final bool isDone; + final bool isLoading; + + final TextStyle? customTextStyle; + + final ButtonStyle? customButtonStyle; + + final Color? backgroundColor; + + final Color? foregroundColor; + + @override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: enabled ? onPressed : null, + style: isSecondary + ? null + : customButtonStyle ?? + ButtonStyle( + textStyle: MaterialStateProperty.all( + customTextStyle + ?? smallTextStyle.copyWith( + fontSize: 13, + color: foregroundColor, + fontWeight: FontWeight.normal + ), + ), + + foregroundColor: MaterialStateProperty.all( + isSecondary + ? Colors.black + : enabled + ? foregroundColor ?? Colors.white + : Colors.black, + ), + backgroundColor: MaterialStateProperty.resolveWith( + (Set states) { + return enabled + ? backgroundColor ?? ColorsManager.primaryColor + : Colors.grey; + }), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(borderRadius ?? 20), + ), + ), + fixedSize: MaterialStateProperty.all( + const Size.fromHeight(50), + ), + padding: MaterialStateProperty.all( + EdgeInsets.all(padding ?? 10), + ), + minimumSize: MaterialStateProperty.all( + const Size.fromHeight(50), + ), + ), + child: SizedBox( + height: height ?? 50, + child: Center( + child: isLoading + ? const SizedBox.square( + dimension: 24, + child: CircularProgressIndicator( + color: Colors.white, + ), + ) + : isDone + ? const Icon( + Icons.check_circle_outline, + color: Colors.white, + ) + : child, + ), + ), + ); + } +} diff --git a/lib/pages/common/first_layer.dart b/lib/pages/common/first_layer.dart new file mode 100644 index 00000000..a566f6d9 --- /dev/null +++ b/lib/pages/common/first_layer.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class FirstLayer extends StatelessWidget { + final Widget? second; + const FirstLayer({super.key,this.second}); + + @override + Widget build(BuildContext context) { + return Center( + child: Stack( + children: [ + SizedBox( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + child: SvgPicture.asset( + Assets.webBackground, + fit: BoxFit.cover, + ), + ), + Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height, + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage(Assets.vector), + fit: BoxFit.cover, + opacity: 0.9, + ), + ), + ), + second! + ], + ), + ); + } +} diff --git a/lib/services/api/http_interceptor.dart b/lib/services/api/http_interceptor.dart index 71fbaba3..0b758aed 100644 --- a/lib/services/api/http_interceptor.dart +++ b/lib/services/api/http_interceptor.dart @@ -1,9 +1,10 @@ import 'dart:io'; + import 'package:dio/dio.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:syncrow_web/pages/auth/model/token.dart'; -import 'dart:async'; +import 'package:syncrow_web/pages/auth/login/model/token.dart'; import 'package:syncrow_web/services/api/network_exception.dart'; +import 'dart:async'; import 'package:syncrow_web/utils/constants/api_const.dart'; import 'package:syncrow_web/utils/snack_bar.dart'; @@ -30,7 +31,7 @@ class HTTPInterceptor extends InterceptorsWrapper { if (checkHeaderExclusionListOfAddedParameters(options.path)) { options.headers.putIfAbsent(HttpHeaders.authorizationHeader, () => "Bearer $token"); } - options.headers['Authorization'] = 'Bearer ${token!}'; + // options.headers['Authorization'] = 'Bearer ${'${token!}123'}'; super.onRequest(options, handler); } diff --git a/lib/services/auth_api.dart b/lib/services/auth_api.dart index 4829f527..ba9480bf 100644 --- a/lib/services/auth_api.dart +++ b/lib/services/auth_api.dart @@ -1,6 +1,8 @@ -import 'package:syncrow_web/pages/auth/model/token.dart'; + + +import 'package:syncrow_web/pages/auth/login/model/token.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; -import 'api/http_service.dart'; class AuthenticationAPI { @@ -9,21 +11,54 @@ class AuthenticationAPI { final response = await HTTPService().post( path: ApiEndpoints.login, body: model.toJson(), - showServerMessage: false, - expectedResponseModel: (json) => Token.fromJson(json['data'])); + showServerMessage: true, + expectedResponseModel: (json) { + return Token.fromJson(json['data']); + }); return response; } - // static Future signUp({required SignUpModel model}) async { - // final response = await HTTPService().post( - // path: ApiEndpoints.signUp, - // body: model.toJson(), - // showServerMessage: false, - // expectedResponseModel: (json) => json['statusCode'] == 201); - // return response; - // } + static Future forgetPassword({ required var email, required var password}) async { + final response = await HTTPService().post( + path: ApiEndpoints.forgetPassword, + body: { + "email": email, + "password": password + }, + showServerMessage: true, + expectedResponseModel: (json) { + }); + return response; + } + static Future sendOtp({ required var email}) async { + final response = await HTTPService().post( + path: ApiEndpoints.sendOtp, + body: { + "email": email, + "type": "VERIFICATION" + }, + showServerMessage: true, + expectedResponseModel: (json) { + print('json===$json'); + }); + return response; + } + + static Future checkOtp({ required var email}) async { + final response = await HTTPService().post( + path: ApiEndpoints.sendOtp, + body: { + "email": email, + "type": "VERIFICATION" + }, + showServerMessage: true, + expectedResponseModel: (json) { + }); + + return response; + } } diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 36e5b0fc..96e7a43a 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -27,4 +27,5 @@ abstract class ColorsManager { static const Color red = Colors.red; static const Color graysColor = Color(0xffEBEBEB); static const Color textGray = Color(0xffD5D5D5); + static const Color btnColor = Color(0xFF00008B); } diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index e188f4e6..8ad54474 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -1,8 +1,10 @@ abstract class ApiEndpoints { - static const String baseUrl = 'https://syncrow-staging.azurewebsites.net'; + static const String baseUrl = 'https://syncrow-dev.azurewebsites.net'; // static const String baseUrl = 'http://100.107.182.63:4001'; //Localhost //https://syncrow-staging.azurewebsites.net ////////////////////////////////////// Authentication /////////////////////////////// - static const String signUp = 'authentication/user/signup'; - static const String login = 'authentication/user/login'; + static const String signUp = '$baseUrl/authentication/user/signup'; + static const String login = '$baseUrl/authentication/user/login'; + static const String forgetPassword = '$baseUrl/authentication/user/forget-password'; + static const String sendOtp = '$baseUrl/authentication/user/send-otp'; } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 1e5e0d00..1dd7a2eb 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -9,4 +9,8 @@ class Assets { static const String loginLogo = "assets/images/login_logo.svg"; static const String whiteLogo = "assets/images/white-logo.png"; static const String window = "assets/images/Window.png"; + static const String liftLine = "assets/images/lift_line.png"; + static const String rightLine = "assets/images/right_line.png"; + static const String google = "assets/images/google.svg"; + static const String facebook = "assets/images/facebook.svg"; } diff --git a/lib/utils/snack_bar.dart b/lib/utils/snack_bar.dart index d50a4250..11e46828 100644 --- a/lib/utils/snack_bar.dart +++ b/lib/utils/snack_bar.dart @@ -16,6 +16,7 @@ class CustomSnackBar { BuildContext? currentContext = key?.currentContext; if (key != null && currentContext != null) { final snackBar = SnackBar( + padding: const EdgeInsets.all(16), backgroundColor: Colors.green, content: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ diff --git a/lib/utils/style.dart b/lib/utils/style.dart index 8e8eb3f6..70f9d0bd 100644 --- a/lib/utils/style.dart +++ b/lib/utils/style.dart @@ -23,4 +23,10 @@ InputDecoration? textBoxDecoration({bool suffixIcon = false}) => InputDecoration TextStyle appBarTextStyle = - const TextStyle(fontSize: 20, color: ColorsManager.whiteColors); +const TextStyle(fontSize: 20, color: ColorsManager.whiteColors); + +TextStyle smallTextStyle = +const TextStyle(fontSize: 13, color: ColorsManager.whiteColors,fontWeight: FontWeight.bold); + + +Decoration containerDecoration = const BoxDecoration(color: Colors.white,borderRadius: BorderRadius.all(Radius.circular(20))); \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 19cd83d0..d3424a3e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -324,10 +324,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "30c5aa827a6ae95ce2853cdc5fe3971daaac00f6f081c419c013f7f57bff2f5e" + sha256: e84c8a53fe1510ef4582f118c7b4bdf15b03002b51d7c2b66983c65843d61193 url: "https://pub.dev" source: hosted - version: "2.2.7" + version: "2.2.8" path_provider_foundation: dependency: transitive description: