diff --git a/apps/mobile/apps/client/lib/src/widgets/session_listener.dart b/apps/mobile/apps/client/lib/src/widgets/session_listener.dart index e6b26b37..968d1a3f 100644 --- a/apps/mobile/apps/client/lib/src/widgets/session_listener.dart +++ b/apps/mobile/apps/client/lib/src/widgets/session_listener.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_modular/flutter_modular.dart'; import 'package:krow_core/core.dart'; +import 'package:krow_domain/krow_domain.dart' show UserRole; /// A widget that listens to session state changes and handles global reactions. /// @@ -37,7 +38,7 @@ class _SessionListenerState extends State { final V2SessionService sessionService = Modular.get(); sessionService.initializeAuthListener( - allowedRoles: const ['CLIENT', 'BUSINESS', 'BOTH'], + allowedRoles: const [UserRole.business, UserRole.both], ); _sessionSubscription = sessionService.onSessionStateChanged diff --git a/apps/mobile/apps/staff/lib/src/widgets/session_listener.dart b/apps/mobile/apps/staff/lib/src/widgets/session_listener.dart index fd064c12..a07aa31f 100644 --- a/apps/mobile/apps/staff/lib/src/widgets/session_listener.dart +++ b/apps/mobile/apps/staff/lib/src/widgets/session_listener.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_modular/flutter_modular.dart'; import 'package:krow_core/core.dart'; +import 'package:krow_domain/krow_domain.dart' show UserRole; /// A widget that listens to session state changes and handles global reactions. /// @@ -37,7 +38,7 @@ class _SessionListenerState extends State { final V2SessionService sessionService = Modular.get(); sessionService.initializeAuthListener( - allowedRoles: const ['STAFF', 'BOTH'], + allowedRoles: const [UserRole.staff, UserRole.both], ); _sessionSubscription = sessionService.onSessionStateChanged diff --git a/apps/mobile/packages/core/lib/src/services/api_service/mixins/session_handler_mixin.dart b/apps/mobile/packages/core/lib/src/services/api_service/mixins/session_handler_mixin.dart index afb14c0a..b8458ca8 100644 --- a/apps/mobile/packages/core/lib/src/services/api_service/mixins/session_handler_mixin.dart +++ b/apps/mobile/packages/core/lib/src/services/api_service/mixins/session_handler_mixin.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:firebase_auth/firebase_auth.dart' as firebase_auth; import 'package:flutter/cupertino.dart'; +import 'package:krow_domain/krow_domain.dart' show UserRole; /// Enum representing the current session state. enum SessionStateType { loading, authenticated, unauthenticated, error } @@ -85,11 +86,11 @@ mixin SessionHandlerMixin { firebase_auth.FirebaseAuth get auth; /// List of allowed roles for this app (set during initialization). - List _allowedRoles = []; + List _allowedRoles = []; /// Initialize the auth state listener (call once on app startup). void initializeAuthListener({ - List allowedRoles = const [], + List allowedRoles = const [], }) { _allowedRoles = allowedRoles; @@ -112,10 +113,10 @@ mixin SessionHandlerMixin { /// Validates if user has one of the allowed roles. Future validateUserRole( String userId, - List allowedRoles, + List allowedRoles, ) async { try { - final String? userRole = await fetchUserRole(userId); + final UserRole? userRole = await fetchUserRole(userId); return userRole != null && allowedRoles.contains(userRole); } catch (e) { debugPrint('Failed to validate user role: $e'); @@ -123,11 +124,9 @@ mixin SessionHandlerMixin { } } - /// Fetches user role from the backend. - /// - /// Implementors should call `GET /auth/session` via [ApiService] and - /// extract the role from the response. - Future fetchUserRole(String userId); + /// Fetches the user role from the backend by calling `GET /auth/session` + /// and deriving the [UserRole] from the response context. + Future fetchUserRole(String userId); /// Handle user sign-in event. Future _handleSignIn(firebase_auth.User user) async { @@ -135,7 +134,7 @@ mixin SessionHandlerMixin { _emitSessionState(SessionState.loading()); if (_allowedRoles.isNotEmpty) { - final String? userRole = await fetchUserRole(user.uid); + final UserRole? userRole = await fetchUserRole(user.uid); if (userRole == null) { _emitSessionState(SessionState.unauthenticated()); diff --git a/apps/mobile/packages/core/lib/src/services/session/v2_session_service.dart b/apps/mobile/packages/core/lib/src/services/session/v2_session_service.dart index 35b8879a..0e55469a 100644 --- a/apps/mobile/packages/core/lib/src/services/session/v2_session_service.dart +++ b/apps/mobile/packages/core/lib/src/services/session/v2_session_service.dart @@ -37,10 +37,10 @@ class V2SessionService with SessionHandlerMixin { /// Fetches the user role by calling `GET /auth/session`. /// - /// Returns the role string (e.g. `STAFF`, `BUSINESS`, `BOTH`) or `null` if + /// Returns the [UserRole] derived from the session context, or `null` if /// the call fails or the user has no role. @override - Future fetchUserRole(String userId) async { + Future fetchUserRole(String userId) async { try { final BaseApiService? api = _apiService; if (api == null) { @@ -61,16 +61,7 @@ class V2SessionService with SessionHandlerMixin { // Per V2 auth doc, GET /auth/session is used for app startup hydration. _hydrateSessionStores(data); - // Derive role from the presence of staff/business context. - // The session endpoint returns { user, tenant, business, vendor, staff } - // — there is no explicit "role" field. - final bool hasStaff = data['staff'] is Map; - final bool hasBusiness = data['business'] is Map; - - if (hasStaff && hasBusiness) return 'BOTH'; - if (hasStaff) return 'STAFF'; - if (hasBusiness) return 'BUSINESS'; - return null; + return UserRole.fromSessionData(data); } return null; } catch (e) { diff --git a/apps/mobile/packages/domain/lib/krow_domain.dart b/apps/mobile/packages/domain/lib/krow_domain.dart index 2470cacf..efe8328e 100644 --- a/apps/mobile/packages/domain/lib/krow_domain.dart +++ b/apps/mobile/packages/domain/lib/krow_domain.dart @@ -20,6 +20,7 @@ export 'src/entities/enums/order_type.dart'; export 'src/entities/enums/payment_status.dart'; export 'src/entities/enums/shift_status.dart'; export 'src/entities/enums/staff_status.dart'; +export 'src/entities/enums/user_role.dart'; // Core export 'src/core/services/api_services/api_response.dart'; diff --git a/apps/mobile/packages/domain/lib/src/entities/enums/user_role.dart b/apps/mobile/packages/domain/lib/src/entities/enums/user_role.dart new file mode 100644 index 00000000..e81b6b26 --- /dev/null +++ b/apps/mobile/packages/domain/lib/src/entities/enums/user_role.dart @@ -0,0 +1,27 @@ +/// The derived role of an authenticated user based on their session context. +/// +/// Derived from the presence of `staff` and `business` keys in the +/// `GET /auth/session` response — the API does not return an explicit role. +enum UserRole { + /// User has a staff profile only. + staff, + + /// User has a business membership only. + business, + + /// User has both staff and business context. + both; + + /// Derives the role from a session response map. + /// + /// Returns `null` if neither `staff` nor `business` context is present. + static UserRole? fromSessionData(Map data) { + final bool hasStaff = data['staff'] is Map; + final bool hasBusiness = data['business'] is Map; + + if (hasStaff && hasBusiness) return UserRole.both; + if (hasStaff) return UserRole.staff; + if (hasBusiness) return UserRole.business; + return null; + } +}