feat: Migrate staff profile features from Data Connect to V2 REST API
- Removed data_connect package from mobile pubspec.yaml. - Added documentation for V2 profile migration status and QA findings. - Implemented new session management with ClientSessionStore and StaffSessionStore. - Created V2SessionService for handling user sessions via the V2 API. - Developed use cases for cancelling late worker assignments and submitting worker reviews. - Added arguments and use cases for payment chart retrieval and profile completion checks. - Implemented repository interfaces and their implementations for staff main and profile features. - Ensured proper error handling and validation in use cases.
This commit is contained in:
@@ -1,59 +1,99 @@
|
||||
import 'dart:async';
|
||||
|
||||
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_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart' as domain;
|
||||
import '../../utils/test_phone_numbers.dart';
|
||||
import '../../domain/ui_entities/auth_mode.dart';
|
||||
|
||||
import '../../domain/repositories/auth_repository_interface.dart';
|
||||
import 'package:staff_authentication/src/domain/repositories/auth_repository_interface.dart';
|
||||
import 'package:staff_authentication/src/domain/ui_entities/auth_mode.dart';
|
||||
import 'package:staff_authentication/src/utils/test_phone_numbers.dart';
|
||||
|
||||
/// Implementation of [AuthRepositoryInterface].
|
||||
/// V2 API implementation of [AuthRepositoryInterface].
|
||||
///
|
||||
/// Uses the Firebase Auth SDK for client-side phone verification,
|
||||
/// then calls the V2 unified API to hydrate the session context.
|
||||
/// All Data Connect dependencies have been removed.
|
||||
class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
AuthRepositoryImpl() : _service = DataConnectService.instance;
|
||||
/// Creates an [AuthRepositoryImpl].
|
||||
///
|
||||
/// Requires a [domain.BaseApiService] for V2 API calls.
|
||||
AuthRepositoryImpl({required domain.BaseApiService apiService})
|
||||
: _apiService = apiService;
|
||||
|
||||
final DataConnectService _service;
|
||||
/// The V2 API service for backend calls.
|
||||
final domain.BaseApiService _apiService;
|
||||
|
||||
/// Firebase Auth instance for client-side phone verification.
|
||||
final FirebaseAuth _auth = FirebaseAuth.instance;
|
||||
|
||||
/// Completer for the pending phone verification request.
|
||||
Completer<String?>? _pendingVerification;
|
||||
|
||||
@override
|
||||
Stream<domain.User?> get currentUser =>
|
||||
_service.auth.authStateChanges().map((User? firebaseUser) {
|
||||
_auth.authStateChanges().map((User? firebaseUser) {
|
||||
if (firebaseUser == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return domain.User(
|
||||
id: firebaseUser.uid,
|
||||
email: firebaseUser.email ?? '',
|
||||
email: firebaseUser.email,
|
||||
displayName: firebaseUser.displayName,
|
||||
phone: firebaseUser.phoneNumber,
|
||||
role: 'staff',
|
||||
status: domain.UserStatus.active,
|
||||
);
|
||||
});
|
||||
|
||||
/// Signs in with a phone number and returns a verification ID.
|
||||
/// Initiates phone verification via the V2 API.
|
||||
///
|
||||
/// Calls `POST /auth/staff/phone/start` first. The server decides the
|
||||
/// verification mode:
|
||||
/// - `CLIENT_FIREBASE_SDK` — mobile must do Firebase phone auth client-side
|
||||
/// - `IDENTITY_TOOLKIT_SMS` — server sent the SMS, returns `sessionInfo`
|
||||
///
|
||||
/// For mobile without recaptcha tokens, the server returns
|
||||
/// `CLIENT_FIREBASE_SDK` and we fall back to the Firebase Auth SDK.
|
||||
@override
|
||||
Future<String?> signInWithPhone({required String phoneNumber}) async {
|
||||
// Step 1: Try V2 to let the server decide the auth mode.
|
||||
// Falls back to CLIENT_FIREBASE_SDK if the API call fails (e.g. server
|
||||
// down, 500, or non-JSON response).
|
||||
String mode = 'CLIENT_FIREBASE_SDK';
|
||||
String? sessionInfo;
|
||||
|
||||
try {
|
||||
final domain.ApiResponse startResponse = await _apiService.post(
|
||||
V2ApiEndpoints.staffPhoneStart,
|
||||
data: <String, dynamic>{
|
||||
'phoneNumber': phoneNumber,
|
||||
},
|
||||
);
|
||||
|
||||
final Map<String, dynamic> startData =
|
||||
startResponse.data as Map<String, dynamic>;
|
||||
mode = startData['mode'] as String? ?? 'CLIENT_FIREBASE_SDK';
|
||||
sessionInfo = startData['sessionInfo'] as String?;
|
||||
} catch (_) {
|
||||
// V2 start call failed — fall back to client-side Firebase SDK.
|
||||
}
|
||||
|
||||
// Step 2: If server sent the SMS, return the sessionInfo for verify step.
|
||||
if (mode == 'IDENTITY_TOOLKIT_SMS') {
|
||||
return sessionInfo;
|
||||
}
|
||||
|
||||
// Step 3: CLIENT_FIREBASE_SDK mode — do Firebase phone auth client-side.
|
||||
final Completer<String?> completer = Completer<String?>();
|
||||
_pendingVerification = completer;
|
||||
|
||||
await _service.auth.verifyPhoneNumber(
|
||||
await _auth.verifyPhoneNumber(
|
||||
phoneNumber: phoneNumber,
|
||||
verificationCompleted: (PhoneAuthCredential credential) {
|
||||
// Skip auto-verification for test numbers to allow manual code entry
|
||||
if (TestPhoneNumbers.isTestNumber(phoneNumber)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For real numbers, we can support auto-verification if desired.
|
||||
// But since this method returns a verificationId for manual OTP entry,
|
||||
// we might not handle direct sign-in here unless the architecture changes.
|
||||
// Currently, we just ignore it for the completer flow,
|
||||
// or we could sign in directly if the credential is provided.
|
||||
if (TestPhoneNumbers.isTestNumber(phoneNumber)) return;
|
||||
},
|
||||
verificationFailed: (FirebaseAuthException e) {
|
||||
if (!completer.isCompleted) {
|
||||
// Map Firebase network errors to NetworkException
|
||||
if (e.code == 'network-request-failed' ||
|
||||
e.message?.contains('Unable to resolve host') == true) {
|
||||
completer.completeError(
|
||||
@@ -94,35 +134,36 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
_pendingVerification = null;
|
||||
}
|
||||
|
||||
/// Signs out the current user.
|
||||
@override
|
||||
Future<void> signOut() async {
|
||||
return await _service.signOut();
|
||||
}
|
||||
|
||||
/// Verifies an OTP code and returns the authenticated user.
|
||||
/// Verifies the OTP and completes authentication via the V2 API.
|
||||
///
|
||||
/// 1. Signs in with the Firebase credential (client-side).
|
||||
/// 2. Gets the Firebase ID token.
|
||||
/// 3. Calls `POST /auth/staff/phone/verify` with the ID token and mode.
|
||||
/// 4. Parses the V2 auth envelope and populates the session.
|
||||
@override
|
||||
Future<domain.User?> verifyOtp({
|
||||
required String verificationId,
|
||||
required String smsCode,
|
||||
required AuthMode mode,
|
||||
}) async {
|
||||
// Step 1: Sign in with Firebase credential (client-side).
|
||||
final PhoneAuthCredential credential = PhoneAuthProvider.credential(
|
||||
verificationId: verificationId,
|
||||
smsCode: smsCode,
|
||||
);
|
||||
final UserCredential userCredential = await _service.run(() async {
|
||||
try {
|
||||
return await _service.auth.signInWithCredential(credential);
|
||||
} on FirebaseAuthException catch (e) {
|
||||
if (e.code == 'invalid-verification-code') {
|
||||
throw const domain.InvalidCredentialsException(
|
||||
technicalMessage: 'Invalid OTP code entered.',
|
||||
);
|
||||
}
|
||||
rethrow;
|
||||
|
||||
final UserCredential userCredential;
|
||||
try {
|
||||
userCredential = await _auth.signInWithCredential(credential);
|
||||
} on FirebaseAuthException catch (e) {
|
||||
if (e.code == 'invalid-verification-code') {
|
||||
throw const domain.InvalidCredentialsException(
|
||||
technicalMessage: 'Invalid OTP code entered.',
|
||||
);
|
||||
}
|
||||
}, requiresAuthentication: false);
|
||||
rethrow;
|
||||
}
|
||||
|
||||
final User? firebaseUser = userCredential.user;
|
||||
if (firebaseUser == null) {
|
||||
throw const domain.SignInFailedException(
|
||||
@@ -131,115 +172,68 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
);
|
||||
}
|
||||
|
||||
final QueryResult<GetUserByIdData, GetUserByIdVariables> response =
|
||||
await _service.run(
|
||||
() => _service.connector.getUserById(id: firebaseUser.uid).execute(),
|
||||
requiresAuthentication: false,
|
||||
);
|
||||
final GetUserByIdUser? user = response.data.user;
|
||||
|
||||
GetStaffByUserIdStaffs? staffRecord;
|
||||
|
||||
if (mode == AuthMode.signup) {
|
||||
if (user == null) {
|
||||
await _service.run(
|
||||
() => _service.connector
|
||||
.createUser(id: firebaseUser.uid, role: UserBaseRole.USER)
|
||||
.userRole('STAFF')
|
||||
.execute(),
|
||||
requiresAuthentication: false,
|
||||
);
|
||||
} else {
|
||||
// User exists in PostgreSQL. Check if they have a STAFF profile.
|
||||
final QueryResult<GetStaffByUserIdData, GetStaffByUserIdVariables>
|
||||
staffResponse = await _service.run(
|
||||
() => _service.connector
|
||||
.getStaffByUserId(userId: firebaseUser.uid)
|
||||
.execute(),
|
||||
requiresAuthentication: false,
|
||||
);
|
||||
|
||||
if (staffResponse.data.staffs.isNotEmpty) {
|
||||
// If profile exists, they should use Login mode.
|
||||
await _service.signOut();
|
||||
throw const domain.AccountExistsException(
|
||||
technicalMessage:
|
||||
'This user already has a staff profile. Please log in.',
|
||||
);
|
||||
}
|
||||
|
||||
// If they don't have a staff profile but they exist as BUSINESS,
|
||||
// they are allowed to "Sign Up" for Staff.
|
||||
// We update their userRole to 'BOTH'.
|
||||
if (user.userRole == 'BUSINESS') {
|
||||
await _service.run(
|
||||
() => _service.connector
|
||||
.updateUser(id: firebaseUser.uid)
|
||||
.userRole('BOTH')
|
||||
.execute(),
|
||||
requiresAuthentication: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (user == null) {
|
||||
await _service.signOut();
|
||||
throw const domain.UserNotFoundException(
|
||||
technicalMessage: 'Authenticated user profile not found in database.',
|
||||
);
|
||||
}
|
||||
// Allow STAFF or BOTH roles to log in to the Staff App
|
||||
if (user.userRole != 'STAFF' && user.userRole != 'BOTH') {
|
||||
await _service.signOut();
|
||||
throw const domain.UnauthorizedAppException(
|
||||
technicalMessage: 'User is not authorized for this app.',
|
||||
);
|
||||
}
|
||||
|
||||
final QueryResult<GetStaffByUserIdData, GetStaffByUserIdVariables>
|
||||
staffResponse = await _service.run(
|
||||
() => _service.connector
|
||||
.getStaffByUserId(userId: firebaseUser.uid)
|
||||
.execute(),
|
||||
requiresAuthentication: false,
|
||||
// Step 2: Get the Firebase ID token.
|
||||
final String? idToken = await firebaseUser.getIdToken();
|
||||
if (idToken == null) {
|
||||
throw const domain.SignInFailedException(
|
||||
technicalMessage: 'Failed to obtain Firebase ID token.',
|
||||
);
|
||||
if (staffResponse.data.staffs.isEmpty) {
|
||||
await _service.signOut();
|
||||
}
|
||||
|
||||
// Step 3: Call V2 verify endpoint with the Firebase ID token.
|
||||
final String v2Mode = mode == AuthMode.signup ? 'sign-up' : 'sign-in';
|
||||
final domain.ApiResponse response = await _apiService.post(
|
||||
V2ApiEndpoints.staffPhoneVerify,
|
||||
data: <String, dynamic>{
|
||||
'idToken': idToken,
|
||||
'mode': v2Mode,
|
||||
},
|
||||
);
|
||||
|
||||
final Map<String, dynamic> data = response.data as Map<String, dynamic>;
|
||||
|
||||
// Step 4: Check for business logic errors from the V2 API.
|
||||
final bool requiresProfileSetup =
|
||||
data['requiresProfileSetup'] as bool? ?? false;
|
||||
final Map<String, dynamic>? staffData =
|
||||
data['staff'] as Map<String, dynamic>?;
|
||||
final Map<String, dynamic>? userData =
|
||||
data['user'] as Map<String, dynamic>?;
|
||||
|
||||
// Handle mode-specific logic:
|
||||
// - Sign-up: staff may be null (requiresProfileSetup=true)
|
||||
// - Sign-in: staff must exist
|
||||
if (mode == AuthMode.login) {
|
||||
if (staffData == null) {
|
||||
await _auth.signOut();
|
||||
throw const domain.UserNotFoundException(
|
||||
technicalMessage:
|
||||
'Your account is not registered yet. Please register first.',
|
||||
);
|
||||
}
|
||||
staffRecord = staffResponse.data.staffs.first;
|
||||
}
|
||||
|
||||
//TO-DO: create(registration) user and staff account
|
||||
//TO-DO: save user data locally
|
||||
// Build the domain user from the V2 response.
|
||||
final domain.User domainUser = domain.User(
|
||||
id: firebaseUser.uid,
|
||||
email: user?.email ?? '',
|
||||
phone: user?.phone,
|
||||
role: user?.role.stringValue ?? 'USER',
|
||||
);
|
||||
final domain.Staff? domainStaff = staffRecord == null
|
||||
? null
|
||||
: domain.Staff(
|
||||
id: staffRecord.id,
|
||||
authProviderId: staffRecord.userId,
|
||||
name: staffRecord.fullName,
|
||||
email: staffRecord.email ?? '',
|
||||
phone: staffRecord.phone,
|
||||
status: domain.StaffStatus.completedProfile,
|
||||
address: staffRecord.addres,
|
||||
avatar: staffRecord.photoUrl,
|
||||
);
|
||||
StaffSessionStore.instance.setSession(
|
||||
StaffSession(
|
||||
staff: domainStaff,
|
||||
ownerId: staffRecord?.ownerId,
|
||||
),
|
||||
id: userData?['id'] as String? ?? firebaseUser.uid,
|
||||
email: userData?['email'] as String?,
|
||||
displayName: userData?['displayName'] as String?,
|
||||
phone: userData?['phone'] as String? ?? firebaseUser.phoneNumber,
|
||||
status: domain.UserStatus.active,
|
||||
);
|
||||
|
||||
return domainUser;
|
||||
}
|
||||
|
||||
/// Signs out via the V2 API and locally.
|
||||
@override
|
||||
Future<void> signOut() async {
|
||||
try {
|
||||
await _apiService.post(V2ApiEndpoints.signOut);
|
||||
} catch (_) {
|
||||
// Sign-out should not fail even if the API call fails.
|
||||
// The local sign-out below will clear the session regardless.
|
||||
}
|
||||
await _auth.signOut();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
import '../../domain/repositories/place_repository.dart';
|
||||
import 'package:staff_authentication/src/domain/repositories/place_repository.dart';
|
||||
|
||||
class PlaceRepositoryImpl implements PlaceRepository {
|
||||
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||
import 'package:firebase_data_connect/firebase_data_connect.dart' as fdc;
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart' as auth;
|
||||
import '../../domain/repositories/profile_setup_repository.dart';
|
||||
|
||||
import 'package:staff_authentication/src/domain/repositories/profile_setup_repository.dart';
|
||||
|
||||
/// V2 API implementation of [ProfileSetupRepository].
|
||||
///
|
||||
/// Submits the staff profile setup data to the V2 unified API
|
||||
/// endpoint `POST /staff/profile/setup`.
|
||||
class ProfileSetupRepositoryImpl implements ProfileSetupRepository {
|
||||
/// Creates a [ProfileSetupRepositoryImpl].
|
||||
///
|
||||
/// Requires a [BaseApiService] for V2 API calls.
|
||||
ProfileSetupRepositoryImpl({required BaseApiService apiService})
|
||||
: _apiService = apiService;
|
||||
|
||||
ProfileSetupRepositoryImpl() : _service = DataConnectService.instance;
|
||||
final DataConnectService _service;
|
||||
/// The V2 API service for backend calls.
|
||||
final BaseApiService _apiService;
|
||||
|
||||
@override
|
||||
Future<void> submitProfile({
|
||||
@@ -18,46 +26,27 @@ class ProfileSetupRepositoryImpl implements ProfileSetupRepository {
|
||||
required List<String> industries,
|
||||
required List<String> skills,
|
||||
}) async {
|
||||
return _service.run(() async {
|
||||
final auth.User? firebaseUser = _service.auth.currentUser;
|
||||
if (firebaseUser == null) {
|
||||
throw const NotAuthenticatedException(
|
||||
technicalMessage: 'User not authenticated.',
|
||||
);
|
||||
}
|
||||
final ApiResponse response = await _apiService.post(
|
||||
V2ApiEndpoints.staffProfileSetup,
|
||||
data: <String, dynamic>{
|
||||
'fullName': fullName,
|
||||
if (bio != null && bio.isNotEmpty) 'bio': bio,
|
||||
'preferredLocations': preferredLocations,
|
||||
'maxDistanceMiles': maxDistanceMiles.toInt(),
|
||||
'industries': industries,
|
||||
'skills': skills,
|
||||
},
|
||||
);
|
||||
|
||||
final StaffSession? session = StaffSessionStore.instance.session;
|
||||
final String email = session?.staff?.email ?? '';
|
||||
final String? phone = firebaseUser.phoneNumber;
|
||||
|
||||
final fdc.OperationResult<CreateStaffData, CreateStaffVariables> result =
|
||||
await _service.connector
|
||||
.createStaff(userId: firebaseUser.uid, fullName: fullName)
|
||||
.bio(bio)
|
||||
.preferredLocations(preferredLocations)
|
||||
.maxDistanceMiles(maxDistanceMiles.toInt())
|
||||
.industries(industries)
|
||||
.skills(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: fullName,
|
||||
email: email,
|
||||
phone: phone,
|
||||
status: StaffStatus.completedProfile,
|
||||
// Check for API-level errors.
|
||||
final Map<String, dynamic> data = response.data as Map<String, dynamic>;
|
||||
if (data['code'] != null &&
|
||||
data['code'].toString() != '200' &&
|
||||
data['code'].toString() != '201') {
|
||||
throw SignInFailedException(
|
||||
technicalMessage:
|
||||
data['message']?.toString() ?? 'Profile setup failed.',
|
||||
);
|
||||
|
||||
if (session != null) {
|
||||
StaffSessionStore.instance.setSession(
|
||||
StaffSession(staff: staff, ownerId: session.ownerId),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
import '../ui_entities/auth_mode.dart';
|
||||
import 'package:staff_authentication/src/domain/ui_entities/auth_mode.dart';
|
||||
|
||||
/// Represents the arguments required for the [VerifyOtpUseCase].
|
||||
///
|
||||
|
||||
@@ -1,24 +1,34 @@
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../ui_entities/auth_mode.dart';
|
||||
|
||||
import 'package:staff_authentication/src/domain/ui_entities/auth_mode.dart';
|
||||
|
||||
/// Interface for authentication repository.
|
||||
///
|
||||
/// Defines the contract for staff phone-based authentication,
|
||||
/// OTP verification, and sign-out operations.
|
||||
abstract interface class AuthRepositoryInterface {
|
||||
/// Stream of the current Firebase Auth user mapped to a domain [User].
|
||||
Stream<User?> get currentUser;
|
||||
|
||||
/// Signs in with a phone number and returns a verification ID.
|
||||
/// Initiates phone verification and returns a verification ID.
|
||||
///
|
||||
/// Uses the Firebase Auth SDK client-side to send an SMS code.
|
||||
Future<String?> signInWithPhone({required String phoneNumber});
|
||||
|
||||
/// Cancels any pending phone verification request (if possible).
|
||||
void cancelPendingPhoneVerification();
|
||||
|
||||
/// Verifies the OTP code and returns the authenticated user.
|
||||
/// Verifies the OTP code and completes authentication via the V2 API.
|
||||
///
|
||||
/// After Firebase credential sign-in, calls the V2 verify endpoint
|
||||
/// to hydrate the session context. Returns the authenticated [User]
|
||||
/// or `null` if verification fails.
|
||||
Future<User?> verifyOtp({
|
||||
required String verificationId,
|
||||
required String smsCode,
|
||||
required AuthMode mode,
|
||||
});
|
||||
|
||||
/// Signs out the current user.
|
||||
/// Signs out the current user via the V2 API and locally.
|
||||
Future<void> signOut();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import '../repositories/place_repository.dart';
|
||||
import 'package:staff_authentication/src/domain/repositories/place_repository.dart';
|
||||
|
||||
/// Use case for searching cities via the Places API.
|
||||
///
|
||||
/// Delegates to [PlaceRepository] for autocomplete results.
|
||||
class SearchCitiesUseCase {
|
||||
|
||||
/// Creates a [SearchCitiesUseCase].
|
||||
SearchCitiesUseCase(this._repository);
|
||||
|
||||
/// The repository for place search operations.
|
||||
final PlaceRepository _repository;
|
||||
|
||||
/// Searches for cities matching the given [query].
|
||||
Future<List<String>> call(String query) {
|
||||
return _repository.searchCities(query);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
import '../arguments/sign_in_with_phone_arguments.dart';
|
||||
import '../repositories/auth_repository_interface.dart';
|
||||
import 'package:staff_authentication/src/domain/arguments/sign_in_with_phone_arguments.dart';
|
||||
import 'package:staff_authentication/src/domain/repositories/auth_repository_interface.dart';
|
||||
|
||||
/// Use case for signing in with a phone number.
|
||||
///
|
||||
/// This use case delegates the sign-in logic to the [AuthRepositoryInterface].
|
||||
/// Delegates the sign-in logic to the [AuthRepositoryInterface].
|
||||
class SignInWithPhoneUseCase
|
||||
implements UseCase<SignInWithPhoneArguments, String?> {
|
||||
|
||||
/// Creates a [SignInWithPhoneUseCase].
|
||||
///
|
||||
/// Requires an [AuthRepositoryInterface] to interact with the authentication data source.
|
||||
SignInWithPhoneUseCase(this._repository);
|
||||
|
||||
/// The repository for authentication operations.
|
||||
final AuthRepositoryInterface _repository;
|
||||
|
||||
@override
|
||||
@@ -19,6 +18,7 @@ class SignInWithPhoneUseCase
|
||||
return _repository.signInWithPhone(phoneNumber: arguments.phoneNumber);
|
||||
}
|
||||
|
||||
/// Cancels any pending phone verification request.
|
||||
void cancelPending() {
|
||||
_repository.cancelPendingPhoneVerification();
|
||||
}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import '../repositories/profile_setup_repository.dart';
|
||||
import 'package:staff_authentication/src/domain/repositories/profile_setup_repository.dart';
|
||||
|
||||
/// Use case for submitting the staff profile setup.
|
||||
///
|
||||
/// Delegates to [ProfileSetupRepository] to persist the profile data.
|
||||
class SubmitProfileSetup {
|
||||
|
||||
/// Creates a [SubmitProfileSetup].
|
||||
SubmitProfileSetup(this.repository);
|
||||
|
||||
/// The repository for profile setup operations.
|
||||
final ProfileSetupRepository repository;
|
||||
|
||||
/// Submits the profile setup with the given data.
|
||||
Future<void> call({
|
||||
required String fullName,
|
||||
String? bio,
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../arguments/verify_otp_arguments.dart';
|
||||
import '../repositories/auth_repository_interface.dart';
|
||||
import 'package:staff_authentication/src/domain/arguments/verify_otp_arguments.dart';
|
||||
import 'package:staff_authentication/src/domain/repositories/auth_repository_interface.dart';
|
||||
|
||||
/// Use case for verifying an OTP code.
|
||||
///
|
||||
/// This use case delegates the OTP verification logic to the [AuthRepositoryInterface].
|
||||
/// Delegates the OTP verification logic to the [AuthRepositoryInterface].
|
||||
class VerifyOtpUseCase implements UseCase<VerifyOtpArguments, User?> {
|
||||
|
||||
/// Creates a [VerifyOtpUseCase].
|
||||
///
|
||||
/// Requires an [AuthRepositoryInterface] to interact with the authentication data source.
|
||||
VerifyOtpUseCase(this._repository);
|
||||
|
||||
/// The repository for authentication operations.
|
||||
final AuthRepositoryInterface _repository;
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import '../../domain/arguments/sign_in_with_phone_arguments.dart';
|
||||
import '../../domain/arguments/verify_otp_arguments.dart';
|
||||
import '../../domain/usecases/sign_in_with_phone_usecase.dart';
|
||||
import '../../domain/usecases/verify_otp_usecase.dart';
|
||||
import 'auth_event.dart';
|
||||
import 'auth_state.dart';
|
||||
import 'package:staff_authentication/src/domain/arguments/sign_in_with_phone_arguments.dart';
|
||||
import 'package:staff_authentication/src/domain/arguments/verify_otp_arguments.dart';
|
||||
import 'package:staff_authentication/src/domain/usecases/sign_in_with_phone_usecase.dart';
|
||||
import 'package:staff_authentication/src/domain/usecases/verify_otp_usecase.dart';
|
||||
import 'package:staff_authentication/src/presentation/blocs/auth_event.dart';
|
||||
import 'package:staff_authentication/src/presentation/blocs/auth_state.dart';
|
||||
|
||||
/// BLoC responsible for handling authentication logic.
|
||||
///
|
||||
/// Coordinates phone verification and OTP submission via use cases.
|
||||
class AuthBloc extends Bloc<AuthEvent, AuthState>
|
||||
with BlocErrorHandler<AuthState>
|
||||
implements Disposable {
|
||||
|
||||
/// Creates an [AuthBloc].
|
||||
AuthBloc({
|
||||
required SignInWithPhoneUseCase signInUseCase,
|
||||
required VerifyOtpUseCase verifyOtpUseCase,
|
||||
}) : _signInUseCase = signInUseCase,
|
||||
_verifyOtpUseCase = verifyOtpUseCase,
|
||||
super(const AuthState()) {
|
||||
}) : _signInUseCase = signInUseCase,
|
||||
_verifyOtpUseCase = verifyOtpUseCase,
|
||||
super(const AuthState()) {
|
||||
on<AuthSignInRequested>(_onSignInRequested);
|
||||
on<AuthOtpSubmitted>(_onOtpSubmitted);
|
||||
on<AuthErrorCleared>(_onErrorCleared);
|
||||
@@ -30,15 +32,26 @@ class AuthBloc extends Bloc<AuthEvent, AuthState>
|
||||
on<AuthResetRequested>(_onResetRequested);
|
||||
on<AuthCooldownTicked>(_onCooldownTicked);
|
||||
}
|
||||
|
||||
/// The use case for signing in with a phone number.
|
||||
final SignInWithPhoneUseCase _signInUseCase;
|
||||
|
||||
/// The use case for verifying an OTP.
|
||||
final VerifyOtpUseCase _verifyOtpUseCase;
|
||||
|
||||
/// Token to track the latest request and ignore stale completions.
|
||||
int _requestToken = 0;
|
||||
|
||||
/// Timestamp of the last code request for cooldown enforcement.
|
||||
DateTime? _lastCodeRequestAt;
|
||||
|
||||
/// When the cooldown expires.
|
||||
DateTime? _cooldownUntil;
|
||||
|
||||
/// Duration users must wait between code requests.
|
||||
static const Duration _resendCooldown = Duration(seconds: 31);
|
||||
|
||||
/// Timer for ticking down the cooldown.
|
||||
Timer? _cooldownTimer;
|
||||
|
||||
/// Clears any authentication error from the state.
|
||||
@@ -138,6 +151,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState>
|
||||
);
|
||||
}
|
||||
|
||||
/// Handles cooldown tick events.
|
||||
void _onCooldownTicked(
|
||||
AuthCooldownTicked event,
|
||||
Emitter<AuthState> emit,
|
||||
@@ -165,22 +179,27 @@ class AuthBloc extends Bloc<AuthEvent, AuthState>
|
||||
);
|
||||
}
|
||||
|
||||
/// Starts the cooldown timer with the given remaining seconds.
|
||||
void _startCooldown(int secondsRemaining) {
|
||||
_cancelCooldownTimer();
|
||||
int remaining = secondsRemaining;
|
||||
add(AuthCooldownTicked(remaining));
|
||||
_cooldownTimer = Timer.periodic(const Duration(seconds: 1), (Timer timer) {
|
||||
remaining -= 1;
|
||||
if (remaining <= 0) {
|
||||
timer.cancel();
|
||||
_cooldownTimer = null;
|
||||
add(const AuthCooldownTicked(0));
|
||||
return;
|
||||
}
|
||||
add(AuthCooldownTicked(remaining));
|
||||
});
|
||||
_cooldownTimer = Timer.periodic(
|
||||
const Duration(seconds: 1),
|
||||
(Timer timer) {
|
||||
remaining -= 1;
|
||||
if (remaining <= 0) {
|
||||
timer.cancel();
|
||||
_cooldownTimer = null;
|
||||
add(const AuthCooldownTicked(0));
|
||||
return;
|
||||
}
|
||||
add(AuthCooldownTicked(remaining));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Cancels the cooldown timer if active.
|
||||
void _cancelCooldownTimer() {
|
||||
_cooldownTimer?.cancel();
|
||||
_cooldownTimer = null;
|
||||
@@ -218,4 +237,3 @@ class AuthBloc extends Bloc<AuthEvent, AuthState>
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import '../../../domain/usecases/submit_profile_setup_usecase.dart';
|
||||
import 'package:staff_authentication/src/domain/usecases/submit_profile_setup_usecase.dart';
|
||||
import 'package:staff_authentication/src/domain/usecases/search_cities_usecase.dart';
|
||||
import 'package:staff_authentication/src/presentation/blocs/profile_setup/profile_setup_event.dart';
|
||||
import 'package:staff_authentication/src/presentation/blocs/profile_setup/profile_setup_state.dart';
|
||||
|
||||
import '../../../domain/usecases/search_cities_usecase.dart';
|
||||
|
||||
import 'profile_setup_event.dart';
|
||||
import 'profile_setup_state.dart';
|
||||
|
||||
export 'profile_setup_event.dart';
|
||||
export 'profile_setup_state.dart';
|
||||
export 'package:staff_authentication/src/presentation/blocs/profile_setup/profile_setup_event.dart';
|
||||
export 'package:staff_authentication/src/presentation/blocs/profile_setup/profile_setup_state.dart';
|
||||
|
||||
/// BLoC responsible for managing the profile setup state and logic.
|
||||
class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState>
|
||||
with BlocErrorHandler<ProfileSetupState> {
|
||||
/// Creates a [ProfileSetupBloc].
|
||||
ProfileSetupBloc({
|
||||
required SubmitProfileSetup submitProfileSetup,
|
||||
required SearchCitiesUseCase searchCities,
|
||||
}) : _submitProfileSetup = submitProfileSetup,
|
||||
_searchCities = searchCities,
|
||||
super(const ProfileSetupState()) {
|
||||
}) : _submitProfileSetup = submitProfileSetup,
|
||||
_searchCities = searchCities,
|
||||
super(const ProfileSetupState()) {
|
||||
on<ProfileSetupFullNameChanged>(_onFullNameChanged);
|
||||
on<ProfileSetupBioChanged>(_onBioChanged);
|
||||
on<ProfileSetupLocationsChanged>(_onLocationsChanged);
|
||||
@@ -30,7 +29,10 @@ class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState>
|
||||
on<ProfileSetupClearLocationSuggestions>(_onClearLocationSuggestions);
|
||||
}
|
||||
|
||||
/// The use case for submitting the profile setup.
|
||||
final SubmitProfileSetup _submitProfileSetup;
|
||||
|
||||
/// The use case for searching cities.
|
||||
final SearchCitiesUseCase _searchCities;
|
||||
|
||||
/// Handles the [ProfileSetupFullNameChanged] event.
|
||||
@@ -109,6 +111,7 @@ class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState>
|
||||
);
|
||||
}
|
||||
|
||||
/// Handles location query changes for autocomplete search.
|
||||
Future<void> _onLocationQueryChanged(
|
||||
ProfileSetupLocationQueryChanged event,
|
||||
Emitter<ProfileSetupState> emit,
|
||||
@@ -118,17 +121,16 @@ class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState>
|
||||
return;
|
||||
}
|
||||
|
||||
// For search, we might want to handle errors silently or distinctively
|
||||
// Using simple try-catch here as it's a search-as-you-type feature where error dialogs are intrusive
|
||||
try {
|
||||
final List<String> results = await _searchCities(event.query);
|
||||
emit(state.copyWith(locationSuggestions: results));
|
||||
} catch (e) {
|
||||
// Quietly fail or clear
|
||||
// Quietly fail for search-as-you-type.
|
||||
emit(state.copyWith(locationSuggestions: <String>[]));
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears the location suggestions list.
|
||||
void _onClearLocationSuggestions(
|
||||
ProfileSetupClearLocationSuggestions event,
|
||||
Emitter<ProfileSetupState> emit,
|
||||
@@ -136,4 +138,3 @@ class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState>
|
||||
emit(state.copyWith(locationSuggestions: <String>[]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
|
||||
import 'package:krow_core/core.dart';
|
||||
import '../widgets/get_started_page/get_started_actions.dart';
|
||||
import '../widgets/get_started_page/get_started_background.dart';
|
||||
import '../widgets/get_started_page/get_started_header.dart';
|
||||
import 'package:staff_authentication/src/presentation/widgets/get_started_page/get_started_actions.dart';
|
||||
import 'package:staff_authentication/src/presentation/widgets/get_started_page/get_started_background.dart';
|
||||
import 'package:staff_authentication/src/presentation/widgets/get_started_page/get_started_header.dart';
|
||||
|
||||
/// The entry point page for staff authentication.
|
||||
///
|
||||
|
||||
@@ -9,8 +9,8 @@ import 'package:staff_authentication/src/presentation/blocs/auth_state.dart';
|
||||
import 'package:staff_authentication/staff_authentication.dart';
|
||||
|
||||
import 'package:krow_core/core.dart';
|
||||
import '../widgets/phone_verification_page/otp_verification.dart';
|
||||
import '../widgets/phone_verification_page/phone_input.dart';
|
||||
import 'package:staff_authentication/src/presentation/widgets/phone_verification_page/otp_verification.dart';
|
||||
import 'package:staff_authentication/src/presentation/widgets/phone_verification_page/phone_input.dart';
|
||||
|
||||
/// A combined page for phone number entry and OTP verification.
|
||||
///
|
||||
|
||||
@@ -6,11 +6,11 @@ import 'package:flutter_modular/flutter_modular.dart'
|
||||
hide ModularWatchExtension;
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
import '../blocs/profile_setup/profile_setup_bloc.dart';
|
||||
import '../widgets/profile_setup_page/profile_setup_basic_info.dart';
|
||||
import '../widgets/profile_setup_page/profile_setup_experience.dart';
|
||||
import '../widgets/profile_setup_page/profile_setup_header.dart';
|
||||
import '../widgets/profile_setup_page/profile_setup_location.dart';
|
||||
import 'package:staff_authentication/src/presentation/blocs/profile_setup/profile_setup_bloc.dart';
|
||||
import 'package:staff_authentication/src/presentation/widgets/profile_setup_page/profile_setup_basic_info.dart';
|
||||
import 'package:staff_authentication/src/presentation/widgets/profile_setup_page/profile_setup_experience.dart';
|
||||
import 'package:staff_authentication/src/presentation/widgets/profile_setup_page/profile_setup_header.dart';
|
||||
import 'package:staff_authentication/src/presentation/widgets/profile_setup_page/profile_setup_location.dart';
|
||||
|
||||
/// Page for setting up the user profile after authentication.
|
||||
class ProfileSetupPage extends StatefulWidget {
|
||||
|
||||
@@ -4,8 +4,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../../blocs/auth_event.dart';
|
||||
import '../../../blocs/auth_bloc.dart';
|
||||
import 'package:staff_authentication/src/presentation/blocs/auth_event.dart';
|
||||
import 'package:staff_authentication/src/presentation/blocs/auth_bloc.dart';
|
||||
|
||||
/// A widget that displays a 6-digit OTP input field.
|
||||
///
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:core_localization/core_localization.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../common/auth_trouble_link.dart';
|
||||
import 'package:staff_authentication/src/presentation/widgets/common/auth_trouble_link.dart';
|
||||
|
||||
/// A widget that displays the primary action button and trouble link for OTP verification.
|
||||
class OtpVerificationActions extends StatelessWidget {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:staff_authentication/src/presentation/widgets/common/section_title_subtitle.dart';
|
||||
|
||||
/// A widget for setting up skills and preferred industries.
|
||||
@@ -27,6 +26,36 @@ class ProfileSetupExperience extends StatelessWidget {
|
||||
/// Callback for when industries change.
|
||||
final ValueChanged<List<String>> onIndustriesChanged;
|
||||
|
||||
/// Available skill options with their API values and labels.
|
||||
static const List<String> _skillValues = <String>[
|
||||
'food_service',
|
||||
'bartending',
|
||||
'event_setup',
|
||||
'hospitality',
|
||||
'warehouse',
|
||||
'customer_service',
|
||||
'cleaning',
|
||||
'security',
|
||||
'retail',
|
||||
'cooking',
|
||||
'cashier',
|
||||
'server',
|
||||
'barista',
|
||||
'host_hostess',
|
||||
'busser',
|
||||
'driving',
|
||||
];
|
||||
|
||||
/// Available industry options with their API values.
|
||||
static const List<String> _industryValues = <String>[
|
||||
'hospitality',
|
||||
'food_service',
|
||||
'warehouse',
|
||||
'events',
|
||||
'retail',
|
||||
'healthcare',
|
||||
];
|
||||
|
||||
/// Toggles a skill.
|
||||
void _toggleSkill({required String skill}) {
|
||||
final List<String> updatedList = List<String>.from(skills);
|
||||
@@ -71,15 +100,14 @@ class ProfileSetupExperience extends StatelessWidget {
|
||||
Wrap(
|
||||
spacing: UiConstants.space2,
|
||||
runSpacing: UiConstants.space2,
|
||||
children: ExperienceSkill.values.map((ExperienceSkill skill) {
|
||||
final bool isSelected = skills.contains(skill.value);
|
||||
// Dynamic translation access
|
||||
children: _skillValues.map((String skill) {
|
||||
final bool isSelected = skills.contains(skill);
|
||||
final String label = _getSkillLabel(skill);
|
||||
|
||||
return UiChip(
|
||||
label: label,
|
||||
isSelected: isSelected,
|
||||
onTap: () => _toggleSkill(skill: skill.value),
|
||||
onTap: () => _toggleSkill(skill: skill),
|
||||
leadingIcon: isSelected ? UiIcons.check : null,
|
||||
variant: UiChipVariant.primary,
|
||||
);
|
||||
@@ -97,14 +125,14 @@ class ProfileSetupExperience extends StatelessWidget {
|
||||
Wrap(
|
||||
spacing: UiConstants.space2,
|
||||
runSpacing: UiConstants.space2,
|
||||
children: Industry.values.map((Industry industry) {
|
||||
final bool isSelected = industries.contains(industry.value);
|
||||
children: _industryValues.map((String industry) {
|
||||
final bool isSelected = industries.contains(industry);
|
||||
final String label = _getIndustryLabel(industry);
|
||||
|
||||
return UiChip(
|
||||
label: label,
|
||||
isSelected: isSelected,
|
||||
onTap: () => _toggleIndustry(industry: industry.value),
|
||||
onTap: () => _toggleIndustry(industry: industry),
|
||||
leadingIcon: isSelected ? UiIcons.check : null,
|
||||
variant: isSelected
|
||||
? UiChipVariant.accent
|
||||
@@ -116,131 +144,71 @@ class ProfileSetupExperience extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
String _getSkillLabel(ExperienceSkill skill) {
|
||||
String _getSkillLabel(String skill) {
|
||||
final TranslationsStaffAuthenticationProfileSetupPageExperienceSkillsEn
|
||||
skillsI18n = t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.skills;
|
||||
switch (skill) {
|
||||
case ExperienceSkill.foodService:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.skills
|
||||
.food_service;
|
||||
case ExperienceSkill.bartending:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.skills
|
||||
.bartending;
|
||||
case ExperienceSkill.warehouse:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.skills
|
||||
.warehouse;
|
||||
case ExperienceSkill.retail:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.skills
|
||||
.retail;
|
||||
// Note: 'events' was removed from enum in favor of 'event_setup' or industry.
|
||||
// Using 'events' translation for eventSetup if available or fallback.
|
||||
case ExperienceSkill.eventSetup:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.skills
|
||||
.events;
|
||||
case ExperienceSkill.customerService:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.skills
|
||||
.customer_service;
|
||||
case ExperienceSkill.cleaning:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.skills
|
||||
.cleaning;
|
||||
case ExperienceSkill.security:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.skills
|
||||
.security;
|
||||
case ExperienceSkill.driving:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.skills
|
||||
.driving;
|
||||
case ExperienceSkill.cooking:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.skills
|
||||
.cooking;
|
||||
case 'food_service':
|
||||
return skillsI18n.food_service;
|
||||
case 'bartending':
|
||||
return skillsI18n.bartending;
|
||||
case 'warehouse':
|
||||
return skillsI18n.warehouse;
|
||||
case 'retail':
|
||||
return skillsI18n.retail;
|
||||
case 'event_setup':
|
||||
return skillsI18n.events;
|
||||
case 'customer_service':
|
||||
return skillsI18n.customer_service;
|
||||
case 'cleaning':
|
||||
return skillsI18n.cleaning;
|
||||
case 'security':
|
||||
return skillsI18n.security;
|
||||
case 'driving':
|
||||
return skillsI18n.driving;
|
||||
case 'cooking':
|
||||
return skillsI18n.cooking;
|
||||
case 'cashier':
|
||||
return skillsI18n.cashier;
|
||||
case 'server':
|
||||
return skillsI18n.server;
|
||||
case 'barista':
|
||||
return skillsI18n.barista;
|
||||
case 'host_hostess':
|
||||
return skillsI18n.host_hostess;
|
||||
case 'busser':
|
||||
return skillsI18n.busser;
|
||||
default:
|
||||
return skill.value;
|
||||
return skill;
|
||||
}
|
||||
}
|
||||
|
||||
String _getIndustryLabel(Industry industry) {
|
||||
String _getIndustryLabel(String industry) {
|
||||
final TranslationsStaffAuthenticationProfileSetupPageExperienceIndustriesEn
|
||||
industriesI18n = t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.industries;
|
||||
switch (industry) {
|
||||
case Industry.hospitality:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.industries
|
||||
.hospitality;
|
||||
case Industry.foodService:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.industries
|
||||
.food_service;
|
||||
case Industry.warehouse:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.industries
|
||||
.warehouse;
|
||||
case Industry.events:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.industries
|
||||
.events;
|
||||
case Industry.retail:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.industries
|
||||
.retail;
|
||||
case Industry.healthcare:
|
||||
return t
|
||||
.staff_authentication
|
||||
.profile_setup_page
|
||||
.experience
|
||||
.industries
|
||||
.healthcare;
|
||||
case 'hospitality':
|
||||
return industriesI18n.hospitality;
|
||||
case 'food_service':
|
||||
return industriesI18n.food_service;
|
||||
case 'warehouse':
|
||||
return industriesI18n.warehouse;
|
||||
case 'events':
|
||||
return industriesI18n.events;
|
||||
case 'retail':
|
||||
return industriesI18n.retail;
|
||||
case 'healthcare':
|
||||
return industriesI18n.healthcare;
|
||||
default:
|
||||
return industry.value;
|
||||
return industry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:staff_authentication/src/data/repositories_impl/auth_repository_impl.dart';
|
||||
import 'package:staff_authentication/src/domain/repositories/auth_repository_interface.dart';
|
||||
import 'package:staff_authentication/src/domain/usecases/sign_in_with_phone_usecase.dart';
|
||||
@@ -20,17 +20,24 @@ import 'package:staff_authentication/src/presentation/pages/phone_verification_p
|
||||
import 'package:staff_authentication/src/presentation/pages/profile_setup_page.dart';
|
||||
import 'package:staff_authentication/src/domain/ui_entities/auth_mode.dart';
|
||||
|
||||
/// A [Module] for the staff authentication feature.
|
||||
/// A [Module] for the staff authentication feature.
|
||||
///
|
||||
/// Provides repositories, use cases, and BLoCs for phone-based
|
||||
/// authentication and profile setup. Uses V2 API via [BaseApiService].
|
||||
class StaffAuthenticationModule extends Module {
|
||||
@override
|
||||
List<Module> get imports => <Module>[DataConnectModule()];
|
||||
List<Module> get imports => <Module>[CoreModule()];
|
||||
|
||||
@override
|
||||
void binds(Injector i) {
|
||||
// Repositories
|
||||
i.addLazySingleton<ProfileSetupRepository>(ProfileSetupRepositoryImpl.new);
|
||||
i.addLazySingleton<AuthRepositoryInterface>(
|
||||
() => AuthRepositoryImpl(apiService: i.get<BaseApiService>()),
|
||||
);
|
||||
i.addLazySingleton<ProfileSetupRepository>(
|
||||
() => ProfileSetupRepositoryImpl(apiService: i.get<BaseApiService>()),
|
||||
);
|
||||
i.addLazySingleton<PlaceRepository>(PlaceRepositoryImpl.new);
|
||||
i.addLazySingleton<AuthRepositoryInterface>(AuthRepositoryImpl.new);
|
||||
|
||||
// UseCases
|
||||
i.addLazySingleton(SignInWithPhoneUseCase.new);
|
||||
@@ -53,7 +60,6 @@ class StaffAuthenticationModule extends Module {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
void routes(RouteManager r) {
|
||||
r.child(StaffPaths.root, child: (_) => const IntroPage());
|
||||
|
||||
@@ -14,16 +14,12 @@ dependencies:
|
||||
flutter_bloc: ^8.1.0
|
||||
flutter_modular: ^6.3.0
|
||||
equatable: ^2.0.5
|
||||
firebase_core: ^4.2.1
|
||||
firebase_auth: ^6.1.2
|
||||
firebase_data_connect: ^0.2.2+1
|
||||
http: ^1.2.0
|
||||
|
||||
|
||||
# Architecture Packages
|
||||
krow_domain:
|
||||
path: ../../../domain
|
||||
krow_data_connect:
|
||||
path: ../../../data_connect
|
||||
krow_core:
|
||||
path: ../../../core
|
||||
design_system:
|
||||
|
||||
Reference in New Issue
Block a user