Merge branch '208-p0-auth-05-get-started-screen' into rapid_order_client

This commit is contained in:
José Salazar
2026-01-23 09:42:40 -05:00
53 changed files with 1942 additions and 1990 deletions

View File

@@ -6,7 +6,7 @@ import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:client_authentication/client_authentication.dart'
as client_authentication;
import 'package:client_home/client_home.dart' as client_home;
import 'package:client_main/client_main.dart' as client_main;
import 'package:client_settings/client_settings.dart' as client_settings;
import 'package:client_hubs/client_hubs.dart' as client_hubs;
import 'package:client_create_order/client_create_order.dart'
@@ -29,8 +29,8 @@ class AppModule extends Module {
// Initial route points to the client authentication flow
r.module('/', module: client_authentication.ClientAuthenticationModule());
// Client home route
r.module('/client-home', module: client_home.ClientHomeModule());
// Client main shell with bottom navigation (includes home as a child)
r.module('/client-main', module: client_main.ClientMainModule());
// Client settings route
r.module(

View File

@@ -20,6 +20,8 @@ dependencies:
# Feature Packages
client_authentication:
path: ../../packages/features/client/authentication
client_main:
path: ../../packages/features/client/client_main
client_home:
path: ../../packages/features/client/home
client_settings:

View File

@@ -280,13 +280,17 @@
"end_label": "End",
"workers_label": "Workers",
"lunch_break_label": "Lunch Break",
"no_break": "No break",
"paid_break": "min (Paid)",
"unpaid_break": "min (Unpaid)",
"different_location": "Use different location for this position",
"different_location_title": "Different Location",
"different_location_hint": "Enter different address",
"create_order": "Create Order",
"creating": "Creating...",
"success_title": "Order Created!",
"success_message": "Your shift request has been posted. Workers will start applying soon."
"success_message": "Your shift request has been posted. Workers will start applying soon.",
"back_to_orders": "Back to Orders"
},
"recurring": {
"title": "Recurring Order",
@@ -298,6 +302,15 @@
"subtitle": "Long-term staffing placement",
"placeholder": "Permanent Order Flow (Work in Progress)"
}
},
"client_main": {
"tabs": {
"coverage": "Coverage",
"billing": "Billing",
"home": "Home",
"orders": "Orders",
"reports": "Reports"
}
}
}

View File

@@ -286,7 +286,11 @@
"create_order": "Crear Orden",
"creating": "Creando...",
"success_title": "¡Orden Creada!",
"success_message": "Tu solicitud de turno ha sido publicada. Los trabajadores comenzarán a postularse pronto."
"success_message": "Tu solicitud de turno ha sido publicada. Los trabajadores comenzarán a postularse pronto.",
"back_to_orders": "Volver a Órdenes",
"no_break": "Sin descanso",
"paid_break": "min (Pagado)",
"unpaid_break": "min (No pagado)"
},
"recurring": {
"title": "Orden Recurrente",
@@ -298,5 +302,14 @@
"subtitle": "Colocación de personal a largo plazo",
"placeholder": "Flujo de Orden Permanente (Trabajo en Progreso)"
}
},
"client_main": {
"tabs": {
"coverage": "Cobertura",
"billing": "Facturación",
"home": "Inicio",
"orders": "Órdenes",
"reports": "Reportes"
}
}
}

View File

@@ -78,6 +78,9 @@ class UiIcons {
/// Chevron left icon
static const IconData chevronLeft = _IconLib.chevronLeft;
/// Chevron down icon
static const IconData chevronDown = _IconLib.chevronDown;
// --- Status & Feedback ---
/// Info icon
@@ -177,4 +180,7 @@ class UiIcons {
/// NFC icon
static const IconData nfc = _IconLib.nfc;
/// Chart icon for reports
static const IconData chart = _IconLib.barChart3;
}

View File

@@ -18,8 +18,9 @@ extension ClientAuthNavigator on IModularNavigator {
/// Navigates to the main client home dashboard.
///
/// Uses absolute path navigation to reset the navigation stack if necessary.
/// Uses absolute path navigation to the client main shell,
/// which will display the home tab by default.
void navigateClientHome() {
navigate('/client-home');
navigate('/client-main/home');
}
}

View File

@@ -0,0 +1,4 @@
library;
export 'src/client_main_module.dart';
export 'src/presentation/navigation/client_main_navigator.dart';

View File

@@ -0,0 +1,46 @@
import 'package:client_home/client_home.dart';
import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'presentation/blocs/client_main_cubit.dart';
import 'presentation/pages/client_main_page.dart';
import 'presentation/pages/placeholder_page.dart';
class ClientMainModule extends Module {
@override
void binds(Injector i) {
i.addSingleton(ClientMainCubit.new);
}
@override
void routes(RouteManager r) {
r.child(
'/',
child: (BuildContext context) => const ClientMainPage(),
children: <ParallelRoute<dynamic>>[
ModuleRoute<dynamic>('/home', module: ClientHomeModule()),
// Placeholders for other tabs
ChildRoute<dynamic>(
'/coverage',
child: (BuildContext context) =>
const PlaceholderPage(title: 'Coverage'),
),
ChildRoute<dynamic>(
'/billing',
child: (BuildContext context) =>
const PlaceholderPage(title: 'Billing'),
),
ChildRoute<dynamic>(
'/orders',
child: (BuildContext context) =>
const PlaceholderPage(title: 'Orders'),
),
ChildRoute<dynamic>(
'/reports',
child: (BuildContext context) =>
const PlaceholderPage(title: 'Reports'),
),
],
);
}
}

View File

@@ -0,0 +1,62 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'client_main_state.dart';
class ClientMainCubit extends Cubit<ClientMainState> implements Disposable {
ClientMainCubit() : super(const ClientMainState()) {
Modular.to.addListener(_onRouteChanged);
_onRouteChanged();
}
void _onRouteChanged() {
final String path = Modular.to.path;
int newIndex = state.currentIndex;
// Detect which tab is active based on the route path
// Using contains() to handle child routes and trailing slashes
if (path.contains('/client-main/coverage')) {
newIndex = 0;
} else if (path.contains('/client-main/billing')) {
newIndex = 1;
} else if (path.contains('/client-main/home')) {
newIndex = 2;
} else if (path.contains('/client-main/orders')) {
newIndex = 3;
} else if (path.contains('/client-main/reports')) {
newIndex = 4;
}
if (newIndex != state.currentIndex) {
emit(state.copyWith(currentIndex: newIndex));
}
}
void navigateToTab(int index) {
if (index == state.currentIndex) return;
switch (index) {
case 0:
Modular.to.navigate('/client-main/coverage');
break;
case 1:
Modular.to.navigate('/client-main/billing');
break;
case 2:
Modular.to.navigate('/client-main/home');
break;
case 3:
Modular.to.navigate('/client-main/orders');
break;
case 4:
Modular.to.navigate('/client-main/reports');
break;
}
// State update will happen via _onRouteChanged
}
@override
void dispose() {
Modular.to.removeListener(_onRouteChanged);
close();
}
}

View File

@@ -0,0 +1,16 @@
import 'package:equatable/equatable.dart';
class ClientMainState extends Equatable {
const ClientMainState({
this.currentIndex = 2, // Default to Home
});
final int currentIndex;
ClientMainState copyWith({int? currentIndex}) {
return ClientMainState(currentIndex: currentIndex ?? this.currentIndex);
}
@override
List<Object> get props => <Object>[currentIndex];
}

View File

@@ -0,0 +1,10 @@
import 'package:flutter_modular/flutter_modular.dart';
/// Extension to provide typed navigation for the Client Main feature.
extension ClientMainNavigator on IModularNavigator {
/// Navigates to the Client Main Shell (Home).
/// This replaces the current navigation stack.
void navigateClientMain() {
navigate('/client-main/');
}
}

View File

@@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import '../blocs/client_main_cubit.dart';
import '../blocs/client_main_state.dart';
import '../widgets/client_main_bottom_bar.dart';
/// The main page for the Client app, acting as a shell for the bottom navigation.
///
/// It follows KROW Clean Architecture by:
/// - Being a [StatelessWidget].
/// - Delegating state management to [ClientMainCubit].
/// - Using [RouterOutlet] for nested navigation.
class ClientMainPage extends StatelessWidget {
const ClientMainPage({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider<ClientMainCubit>(
create: (BuildContext context) => Modular.get<ClientMainCubit>(),
child: Scaffold(
extendBody: true,
body: const RouterOutlet(),
bottomNavigationBar: BlocBuilder<ClientMainCubit, ClientMainState>(
builder: (BuildContext context, ClientMainState state) {
return ClientMainBottomBar(
currentIndex: state.currentIndex,
onTap: (int index) {
BlocProvider.of<ClientMainCubit>(context).navigateToTab(index);
},
);
},
),
),
);
}
}

View File

@@ -0,0 +1,33 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// A placeholder page for features that are not yet implemented.
///
/// This page displays a simple message indicating that the feature
/// is coming soon. It follows the KROW Design System guidelines by:
/// - Using [UiAppBar] for the app bar
/// - Using [UiTypography] for text styling
/// - Using [UiColors] via typography extensions
class PlaceholderPage extends StatelessWidget {
/// Creates a [PlaceholderPage].
///
/// The [title] is displayed in the app bar and used in the
/// "coming soon" message.
const PlaceholderPage({required this.title, super.key});
/// The title of the feature being displayed.
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: UiAppBar(title: title),
body: Center(
child: Text(
'$title Feature Coming Soon',
style: UiTypography.body1r.textPrimary,
),
),
);
}
}

View File

@@ -0,0 +1,156 @@
import 'dart:ui';
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// A custom bottom navigation bar for the Client app.
///
/// This widget provides a glassmorphic bottom navigation bar with blur effect
/// and follows the KROW Design System guidelines. It displays five tabs:
/// Coverage, Billing, Home, Orders, and Reports.
///
/// The widget uses:
/// - [UiColors] for all color values
/// - [UiTypography] for text styling
/// - [UiIcons] for icon assets
/// - [UiConstants] for spacing and sizing
class ClientMainBottomBar extends StatelessWidget {
/// Creates a [ClientMainBottomBar].
///
/// The [currentIndex] indicates which tab is currently selected.
/// The [onTap] callback is invoked when a tab is tapped.
const ClientMainBottomBar({
required this.currentIndex,
required this.onTap,
super.key,
});
/// The index of the currently selected tab.
final int currentIndex;
/// Callback invoked when a tab is tapped.
///
/// The callback receives the index of the tapped tab.
final ValueChanged<int> onTap;
@override
Widget build(BuildContext context) {
// Client App colors from design system
const Color activeColor = UiColors.textPrimary;
const Color inactiveColor = UiColors.textInactive;
return Stack(
clipBehavior: Clip.none,
children: <Widget>[
// Glassmorphic background with blur effect
Positioned.fill(
child: ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: Container(
decoration: BoxDecoration(
color: UiColors.white.withValues(alpha: 0.85),
border: Border(
top: BorderSide(
color: UiColors.black.withValues(alpha: 0.1),
),
),
),
),
),
),
),
// Navigation items
Container(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom + UiConstants.space2,
top: UiConstants.space4,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
_buildNavItem(
index: 0,
icon: UiIcons.calendar,
label: t.client_main.tabs.coverage,
activeColor: activeColor,
inactiveColor: inactiveColor,
),
_buildNavItem(
index: 1,
icon: UiIcons.dollar,
label: t.client_main.tabs.billing,
activeColor: activeColor,
inactiveColor: inactiveColor,
),
_buildNavItem(
index: 2,
icon: UiIcons.building,
label: t.client_main.tabs.home,
activeColor: activeColor,
inactiveColor: inactiveColor,
),
_buildNavItem(
index: 3,
icon: UiIcons.file,
label: t.client_main.tabs.orders,
activeColor: activeColor,
inactiveColor: inactiveColor,
),
_buildNavItem(
index: 4,
icon: UiIcons.chart,
label: t.client_main.tabs.reports,
activeColor: activeColor,
inactiveColor: inactiveColor,
),
],
),
),
],
);
}
/// Builds a single navigation item.
///
/// Uses design system tokens for all styling:
/// - Icon size uses a standard value (24px is acceptable for navigation icons)
/// - Spacing uses [UiConstants.space1]
/// - Typography uses [UiTypography.footnote2m]
/// - Colors are passed as parameters from design system
Widget _buildNavItem({
required int index,
required IconData icon,
required String label,
required Color activeColor,
required Color inactiveColor,
}) {
final bool isSelected = currentIndex == index;
return Expanded(
child: GestureDetector(
onTap: () => onTap(index),
behavior: HitTestBehavior.opaque,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Icon(
icon,
color: isSelected ? activeColor : inactiveColor,
size: 24, // Standard navigation icon size
),
const SizedBox(height: UiConstants.space1),
Text(
label,
style: UiTypography.footnote2m.copyWith(
color: isSelected ? activeColor : inactiveColor,
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,38 @@
name: client_main
description: Main shell and navigation for the client application.
version: 0.0.1
publish_to: none
resolution: workspace
environment:
sdk: '>=3.10.0 <4.0.0'
flutter: ">=3.0.0"
dependencies:
flutter:
sdk: flutter
flutter_bloc: ^8.1.0
flutter_modular: ^6.3.0
equatable: ^2.0.5
lucide_icons: ^0.257.0
# Architecture Packages
design_system:
path: ../../../design_system
core_localization:
path: ../../../core_localization
client_home:
path: ../home
# Intentionally commenting these out as they might not exist yet
# client_settings:
# path: ../settings
dev_dependencies:
flutter_test:
sdk: flutter
bloc_test: ^9.1.0
mocktail: ^1.0.0
flutter_lints: ^6.0.0
flutter:
uses-material-design: true

View File

@@ -0,0 +1,38 @@
import 'package:bloc_test/bloc_test.dart';
import 'package:client_main/src/presentation/blocs/client_main_cubit.dart';
import 'package:client_main/src/presentation/blocs/client_main_state.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
class MockIModularNavigator extends Mock implements IModularNavigator {}
void main() {
group('ClientMainCubit', () {
late MockIModularNavigator navigator;
setUp(() {
navigator = MockIModularNavigator();
when(() => navigator.path).thenReturn('/home');
when(() => navigator.addListener(any())).thenReturn(null);
// Stub addListener to avoid errors when Cubit adds listener
// Note: addListener might be on Modular directly or via some other mechanic,
// but for this unit test we just want to suppress errors if possible or let the Cubit work.
// Actually Modular.to.addListener calls Modular.navigatorDelegate.addListener if it exists?
// Modular.to.addListener uses the internal RouterDelegate.
// Mocking Modular internals is hard.
// Let's rely on the fact that we mocked navigatorDelegate.
Modular.navigatorDelegate = navigator;
});
test('initial state is correct', () {
final cubit = ClientMainCubit();
expect(cubit.state, const ClientMainState(currentIndex: 2));
cubit.close();
});
// Note: Testing actual route changes requires more complex Modular mocking
// or integration tests, but the structure allows it.
});
}

View File

@@ -1,3 +1,4 @@
library client_create_order;
/// Library for the Client Create Order feature.
library;
export 'src/create_order_module.dart';

View File

@@ -29,7 +29,8 @@ class ClientCreateOrderModule extends Module {
// Repositories
i.addLazySingleton<ClientCreateOrderRepositoryInterface>(
() => ClientCreateOrderRepositoryImpl(
orderMock: i.get<OrderRepositoryMock>()),
orderMock: i.get<OrderRepositoryMock>(),
),
);
// UseCases
@@ -45,14 +46,22 @@ class ClientCreateOrderModule extends Module {
@override
void routes(RouteManager r) {
r.child('/',
child: (BuildContext context) => const ClientCreateOrderPage());
r.child(
'/',
child: (BuildContext context) => const ClientCreateOrderPage(),
);
r.child('/rapid', child: (BuildContext context) => const RapidOrderPage());
r.child('/one-time',
child: (BuildContext context) => const OneTimeOrderPage());
r.child('/recurring',
child: (BuildContext context) => const RecurringOrderPage());
r.child('/permanent',
child: (BuildContext context) => const PermanentOrderPage());
r.child(
'/one-time',
child: (BuildContext context) => const OneTimeOrderPage(),
);
r.child(
'/recurring',
child: (BuildContext context) => const RecurringOrderPage(),
);
r.child(
'/permanent',
child: (BuildContext context) => const PermanentOrderPage(),
);
}
}

View File

@@ -4,30 +4,37 @@ import '../../domain/repositories/client_create_order_repository_interface.dart'
/// Implementation of [ClientCreateOrderRepositoryInterface].
///
/// This implementation delegates all data access to the Data Connect layer,
/// specifically using [OrderRepositoryMock] for now as per the platform's mocking strategy.
/// This implementation coordinates data access for order creation by delegating
/// to the [OrderRepositoryMock] and [ExampleConnector] from the shared
/// Data Connect package.
///
/// It follows the KROW Clean Architecture by keeping the data layer focused
/// on delegation and data mapping, without business logic.
class ClientCreateOrderRepositoryImpl
implements ClientCreateOrderRepositoryInterface {
/// Creates a [ClientCreateOrderRepositoryImpl].
///
/// Requires an [OrderRepositoryMock] from the Data Connect shared package.
/// Requires the [OrderRepositoryMock] from the shared Data Connect package.
/// TODO: Inject and use ExampleConnector when real mutations are available.
ClientCreateOrderRepositoryImpl({required OrderRepositoryMock orderMock})
: _orderMock = orderMock;
: _orderMock = orderMock;
final OrderRepositoryMock _orderMock;
@override
Future<List<OrderType>> getOrderTypes() {
// Delegates to Data Connect layer
return _orderMock.getOrderTypes();
}
@override
Future<void> createOneTimeOrder(OneTimeOrder order) {
// Delegates to Data Connect layer
return _orderMock.createOneTimeOrder(order);
}
@override
Future<void> createRapidOrder(String description) {
// Delegates to Data Connect layer
return _orderMock.createRapidOrder(description);
}
}

View File

@@ -2,9 +2,15 @@ import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
/// Represents the arguments required for the [CreateOneTimeOrderUseCase].
///
/// Encapsulates the [OneTimeOrder] details required to create a new
/// one-time staffing request.
class OneTimeOrderArguments extends UseCaseArgument {
/// Creates a [OneTimeOrderArguments] instance.
///
/// Requires the [order] details.
const OneTimeOrderArguments({required this.order});
/// The order details to be created.
final OneTimeOrder order;

View File

@@ -1,9 +1,15 @@
import 'package:krow_core/core.dart';
/// Represents the arguments required for the [CreateRapidOrderUseCase].
///
/// Encapsulates the text description of the urgent staffing need
/// for rapid order creation.
class RapidOrderArguments extends UseCaseArgument {
/// Creates a [RapidOrderArguments] instance.
///
/// Requires the [description] of the staffing need.
const RapidOrderArguments({required this.description});
/// The text description of the urgent staffing need.
final String description;

View File

@@ -2,15 +2,23 @@ import 'package:krow_domain/krow_domain.dart';
/// Interface for the Client Create Order repository.
///
/// This repository handles the retrieval of available order types and the
/// submission of different types of staffing orders (Rapid, One-Time, etc.).
/// This repository is responsible for:
/// 1. Retrieving available order types for the client.
/// 2. Submitting different types of staffing orders (Rapid, One-Time).
///
/// It follows the KROW Clean Architecture by defining the contract in the
/// domain layer, to be implemented in the data layer.
abstract interface class ClientCreateOrderRepositoryInterface {
/// Retrieves the list of available order types.
/// Retrieves the list of available order types (e.g., Rapid, One-Time, Recurring).
Future<List<OrderType>> getOrderTypes();
/// Submits a one-time staffing order.
/// Submits a one-time staffing order with specific details.
///
/// [order] contains the date, location, and required positions.
Future<void> createOneTimeOrder(OneTimeOrder order);
/// Submits a rapid (urgent) staffing order with a text description.
/// Submits a rapid (urgent) staffing order via a text description.
///
/// [description] is the text message (or transcribed voice) describing the need.
Future<void> createRapidOrder(String description);
}

View File

@@ -4,12 +4,14 @@ import '../repositories/client_create_order_repository_interface.dart';
/// Use case for creating a one-time staffing order.
///
/// This use case uses the [ClientCreateOrderRepositoryInterface] to submit
/// a [OneTimeOrder] provided via [OneTimeOrderArguments].
/// This use case encapsulates the logic for submitting a structured
/// staffing request and delegates the data operation to the
/// [ClientCreateOrderRepositoryInterface].
class CreateOneTimeOrderUseCase
implements UseCase<OneTimeOrderArguments, void> {
/// Creates a [CreateOneTimeOrderUseCase].
///
/// Requires a [ClientCreateOrderRepositoryInterface] to interact with the data layer.
const CreateOneTimeOrderUseCase(this._repository);
final ClientCreateOrderRepositoryInterface _repository;

View File

@@ -4,11 +4,12 @@ import '../repositories/client_create_order_repository_interface.dart';
/// Use case for creating a rapid (urgent) staffing order.
///
/// This use case uses the [ClientCreateOrderRepositoryInterface] to submit
/// a text-based urgent request via [RapidOrderArguments].
/// This use case handles urgent, text-based staffing requests and
/// delegates the submission to the [ClientCreateOrderRepositoryInterface].
class CreateRapidOrderUseCase implements UseCase<RapidOrderArguments, void> {
/// Creates a [CreateRapidOrderUseCase].
///
/// Requires a [ClientCreateOrderRepositoryInterface] to interact with the data layer.
const CreateRapidOrderUseCase(this._repository);
final ClientCreateOrderRepositoryInterface _repository;

View File

@@ -4,11 +4,12 @@ import '../repositories/client_create_order_repository_interface.dart';
/// Use case for retrieving the available order types for a client.
///
/// This use case interacts with the [ClientCreateOrderRepositoryInterface] to
/// fetch the list of staffing order types (e.g., Rapid, One-Time).
/// This use case fetches the list of supported staffing order types
/// from the [ClientCreateOrderRepositoryInterface].
class GetOrderTypesUseCase implements NoInputUseCase<List<OrderType>> {
/// Creates a [GetOrderTypesUseCase].
///
/// Requires a [ClientCreateOrderRepositoryInterface] to interact with the data layer.
const GetOrderTypesUseCase(this._repository);
final ClientCreateOrderRepositoryInterface _repository;

View File

@@ -7,7 +7,6 @@ import 'client_create_order_state.dart';
/// BLoC for managing the list of available order types.
class ClientCreateOrderBloc
extends Bloc<ClientCreateOrderEvent, ClientCreateOrderState> {
ClientCreateOrderBloc(this._getOrderTypesUseCase)
: super(const ClientCreateOrderInitial()) {
on<ClientCreateOrderTypesRequested>(_onTypesRequested);

View File

@@ -12,7 +12,6 @@ class ClientCreateOrderTypesRequested extends ClientCreateOrderEvent {
}
class ClientCreateOrderTypeSelected extends ClientCreateOrderEvent {
const ClientCreateOrderTypeSelected(this.typeId);
final String typeId;

View File

@@ -16,8 +16,8 @@ class ClientCreateOrderInitial extends ClientCreateOrderState {
/// State representing successfully loaded order types from the repository.
class ClientCreateOrderLoadSuccess extends ClientCreateOrderState {
const ClientCreateOrderLoadSuccess(this.orderTypes);
/// The list of available order types retrieved from the domain.
final List<OrderType> orderTypes;

View File

@@ -7,7 +7,6 @@ import 'one_time_order_state.dart';
/// BLoC for managing the multi-step one-time order creation form.
class OneTimeOrderBloc extends Bloc<OneTimeOrderEvent, OneTimeOrderState> {
OneTimeOrderBloc(this._createOneTimeOrderUseCase)
: super(OneTimeOrderState.initial()) {
on<OneTimeOrderDateChanged>(_onDateChanged);

View File

@@ -6,7 +6,6 @@ import 'rapid_order_state.dart';
/// BLoC for managing the rapid (urgent) order creation flow.
class RapidOrderBloc extends Bloc<RapidOrderEvent, RapidOrderState> {
RapidOrderBloc(this._createRapidOrderUseCase)
: super(
const RapidOrderInitial(
@@ -45,7 +44,7 @@ class RapidOrderBloc extends Bloc<RapidOrderEvent, RapidOrderState> {
// Simulate voice recognition
if (newListeningState) {
await Future.delayed(const Duration(seconds: 2));
await Future<void>.delayed(const Duration(seconds: 2));
if (state is RapidOrderInitial) {
emit(
(state as RapidOrderInitial).copyWith(

View File

@@ -8,7 +8,6 @@ abstract class RapidOrderEvent extends Equatable {
}
class RapidOrderMessageChanged extends RapidOrderEvent {
const RapidOrderMessageChanged(this.message);
final String message;
@@ -25,7 +24,6 @@ class RapidOrderSubmitted extends RapidOrderEvent {
}
class RapidOrderExampleSelected extends RapidOrderEvent {
const RapidOrderExampleSelected(this.example);
final String example;

View File

@@ -8,7 +8,6 @@ abstract class RapidOrderState extends Equatable {
}
class RapidOrderInitial extends RapidOrderState {
const RapidOrderInitial({
this.message = '',
this.isListening = false,
@@ -43,7 +42,6 @@ class RapidOrderSuccess extends RapidOrderState {
}
class RapidOrderFailure extends RapidOrderState {
const RapidOrderFailure(this.error);
final String error;

View File

@@ -1,39 +1,15 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_domain/krow_domain.dart';
import '../blocs/client_create_order_bloc.dart';
import '../blocs/client_create_order_event.dart';
import '../blocs/client_create_order_state.dart';
import '../navigation/client_create_order_navigator.dart';
import '../widgets/order_type_card.dart';
/// Helper to map keys to localized strings.
String _getTranslation({required String key}) {
if (key == 'client_create_order.types.rapid') {
return t.client_create_order.types.rapid;
} else if (key == 'client_create_order.types.rapid_desc') {
return t.client_create_order.types.rapid_desc;
} else if (key == 'client_create_order.types.one_time') {
return t.client_create_order.types.one_time;
} else if (key == 'client_create_order.types.one_time_desc') {
return t.client_create_order.types.one_time_desc;
} else if (key == 'client_create_order.types.recurring') {
return t.client_create_order.types.recurring;
} else if (key == 'client_create_order.types.recurring_desc') {
return t.client_create_order.types.recurring_desc;
} else if (key == 'client_create_order.types.permanent') {
return t.client_create_order.types.permanent;
} else if (key == 'client_create_order.types.permanent_desc') {
return t.client_create_order.types.permanent_desc;
}
return key;
}
import '../widgets/create_order/create_order_view.dart';
/// Main entry page for the client create order flow.
/// Allows the user to select the type of order they want to create.
///
/// This page initializes the [ClientCreateOrderBloc] and displays the [CreateOrderView].
/// It follows the Krow Clean Architecture by being a [StatelessWidget] and
/// delegating its state and UI to other components.
class ClientCreateOrderPage extends StatelessWidget {
/// Creates a [ClientCreateOrderPage].
const ClientCreateOrderPage({super.key});
@@ -43,191 +19,7 @@ class ClientCreateOrderPage extends StatelessWidget {
return BlocProvider<ClientCreateOrderBloc>(
create: (BuildContext context) => Modular.get<ClientCreateOrderBloc>()
..add(const ClientCreateOrderTypesRequested()),
child: const _CreateOrderView(),
child: const CreateOrderView(),
);
}
}
class _CreateOrderView extends StatelessWidget {
const _CreateOrderView();
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: UiColors.bgPrimary,
appBar: UiAppBar(
title: t.client_create_order.title,
onLeadingPressed: () => Modular.to.pop(),
),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space5,
vertical: UiConstants.space6,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(bottom: UiConstants.space6),
child: Text(
t.client_create_order.section_title,
style: UiTypography.footnote1m.copyWith(
color: UiColors.textDescription,
letterSpacing: 0.5,
),
),
),
Expanded(
child:
BlocBuilder<ClientCreateOrderBloc, ClientCreateOrderState>(
builder:
(BuildContext context, ClientCreateOrderState state) {
if (state is ClientCreateOrderLoadSuccess) {
return GridView.builder(
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: UiConstants.space4,
crossAxisSpacing: UiConstants.space4,
childAspectRatio: 1,
),
itemCount: state.orderTypes.length,
itemBuilder: (BuildContext context, int index) {
final OrderType type = state.orderTypes[index];
final _OrderTypeUiMetadata ui =
_OrderTypeUiMetadata.fromId(id: type.id);
return OrderTypeCard(
icon: ui.icon,
title: _getTranslation(key: type.titleKey),
description: _getTranslation(
key: type.descriptionKey,
),
backgroundColor: ui.backgroundColor,
borderColor: ui.borderColor,
iconBackgroundColor: ui.iconBackgroundColor,
iconColor: ui.iconColor,
textColor: ui.textColor,
descriptionColor: ui.descriptionColor,
onTap: () {
switch (type.id) {
case 'rapid':
Modular.to.pushRapidOrder();
break;
case 'one-time':
Modular.to.pushOneTimeOrder();
break;
case 'recurring':
Modular.to.pushRecurringOrder();
break;
case 'permanent':
Modular.to.pushPermanentOrder();
break;
}
},
);
},
);
}
return const Center(child: CircularProgressIndicator());
},
),
),
],
),
),
),
);
}
}
/// Metadata for styling order type cards based on their ID.
class _OrderTypeUiMetadata {
const _OrderTypeUiMetadata({
required this.icon,
required this.backgroundColor,
required this.borderColor,
required this.iconBackgroundColor,
required this.iconColor,
required this.textColor,
required this.descriptionColor,
});
/// Factory to get metadata based on order type ID.
factory _OrderTypeUiMetadata.fromId({required String id}) {
switch (id) {
case 'rapid':
return const _OrderTypeUiMetadata(
icon: UiIcons.zap,
backgroundColor: UiColors.tagPending,
borderColor: UiColors.separatorSpecial,
iconBackgroundColor: UiColors.textWarning,
iconColor: UiColors.white,
textColor: UiColors.textWarning,
descriptionColor: UiColors.textWarning,
);
case 'one-time':
return const _OrderTypeUiMetadata(
icon: UiIcons.calendar,
backgroundColor: UiColors.tagInProgress,
borderColor: UiColors.primaryInverse,
iconBackgroundColor: UiColors.primary,
iconColor: UiColors.white,
textColor: UiColors.textLink,
descriptionColor: UiColors.textLink,
);
case 'recurring':
return const _OrderTypeUiMetadata(
icon: UiIcons.rotateCcw,
backgroundColor: UiColors.tagSuccess,
borderColor: UiColors.switchActive,
iconBackgroundColor: UiColors.textSuccess,
iconColor: UiColors.white,
textColor: UiColors.textSuccess,
descriptionColor: UiColors.textSuccess,
);
case 'permanent':
return const _OrderTypeUiMetadata(
icon: UiIcons.briefcase,
backgroundColor: UiColors.tagRefunded,
borderColor: UiColors.primaryInverse,
iconBackgroundColor: UiColors.primary,
iconColor: UiColors.white,
textColor: UiColors.textLink,
descriptionColor: UiColors.textLink,
);
default:
return const _OrderTypeUiMetadata(
icon: UiIcons.help,
backgroundColor: UiColors.bgSecondary,
borderColor: UiColors.border,
iconBackgroundColor: UiColors.iconSecondary,
iconColor: UiColors.white,
textColor: UiColors.textPrimary,
descriptionColor: UiColors.textSecondary,
);
}
}
/// Icon for the order type.
final IconData icon;
/// Background color for the card.
final Color backgroundColor;
/// Border color for the card.
final Color borderColor;
/// Background color for the icon.
final Color iconBackgroundColor;
/// Color for the icon.
final Color iconColor;
/// Color for the title text.
final Color textColor;
/// Color for the description text.
final Color descriptionColor;
}

View File

@@ -1,20 +1,15 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_domain/krow_domain.dart';
import '../blocs/one_time_order_bloc.dart';
import '../blocs/one_time_order_event.dart';
import '../blocs/one_time_order_state.dart';
import '../widgets/one_time_order/one_time_order_date_picker.dart';
import '../widgets/one_time_order/one_time_order_location_input.dart';
import '../widgets/one_time_order/one_time_order_position_card.dart';
import '../widgets/one_time_order/one_time_order_section_header.dart';
import '../widgets/one_time_order/one_time_order_success_view.dart';
import '../widgets/one_time_order/one_time_order_view.dart';
/// Page for creating a one-time staffing order.
/// Users can specify the date, location, and multiple staff positions required.
///
/// This page initializes the [OneTimeOrderBloc] and displays the [OneTimeOrderView].
/// It follows the Krow Clean Architecture by being a [StatelessWidget] and
/// delegating its state and UI to other components.
class OneTimeOrderPage extends StatelessWidget {
/// Creates a [OneTimeOrderPage].
const OneTimeOrderPage({super.key});
@@ -23,186 +18,7 @@ class OneTimeOrderPage extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider<OneTimeOrderBloc>(
create: (BuildContext context) => Modular.get<OneTimeOrderBloc>(),
child: const _OneTimeOrderView(),
child: const OneTimeOrderView(),
);
}
}
class _OneTimeOrderView extends StatelessWidget {
const _OneTimeOrderView();
@override
Widget build(BuildContext context) {
final TranslationsClientCreateOrderOneTimeEn labels =
t.client_create_order.one_time;
return BlocBuilder<OneTimeOrderBloc, OneTimeOrderState>(
builder: (BuildContext context, OneTimeOrderState state) {
if (state.status == OneTimeOrderStatus.success) {
return OneTimeOrderSuccessView(
title: labels.success_title,
message: labels.success_message,
buttonLabel: 'Done',
onDone: () => Modular.to.pop(),
);
}
return Scaffold(
backgroundColor: UiColors.bgPrimary,
appBar: UiAppBar(
title: labels.title,
onLeadingPressed: () => Modular.to.pop(),
),
body: Stack(
children: <Widget>[
_OneTimeOrderForm(state: state),
if (state.status == OneTimeOrderStatus.loading)
const Center(child: CircularProgressIndicator()),
],
),
bottomNavigationBar: _BottomActionButton(
label: labels.create_order,
isLoading: state.status == OneTimeOrderStatus.loading,
onPressed: () => BlocProvider.of<OneTimeOrderBloc>(context)
.add(const OneTimeOrderSubmitted()),
),
);
},
);
}
}
class _OneTimeOrderForm extends StatelessWidget {
const _OneTimeOrderForm({required this.state});
final OneTimeOrderState state;
@override
Widget build(BuildContext context) {
final TranslationsClientCreateOrderOneTimeEn labels =
t.client_create_order.one_time;
return ListView(
padding: const EdgeInsets.all(UiConstants.space5),
children: <Widget>[
OneTimeOrderSectionHeader(title: labels.create_your_order),
const SizedBox(height: UiConstants.space4),
OneTimeOrderDatePicker(
label: labels.date_label,
value: state.date,
onChanged: (DateTime date) =>
BlocProvider.of<OneTimeOrderBloc>(context)
.add(OneTimeOrderDateChanged(date)),
),
const SizedBox(height: UiConstants.space4),
OneTimeOrderLocationInput(
label: labels.location_label,
value: state.location,
onChanged: (String location) =>
BlocProvider.of<OneTimeOrderBloc>(context)
.add(OneTimeOrderLocationChanged(location)),
),
const SizedBox(height: UiConstants.space6),
OneTimeOrderSectionHeader(
title: labels.positions_title,
actionLabel: labels.add_position,
onAction: () => BlocProvider.of<OneTimeOrderBloc>(context)
.add(const OneTimeOrderPositionAdded()),
),
const SizedBox(height: UiConstants.space4),
// Positions List
...state.positions
.asMap()
.entries
.map((MapEntry<int, OneTimeOrderPosition> entry) {
final int index = entry.key;
final OneTimeOrderPosition position = entry.value;
return Padding(
padding: const EdgeInsets.only(bottom: UiConstants.space4),
child: OneTimeOrderPositionCard(
index: index,
position: position,
isRemovable: state.positions.length > 1,
positionLabel: labels.positions_title,
roleLabel: labels.select_role,
workersLabel: labels.workers_label,
startLabel: labels.start_label,
endLabel: labels.end_label,
lunchLabel: labels.lunch_break_label,
onUpdated: (OneTimeOrderPosition updated) {
BlocProvider.of<OneTimeOrderBloc>(context).add(
OneTimeOrderPositionUpdated(index, updated),
);
},
onRemoved: () {
BlocProvider.of<OneTimeOrderBloc>(context)
.add(OneTimeOrderPositionRemoved(index));
},
),
);
}),
const SizedBox(height: 100), // Space for bottom button
],
);
}
}
class _BottomActionButton extends StatelessWidget {
const _BottomActionButton({
required this.label,
required this.onPressed,
this.isLoading = false,
});
final String label;
final VoidCallback onPressed;
final bool isLoading;
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(
left: UiConstants.space5,
right: UiConstants.space5,
top: UiConstants.space4,
bottom: MediaQuery.of(context).padding.bottom + UiConstants.space4,
),
decoration: BoxDecoration(
color: UiColors.white,
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, -4),
),
],
),
child: isLoading
? const UiButton(
buttonBuilder: _dummyBuilder,
child: SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
color: UiColors.primary, strokeWidth: 2),
),
)
: UiButton.primary(
text: label,
onPressed: onPressed,
size: UiButtonSize.large,
),
);
}
static Widget _dummyBuilder(
BuildContext context,
VoidCallback? onPressed,
ButtonStyle? style,
Widget child,
) {
return Center(child: child);
}
}

View File

@@ -1,18 +1,15 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:intl/intl.dart';
import '../blocs/rapid_order_bloc.dart';
import '../blocs/rapid_order_event.dart';
import '../blocs/rapid_order_state.dart';
import '../widgets/rapid_order/rapid_order_example_card.dart';
import '../widgets/rapid_order/rapid_order_header.dart';
import '../widgets/rapid_order/rapid_order_success_view.dart';
import '../widgets/rapid_order/rapid_order_view.dart';
/// Rapid Order Flow Page - Emergency staffing requests.
/// Features voice recognition simulation and quick example selection.
///
/// This page initializes the [RapidOrderBloc] and displays the [RapidOrderView].
/// It follows the Krow Clean Architecture by being a [StatelessWidget] and
/// delegating its state and UI to other components.
class RapidOrderPage extends StatelessWidget {
/// Creates a [RapidOrderPage].
const RapidOrderPage({super.key});
@@ -21,306 +18,7 @@ class RapidOrderPage extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider<RapidOrderBloc>(
create: (BuildContext context) => Modular.get<RapidOrderBloc>(),
child: const _RapidOrderView(),
);
}
}
class _RapidOrderView extends StatelessWidget {
const _RapidOrderView();
@override
Widget build(BuildContext context) {
final TranslationsClientCreateOrderRapidEn labels =
t.client_create_order.rapid;
return BlocBuilder<RapidOrderBloc, RapidOrderState>(
builder: (BuildContext context, RapidOrderState state) {
if (state is RapidOrderSuccess) {
return RapidOrderSuccessView(
title: labels.success_title,
message: labels.success_message,
buttonLabel: labels.back_to_orders,
onDone: () => Modular.to.pop(),
);
}
return const _RapidOrderForm();
},
);
}
}
class _RapidOrderForm extends StatefulWidget {
const _RapidOrderForm();
@override
State<_RapidOrderForm> createState() => _RapidOrderFormState();
}
class _RapidOrderFormState extends State<_RapidOrderForm> {
final TextEditingController _messageController = TextEditingController();
@override
void dispose() {
_messageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final TranslationsClientCreateOrderRapidEn labels =
t.client_create_order.rapid;
final DateTime now = DateTime.now();
final String dateStr = DateFormat('EEE, MMM dd, yyyy').format(now);
final String timeStr = DateFormat('h:mm a').format(now);
return BlocListener<RapidOrderBloc, RapidOrderState>(
listener: (BuildContext context, RapidOrderState state) {
if (state is RapidOrderInitial) {
if (_messageController.text != state.message) {
_messageController.text = state.message;
_messageController.selection = TextSelection.fromPosition(
TextPosition(offset: _messageController.text.length),
);
}
}
},
child: Scaffold(
backgroundColor: UiColors.bgPrimary,
body: Column(
children: <Widget>[
RapidOrderHeader(
title: labels.title,
subtitle: labels.subtitle,
date: dateStr,
time: timeStr,
onBack: () => Modular.to.pop(),
),
// Content
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(UiConstants.space5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
labels.tell_us,
style: UiTypography.headline3m.textPrimary,
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space2,
vertical: UiConstants.space1,
),
decoration: BoxDecoration(
color: UiColors.destructive,
borderRadius: UiConstants.radiusSm,
),
child: Text(
labels.urgent_badge,
style: UiTypography.footnote2b.copyWith(
color: UiColors.white,
),
),
),
],
),
const SizedBox(height: UiConstants.space4),
// Main Card
Container(
padding: const EdgeInsets.all(UiConstants.space6),
decoration: BoxDecoration(
color: UiColors.white,
borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border),
),
child: BlocBuilder<RapidOrderBloc, RapidOrderState>(
builder: (BuildContext context, RapidOrderState state) {
final RapidOrderInitial? initialState =
state is RapidOrderInitial ? state : null;
final bool isSubmitting =
state is RapidOrderSubmitting;
return Column(
children: <Widget>[
// Icon
_AnimatedZapIcon(),
const SizedBox(height: UiConstants.space4),
Text(
labels.need_staff,
style: UiTypography.headline2m.textPrimary,
),
const SizedBox(height: UiConstants.space2),
Text(
labels.type_or_speak,
textAlign: TextAlign.center,
style: UiTypography.body2r.textSecondary,
),
const SizedBox(height: UiConstants.space6),
// Examples
if (initialState != null)
...initialState.examples
.asMap()
.entries
.map((MapEntry<int, String> entry) {
final int index = entry.key;
final String example = entry.value;
final bool isHighlighted = index == 0;
return Padding(
padding: const EdgeInsets.only(
bottom: UiConstants.space2),
child: RapidOrderExampleCard(
example: example,
isHighlighted: isHighlighted,
label: labels.example,
onTap: () =>
BlocProvider.of<RapidOrderBloc>(
context)
.add(
RapidOrderExampleSelected(example),
),
),
);
}),
const SizedBox(height: UiConstants.space4),
// Input
TextField(
controller: _messageController,
maxLines: 4,
onChanged: (String value) {
BlocProvider.of<RapidOrderBloc>(context).add(
RapidOrderMessageChanged(value),
);
},
decoration: InputDecoration(
hintText: labels.hint,
hintStyle: UiTypography.body2r.copyWith(
color: UiColors.textPlaceholder,
),
border: OutlineInputBorder(
borderRadius: UiConstants.radiusLg,
borderSide: const BorderSide(
color: UiColors.border,
),
),
contentPadding:
const EdgeInsets.all(UiConstants.space4),
),
),
const SizedBox(height: UiConstants.space4),
// Actions
_RapidOrderActions(
labels: labels,
isSubmitting: isSubmitting,
isListening: initialState?.isListening ?? false,
isMessageEmpty: initialState != null &&
initialState.message.trim().isEmpty,
),
],
);
},
),
),
],
),
),
),
],
),
),
);
}
}
class _AnimatedZapIcon extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: 64,
height: 64,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: <Color>[
UiColors.destructive,
UiColors.destructive.withValues(alpha: 0.85),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: UiConstants.radiusLg,
boxShadow: <BoxShadow>[
BoxShadow(
color: UiColors.destructive.withValues(alpha: 0.3),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: const Icon(
UiIcons.zap,
color: UiColors.white,
size: 32,
),
);
}
}
class _RapidOrderActions extends StatelessWidget {
const _RapidOrderActions({
required this.labels,
required this.isSubmitting,
required this.isListening,
required this.isMessageEmpty,
});
final TranslationsClientCreateOrderRapidEn labels;
final bool isSubmitting;
final bool isListening;
final bool isMessageEmpty;
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Expanded(
child: UiButton.secondary(
text: isListening ? labels.listening : labels.speak,
leadingIcon: UiIcons.bell, // Placeholder for mic
onPressed: () => BlocProvider.of<RapidOrderBloc>(context).add(
const RapidOrderVoiceToggled(),
),
style: OutlinedButton.styleFrom(
backgroundColor: isListening
? UiColors.destructive.withValues(alpha: 0.05)
: null,
side: isListening
? const BorderSide(color: UiColors.destructive)
: null,
),
),
),
const SizedBox(width: UiConstants.space3),
Expanded(
child: UiButton.primary(
text: isSubmitting ? labels.sending : labels.send,
trailingIcon: UiIcons.arrowRight,
onPressed: isSubmitting || isMessageEmpty
? null
: () => BlocProvider.of<RapidOrderBloc>(context).add(
const RapidOrderSubmitted(),
),
),
),
],
child: const RapidOrderView(),
);
}
}

View File

@@ -0,0 +1,93 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/widgets.dart';
/// Metadata for styling order type cards based on their ID.
class OrderTypeUiMetadata {
/// Creates an [OrderTypeUiMetadata].
const OrderTypeUiMetadata({
required this.icon,
required this.backgroundColor,
required this.borderColor,
required this.iconBackgroundColor,
required this.iconColor,
required this.textColor,
required this.descriptionColor,
});
/// Factory to get metadata based on order type ID.
factory OrderTypeUiMetadata.fromId({required String id}) {
switch (id) {
case 'rapid':
return const OrderTypeUiMetadata(
icon: UiIcons.zap,
backgroundColor: UiColors.tagPending,
borderColor: UiColors.separatorSpecial,
iconBackgroundColor: UiColors.textWarning,
iconColor: UiColors.white,
textColor: UiColors.textWarning,
descriptionColor: UiColors.textWarning,
);
case 'one-time':
return const OrderTypeUiMetadata(
icon: UiIcons.calendar,
backgroundColor: UiColors.tagInProgress,
borderColor: UiColors.primaryInverse,
iconBackgroundColor: UiColors.primary,
iconColor: UiColors.white,
textColor: UiColors.textLink,
descriptionColor: UiColors.textLink,
);
case 'recurring':
return const OrderTypeUiMetadata(
icon: UiIcons.rotateCcw,
backgroundColor: UiColors.tagSuccess,
borderColor: UiColors.switchActive,
iconBackgroundColor: UiColors.textSuccess,
iconColor: UiColors.white,
textColor: UiColors.textSuccess,
descriptionColor: UiColors.textSuccess,
);
case 'permanent':
return const OrderTypeUiMetadata(
icon: UiIcons.briefcase,
backgroundColor: UiColors.tagRefunded,
borderColor: UiColors.primaryInverse,
iconBackgroundColor: UiColors.primary,
iconColor: UiColors.white,
textColor: UiColors.textLink,
descriptionColor: UiColors.textLink,
);
default:
return const OrderTypeUiMetadata(
icon: UiIcons.help,
backgroundColor: UiColors.bgSecondary,
borderColor: UiColors.border,
iconBackgroundColor: UiColors.iconSecondary,
iconColor: UiColors.white,
textColor: UiColors.textPrimary,
descriptionColor: UiColors.textSecondary,
);
}
}
/// Icon for the order type.
final IconData icon;
/// Background color for the card.
final Color backgroundColor;
/// Border color for the card.
final Color borderColor;
/// Background color for the icon.
final Color iconBackgroundColor;
/// Color for the icon.
final Color iconColor;
/// Color for the title text.
final Color textColor;
/// Color for the description text.
final Color descriptionColor;
}

View File

@@ -0,0 +1,129 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../blocs/client_create_order_bloc.dart';
import '../../blocs/client_create_order_state.dart';
import '../../navigation/client_create_order_navigator.dart';
import '../../ui_entities/order_type_ui_metadata.dart';
import '../order_type_card.dart';
/// Helper to map keys to localized strings.
String _getTranslation({required String key}) {
if (key == 'client_create_order.types.rapid') {
return t.client_create_order.types.rapid;
} else if (key == 'client_create_order.types.rapid_desc') {
return t.client_create_order.types.rapid_desc;
} else if (key == 'client_create_order.types.one_time') {
return t.client_create_order.types.one_time;
} else if (key == 'client_create_order.types.one_time_desc') {
return t.client_create_order.types.one_time_desc;
} else if (key == 'client_create_order.types.recurring') {
return t.client_create_order.types.recurring;
} else if (key == 'client_create_order.types.recurring_desc') {
return t.client_create_order.types.recurring_desc;
} else if (key == 'client_create_order.types.permanent') {
return t.client_create_order.types.permanent;
} else if (key == 'client_create_order.types.permanent_desc') {
return t.client_create_order.types.permanent_desc;
}
return key;
}
/// The main content of the Create Order page.
class CreateOrderView extends StatelessWidget {
/// Creates a [CreateOrderView].
const CreateOrderView({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: UiColors.bgPrimary,
appBar: UiAppBar(
title: t.client_create_order.title,
onLeadingPressed: () => Modular.to.pop(),
),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space5,
vertical: UiConstants.space6,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(bottom: UiConstants.space6),
child: Text(
t.client_create_order.section_title,
style: UiTypography.footnote1m.copyWith(
color: UiColors.textDescription,
letterSpacing: 0.5,
),
),
),
Expanded(
child:
BlocBuilder<ClientCreateOrderBloc, ClientCreateOrderState>(
builder:
(BuildContext context, ClientCreateOrderState state) {
if (state is ClientCreateOrderLoadSuccess) {
return GridView.builder(
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: UiConstants.space4,
crossAxisSpacing: UiConstants.space4,
childAspectRatio: 1,
),
itemCount: state.orderTypes.length,
itemBuilder: (BuildContext context, int index) {
final OrderType type = state.orderTypes[index];
final OrderTypeUiMetadata ui =
OrderTypeUiMetadata.fromId(id: type.id);
return OrderTypeCard(
icon: ui.icon,
title: _getTranslation(key: type.titleKey),
description: _getTranslation(
key: type.descriptionKey,
),
backgroundColor: ui.backgroundColor,
borderColor: ui.borderColor,
iconBackgroundColor: ui.iconBackgroundColor,
iconColor: ui.iconColor,
textColor: ui.textColor,
descriptionColor: ui.descriptionColor,
onTap: () {
switch (type.id) {
case 'rapid':
Modular.to.pushRapidOrder();
break;
case 'one-time':
Modular.to.pushOneTimeOrder();
break;
case 'recurring':
Modular.to.pushRecurringOrder();
break;
case 'permanent':
Modular.to.pushPermanentOrder();
break;
}
},
);
},
);
}
return const Center(child: CircularProgressIndicator());
},
),
),
],
),
),
),
);
}
}

View File

@@ -3,7 +3,16 @@ import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
/// A date picker field for the one-time order form.
class OneTimeOrderDatePicker extends StatelessWidget {
/// Matches the prototype input field style.
class OneTimeOrderDatePicker extends StatefulWidget {
/// Creates a [OneTimeOrderDatePicker].
const OneTimeOrderDatePicker({
required this.label,
required this.value,
required this.onChanged,
super.key,
});
/// The label text to display above the field.
final String label;
@@ -13,56 +22,53 @@ class OneTimeOrderDatePicker extends StatelessWidget {
/// Callback when a new date is selected.
final ValueChanged<DateTime> onChanged;
/// Creates a [OneTimeOrderDatePicker].
const OneTimeOrderDatePicker({
required this.label,
required this.value,
required this.onChanged,
super.key,
});
@override
State<OneTimeOrderDatePicker> createState() => _OneTimeOrderDatePickerState();
}
class _OneTimeOrderDatePickerState extends State<OneTimeOrderDatePicker> {
late final TextEditingController _controller;
@override
void initState() {
super.initState();
_controller = TextEditingController(
text: DateFormat('yyyy-MM-dd').format(widget.value),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
void didUpdateWidget(OneTimeOrderDatePicker oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.value != oldWidget.value) {
_controller.text = DateFormat('yyyy-MM-dd').format(widget.value);
}
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(label, style: UiTypography.footnote1m.textSecondary),
const SizedBox(height: UiConstants.space2),
InkWell(
onTap: () async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: value,
firstDate: DateTime.now(),
lastDate: DateTime.now().add(const Duration(days: 365)),
);
if (picked != null) {
onChanged(picked);
}
},
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space4,
vertical: UiConstants.space3 + 2,
),
decoration: BoxDecoration(
border: Border.all(color: UiColors.border),
borderRadius: UiConstants.radiusLg,
),
child: Row(
children: <Widget>[
const Icon(UiIcons.calendar,
size: 20, color: UiColors.iconSecondary),
const SizedBox(width: UiConstants.space3),
Text(
DateFormat('EEEE, MMM d, yyyy').format(value),
style: UiTypography.body1r.textPrimary,
),
],
),
),
),
],
return UiTextField(
label: widget.label,
controller: _controller,
readOnly: true,
prefixIcon: UiIcons.calendar,
onTap: () async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: widget.value,
firstDate: DateTime.now(),
lastDate: DateTime.now().add(const Duration(days: 365)),
);
if (picked != null) {
widget.onChanged(picked);
}
},
);
}
}

View File

@@ -0,0 +1,73 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// A header widget for the one-time order flow with a colored background.
class OneTimeOrderHeader extends StatelessWidget {
/// Creates a [OneTimeOrderHeader].
const OneTimeOrderHeader({
required this.title,
required this.subtitle,
required this.onBack,
super.key,
});
/// The title of the page.
final String title;
/// The subtitle or description.
final String subtitle;
/// Callback when the back button is pressed.
final VoidCallback onBack;
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(
top: MediaQuery.of(context).padding.top + UiConstants.space5,
bottom: UiConstants.space5,
left: UiConstants.space5,
right: UiConstants.space5,
),
color: UiColors.primary,
child: Row(
children: <Widget>[
GestureDetector(
onTap: onBack,
child: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: UiColors.white.withValues(alpha: 0.2),
borderRadius: UiConstants.radiusMd,
),
child: const Icon(
UiIcons.chevronLeft,
color: UiColors.white,
size: 24,
),
),
),
const SizedBox(width: UiConstants.space3),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
title,
style: UiTypography.headline3m.copyWith(
color: UiColors.white,
),
),
Text(
subtitle,
style: UiTypography.footnote2r.copyWith(
color: UiColors.white.withValues(alpha: 0.8),
),
),
],
),
],
),
);
}
}

View File

@@ -2,16 +2,8 @@ import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// A location input field for the one-time order form.
class OneTimeOrderLocationInput extends StatelessWidget {
/// The label text to display above the field.
final String label;
/// The current location value.
final String value;
/// Callback when the location text changes.
final ValueChanged<String> onChanged;
/// Matches the prototype input field style.
class OneTimeOrderLocationInput extends StatefulWidget {
/// Creates a [OneTimeOrderLocationInput].
const OneTimeOrderLocationInput({
required this.label,
@@ -20,14 +12,50 @@ class OneTimeOrderLocationInput extends StatelessWidget {
super.key,
});
/// The label text to display above the field.
final String label;
/// The current location value.
final String value;
/// Callback when the location value changes.
final ValueChanged<String> onChanged;
@override
State<OneTimeOrderLocationInput> createState() =>
_OneTimeOrderLocationInputState();
}
class _OneTimeOrderLocationInputState extends State<OneTimeOrderLocationInput> {
late final TextEditingController _controller;
@override
void initState() {
super.initState();
_controller = TextEditingController(text: widget.value);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
void didUpdateWidget(OneTimeOrderLocationInput oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.value != _controller.text) {
_controller.text = widget.value;
}
}
@override
Widget build(BuildContext context) {
return UiTextField(
label: label,
hintText: 'Select Branch/Location',
controller: TextEditingController(text: value)
..selection = TextSelection.collapsed(offset: value.length),
onChanged: onChanged,
label: widget.label,
controller: _controller,
onChanged: widget.onChanged,
hintText: 'Enter address',
prefixIcon: UiIcons.mapPin,
);
}

View File

@@ -1,9 +1,27 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:krow_domain/krow_domain.dart';
/// A card widget for editing a specific position in a one-time order.
/// Matches the prototype layout while using design system tokens.
class OneTimeOrderPositionCard extends StatelessWidget {
/// Creates a [OneTimeOrderPositionCard].
const OneTimeOrderPositionCard({
required this.index,
required this.position,
required this.isRemovable,
required this.onUpdated,
required this.onRemoved,
required this.positionLabel,
required this.roleLabel,
required this.workersLabel,
required this.startLabel,
required this.endLabel,
required this.lunchLabel,
super.key,
});
/// The index of the position in the list.
final int index;
@@ -37,22 +55,6 @@ class OneTimeOrderPositionCard extends StatelessWidget {
/// Label for the lunch break.
final String lunchLabel;
/// Creates a [OneTimeOrderPositionCard].
const OneTimeOrderPositionCard({
required this.index,
required this.position,
required this.isRemovable,
required this.onUpdated,
required this.onRemoved,
required this.positionLabel,
required this.roleLabel,
required this.workersLabel,
required this.startLabel,
required this.endLabel,
required this.lunchLabel,
super.key,
});
@override
Widget build(BuildContext context) {
return Container(
@@ -61,13 +63,6 @@ class OneTimeOrderPositionCard extends StatelessWidget {
color: UiColors.white,
borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border),
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -77,149 +72,281 @@ class OneTimeOrderPositionCard extends StatelessWidget {
children: <Widget>[
Text(
'$positionLabel #${index + 1}',
style: UiTypography.body1b.textPrimary,
style: UiTypography.footnote1m.textSecondary,
),
if (isRemovable)
IconButton(
icon: const Icon(UiIcons.delete,
size: 20, color: UiColors.destructive),
onPressed: onRemoved,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
visualDensity: VisualDensity.compact,
GestureDetector(
onTap: onRemoved,
child: Text(
t.client_create_order.one_time.remove,
style: UiTypography.footnote1m.copyWith(
color: UiColors.destructive,
),
),
),
],
),
const Divider(height: UiConstants.space6),
const SizedBox(height: UiConstants.space3),
// Role (Dropdown)
_LabelField(
label: roleLabel,
child: DropdownButtonFormField<String>(
value: position.role.isEmpty ? null : position.role,
items: <String>['Server', 'Bartender', 'Cook', 'Busser', 'Host']
.map((String role) => DropdownMenuItem<String>(
value: role,
child:
Text(role, style: UiTypography.body1r.textPrimary),
))
.toList(),
onChanged: (String? val) {
if (val != null) {
onUpdated(position.copyWith(role: val));
}
},
decoration: _inputDecoration(UiIcons.briefcase),
Container(
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space3),
height: 44,
decoration: BoxDecoration(
borderRadius: UiConstants.radiusMd,
border: Border.all(color: UiColors.border),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
hint:
Text(roleLabel, style: UiTypography.body2r.textPlaceholder),
value: position.role.isEmpty ? null : position.role,
icon: const Icon(
UiIcons.chevronDown,
size: 18,
color: UiColors.iconSecondary,
),
onChanged: (String? val) {
if (val != null) {
onUpdated(position.copyWith(role: val));
}
},
items: <String>[
'Server',
'Bartender',
'Cook',
'Busser',
'Host',
'Barista',
'Dishwasher',
'Event Staff'
].map((String role) {
// Mock rates for UI matching
final int rate = _getMockRate(role);
return DropdownMenuItem<String>(
value: role,
child: Text(
'$role - \$$rate/hr',
style: UiTypography.body2r.textPrimary,
),
);
}).toList(),
),
),
),
const SizedBox(height: UiConstants.space3),
// Start/End/Workers Row
Row(
children: <Widget>[
// Start Time
Expanded(
child: _buildTimeInput(
context: context,
label: startLabel,
value: position.startTime,
onTap: () async {
final TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
if (picked != null && context.mounted) {
onUpdated(
position.copyWith(startTime: picked.format(context)));
}
},
),
),
const SizedBox(width: UiConstants.space2),
// End Time
Expanded(
child: _buildTimeInput(
context: context,
label: endLabel,
value: position.endTime,
onTap: () async {
final TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
if (picked != null && context.mounted) {
onUpdated(
position.copyWith(endTime: picked.format(context)));
}
},
),
),
const SizedBox(width: UiConstants.space2),
// Workers Count
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
workersLabel,
style: UiTypography.footnote2r.textSecondary,
),
const SizedBox(height: UiConstants.space1),
Container(
height: 40,
decoration: BoxDecoration(
color: UiColors.bgSecondary,
borderRadius: UiConstants.radiusSm,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
GestureDetector(
onTap: () => onUpdated(position.copyWith(
count: (position.count > 1)
? position.count - 1
: 1)),
child: const Icon(UiIcons.minus, size: 12),
),
Text(
'${position.count}',
style: UiTypography.body2b.textPrimary,
),
GestureDetector(
onTap: () => onUpdated(
position.copyWith(count: position.count + 1)),
child: const Icon(UiIcons.add, size: 12),
),
],
),
),
],
),
),
],
),
const SizedBox(height: UiConstants.space4),
// Count (Counter)
_LabelField(
label: workersLabel,
child: Row(
// Optional Location Override
if (position.location == null)
GestureDetector(
onTap: () => onUpdated(position.copyWith(location: '')),
child: Row(
children: <Widget>[
const Icon(UiIcons.mapPin, size: 14, color: UiColors.primary),
const SizedBox(width: UiConstants.space1),
Text(
t.client_create_order.one_time.different_location,
style: UiTypography.footnote1m.copyWith(
color: UiColors.primary,
),
),
],
),
)
else
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_CounterButton(
icon: UiIcons.minus,
onPressed: position.count > 1
? () => onUpdated(
position.copyWith(count: position.count - 1))
: null,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Row(
children: <Widget>[
const Icon(UiIcons.mapPin,
size: 14, color: UiColors.iconSecondary),
const SizedBox(width: UiConstants.space1),
Text(
t.client_create_order.one_time
.different_location_title,
style: UiTypography.footnote1m.textSecondary,
),
],
),
GestureDetector(
onTap: () => onUpdated(position.copyWith(location: null)),
child: const Icon(
UiIcons.close,
size: 14,
color: UiColors.destructive,
),
),
],
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space4),
child: Text('${position.count}',
style: UiTypography.headline3m.textPrimary),
),
_CounterButton(
icon: UiIcons.add,
onPressed: () =>
onUpdated(position.copyWith(count: position.count + 1)),
const SizedBox(height: UiConstants.space2),
_PositionLocationInput(
value: position.location ?? '',
onChanged: (String val) =>
onUpdated(position.copyWith(location: val)),
hintText:
t.client_create_order.one_time.different_location_hint,
),
],
),
),
const SizedBox(height: UiConstants.space4),
// Start/End Time
Row(
children: <Widget>[
Expanded(
child: _LabelField(
label: startLabel,
child: InkWell(
onTap: () async {
final TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: const TimeOfDay(hour: 9, minute: 0),
);
if (picked != null) {
onUpdated(position.copyWith(
startTime: picked.format(context)));
}
},
child: Container(
padding: const EdgeInsets.all(UiConstants.space3),
decoration: _boxDecoration(),
child: Text(
position.startTime.isEmpty
? '--:--'
: position.startTime,
style: UiTypography.body1r.textPrimary,
),
),
),
),
),
const SizedBox(width: UiConstants.space3),
Expanded(
child: _LabelField(
label: endLabel,
child: InkWell(
onTap: () async {
final TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: const TimeOfDay(hour: 17, minute: 0),
);
if (picked != null) {
onUpdated(
position.copyWith(endTime: picked.format(context)));
}
},
child: Container(
padding: const EdgeInsets.all(UiConstants.space3),
decoration: _boxDecoration(),
child: Text(
position.endTime.isEmpty ? '--:--' : position.endTime,
style: UiTypography.body1r.textPrimary,
),
),
),
),
),
],
),
const SizedBox(height: UiConstants.space4),
const SizedBox(height: UiConstants.space3),
// Lunch Break
_LabelField(
label: lunchLabel,
child: DropdownButtonFormField<int>(
value: position.lunchBreak,
items: <int>[0, 30, 45, 60]
.map((int mins) => DropdownMenuItem<int>(
value: mins,
child: Text('${mins}m',
style: UiTypography.body1r.textPrimary),
))
.toList(),
onChanged: (int? val) {
if (val != null) {
onUpdated(position.copyWith(lunchBreak: val));
}
},
decoration: _inputDecoration(UiIcons.clock),
Text(
lunchLabel,
style: UiTypography.footnote2r.textSecondary,
),
const SizedBox(height: UiConstants.space1),
Container(
height: 44,
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space3),
decoration: BoxDecoration(
borderRadius: UiConstants.radiusMd,
border: Border.all(color: UiColors.border),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<int>(
isExpanded: true,
value: position.lunchBreak,
icon: const Icon(
UiIcons.chevronDown,
size: 18,
color: UiColors.iconSecondary,
),
onChanged: (int? val) {
if (val != null) {
onUpdated(position.copyWith(lunchBreak: val));
}
},
items: <DropdownMenuItem<int>>[
DropdownMenuItem<int>(
value: 0,
child: Text(t.client_create_order.one_time.no_break,
style: UiTypography.body2r.textPrimary),
),
DropdownMenuItem<int>(
value: 10,
child: Text(
'10 ${t.client_create_order.one_time.paid_break}',
style: UiTypography.body2r.textPrimary),
),
DropdownMenuItem<int>(
value: 15,
child: Text(
'15 ${t.client_create_order.one_time.paid_break}',
style: UiTypography.body2r.textPrimary),
),
DropdownMenuItem<int>(
value: 30,
child: Text(
'30 ${t.client_create_order.one_time.unpaid_break}',
style: UiTypography.body2r.textPrimary),
),
DropdownMenuItem<int>(
value: 45,
child: Text(
'45 ${t.client_create_order.one_time.unpaid_break}',
style: UiTypography.body2r.textPrimary),
),
DropdownMenuItem<int>(
value: 60,
child: Text(
'60 ${t.client_create_order.one_time.unpaid_break}',
style: UiTypography.body2r.textPrimary),
),
],
),
),
),
],
@@ -227,68 +354,89 @@ class OneTimeOrderPositionCard extends StatelessWidget {
);
}
InputDecoration _inputDecoration(IconData icon) => InputDecoration(
prefixIcon: Icon(icon, size: 18, color: UiColors.iconSecondary),
contentPadding:
const EdgeInsets.symmetric(horizontal: UiConstants.space3),
border: OutlineInputBorder(
borderRadius: UiConstants.radiusLg,
borderSide: const BorderSide(color: UiColors.border),
),
);
BoxDecoration _boxDecoration() => BoxDecoration(
border: Border.all(color: UiColors.border),
borderRadius: UiConstants.radiusLg,
);
}
class _LabelField extends StatelessWidget {
const _LabelField({required this.label, required this.child});
final String label;
final Widget child;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(label, style: UiTypography.footnote1m.textSecondary),
const SizedBox(height: UiConstants.space1),
child,
],
Widget _buildTimeInput({
required BuildContext context,
required String label,
required String value,
required VoidCallback onTap,
}) {
return UiTextField(
label: label,
controller: TextEditingController(text: value),
readOnly: true,
onTap: onTap,
hintText: '--:--',
);
}
int _getMockRate(String role) {
switch (role) {
case 'Server':
return 18;
case 'Bartender':
return 22;
case 'Cook':
return 20;
case 'Busser':
return 16;
case 'Host':
return 17;
case 'Barista':
return 16;
case 'Dishwasher':
return 15;
case 'Event Staff':
return 20;
default:
return 15;
}
}
}
class _CounterButton extends StatelessWidget {
const _CounterButton({required this.icon, this.onPressed});
final IconData icon;
final VoidCallback? onPressed;
class _PositionLocationInput extends StatefulWidget {
const _PositionLocationInput({
required this.value,
required this.hintText,
required this.onChanged,
});
final String value;
final String hintText;
final ValueChanged<String> onChanged;
@override
State<_PositionLocationInput> createState() => _PositionLocationInputState();
}
class _PositionLocationInputState extends State<_PositionLocationInput> {
late final TextEditingController _controller;
@override
void initState() {
super.initState();
_controller = TextEditingController(text: widget.value);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
void didUpdateWidget(_PositionLocationInput oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.value != _controller.text) {
_controller.text = widget.value;
}
}
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onPressed,
child: Container(
width: 32,
height: 32,
decoration: BoxDecoration(
border: Border.all(
color: onPressed != null
? UiColors.border
: UiColors.border.withOpacity(0.5)),
borderRadius: UiConstants.radiusLg,
color: onPressed != null ? UiColors.white : UiColors.background,
),
child: Icon(
icon,
size: 16,
color: onPressed != null
? UiColors.iconPrimary
: UiColors.iconSecondary.withOpacity(0.5),
),
),
return UiTextField(
controller: _controller,
onChanged: widget.onChanged,
hintText: widget.hintText,
);
}
}

View File

@@ -3,6 +3,14 @@ import 'package:flutter/material.dart';
/// A header widget for sections in the one-time order form.
class OneTimeOrderSectionHeader extends StatelessWidget {
/// Creates a [OneTimeOrderSectionHeader].
const OneTimeOrderSectionHeader({
required this.title,
this.actionLabel,
this.onAction,
super.key,
});
/// The title text for the section.
final String title;
@@ -12,14 +20,6 @@ class OneTimeOrderSectionHeader extends StatelessWidget {
/// Callback when the action button is tapped.
final VoidCallback? onAction;
/// Creates a [OneTimeOrderSectionHeader].
const OneTimeOrderSectionHeader({
required this.title,
this.actionLabel,
this.onAction,
super.key,
});
@override
Widget build(BuildContext context) {
return Row(
@@ -27,13 +27,14 @@ class OneTimeOrderSectionHeader extends StatelessWidget {
children: <Widget>[
Text(title, style: UiTypography.headline4m.textPrimary),
if (actionLabel != null && onAction != null)
TextButton.icon(
UiButton.text(
onPressed: onAction,
icon: const Icon(UiIcons.add, size: 16, color: UiColors.primary),
label: Text(actionLabel!, style: UiTypography.body2b.textPrimary),
leadingIcon: UiIcons.add,
text: actionLabel!,
iconSize: 16,
style: TextButton.styleFrom(
padding:
const EdgeInsets.symmetric(horizontal: UiConstants.space2),
minimumSize: const Size(0, 24),
maximumSize: const Size(0, 24),
),
),
],

View File

@@ -2,7 +2,17 @@ import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// A view to display when a one-time order has been successfully created.
/// Matches the prototype success view layout with a gradient background and centered card.
class OneTimeOrderSuccessView extends StatelessWidget {
/// Creates a [OneTimeOrderSuccessView].
const OneTimeOrderSuccessView({
required this.title,
required this.message,
required this.buttonLabel,
required this.onDone,
super.key,
});
/// The title of the success message.
final String title;
@@ -15,54 +25,78 @@ class OneTimeOrderSuccessView extends StatelessWidget {
/// Callback when the completion button is tapped.
final VoidCallback onDone;
/// Creates a [OneTimeOrderSuccessView].
const OneTimeOrderSuccessView({
required this.title,
required this.message,
required this.buttonLabel,
required this.onDone,
super.key,
});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: UiColors.white,
body: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space8),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 100,
height: 100,
decoration: const BoxDecoration(
color: UiColors.tagSuccess,
shape: BoxShape.circle,
),
child: const Icon(UiIcons.check,
size: 50, color: UiColors.textSuccess),
body: Container(
width: double.infinity,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: <Color>[UiColors.primary, UiColors.buttonPrimaryHover],
),
),
child: SafeArea(
child: Center(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 40),
padding: const EdgeInsets.all(UiConstants.space8),
decoration: BoxDecoration(
color: UiColors.white,
borderRadius: UiConstants.radiusLg * 1.5,
boxShadow: <BoxShadow>[
BoxShadow(
color: Colors.black.withValues(alpha: 0.2),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
const SizedBox(height: UiConstants.space8),
Text(
title,
style: UiTypography.headline2m.textPrimary,
textAlign: TextAlign.center,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
width: 64,
height: 64,
decoration: const BoxDecoration(
color: UiColors.accent,
shape: BoxShape.circle,
),
child: const Center(
child: Icon(
UiIcons.check,
color: UiColors.black,
size: 32,
),
),
),
const SizedBox(height: UiConstants.space6),
Text(
title,
style: UiTypography.headline2m.textPrimary,
textAlign: TextAlign.center,
),
const SizedBox(height: UiConstants.space3),
Text(
message,
textAlign: TextAlign.center,
style: UiTypography.body2r.textSecondary.copyWith(
height: 1.5,
),
),
const SizedBox(height: UiConstants.space8),
SizedBox(
width: double.infinity,
child: UiButton.primary(
text: buttonLabel,
onPressed: onDone,
size: UiButtonSize.large,
),
),
],
),
const SizedBox(height: UiConstants.space4),
Text(
message,
style: UiTypography.body1r.textSecondary,
textAlign: TextAlign.center,
),
const SizedBox(height: UiConstants.space10),
UiButton.primary(
text: buttonLabel,
onPressed: onDone,
size: UiButtonSize.large,
),
],
),
),
),
),

View File

@@ -0,0 +1,185 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../blocs/one_time_order_bloc.dart';
import '../../blocs/one_time_order_event.dart';
import '../../blocs/one_time_order_state.dart';
import 'one_time_order_date_picker.dart';
import 'one_time_order_header.dart';
import 'one_time_order_location_input.dart';
import 'one_time_order_position_card.dart';
import 'one_time_order_section_header.dart';
import 'one_time_order_success_view.dart';
/// The main content of the One-Time Order page.
class OneTimeOrderView extends StatelessWidget {
/// Creates a [OneTimeOrderView].
const OneTimeOrderView({super.key});
@override
Widget build(BuildContext context) {
final TranslationsClientCreateOrderOneTimeEn labels =
t.client_create_order.one_time;
return BlocBuilder<OneTimeOrderBloc, OneTimeOrderState>(
builder: (BuildContext context, OneTimeOrderState state) {
if (state.status == OneTimeOrderStatus.success) {
return OneTimeOrderSuccessView(
title: labels.success_title,
message: labels.success_message,
buttonLabel: labels.back_to_orders,
onDone: () => Modular.to.pop(),
);
}
return Scaffold(
backgroundColor: UiColors.bgPrimary,
body: Column(
children: <Widget>[
OneTimeOrderHeader(
title: labels.title,
subtitle: labels.subtitle,
onBack: () => Modular.to.pop(),
),
Expanded(
child: Stack(
children: <Widget>[
_OneTimeOrderForm(state: state),
if (state.status == OneTimeOrderStatus.loading)
const Center(child: CircularProgressIndicator()),
],
),
),
_BottomActionButton(
label: state.status == OneTimeOrderStatus.loading
? labels.creating
: labels.create_order,
isLoading: state.status == OneTimeOrderStatus.loading,
onPressed: () => BlocProvider.of<OneTimeOrderBloc>(context)
.add(const OneTimeOrderSubmitted()),
),
],
),
);
},
);
}
}
class _OneTimeOrderForm extends StatelessWidget {
const _OneTimeOrderForm({required this.state});
final OneTimeOrderState state;
@override
Widget build(BuildContext context) {
final TranslationsClientCreateOrderOneTimeEn labels =
t.client_create_order.one_time;
return ListView(
padding: const EdgeInsets.all(UiConstants.space5),
children: <Widget>[
Text(
labels.create_your_order,
style: UiTypography.headline3m.textPrimary,
),
const SizedBox(height: UiConstants.space4),
OneTimeOrderDatePicker(
label: labels.date_label,
value: state.date,
onChanged: (DateTime date) =>
BlocProvider.of<OneTimeOrderBloc>(context)
.add(OneTimeOrderDateChanged(date)),
),
const SizedBox(height: UiConstants.space4),
OneTimeOrderLocationInput(
label: labels.location_label,
value: state.location,
onChanged: (String location) =>
BlocProvider.of<OneTimeOrderBloc>(context)
.add(OneTimeOrderLocationChanged(location)),
),
const SizedBox(height: UiConstants.space6),
OneTimeOrderSectionHeader(
title: labels.positions_title,
actionLabel: labels.add_position,
onAction: () => BlocProvider.of<OneTimeOrderBloc>(context)
.add(const OneTimeOrderPositionAdded()),
),
const SizedBox(height: UiConstants.space3),
// Positions List
...state.positions
.asMap()
.entries
.map((MapEntry<int, OneTimeOrderPosition> entry) {
final int index = entry.key;
final OneTimeOrderPosition position = entry.value;
return Padding(
padding: const EdgeInsets.only(bottom: UiConstants.space3),
child: OneTimeOrderPositionCard(
index: index,
position: position,
isRemovable: state.positions.length > 1,
positionLabel: labels.positions_title,
roleLabel: labels.select_role,
workersLabel: labels.workers_label,
startLabel: labels.start_label,
endLabel: labels.end_label,
lunchLabel: labels.lunch_break_label,
onUpdated: (OneTimeOrderPosition updated) {
BlocProvider.of<OneTimeOrderBloc>(context).add(
OneTimeOrderPositionUpdated(index, updated),
);
},
onRemoved: () {
BlocProvider.of<OneTimeOrderBloc>(context)
.add(OneTimeOrderPositionRemoved(index));
},
),
);
}),
],
);
}
}
class _BottomActionButton extends StatelessWidget {
const _BottomActionButton({
required this.label,
required this.onPressed,
this.isLoading = false,
});
final String label;
final VoidCallback onPressed;
final bool isLoading;
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(
left: UiConstants.space5,
right: UiConstants.space5,
top: UiConstants.space5,
bottom: MediaQuery.of(context).padding.bottom + UiConstants.space5,
),
decoration: const BoxDecoration(
color: UiColors.white,
border: Border(top: BorderSide(color: UiColors.border)),
),
child: SizedBox(
width: double.infinity,
child: UiButton.primary(
text: label,
onPressed: isLoading ? null : onPressed,
size: UiButtonSize.large,
),
),
);
}
}

View File

@@ -3,6 +3,21 @@ import 'package:flutter/material.dart';
/// A card widget representing an order type in the creation flow.
class OrderTypeCard extends StatelessWidget {
/// Creates an [OrderTypeCard].
const OrderTypeCard({
required this.icon,
required this.title,
required this.description,
required this.backgroundColor,
required this.borderColor,
required this.iconBackgroundColor,
required this.iconColor,
required this.textColor,
required this.descriptionColor,
required this.onTap,
super.key,
});
/// Icon to display at the top of the card.
final IconData icon;
@@ -33,21 +48,6 @@ class OrderTypeCard extends StatelessWidget {
/// Callback when the card is tapped.
final VoidCallback onTap;
/// Creates an [OrderTypeCard].
const OrderTypeCard({
required this.icon,
required this.title,
required this.description,
required this.backgroundColor,
required this.borderColor,
required this.iconBackgroundColor,
required this.iconColor,
required this.textColor,
required this.descriptionColor,
required this.onTap,
super.key,
});
@override
Widget build(BuildContext context) {
return GestureDetector(

View File

@@ -3,6 +3,15 @@ import 'package:flutter/material.dart';
/// A card displaying an example message for a rapid order.
class RapidOrderExampleCard extends StatelessWidget {
/// Creates a [RapidOrderExampleCard].
const RapidOrderExampleCard({
required this.example,
required this.isHighlighted,
required this.label,
required this.onTap,
super.key,
});
/// The example text.
final String example;
@@ -15,15 +24,6 @@ class RapidOrderExampleCard extends StatelessWidget {
/// Callback when the card is tapped.
final VoidCallback onTap;
/// Creates a [RapidOrderExampleCard].
const RapidOrderExampleCard({
required this.example,
required this.isHighlighted,
required this.label,
required this.onTap,
super.key,
});
@override
Widget build(BuildContext context) {
return GestureDetector(

View File

@@ -3,6 +3,16 @@ import 'package:flutter/material.dart';
/// A header widget for the rapid order flow with a gradient background.
class RapidOrderHeader extends StatelessWidget {
/// Creates a [RapidOrderHeader].
const RapidOrderHeader({
required this.title,
required this.subtitle,
required this.date,
required this.time,
required this.onBack,
super.key,
});
/// The title of the page.
final String title;
@@ -18,16 +28,6 @@ class RapidOrderHeader extends StatelessWidget {
/// Callback when the back button is pressed.
final VoidCallback onBack;
/// Creates a [RapidOrderHeader].
const RapidOrderHeader({
required this.title,
required this.subtitle,
required this.date,
required this.time,
required this.onBack,
super.key,
});
@override
Widget build(BuildContext context) {
return Container(

View File

@@ -3,6 +3,15 @@ import 'package:flutter/material.dart';
/// A view to display when a rapid order has been successfully created.
class RapidOrderSuccessView extends StatelessWidget {
/// Creates a [RapidOrderSuccessView].
const RapidOrderSuccessView({
required this.title,
required this.message,
required this.buttonLabel,
required this.onDone,
super.key,
});
/// The title of the success message.
final String title;
@@ -15,15 +24,6 @@ class RapidOrderSuccessView extends StatelessWidget {
/// Callback when the completion button is tapped.
final VoidCallback onDone;
/// Creates a [RapidOrderSuccessView].
const RapidOrderSuccessView({
required this.title,
required this.message,
required this.buttonLabel,
required this.onDone,
super.key,
});
@override
Widget build(BuildContext context) {
return Scaffold(

View File

@@ -0,0 +1,302 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:intl/intl.dart';
import '../../blocs/rapid_order_bloc.dart';
import '../../blocs/rapid_order_event.dart';
import '../../blocs/rapid_order_state.dart';
import 'rapid_order_example_card.dart';
import 'rapid_order_header.dart';
import 'rapid_order_success_view.dart';
/// The main content of the Rapid Order page.
class RapidOrderView extends StatelessWidget {
/// Creates a [RapidOrderView].
const RapidOrderView({super.key});
@override
Widget build(BuildContext context) {
final TranslationsClientCreateOrderRapidEn labels =
t.client_create_order.rapid;
return BlocBuilder<RapidOrderBloc, RapidOrderState>(
builder: (BuildContext context, RapidOrderState state) {
if (state is RapidOrderSuccess) {
return RapidOrderSuccessView(
title: labels.success_title,
message: labels.success_message,
buttonLabel: labels.back_to_orders,
onDone: () => Modular.to.pop(),
);
}
return const _RapidOrderForm();
},
);
}
}
class _RapidOrderForm extends StatefulWidget {
const _RapidOrderForm();
@override
State<_RapidOrderForm> createState() => _RapidOrderFormState();
}
class _RapidOrderFormState extends State<_RapidOrderForm> {
final TextEditingController _messageController = TextEditingController();
@override
void dispose() {
_messageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final TranslationsClientCreateOrderRapidEn labels =
t.client_create_order.rapid;
final DateTime now = DateTime.now();
final String dateStr = DateFormat('EEE, MMM dd, yyyy').format(now);
final String timeStr = DateFormat('h:mm a').format(now);
return BlocListener<RapidOrderBloc, RapidOrderState>(
listener: (BuildContext context, RapidOrderState state) {
if (state is RapidOrderInitial) {
if (_messageController.text != state.message) {
_messageController.text = state.message;
_messageController.selection = TextSelection.fromPosition(
TextPosition(offset: _messageController.text.length),
);
}
}
},
child: Scaffold(
backgroundColor: UiColors.bgPrimary,
body: Column(
children: <Widget>[
RapidOrderHeader(
title: labels.title,
subtitle: labels.subtitle,
date: dateStr,
time: timeStr,
onBack: () => Modular.to.pop(),
),
// Content
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(UiConstants.space5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
labels.tell_us,
style: UiTypography.headline3m.textPrimary,
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space2,
vertical: UiConstants.space1,
),
decoration: BoxDecoration(
color: UiColors.destructive,
borderRadius: UiConstants.radiusSm,
),
child: Text(
labels.urgent_badge,
style: UiTypography.footnote2b.copyWith(
color: UiColors.white,
),
),
),
],
),
const SizedBox(height: UiConstants.space4),
// Main Card
Container(
padding: const EdgeInsets.all(UiConstants.space6),
decoration: BoxDecoration(
color: UiColors.white,
borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border),
),
child: BlocBuilder<RapidOrderBloc, RapidOrderState>(
builder: (BuildContext context, RapidOrderState state) {
final RapidOrderInitial? initialState =
state is RapidOrderInitial ? state : null;
final bool isSubmitting =
state is RapidOrderSubmitting;
return Column(
children: <Widget>[
// Icon
const _AnimatedZapIcon(),
const SizedBox(height: UiConstants.space4),
Text(
labels.need_staff,
style: UiTypography.headline2m.textPrimary,
),
const SizedBox(height: UiConstants.space2),
Text(
labels.type_or_speak,
textAlign: TextAlign.center,
style: UiTypography.body2r.textSecondary,
),
const SizedBox(height: UiConstants.space6),
// Examples
if (initialState != null)
...initialState.examples
.asMap()
.entries
.map((MapEntry<int, String> entry) {
final int index = entry.key;
final String example = entry.value;
final bool isHighlighted = index == 0;
return Padding(
padding: const EdgeInsets.only(
bottom: UiConstants.space2),
child: RapidOrderExampleCard(
example: example,
isHighlighted: isHighlighted,
label: labels.example,
onTap: () =>
BlocProvider.of<RapidOrderBloc>(
context)
.add(
RapidOrderExampleSelected(example),
),
),
);
}),
const SizedBox(height: UiConstants.space4),
// Input
UiTextField(
controller: _messageController,
maxLines: 4,
onChanged: (String value) {
BlocProvider.of<RapidOrderBloc>(context).add(
RapidOrderMessageChanged(value),
);
},
hintText: labels.hint,
),
const SizedBox(height: UiConstants.space4),
// Actions
_RapidOrderActions(
labels: labels,
isSubmitting: isSubmitting,
isListening: initialState?.isListening ?? false,
isMessageEmpty: initialState != null &&
initialState.message.trim().isEmpty,
),
],
);
},
),
),
],
),
),
),
],
),
),
);
}
}
class _AnimatedZapIcon extends StatelessWidget {
const _AnimatedZapIcon();
@override
Widget build(BuildContext context) {
return Container(
width: 64,
height: 64,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: <Color>[
UiColors.destructive,
UiColors.destructive.withValues(alpha: 0.85),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: UiConstants.radiusLg,
boxShadow: <BoxShadow>[
BoxShadow(
color: UiColors.destructive.withValues(alpha: 0.3),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: const Icon(
UiIcons.zap,
color: UiColors.white,
size: 32,
),
);
}
}
class _RapidOrderActions extends StatelessWidget {
const _RapidOrderActions({
required this.labels,
required this.isSubmitting,
required this.isListening,
required this.isMessageEmpty,
});
final TranslationsClientCreateOrderRapidEn labels;
final bool isSubmitting;
final bool isListening;
final bool isMessageEmpty;
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Expanded(
child: UiButton.secondary(
text: isListening ? labels.listening : labels.speak,
leadingIcon: UiIcons.bell, // Placeholder for mic
onPressed: () => BlocProvider.of<RapidOrderBloc>(context).add(
const RapidOrderVoiceToggled(),
),
style: OutlinedButton.styleFrom(
backgroundColor: isListening
? UiColors.destructive.withValues(alpha: 0.05)
: null,
side: isListening
? const BorderSide(color: UiColors.destructive)
: null,
),
),
),
const SizedBox(width: UiConstants.space3),
Expanded(
child: UiButton.primary(
text: isSubmitting ? labels.sending : labels.send,
trailingIcon: UiIcons.arrowRight,
onPressed: isSubmitting || isMessageEmpty
? null
: () => BlocProvider.of<RapidOrderBloc>(context).add(
const RapidOrderSubmitted(),
),
),
),
],
);
}
}

View File

@@ -1,858 +0,0 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_fe_analyzer_shared:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d
url: "https://pub.dev"
source: hosted
version: "91.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08
url: "https://pub.dev"
source: hosted
version: "8.4.1"
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
source: hosted
version: "2.7.0"
async:
dependency: transitive
description:
name: async
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.dev"
source: hosted
version: "2.13.0"
auto_injector:
dependency: transitive
description:
name: auto_injector
sha256: "1fc2624898e92485122eb2b1698dd42511d7ff6574f84a3a8606fc4549a1e8f8"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
bloc:
dependency: transitive
description:
name: bloc
sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
url: "https://pub.dev"
source: hosted
version: "8.1.4"
bloc_test:
dependency: "direct dev"
description:
name: bloc_test
sha256: "165a6ec950d9252ebe36dc5335f2e6eb13055f33d56db0eeb7642768849b43d2"
url: "https://pub.dev"
source: hosted
version: "9.1.7"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
characters:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev"
source: hosted
version: "1.4.0"
cli_config:
dependency: transitive
description:
name: cli_config
sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec
url: "https://pub.dev"
source: hosted
version: "0.2.0"
clock:
dependency: transitive
description:
name: clock
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.2"
code_assets:
dependency: transitive
description:
name: code_assets
sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.1"
convert:
dependency: transitive
description:
name: convert
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
url: "https://pub.dev"
source: hosted
version: "3.1.2"
core_localization:
dependency: "direct main"
description:
path: "../../../core_localization"
relative: true
source: path
version: "0.0.1"
coverage:
dependency: transitive
description:
name: coverage
sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d"
url: "https://pub.dev"
source: hosted
version: "1.15.0"
crypto:
dependency: transitive
description:
name: crypto
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
url: "https://pub.dev"
source: hosted
version: "3.0.7"
csv:
dependency: transitive
description:
name: csv
sha256: c6aa2679b2a18cb57652920f674488d89712efaf4d3fdf2e537215b35fc19d6c
url: "https://pub.dev"
source: hosted
version: "6.0.0"
design_system:
dependency: "direct main"
description:
path: "../../../design_system"
relative: true
source: path
version: "0.0.1"
diff_match_patch:
dependency: transitive
description:
name: diff_match_patch
sha256: "2efc9e6e8f449d0abe15be240e2c2a3bcd977c8d126cfd70598aee60af35c0a4"
url: "https://pub.dev"
source: hosted
version: "0.4.1"
equatable:
dependency: "direct main"
description:
name: equatable
sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b"
url: "https://pub.dev"
source: hosted
version: "2.0.8"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
ffi:
dependency: transitive
description:
name: ffi
sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c
url: "https://pub.dev"
source: hosted
version: "2.1.5"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.1"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
source: hosted
version: "1.1.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_bloc:
dependency: "direct main"
description:
name: flutter_bloc
sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a
url: "https://pub.dev"
source: hosted
version: "8.1.6"
flutter_localizations:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
flutter_modular:
dependency: "direct main"
description:
name: flutter_modular
sha256: "33a63d9fe61429d12b3dfa04795ed890f17d179d3d38e988ba7969651fcd5586"
url: "https://pub.dev"
source: hosted
version: "6.4.1"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
font_awesome_flutter:
dependency: transitive
description:
name: font_awesome_flutter
sha256: b9011df3a1fa02993630b8fb83526368cf2206a711259830325bab2f1d2a4eb0
url: "https://pub.dev"
source: hosted
version: "10.12.0"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
url: "https://pub.dev"
source: hosted
version: "4.0.0"
glob:
dependency: transitive
description:
name: glob
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
url: "https://pub.dev"
source: hosted
version: "2.1.3"
google_fonts:
dependency: transitive
description:
name: google_fonts
sha256: "6996212014b996eaa17074e02b1b925b212f5e053832d9048970dc27255a8fb3"
url: "https://pub.dev"
source: hosted
version: "7.1.0"
hooks:
dependency: transitive
description:
name: hooks
sha256: "5d309c86e7ce34cd8e37aa71cb30cb652d3829b900ab145e4d9da564b31d59f7"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
http:
dependency: transitive
description:
name: http
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
url: "https://pub.dev"
source: hosted
version: "1.6.0"
http_multi_server:
dependency: transitive
description:
name: http_multi_server
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
url: "https://pub.dev"
source: hosted
version: "3.2.2"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.1.2"
intl:
dependency: "direct main"
description:
name: intl
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
url: "https://pub.dev"
source: hosted
version: "0.20.2"
io:
dependency: transitive
description:
name: io
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
url: "https://pub.dev"
source: hosted
version: "1.0.5"
js:
dependency: transitive
description:
name: js
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
url: "https://pub.dev"
source: hosted
version: "0.7.2"
krow_core:
dependency: "direct main"
description:
path: "../../../core"
relative: true
source: path
version: "0.0.1"
krow_data_connect:
dependency: "direct main"
description:
path: "../../../data_connect"
relative: true
source: path
version: "0.0.1"
krow_domain:
dependency: "direct main"
description:
path: "../../../domain"
relative: true
source: path
version: "0.0.1"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev"
source: hosted
version: "11.0.2"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
logging:
dependency: transitive
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.3.0"
lucide_icons:
dependency: transitive
description:
name: lucide_icons
sha256: ad24d0fd65707e48add30bebada7d90bff2a1bba0a72d6e9b19d44246b0e83c4
url: "https://pub.dev"
source: hosted
version: "0.257.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev"
source: hosted
version: "0.12.17"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
version: "1.17.0"
mime:
dependency: transitive
description:
name: mime
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
mocktail:
dependency: transitive
description:
name: mocktail
sha256: "890df3f9688106f25755f26b1c60589a92b3ab91a22b8b224947ad041bf172d8"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
modular_core:
dependency: transitive
description:
name: modular_core
sha256: "1db0420a0dfb8a2c6dca846e7cbaa4ffeb778e247916dbcb27fb25aa566e5436"
url: "https://pub.dev"
source: hosted
version: "3.4.1"
native_toolchain_c:
dependency: transitive
description:
name: native_toolchain_c
sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac"
url: "https://pub.dev"
source: hosted
version: "0.17.4"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
node_preamble:
dependency: transitive
description:
name: node_preamble
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
objective_c:
dependency: transitive
description:
name: objective_c
sha256: "9922a1ad59ac5afb154cc948aa6ded01987a75003651d0a2866afc23f4da624e"
url: "https://pub.dev"
source: hosted
version: "9.2.3"
package_config:
dependency: transitive
description:
name: package_config
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
url: "https://pub.dev"
source: hosted
version: "2.2.0"
path:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
path_provider:
dependency: transitive
description:
name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
url: "https://pub.dev"
source: hosted
version: "2.1.5"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e
url: "https://pub.dev"
source: hosted
version: "2.2.22"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
url: "https://pub.dev"
source: hosted
version: "2.6.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
pool:
dependency: transitive
description:
name: pool
sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
url: "https://pub.dev"
source: hosted
version: "1.5.2"
provider:
dependency: transitive
description:
name: provider
sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
url: "https://pub.dev"
source: hosted
version: "6.1.5+1"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
result_dart:
dependency: transitive
description:
name: result_dart
sha256: "0666b21fbdf697b3bdd9986348a380aa204b3ebe7c146d8e4cdaa7ce735e6054"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
shared_preferences:
dependency: transitive
description:
name: shared_preferences
sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc"
url: "https://pub.dev"
source: hosted
version: "2.4.18"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
url: "https://pub.dev"
source: hosted
version: "2.5.6"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
url: "https://pub.dev"
source: hosted
version: "2.4.3"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shelf:
dependency: transitive
description:
name: shelf
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
url: "https://pub.dev"
source: hosted
version: "1.4.2"
shelf_packages_handler:
dependency: transitive
description:
name: shelf_packages_handler
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
shelf_static:
dependency: transitive
description:
name: shelf_static
sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
url: "https://pub.dev"
source: hosted
version: "1.1.3"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
slang:
dependency: transitive
description:
name: slang
sha256: "13e3b6f07adc51ab751e7889647774d294cbce7a3382f81d9e5029acfe9c37b2"
url: "https://pub.dev"
source: hosted
version: "4.12.0"
slang_flutter:
dependency: transitive
description:
name: slang_flutter
sha256: "0a4545cca5404d6b7487cf61cf1fe56c52daeb08de56a7574ee8381fbad035a0"
url: "https://pub.dev"
source: hosted
version: "4.12.0"
source_map_stack_trace:
dependency: transitive
description:
name: source_map_stack_trace
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
url: "https://pub.dev"
source: hosted
version: "2.1.2"
source_maps:
dependency: transitive
description:
name: source_maps
sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
url: "https://pub.dev"
source: hosted
version: "0.10.13"
source_span:
dependency: transitive
description:
name: source_span
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
url: "https://pub.dev"
source: hosted
version: "1.10.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
test:
dependency: transitive
description:
name: test
sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7"
url: "https://pub.dev"
source: hosted
version: "1.26.3"
test_api:
dependency: transitive
description:
name: test_api
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
url: "https://pub.dev"
source: hosted
version: "0.7.7"
test_core:
dependency: transitive
description:
name: test_core
sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0"
url: "https://pub.dev"
source: hosted
version: "0.6.12"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.4.0"
uuid:
dependency: transitive
description:
name: uuid
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
url: "https://pub.dev"
source: hosted
version: "4.5.2"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
source: hosted
version: "2.2.0"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
url: "https://pub.dev"
source: hosted
version: "15.0.2"
watcher:
dependency: transitive
description:
name: watcher
sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
web:
dependency: transitive
description:
name: web
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
web_socket:
dependency: transitive
description:
name: web_socket
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
url: "https://pub.dev"
source: hosted
version: "3.0.3"
webkit_inspection_protocol:
dependency: transitive
description:
name: webkit_inspection_protocol
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
yaml:
dependency: transitive
description:
name: yaml
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
url: "https://pub.dev"
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.10.7 <4.0.0"
flutter: ">=3.38.4"

View File

@@ -2,9 +2,10 @@ name: client_create_order
description: Client create order feature
version: 0.0.1
publish_to: none
resolution: workspace
environment:
sdk: ">=3.0.0 <4.0.0"
sdk: ">=3.10.0 <4.0.0"
dependencies:
flutter:

View File

@@ -185,13 +185,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.4.2"
client_create_order:
dependency: transitive
description:
path: "packages/features/client/create_order"
relative: true
source: path
version: "0.0.1"
clock:
dependency: transitive
description:

View File

@@ -14,6 +14,8 @@ workspace:
- packages/features/client/home
- packages/features/client/settings
- packages/features/client/hubs
- packages/features/client/create_order
- packages/features/client/client_main
- apps/staff
- apps/client
- apps/design_system_viewer