auth UI and Api

This commit is contained in:
mohammad
2024-07-31 16:02:05 +03:00
parent 69abad24b7
commit 1d65617d18
13 changed files with 405 additions and 228 deletions

View File

@ -9,6 +9,8 @@ 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/services/auth_api.dart';
import 'package:syncrow_web/utils/constants/strings_manager.dart';
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
import 'package:syncrow_web/utils/snack_bar.dart';
class AuthBloc extends Bloc<AuthEvent, AuthState> {
@ -20,16 +22,16 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
on<StopTimerEvent>(_onStopTimer);
on<UpdateTimerEvent>(_onUpdateTimer);
on<PasswordVisibleEvent>(_passwordVisible);
}
////////////////////////////// forget password //////////////////////////////////
final TextEditingController forgetEmailController = TextEditingController();
final TextEditingController forgetPasswordController = TextEditingController();
final TextEditingController forgetOtp = TextEditingController();
final forgetFormKey = GlobalKey<FormState>();
Timer? _timer;
int _remainingTime = 0;
@ -53,7 +55,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
add(const UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true));
print("Timer finished"); // Debug print
} else {
add(UpdateTimerEvent(remainingTime: _remainingTime, isButtonEnabled: false));
add(UpdateTimerEvent(
remainingTime: _remainingTime, isButtonEnabled: false));
}
});
}
@ -67,40 +70,49 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
ChangePasswordEvent event, Emitter<AuthState> emit) async {
try {
emit(LoadingForgetState());
await AuthenticationAPI.forgetPassword(
password: forgetPasswordController.text, email: forgetEmailController.text);
bool response = await AuthenticationAPI.verifyOtp(
email: forgetEmailController.text, otpCode: forgetOtp.text);
if (response == true) {
await AuthenticationAPI.forgetPassword(
password: forgetPasswordController.text,
email: forgetEmailController.text);
_timer?.cancel();
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
}
emit(SuccessForgetState());
} catch (failure) {
print(failure);
emit(FailureForgetState(error: failure.toString()));
}
}
void _onUpdateTimer(UpdateTimerEvent event, Emitter<AuthState> emit) {
emit(TimerState(
isButtonEnabled: event.isButtonEnabled,
remainingTime: event.remainingTime));
}
///////////////////////////////////// login /////////////////////////////////////
final TextEditingController loginEmailController = TextEditingController();
final TextEditingController loginPasswordController = TextEditingController();
final loginFormKey = GlobalKey<FormState>();
bool isChecked = false;
bool obscureText = false;
bool obscureText = true;
String newPassword = '';
String maskedEmail = '';
String otpCode = '';
static Token token = Token.emptyConstructor();
static UserModel? user;
bool showValidationMessage = false;
void _login(LoginButtonPressed event, Emitter<AuthState> emit) async {
void _login(LoginButtonPressed event, Emitter<AuthState> emit) async {
emit(LoginLoading());
if(isChecked) {
if (isChecked) {
try {
if (event.username.isEmpty || event.password.isEmpty) {
CustomSnackBar.displaySnackBar('Please enter your credentials');
@ -113,8 +125,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
password: event.password,
),
);
}
catch (failure) {
} catch (failure) {
emit(const LoginFailure(error: 'Something went wrong'));
// emit(LoginFailure(error: failure.toString()));
return;
@ -124,11 +135,9 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
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()
);
value: Token.decodeToken(token.accessToken)['uuid'].toString());
user = UserModel.fromToken(token);
loginEmailController.clear();
loginPasswordController.clear();
@ -136,18 +145,24 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
} else {
emit(const LoginFailure(error: 'Something went wrong'));
}
}
else{
} else {
emit(const LoginFailure(error: 'Accept terms and condition'));
}
}
checkBoxToggle(CheckBoxEvent event, Emitter<AuthState> emit,){
checkBoxToggle(CheckBoxEvent event, Emitter<AuthState> emit,) {
emit(LoginLoading());
isChecked = event.newValue!;
emit(LoginInitial());
}
checkOtpCode(ChangePasswordEvent event, Emitter<AuthState> emit,) async {
emit(LoadingForgetState());
await AuthenticationAPI.verifyOtp(
email: forgetEmailController.text, otpCode: forgetOtp.text);
emit(SuccessForgetState());
}
void _passwordVisible(PasswordVisibleEvent event, Emitter<AuthState> emit) {
emit(LoginLoading());
obscureText = !event.newValue!;
@ -161,8 +176,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
}
/////////////////////////////////////VALIDATORS/////////////////////////////////////
/////////////////////////////////////VALIDATORS/////////////////////////////////////
String? validatePassword(String? value) {
if (value == null || value.isEmpty) {
return 'Password is required';
@ -192,25 +207,23 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
emit(LoadingForgetState());
final nameError = validateEmail(forgetEmailController.text);
if (nameError != null) {
emit(FailureForgetState(error:nameError )) ;
// CustomSnackBar.displaySnackBar(nameError);
emit(FailureForgetState(error: nameError));
return true;
}
return false;
}
String? validateRegion(String? value) {
if (value == null || value.isEmpty) {
return 'Please select a region';
}
return null;
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<String> validationErrors = [];
if (!RegExp(r'^(?=.*[a-z])').hasMatch(value)) {
@ -230,73 +243,30 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
}
if (validationErrors.isNotEmpty) {
return 'Password must contain at least:\n' + validationErrors.join('\n');
return 'Password must contain at least:\n${validationErrors.join('\n')}';
}
return null;
}
String? fullNameValidator(String? value) {
if (value == null) return 'Full name is required';
final withoutExtraSpaces = value.replaceAll(RegExp(r"\s+"), ' ').trim();
if (withoutExtraSpaces.length < 2 || withoutExtraSpaces.length > 30) {
return 'Full name must be between 2 and 30 characters long';
}
// Test if it contains anything but alphanumeric spaces and single quote
if (RegExp(r"/[^ a-zA-Z0-9-\']/").hasMatch(withoutExtraSpaces)) {
return 'Only alphanumeric characters, space, dash and single quote are allowed';
}
final parts = withoutExtraSpaces.split(' ');
if (parts.length < 2) return 'Full name must contain first and last names';
if (parts.length > 3) return 'Full name can at most contain 3 parts';
if (parts.any((part) => part.length < 2 || part.length > 30)) {
return 'Full name parts must be between 2 and 30 characters long';
}
return null;
}
String? reEnterPasswordCheckForgetPass(String? value) {
passwordValidator(value);
if (newPassword == value) {
return null;
} else {
return 'Passwords do not match';
}
}
String? emailAddressValidator(String? value) {
if (value != null && value.isNotEmpty && value != "") {
if (checkValidityOfEmail(value)) {
return null;
} else {
return 'Please enter a valid email';
}
} else {
return 'Email address is required';
}
}
bool checkValidityOfEmail(String? email) {
if (email != null) {
return RegExp(
r"^[a-zA-Z0-9]+([.!#$%&'*+/=?^_`{|}~-]?[a-zA-Z0-9]+)*@[a-zA-Z0-9]+([.-]?[a-zA-Z0-9]+)*\.[a-zA-Z0-9]{2,}$")
.hasMatch(email);
} else {
return false;
}
}
String maskEmail(String email) {
final emailParts = email.split('@');
if (emailParts.length != 2) return email;
@ -324,4 +294,36 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
];
static Future<String> getTokenAndValidate() async {
try {
const storage = FlutterSecureStorage();
final firstLaunch = await SharedPreferencesHelper.readBoolFromSP(StringsManager.firstLaunch) ?? true;
if (firstLaunch) {
storage.deleteAll();
}
await SharedPreferencesHelper.saveBoolToSP(StringsManager.firstLaunch, false);
final value = await storage.read(key: Token.loginAccessTokenKey) ?? '';
if (value.isEmpty) {
return 'Token not found';
}
final tokenData = Token.decodeToken(value);
if (tokenData.containsKey('exp')) {
final exp = tokenData['exp'] ?? 0;
final currentTime = DateTime.now().millisecondsSinceEpoch ~/ 1000;
if (currentTime < exp) {
return 'Success';
} else {
return 'expired';
}
} else {
return 'Something went wrong';
}
} catch (_) {
return 'Something went wrong';
}
}
}

View File

@ -9,6 +9,8 @@ abstract class AuthState extends Equatable {
class LoginInitial extends AuthState {}
class AuthTokenLoading extends AuthState {}
class LoginLoading extends AuthState {}
class LoginSuccess extends AuthState {}
@ -38,6 +40,7 @@ class InitialForgetState extends AuthState{}
class LoadingForgetState extends AuthState{}
class SuccessForgetState extends AuthState{}
class PasswordVisibleState extends AuthState{}
class FailureForgetState extends AuthState {
@ -58,8 +61,18 @@ class TimerState extends AuthState {
}
class AuthError extends AuthState {
final String message;
String? code;
AuthError({required this.message, this.code});
}
class AuthTokenError extends AuthError {
AuthTokenError({required super.message, super.code});
}
class AuthSuccess extends AuthState {}
class AuthTokenSuccess extends AuthSuccess {}
// class AuthState extends AuthState {}

View File

@ -9,7 +9,6 @@ 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});
@ -21,7 +20,11 @@ class ForgetPasswordWebPage extends StatelessWidget {
child: BlocConsumer<AuthBloc, AuthState>(
listener: (context, state) {
if (state is LoadingForgetState) {
} else if (state is FailureForgetState) {
} else if (state is SuccessForgetState){
Navigator.of(context).pop();
}
else if (state is FailureForgetState) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(state.error),
@ -138,10 +141,8 @@ class ForgetPasswordWebPage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
"Account",
style: smallTextStyle,
),
Text("Account",
style: smallTextStyle,),
SizedBox(
width: MediaQuery.sizeOf(context).width * 0.2,
child: TextFormField(

View File

@ -287,108 +287,7 @@ class LoginWebPage extends StatelessWidget {
},
),
),
// 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",
// )),
// ],
// ),
// )
],
),
),
@ -403,3 +302,105 @@ class LoginWebPage extends StatelessWidget {
);
}
}
// 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",
// )),
// ],
// ),
// )