feat: Refactor session management and improve user session data retrieval

This commit is contained in:
Achintha Isuru
2026-02-17 16:05:45 -05:00
parent 631af65a2f
commit ddf270074b
8 changed files with 281 additions and 223 deletions

View File

@@ -1,5 +1,3 @@
import 'package:krow_domain/krow_domain.dart' as domain;
class ClientBusinessSession { class ClientBusinessSession {
final String id; final String id;
final String businessName; final String businessName;
@@ -19,15 +17,9 @@ class ClientBusinessSession {
} }
class ClientSession { class ClientSession {
final domain.User user;
final String? userPhotoUrl;
final ClientBusinessSession? business; final ClientBusinessSession? business;
const ClientSession({ const ClientSession({required this.business});
required this.user,
required this.userPhotoUrl,
required this.business,
});
} }
class ClientSessionStore { class ClientSessionStore {

View File

@@ -24,9 +24,8 @@ import '../../domain/repositories/auth_repository_interface.dart';
/// identity management and Krow's Data Connect SDK for storing user profile data. /// identity management and Krow's Data Connect SDK for storing user profile data.
class AuthRepositoryImpl implements AuthRepositoryInterface { class AuthRepositoryImpl implements AuthRepositoryInterface {
/// Creates an [AuthRepositoryImpl] with the real dependencies. /// Creates an [AuthRepositoryImpl] with the real dependencies.
AuthRepositoryImpl({ AuthRepositoryImpl({dc.DataConnectService? service})
dc.DataConnectService? service, : _service = service ?? dc.DataConnectService.instance;
}) : _service = service ?? dc.DataConnectService.instance;
final dc.DataConnectService _service; final dc.DataConnectService _service;
@@ -36,11 +35,8 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
required String password, required String password,
}) async { }) async {
try { try {
final firebase.UserCredential credential = final firebase.UserCredential credential = await _service.auth
await _service.auth.signInWithEmailAndPassword( .signInWithEmailAndPassword(email: email, password: password);
email: email,
password: password,
);
final firebase.User? firebaseUser = credential.user; final firebase.User? firebaseUser = credential.user;
if (firebaseUser == null) { if (firebaseUser == null) {
@@ -60,9 +56,7 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
technicalMessage: 'Firebase error code: ${e.code}', technicalMessage: 'Firebase error code: ${e.code}',
); );
} else if (e.code == 'network-request-failed') { } else if (e.code == 'network-request-failed') {
throw NetworkException( throw NetworkException(technicalMessage: 'Firebase: ${e.message}');
technicalMessage: 'Firebase: ${e.message}',
);
} else { } else {
throw SignInFailedException( throw SignInFailedException(
technicalMessage: 'Firebase auth error: ${e.message}', technicalMessage: 'Firebase auth error: ${e.message}',
@@ -71,9 +65,7 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
} on domain.AppException { } on domain.AppException {
rethrow; rethrow;
} catch (e) { } catch (e) {
throw SignInFailedException( throw SignInFailedException(technicalMessage: 'Unexpected error: $e');
technicalMessage: 'Unexpected error: $e',
);
} }
} }
@@ -88,11 +80,8 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
try { try {
// Step 1: Try to create Firebase Auth user // Step 1: Try to create Firebase Auth user
final firebase.UserCredential credential = final firebase.UserCredential credential = await _service.auth
await _service.auth.createUserWithEmailAndPassword( .createUserWithEmailAndPassword(email: email, password: password);
email: email,
password: password,
);
firebaseUser = credential.user; firebaseUser = credential.user;
if (firebaseUser == null) { if (firebaseUser == null) {
@@ -111,9 +100,7 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
); );
} on firebase.FirebaseAuthException catch (e) { } on firebase.FirebaseAuthException catch (e) {
if (e.code == 'weak-password') { if (e.code == 'weak-password') {
throw WeakPasswordException( throw WeakPasswordException(technicalMessage: 'Firebase: ${e.message}');
technicalMessage: 'Firebase: ${e.message}',
);
} else if (e.code == 'email-already-in-use') { } else if (e.code == 'email-already-in-use') {
// Email exists in Firebase Auth - try to sign in and complete registration // Email exists in Firebase Auth - try to sign in and complete registration
return await _handleExistingFirebaseAccount( return await _handleExistingFirebaseAccount(
@@ -122,9 +109,7 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
companyName: companyName, companyName: companyName,
); );
} else if (e.code == 'network-request-failed') { } else if (e.code == 'network-request-failed') {
throw NetworkException( throw NetworkException(technicalMessage: 'Firebase: ${e.message}');
technicalMessage: 'Firebase: ${e.message}',
);
} else { } else {
throw SignUpFailedException( throw SignUpFailedException(
technicalMessage: 'Firebase auth error: ${e.message}', technicalMessage: 'Firebase auth error: ${e.message}',
@@ -133,15 +118,17 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
} on domain.AppException { } on domain.AppException {
// Rollback for our known exceptions // Rollback for our known exceptions
await _rollbackSignUp( await _rollbackSignUp(
firebaseUser: firebaseUser, businessId: createdBusinessId); firebaseUser: firebaseUser,
businessId: createdBusinessId,
);
rethrow; rethrow;
} catch (e) { } catch (e) {
// Rollback: Clean up any partially created resources // Rollback: Clean up any partially created resources
await _rollbackSignUp( await _rollbackSignUp(
firebaseUser: firebaseUser, businessId: createdBusinessId); firebaseUser: firebaseUser,
throw SignUpFailedException( businessId: createdBusinessId,
technicalMessage: 'Unexpected error: $e',
); );
throw SignUpFailedException(technicalMessage: 'Unexpected error: $e');
} }
} }
@@ -161,16 +148,15 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
required String password, required String password,
required String companyName, required String companyName,
}) async { }) async {
developer.log('Email exists in Firebase, attempting sign-in: $email', developer.log(
name: 'AuthRepository'); 'Email exists in Firebase, attempting sign-in: $email',
name: 'AuthRepository',
);
try { try {
// Try to sign in with the provided password // Try to sign in with the provided password
final firebase.UserCredential credential = final firebase.UserCredential credential = await _service.auth
await _service.auth.signInWithEmailAndPassword( .signInWithEmailAndPassword(email: email, password: password);
email: email,
password: password,
);
final firebase.User? firebaseUser = credential.user; final firebase.User? firebaseUser = credential.user;
if (firebaseUser == null) { if (firebaseUser == null) {
@@ -180,32 +166,40 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
} }
// Sign-in succeeded! Check if user already has a BUSINESS account in PostgreSQL // Sign-in succeeded! Check if user already has a BUSINESS account in PostgreSQL
final bool hasBusinessAccount = final bool hasBusinessAccount = await _checkBusinessUserExists(
await _checkBusinessUserExists(firebaseUser.uid); firebaseUser.uid,
);
if (hasBusinessAccount) { if (hasBusinessAccount) {
// User already has a KROW Client account // User already has a KROW Client account
developer.log('User already has BUSINESS account: ${firebaseUser.uid}', developer.log(
name: 'AuthRepository'); 'User already has BUSINESS account: ${firebaseUser.uid}',
name: 'AuthRepository',
);
throw AccountExistsException( throw AccountExistsException(
technicalMessage: 'User ${firebaseUser.uid} already has BUSINESS role', technicalMessage:
'User ${firebaseUser.uid} already has BUSINESS role',
); );
} }
// User exists in Firebase but not in KROW PostgreSQL - create the entities // User exists in Firebase but not in KROW PostgreSQL - create the entities
developer.log( developer.log(
'Creating BUSINESS account for existing Firebase user: ${firebaseUser.uid}', 'Creating BUSINESS account for existing Firebase user: ${firebaseUser.uid}',
name: 'AuthRepository'); name: 'AuthRepository',
);
return await _createBusinessAndUser( return await _createBusinessAndUser(
firebaseUser: firebaseUser, firebaseUser: firebaseUser,
companyName: companyName, companyName: companyName,
email: email, email: email,
onBusinessCreated: (_) {}, // No rollback needed for existing Firebase user onBusinessCreated:
(_) {}, // No rollback needed for existing Firebase user
); );
} on firebase.FirebaseAuthException catch (e) { } on firebase.FirebaseAuthException catch (e) {
// Sign-in failed - check why // Sign-in failed - check why
developer.log('Sign-in failed with code: ${e.code}', developer.log(
name: 'AuthRepository'); 'Sign-in failed with code: ${e.code}',
name: 'AuthRepository',
);
if (e.code == 'wrong-password' || e.code == 'invalid-credential') { if (e.code == 'wrong-password' || e.code == 'invalid-credential') {
// Password doesn't match - check what providers are available // Password doesn't match - check what providers are available
@@ -229,8 +223,10 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
// We can't distinguish between "wrong password" and "no password provider" // We can't distinguish between "wrong password" and "no password provider"
// due to Firebase deprecating fetchSignInMethodsForEmail. // due to Firebase deprecating fetchSignInMethodsForEmail.
// The PasswordMismatchException message covers both scenarios. // The PasswordMismatchException message covers both scenarios.
developer.log('Password mismatch or different provider for: $email', developer.log(
name: 'AuthRepository'); 'Password mismatch or different provider for: $email',
name: 'AuthRepository',
);
throw PasswordMismatchException( throw PasswordMismatchException(
technicalMessage: technicalMessage:
'Email $email: password mismatch or different auth provider', 'Email $email: password mismatch or different auth provider',
@@ -242,7 +238,8 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
Future<bool> _checkBusinessUserExists(String firebaseUserId) async { Future<bool> _checkBusinessUserExists(String firebaseUserId) async {
final QueryResult<dc.GetUserByIdData, dc.GetUserByIdVariables> response = final QueryResult<dc.GetUserByIdData, dc.GetUserByIdVariables> response =
await _service.run( await _service.run(
() => _service.connector.getUserById(id: firebaseUserId).execute()); () => _service.connector.getUserById(id: firebaseUserId).execute(),
);
final dc.GetUserByIdUser? user = response.data.user; final dc.GetUserByIdUser? user = response.data.user;
return user != null && return user != null &&
(user.userRole == 'BUSINESS' || user.userRole == 'BOTH'); (user.userRole == 'BUSINESS' || user.userRole == 'BOTH');
@@ -258,14 +255,16 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
// Create Business entity in PostgreSQL // Create Business entity in PostgreSQL
final OperationResult<dc.CreateBusinessData, dc.CreateBusinessVariables> final OperationResult<dc.CreateBusinessData, dc.CreateBusinessVariables>
createBusinessResponse = await _service.run(() => _service.connector createBusinessResponse = await _service.run(
.createBusiness( () => _service.connector
businessName: companyName, .createBusiness(
userId: firebaseUser.uid, businessName: companyName,
rateGroup: dc.BusinessRateGroup.STANDARD, userId: firebaseUser.uid,
status: dc.BusinessStatus.PENDING, rateGroup: dc.BusinessRateGroup.STANDARD,
) status: dc.BusinessStatus.PENDING,
.execute()); )
.execute(),
);
final dc.CreateBusinessBusinessInsert businessData = final dc.CreateBusinessBusinessInsert businessData =
createBusinessResponse.data.business_insert; createBusinessResponse.data.business_insert;
@@ -273,28 +272,28 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
// Check if User entity already exists in PostgreSQL // Check if User entity already exists in PostgreSQL
final QueryResult<dc.GetUserByIdData, dc.GetUserByIdVariables> userResult = final QueryResult<dc.GetUserByIdData, dc.GetUserByIdVariables> userResult =
await _service.run(() => await _service.run(
_service.connector.getUserById(id: firebaseUser.uid).execute()); () => _service.connector.getUserById(id: firebaseUser.uid).execute(),
);
final dc.GetUserByIdUser? existingUser = userResult.data.user; final dc.GetUserByIdUser? existingUser = userResult.data.user;
if (existingUser != null) { if (existingUser != null) {
// User exists (likely in another app like STAFF). Update role to BOTH. // User exists (likely in another app like STAFF). Update role to BOTH.
await _service.run(() => _service.connector await _service.run(
.updateUser( () => _service.connector
id: firebaseUser.uid, .updateUser(id: firebaseUser.uid)
) .userRole('BOTH')
.userRole('BOTH') .execute(),
.execute()); );
} else { } else {
// Create new User entity in PostgreSQL // Create new User entity in PostgreSQL
await _service.run(() => _service.connector await _service.run(
.createUser( () => _service.connector
id: firebaseUser.uid, .createUser(id: firebaseUser.uid, role: dc.UserBaseRole.USER)
role: dc.UserBaseRole.USER, .email(email)
) .userRole('BUSINESS')
.email(email) .execute(),
.userRole('BUSINESS') );
.execute());
} }
return _getUserProfile( return _getUserProfile(
@@ -340,7 +339,8 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
@override @override
Future<domain.User> signInWithSocial({required String provider}) { Future<domain.User> signInWithSocial({required String provider}) {
throw UnimplementedError( throw UnimplementedError(
'Social authentication with $provider is not yet implemented.'); 'Social authentication with $provider is not yet implemented.',
);
} }
Future<domain.User> _getUserProfile({ Future<domain.User> _getUserProfile({
@@ -349,8 +349,9 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
bool requireBusinessRole = false, bool requireBusinessRole = false,
}) async { }) async {
final QueryResult<dc.GetUserByIdData, dc.GetUserByIdVariables> response = final QueryResult<dc.GetUserByIdData, dc.GetUserByIdVariables> response =
await _service.run(() => await _service.run(
_service.connector.getUserById(id: firebaseUserId).execute()); () => _service.connector.getUserById(id: firebaseUserId).execute(),
);
final dc.GetUserByIdUser? user = response.data.user; final dc.GetUserByIdUser? user = response.data.user;
if (user == null) { if (user == null) {
throw UserNotFoundException( throw UserNotFoundException(
@@ -383,22 +384,22 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
role: user.role.stringValue, role: user.role.stringValue,
); );
final QueryResult<dc.GetBusinessesByUserIdData, final QueryResult<
dc.GetBusinessesByUserIdVariables> businessResponse = dc.GetBusinessesByUserIdData,
await _service.run(() => _service.connector dc.GetBusinessesByUserIdVariables
.getBusinessesByUserId( >
userId: firebaseUserId, businessResponse = await _service.run(
) () => _service.connector
.execute()); .getBusinessesByUserId(userId: firebaseUserId)
.execute(),
);
final dc.GetBusinessesByUserIdBusinesses? business = final dc.GetBusinessesByUserIdBusinesses? business =
businessResponse.data.businesses.isNotEmpty businessResponse.data.businesses.isNotEmpty
? businessResponse.data.businesses.first ? businessResponse.data.businesses.first
: null; : null;
dc.ClientSessionStore.instance.setSession( dc.ClientSessionStore.instance.setSession(
dc.ClientSession( dc.ClientSession(
user: domainUser,
userPhotoUrl: user.photoUrl,
business: business == null business: business == null
? null ? null
: dc.ClientBusinessSession( : dc.ClientBusinessSession(

View File

@@ -19,26 +19,43 @@ class HomeRepositoryImpl implements HomeRepositoryInterface {
final DateTime now = DateTime.now(); final DateTime now = DateTime.now();
final int daysFromMonday = now.weekday - DateTime.monday; final int daysFromMonday = now.weekday - DateTime.monday;
final DateTime monday = final DateTime monday = DateTime(
DateTime(now.year, now.month, now.day).subtract(Duration(days: daysFromMonday)); now.year,
final DateTime weekRangeStart = DateTime(monday.year, monday.month, monday.day); now.month,
final DateTime weekRangeEnd = now.day,
DateTime(monday.year, monday.month, monday.day + 13, 23, 59, 59, 999); ).subtract(Duration(days: daysFromMonday));
final fdc.QueryResult<dc.GetCompletedShiftsByBusinessIdData, final DateTime weekRangeStart = DateTime(
dc.GetCompletedShiftsByBusinessIdVariables> completedResult = monday.year,
await _service.connector monday.month,
.getCompletedShiftsByBusinessId( monday.day,
businessId: businessId, );
dateFrom: _service.toTimestamp(weekRangeStart), final DateTime weekRangeEnd = DateTime(
dateTo: _service.toTimestamp(weekRangeEnd), monday.year,
) monday.month,
.execute(); monday.day + 13,
23,
59,
59,
999,
);
final fdc.QueryResult<
dc.GetCompletedShiftsByBusinessIdData,
dc.GetCompletedShiftsByBusinessIdVariables
>
completedResult = await _service.connector
.getCompletedShiftsByBusinessId(
businessId: businessId,
dateFrom: _service.toTimestamp(weekRangeStart),
dateTo: _service.toTimestamp(weekRangeEnd),
)
.execute();
double weeklySpending = 0.0; double weeklySpending = 0.0;
double next7DaysSpending = 0.0; double next7DaysSpending = 0.0;
int weeklyShifts = 0; int weeklyShifts = 0;
int next7DaysScheduled = 0; int next7DaysScheduled = 0;
for (final dc.GetCompletedShiftsByBusinessIdShifts shift in completedResult.data.shifts) { for (final dc.GetCompletedShiftsByBusinessIdShifts shift
in completedResult.data.shifts) {
final DateTime? shiftDate = shift.date?.toDateTime(); final DateTime? shiftDate = shift.date?.toDateTime();
if (shiftDate == null) { if (shiftDate == null) {
continue; continue;
@@ -58,17 +75,27 @@ class HomeRepositoryImpl implements HomeRepositoryInterface {
} }
final DateTime start = DateTime(now.year, now.month, now.day); final DateTime start = DateTime(now.year, now.month, now.day);
final DateTime end = DateTime(now.year, now.month, now.day, 23, 59, 59, 999); final DateTime end = DateTime(
now.year,
now.month,
now.day,
23,
59,
59,
999,
);
final fdc.QueryResult<dc.ListShiftRolesByBusinessAndDateRangeData, final fdc.QueryResult<
dc.ListShiftRolesByBusinessAndDateRangeVariables> result = dc.ListShiftRolesByBusinessAndDateRangeData,
await _service.connector dc.ListShiftRolesByBusinessAndDateRangeVariables
.listShiftRolesByBusinessAndDateRange( >
businessId: businessId, result = await _service.connector
start: _service.toTimestamp(start), .listShiftRolesByBusinessAndDateRange(
end: _service.toTimestamp(end), businessId: businessId,
) start: _service.toTimestamp(start),
.execute(); end: _service.toTimestamp(end),
)
.execute();
int totalNeeded = 0; int totalNeeded = 0;
int totalFilled = 0; int totalFilled = 0;
@@ -90,12 +117,47 @@ class HomeRepositoryImpl implements HomeRepositoryInterface {
} }
@override @override
UserSessionData getUserSessionData() { Future<UserSessionData> getUserSessionData() async {
final dc.ClientSession? session = dc.ClientSessionStore.instance.session; final dc.ClientSession? session = dc.ClientSessionStore.instance.session;
return UserSessionData( final dc.ClientBusinessSession? business = session?.business;
businessName: session?.business?.businessName ?? '',
photoUrl: null, // Business photo isn't currently in session // If session data is available, return it immediately
); if (business != null) {
return UserSessionData(
businessName: business.businessName,
photoUrl: business.companyLogoUrl,
);
}
return await _service.run(() async {
// If session is not initialized, attempt to fetch business data to populate session
final String businessId = await _service.getBusinessId();
final fdc.QueryResult<dc.GetBusinessByIdData, dc.GetBusinessByIdVariables>
businessResult = await _service.connector
.getBusinessById(id: businessId)
.execute();
if (businessResult.data.business == null) {
throw Exception('Business data not found for ID: $businessId');
}
final dc.ClientSession updatedSession = dc.ClientSession(
business: dc.ClientBusinessSession(
id: businessResult.data.business!.id,
businessName: businessResult.data.business?.businessName ?? '',
email: businessResult.data.business?.email ?? '',
city: businessResult.data.business?.city ?? '',
contactName: businessResult.data.business?.contactName ?? '',
companyLogoUrl: businessResult.data.business?.companyLogoUrl,
),
);
dc.ClientSessionStore.instance.setSession(updatedSession);
return UserSessionData(
businessName: businessResult.data.business!.businessName,
photoUrl: businessResult.data.business!.companyLogoUrl,
);
});
} }
@override @override
@@ -108,33 +170,34 @@ class HomeRepositoryImpl implements HomeRepositoryInterface {
final fdc.Timestamp startTimestamp = _service.toTimestamp(start); final fdc.Timestamp startTimestamp = _service.toTimestamp(start);
final fdc.Timestamp endTimestamp = _service.toTimestamp(now); final fdc.Timestamp endTimestamp = _service.toTimestamp(now);
final fdc.QueryResult<dc.ListShiftRolesByBusinessDateRangeCompletedOrdersData, final fdc.QueryResult<
dc.ListShiftRolesByBusinessDateRangeCompletedOrdersVariables> result = dc.ListShiftRolesByBusinessDateRangeCompletedOrdersData,
await _service.connector dc.ListShiftRolesByBusinessDateRangeCompletedOrdersVariables
.listShiftRolesByBusinessDateRangeCompletedOrders( >
businessId: businessId, result = await _service.connector
start: startTimestamp, .listShiftRolesByBusinessDateRangeCompletedOrders(
end: endTimestamp, businessId: businessId,
) start: startTimestamp,
.execute(); end: endTimestamp,
)
.execute();
return result.data.shiftRoles return result.data.shiftRoles.map((
.map(( dc.ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRoles shiftRole,
dc.ListShiftRolesByBusinessDateRangeCompletedOrdersShiftRoles shiftRole, ) {
) { final String location =
final String location = shiftRole.shift.location ?? shiftRole.shift.locationAddress ?? ''; shiftRole.shift.location ?? shiftRole.shift.locationAddress ?? '';
final String type = shiftRole.shift.order.orderType.stringValue; final String type = shiftRole.shift.order.orderType.stringValue;
return ReorderItem( return ReorderItem(
orderId: shiftRole.shift.order.id, orderId: shiftRole.shift.order.id,
title: '${shiftRole.role.name} - ${shiftRole.shift.title}', title: '${shiftRole.role.name} - ${shiftRole.shift.title}',
location: location, location: location,
hourlyRate: shiftRole.role.costPerHour, hourlyRate: shiftRole.role.costPerHour,
hours: shiftRole.hours ?? 0, hours: shiftRole.hours ?? 0,
workers: shiftRole.count, workers: shiftRole.count,
type: type, type: type,
); );
}) }).toList();
.toList();
}); });
} }
} }

View File

@@ -24,7 +24,7 @@ abstract interface class HomeRepositoryInterface {
Future<HomeDashboardData> getDashboardData(); Future<HomeDashboardData> getDashboardData();
/// Fetches the user's session data (business name and photo). /// Fetches the user's session data (business name and photo).
UserSessionData getUserSessionData(); Future<UserSessionData> getUserSessionData();
/// Fetches recently completed shift roles for reorder suggestions. /// Fetches recently completed shift roles for reorder suggestions.
Future<List<ReorderItem>> getRecentReorders(); Future<List<ReorderItem>> getRecentReorders();

View File

@@ -10,7 +10,7 @@ class GetUserSessionDataUseCase {
final HomeRepositoryInterface _repository; final HomeRepositoryInterface _repository;
/// Executes the use case to get session data. /// Executes the use case to get session data.
UserSessionData call() { Future<UserSessionData> call() {
return _repository.getUserSessionData(); return _repository.getUserSessionData();
} }
} }

View File

@@ -40,7 +40,7 @@ class ClientHomeBloc extends Bloc<ClientHomeEvent, ClientHomeState>
emit: emit, emit: emit,
action: () async { action: () async {
// Get session data // Get session data
final UserSessionData sessionData = _getUserSessionDataUseCase(); final UserSessionData sessionData = await _getUserSessionDataUseCase();
// Get dashboard data // Get dashboard data
final HomeDashboardData data = await _getDashboardDataUseCase(); final HomeDashboardData data = await _getDashboardDataUseCase();

View File

@@ -16,16 +16,16 @@ import '../../domain/repositories/hub_repository_interface.dart';
/// Implementation of [HubRepositoryInterface] backed by Data Connect. /// Implementation of [HubRepositoryInterface] backed by Data Connect.
class HubRepositoryImpl implements HubRepositoryInterface { class HubRepositoryImpl implements HubRepositoryInterface {
HubRepositoryImpl({ HubRepositoryImpl({required dc.DataConnectService service})
required dc.DataConnectService service, : _service = service;
}) : _service = service;
final dc.DataConnectService _service; final dc.DataConnectService _service;
@override @override
Future<List<domain.Hub>> getHubs() async { Future<List<domain.Hub>> getHubs() async {
return _service.run(() async { return _service.run(() async {
final dc.GetBusinessesByUserIdBusinesses business = await _getBusinessForCurrentUser(); final dc.GetBusinessesByUserIdBusinesses business =
await _getBusinessForCurrentUser();
final String teamId = await _getOrCreateTeamId(business); final String teamId = await _getOrCreateTeamId(business);
return _fetchHubsForTeam(teamId: teamId, businessId: business.id); return _fetchHubsForTeam(teamId: teamId, businessId: business.id);
}); });
@@ -45,10 +45,12 @@ class HubRepositoryImpl implements HubRepositoryInterface {
String? zipCode, String? zipCode,
}) async { }) async {
return _service.run(() async { return _service.run(() async {
final dc.GetBusinessesByUserIdBusinesses business = await _getBusinessForCurrentUser(); final dc.GetBusinessesByUserIdBusinesses business =
await _getBusinessForCurrentUser();
final String teamId = await _getOrCreateTeamId(business); final String teamId = await _getOrCreateTeamId(business);
final _PlaceAddress? placeAddress = final _PlaceAddress? placeAddress = placeId == null || placeId.isEmpty
placeId == null || placeId.isEmpty ? null : await _fetchPlaceAddress(placeId); ? null
: await _fetchPlaceAddress(placeId);
final String? cityValue = city ?? placeAddress?.city ?? business.city; final String? cityValue = city ?? placeAddress?.city ?? business.city;
final String? stateValue = state ?? placeAddress?.state; final String? stateValue = state ?? placeAddress?.state;
final String? streetValue = street ?? placeAddress?.street; final String? streetValue = street ?? placeAddress?.street;
@@ -56,21 +58,17 @@ class HubRepositoryImpl implements HubRepositoryInterface {
final String? zipCodeValue = zipCode ?? placeAddress?.zipCode; final String? zipCodeValue = zipCode ?? placeAddress?.zipCode;
final OperationResult<dc.CreateTeamHubData, dc.CreateTeamHubVariables> final OperationResult<dc.CreateTeamHubData, dc.CreateTeamHubVariables>
result = await _service.connector result = await _service.connector
.createTeamHub( .createTeamHub(teamId: teamId, hubName: name, address: address)
teamId: teamId, .placeId(placeId)
hubName: name, .latitude(latitude)
address: address, .longitude(longitude)
) .city(cityValue?.isNotEmpty == true ? cityValue : '')
.placeId(placeId) .state(stateValue)
.latitude(latitude) .street(streetValue)
.longitude(longitude) .country(countryValue)
.city(cityValue?.isNotEmpty == true ? cityValue : '') .zipCode(zipCodeValue)
.state(stateValue) .execute();
.street(streetValue)
.country(countryValue)
.zipCode(zipCodeValue)
.execute();
final String createdId = result.data.teamHub_insert.id; final String createdId = result.data.teamHub_insert.id;
final List<domain.Hub> hubs = await _fetchHubsForTeam( final List<domain.Hub> hubs = await _fetchHubsForTeam(
@@ -101,14 +99,13 @@ class HubRepositoryImpl implements HubRepositoryInterface {
return _service.run(() async { return _service.run(() async {
final String businessId = await _service.getBusinessId(); final String businessId = await _service.getBusinessId();
final QueryResult<dc.ListOrdersByBusinessAndTeamHubData, final QueryResult<
dc.ListOrdersByBusinessAndTeamHubVariables> result = dc.ListOrdersByBusinessAndTeamHubData,
await _service.connector dc.ListOrdersByBusinessAndTeamHubVariables
.listOrdersByBusinessAndTeamHub( >
businessId: businessId, result = await _service.connector
teamHubId: id, .listOrdersByBusinessAndTeamHub(businessId: businessId, teamHubId: id)
) .execute();
.execute();
if (result.data.orders.isNotEmpty) { if (result.data.orders.isNotEmpty) {
throw HubHasOrdersException( throw HubHasOrdersException(
@@ -121,14 +118,14 @@ class HubRepositoryImpl implements HubRepositoryInterface {
} }
@override @override
Future<void> assignNfcTag({ Future<void> assignNfcTag({required String hubId, required String nfcTagId}) {
required String hubId, throw UnimplementedError(
required String nfcTagId, 'NFC tag assignment is not supported for team hubs.',
}) { );
throw UnimplementedError('NFC tag assignment is not supported for team hubs.');
} }
Future<dc.GetBusinessesByUserIdBusinesses> _getBusinessForCurrentUser() async { Future<dc.GetBusinessesByUserIdBusinesses>
_getBusinessForCurrentUser() async {
final dc.ClientSession? session = dc.ClientSessionStore.instance.session; final dc.ClientSession? session = dc.ClientSessionStore.instance.session;
final dc.ClientBusinessSession? cachedBusiness = session?.business; final dc.ClientBusinessSession? cachedBusiness = session?.business;
if (cachedBusiness != null) { if (cachedBusiness != null) {
@@ -136,7 +133,9 @@ class HubRepositoryImpl implements HubRepositoryInterface {
id: cachedBusiness.id, id: cachedBusiness.id,
businessName: cachedBusiness.businessName, businessName: cachedBusiness.businessName,
userId: _service.auth.currentUser?.uid ?? '', userId: _service.auth.currentUser?.uid ?? '',
rateGroup: const dc.Known<dc.BusinessRateGroup>(dc.BusinessRateGroup.STANDARD), rateGroup: const dc.Known<dc.BusinessRateGroup>(
dc.BusinessRateGroup.STANDARD,
),
status: const dc.Known<dc.BusinessStatus>(dc.BusinessStatus.ACTIVE), status: const dc.Known<dc.BusinessStatus>(dc.BusinessStatus.ACTIVE),
contactName: cachedBusiness.contactName, contactName: cachedBusiness.contactName,
companyLogoUrl: cachedBusiness.companyLogoUrl, companyLogoUrl: cachedBusiness.companyLogoUrl,
@@ -160,11 +159,13 @@ class HubRepositoryImpl implements HubRepositoryInterface {
); );
} }
final QueryResult<dc.GetBusinessesByUserIdData, final QueryResult<
dc.GetBusinessesByUserIdVariables> result = dc.GetBusinessesByUserIdData,
await _service.connector.getBusinessesByUserId( dc.GetBusinessesByUserIdVariables
userId: user.uid, >
).execute(); result = await _service.connector
.getBusinessesByUserId(userId: user.uid)
.execute();
if (result.data.businesses.isEmpty) { if (result.data.businesses.isEmpty) {
await _service.auth.signOut(); await _service.auth.signOut();
throw BusinessNotFoundException( throw BusinessNotFoundException(
@@ -172,12 +173,11 @@ class HubRepositoryImpl implements HubRepositoryInterface {
); );
} }
final dc.GetBusinessesByUserIdBusinesses business = result.data.businesses.first; final dc.GetBusinessesByUserIdBusinesses business =
result.data.businesses.first;
if (session != null) { if (session != null) {
dc.ClientSessionStore.instance.setSession( dc.ClientSessionStore.instance.setSession(
dc.ClientSession( dc.ClientSession(
user: session.user,
userPhotoUrl: session.userPhotoUrl,
business: dc.ClientBusinessSession( business: dc.ClientBusinessSession(
id: business.id, id: business.id,
businessName: business.businessName, businessName: business.businessName,
@@ -197,26 +197,26 @@ class HubRepositoryImpl implements HubRepositoryInterface {
dc.GetBusinessesByUserIdBusinesses business, dc.GetBusinessesByUserIdBusinesses business,
) async { ) async {
final QueryResult<dc.GetTeamsByOwnerIdData, dc.GetTeamsByOwnerIdVariables> final QueryResult<dc.GetTeamsByOwnerIdData, dc.GetTeamsByOwnerIdVariables>
teamsResult = await _service.connector.getTeamsByOwnerId( teamsResult = await _service.connector
ownerId: business.id, .getTeamsByOwnerId(ownerId: business.id)
).execute(); .execute();
if (teamsResult.data.teams.isNotEmpty) { if (teamsResult.data.teams.isNotEmpty) {
return teamsResult.data.teams.first.id; return teamsResult.data.teams.first.id;
} }
final dc.CreateTeamVariablesBuilder createTeamBuilder = _service.connector.createTeam( final dc.CreateTeamVariablesBuilder createTeamBuilder = _service.connector
teamName: '${business.businessName} Team', .createTeam(
ownerId: business.id, teamName: '${business.businessName} Team',
ownerName: business.contactName ?? '', ownerId: business.id,
ownerRole: 'OWNER', ownerName: business.contactName ?? '',
); ownerRole: 'OWNER',
);
if (business.email != null) { if (business.email != null) {
createTeamBuilder.email(business.email); createTeamBuilder.email(business.email);
} }
final OperationResult<dc.CreateTeamData, dc.CreateTeamVariables> final OperationResult<dc.CreateTeamData, dc.CreateTeamVariables>
createTeamResult = createTeamResult = await createTeamBuilder.execute();
await createTeamBuilder.execute();
final String teamId = createTeamResult.data.team_insert.id; final String teamId = createTeamResult.data.team_insert.id;
return teamId; return teamId;
@@ -226,11 +226,13 @@ class HubRepositoryImpl implements HubRepositoryInterface {
required String teamId, required String teamId,
required String businessId, required String businessId,
}) async { }) async {
final QueryResult<dc.GetTeamHubsByTeamIdData, final QueryResult<
dc.GetTeamHubsByTeamIdVariables> hubsResult = dc.GetTeamHubsByTeamIdData,
await _service.connector.getTeamHubsByTeamId( dc.GetTeamHubsByTeamIdVariables
teamId: teamId, >
).execute(); hubsResult = await _service.connector
.getTeamHubsByTeamId(teamId: teamId)
.execute();
return hubsResult.data.teamHubs return hubsResult.data.teamHubs
.map( .map(
@@ -240,10 +242,9 @@ class HubRepositoryImpl implements HubRepositoryInterface {
name: hub.hubName, name: hub.hubName,
address: hub.address, address: hub.address,
nfcTagId: null, nfcTagId: null,
status: status: hub.isActive
hub.isActive ? domain.HubStatus.active
? domain.HubStatus.active : domain.HubStatus.inactive,
: domain.HubStatus.inactive,
), ),
) )
.toList(); .toList();
@@ -288,7 +289,8 @@ class HubRepositoryImpl implements HubRepositoryInterface {
for (final dynamic entry in components) { for (final dynamic entry in components) {
final Map<String, dynamic> component = entry as Map<String, dynamic>; final Map<String, dynamic> component = entry as Map<String, dynamic>;
final List<dynamic> types = component['types'] as List<dynamic>? ?? <dynamic>[]; final List<dynamic> types =
component['types'] as List<dynamic>? ?? <dynamic>[];
final String? longName = component['long_name'] as String?; final String? longName = component['long_name'] as String?;
final String? shortName = component['short_name'] as String?; final String? shortName = component['short_name'] as String?;

View File

@@ -17,8 +17,8 @@ class SettingsProfileHeader extends StatelessWidget {
final dc.ClientSession? session = dc.ClientSessionStore.instance.session; final dc.ClientSession? session = dc.ClientSessionStore.instance.session;
final String businessName = final String businessName =
session?.business?.businessName ?? 'Your Company'; session?.business?.businessName ?? 'Your Company';
final String email = session?.user.email ?? 'client@example.com'; final String email = session?.business?.email ?? 'client@example.com';
final String? photoUrl = session?.userPhotoUrl; final String? photoUrl = session?.business?.companyLogoUrl;
final String avatarLetter = businessName.trim().isNotEmpty final String avatarLetter = businessName.trim().isNotEmpty
? businessName.trim()[0].toUpperCase() ? businessName.trim()[0].toUpperCase()
: 'C'; : 'C';