feat: Implement role-based session management and refactor authentication flow
This commit is contained in:
@@ -257,77 +257,4 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
);
|
||||
return domainUser;
|
||||
}
|
||||
@override
|
||||
Future<domain.User?> restoreSession() async {
|
||||
final User? firebaseUser = _service.auth.currentUser;
|
||||
if (firebaseUser == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Fetch User
|
||||
final QueryResult<GetUserByIdData, GetUserByIdVariables> response =
|
||||
await _service.run(() => _service.connector
|
||||
.getUserById(
|
||||
id: firebaseUser.uid,
|
||||
)
|
||||
.execute());
|
||||
final GetUserByIdUser? user = response.data.user;
|
||||
|
||||
if (user == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. Check Role
|
||||
if (user.userRole != 'STAFF' && user.userRole != 'BOTH') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. Fetch Staff Profile
|
||||
final QueryResult<GetStaffByUserIdData, GetStaffByUserIdVariables>
|
||||
staffResponse = await _service.run(() => _service.connector
|
||||
.getStaffByUserId(
|
||||
userId: firebaseUser.uid,
|
||||
)
|
||||
.execute());
|
||||
|
||||
if (staffResponse.data.staffs.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final GetStaffByUserIdStaffs staffRecord = staffResponse.data.staffs.first;
|
||||
|
||||
// 4. Populate Session
|
||||
final domain.User domainUser = domain.User(
|
||||
id: firebaseUser.uid,
|
||||
email: user.email ?? '',
|
||||
phone: firebaseUser.phoneNumber,
|
||||
role: user.role.stringValue,
|
||||
);
|
||||
|
||||
final domain.Staff domainStaff = domain.Staff(
|
||||
id: staffRecord.id,
|
||||
authProviderId: staffRecord.userId,
|
||||
name: staffRecord.fullName,
|
||||
email: staffRecord.email ?? '',
|
||||
phone: staffRecord.phone,
|
||||
status: domain.StaffStatus.completedProfile,
|
||||
address: staffRecord.addres,
|
||||
avatar: staffRecord.photoUrl,
|
||||
);
|
||||
|
||||
StaffSessionStore.instance.setSession(
|
||||
StaffSession(
|
||||
user: domainUser,
|
||||
staff: domainStaff,
|
||||
ownerId: staffRecord.ownerId,
|
||||
),
|
||||
);
|
||||
|
||||
return domainUser;
|
||||
} catch (e) {
|
||||
// If restoration fails (network, etc), we rethrow to let UI handle it.
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,4 @@ abstract interface class AuthRepositoryInterface {
|
||||
|
||||
/// Signs out the current user.
|
||||
Future<void> signOut();
|
||||
|
||||
/// Restores the session if a user is already logged in.
|
||||
Future<User?> restoreSession();
|
||||
}
|
||||
|
||||
@@ -1,65 +1,14 @@
|
||||
import 'dart:async';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:staff_authentication/src/domain/repositories/auth_repository_interface.dart';
|
||||
|
||||
class IntroPage extends StatefulWidget {
|
||||
/// A simple introductory page that displays the KROW logo.
|
||||
class IntroPage extends StatelessWidget {
|
||||
const IntroPage({super.key});
|
||||
|
||||
@override
|
||||
State<IntroPage> createState() => _IntroPageState();
|
||||
}
|
||||
|
||||
class _IntroPageState extends State<IntroPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_checkSession();
|
||||
}
|
||||
|
||||
Future<void> _checkSession() async {
|
||||
// Check session immediately without artificial delay
|
||||
if (!mounted) return;
|
||||
|
||||
try {
|
||||
final AuthRepositoryInterface authRepo = Modular.get<AuthRepositoryInterface>();
|
||||
// Add a timeout to prevent infinite loading
|
||||
final user = await authRepo.restoreSession().timeout(
|
||||
const Duration(seconds: 5),
|
||||
onTimeout: () {
|
||||
// If it takes too long, navigate to Get Started.
|
||||
// This handles poor network conditions gracefully.
|
||||
throw TimeoutException('Session restore timed out');
|
||||
},
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
if (user != null) {
|
||||
Modular.to.navigate(StaffPaths.home);
|
||||
} else {
|
||||
Modular.to.navigate(StaffPaths.getStarted);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('IntroPage: Session check error: $e');
|
||||
if (mounted) {
|
||||
Modular.to.navigate(StaffPaths.getStarted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
body: Center(
|
||||
child: Image.asset(
|
||||
'assets/logo-yellow.png',
|
||||
package: 'design_system',
|
||||
width: 120,
|
||||
),
|
||||
),
|
||||
body: Center(child: Image.asset(UiImageAssets.logoYellow, width: 120)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,15 +58,14 @@ class _PhoneVerificationPageState extends State<PhoneVerificationPage> {
|
||||
}
|
||||
|
||||
if (normalized.length == 10) {
|
||||
BlocProvider.of<AuthBloc>(
|
||||
context,
|
||||
).add(
|
||||
BlocProvider.of<AuthBloc>(context).add(
|
||||
AuthSignInRequested(phoneNumber: '+1$normalized', mode: widget.mode),
|
||||
);
|
||||
} else {
|
||||
UiSnackbar.show(
|
||||
context,
|
||||
message: t.staff_authentication.phone_verification_page.validation_error,
|
||||
message:
|
||||
t.staff_authentication.phone_verification_page.validation_error,
|
||||
type: UiSnackbarType.error,
|
||||
margin: const EdgeInsets.only(bottom: 180, left: 16, right: 16),
|
||||
);
|
||||
@@ -79,9 +78,7 @@ class _PhoneVerificationPageState extends State<PhoneVerificationPage> {
|
||||
required String otp,
|
||||
required String verificationId,
|
||||
}) {
|
||||
BlocProvider.of<AuthBloc>(
|
||||
context,
|
||||
).add(
|
||||
BlocProvider.of<AuthBloc>(context).add(
|
||||
AuthOtpSubmitted(
|
||||
verificationId: verificationId,
|
||||
smsCode: otp,
|
||||
@@ -92,9 +89,9 @@ class _PhoneVerificationPageState extends State<PhoneVerificationPage> {
|
||||
|
||||
/// Handles the request to resend the verification code using the phone number in the state.
|
||||
void _onResend({required BuildContext context}) {
|
||||
BlocProvider.of<AuthBloc>(context).add(
|
||||
AuthSignInRequested(mode: widget.mode),
|
||||
);
|
||||
BlocProvider.of<AuthBloc>(
|
||||
context,
|
||||
).add(AuthSignInRequested(mode: widget.mode));
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -108,8 +105,6 @@ class _PhoneVerificationPageState extends State<PhoneVerificationPage> {
|
||||
if (state.status == AuthStatus.authenticated) {
|
||||
if (state.mode == AuthMode.signup) {
|
||||
Modular.to.toProfileSetup();
|
||||
} else {
|
||||
Modular.to.toStaffHome();
|
||||
}
|
||||
} else if (state.status == AuthStatus.error &&
|
||||
state.mode == AuthMode.signup) {
|
||||
@@ -120,7 +115,11 @@ class _PhoneVerificationPageState extends State<PhoneVerificationPage> {
|
||||
context,
|
||||
message: translateErrorKey(messageKey),
|
||||
type: UiSnackbarType.error,
|
||||
margin: const EdgeInsets.only(bottom: 180, left: 16, right: 16),
|
||||
margin: const EdgeInsets.only(
|
||||
bottom: 180,
|
||||
left: 16,
|
||||
right: 16,
|
||||
),
|
||||
);
|
||||
Future<void>.delayed(const Duration(seconds: 5), () {
|
||||
if (!mounted) return;
|
||||
@@ -153,9 +152,9 @@ class _PhoneVerificationPageState extends State<PhoneVerificationPage> {
|
||||
centerTitle: true,
|
||||
showBackButton: true,
|
||||
onLeadingPressed: () {
|
||||
BlocProvider.of<AuthBloc>(context).add(
|
||||
AuthResetRequested(mode: widget.mode),
|
||||
);
|
||||
BlocProvider.of<AuthBloc>(
|
||||
context,
|
||||
).add(AuthResetRequested(mode: widget.mode));
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
@@ -175,13 +174,13 @@ class _PhoneVerificationPageState extends State<PhoneVerificationPage> {
|
||||
verificationId: state.verificationId ?? '',
|
||||
),
|
||||
)
|
||||
: PhoneInput(
|
||||
state: state,
|
||||
onSendCode: (String phoneNumber) => _onSendCode(
|
||||
context: context,
|
||||
phoneNumber: phoneNumber,
|
||||
: PhoneInput(
|
||||
state: state,
|
||||
onSendCode: (String phoneNumber) => _onSendCode(
|
||||
context: context,
|
||||
phoneNumber: phoneNumber,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user