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 {
|
List<SingleChildWidget> get authProvider {
|
||||||
return [
|
return [
|
||||||
ChangeNotifierProvider(
|
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
|
lazy: false, // Load immediately for auth state checks
|
||||||
),
|
),
|
||||||
|
|
||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
create: (context) => FinalCompletionOfDocumentsViewModel(),
|
create: (context) => FinalCompletionOfDocumentsViewModel(),
|
||||||
lazy: false, // Load immediately for auth state checks
|
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/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/login_response/login_response_model.dart';
|
||||||
import 'package:tm_app/data/services/model/verify_otp_response/verify_otp_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 {
|
Future<Result<bool>> checkIsLoggedIn() async {
|
||||||
return _authService.checkIsLoggedIn();
|
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:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:tm_app/core/utils/logger.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/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/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/login_response/login_response_model.dart';
|
||||||
import 'package:tm_app/data/services/model/logout_response/logout_response.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;
|
final token = result.value.data.accessToken;
|
||||||
AppLogger().info('🔑 Bearer token: $token');
|
AppLogger().info('🔑 Bearer token: $token');
|
||||||
final refreshToken = result.value.data.refreshToken;
|
final refreshToken = result.value.data.refreshToken;
|
||||||
|
final customer = result.value.data.customer;
|
||||||
|
|
||||||
await prefs.setString('bearer', token!);
|
await prefs.setString('bearer', token!);
|
||||||
await prefs.setString('refresh_token', refreshToken!);
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -68,6 +77,12 @@ class AuthService {
|
|||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
(json) => LogoutResponse.fromJson(json),
|
(json) => LogoutResponse.fromJson(json),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Clear local data after successful logout
|
||||||
|
if (result is Ok<LogoutResponse>) {
|
||||||
|
await localLogout();
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +104,8 @@ class AuthService {
|
|||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
await prefs.remove('bearer');
|
await prefs.remove('bearer');
|
||||||
await prefs.remove('refresh_token');
|
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({
|
Future<Result<ForgotPasswordResponseModel>> forgotPassword({
|
||||||
@@ -131,4 +147,19 @@ class AuthService {
|
|||||||
}
|
}
|
||||||
return const Result.ok(true);
|
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();
|
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() {
|
void _onTextChanged() {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
@@ -97,11 +110,22 @@ class AuthViewModel with ChangeNotifier {
|
|||||||
if (result is Ok<bool>) {
|
if (result is Ok<bool>) {
|
||||||
if (result.value) {
|
if (result.value) {
|
||||||
_isLoggedIn = 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 {
|
} else {
|
||||||
_isLoggedIn = false;
|
_isLoggedIn = false;
|
||||||
|
_loggedInUser = null;
|
||||||
|
_userRole = null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_isLoggedIn = false;
|
_isLoggedIn = false;
|
||||||
|
_loggedInUser = null;
|
||||||
|
_userRole = null;
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
@@ -113,6 +137,7 @@ class AuthViewModel with ChangeNotifier {
|
|||||||
passwordController.clear();
|
passwordController.clear();
|
||||||
_isLoggedIn = false;
|
_isLoggedIn = false;
|
||||||
_loggedInUser = null;
|
_loggedInUser = null;
|
||||||
|
_userRole = null;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class _MeetingTimeDialogState extends State<MeetingTimeDialog> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final userRole = context.read<AuthViewModel>().userRole;
|
final userRole = context.watch<AuthViewModel>().userRole;
|
||||||
return Dialog(
|
return Dialog(
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class TenderDetailActions extends StatefulWidget {
|
|||||||
class _TenderDetailActionsState extends State<TenderDetailActions> {
|
class _TenderDetailActionsState extends State<TenderDetailActions> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final userRole = context.read<AuthViewModel>().userRole;
|
final userRole = context.watch<AuthViewModel>().userRole;
|
||||||
return widget.isScreenBig
|
return widget.isScreenBig
|
||||||
? _webActions(userRole!)
|
? _webActions(userRole!)
|
||||||
: Column(
|
: Column(
|
||||||
|
|||||||
+4
-1
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:tm_app/core/theme/colors.dart';
|
import 'package:tm_app/core/theme/colors.dart';
|
||||||
import 'package:tm_app/core/utils/size_config.dart';
|
import 'package:tm_app/core/utils/size_config.dart';
|
||||||
@@ -50,7 +51,9 @@ class DesktopFinalCompletionOfDocumentsPage extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
BaseButton(
|
BaseButton(
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
onPressed: () {},
|
onPressed: () {
|
||||||
|
context.pop();
|
||||||
|
},
|
||||||
width: 176,
|
width: 176,
|
||||||
backgroundColor: AppColors.primary10,
|
backgroundColor: AppColors.primary10,
|
||||||
textColor: AppColors.mainBlue,
|
textColor: AppColors.mainBlue,
|
||||||
|
|||||||
+4
-1
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:tm_app/core/theme/colors.dart';
|
import 'package:tm_app/core/theme/colors.dart';
|
||||||
import 'package:tm_app/core/utils/size_config.dart';
|
import 'package:tm_app/core/utils/size_config.dart';
|
||||||
@@ -61,7 +62,9 @@ class TabletFinalCompletionOfDocumentsPage extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
BaseButton(
|
BaseButton(
|
||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
onPressed: () {},
|
onPressed: () {
|
||||||
|
context.pop();
|
||||||
|
},
|
||||||
width: 176.0.w(),
|
width: 176.0.w(),
|
||||||
backgroundColor: AppColors.primary10,
|
backgroundColor: AppColors.primary10,
|
||||||
textColor: AppColors.mainBlue,
|
textColor: AppColors.mainBlue,
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class _DesktopTendersPageState extends State<DesktopTendersPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final userRole = context.read<AuthViewModel>().userRole;
|
final userRole = context.watch<AuthViewModel>().userRole;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: AppColors.backgroundColor,
|
backgroundColor: AppColors.backgroundColor,
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
|
|||||||
Reference in New Issue
Block a user