registration and login ready, with some validations
This commit is contained in:
@@ -4,6 +4,7 @@ import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||
import 'package:krow_domain/krow_domain.dart' as domain;
|
||||
import '../../domain/ui_entities/auth_mode.dart';
|
||||
|
||||
import '../../domain/repositories/auth_repository_interface.dart';
|
||||
|
||||
@@ -67,6 +68,7 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
/// Signs out the current user.
|
||||
@override
|
||||
Future<void> signOut() {
|
||||
StaffSessionStore.instance.clear();
|
||||
return firebaseAuth.signOut();
|
||||
}
|
||||
|
||||
@@ -75,6 +77,7 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
Future<domain.User?> verifyOtp({
|
||||
required String verificationId,
|
||||
required String smsCode,
|
||||
required AuthMode mode,
|
||||
}) async {
|
||||
final PhoneAuthCredential credential = PhoneAuthProvider.credential(
|
||||
verificationId: verificationId,
|
||||
@@ -86,33 +89,76 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
throw Exception('Phone verification failed, no Firebase user received.');
|
||||
}
|
||||
|
||||
final QueryResult<GetUserByIdData, GetUserByIdVariables> response = await dataConnect.getUserById(
|
||||
id: firebaseUser.uid,
|
||||
).execute();
|
||||
final QueryResult<GetUserByIdData, GetUserByIdVariables> response =
|
||||
await dataConnect.getUserById(
|
||||
id: firebaseUser.uid,
|
||||
).execute();
|
||||
final GetUserByIdUser? user = response.data.user;
|
||||
if (user == null) {
|
||||
await firebaseAuth.signOut();
|
||||
throw Exception('Authenticated user profile not found in database.');
|
||||
}
|
||||
if (user.userRole != 'STAFF') {
|
||||
await firebaseAuth.signOut();
|
||||
throw Exception('User is not authorized for this app.');
|
||||
|
||||
if (mode == AuthMode.signup) {
|
||||
if (user == null) {
|
||||
await dataConnect
|
||||
.createUser(
|
||||
id: firebaseUser.uid,
|
||||
role: UserBaseRole.USER,
|
||||
)
|
||||
.userRole('STAFF')
|
||||
.execute();
|
||||
} else {
|
||||
if (user.userRole != 'STAFF') {
|
||||
await firebaseAuth.signOut();
|
||||
throw Exception('User is not authorized for this app.');
|
||||
}
|
||||
final QueryResult<GetStaffByUserIdData, GetStaffByUserIdVariables>
|
||||
staffResponse = await dataConnect.getStaffByUserId(
|
||||
userId: firebaseUser.uid,
|
||||
).execute();
|
||||
if (staffResponse.data.staffs.isNotEmpty) {
|
||||
await firebaseAuth.signOut();
|
||||
throw Exception(
|
||||
'This user already has a staff profile. Please log in.',
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (user == null) {
|
||||
await firebaseAuth.signOut();
|
||||
throw Exception('Authenticated user profile not found in database.');
|
||||
}
|
||||
if (user.userRole != 'STAFF') {
|
||||
await firebaseAuth.signOut();
|
||||
throw Exception('User is not authorized for this app.');
|
||||
}
|
||||
|
||||
final QueryResult<GetStaffByUserIdData, GetStaffByUserIdVariables>
|
||||
staffResponse = await dataConnect.getStaffByUserId(
|
||||
userId: firebaseUser.uid,
|
||||
).execute();
|
||||
if (staffResponse.data.staffs.isEmpty) {
|
||||
await firebaseAuth.signOut();
|
||||
throw Exception(
|
||||
'Your account is not registered yet. Please register first.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final String email = user.email ?? '';
|
||||
if (email.isEmpty) {
|
||||
final String email = user?.email ?? '';
|
||||
if (mode != AuthMode.signup && email.isEmpty) {
|
||||
await firebaseAuth.signOut();
|
||||
throw Exception('User email is missing in profile data.');
|
||||
}
|
||||
|
||||
//TO-DO: validate if user has staff account, else logout, throw message and login
|
||||
//TO-DO: create(registration) user and staff account
|
||||
//TO-DO: save user data locally
|
||||
return domain.User(
|
||||
id: user.id,
|
||||
final domain.User domainUser = domain.User(
|
||||
id: firebaseUser.uid,
|
||||
email: email,
|
||||
phone: firebaseUser.phoneNumber,
|
||||
role: user.role.stringValue,
|
||||
role: user?.role.stringValue ?? 'USER',
|
||||
);
|
||||
StaffSessionStore.instance.setSession(
|
||||
StaffSession(user: domainUser, staff: null),
|
||||
);
|
||||
return domainUser;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
import '../ui_entities/auth_mode.dart';
|
||||
|
||||
/// Represents the arguments required for the [VerifyOtpUseCase].
|
||||
///
|
||||
@@ -11,14 +12,18 @@ class VerifyOtpArguments extends UseCaseArgument {
|
||||
/// The one-time password (OTP) sent to the user's phone.
|
||||
final String smsCode;
|
||||
|
||||
/// The authentication mode (login or signup).
|
||||
final AuthMode mode;
|
||||
|
||||
/// Creates a [VerifyOtpArguments] instance.
|
||||
///
|
||||
/// Both [verificationId] and [smsCode] are required.
|
||||
const VerifyOtpArguments({
|
||||
required this.verificationId,
|
||||
required this.smsCode,
|
||||
required this.mode,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => <Object>[verificationId, smsCode];
|
||||
List<Object> get props => <Object>[verificationId, smsCode, mode];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../ui_entities/auth_mode.dart';
|
||||
|
||||
/// Interface for authentication repository.
|
||||
abstract interface class AuthRepositoryInterface {
|
||||
@@ -11,6 +12,7 @@ abstract interface class AuthRepositoryInterface {
|
||||
Future<User?> verifyOtp({
|
||||
required String verificationId,
|
||||
required String smsCode,
|
||||
required AuthMode mode,
|
||||
});
|
||||
|
||||
/// Signs out the current user.
|
||||
|
||||
@@ -19,6 +19,7 @@ class VerifyOtpUseCase implements UseCase<VerifyOtpArguments, User?> {
|
||||
return _repository.verifyOtp(
|
||||
verificationId: arguments.verificationId,
|
||||
smsCode: arguments.smsCode,
|
||||
mode: arguments.mode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +93,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> implements Disposable {
|
||||
VerifyOtpArguments(
|
||||
verificationId: event.verificationId,
|
||||
smsCode: event.smsCode,
|
||||
mode: event.mode,
|
||||
),
|
||||
);
|
||||
emit(state.copyWith(status: AuthStatus.authenticated, user: user));
|
||||
|
||||
@@ -33,10 +33,17 @@ class AuthOtpSubmitted extends AuthEvent {
|
||||
/// The SMS code (OTP) entered by the user.
|
||||
final String smsCode;
|
||||
|
||||
const AuthOtpSubmitted({required this.verificationId, required this.smsCode});
|
||||
/// The authentication mode (login or signup).
|
||||
final AuthMode mode;
|
||||
|
||||
const AuthOtpSubmitted({
|
||||
required this.verificationId,
|
||||
required this.smsCode,
|
||||
required this.mode,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => <Object>[verificationId, smsCode];
|
||||
List<Object> get props => <Object>[verificationId, smsCode, mode];
|
||||
}
|
||||
|
||||
/// Event for clearing any authentication error in the state.
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart' as auth;
|
||||
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
|
||||
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'profile_setup_event.dart';
|
||||
import 'profile_setup_state.dart';
|
||||
|
||||
@@ -8,8 +12,12 @@ export 'profile_setup_state.dart';
|
||||
|
||||
/// BLoC responsible for managing the profile setup state and logic.
|
||||
class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState> {
|
||||
/// Creates a [ProfileSetupBloc] with an initial state.
|
||||
ProfileSetupBloc() : super(const ProfileSetupState()) {
|
||||
ProfileSetupBloc({
|
||||
required auth.FirebaseAuth firebaseAuth,
|
||||
required dc.ExampleConnector dataConnect,
|
||||
}) : _firebaseAuth = firebaseAuth,
|
||||
_dataConnect = dataConnect,
|
||||
super(const ProfileSetupState()) {
|
||||
on<ProfileSetupFullNameChanged>(_onFullNameChanged);
|
||||
on<ProfileSetupBioChanged>(_onBioChanged);
|
||||
on<ProfileSetupLocationsChanged>(_onLocationsChanged);
|
||||
@@ -19,6 +27,9 @@ class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState> {
|
||||
on<ProfileSetupSubmitted>(_onSubmitted);
|
||||
}
|
||||
|
||||
final auth.FirebaseAuth _firebaseAuth;
|
||||
final dc.ExampleConnector _dataConnect;
|
||||
|
||||
/// Handles the [ProfileSetupFullNameChanged] event.
|
||||
void _onFullNameChanged(
|
||||
ProfileSetupFullNameChanged event,
|
||||
@@ -75,17 +86,44 @@ class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState> {
|
||||
emit(state.copyWith(status: ProfileSetupStatus.loading));
|
||||
|
||||
try {
|
||||
// In a real app, we would send this data to a UseCase
|
||||
debugPrint('Submitting Profile:');
|
||||
debugPrint('Name: ${state.fullName}');
|
||||
debugPrint('Bio: ${state.bio}');
|
||||
debugPrint('Locations: ${state.preferredLocations}');
|
||||
debugPrint('Distance: ${state.maxDistanceMiles}');
|
||||
debugPrint('Skills: ${state.skills}');
|
||||
debugPrint('Industries: ${state.industries}');
|
||||
final auth.User? firebaseUser = _firebaseAuth.currentUser;
|
||||
if (firebaseUser == null) {
|
||||
throw Exception('User not authenticated.');
|
||||
}
|
||||
|
||||
// Mocking profile creation delay
|
||||
await Future.delayed(const Duration(milliseconds: 1500));
|
||||
final dc.StaffSession? session = dc.StaffSessionStore.instance.session;
|
||||
final String email = session?.user.email ?? '';
|
||||
final String? phone = firebaseUser.phoneNumber;
|
||||
|
||||
final fdc.OperationResult<dc.CreateStaffData, dc.CreateStaffVariables>
|
||||
result = await _dataConnect
|
||||
.createStaff(
|
||||
userId: firebaseUser.uid,
|
||||
fullName: state.fullName,
|
||||
)
|
||||
.bio(state.bio.isEmpty ? null : state.bio)
|
||||
.preferredLocations(fdc.AnyValue(state.preferredLocations))
|
||||
.maxDistanceMiles(state.maxDistanceMiles.toInt())
|
||||
.industries(fdc.AnyValue(state.industries))
|
||||
.skills(fdc.AnyValue(state.skills))
|
||||
.email(email.isEmpty ? null : email)
|
||||
.phone(phone)
|
||||
.execute();
|
||||
|
||||
final String staffId = result.data?.staff_insert.id ?? '';
|
||||
final Staff staff = Staff(
|
||||
id: staffId,
|
||||
authProviderId: firebaseUser.uid,
|
||||
name: state.fullName,
|
||||
email: email,
|
||||
phone: phone,
|
||||
status: StaffStatus.completedProfile,
|
||||
);
|
||||
if (session != null) {
|
||||
dc.StaffSessionStore.instance.setSession(
|
||||
dc.StaffSession(user: session.user, staff: staff),
|
||||
);
|
||||
}
|
||||
|
||||
emit(state.copyWith(status: ProfileSetupStatus.success));
|
||||
} catch (e) {
|
||||
|
||||
@@ -50,7 +50,13 @@ class PhoneVerificationPage extends StatelessWidget {
|
||||
}) {
|
||||
BlocProvider.of<AuthBloc>(
|
||||
context,
|
||||
).add(AuthOtpSubmitted(verificationId: verificationId, smsCode: otp));
|
||||
).add(
|
||||
AuthOtpSubmitted(
|
||||
verificationId: verificationId,
|
||||
smsCode: otp,
|
||||
mode: mode,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Handles the request to resend the verification code using the phone number in the state.
|
||||
@@ -72,6 +78,19 @@ class PhoneVerificationPage extends StatelessWidget {
|
||||
} else {
|
||||
Modular.to.pushWorkerHome();
|
||||
}
|
||||
} else if (state.status == AuthStatus.error &&
|
||||
state.mode == AuthMode.signup) {
|
||||
final String message = state.errorMessage ?? '';
|
||||
if (message.contains('staff profile')) {
|
||||
Modular.to.pushReplacementNamed(
|
||||
'./phone-verification',
|
||||
arguments: <String, String>{
|
||||
'mode': AuthMode.login.name,
|
||||
},
|
||||
);
|
||||
} else if (message.contains('not authorized')) {
|
||||
Modular.to.pop();
|
||||
}
|
||||
}
|
||||
},
|
||||
child: BlocBuilder<AuthBloc, AuthState>(
|
||||
|
||||
@@ -47,7 +47,12 @@ class StaffAuthenticationModule extends Module {
|
||||
verifyOtpUseCase: i.get<VerifyOtpUseCase>(),
|
||||
),
|
||||
);
|
||||
i.add<ProfileSetupBloc>(ProfileSetupBloc.new);
|
||||
i.add<ProfileSetupBloc>(
|
||||
() => ProfileSetupBloc(
|
||||
firebaseAuth: firebase.FirebaseAuth.instance,
|
||||
dataConnect: ExampleConnector.instance,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
Reference in New Issue
Block a user