From b557b5874d77f944c3344251be347872e36d50e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Salazar?= <73718835+joshrs23@users.noreply.github.com> Date: Thu, 22 Jan 2026 15:55:51 -0500 Subject: [PATCH 1/7] validation when the user is not a businnes user --- .../auth_repository_impl.dart | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/apps/mobile/packages/features/client/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart b/apps/mobile/packages/features/client/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart index ede79873..3e207b2f 100644 --- a/apps/mobile/packages/features/client/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart +++ b/apps/mobile/packages/features/client/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart @@ -34,12 +34,29 @@ class AuthRepositoryImpl implements AuthRepositoryInterface { throw Exception('Sign-in failed, no Firebase user received.'); } - return _getUserProfile( - firebaseUserId: firebaseUser.uid, - fallbackEmail: firebaseUser.email ?? email, - ); + final response = await _dataConnect.getUserById( + id: firebaseUser.uid, + ).execute(); + final user = response.data?.user; + if (user == null) { + await _firebaseAuth.signOut(); + throw Exception('Authenticated user profile not found in database.'); + } + if (user.userRole != 'BUSINESS') { + await _firebaseAuth.signOut(); + throw Exception('User is not authorized for this app.'); + } - //TO-DO: validate that user is business role and has business account + final resolvedEmail = user.email ?? firebaseUser.email ?? email; + if (resolvedEmail.isEmpty) { + throw Exception('User email is missing in profile data.'); + } + + return domain.User( + id: user.id, + email: resolvedEmail, + role: user.role.stringValue, + ); } on firebase.FirebaseAuthException catch (e) { if (e.code == 'invalid-credential' || e.code == 'wrong-password') { @@ -47,6 +64,8 @@ class AuthRepositoryImpl implements AuthRepositoryInterface { } else { throw Exception('Authentication error: ${e.message}'); } + } on Exception catch (e) { + throw e; } catch (e) { throw Exception('Failed to sign in and fetch user data: ${e.toString()}'); } From 6e8578c3d71d14fec35b41ee20755735ea48ef37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Salazar?= <73718835+joshrs23@users.noreply.github.com> Date: Thu, 22 Jan 2026 16:04:10 -0500 Subject: [PATCH 2/7] validation userrole --- .../auth_repository_impl.dart | 94 +++++++------------ 1 file changed, 35 insertions(+), 59 deletions(-) diff --git a/apps/mobile/packages/features/client/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart b/apps/mobile/packages/features/client/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart index 0c2ebf11..ede79873 100644 --- a/apps/mobile/packages/features/client/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart +++ b/apps/mobile/packages/features/client/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart @@ -1,5 +1,5 @@ -import 'package:firebase_auth/firebase_auth.dart'; -import 'package:krow_data_connect/krow_data_connect.dart'; +import 'package:firebase_auth/firebase_auth.dart' as firebase; +import 'package:krow_data_connect/krow_data_connect.dart' as dc; import 'package:krow_domain/krow_domain.dart' as domain; import '../../domain/repositories/auth_repository_interface.dart'; @@ -8,15 +8,15 @@ import '../../domain/repositories/auth_repository_interface.dart'; /// This implementation integrates with Firebase Authentication for user /// identity management and Krow's Data Connect SDK for storing user profile data. class AuthRepositoryImpl implements AuthRepositoryInterface { - final FirebaseAuth _firebaseAuth; - final ExampleConnector _dataConnect; + final firebase.FirebaseAuth _firebaseAuth; + final dc.ExampleConnector _dataConnect; /// Creates an [AuthRepositoryImpl] with the real dependencies. AuthRepositoryImpl({ - required FirebaseAuth firebaseAuth, - required ExampleConnector dataConnect, - }) : _firebaseAuth = firebaseAuth, - _dataConnect = dataConnect; + required firebase.FirebaseAuth firebaseAuth, + required dc.ExampleConnector dataConnect, + }) : _firebaseAuth = firebaseAuth, + _dataConnect = dataConnect; @override Future signInWithEmail({ @@ -34,38 +34,19 @@ class AuthRepositoryImpl implements AuthRepositoryInterface { throw Exception('Sign-in failed, no Firebase user received.'); } - final response = await _dataConnect.getUserById( - id: firebaseUser.uid, - ).execute(); - final user = response.data?.user; - if (user == null) { - await _firebaseAuth.signOut(); - throw Exception('Authenticated user profile not found in database.'); - } - if (user.userRole != 'BUSINESS') { - await _firebaseAuth.signOut(); - throw Exception('User is not authorized for this app.'); - } - - final resolvedEmail = user.email ?? firebaseUser.email ?? email; - if (resolvedEmail.isEmpty) { - throw Exception('User email is missing in profile data.'); - } - - return domain.User( - id: user.id, - email: resolvedEmail, - role: user.role.stringValue, + return _getUserProfile( + firebaseUserId: firebaseUser.uid, + fallbackEmail: firebaseUser.email ?? email, ); + //TO-DO: validate that user is business role and has business account + } on firebase.FirebaseAuthException catch (e) { if (e.code == 'invalid-credential' || e.code == 'wrong-password') { throw Exception('Incorrect email or password.'); } else { throw Exception('Authentication error: ${e.message}'); } - } on Exception catch (e) { - throw e; } catch (e) { throw Exception('Failed to sign in and fetch user data: ${e.toString()}'); } @@ -91,25 +72,23 @@ class AuthRepositoryImpl implements AuthRepositoryInterface { // Client-specific business logic: // 1. Create a `Business` entity. // 2. Create a `User` entity associated with the business. - final createBusinessResponse = await _dataConnect - .createBusiness( - businessName: companyName, - userId: firebaseUser.uid, - rateGroup: BusinessRateGroup.STANDARD, - status: BusinessStatus.PENDING, - ) - .execute(); + final createBusinessResponse = await _dataConnect.createBusiness( + businessName: companyName, + userId: firebaseUser.uid, + rateGroup: dc.BusinessRateGroup.STANDARD, + status: dc.BusinessStatus.PENDING, + ).execute(); final businessData = createBusinessResponse.data?.business_insert; if (businessData == null) { await firebaseUser.delete(); // Rollback if business creation fails - throw Exception( - 'Business creation failed after Firebase user registration.', - ); + throw Exception('Business creation failed after Firebase user registration.'); } - final createUserResponse = await _dataConnect - .createUser(id: firebaseUser.uid, role: UserBaseRole.USER) + final createUserResponse = await _dataConnect.createUser( + id: firebaseUser.uid, + role: dc.UserBaseRole.USER, + ) .email(email) .userRole('BUSINESS') .execute(); @@ -118,16 +97,15 @@ class AuthRepositoryImpl implements AuthRepositoryInterface { if (newUserData == null) { await firebaseUser.delete(); // Rollback if user profile creation fails // TO-DO: Also delete the created Business if this fails - throw Exception( - 'User profile creation failed after Firebase user registration.', - ); + throw Exception('User profile creation failed after Firebase user registration.'); } return _getUserProfile( firebaseUserId: firebaseUser.uid, fallbackEmail: firebaseUser.email ?? email, ); - } on FirebaseAuthException catch (e) { + + } on firebase.FirebaseAuthException catch (e) { if (e.code == 'weak-password') { throw Exception('The password provided is too weak.'); } else if (e.code == 'email-already-in-use') { @@ -136,9 +114,7 @@ class AuthRepositoryImpl implements AuthRepositoryInterface { throw Exception('Sign-up error: ${e.message}'); } } catch (e) { - throw Exception( - 'Failed to sign up and create user data: ${e.toString()}', - ); + throw Exception('Failed to sign up and create user data: ${e.toString()}'); } } @@ -153,18 +129,14 @@ class AuthRepositoryImpl implements AuthRepositoryInterface { @override Future signInWithSocial({required String provider}) { - throw UnimplementedError( - 'Social authentication with $provider is not yet implemented.', - ); + throw UnimplementedError('Social authentication with $provider is not yet implemented.'); } Future _getUserProfile({ required String firebaseUserId, required String? fallbackEmail, }) async { - final response = await _dataConnect - .getUserById(id: firebaseUserId) - .execute(); + final response = await _dataConnect.getUserById(id: firebaseUserId).execute(); final user = response.data?.user; if (user == null) { throw Exception('Authenticated user profile not found in database.'); @@ -175,6 +147,10 @@ class AuthRepositoryImpl implements AuthRepositoryInterface { throw Exception('User email is missing in profile data.'); } - return domain.User(id: user.id, email: email, role: user.role.stringValue); + return domain.User( + id: user.id, + email: email, + role: user.role.stringValue, + ); } } From 1b8bdf6bfeb428bc02f7bc784fa588720931c8ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Salazar?= <73718835+joshrs23@users.noreply.github.com> Date: Thu, 22 Jan 2026 17:09:13 -0500 Subject: [PATCH 3/7] first steps for auth in staff app --- .../auth_repository_impl.dart | 101 ++++++++++++++++-- .../lib/staff_authentication.dart | 6 +- 2 files changed, 95 insertions(+), 12 deletions(-) diff --git a/apps/mobile/packages/features/staff/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart b/apps/mobile/packages/features/staff/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart index d8935c46..00b33378 100644 --- a/apps/mobile/packages/features/staff/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart +++ b/apps/mobile/packages/features/staff/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart @@ -1,34 +1,113 @@ -import 'package:krow_data_connect/krow_data_connect.dart'; -import 'package:krow_domain/krow_domain.dart'; +import 'dart:async'; + +import 'package:firebase_auth/firebase_auth.dart' as firebase; +import 'package:krow_data_connect/krow_data_connect.dart' as dc; +import 'package:krow_domain/krow_domain.dart' as domain; import '../../domain/repositories/auth_repository_interface.dart'; /// Implementation of [AuthRepositoryInterface]. class AuthRepositoryImpl implements AuthRepositoryInterface { - final AuthRepositoryMock mock; + final firebase.FirebaseAuth _firebaseAuth; + final dc.ExampleConnector _dataConnect; - AuthRepositoryImpl({required this.mock}); + AuthRepositoryImpl({ + required firebase.FirebaseAuth firebaseAuth, + required dc.ExampleConnector dataConnect, + }) : _firebaseAuth = firebaseAuth, + _dataConnect = dataConnect; @override - Stream get currentUser => mock.currentUser; + Stream get currentUser => _firebaseAuth + .authStateChanges() + .map((firebaseUser) { + if (firebaseUser == null) { + return null; + } + + return domain.User( + id: firebaseUser.uid, + email: firebaseUser.email ?? '', + phone: firebaseUser.phoneNumber, + role: 'staff', + ); + }); /// Signs in with a phone number and returns a verification ID. @override - Future signInWithPhone({required String phoneNumber}) { - return mock.signInWithPhone(phoneNumber); + Future signInWithPhone({required String phoneNumber}) async { + final completer = Completer(); + + await _firebaseAuth.verifyPhoneNumber( + phoneNumber: phoneNumber, + verificationCompleted: (_) {}, + verificationFailed: (e) { + if (!completer.isCompleted) { + completer.completeError( + Exception(e.message ?? 'Phone verification failed.'), + ); + } + }, + codeSent: (verificationId, _) { + if (!completer.isCompleted) { + completer.complete(verificationId); + } + }, + codeAutoRetrievalTimeout: (verificationId) { + if (!completer.isCompleted) { + completer.complete(verificationId); + } + }, + ); + + return completer.future; } /// Signs out the current user. @override Future signOut() { - return mock.signOut(); + return _firebaseAuth.signOut(); } /// Verifies an OTP code and returns the authenticated user. @override - Future verifyOtp({ + Future verifyOtp({ required String verificationId, required String smsCode, - }) { - return mock.verifyOtp(verificationId, smsCode); + }) async { + final credential = firebase.PhoneAuthProvider.credential( + verificationId: verificationId, + smsCode: smsCode, + ); + final userCredential = await _firebaseAuth.signInWithCredential(credential); + final firebaseUser = userCredential.user; + if (firebaseUser == null) { + throw Exception('Phone verification failed, no Firebase user received.'); + } + + final response = await _dataConnect.getUserById( + id: firebaseUser.uid, + ).execute(); + final 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.'); + } + + final email = user.email ?? ''; + if (email.isEmpty) { + await _firebaseAuth.signOut(); + throw Exception('User email is missing in profile data.'); + } + + return domain.User( + id: user.id, + email: email, + phone: firebaseUser.phoneNumber, + role: user.role.stringValue, + ); } } diff --git a/apps/mobile/packages/features/staff/authentication/lib/staff_authentication.dart b/apps/mobile/packages/features/staff/authentication/lib/staff_authentication.dart index 2c272187..ab9bc99a 100644 --- a/apps/mobile/packages/features/staff/authentication/lib/staff_authentication.dart +++ b/apps/mobile/packages/features/staff/authentication/lib/staff_authentication.dart @@ -2,6 +2,7 @@ library staff_authentication; import 'package:flutter_modular/flutter_modular.dart'; import 'package:krow_data_connect/krow_data_connect.dart'; +import 'package:firebase_auth/firebase_auth.dart' as firebase; 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'; @@ -28,7 +29,10 @@ class StaffAuthenticationModule extends Module { void binds(Injector i) { // Repositories i.addLazySingleton( - () => AuthRepositoryImpl(mock: i.get()), + () => AuthRepositoryImpl( + firebaseAuth: firebase.FirebaseAuth.instance, + dataConnect: ExampleConnector.instance, + ), ); // UseCases From 9b1d4fce7864ae61872ab4e3dcd90709e62ce972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Salazar?= <73718835+joshrs23@users.noreply.github.com> Date: Thu, 22 Jan 2026 17:36:12 -0500 Subject: [PATCH 4/7] configuration for staff firebase and auth login --- .../apps/staff/android/app/build.gradle.kts | 1 + .../staff/android/app/google-services.json | 226 ++++++++++++++++++ .../apps/staff/android/settings.gradle.kts | 1 + apps/mobile/apps/staff/lib/main.dart | 3 +- .../auth_repository_impl.dart | 3 + 5 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 apps/mobile/apps/staff/android/app/google-services.json diff --git a/apps/mobile/apps/staff/android/app/build.gradle.kts b/apps/mobile/apps/staff/android/app/build.gradle.kts index 0f75ed01..80f2b222 100644 --- a/apps/mobile/apps/staff/android/app/build.gradle.kts +++ b/apps/mobile/apps/staff/android/app/build.gradle.kts @@ -3,6 +3,7 @@ plugins { id("kotlin-android") // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id("dev.flutter.flutter-gradle-plugin") + id("com.google.gms.google-services") } android { diff --git a/apps/mobile/apps/staff/android/app/google-services.json b/apps/mobile/apps/staff/android/app/google-services.json new file mode 100644 index 00000000..13b4592b --- /dev/null +++ b/apps/mobile/apps/staff/android/app/google-services.json @@ -0,0 +1,226 @@ +{ + "project_info": { + "project_number": "933560802882", + "project_id": "krow-workforce-dev", + "storage_bucket": "krow-workforce-dev.firebasestorage.app" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:933560802882:android:87d41566f8dda41d7757db", + "android_client_info": { + "package_name": "com.example.krow_workforce" + } + }, + "oauth_client": [ + { + "client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyDBYhflhK6DThKnS7RM-9raKdvyKzLUjY4" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "933560802882-dppsapp5i3lsfrlm1mhob2s21peofg1t.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.krow.app.staff.dev" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:933560802882:android:edcddb83ea4bbb517757db", + "android_client_info": { + "package_name": "com.krow.app.business.dev" + } + }, + "oauth_client": [ + { + "client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyDBYhflhK6DThKnS7RM-9raKdvyKzLUjY4" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "933560802882-dppsapp5i3lsfrlm1mhob2s21peofg1t.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.krow.app.staff.dev" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:933560802882:android:d49b8c0f4d19e95e7757db", + "android_client_info": { + "package_name": "com.krow.app.staff.dev" + } + }, + "oauth_client": [ + { + "client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyDBYhflhK6DThKnS7RM-9raKdvyKzLUjY4" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "933560802882-dppsapp5i3lsfrlm1mhob2s21peofg1t.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.krow.app.staff.dev" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:933560802882:android:da13569105659ead7757db", + "android_client_info": { + "package_name": "com.krowwithus.client" + } + }, + "oauth_client": [ + { + "client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyDBYhflhK6DThKnS7RM-9raKdvyKzLUjY4" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "933560802882-dppsapp5i3lsfrlm1mhob2s21peofg1t.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.krow.app.staff.dev" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:933560802882:android:d26bde4ee337b0b17757db", + "android_client_info": { + "package_name": "com.krowwithus.krow_workforce.dev" + } + }, + "oauth_client": [ + { + "client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyDBYhflhK6DThKnS7RM-9raKdvyKzLUjY4" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "933560802882-dppsapp5i3lsfrlm1mhob2s21peofg1t.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.krow.app.staff.dev" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:933560802882:android:1ae05d85c865f77c7757db", + "android_client_info": { + "package_name": "com.krowwithus.staff" + } + }, + "oauth_client": [ + { + "client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyDBYhflhK6DThKnS7RM-9raKdvyKzLUjY4" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "933560802882-grp98a1v7amflnnup68vh01tj06eaem1.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "933560802882-dppsapp5i3lsfrlm1mhob2s21peofg1t.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.krow.app.staff.dev" + } + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/apps/mobile/apps/staff/android/settings.gradle.kts b/apps/mobile/apps/staff/android/settings.gradle.kts index ca7fe065..e4e86fb6 100644 --- a/apps/mobile/apps/staff/android/settings.gradle.kts +++ b/apps/mobile/apps/staff/android/settings.gradle.kts @@ -21,6 +21,7 @@ plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("com.android.application") version "8.11.1" apply false id("org.jetbrains.kotlin.android") version "2.2.20" apply false + id("com.google.gms.google-services") version "4.4.2" apply false } include(":app") diff --git a/apps/mobile/apps/staff/lib/main.dart b/apps/mobile/apps/staff/lib/main.dart index cbfcaf74..63d82ff0 100644 --- a/apps/mobile/apps/staff/lib/main.dart +++ b/apps/mobile/apps/staff/lib/main.dart @@ -6,10 +6,11 @@ import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_modular/flutter_modular.dart'; import 'package:staff_authentication/staff_authentication.dart' as staff_authentication; +import 'package:firebase_core/firebase_core.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - + await Firebase.initializeApp(); runApp(ModularApp(module: AppModule(), child: const AppWidget())); } diff --git a/apps/mobile/packages/features/staff/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart b/apps/mobile/packages/features/staff/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart index 00b33378..50ccf277 100644 --- a/apps/mobile/packages/features/staff/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart +++ b/apps/mobile/packages/features/staff/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart @@ -103,6 +103,9 @@ class AuthRepositoryImpl implements AuthRepositoryInterface { throw Exception('User email is missing in profile data.'); } + //TO-DO: validate if user has staff account + //TO-DO: create(registration) user and staff account + //TO-DO: save user data locally return domain.User( id: user.id, email: email, From 9ddb0435edbcbfcc78743d88a0c299c8f2aa9802 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Salazar?= <73718835+joshrs23@users.noreply.github.com> Date: Thu, 22 Jan 2026 17:38:23 -0500 Subject: [PATCH 5/7] auth repo imp for staff --- .../lib/src/data/repositories_impl/auth_repository_impl.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/mobile/packages/features/staff/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart b/apps/mobile/packages/features/staff/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart index 50ccf277..a399956d 100644 --- a/apps/mobile/packages/features/staff/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart +++ b/apps/mobile/packages/features/staff/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart @@ -103,7 +103,7 @@ class AuthRepositoryImpl implements AuthRepositoryInterface { throw Exception('User email is missing in profile data.'); } - //TO-DO: validate if user has staff account + //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( From a70468ceb45e43d8c8c7f5c462f84d8f4163fbde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Salazar?= <73718835+joshrs23@users.noreply.github.com> Date: Thu, 22 Jan 2026 18:17:24 -0500 Subject: [PATCH 6/7] session creation and show business information --- .../data_connect/lib/krow_data_connect.dart | 1 + .../lib/src/session/client_session_store.dart | 49 +++++++++++++++++++ .../auth_repository_impl.dart | 29 ++++++++++- .../presentation/pages/client_home_page.dart | 30 +++++++++--- .../settings_profile_header.dart | 35 +++++++++---- 5 files changed, 127 insertions(+), 17 deletions(-) create mode 100644 apps/mobile/packages/data_connect/lib/src/session/client_session_store.dart diff --git a/apps/mobile/packages/data_connect/lib/krow_data_connect.dart b/apps/mobile/packages/data_connect/lib/krow_data_connect.dart index 445db229..a67d149b 100644 --- a/apps/mobile/packages/data_connect/lib/krow_data_connect.dart +++ b/apps/mobile/packages/data_connect/lib/krow_data_connect.dart @@ -18,6 +18,7 @@ export 'src/mocks/home_repository_mock.dart'; export 'src/mocks/business_repository_mock.dart'; export 'src/mocks/order_repository_mock.dart'; export 'src/data_connect_module.dart'; +export 'src/session/client_session_store.dart'; // Export the generated Data Connect SDK export 'src/dataconnect_generated/generated.dart'; diff --git a/apps/mobile/packages/data_connect/lib/src/session/client_session_store.dart b/apps/mobile/packages/data_connect/lib/src/session/client_session_store.dart new file mode 100644 index 00000000..e17f22a4 --- /dev/null +++ b/apps/mobile/packages/data_connect/lib/src/session/client_session_store.dart @@ -0,0 +1,49 @@ +import 'package:krow_domain/krow_domain.dart' as domain; + +class ClientBusinessSession { + final String id; + final String businessName; + final String? email; + final String? city; + final String? contactName; + final String? companyLogoUrl; + + const ClientBusinessSession({ + required this.id, + required this.businessName, + this.email, + this.city, + this.contactName, + this.companyLogoUrl, + }); +} + +class ClientSession { + final domain.User user; + final String? userPhotoUrl; + final ClientBusinessSession? business; + + const ClientSession({ + required this.user, + required this.userPhotoUrl, + required this.business, + }); +} + +class ClientSessionStore { + ClientSession? _session; + + ClientSession? get session => _session; + + void setSession(ClientSession session) { + _session = session; + } + + void clear() { + _session = null; + } + + static final ClientSessionStore instance = ClientSessionStore._(); + + ClientSessionStore._(); +} diff --git a/apps/mobile/packages/features/client/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart b/apps/mobile/packages/features/client/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart index ede79873..0756527f 100644 --- a/apps/mobile/packages/features/client/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart +++ b/apps/mobile/packages/features/client/authentication/lib/src/data/repositories_impl/auth_repository_impl.dart @@ -122,6 +122,7 @@ class AuthRepositoryImpl implements AuthRepositoryInterface { Future signOut() async { try { await _firebaseAuth.signOut(); + dc.ClientSessionStore.instance.clear(); } catch (e) { throw Exception('Error signing out: ${e.toString()}'); } @@ -147,10 +148,36 @@ class AuthRepositoryImpl implements AuthRepositoryInterface { throw Exception('User email is missing in profile data.'); } - return domain.User( + final domainUser = domain.User( id: user.id, email: email, role: user.role.stringValue, ); + + final businessResponse = await _dataConnect.getBusinessesByUserId( + userId: firebaseUserId, + ).execute(); + final business = businessResponse.data.businesses.isNotEmpty + ? businessResponse.data.businesses.first + : null; + + dc.ClientSessionStore.instance.setSession( + dc.ClientSession( + user: domainUser, + userPhotoUrl: user.photoUrl, + business: business == null + ? null + : dc.ClientBusinessSession( + id: business.id, + businessName: business.businessName, + email: business.email, + city: business.city, + contactName: business.contactName, + companyLogoUrl: business.companyLogoUrl, + ), + ), + ); + + return domainUser; } } diff --git a/apps/mobile/packages/features/client/home/lib/src/presentation/pages/client_home_page.dart b/apps/mobile/packages/features/client/home/lib/src/presentation/pages/client_home_page.dart index 6f3840df..32c698d8 100644 --- a/apps/mobile/packages/features/client/home/lib/src/presentation/pages/client_home_page.dart +++ b/apps/mobile/packages/features/client/home/lib/src/presentation/pages/client_home_page.dart @@ -3,6 +3,7 @@ import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_modular/flutter_modular.dart'; +import 'package:krow_data_connect/krow_data_connect.dart' as dc; import '../blocs/client_home_bloc.dart'; import '../blocs/client_home_event.dart'; @@ -116,6 +117,14 @@ class ClientHomePage extends StatelessWidget { Widget _buildHeader(BuildContext context, dynamic i18n) { return BlocBuilder( builder: (context, state) { + final session = dc.ClientSessionStore.instance.session; + final businessName = + session?.business?.businessName ?? 'Your Company'; + final photoUrl = session?.userPhotoUrl; + final avatarLetter = businessName.trim().isNotEmpty + ? businessName.trim()[0].toUpperCase() + : 'C'; + return Padding( padding: const EdgeInsets.fromLTRB( UiConstants.space4, @@ -140,12 +149,19 @@ class ClientHomePage extends StatelessWidget { ), child: CircleAvatar( backgroundColor: UiColors.primary.withValues(alpha: 0.1), - child: Text( - 'C', - style: UiTypography.body2b.copyWith( - color: UiColors.primary, - ), - ), + backgroundImage: + photoUrl != null && photoUrl.isNotEmpty + ? NetworkImage(photoUrl) + : null, + child: + photoUrl != null && photoUrl.isNotEmpty + ? null + : Text( + avatarLetter, + style: UiTypography.body2b.copyWith( + color: UiColors.primary, + ), + ), ), ), const SizedBox(width: UiConstants.space3), @@ -156,7 +172,7 @@ class ClientHomePage extends StatelessWidget { i18n.dashboard.welcome_back, style: UiTypography.footnote2r.textSecondary, ), - Text('Your Company', style: UiTypography.body1b), + Text(businessName, style: UiTypography.body1b), ], ), ], diff --git a/apps/mobile/packages/features/client/settings/lib/src/presentation/widgets/client_settings_page/settings_profile_header.dart b/apps/mobile/packages/features/client/settings/lib/src/presentation/widgets/client_settings_page/settings_profile_header.dart index 48f6a57f..9f795f35 100644 --- a/apps/mobile/packages/features/client/settings/lib/src/presentation/widgets/client_settings_page/settings_profile_header.dart +++ b/apps/mobile/packages/features/client/settings/lib/src/presentation/widgets/client_settings_page/settings_profile_header.dart @@ -2,6 +2,7 @@ import 'package:core_localization/core_localization.dart'; import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_modular/flutter_modular.dart'; +import 'package:krow_data_connect/krow_data_connect.dart' as dc; /// A widget that displays the profile header with avatar and company info. class SettingsProfileHeader extends StatelessWidget { @@ -12,6 +13,14 @@ class SettingsProfileHeader extends StatelessWidget { /// Builds the profile header UI. Widget build(BuildContext context) { final labels = t.client_settings.profile; + final session = dc.ClientSessionStore.instance.session; + final businessName = + session?.business?.businessName ?? 'Your Company'; + final email = session?.user.email ?? 'client@example.com'; + final photoUrl = session?.userPhotoUrl; + final avatarLetter = businessName.trim().isNotEmpty + ? businessName.trim()[0].toUpperCase() + : 'C'; return SliverAppBar( backgroundColor: UiColors.bgSecondary, @@ -40,20 +49,28 @@ class SettingsProfileHeader extends StatelessWidget { border: Border.all(color: UiColors.border, width: 2), color: UiColors.white, ), - child: Center( - child: Text( - 'C', - style: UiTypography.headline1m.copyWith( - color: UiColors.primary, - ), - ), + child: CircleAvatar( + backgroundColor: UiColors.primary.withValues(alpha: 0.1), + backgroundImage: + photoUrl != null && photoUrl.isNotEmpty + ? NetworkImage(photoUrl) + : null, + child: + photoUrl != null && photoUrl.isNotEmpty + ? null + : Text( + avatarLetter, + style: UiTypography.headline1m.copyWith( + color: UiColors.primary, + ), + ), ), ), Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - Text('Your Company', style: UiTypography.body1b.textPrimary), + Text(businessName, style: UiTypography.body1b.textPrimary), const SizedBox(height: UiConstants.space1), Row( mainAxisAlignment: MainAxisAlignment.start, @@ -65,7 +82,7 @@ class SettingsProfileHeader extends StatelessWidget { color: UiColors.textSecondary, ), Text( - 'client@example.com', + email, style: UiTypography.footnote1r.textSecondary, ), ], From 41175109ee56e6ef344384656023a971be8d4c3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Salazar?= <73718835+joshrs23@users.noreply.github.com> Date: Thu, 22 Jan 2026 18:21:44 -0500 Subject: [PATCH 7/7] modification hubs to dont call businees again and use session --- .../hub_repository_impl.dart | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/apps/mobile/packages/features/client/hubs/lib/src/data/repositories_impl/hub_repository_impl.dart b/apps/mobile/packages/features/client/hubs/lib/src/data/repositories_impl/hub_repository_impl.dart index 1fee40f5..3de1e29a 100644 --- a/apps/mobile/packages/features/client/hubs/lib/src/data/repositories_impl/hub_repository_impl.dart +++ b/apps/mobile/packages/features/client/hubs/lib/src/data/repositories_impl/hub_repository_impl.dart @@ -79,6 +79,30 @@ class HubRepositoryImpl implements HubRepositoryInterface { } Future _getBusinessForCurrentUser() async { + final session = dc.ClientSessionStore.instance.session; + final cachedBusiness = session?.business; + if (cachedBusiness != null) { + return dc.GetBusinessesByUserIdBusinesses( + id: cachedBusiness.id, + businessName: cachedBusiness.businessName, + userId: _firebaseAuth.currentUser?.uid ?? '', + rateGroup: dc.Known(dc.BusinessRateGroup.STANDARD), + status: dc.Known(dc.BusinessStatus.ACTIVE), + contactName: cachedBusiness.contactName, + companyLogoUrl: cachedBusiness.companyLogoUrl, + phone: null, + email: cachedBusiness.email, + hubBuilding: null, + address: null, + city: cachedBusiness.city, + area: null, + sector: null, + notes: null, + createdAt: null, + updatedAt: null, + ); + } + final user = _firebaseAuth.currentUser; if (user == null) { throw Exception('User is not authenticated.'); @@ -92,7 +116,25 @@ class HubRepositoryImpl implements HubRepositoryInterface { throw Exception('No business found for this user. Please sign in again.'); } - return result.data.businesses.first; + final business = result.data.businesses.first; + if (session != null) { + dc.ClientSessionStore.instance.setSession( + dc.ClientSession( + user: session.user, + userPhotoUrl: session.userPhotoUrl, + business: dc.ClientBusinessSession( + id: business.id, + businessName: business.businessName, + email: business.email, + city: business.city, + contactName: business.contactName, + companyLogoUrl: business.companyLogoUrl, + ), + ), + ); + } + + return business; } Future _getOrCreateTeamId(