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.
This commit is contained in:
@@ -157,11 +157,16 @@ List<SingleChildWidget> get services {
|
||||
List<SingleChildWidget> 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
|
||||
),
|
||||
|
||||
@@ -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<Result<bool>> checkIsLoggedIn() async {
|
||||
return _authService.checkIsLoggedIn();
|
||||
}
|
||||
|
||||
Future<Customer?> getStoredCustomer() async {
|
||||
return _authService.getStoredCustomer();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<LogoutResponse>) {
|
||||
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<Result<ForgotPasswordResponseModel>> forgotPassword({
|
||||
@@ -131,4 +147,19 @@ class AuthService {
|
||||
}
|
||||
return const Result.ok(true);
|
||||
}
|
||||
|
||||
Future<Customer?> 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<String, dynamic>;
|
||||
return Customer.fromJson(customerMap);
|
||||
} catch (e) {
|
||||
AppLogger().error('❌ Failed to parse stored customer data: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,19 @@ class AuthViewModel with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> 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<bool>) {
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ class _MeetingTimeDialogState extends State<MeetingTimeDialog> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final userRole = context.read<AuthViewModel>().userRole;
|
||||
final userRole = context.watch<AuthViewModel>().userRole;
|
||||
return Dialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
|
||||
@@ -35,7 +35,7 @@ class TenderDetailActions extends StatefulWidget {
|
||||
class _TenderDetailActionsState extends State<TenderDetailActions> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final userRole = context.read<AuthViewModel>().userRole;
|
||||
final userRole = context.watch<AuthViewModel>().userRole;
|
||||
return widget.isScreenBig
|
||||
? _webActions(userRole!)
|
||||
: Column(
|
||||
|
||||
+4
-1
@@ -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,
|
||||
|
||||
+4
-1
@@ -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,
|
||||
|
||||
@@ -50,7 +50,7 @@ class _DesktopTendersPageState extends State<DesktopTendersPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final userRole = context.read<AuthViewModel>().userRole;
|
||||
final userRole = context.watch<AuthViewModel>().userRole;
|
||||
return Scaffold(
|
||||
backgroundColor: AppColors.backgroundColor,
|
||||
body: SafeArea(
|
||||
|
||||
Reference in New Issue
Block a user