feat: Implement staff authentication as a Flutter Modular feature module, centralizing dependency injection and routing while refactoring imports for better modularity.

This commit is contained in:
Achintha Isuru
2026-02-10 12:15:51 -05:00
parent aef0c3df14
commit a34cc5b462
19 changed files with 225 additions and 254 deletions

View File

@@ -1,33 +0,0 @@
# Feature Manifest: Staff Authentication
## Overview
**Feature Name:** Staff Authentication & Onboarding
**Package Path:** `packages/features/staff/authentication`
## Responsibilities
* Handle user sign-up and log-in via Phone Auth.
* Verify OTP codes.
* Manage the Onboarding Wizard for new staff.
* Persist onboarding progress.
## Architecture
* **Domain**:
* `AuthRepositoryInterface`
* `SignInWithPhoneUseCase`
* `VerifyOtpUseCase`
* **Data**:
* `AuthRepositoryImpl` (uses `AuthRepositoryMock` from `krow_data_connect`)
* **Presentation**:
* `AuthBloc`: Manages auth state (phone, otp, user status).
* `OnboardingBloc`: Manages wizard steps.
* Pages: `GetStartedPage`, `PhoneVerificationPage`, `ProfileSetupPage`.
## Dependencies
* `krow_domain`: User entities.
* `krow_data_connect`: Auth mocks.
* `design_system`: UI components.
## Routes
* `/`: Get Started (Welcome)
* `/phone-verification`: OTP Entry
* `/profile-setup`: Onboarding Wizard

View File

@@ -1,3 +1,4 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@@ -49,9 +50,7 @@ class _PhoneVerificationPageState extends State<PhoneVerificationPage> {
}) { }) {
final String normalized = phoneNumber.replaceAll(RegExp(r'\\D'), ''); final String normalized = phoneNumber.replaceAll(RegExp(r'\\D'), '');
if (normalized.length == 10) { if (normalized.length == 10) {
BlocProvider.of<AuthBloc>( BlocProvider.of<AuthBloc>(context).add(
context,
).add(
AuthSignInRequested(phoneNumber: '+1$normalized', mode: widget.mode), AuthSignInRequested(phoneNumber: '+1$normalized', mode: widget.mode),
); );
} else { } else {
@@ -71,9 +70,7 @@ class _PhoneVerificationPageState extends State<PhoneVerificationPage> {
required String otp, required String otp,
required String verificationId, required String verificationId,
}) { }) {
BlocProvider.of<AuthBloc>( BlocProvider.of<AuthBloc>(context).add(
context,
).add(
AuthOtpSubmitted( AuthOtpSubmitted(
verificationId: verificationId, verificationId: verificationId,
smsCode: otp, smsCode: otp,
@@ -84,9 +81,9 @@ class _PhoneVerificationPageState extends State<PhoneVerificationPage> {
/// Handles the request to resend the verification code using the phone number in the state. /// Handles the request to resend the verification code using the phone number in the state.
void _onResend({required BuildContext context}) { void _onResend({required BuildContext context}) {
BlocProvider.of<AuthBloc>(context).add( BlocProvider.of<AuthBloc>(
AuthSignInRequested(mode: widget.mode), context,
); ).add(AuthSignInRequested(mode: widget.mode));
} }
@override @override
@@ -107,8 +104,9 @@ class _PhoneVerificationPageState extends State<PhoneVerificationPage> {
state.mode == AuthMode.signup) { state.mode == AuthMode.signup) {
final String message = state.errorMessage ?? ''; final String message = state.errorMessage ?? '';
if (message.contains('staff profile')) { if (message.contains('staff profile')) {
final ScaffoldMessengerState messenger = final ScaffoldMessengerState messenger = ScaffoldMessenger.of(
ScaffoldMessenger.of(context); context,
);
messenger.hideCurrentSnackBar(); messenger.hideCurrentSnackBar();
messenger.showSnackBar( messenger.showSnackBar(
SnackBar( SnackBar(
@@ -147,9 +145,9 @@ class _PhoneVerificationPageState extends State<PhoneVerificationPage> {
centerTitle: true, centerTitle: true,
showBackButton: true, showBackButton: true,
onLeadingPressed: () { onLeadingPressed: () {
BlocProvider.of<AuthBloc>(context).add( BlocProvider.of<AuthBloc>(
AuthResetRequested(mode: widget.mode), context,
); ).add(AuthResetRequested(mode: widget.mode));
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
), ),
@@ -169,13 +167,13 @@ class _PhoneVerificationPageState extends State<PhoneVerificationPage> {
verificationId: state.verificationId ?? '', verificationId: state.verificationId ?? '',
), ),
) )
: PhoneInput( : PhoneInput(
state: state, state: state,
onSendCode: (String phoneNumber) => _onSendCode( onSendCode: (String phoneNumber) => _onSendCode(
context: context, context: context,
phoneNumber: phoneNumber, phoneNumber: phoneNumber,
),
), ),
),
), ),
), ),
); );

View File

@@ -1,15 +1,16 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart' import 'package:flutter_modular/flutter_modular.dart'
hide ModularWatchExtension; hide ModularWatchExtension;
import 'package:krow_core/core.dart';
import '../blocs/profile_setup/profile_setup_bloc.dart'; import '../blocs/profile_setup/profile_setup_bloc.dart';
import '../widgets/profile_setup_page/profile_setup_basic_info.dart'; import '../widgets/profile_setup_page/profile_setup_basic_info.dart';
import '../widgets/profile_setup_page/profile_setup_location.dart';
import '../widgets/profile_setup_page/profile_setup_experience.dart'; import '../widgets/profile_setup_page/profile_setup_experience.dart';
import '../widgets/profile_setup_page/profile_setup_header.dart'; import '../widgets/profile_setup_page/profile_setup_header.dart';
import 'package:staff_authentication/staff_authentication.dart'; import '../widgets/profile_setup_page/profile_setup_location.dart';
import 'package:krow_core/core.dart';
/// Page for setting up the user profile after authentication. /// Page for setting up the user profile after authentication.
class ProfileSetupPage extends StatefulWidget { class ProfileSetupPage extends StatefulWidget {
@@ -106,7 +107,8 @@ class _ProfileSetupPageState extends State<ProfileSetupPage> {
} }
}, },
builder: (BuildContext context, ProfileSetupState state) { builder: (BuildContext context, ProfileSetupState state) {
final bool isCreatingProfile = state.status == ProfileSetupStatus.loading; final bool isCreatingProfile =
state.status == ProfileSetupStatus.loading;
return Scaffold( return Scaffold(
body: SafeArea( body: SafeArea(
@@ -125,7 +127,10 @@ class _ProfileSetupPageState extends State<ProfileSetupPage> {
// Step Indicators // Step Indicators
UiStepIndicator( UiStepIndicator(
stepIcons: steps stepIcons: steps
.map((Map<String, dynamic> step) => step['icon'] as IconData) .map(
(Map<String, dynamic> step) =>
step['icon'] as IconData,
)
.toList(), .toList(),
currentStep: _currentStep, currentStep: _currentStep,
), ),
@@ -211,9 +216,10 @@ class _ProfileSetupPageState extends State<ProfileSetupPage> {
return ProfileSetupLocation( return ProfileSetupLocation(
preferredLocations: state.preferredLocations, preferredLocations: state.preferredLocations,
maxDistanceMiles: state.maxDistanceMiles, maxDistanceMiles: state.maxDistanceMiles,
onLocationsChanged: (List<String> val) => BlocProvider.of<ProfileSetupBloc>( onLocationsChanged: (List<String> val) =>
context, BlocProvider.of<ProfileSetupBloc>(
).add(ProfileSetupLocationsChanged(val)), context,
).add(ProfileSetupLocationsChanged(val)),
onDistanceChanged: (double val) => BlocProvider.of<ProfileSetupBloc>( onDistanceChanged: (double val) => BlocProvider.of<ProfileSetupBloc>(
context, context,
).add(ProfileSetupDistanceChanged(val)), ).add(ProfileSetupDistanceChanged(val)),
@@ -222,12 +228,14 @@ class _ProfileSetupPageState extends State<ProfileSetupPage> {
return ProfileSetupExperience( return ProfileSetupExperience(
skills: state.skills, skills: state.skills,
industries: state.industries, industries: state.industries,
onSkillsChanged: (List<String> val) => BlocProvider.of<ProfileSetupBloc>( onSkillsChanged: (List<String> val) =>
context, BlocProvider.of<ProfileSetupBloc>(
).add(ProfileSetupSkillsChanged(val)), context,
onIndustriesChanged: (List<String> val) => BlocProvider.of<ProfileSetupBloc>( ).add(ProfileSetupSkillsChanged(val)),
context, onIndustriesChanged: (List<String> val) =>
).add(ProfileSetupIndustriesChanged(val)), BlocProvider.of<ProfileSetupBloc>(
context,
).add(ProfileSetupIndustriesChanged(val)),
); );
default: default:
return const SizedBox.shrink(); return const SizedBox.shrink();

View File

@@ -1,6 +1,6 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:staff_authentication/staff_authentication.dart';
/// A common widget that displays a "Having trouble? Contact Support" link. /// A common widget that displays a "Having trouble? Contact Support" link.
class AuthTroubleLink extends StatelessWidget { class AuthTroubleLink extends StatelessWidget {

View File

@@ -1,76 +1,71 @@
import 'package:flutter/material.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
class GetStartedBackground extends StatelessWidget { class GetStartedBackground extends StatelessWidget {
const GetStartedBackground({super.key}); const GetStartedBackground({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Column(
child: Column( children: [
children: [ const SizedBox(height: 32),
const SizedBox(height: 32), // Logo
// Logo Image.asset(UiImageAssets.logoBlue, height: 40),
Image.asset( Expanded(
UiImageAssets.logoBlue, child: Center(
height: 40, child: Container(
), width: 288,
Expanded( height: 288,
child: Center( decoration: BoxDecoration(
child: Container( shape: BoxShape.circle,
width: 288, color: const Color(0xFF3A4A5A).withOpacity(0.05),
height: 288, ),
decoration: BoxDecoration( padding: const EdgeInsets.all(8.0),
shape: BoxShape.circle, child: ClipOval(
color: const Color(0xFF3A4A5A).withOpacity(0.05), child: Image.network(
), 'https://images.unsplash.com/photo-1577219491135-ce391730fb2c?w=400&h=400&fit=crop&crop=faces',
padding: const EdgeInsets.all(8.0), fit: BoxFit.cover,
child: ClipOval( errorBuilder: (context, error, stackTrace) {
child: Image.network( return Image.asset(UiImageAssets.logoBlue);
'https://images.unsplash.com/photo-1577219491135-ce391730fb2c?w=400&h=400&fit=crop&crop=faces', },
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Image.asset(UiImageAssets.logoBlue);
},
),
), ),
), ),
), ),
), ),
// Pagination dots (Visual only) ),
Row( // Pagination dots (Visual only)
mainAxisAlignment: MainAxisAlignment.center, Row(
children: [ mainAxisAlignment: MainAxisAlignment.center,
Container( children: [
width: 24, Container(
height: 8, width: 24,
decoration: BoxDecoration( height: 8,
color: UiColors.primary, decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4), color: UiColors.primary,
), borderRadius: BorderRadius.circular(4),
), ),
const SizedBox(width: 8), ),
Container( const SizedBox(width: 8),
width: 8, Container(
height: 8, width: 8,
decoration: BoxDecoration( height: 8,
color: UiColors.primary.withOpacity(0.2), decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4), color: UiColors.primary.withOpacity(0.2),
), borderRadius: BorderRadius.circular(4),
), ),
const SizedBox(width: 8), ),
Container( const SizedBox(width: 8),
width: 8, Container(
height: 8, width: 8,
decoration: BoxDecoration( height: 8,
color: UiColors.primary.withOpacity(0.2), decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4), color: UiColors.primary.withOpacity(0.2),
), borderRadius: BorderRadius.circular(4),
), ),
], ),
), ],
], ),
), ],
); );
} }
} }

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:staff_authentication/staff_authentication.dart'; import 'package:flutter/material.dart';
/// A widget that displays the welcome text and description on the Get Started page. /// A widget that displays the welcome text and description on the Get Started page.
class GetStartedHeader extends StatelessWidget { class GetStartedHeader extends StatelessWidget {
@@ -20,9 +20,7 @@ class GetStartedHeader extends StatelessWidget {
text: TextSpan( text: TextSpan(
style: UiTypography.displayM, style: UiTypography.displayM,
children: <InlineSpan>[ children: <InlineSpan>[
TextSpan( TextSpan(text: i18n.title_part1),
text: i18n.title_part1,
),
TextSpan( TextSpan(
text: i18n.title_part2, text: i18n.title_part2,
style: UiTypography.displayMb.textLink, style: UiTypography.displayMb.textLink,
@@ -39,4 +37,4 @@ class GetStartedHeader extends StatelessWidget {
], ],
); );
} }
} }

View File

@@ -1,6 +1,6 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:staff_authentication/staff_authentication.dart';
/// A widget that handles the OTP resend logic and countdown timer. /// A widget that handles the OTP resend logic and countdown timer.
class OtpResendSection extends StatefulWidget { class OtpResendSection extends StatefulWidget {

View File

@@ -1,6 +1,7 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:staff_authentication/staff_authentication.dart';
import '../../common/auth_trouble_link.dart'; import '../../common/auth_trouble_link.dart';
/// A widget that displays the primary action button and trouble link for OTP verification. /// A widget that displays the primary action button and trouble link for OTP verification.

View File

@@ -1,6 +1,6 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:staff_authentication/staff_authentication.dart';
/// A widget that displays the title and subtitle for the OTP Verification page. /// A widget that displays the title and subtitle for the OTP Verification page.
class OtpVerificationHeader extends StatelessWidget { class OtpVerificationHeader extends StatelessWidget {

View File

@@ -1,7 +1,7 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:staff_authentication/src/presentation/widgets/common/auth_trouble_link.dart'; import 'package:staff_authentication/src/presentation/widgets/common/auth_trouble_link.dart';
import 'package:staff_authentication/staff_authentication.dart';
/// A widget that displays the primary action button and trouble link for Phone Input. /// A widget that displays the primary action button and trouble link for Phone Input.
class PhoneInputActions extends StatelessWidget { class PhoneInputActions extends StatelessWidget {

View File

@@ -1,7 +1,7 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:staff_authentication/staff_authentication.dart';
/// A widget that displays the phone number input field with country code. /// A widget that displays the phone number input field with country code.
/// ///

View File

@@ -1,6 +1,6 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:staff_authentication/staff_authentication.dart';
/// A widget that displays the title and subtitle for the Phone Input page. /// A widget that displays the title and subtitle for the Phone Input page.
class PhoneInputHeader extends StatelessWidget { class PhoneInputHeader extends StatelessWidget {

View File

@@ -1,7 +1,7 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:staff_authentication/src/presentation/widgets/common/section_title_subtitle.dart'; import 'package:staff_authentication/src/presentation/widgets/common/section_title_subtitle.dart';
import 'package:staff_authentication/staff_authentication.dart';
/// A widget for setting up basic profile information (photo, name, bio). /// A widget for setting up basic profile information (photo, name, bio).
class ProfileSetupBasicInfo extends StatelessWidget { class ProfileSetupBasicInfo extends StatelessWidget {

View File

@@ -1,8 +1,8 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import 'package:staff_authentication/src/presentation/widgets/common/section_title_subtitle.dart'; import 'package:staff_authentication/src/presentation/widgets/common/section_title_subtitle.dart';
import 'package:staff_authentication/staff_authentication.dart';
/// A widget for setting up skills and preferred industries. /// A widget for setting up skills and preferred industries.
class ProfileSetupExperience extends StatelessWidget { class ProfileSetupExperience extends StatelessWidget {

View File

@@ -1,6 +1,6 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:staff_authentication/staff_authentication.dart';
/// A header widget for the profile setup page showing back button and step count. /// A header widget for the profile setup page showing back button and step count.
class ProfileSetupHeader extends StatelessWidget { class ProfileSetupHeader extends StatelessWidget {

View File

@@ -1,10 +1,11 @@
import 'dart:async'; import 'dart:async';
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:staff_authentication/src/presentation/blocs/profile_setup/profile_setup_bloc.dart'; import 'package:staff_authentication/src/presentation/blocs/profile_setup/profile_setup_bloc.dart';
import 'package:staff_authentication/src/presentation/widgets/common/section_title_subtitle.dart'; import 'package:staff_authentication/src/presentation/widgets/common/section_title_subtitle.dart';
import 'package:staff_authentication/staff_authentication.dart';
/// A widget for setting up preferred work locations and distance. /// A widget for setting up preferred work locations and distance.
class ProfileSetupLocation extends StatefulWidget { class ProfileSetupLocation extends StatefulWidget {
@@ -47,22 +48,23 @@ class _ProfileSetupLocationState extends State<ProfileSetupLocation> {
void _onSearchChanged(String query) { void _onSearchChanged(String query) {
if (_debounce?.isActive ?? false) _debounce!.cancel(); if (_debounce?.isActive ?? false) _debounce!.cancel();
_debounce = Timer(const Duration(milliseconds: 300), () { _debounce = Timer(const Duration(milliseconds: 300), () {
context context.read<ProfileSetupBloc>().add(
.read<ProfileSetupBloc>() ProfileSetupLocationQueryChanged(query),
.add(ProfileSetupLocationQueryChanged(query)); );
}); });
} }
/// Adds the selected location. /// Adds the selected location.
void _addLocation(String location) { void _addLocation(String location) {
if (location.isNotEmpty && !widget.preferredLocations.contains(location)) { if (location.isNotEmpty && !widget.preferredLocations.contains(location)) {
final List<String> updatedList = final List<String> updatedList = List<String>.from(
List<String>.from(widget.preferredLocations)..add(location); widget.preferredLocations,
)..add(location);
widget.onLocationsChanged(updatedList); widget.onLocationsChanged(updatedList);
_locationController.clear(); _locationController.clear();
context context.read<ProfileSetupBloc>().add(
.read<ProfileSetupBloc>() const ProfileSetupClearLocationSuggestions(),
.add(const ProfileSetupClearLocationSuggestions()); );
} }
} }
@@ -79,10 +81,16 @@ class _ProfileSetupLocationState extends State<ProfileSetupLocation> {
// Search Input // Search Input
UiTextField( UiTextField(
label: t.staff_authentication.profile_setup_page.location label: t
.staff_authentication
.profile_setup_page
.location
.add_location_label, .add_location_label,
controller: _locationController, controller: _locationController,
hintText: t.staff_authentication.profile_setup_page.location hintText: t
.staff_authentication
.profile_setup_page
.location
.add_location_hint, .add_location_hint,
onChanged: _onSearchChanged, onChanged: _onSearchChanged,
), ),
@@ -99,15 +107,8 @@ class _ProfileSetupLocationState extends State<ProfileSetupLocation> {
constraints: const BoxConstraints(maxHeight: 200), constraints: const BoxConstraints(maxHeight: 200),
margin: const EdgeInsets.only(top: UiConstants.space2), margin: const EdgeInsets.only(top: UiConstants.space2),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).cardColor, color: UiColors.cardViewBackground,
borderRadius: UiConstants.radiusMd, borderRadius: UiConstants.radiusMd,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
), ),
child: ListView.separated( child: ListView.separated(
shrinkWrap: true, shrinkWrap: true,
@@ -167,12 +168,18 @@ class _ProfileSetupLocationState extends State<ProfileSetupLocation> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[ children: <Widget>[
Text( Text(
t.staff_authentication.profile_setup_page.location t
.staff_authentication
.profile_setup_page
.location
.min_dist_label, .min_dist_label,
style: UiTypography.footnote1r.textSecondary, style: UiTypography.footnote1r.textSecondary,
), ),
Text( Text(
t.staff_authentication.profile_setup_page.location t
.staff_authentication
.profile_setup_page
.location
.max_dist_label, .max_dist_label,
style: UiTypography.footnote1r.textSecondary, style: UiTypography.footnote1r.textSecondary,
), ),
@@ -185,8 +192,9 @@ class _ProfileSetupLocationState extends State<ProfileSetupLocation> {
/// Removes the specified [location] from the list. /// Removes the specified [location] from the list.
void _removeLocation({required String location}) { void _removeLocation({required String location}) {
final List<String> updatedList = final List<String> updatedList = List<String>.from(
List<String>.from(widget.preferredLocations)..remove(location); widget.preferredLocations,
)..remove(location);
widget.onLocationsChanged(updatedList); widget.onLocationsChanged(updatedList);
} }
} }

View File

@@ -0,0 +1,83 @@
import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_core/core.dart';
import 'package:krow_data_connect/krow_data_connect.dart';
import 'package:firebase_auth/firebase_auth.dart' as firebase;
import 'package:staff_authentication/src/data/repositories_impl/auth_repository_impl.dart';
import 'package:staff_authentication/src/domain/repositories/auth_repository_interface.dart';
import 'package:staff_authentication/src/domain/usecases/sign_in_with_phone_usecase.dart';
import 'package:staff_authentication/src/domain/usecases/verify_otp_usecase.dart';
import 'package:staff_authentication/src/domain/repositories/profile_setup_repository.dart';
import 'package:staff_authentication/src/data/repositories_impl/profile_setup_repository_impl.dart';
import 'package:staff_authentication/src/domain/usecases/submit_profile_setup_usecase.dart';
import 'package:staff_authentication/src/domain/repositories/place_repository.dart';
import 'package:staff_authentication/src/data/repositories_impl/place_repository_impl.dart';
import 'package:staff_authentication/src/domain/usecases/search_cities_usecase.dart';
import 'package:staff_authentication/src/presentation/blocs/auth_bloc.dart';
import 'package:staff_authentication/src/presentation/blocs/profile_setup/profile_setup_bloc.dart';
import 'package:staff_authentication/src/presentation/pages/get_started_page.dart';
import 'package:staff_authentication/src/presentation/pages/phone_verification_page.dart';
import 'package:staff_authentication/src/presentation/pages/profile_setup_page.dart';
import 'package:staff_authentication/src/domain/ui_entities/auth_mode.dart';
/// A [Module] for the staff authentication feature.
class StaffAuthenticationModule extends Module {
@override
List<Module> get imports => <Module>[DataConnectModule()];
@override
void binds(Injector i) {
// Repositories
i.addLazySingleton<AuthRepositoryInterface>(
() => AuthRepositoryImpl(
firebaseAuth: firebase.FirebaseAuth.instance,
dataConnect: ExampleConnector.instance,
),
);
i.addLazySingleton<ProfileSetupRepository>(
() => ProfileSetupRepositoryImpl(
firebaseAuth: firebase.FirebaseAuth.instance,
dataConnect: ExampleConnector.instance,
),
);
i.addLazySingleton<PlaceRepository>(PlaceRepositoryImpl.new);
// UseCases
i.addLazySingleton(SignInWithPhoneUseCase.new);
i.addLazySingleton(VerifyOtpUseCase.new);
i.addLazySingleton(SubmitProfileSetup.new);
i.addLazySingleton(SearchCitiesUseCase.new);
// BLoCs
i.addLazySingleton<AuthBloc>(
() => AuthBloc(
signInUseCase: i.get<SignInWithPhoneUseCase>(),
verifyOtpUseCase: i.get<VerifyOtpUseCase>(),
),
);
i.add<ProfileSetupBloc>(
() => ProfileSetupBloc(
submitProfileSetup: i.get<SubmitProfileSetup>(),
searchCities: i.get<SearchCitiesUseCase>(),
),
);
}
@override
void routes(RouteManager r) {
r.child(StaffPaths.root, child: (_) => const GetStartedPage());
r.child(
StaffPaths.phoneVerification,
child: (BuildContext context) {
final Map<String, dynamic>? data = r.args.data;
final String? modeName = data?['mode'];
final AuthMode mode = AuthMode.values.firstWhere(
(AuthMode e) => e.name == modeName,
orElse: () => AuthMode.login,
);
return PhoneVerificationPage(mode: mode);
},
);
r.child(StaffPaths.profileSetup, child: (_) => const ProfileSetupPage());
}
}

View File

@@ -1,91 +1,5 @@
library staff_authentication;
import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_core/core.dart';
import 'package:krow_data_connect/krow_data_connect.dart';
import 'package:firebase_auth/firebase_auth.dart' as firebase;
import 'package:staff_authentication/src/data/repositories_impl/auth_repository_impl.dart';
import 'package:staff_authentication/src/domain/repositories/auth_repository_interface.dart';
import 'package:staff_authentication/src/domain/usecases/sign_in_with_phone_usecase.dart';
import 'package:staff_authentication/src/domain/usecases/verify_otp_usecase.dart';
import 'package:staff_authentication/src/domain/repositories/profile_setup_repository.dart';
import 'package:staff_authentication/src/data/repositories_impl/profile_setup_repository_impl.dart';
import 'package:staff_authentication/src/domain/usecases/submit_profile_setup_usecase.dart';
import 'package:staff_authentication/src/domain/repositories/place_repository.dart';
import 'package:staff_authentication/src/data/repositories_impl/place_repository_impl.dart';
import 'package:staff_authentication/src/domain/usecases/search_cities_usecase.dart';
import 'package:staff_authentication/src/presentation/blocs/auth_bloc.dart';
import 'package:staff_authentication/src/presentation/blocs/profile_setup/profile_setup_bloc.dart';
import 'package:staff_authentication/src/presentation/pages/get_started_page.dart';
import 'package:staff_authentication/src/presentation/pages/phone_verification_page.dart';
import 'package:staff_authentication/src/presentation/pages/profile_setup_page.dart';
import 'package:staff_authentication/src/domain/ui_entities/auth_mode.dart';
export 'src/domain/ui_entities/auth_mode.dart'; export 'src/domain/ui_entities/auth_mode.dart';
export 'src/presentation/pages/get_started_page.dart'; export 'src/presentation/pages/get_started_page.dart';
export 'src/presentation/pages/phone_verification_page.dart'; export 'src/presentation/pages/phone_verification_page.dart';
export 'src/presentation/pages/profile_setup_page.dart'; export 'src/presentation/pages/profile_setup_page.dart';
export 'package:core_localization/core_localization.dart'; export 'src/staff_authentication_module.dart';
/// A [Module] for the staff authentication feature.
class StaffAuthenticationModule extends Module {
@override
List<Module> get imports => <Module>[DataConnectModule()];
@override
void binds(Injector i) {
// Repositories
i.addLazySingleton<AuthRepositoryInterface>(
() => AuthRepositoryImpl(
firebaseAuth: firebase.FirebaseAuth.instance,
dataConnect: ExampleConnector.instance,
),
);
i.addLazySingleton<ProfileSetupRepository>(
() => ProfileSetupRepositoryImpl(
firebaseAuth: firebase.FirebaseAuth.instance,
dataConnect: ExampleConnector.instance,
),
);
i.addLazySingleton<PlaceRepository>(PlaceRepositoryImpl.new);
// UseCases
i.addLazySingleton(SignInWithPhoneUseCase.new);
i.addLazySingleton(VerifyOtpUseCase.new);
i.addLazySingleton(SubmitProfileSetup.new);
i.addLazySingleton(SearchCitiesUseCase.new);
// BLoCs
i.addLazySingleton<AuthBloc>(
() => AuthBloc(
signInUseCase: i.get<SignInWithPhoneUseCase>(),
verifyOtpUseCase: i.get<VerifyOtpUseCase>(),
),
);
i.add<ProfileSetupBloc>(
() => ProfileSetupBloc(
submitProfileSetup: i.get<SubmitProfileSetup>(),
searchCities: i.get<SearchCitiesUseCase>(),
),
);
}
@override
void routes(RouteManager r) {
r.child(StaffPaths.root, child: (_) => const GetStartedPage());
r.child(
StaffPaths.phoneVerification,
child: (BuildContext context) {
final Map<String, dynamic>? data = r.args.data;
final String? modeName = data?['mode'];
final AuthMode mode = AuthMode.values.firstWhere(
(AuthMode e) => e.name == modeName,
orElse: () => AuthMode.login,
);
return PhoneVerificationPage(mode: mode);
},
);
r.child(StaffPaths.profileSetup, child: (_) => const ProfileSetupPage());
}
}

View File

@@ -14,9 +14,8 @@ dependencies:
flutter_bloc: ^8.1.0 flutter_bloc: ^8.1.0
flutter_modular: ^6.3.0 flutter_modular: ^6.3.0
equatable: ^2.0.5 equatable: ^2.0.5
lucide_icons: ^0.257.0
firebase_core: ^4.2.1 firebase_core: ^4.2.1
firebase_auth: ^6.1.2 # Updated for compatibility firebase_auth: ^6.1.2
firebase_data_connect: ^0.2.2+1 firebase_data_connect: ^0.2.2+1
http: ^1.2.0 http: ^1.2.0