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:
@@ -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
|
|
||||||
@@ -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,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user