From 38222afcfaa5d7daa397d096cdc70870fd1f86b4 Mon Sep 17 00:00:00 2001 From: amirrezaghabeli Date: Tue, 14 Oct 2025 12:24:04 +0330 Subject: [PATCH] Enhance authentication flow and user role management - Updated AuthViewModel to hydrate user data from storage on initialization. - Added method to AuthRepository for retrieving stored customer data. - Enhanced AuthService to save and retrieve customer data from SharedPreferences. - Refactored UI components to use context.watch for user role updates in MeetingTimeDialog, TenderDetailActions, and TendersPage. - Improved navigation logic in final completion pages to handle user interactions more effectively. --- lib/core/config/dependencies.dart | 9 +++-- lib/data/repositories/auth_repository.dart | 5 +++ lib/data/services/auth_service.dart | 33 ++++++++++++++++++- lib/view_models/auth_view_model.dart | 25 ++++++++++++++ .../detail/widgets/meeting_time_dialog.dart | 2 +- .../detail/widgets/tender_detail_action.dart | 2 +- ...op_final_completion_of_documents_page.dart | 5 ++- ...et_final_completion_of_documents_page.dart | 5 ++- lib/views/tenders/pages/d_tenders_page.dart | 2 +- 9 files changed, 80 insertions(+), 8 deletions(-) diff --git a/lib/core/config/dependencies.dart b/lib/core/config/dependencies.dart index 01a1e53..3b9a416 100644 --- a/lib/core/config/dependencies.dart +++ b/lib/core/config/dependencies.dart @@ -157,11 +157,16 @@ List get services { List get authProvider { return [ ChangeNotifierProvider( - create: (context) => AuthViewModel(authRepository: context.read()), + create: (context) { + final vm = AuthViewModel(authRepository: context.read()); + // Hydrate stored user/customer so userRole is available on hot reloads + vm.hydrateFromStorage(); + return vm; + }, lazy: false, // Load immediately for auth state checks ), - ChangeNotifierProvider( + ChangeNotifierProvider( create: (context) => FinalCompletionOfDocumentsViewModel(), lazy: false, // Load immediately for auth state checks ), diff --git a/lib/data/repositories/auth_repository.dart b/lib/data/repositories/auth_repository.dart index 1325926..cc1fbc0 100644 --- a/lib/data/repositories/auth_repository.dart +++ b/lib/data/repositories/auth_repository.dart @@ -1,3 +1,4 @@ +import 'package:tm_app/data/services/model/customer/customer.dart'; import 'package:tm_app/data/services/model/forgot_password_response/forgot_password_response_model.dart'; import 'package:tm_app/data/services/model/login_response/login_response_model.dart'; import 'package:tm_app/data/services/model/verify_otp_response/verify_otp_response_model.dart'; @@ -51,4 +52,8 @@ class AuthRepository { Future> checkIsLoggedIn() async { return _authService.checkIsLoggedIn(); } + + Future getStoredCustomer() async { + return _authService.getStoredCustomer(); + } } diff --git a/lib/data/services/auth_service.dart b/lib/data/services/auth_service.dart index ae136f6..d40f3d5 100644 --- a/lib/data/services/auth_service.dart +++ b/lib/data/services/auth_service.dart @@ -6,6 +6,7 @@ import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:tm_app/core/utils/logger.dart'; import 'package:tm_app/data/services/api/auth_api.dart'; +import 'package:tm_app/data/services/model/customer/customer.dart'; import 'package:tm_app/data/services/model/forgot_password_response/forgot_password_response_model.dart'; import 'package:tm_app/data/services/model/login_response/login_response_model.dart'; import 'package:tm_app/data/services/model/logout_response/logout_response.dart'; @@ -55,9 +56,17 @@ class AuthService { final token = result.value.data.accessToken; AppLogger().info('🔑 Bearer token: $token'); final refreshToken = result.value.data.refreshToken; + final customer = result.value.data.customer; await prefs.setString('bearer', token!); await prefs.setString('refresh_token', refreshToken!); + + // Save customer data as JSON string + if (customer != null) { + final customerJson = jsonEncode(customer.toJson()); + await prefs.setString('customer_data', customerJson); + AppLogger().info('👤 Customer data saved to preferences'); + } } return result; } @@ -68,6 +77,12 @@ class AuthService { method: 'DELETE', (json) => LogoutResponse.fromJson(json), ); + + // Clear local data after successful logout + if (result is Ok) { + await localLogout(); + } + return result; } @@ -89,7 +104,8 @@ class AuthService { final prefs = await SharedPreferences.getInstance(); await prefs.remove('bearer'); await prefs.remove('refresh_token'); - appLogger.info('tokens cleared!'); + await prefs.remove('customer_data'); + appLogger.info('tokens and customer data cleared!'); } Future> forgotPassword({ @@ -131,4 +147,19 @@ class AuthService { } return const Result.ok(true); } + + Future getStoredCustomer() async { + final prefs = await SharedPreferences.getInstance(); + final customerJson = prefs.getString('customer_data'); + if (customerJson == null) { + return null; + } + try { + final customerMap = jsonDecode(customerJson) as Map; + return Customer.fromJson(customerMap); + } catch (e) { + AppLogger().error('❌ Failed to parse stored customer data: $e'); + return null; + } + } } diff --git a/lib/view_models/auth_view_model.dart b/lib/view_models/auth_view_model.dart index 37d3beb..3c65b84 100644 --- a/lib/view_models/auth_view_model.dart +++ b/lib/view_models/auth_view_model.dart @@ -46,6 +46,19 @@ class AuthViewModel with ChangeNotifier { notifyListeners(); } + Future hydrateFromStorage() async { + try { + final customer = await _authRepository.getStoredCustomer(); + if (customer != null) { + _loggedInUser = customer; + _userRole = customer.role == 'admin' ? Role.admin : Role.analyst; + notifyListeners(); + } + } catch (e) { + AppLogger().error('❌ Failed to hydrate auth state: $e'); + } + } + void _onTextChanged() { notifyListeners(); } @@ -97,11 +110,22 @@ class AuthViewModel with ChangeNotifier { if (result is Ok) { if (result.value) { _isLoggedIn = result.value; + + // Restore customer data from SharedPreferences + final customer = await _authRepository.getStoredCustomer(); + if (customer != null) { + _loggedInUser = customer; + _userRole = customer.role == 'admin' ? Role.admin : Role.analyst; + } } else { _isLoggedIn = false; + _loggedInUser = null; + _userRole = null; } } else { _isLoggedIn = false; + _loggedInUser = null; + _userRole = null; } notifyListeners(); } @@ -113,6 +137,7 @@ class AuthViewModel with ChangeNotifier { passwordController.clear(); _isLoggedIn = false; _loggedInUser = null; + _userRole = null; notifyListeners(); } diff --git a/lib/views/detail/widgets/meeting_time_dialog.dart b/lib/views/detail/widgets/meeting_time_dialog.dart index 7813fdc..3db5a6a 100644 --- a/lib/views/detail/widgets/meeting_time_dialog.dart +++ b/lib/views/detail/widgets/meeting_time_dialog.dart @@ -29,7 +29,7 @@ class _MeetingTimeDialogState extends State { @override Widget build(BuildContext context) { - final userRole = context.read().userRole; + final userRole = context.watch().userRole; return Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), diff --git a/lib/views/detail/widgets/tender_detail_action.dart b/lib/views/detail/widgets/tender_detail_action.dart index 37d38df..515c707 100644 --- a/lib/views/detail/widgets/tender_detail_action.dart +++ b/lib/views/detail/widgets/tender_detail_action.dart @@ -35,7 +35,7 @@ class TenderDetailActions extends StatefulWidget { class _TenderDetailActionsState extends State { @override Widget build(BuildContext context) { - final userRole = context.read().userRole; + final userRole = context.watch().userRole; return widget.isScreenBig ? _webActions(userRole!) : Column( diff --git a/lib/views/final_completion_of_documents/pages/desktop_final_completion_of_documents_page.dart b/lib/views/final_completion_of_documents/pages/desktop_final_completion_of_documents_page.dart index 2aa01b1..8b5326d 100644 --- a/lib/views/final_completion_of_documents/pages/desktop_final_completion_of_documents_page.dart +++ b/lib/views/final_completion_of_documents/pages/desktop_final_completion_of_documents_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/core/utils/size_config.dart'; @@ -50,7 +51,9 @@ class DesktopFinalCompletionOfDocumentsPage extends StatelessWidget { children: [ BaseButton( isEnabled: true, - onPressed: () {}, + onPressed: () { + context.pop(); + }, width: 176, backgroundColor: AppColors.primary10, textColor: AppColors.mainBlue, diff --git a/lib/views/final_completion_of_documents/pages/tablet_final_completion_of_documents_page.dart b/lib/views/final_completion_of_documents/pages/tablet_final_completion_of_documents_page.dart index fcfd181..e190521 100644 --- a/lib/views/final_completion_of_documents/pages/tablet_final_completion_of_documents_page.dart +++ b/lib/views/final_completion_of_documents/pages/tablet_final_completion_of_documents_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/core/utils/size_config.dart'; @@ -61,7 +62,9 @@ class TabletFinalCompletionOfDocumentsPage extends StatelessWidget { children: [ BaseButton( isEnabled: true, - onPressed: () {}, + onPressed: () { + context.pop(); + }, width: 176.0.w(), backgroundColor: AppColors.primary10, textColor: AppColors.mainBlue, diff --git a/lib/views/tenders/pages/d_tenders_page.dart b/lib/views/tenders/pages/d_tenders_page.dart index da0ea0d..7a7bb85 100644 --- a/lib/views/tenders/pages/d_tenders_page.dart +++ b/lib/views/tenders/pages/d_tenders_page.dart @@ -50,7 +50,7 @@ class _DesktopTendersPageState extends State { @override Widget build(BuildContext context) { - final userRole = context.read().userRole; + final userRole = context.watch().userRole; return Scaffold( backgroundColor: AppColors.backgroundColor, body: SafeArea(