Merge branch 'dev' into feature/centralized-data-error-handling and resolve conflicts

This commit is contained in:
2026-02-11 12:34:29 +05:30
158 changed files with 10945 additions and 5478 deletions

1
.gitignore vendored
View File

@@ -142,6 +142,7 @@ build/
.firebase/ .firebase/
dataconnect/.dataconnect/ dataconnect/.dataconnect/
backend/dataconnect/.dataconnect/ backend/dataconnect/.dataconnect/
backend/dataconnect/dataconnect.yaml
# Debug Logs (Recursive) # Debug Logs (Recursive)
**/firebase-debug.log **/firebase-debug.log

View File

@@ -55,22 +55,25 @@ help:
@echo "" @echo ""
@echo " 🗄️ DATA CONNECT & BACKEND (backend/dataconnect)" @echo " 🗄️ DATA CONNECT & BACKEND (backend/dataconnect)"
@echo " ────────────────────────────────────────────────────────────────────" @echo " ────────────────────────────────────────────────────────────────────"
@echo " make dataconnect-init Initialize Firebase Data Connect" @echo " make dataconnect-init Initialize Firebase Data Connect"
@echo " make dataconnect-deploy Deploy Data Connect schemas to Cloud SQL" @echo " make dataconnect-deploy [ENV=dev] Deploy Data Connect schemas (dev/staging)"
@echo " make dataconnect-sql-migrate Apply pending SQL migrations" @echo " make dataconnect-sql-migrate [ENV=dev] Apply pending SQL migrations"
@echo " make dataconnect-generate-sdk Regenerate Data Connect client SDK" @echo " make dataconnect-generate-sdk [ENV=dev] Regenerate Data Connect client SDK"
@echo " make dataconnect-sync Full sync: deploy + migrate + generate SDK" @echo " make dataconnect-sync [ENV=dev] Full sync: deploy + migrate + generate SDK"
@echo " make dataconnect-seed Seed database with test data" @echo " make dataconnect-seed [ENV=dev] Seed database with test data"
@echo " make dataconnect-clean Delete all data from Data Connect" @echo " make dataconnect-clean [ENV=dev] Delete all data from Data Connect"
@echo " make dataconnect-test Test Data Connect deployment (dry-run)" @echo " make dataconnect-test [ENV=dev] Test Data Connect deployment (dry-run)"
@echo " make dataconnect-enable-apis Enable required GCP APIs" @echo " make dataconnect-enable-apis [ENV=dev] Enable required GCP APIs"
@echo " make dataconnect-bootstrap-db ONE-TIME: Full Cloud SQL + Data Connect setup" @echo " make dataconnect-bootstrap-db ONE-TIME: Full Cloud SQL + Data Connect setup (dev)"
@echo " make dataconnect-bootstrap-validation-database ONE-TIME: Setup validation database"
@echo " make dataconnect-backup-dev-to-validation Backup dev database to validation"
@echo "" @echo ""
@echo " 🛠️ DEVELOPMENT TOOLS" @echo " 🛠️ DEVELOPMENT TOOLS"
@echo " ────────────────────────────────────────────────────────────────────" @echo " ────────────────────────────────────────────────────────────────────"
@echo " make install-melos Install Melos globally (for mobile dev)" @echo " make install-melos Install Melos globally (for mobile dev)"
@echo " make install-git-hooks Install git pre-push hook (protect main/dev)" @echo " make install-git-hooks Install git pre-push hook (protect main/dev)"
@echo " make sync-prototypes Sync prototypes from client-krow-poc repo" @echo " make sync-prototypes Sync prototypes from client-krow-poc repo"
@echo " make clean-branches Delete local branches (keeps main/dev/demo/**/protected)"
@echo "" @echo ""
@echo " HELP" @echo " HELP"
@echo " ────────────────────────────────────────────────────────────────────" @echo " ────────────────────────────────────────────────────────────────────"
@@ -79,4 +82,5 @@ help:
@echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" @echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
@echo " 💡 Tip: Run 'make mobile-install' first for mobile development" @echo " 💡 Tip: Run 'make mobile-install' first for mobile development"
@echo " 💡 Tip: Use 'make dataconnect-sync' after schema changes" @echo " 💡 Tip: Use 'make dataconnect-sync' after schema changes"
@echo " 💡 Tip: Default ENV=dev, use ENV=staging for staging environment"
@echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" @echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

5
PROTECTED_BRANCHES.md Normal file
View File

@@ -0,0 +1,5 @@
# Protected Branches
- `main`
- `dev`
- `demo/**`

View File

@@ -5,16 +5,14 @@
* Break down large widgets into **smaller, reusable widgets** * Break down large widgets into **smaller, reusable widgets**
* Add **doc comments** where necessary to improve readability and maintainability * Add **doc comments** where necessary to improve readability and maintainability
* **Remove overly complicated or unnecessary logic** introduced by AI and simplify where possible * **Remove overly complicated or unnecessary logic** introduced by AI and simplify where possible
* **Adhere to the design system** and remove all **hard-coded colors and typography**, using shared tokens instead
* Improvement points * Improvement points
- apps/mobile/packages/features/client/client_coverage/lib/src/data/repositories_impl/coverage_repository_impl.dart - apps/mobile/packages/features/client/client_coverage/lib/src/data/repositories_impl/coverage_repository_impl.dart
- Fix the location field in CoverageShiftRole to use the correct fallback logic. - Fix the location field in CoverageShiftRole to use the correct fallback logic.
- line 125 remove redundant location values. - line 125 remove redundant location values.
- Need to clarify the difference b/w `case dc.ApplicationStatus.ACCEPTED` and `case dc.ApplicationStatus.CONFIRMED`.
- Update the dataconnect docs. - Update the dataconnect docs.
- Track `lat` and `lng` in the staff preferred work locations (for now we are only storing the name). - Track `lat` and `lng` in the staff preferred work locations (for now we are only storing the name).
- Change the name of the dataconnect connector replacing the "ExampleConnecter" with "KrowConnecter"
- ` final String status;` in `OrderItem` make it an enum. - ` final String status;` in `OrderItem` make it an enum.
- /// Date of the shift (ISO format). - /// Date of the shift (ISO format).
@@ -23,20 +21,20 @@
- in `view_orders_cubit.dart` combine the logic of `_calculateUpNextCount ` and `_calculateTodayCount` into a single function that calculates both counts together to avoid redundant filtering of orders. - in `view_orders_cubit.dart` combine the logic of `_calculateUpNextCount ` and `_calculateTodayCount` into a single function that calculates both counts together to avoid redundant filtering of orders.
- In places api call in the when the api's not working we need to show a proper error message instead of just an empty list. - In places api call in the when the api's not working we need to show a proper error message instead of just an empty list.
- pending should come first in the view order list. - pending should come first in the view order list.
- rename connect name to 'krow-connect' in the project.
- fix "dartdepend_on_referenced_packages" warnings.
- fix "dartunnecessary_library_name" warnings.
- fix lint issues.
- fix localization.
- centralise way to handle errors.
- track minimum shift hours in the staff profile and show a warning if they try to apply for shifts that are below their minimum hours. - track minimum shift hours in the staff profile and show a warning if they try to apply for shifts that are below their minimum hours.
- this need to be added in the BE and also a FE validation (5 hrs). - this need to be added in the BE and also a FE validation (5 hrs).
- Cannot cancel before 24 hours of the shift start time. If do we should charge for 4 hours of work for each shifts. - Cannot cancel before 24 hours of the shift start time. If do we should charge for 4 hours of work for each shifts.
- verify the order creation process in the client app. - verify the order creation process in the client app.
- Vendor don't need to verify the order, when the order is created it should be automatically published. - Vendor don't need to verify the order, when the order is created it should be automatically published.
- rethink the order status, we need to simplify it. - rethink the order status, we need to simplify it.
- Validation layer - Validation layer
- Profile info - Profile info
- emergency contact - emergency contact
@@ -48,4 +46,9 @@
- there should be manual verification by the client even if the ai verification is passed. - there should be manual verification by the client even if the ai verification is passed.
- to track false positives and false negatives. - to track false positives and false negatives.
- documents - documents
- tax forms - tax forms
- How do we handle the current bank account verifcaiton in the current legacy application.
- We need have a show a list of clothing items in the staff app -> shift page.
- Template models for the pdf reports in the client and web apps.
- remove `any` type and replace it with the correct types in the codebase.

View File

@@ -1,7 +1,7 @@
name: krowwithus_client name: krowwithus_client
description: "Krow Client Application" description: "Krow Client Application"
publish_to: "none" publish_to: "none"
version: 0.0.1-M3+5 version: 0.0.1-IlianaClientM3
resolution: workspace resolution: workspace
environment: environment:

View File

@@ -1,7 +1,7 @@
name: krowwithus_staff name: krowwithus_staff
description: "Krow Staff Application" description: "Krow Staff Application"
publish_to: 'none' publish_to: 'none'
version: 0.0.1-M3+3 version: 0.0.1-IlianaStaffM3
resolution: workspace resolution: workspace
environment: environment:

View File

@@ -10,5 +10,5 @@ export 'src/widgets/ui_step_indicator.dart';
export 'src/widgets/ui_icon_button.dart'; export 'src/widgets/ui_icon_button.dart';
export 'src/widgets/ui_button.dart'; export 'src/widgets/ui_button.dart';
export 'src/widgets/ui_chip.dart'; export 'src/widgets/ui_chip.dart';
export 'src/widgets/ui_error_snackbar.dart'; export 'src/widgets/ui_loading_page.dart';
export 'src/widgets/ui_success_snackbar.dart'; export 'src/widgets/ui_snackbar.dart';

View File

@@ -120,7 +120,7 @@ class UiColors {
static const Color textDescription = mutedForeground; static const Color textDescription = mutedForeground;
/// Success text (#10B981) /// Success text (#10B981)
static const Color textSuccess = Color(0xFF10B981); static const Color textSuccess = Color(0xFF0A8159);
/// Error text (#F04444) /// Error text (#F04444)
static const Color textError = destructive; static const Color textError = destructive;

View File

@@ -225,4 +225,28 @@ class UiIcons {
/// Globe icon /// Globe icon
static const IconData globe = _IconLib.globe; static const IconData globe = _IconLib.globe;
/// Sunrise icon
static const IconData sunrise = _IconLib.sunrise;
/// Sun icon
static const IconData sun = _IconLib.sun;
/// Moon icon
static const IconData moon = _IconLib.moon;
/// Timer icon
static const IconData timer = _IconLib.timer;
/// Coffee icon for breaks
static const IconData coffee = _IconLib.coffee;
/// Wifi icon for NFC check-in
static const IconData wifi = _IconLib.wifi;
/// X Circle icon for no-shows
static const IconData xCircle = _IconLib.xCircle;
/// Ban icon for cancellations
static const IconData ban = _IconLib.ban;
} }

View File

@@ -255,7 +255,7 @@ class UiTheme {
} }
return UiColors.switchInactive; return UiColors.switchInactive;
}), }),
thumbColor: const WidgetStatePropertyAll(UiColors.white), thumbColor: const WidgetStatePropertyAll<Color>(UiColors.white),
), ),
// Checkbox Theme // Checkbox Theme

View File

@@ -579,4 +579,10 @@ extension TypographyColors on TextStyle {
/// Active content color /// Active content color
TextStyle get activeContentColor => copyWith(color: UiColors.textPrimary); TextStyle get activeContentColor => copyWith(color: UiColors.textPrimary);
/// Primary color
TextStyle get primary => copyWith(color: UiColors.primary);
/// Accent color
TextStyle get accent => copyWith(color: UiColors.accent);
} }

View File

@@ -1,202 +0,0 @@
import 'package:flutter/material.dart';
import 'package:core_localization/core_localization.dart';
import '../ui_colors.dart';
import '../ui_typography.dart';
/// Centralized error snackbar for consistent error presentation across the app.
///
/// This widget automatically resolves localization keys and displays
/// user-friendly error messages with optional error codes for support.
///
/// Usage:
/// ```dart
/// UiErrorSnackbar.show(
/// context,
/// messageKey: 'errors.auth.invalid_credentials',
/// errorCode: 'AUTH_001',
/// );
/// ```
class UiErrorSnackbar {
/// Shows an error snackbar with a localized message.
///
/// [messageKey] should be a dot-separated path like 'errors.auth.invalid_credentials'
/// [errorCode] is optional and will be shown in smaller text for support reference
/// [duration] controls how long the snackbar is visible
static void show(
BuildContext context, {
required String messageKey,
String? errorCode,
Duration duration = const Duration(seconds: 4),
}) {
// 1. Added explicit type 'Translations' to satisfy the lint
final Translations texts = Translations.of(context);
final String message = _getMessageFromKey(texts, messageKey);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
const Icon(Icons.error_outline, color: UiColors.white),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
message,
style: UiTypography.body2m.copyWith(color: UiColors.white),
),
if (errorCode != null) ...[
const SizedBox(height: 4),
Text(
'Error Code: $errorCode',
style: UiTypography.footnote2r.copyWith(
// 3. Fixed deprecated member use
color: UiColors.white.withValues(alpha: 0.7),
),
),
],
],
),
),
],
),
backgroundColor: UiColors.error,
behavior: SnackBarBehavior.floating,
duration: duration,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
margin: const EdgeInsets.all(16),
),
);
}
/// Resolves a localization key path to the actual translated message.
///
/// Supports keys like:
/// - errors.auth.invalid_credentials
/// - errors.hub.has_orders
/// - errors.generic.unknown
static String _getMessageFromKey(Translations texts, String key) {
// Parse key like "errors.auth.invalid_credentials"
final parts = key.split('.');
if (parts.length < 2) return texts.errors.generic.unknown;
try {
switch (parts[1]) {
case 'auth':
return _getAuthError(texts, parts.length > 2 ? parts[2] : '');
case 'hub':
return _getHubError(texts, parts.length > 2 ? parts[2] : '');
case 'order':
return _getOrderError(texts, parts.length > 2 ? parts[2] : '');
case 'profile':
return _getProfileError(texts, parts.length > 2 ? parts[2] : '');
case 'shift':
return _getShiftError(texts, parts.length > 2 ? parts[2] : '');
case 'generic':
return _getGenericError(texts, parts.length > 2 ? parts[2] : '');
default:
return texts.errors.generic.unknown;
}
} catch (_) {
return texts.errors.generic.unknown;
}
}
static String _getAuthError(Translations texts, String key) {
switch (key) {
case 'invalid_credentials':
return texts.errors.auth.invalid_credentials;
case 'account_exists':
return texts.errors.auth.account_exists;
case 'session_expired':
return texts.errors.auth.session_expired;
case 'user_not_found':
return texts.errors.auth.user_not_found;
case 'unauthorized_app':
return texts.errors.auth.unauthorized_app;
case 'weak_password':
return texts.errors.auth.weak_password;
case 'sign_up_failed':
return texts.errors.auth.sign_up_failed;
case 'sign_in_failed':
return texts.errors.auth.sign_in_failed;
case 'not_authenticated':
return texts.errors.auth.not_authenticated;
case 'password_mismatch':
return texts.errors.auth.password_mismatch;
case 'google_only_account':
return texts.errors.auth.google_only_account;
default:
return texts.errors.generic.unknown;
}
}
static String _getHubError(Translations texts, String key) {
switch (key) {
case 'has_orders':
return texts.errors.hub.has_orders;
case 'not_found':
return texts.errors.hub.not_found;
case 'creation_failed':
return texts.errors.hub.creation_failed;
default:
return texts.errors.generic.unknown;
}
}
static String _getOrderError(Translations texts, String key) {
switch (key) {
case 'missing_hub':
return texts.errors.order.missing_hub;
case 'missing_vendor':
return texts.errors.order.missing_vendor;
case 'creation_failed':
return texts.errors.order.creation_failed;
case 'shift_creation_failed':
return texts.errors.order.shift_creation_failed;
case 'missing_business':
return texts.errors.order.missing_business;
default:
return texts.errors.generic.unknown;
}
}
static String _getProfileError(Translations texts, String key) {
switch (key) {
case 'staff_not_found':
return texts.errors.profile.staff_not_found;
case 'business_not_found':
return texts.errors.profile.business_not_found;
case 'update_failed':
return texts.errors.profile.update_failed;
default:
return texts.errors.generic.unknown;
}
}
static String _getShiftError(Translations texts, String key) {
switch (key) {
case 'no_open_roles':
return texts.errors.shift.no_open_roles;
case 'application_not_found':
return texts.errors.shift.application_not_found;
case 'no_active_shift':
return texts.errors.shift.no_active_shift;
default:
return texts.errors.generic.unknown;
}
}
static String _getGenericError(Translations texts, String key) {
switch (key) {
case 'unknown':
return texts.errors.generic.unknown;
case 'no_connection':
return texts.errors.generic.no_connection;
default:
return texts.errors.generic.unknown;
}
}
}

View File

@@ -0,0 +1,33 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import '../ui_colors.dart';
/// A common loading page that adheres to the design system.
/// It features a blurred background using [UiColors.popupShadow] and a [CircularProgressIndicator].
class UiLoadingPage extends StatelessWidget {
/// Creates a [UiLoadingPage].
const UiLoadingPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: UiColors.transparent,
body: Stack(
fit: StackFit.expand,
children: <Widget>[
// Background blur and color
Positioned.fill(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
child: Container(color: UiColors.popupShadow),
),
),
// Center loading indicator
const Center(child: CircularProgressIndicator()),
],
),
);
}
}

View File

@@ -0,0 +1,99 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import '../ui_colors.dart';
import '../ui_icons.dart';
import '../ui_typography.dart';
/// Types of snackbars available in the design system.
enum UiSnackbarType {
/// Success state - green text and light blurred green background.
success,
/// Message state - blue text and light blurred blue background.
message,
/// Warning state - dark yellow text and light blurred yellow background.
warning,
/// Error state - red text and light blurred red background.
error,
}
/// A centralized snackbar widget that adheres to the design system with glassmorphism effects.
class UiSnackbar {
UiSnackbar._();
/// Shows a snackbar with the specified [message] and [type].
static void show(
BuildContext context, {
required String message,
required UiSnackbarType type,
Duration duration = const Duration(seconds: 3),
}) {
final Color textColor;
final Color backgroundColor;
final IconData icon;
switch (type) {
case UiSnackbarType.success:
textColor = UiColors.textSuccess;
backgroundColor = UiColors.tagSuccess.withValues(alpha: 0.7);
icon = UiIcons.success;
break;
case UiSnackbarType.message:
textColor = UiColors.primary;
backgroundColor = UiColors.tagInProgress.withValues(alpha: 0.7);
icon = UiIcons.info;
break;
case UiSnackbarType.warning:
textColor = UiColors.textWarning;
backgroundColor = UiColors.tagPending.withValues(alpha: 0.7);
icon = UiIcons.warning;
break;
case UiSnackbarType.error:
textColor = UiColors.textError;
backgroundColor = UiColors.tagError.withValues(alpha: 0.7);
icon = UiIcons.error;
break;
}
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
duration: duration,
backgroundColor: UiColors.transparent,
elevation: 0,
behavior: SnackBarBehavior.floating,
content: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: textColor.withValues(alpha: 0.2),
width: 1,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
spacing: 12,
children: <Widget>[
Icon(icon, color: textColor, size: 20),
Expanded(
child: Text(
message,
style: UiTypography.body2b.copyWith(color: textColor),
),
),
],
),
),
),
),
),
);
}
}

View File

@@ -8,12 +8,6 @@ import '../ui_icons.dart';
/// This widget shows a series of circular step indicators connected by lines, /// This widget shows a series of circular step indicators connected by lines,
/// with different visual states for completed, active, and inactive steps. /// with different visual states for completed, active, and inactive steps.
class UiStepIndicator extends StatelessWidget { class UiStepIndicator extends StatelessWidget {
/// The list of icons to display for each step.
final List<IconData> stepIcons;
/// The index of the currently active step (0-based).
final int currentStep;
/// Creates a [UiStepIndicator]. /// Creates a [UiStepIndicator].
const UiStepIndicator({ const UiStepIndicator({
super.key, super.key,
@@ -21,6 +15,12 @@ class UiStepIndicator extends StatelessWidget {
required this.currentStep, required this.currentStep,
}); });
/// The list of icons to display for each step.
final List<IconData> stepIcons;
/// The index of the currently active step (0-based).
final int currentStep;
@override @override
/// Builds the step indicator UI. /// Builds the step indicator UI.
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -35,7 +35,7 @@ class UiStepIndicator extends StatelessWidget {
padding: const EdgeInsets.symmetric(vertical: UiConstants.space2), padding: const EdgeInsets.symmetric(vertical: UiConstants.space2),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(stepIcons.length, (int index) { children: List<Widget>.generate(stepIcons.length, (int index) {
final bool isActive = index == currentStep; final bool isActive = index == currentStep;
final bool isCompleted = index < currentStep; final bool isCompleted = index < currentStep;

View File

@@ -1,49 +0,0 @@
import 'package:flutter/material.dart';
import '../ui_colors.dart';
import '../ui_typography.dart';
/// Centralized success snackbar for consistent success message presentation.
///
/// This widget provides a unified way to show success feedback across the app
/// with consistent styling and behavior.
///
/// Usage:
/// ```dart
/// UiSuccessSnackbar.show(
/// context,
/// message: 'Profile updated successfully!',
/// );
/// ```
class UiSuccessSnackbar {
/// Shows a success snackbar with a custom message.
///
/// [message] is the success message to display
/// [duration] controls how long the snackbar is visible
static void show(
BuildContext context, {
required String message,
Duration duration = const Duration(seconds: 3),
}) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
Icon(Icons.check_circle_outline, color: UiColors.white),
const SizedBox(width: 12),
Expanded(
child: Text(
message,
style: UiTypography.body2m.copyWith(color: UiColors.white),
),
),
],
),
backgroundColor: UiColors.success,
behavior: SnackBarBehavior.floating,
duration: duration,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
margin: const EdgeInsets.all(16),
),
);
}
}

View File

@@ -41,7 +41,6 @@ class TimeCardAdapter {
case 'DISPUTED': case 'DISPUTED':
return TimeCardStatus.disputed; return TimeCardStatus.disputed;
case 'CHECKED_IN': case 'CHECKED_IN':
case 'ACCEPTED':
case 'CONFIRMED': case 'CONFIRMED':
default: default:
return TimeCardStatus.pending; return TimeCardStatus.pending;

View File

@@ -12,8 +12,7 @@ import 'package:krow_domain/krow_domain.dart'
AccountExistsException, AccountExistsException,
UserNotFoundException, UserNotFoundException,
UnauthorizedAppException, UnauthorizedAppException,
PasswordMismatchException, PasswordMismatchException;
GoogleOnlyAccountException;
import 'package:krow_domain/krow_domain.dart' as domain; import 'package:krow_domain/krow_domain.dart' as domain;
import '../../domain/repositories/auth_repository_interface.dart'; import '../../domain/repositories/auth_repository_interface.dart';

View File

@@ -177,8 +177,6 @@ class CoverageRepositoryImpl implements CoverageRepository {
switch (status.value) { switch (status.value) {
case dc.ApplicationStatus.PENDING: case dc.ApplicationStatus.PENDING:
return CoverageWorkerStatus.pending; return CoverageWorkerStatus.pending;
case dc.ApplicationStatus.ACCEPTED:
return CoverageWorkerStatus.confirmed;
case dc.ApplicationStatus.REJECTED: case dc.ApplicationStatus.REJECTED:
return CoverageWorkerStatus.rejected; return CoverageWorkerStatus.rejected;
case dc.ApplicationStatus.CONFIRMED: case dc.ApplicationStatus.CONFIRMED:

View File

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

View File

@@ -1,3 +1,4 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
@@ -36,7 +37,7 @@ class GetStartedPage extends StatelessWidget {
// Content Overlay // Content Overlay
Padding( Padding(
padding: const EdgeInsets.all(24.0), padding: const EdgeInsets.all(UiConstants.space6),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
@@ -44,7 +45,7 @@ class GetStartedPage extends StatelessWidget {
// Main text and actions // Main text and actions
const GetStartedHeader(), const GetStartedHeader(),
const SizedBox(height: 48), const SizedBox(height: UiConstants.space10),
// Actions // Actions
GetStartedActions( GetStartedActions(
@@ -52,7 +53,7 @@ class GetStartedPage extends StatelessWidget {
onLoginPressed: onLoginPressed, onLoginPressed: onLoginPressed,
), ),
const SizedBox(height: 32), const SizedBox(height: UiConstants.space8),
], ],
), ),
), ),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,14 +2,14 @@ import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart' hide ModularWatchExtension; import 'package:flutter_modular/flutter_modular.dart'
hide ModularWatchExtension;
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:lucide_icons/lucide_icons.dart'; import 'package:krow_domain/krow_domain.dart';
import '../blocs/availability_bloc.dart'; import '../blocs/availability_bloc.dart';
import '../blocs/availability_event.dart'; import '../blocs/availability_event.dart';
import '../blocs/availability_state.dart'; import '../blocs/availability_state.dart';
import 'package:krow_domain/krow_domain.dart';
class AvailabilityPage extends StatefulWidget { class AvailabilityPage extends StatefulWidget {
const AvailabilityPage({super.key}); const AvailabilityPage({super.key});
@@ -46,7 +46,6 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
return BlocProvider.value( return BlocProvider.value(
value: _bloc, value: _bloc,
child: Scaffold( child: Scaffold(
backgroundColor: AppColors.krowBackground,
appBar: UiAppBar( appBar: UiAppBar(
title: 'My Availability', title: 'My Availability',
centerTitle: false, centerTitle: false,
@@ -55,13 +54,18 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
body: BlocListener<AvailabilityBloc, AvailabilityState>( body: BlocListener<AvailabilityBloc, AvailabilityState>(
listener: (context, state) { listener: (context, state) {
if (state is AvailabilityLoaded && state.successMessage != null) { if (state is AvailabilityLoaded && state.successMessage != null) {
ScaffoldMessenger.of(context).hideCurrentSnackBar(); UiSnackbar.show(
ScaffoldMessenger.of(context).showSnackBar( context,
SnackBar( message: state.successMessage!,
content: Text(state.successMessage!), type: UiSnackbarType.success,
backgroundColor: Colors.green, );
behavior: SnackBarBehavior.floating, }
),
if (state is AvailabilityError) {
UiSnackbar.show(
context,
message: state.message,
type: UiSnackbarType.error,
); );
} else if (state is AvailabilityError) { } else if (state is AvailabilityError) {
ScaffoldMessenger.of(context).hideCurrentSnackBar(); ScaffoldMessenger.of(context).hideCurrentSnackBar();
@@ -85,19 +89,19 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
child: Column( child: Column(
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space5,
),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
spacing: UiConstants.space6,
children: [ children: [
_buildQuickSet(context), _buildQuickSet(context),
const SizedBox(height: 24),
_buildWeekNavigation(context, state), _buildWeekNavigation(context, state),
const SizedBox(height: 24),
_buildSelectedDayAvailability( _buildSelectedDayAvailability(
context, context,
state.selectedDayAvailability, state.selectedDayAvailability,
), ),
const SizedBox(height: 24),
_buildInfoCard(), _buildInfoCard(),
], ],
), ),
@@ -106,12 +110,7 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
), ),
), ),
if (state.isActionInProgress) if (state.isActionInProgress)
Container( const UiLoadingPage(), // Show loading overlay during actions
color: Colors.black.withOpacity(0.3),
child: const Center(
child: CircularProgressIndicator(),
),
),
], ],
); );
} else if (state is AvailabilityError) { } else if (state is AvailabilityError) {
@@ -124,14 +123,12 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
Text( Text(
translateErrorKey(state.message), translateErrorKey(state.message),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle( style: UiTypography.body2r.textSecondary,
fontSize: 16,
color: AppColors.krowMuted,
),
), ),
], ],
), ),
), ),
),
); );
} }
return const SizedBox.shrink(); return const SizedBox.shrink();
@@ -144,49 +141,31 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
Widget _buildQuickSet(BuildContext context) { Widget _buildQuickSet(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.krowBlue.withOpacity(0.1), color: UiColors.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(16), borderRadius: UiConstants.radiusLg,
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'Quick Set Availability', 'Quick Set Availability',
style: TextStyle( style: UiTypography.body2b,
fontSize: 14,
fontWeight: FontWeight.w500,
color: Color(0xFF333F48),
),
), ),
const SizedBox(height: 12), const SizedBox(height: UiConstants.space3),
Row( Row(
children: [ children: [
Expanded(child: _buildQuickSetButton(context, 'All Week', 'all')),
const SizedBox(width: UiConstants.space2),
Expanded( Expanded(
child: _buildQuickSetButton( child: _buildQuickSetButton(context, 'Weekdays', 'weekdays'),
context,
'All Week',
'all',
),
), ),
const SizedBox(width: 8), const SizedBox(width: UiConstants.space2),
Expanded( Expanded(
child: _buildQuickSetButton( child: _buildQuickSetButton(context, 'Weekends', 'weekends'),
context,
'Weekdays',
'weekdays',
),
), ),
const SizedBox(width: 8), const SizedBox(width: UiConstants.space2),
Expanded(
child: _buildQuickSetButton(
context,
'Weekends',
'weekends',
),
),
const SizedBox(width: 8),
Expanded( Expanded(
child: _buildQuickSetButton( child: _buildQuickSetButton(
context, context,
@@ -211,23 +190,26 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
return SizedBox( return SizedBox(
height: 32, height: 32,
child: OutlinedButton( child: OutlinedButton(
onPressed: () => context.read<AvailabilityBloc>().add(PerformQuickSet(type)), onPressed: () =>
context.read<AvailabilityBloc>().add(PerformQuickSet(type)),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
side: BorderSide( side: BorderSide(
color: isDestructive color: isDestructive
? Colors.red.withOpacity(0.2) ? UiColors.destructive.withValues(alpha: 0.2)
: AppColors.krowBlue.withOpacity(0.2), : UiColors.primary.withValues(alpha: 0.2),
), ),
backgroundColor: Colors.transparent, backgroundColor: UiColors.transparent,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
), ),
foregroundColor: isDestructive ? Colors.red : AppColors.krowBlue, foregroundColor: isDestructive
? UiColors.destructive
: UiColors.primary,
), ),
child: Text( child: Text(
label, label,
style: const TextStyle(fontSize: 10, fontWeight: FontWeight.w500), style: UiTypography.body4r,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@@ -241,42 +223,35 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
final monthYear = DateFormat('MMMM yyyy').format(middleDate); final monthYear = DateFormat('MMMM yyyy').format(middleDate);
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: UiColors.cardViewBackground,
borderRadius: BorderRadius.circular(16), borderRadius: UiConstants.radiusLg,
border: Border.all(color: Colors.grey.shade100), border: Border.all(color: UiColors.border),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 2,
offset: const Offset(0, 1),
),
],
), ),
child: Column( child: Column(
children: [ children: [
// Nav Header // Nav Header
Padding( Padding(
padding: const EdgeInsets.only(bottom: 16), padding: const EdgeInsets.only(bottom: UiConstants.space4),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
_buildNavButton( _buildNavButton(
LucideIcons.chevronLeft, UiIcons.chevronLeft,
() => context.read<AvailabilityBloc>().add(const NavigateWeek(-1)), () => context.read<AvailabilityBloc>().add(
const NavigateWeek(-1),
),
), ),
Text( Text(
monthYear, monthYear,
style: const TextStyle( style: UiTypography.title2b,
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.krowCharcoal,
),
), ),
_buildNavButton( _buildNavButton(
LucideIcons.chevronRight, UiIcons.chevronRight,
() => context.read<AvailabilityBloc>().add(const NavigateWeek(1)), () => context.read<AvailabilityBloc>().add(
const NavigateWeek(1),
),
), ),
], ],
), ),
@@ -284,7 +259,9 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
// Days Row // Days Row
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: state.days.map((day) => _buildDayItem(context, day, state.selectedDate)).toList(), children: state.days
.map((day) => _buildDayItem(context, day, state.selectedDate))
.toList(),
), ),
], ],
), ),
@@ -298,15 +275,19 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
width: 32, width: 32,
height: 32, height: 32,
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: Color(0xFFF1F5F9), // slate-100 color: UiColors.separatorSecondary,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: Icon(icon, size: 20, color: AppColors.krowMuted), child: Icon(icon, size: 20, color: UiColors.iconSecondary),
), ),
); );
} }
Widget _buildDayItem(BuildContext context, DayAvailability day, DateTime selectedDate) { Widget _buildDayItem(
BuildContext context,
DayAvailability day,
DateTime selectedDate,
) {
final isSelected = AvailabilityLoaded.isSameDay(day.date, selectedDate); final isSelected = AvailabilityLoaded.isSameDay(day.date, selectedDate);
final isAvailable = day.isAvailable; final isAvailable = day.isAvailable;
final isToday = AvailabilityLoaded.isSameDay(day.date, DateTime.now()); final isToday = AvailabilityLoaded.isSameDay(day.date, DateTime.now());
@@ -316,30 +297,19 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
onTap: () => context.read<AvailabilityBloc>().add(SelectDate(day.date)), onTap: () => context.read<AvailabilityBloc>().add(SelectDate(day.date)),
child: Container( child: Container(
margin: const EdgeInsets.symmetric(horizontal: 2), margin: const EdgeInsets.symmetric(horizontal: 2),
padding: const EdgeInsets.symmetric(vertical: 12), padding: const EdgeInsets.symmetric(vertical: UiConstants.space3),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected color: isSelected
? AppColors.krowBlue ? UiColors.primary
: (isAvailable : (isAvailable ? UiColors.tagSuccess : UiColors.bgSecondary),
? const Color(0xFFECFDF5) borderRadius: UiConstants.radiusLg,
: const Color(0xFFF8FAFC)), // emerald-50 or slate-50
borderRadius: BorderRadius.circular(16),
border: Border.all( border: Border.all(
color: isSelected color: isSelected
? AppColors.krowBlue ? UiColors.primary
: (isAvailable : (isAvailable
? const Color(0xFFA7F3D0) ? UiColors.success.withValues(alpha: 0.3)
: Colors.transparent), // emerald-200 : UiColors.transparent),
), ),
boxShadow: isSelected
? [
BoxShadow(
color: AppColors.krowBlue.withOpacity(0.3),
blurRadius: 8,
offset: const Offset(0, 4),
),
]
: null,
), ),
child: Stack( child: Stack(
clipBehavior: Clip.none, clipBehavior: Clip.none,
@@ -349,26 +319,24 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
children: [ children: [
Text( Text(
day.date.day.toString().padLeft(2, '0'), day.date.day.toString().padLeft(2, '0'),
style: TextStyle( style: UiTypography.title1m.copyWith(
fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: isSelected color: isSelected
? Colors.white ? UiColors.white
: (isAvailable : (isAvailable
? const Color(0xFF047857) ? UiColors.textSuccess
: AppColors.krowMuted), // emerald-700 : UiColors.textSecondary),
), ),
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
DateFormat('EEE').format(day.date), DateFormat('EEE').format(day.date),
style: TextStyle( style: UiTypography.footnote2r.copyWith(
fontSize: 10,
color: isSelected color: isSelected
? Colors.white.withOpacity(0.8) ? UiColors.white.withValues(alpha: 0.8)
: (isAvailable : (isAvailable
? const Color(0xFF047857) ? UiColors.textSuccess
: AppColors.krowMuted), : UiColors.textSecondary),
), ),
), ),
], ],
@@ -380,7 +348,7 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
width: 6, width: 6,
height: 6, height: 6,
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: AppColors.krowBlue, color: UiColors.primary,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
), ),
@@ -400,18 +368,11 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
final isAvailable = day.isAvailable; final isAvailable = day.isAvailable;
return Container( return Container(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(UiConstants.space5),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: UiColors.cardViewBackground,
borderRadius: BorderRadius.circular(16), borderRadius: UiConstants.radiusLg,
border: Border.all(color: Colors.grey.shade100), border: Border.all(color: UiColors.border),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 2,
offset: const Offset(0, 1),
),
],
), ),
child: Column( child: Column(
children: [ children: [
@@ -424,114 +385,112 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
children: [ children: [
Text( Text(
dateStr, dateStr,
style: const TextStyle( style: UiTypography.title2b,
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.krowCharcoal,
),
), ),
Text( Text(
isAvailable ? 'You are available' : 'Not available', isAvailable ? 'You are available' : 'Not available',
style: const TextStyle( style: UiTypography.body2r.textSecondary,
fontSize: 14,
color: AppColors.krowMuted,
),
), ),
], ],
), ),
Switch( Switch(
value: isAvailable, value: isAvailable,
onChanged: (val) => context.read<AvailabilityBloc>().add(ToggleDayStatus(day)), onChanged: (val) =>
activeColor: AppColors.krowBlue, context.read<AvailabilityBloc>().add(ToggleDayStatus(day)),
activeColor: UiColors.primary,
), ),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: UiConstants.space4),
// Time Slots (only from Domain) // Time Slots (only from Domain)
...day.slots.map((slot) { ...day.slots.map((slot) {
// Get UI config for this slot ID // Get UI config for this slot ID
final uiConfig = _getSlotUiConfig(slot.id); final uiConfig = _getSlotUiConfig(slot.id);
return _buildTimeSlotItem(context, day, slot, uiConfig); return _buildTimeSlotItem(context, day, slot, uiConfig);
}).toList(), }).toList(),
], ],
), ),
); );
} }
Map<String, dynamic> _getSlotUiConfig(String slotId) { Map<String, dynamic> _getSlotUiConfig(String slotId) {
switch (slotId) { switch (slotId) {
case 'morning': case 'morning':
return { return {
'icon': LucideIcons.sunrise, 'icon': UiIcons.sunrise,
'bg': const Color(0xFFE6EBF9), // bg-[#0032A0]/10 'bg': UiColors.primary.withValues(alpha: 0.1),
'iconColor': const Color(0xFF0032A0), 'iconColor': UiColors.primary,
}; };
case 'afternoon': case 'afternoon':
return { return {
'icon': LucideIcons.sun, 'icon': UiIcons.sun,
'bg': const Color(0xFFCCD6EC), // bg-[#0032A0]/20 'bg': UiColors.primary.withValues(alpha: 0.2),
'iconColor': const Color(0xFF0032A0), 'iconColor': UiColors.primary,
}; };
case 'evening': case 'evening':
return { return {
'icon': LucideIcons.moon, 'icon': UiIcons.moon,
'bg': const Color(0xFFEBEDEE), // bg-[#333F48]/10 'bg': UiColors.bgSecondary,
'iconColor': const Color(0xFF333F48), 'iconColor': UiColors.foreground,
}; };
default: default:
return { return {
'icon': LucideIcons.clock, 'icon': UiIcons.clock,
'bg': Colors.grey.shade100, 'bg': UiColors.bgSecondary,
'iconColor': Colors.grey, 'iconColor': UiColors.iconSecondary,
}; };
} }
} }
Widget _buildTimeSlotItem( Widget _buildTimeSlotItem(
BuildContext context, BuildContext context,
DayAvailability day, DayAvailability day,
AvailabilitySlot slot, AvailabilitySlot slot,
Map<String, dynamic> uiConfig Map<String, dynamic> uiConfig,
) { ) {
// Determine styles based on state // Determine styles based on state
final isEnabled = day.isAvailable; final isEnabled = day.isAvailable;
final isActive = slot.isAvailable; final isActive = slot.isAvailable;
// Container style // Container style
Color bgColor; Color bgColor;
Color borderColor; Color borderColor;
if (!isEnabled) { if (!isEnabled) {
bgColor = const Color(0xFFF8FAFC); // slate-50 bgColor = UiColors.bgSecondary;
borderColor = const Color(0xFFF1F5F9); // slate-100 borderColor = UiColors.borderInactive;
} else if (isActive) { } else if (isActive) {
bgColor = AppColors.krowBlue.withOpacity(0.05); bgColor = UiColors.primary.withValues(alpha: 0.05);
borderColor = AppColors.krowBlue.withOpacity(0.2); borderColor = UiColors.primary.withValues(alpha: 0.2);
} else { } else {
bgColor = const Color(0xFFF8FAFC); // slate-50 bgColor = UiColors.bgSecondary;
borderColor = const Color(0xFFE2E8F0); // slate-200 borderColor = UiColors.borderPrimary;
} }
// Text colors // Text colors
final titleColor = (isEnabled && isActive) final titleColor = (isEnabled && isActive)
? AppColors.krowCharcoal ? UiColors.foreground
: AppColors.krowMuted; : UiColors.mutedForeground;
final subtitleColor = (isEnabled && isActive) final subtitleColor = (isEnabled && isActive)
? AppColors.krowMuted ? UiColors.mutedForeground
: Colors.grey.shade400; : UiColors.textInactive;
return GestureDetector( return GestureDetector(
onTap: isEnabled ? () => context.read<AvailabilityBloc>().add(ToggleSlotStatus(day, slot.id)) : null, onTap: isEnabled
? () => context.read<AvailabilityBloc>().add(
ToggleSlotStatus(day, slot.id),
)
: null,
child: AnimatedContainer( child: AnimatedContainer(
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: UiConstants.space3),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: bgColor, color: bgColor,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
border: Border.all(color: borderColor, width: 2), border: Border.all(color: borderColor, width: 2),
), ),
child: Row( child: Row(
@@ -542,7 +501,7 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
height: 40, height: 40,
decoration: BoxDecoration( decoration: BoxDecoration(
color: uiConfig['bg'], color: uiConfig['bg'],
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
), ),
child: Icon( child: Icon(
uiConfig['icon'], uiConfig['icon'],
@@ -550,7 +509,7 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
size: 20, size: 20,
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: UiConstants.space3),
// Text // Text
Expanded( Expanded(
child: Column( child: Column(
@@ -558,18 +517,11 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
children: [ children: [
Text( Text(
slot.label, slot.label,
style: TextStyle( style: UiTypography.body2m.copyWith(color: titleColor),
fontSize: 14,
fontWeight: FontWeight.w500,
color: titleColor,
),
), ),
Text( Text(
slot.timeRange, slot.timeRange,
style: TextStyle( style: UiTypography.body3r.copyWith(color: subtitleColor),
fontSize: 12,
color: subtitleColor,
),
), ),
], ],
), ),
@@ -580,13 +532,13 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
width: 24, width: 24,
height: 24, height: 24,
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: AppColors.krowBlue, color: UiColors.primary,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: const Icon( child: const Icon(
LucideIcons.check, UiIcons.check,
size: 16, size: 16,
color: Colors.white, color: UiColors.white,
), ),
) )
else if (isEnabled && !isActive) else if (isEnabled && !isActive)
@@ -596,9 +548,9 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all( border: Border.all(
color: const Color(0xFFCBD5E1), color: UiColors.borderStill,
width: 2, width: 2,
), // slate-300 ),
), ),
), ),
], ],
@@ -609,32 +561,28 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
Widget _buildInfoCard() { Widget _buildInfoCard() {
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.krowBlue.withOpacity(0.05), color: UiColors.primary.withValues(alpha: 0.05),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
), ),
child: const Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
spacing: UiConstants.space3,
children: [ children: [
Icon(LucideIcons.clock, size: 20, color: AppColors.krowBlue), const Icon(UiIcons.clock, size: 20, color: UiColors.primary),
SizedBox(width: 12),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
spacing: UiConstants.space1,
children: [ children: [
Text( Text(
'Auto-Match uses your availability', 'Auto-Match uses your availability',
style: TextStyle( style: UiTypography.body2m,
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.krowCharcoal,
),
), ),
SizedBox(height: 2),
Text( Text(
"When enabled, you'll only be matched with shifts during your available times.", "When enabled, you'll only be matched with shifts during your available times.",
style: TextStyle(fontSize: 12, color: AppColors.krowMuted), style: UiTypography.body3r.textSecondary,
), ),
], ],
), ),
@@ -644,15 +592,3 @@ class _AvailabilityPageState extends State<AvailabilityPage> {
); );
} }
} }
class AppColors {
static const Color krowBlue = Color(0xFF0A39DF);
static const Color krowYellow = Color(0xFFFFED4A);
static const Color krowCharcoal = Color(0xFF121826);
static const Color krowMuted = Color(0xFF6A7382);
static const Color krowBorder = Color(0xFFE3E6E9);
static const Color krowBackground = Color(0xFFFAFBFC);
static const Color white = Colors.white;
static const Color black = Colors.black;
}

View File

@@ -1,22 +1,17 @@
name: staff_availability name: staff_availability
description: Staff Availability Feature description: Staff Availability Feature
version: 0.0.1 version: 0.0.1
publish_to: 'none' publish_to: "none"
resolution: workspace resolution: workspace
environment: environment:
sdk: '>=3.10.0 <4.0.0' sdk: ">=3.10.0 <4.0.0"
flutter: ">=1.17.0" flutter: ">=1.17.0"
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
flutter_bloc: ^8.1.3
equatable: ^2.0.5
intl: ^0.20.0
lucide_icons: ^0.257.0
flutter_modular: ^6.3.2
# Internal packages # Internal packages
core_localization: core_localization:
path: ../../../core_localization path: ../../../core_localization
@@ -28,6 +23,11 @@ dependencies:
path: ../../../data_connect path: ../../../data_connect
krow_core: krow_core:
path: ../../../core path: ../../../core
flutter_bloc: ^8.1.3
equatable: ^2.0.5
intl: ^0.20.0
flutter_modular: ^6.3.2
firebase_data_connect: ^0.2.2+2 firebase_data_connect: ^0.2.2+2
firebase_auth: ^6.1.4 firebase_auth: ^6.1.4

View File

@@ -5,12 +5,10 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import 'package:lucide_icons/lucide_icons.dart';
import '../bloc/clock_in_bloc.dart'; import '../bloc/clock_in_bloc.dart';
import '../bloc/clock_in_event.dart'; import '../bloc/clock_in_event.dart';
import '../bloc/clock_in_state.dart'; import '../bloc/clock_in_state.dart';
import '../theme/app_colors.dart';
import '../widgets/commute_tracker.dart'; import '../widgets/commute_tracker.dart';
import '../widgets/date_selector.dart'; import '../widgets/date_selector.dart';
import '../widgets/lunch_break_modal.dart'; import '../widgets/lunch_break_modal.dart';
@@ -40,10 +38,10 @@ class _ClockInPageState extends State<ClockInPage> {
listener: (BuildContext context, ClockInState state) { listener: (BuildContext context, ClockInState state) {
if (state.status == ClockInStatus.failure && if (state.status == ClockInStatus.failure &&
state.errorMessage != null) { state.errorMessage != null) {
ScaffoldMessenger.of( UiSnackbar.show(
context, context,
).showSnackBar( message: translateErrorKey(state.errorMessage!),
SnackBar(content: Text(translateErrorKey(state.errorMessage!))), type: UiSnackbarType.error,
); );
} }
}, },
@@ -67,14 +65,6 @@ class _ClockInPageState extends State<ClockInPage> {
final bool isCheckedIn = final bool isCheckedIn =
state.attendance.isCheckedIn && isActiveSelected; state.attendance.isCheckedIn && isActiveSelected;
// Format times for display
final String checkInStr = checkInTime != null
? DateFormat('h:mm a').format(checkInTime)
: '--:-- --';
final String checkOutStr = checkOutTime != null
? DateFormat('h:mm a').format(checkOutTime)
: '--:-- --';
return Scaffold( return Scaffold(
appBar: UiAppBar( appBar: UiAppBar(
titleWidget: Text( titleWidget: Text(
@@ -94,7 +84,9 @@ class _ClockInPageState extends State<ClockInPage> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space5,
),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
@@ -113,26 +105,22 @@ class _ClockInPageState extends State<ClockInPage> {
// Date Selector // Date Selector
DateSelector( DateSelector(
selectedDate: state.selectedDate, selectedDate: state.selectedDate,
onSelect: (DateTime date) => _bloc.add(DateSelected(date)), onSelect: (DateTime date) =>
_bloc.add(DateSelected(date)),
shiftDates: <String>[ shiftDates: <String>[
DateFormat('yyyy-MM-dd').format(DateTime.now()), DateFormat('yyyy-MM-dd').format(DateTime.now()),
], ],
), ),
const SizedBox(height: 20), const SizedBox(height: UiConstants.space5),
// Your Activity Header // Your Activity Header
const Text( Text(
"Your Activity", "Your Activity",
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: TextStyle( style: UiTypography.headline4m,
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppColors.krowCharcoal,
),
), ),
const SizedBox(height: 16), const SizedBox(height: UiConstants.space4),
// Selected Shift Info Card // Selected Shift Info Card
if (todayShifts.isNotEmpty) if (todayShifts.isNotEmpty)
@@ -143,19 +131,21 @@ class _ClockInPageState extends State<ClockInPage> {
onTap: () => onTap: () =>
_bloc.add(ShiftSelected(shift)), _bloc.add(ShiftSelected(shift)),
child: Container( child: Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(
margin: UiConstants.space3,
const EdgeInsets.only(bottom: 12), ),
margin: const EdgeInsets.only(
bottom: UiConstants.space3,
),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: UiColors.white,
borderRadius: BorderRadius.circular( borderRadius:
12, UiConstants.radiusLg,
),
border: Border.all( border: Border.all(
color: shift.id == color: shift.id ==
selectedShift?.id selectedShift?.id
? AppColors.krowBlue ? UiColors.primary
: const Color(0xFFE2E8F0), : UiColors.border,
width: width:
shift.id == selectedShift?.id shift.id == selectedShift?.id
? 2 ? 2
@@ -176,34 +166,25 @@ class _ClockInPageState extends State<ClockInPage> {
selectedShift?.id selectedShift?.id
? "SELECTED SHIFT" ? "SELECTED SHIFT"
: "TODAY'S SHIFT", : "TODAY'S SHIFT",
style: TextStyle( style: UiTypography
fontSize: 10, .titleUppercase4b
fontWeight: .copyWith(
FontWeight.w600,
color: shift.id == color: shift.id ==
selectedShift?.id selectedShift?.id
? AppColors.krowBlue ? UiColors.primary
: AppColors : UiColors
.krowCharcoal, .textSecondary,
letterSpacing: 0.5,
), ),
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
shift.title, shift.title,
style: const TextStyle( style: UiTypography.body2b,
fontSize: 14,
fontWeight:
FontWeight.w600,
color: Color(0xFF1E293B),
),
), ),
Text( Text(
"${shift.clientName}${shift.location}", "${shift.clientName}${shift.location}",
style: const TextStyle( style: UiTypography.body3r
fontSize: 12, .textSecondary,
color: Color(0xFF64748B),
),
), ),
], ],
), ),
@@ -214,18 +195,14 @@ class _ClockInPageState extends State<ClockInPage> {
children: <Widget>[ children: <Widget>[
Text( Text(
"${_formatTime(shift.startTime)} - ${_formatTime(shift.endTime)}", "${_formatTime(shift.startTime)} - ${_formatTime(shift.endTime)}",
style: const TextStyle( style: UiTypography.body3m
fontSize: 12, .textSecondary,
fontWeight: FontWeight.w500,
color: Color(0xFF475569),
),
), ),
Text( Text(
"\$${shift.hourlyRate}/hr", "\$${shift.hourlyRate}/hr",
style: const TextStyle( style: UiTypography.body3m
fontSize: 12, .copyWith(
fontWeight: FontWeight.w600, color: UiColors.primary,
color: AppColors.krowBlue,
), ),
), ),
], ],
@@ -239,46 +216,41 @@ class _ClockInPageState extends State<ClockInPage> {
), ),
// Swipe To Check In / Checked Out State / No Shift State // Swipe To Check In / Checked Out State / No Shift State
if (selectedShift != null && checkOutTime == null) ...<Widget>[ if (selectedShift != null &&
if (!isCheckedIn && checkOutTime == null) ...<Widget>[
!_isCheckInAllowed(selectedShift)) if (!isCheckedIn &&
Container( !_isCheckInAllowed(selectedShift))
width: double.infinity, Container(
padding: const EdgeInsets.all(24), width: double.infinity,
decoration: BoxDecoration( padding:
color: const Color(0xFFF1F5F9), // slate-100 const EdgeInsets.all(UiConstants.space6),
borderRadius: BorderRadius.circular(16), decoration: BoxDecoration(
), color: UiColors.bgSecondary,
child: Column( borderRadius: UiConstants.radiusLg,
children: <Widget>[ ),
const Icon( child: Column(
LucideIcons.clock, children: <Widget>[
size: 48, const Icon(
color: Color(0xFF94A3B8), // slate-400 UiIcons.clock,
), size: 48,
const SizedBox(height: 16), color: UiColors.iconThird,
const Text( ),
"You're early!", const SizedBox(height: UiConstants.space4),
style: TextStyle( Text(
fontSize: 16, "You're early!",
fontWeight: FontWeight.w600, style: UiTypography.body1m.textSecondary,
color: Color(0xFF475569), // slate-600 ),
), const SizedBox(height: UiConstants.space1),
), Text(
const SizedBox(height: 4), "Check-in available at ${_getCheckInAvailabilityTime(selectedShift)}",
Text( style: UiTypography.body2r.textSecondary,
"Check-in available at ${_getCheckInAvailabilityTime(selectedShift)}", textAlign: TextAlign.center,
style: const TextStyle( ),
fontSize: 14, ],
color: Color(0xFF64748B), // slate-500 ),
), )
textAlign: TextAlign.center, else
), SwipeToCheckIn(
],
),
)
else
SwipeToCheckIn(
isCheckedIn: isCheckedIn, isCheckedIn: isCheckedIn,
mode: state.checkInMode, mode: state.checkInMode,
isLoading: isLoading:
@@ -299,14 +271,17 @@ class _ClockInPageState extends State<ClockInPage> {
onCheckOut: () { onCheckOut: () {
showDialog( showDialog(
context: context, context: context,
builder: (BuildContext context) => LunchBreakDialog( builder: (BuildContext context) =>
onComplete: () { LunchBreakDialog(
Navigator.of( onComplete: () {
context, Navigator.of(
).pop(); // Close dialog first context,
_bloc.add(const CheckOutRequested()); ).pop(); // Close dialog first
}, _bloc.add(
), const CheckOutRequested(),
);
},
),
); );
}, },
), ),
@@ -314,13 +289,15 @@ class _ClockInPageState extends State<ClockInPage> {
checkOutTime != null) ...<Widget>[ checkOutTime != null) ...<Widget>[
// Shift Completed State // Shift Completed State
Container( Container(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(UiConstants.space6),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFECFDF5), // emerald-50 color: UiColors.tagSuccess,
borderRadius: BorderRadius.circular(16), borderRadius: UiConstants.radiusLg,
border: Border.all( border: Border.all(
color: const Color(0xFFA7F3D0), color: UiColors.success.withValues(
), // emerald-200 alpha: 0.3,
),
),
), ),
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
@@ -328,31 +305,24 @@ class _ClockInPageState extends State<ClockInPage> {
width: 48, width: 48,
height: 48, height: 48,
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: Color(0xFFD1FAE5), // emerald-100 color: UiColors.tagActive,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: const Icon( child: const Icon(
LucideIcons.check, UiIcons.check,
color: Color(0xFF059669), // emerald-600 color: UiColors.textSuccess,
size: 24, size: 24,
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: UiConstants.space3),
const Text( Text(
"Shift Completed!", "Shift Completed!",
style: TextStyle( style: UiTypography.body1b.textSuccess,
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF065F46), // emerald-800
),
), ),
const SizedBox(height: 4), const SizedBox(height: UiConstants.space1),
const Text( Text(
"Great work today", "Great work today",
style: TextStyle( style: UiTypography.body2r.textSuccess,
fontSize: 14,
color: Color(0xFF059669), // emerald-600
),
), ),
], ],
), ),
@@ -361,29 +331,22 @@ class _ClockInPageState extends State<ClockInPage> {
// No Shift State // No Shift State
Container( Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(UiConstants.space6),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFF1F5F9), // slate-100 color: UiColors.bgSecondary,
borderRadius: BorderRadius.circular(16), borderRadius: UiConstants.radiusLg,
), ),
child: const Column( child: Column(
children: <Widget>[ children: <Widget>[
Text( Text(
"No confirmed shifts for today", "No confirmed shifts for today",
style: TextStyle( style: UiTypography.body1m.textSecondary,
fontSize: 16,
fontWeight: FontWeight.w500,
color: Color(0xFF475569), // slate-600
),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
SizedBox(height: 4), const SizedBox(height: UiConstants.space1),
Text( Text(
"Accept a shift to clock in", "Accept a shift to clock in",
style: TextStyle( style: UiTypography.body2r.textSecondary,
fontSize: 14,
color: Color(0xFF64748B), // slate-500
),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
], ],
@@ -393,15 +356,17 @@ class _ClockInPageState extends State<ClockInPage> {
// Checked In Banner // Checked In Banner
if (isCheckedIn && checkInTime != null) ...<Widget>[ if (isCheckedIn && checkInTime != null) ...<Widget>[
const SizedBox(height: 12), const SizedBox(height: UiConstants.space3),
Container( Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(UiConstants.space3),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFECFDF5), // emerald-50 color: UiColors.tagSuccess,
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
border: Border.all( border: Border.all(
color: const Color(0xFFA7F3D0), color: UiColors.success.withValues(
), // emerald-200 alpha: 0.3,
),
),
), ),
child: Row( child: Row(
mainAxisAlignment: mainAxisAlignment:
@@ -411,23 +376,15 @@ class _ClockInPageState extends State<ClockInPage> {
crossAxisAlignment: crossAxisAlignment:
CrossAxisAlignment.start, CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
const Text( Text(
"Checked in at", "Checked in at",
style: TextStyle( style: UiTypography.body3m.textSuccess,
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF059669),
),
), ),
Text( Text(
DateFormat( DateFormat(
'h:mm a', 'h:mm a',
).format(checkInTime), ).format(checkInTime),
style: const TextStyle( style: UiTypography.body1b.textSuccess,
fontSize: 16,
fontWeight: FontWeight.bold,
color: Color(0xFF065F46),
),
), ),
], ],
), ),
@@ -435,12 +392,12 @@ class _ClockInPageState extends State<ClockInPage> {
width: 40, width: 40,
height: 40, height: 40,
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: Color(0xFFD1FAE5), color: UiColors.tagActive,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: const Icon( child: const Icon(
LucideIcons.check, UiIcons.check,
color: Color(0xFF059669), color: UiColors.textSuccess,
), ),
), ),
], ],
@@ -476,14 +433,14 @@ class _ClockInPageState extends State<ClockInPage> {
child: GestureDetector( child: GestureDetector(
onTap: () => _bloc.add(CheckInModeChanged(value)), onTap: () => _bloc.add(CheckInModeChanged(value)),
child: Container( child: Container(
padding: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.symmetric(vertical: UiConstants.space2),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected ? Colors.white : Colors.transparent, color: isSelected ? UiColors.white : UiColors.transparent,
borderRadius: BorderRadius.circular(8), borderRadius: UiConstants.radiusMd,
boxShadow: isSelected boxShadow: isSelected
? <BoxShadow>[ ? <BoxShadow>[
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.05), color: UiColors.black.withValues(alpha: 0.05),
blurRadius: 2, blurRadius: 2,
offset: const Offset(0, 1), offset: const Offset(0, 1),
), ),
@@ -496,15 +453,15 @@ class _ClockInPageState extends State<ClockInPage> {
Icon( Icon(
icon, icon,
size: 16, size: 16,
color: isSelected ? Colors.black : Colors.grey, color: isSelected ? UiColors.foreground : UiColors.iconThird,
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
Text( Text(
label, label,
style: TextStyle( style: UiTypography.body2m.copyWith(
fontSize: 14, color: isSelected
fontWeight: FontWeight.w500, ? UiColors.foreground
color: isSelected ? Colors.black : Colors.grey, : UiColors.textSecondary,
), ),
), ),
], ],
@@ -534,36 +491,31 @@ class _ClockInPageState extends State<ClockInPage> {
height: 96, height: 96,
decoration: BoxDecoration( decoration: BoxDecoration(
color: scanned color: scanned
? Colors.green.shade50 ? UiColors.tagSuccess
: Colors.blue.shade50, : UiColors.tagInProgress,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: Icon( child: Icon(
scanned ? LucideIcons.check : LucideIcons.nfc, scanned ? UiIcons.check : UiIcons.nfc,
size: 48, size: 48,
color: scanned color: scanned ? UiColors.textSuccess : UiColors.primary,
? Colors.green.shade600
: Colors.blue.shade600,
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
Text( Text(
scanned ? 'Processing check-in...' : 'Ready to scan', scanned ? 'Processing check-in...' : 'Ready to scan',
style: const TextStyle( style: UiTypography.headline4m,
fontSize: 18,
fontWeight: FontWeight.w600,
),
), ),
const SizedBox(height: 8), const SizedBox(height: UiConstants.space2),
Text( Text(
scanned scanned
? 'Please wait...' ? 'Please wait...'
: 'Hold your phone near the NFC tag at the clock-in station', : 'Hold your phone near the NFC tag at the clock-in station',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle(fontSize: 14, color: Colors.grey.shade600), style: UiTypography.body2r.textSecondary,
), ),
if (!scanned) ...<Widget>[ if (!scanned) ...<Widget>[
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
height: 56, height: 56,
@@ -584,19 +536,16 @@ class _ClockInPageState extends State<ClockInPage> {
// But this dialog is just a function call. // But this dialog is just a function call.
// It's safer to just return a result // It's safer to just return a result
}, },
icon: const Icon(LucideIcons.nfc, size: 24), icon: const Icon(UiIcons.nfc, size: 24),
label: const Text( label: Text(
'Tap to Scan', 'Tap to Scan',
style: TextStyle( style: UiTypography.headline4m.white,
fontSize: 18,
fontWeight: FontWeight.w600,
),
), ),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0047FF), backgroundColor: UiColors.primary,
foregroundColor: Colors.white, foregroundColor: UiColors.white,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
), ),
), ),
), ),

View File

@@ -1,13 +0,0 @@
import 'package:flutter/material.dart';
class AppColors {
static const Color krowBlue = Color(0xFF0A39DF);
static const Color krowYellow = Color(0xFFFFED4A);
static const Color krowCharcoal = Color(0xFF121826);
static const Color krowMuted = Color(0xFF6A7382);
static const Color krowBorder = Color(0xFFE3E6E9);
static const Color krowBackground = Color(0xFFFAFBFC);
static const Color white = Colors.white;
static const Color black = Colors.black;
}

View File

@@ -1,10 +1,9 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
enum AttendanceType { checkin, checkout, breaks, days } enum AttendanceType { checkin, checkout, breaks, days }
class AttendanceCard extends StatelessWidget { class AttendanceCard extends StatelessWidget {
const AttendanceCard({ const AttendanceCard({
super.key, super.key,
required this.type, required this.type,
@@ -24,14 +23,14 @@ class AttendanceCard extends StatelessWidget {
final _AttendanceStyle styles = _getStyles(type); final _AttendanceStyle styles = _getStyles(type);
return Container( return Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(UiConstants.space3),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(16), borderRadius: UiConstants.radiusLg,
border: Border.all(color: Colors.grey.shade100), border: Border.all(color: UiColors.bgSecondary),
boxShadow: <BoxShadow>[ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.05), color: UiColors.black.withValues(alpha: 0.05),
blurRadius: 2, blurRadius: 2,
offset: const Offset(0, 1), offset: const Offset(0, 1),
), ),
@@ -46,17 +45,14 @@ class AttendanceCard extends StatelessWidget {
height: 32, height: 32,
decoration: BoxDecoration( decoration: BoxDecoration(
color: styles.bgColor, color: styles.bgColor,
borderRadius: BorderRadius.circular(8), borderRadius: UiConstants.radiusMd,
), ),
child: Icon(styles.icon, size: 16, color: styles.iconColor), child: Icon(styles.icon, size: 16, color: styles.iconColor),
), ),
const SizedBox(height: 8), const SizedBox(height: UiConstants.space2),
Text( Text(
title, title,
style: const TextStyle( style: UiTypography.titleUppercase4m.textSecondary,
fontSize: 11,
color: Color(0xFF64748B), // slate-500
),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@@ -65,27 +61,20 @@ class AttendanceCard extends StatelessWidget {
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
child: Text( child: Text(
value, value,
style: const TextStyle( style: UiTypography.headline4m,
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF0F172A), // slate-900
),
), ),
), ),
if (scheduledTime != null) ...<Widget>[ if (scheduledTime != null) ...<Widget>[
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
"Scheduled: $scheduledTime", "Scheduled: $scheduledTime",
style: const TextStyle( style: UiTypography.footnote2r.textInactive,
fontSize: 10,
color: Color(0xFF94A3B8), // slate-400
),
), ),
], ],
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
subtitle, subtitle,
style: const TextStyle(fontSize: 12, color: Color(0xFF0032A0)), style: UiTypography.footnote1r.copyWith(color: UiColors.primary),
), ),
], ],
), ),
@@ -96,27 +85,27 @@ class AttendanceCard extends StatelessWidget {
switch (type) { switch (type) {
case AttendanceType.checkin: case AttendanceType.checkin:
return _AttendanceStyle( return _AttendanceStyle(
icon: LucideIcons.logIn, icon: UiIcons.logIn,
bgColor: const Color(0xFF0032A0).withOpacity(0.1), bgColor: UiColors.primary.withValues(alpha: 0.1),
iconColor: const Color(0xFF0032A0), iconColor: UiColors.primary,
); );
case AttendanceType.checkout: case AttendanceType.checkout:
return _AttendanceStyle( return _AttendanceStyle(
icon: LucideIcons.logOut, icon: UiIcons.logOut,
bgColor: const Color(0xFF333F48).withOpacity(0.1), bgColor: UiColors.foreground.withValues(alpha: 0.1),
iconColor: const Color(0xFF333F48), iconColor: UiColors.foreground,
); );
case AttendanceType.breaks: case AttendanceType.breaks:
return _AttendanceStyle( return _AttendanceStyle(
icon: LucideIcons.coffee, icon: UiIcons.coffee,
bgColor: const Color(0xFFF9E547).withOpacity(0.2), bgColor: UiColors.accent.withValues(alpha: 0.2),
iconColor: const Color(0xFF4C460D), iconColor: UiColors.accentForeground,
); );
case AttendanceType.days: case AttendanceType.days:
return _AttendanceStyle( return _AttendanceStyle(
icon: LucideIcons.calendar, icon: UiIcons.calendar,
bgColor: Colors.green.withOpacity(0.1), bgColor: UiColors.success.withValues(alpha: 0.1),
iconColor: Colors.green, iconColor: UiColors.textSuccess,
); );
} }
} }

View File

@@ -1,6 +1,5 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
import '../theme/app_colors.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
enum CommuteMode { enum CommuteMode {
@@ -158,21 +157,21 @@ class _CommuteTrackerState extends State<CommuteTracker> {
Widget _buildConsentCard() { Widget _buildConsentCard() {
return Container( return Container(
margin: const EdgeInsets.only(bottom: 20), margin: const EdgeInsets.only(bottom: UiConstants.space5),
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(UiConstants.space3),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: const LinearGradient( gradient: LinearGradient(
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: <Color>[ colors: <Color>[
Color(0xFFEFF6FF), // blue-50 UiColors.primary.withValues(alpha: 0.05),
Color(0xFFECFEFF), // cyan-50 UiColors.primary.withValues(alpha: 0.1),
], ],
), ),
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
boxShadow: <BoxShadow>[ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.05), color: UiColors.black.withValues(alpha: 0.05),
blurRadius: 2, blurRadius: 2,
offset: const Offset(0, 1), offset: const Offset(0, 1),
), ),
@@ -188,42 +187,35 @@ class _CommuteTrackerState extends State<CommuteTracker> {
width: 32, width: 32,
height: 32, height: 32,
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: Color(0xFF2563EB), // blue-600 color: UiColors.primary,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: const Icon( child: const Icon(
LucideIcons.mapPin, UiIcons.mapPin,
size: 16, size: 16,
color: Colors.white, color: UiColors.white,
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: UiConstants.space3),
const Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text( Text(
'Enable Commute Tracking?', 'Enable Commute Tracking?',
style: TextStyle( style: UiTypography.body2m.textPrimary,
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF0F172A), // slate-900
),
), ),
SizedBox(height: 4), const SizedBox(height: UiConstants.space1),
Text( Text(
'Share location 1hr before shift so your manager can see you\'re on the way.', 'Share location 1hr before shift so your manager can see you\'re on the way.',
style: TextStyle( style: UiTypography.body4r.textSecondary,
fontSize: 12,
color: Color(0xFF475569), // slate-600
),
), ),
], ],
), ),
), ),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: UiConstants.space3),
Row( Row(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
@@ -232,25 +224,29 @@ class _CommuteTrackerState extends State<CommuteTracker> {
setState(() => _localHasConsent = false); setState(() => _localHasConsent = false);
}, },
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.symmetric(
side: const BorderSide(color: Color(0xFFE2E8F0)), vertical: UiConstants.space2,
),
side: const BorderSide(color: UiColors.border),
), ),
child: const Text('Not Now', style: TextStyle(fontSize: 12)), child: Text('Not Now', style: UiTypography.footnote1m),
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: UiConstants.space2),
Expanded( Expanded(
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: () {
setState(() => _localHasConsent = true); setState(() => _localHasConsent = true);
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2563EB), // blue-600 backgroundColor: UiColors.primary,
padding: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.symmetric(
vertical: UiConstants.space2,
),
), ),
child: const Text( child: Text(
'Enable', 'Enable',
style: TextStyle(fontSize: 12, color: Colors.white), style: UiTypography.footnote1m.white,
), ),
), ),
), ),
@@ -263,14 +259,14 @@ class _CommuteTrackerState extends State<CommuteTracker> {
Widget _buildPreShiftCard() { Widget _buildPreShiftCard() {
return Container( return Container(
margin: const EdgeInsets.only(bottom: 20), margin: const EdgeInsets.only(bottom: UiConstants.space5),
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(UiConstants.space3),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
boxShadow: <BoxShadow>[ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.05), color: UiColors.black.withValues(alpha: 0.05),
blurRadius: 2, blurRadius: 2,
offset: const Offset(0, 1), offset: const Offset(0, 1),
), ),
@@ -282,56 +278,46 @@ class _CommuteTrackerState extends State<CommuteTracker> {
width: 32, width: 32,
height: 32, height: 32,
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: Color(0xFFF1F5F9), // slate-100 color: UiColors.bgSecondary,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: const Icon( child: const Icon(
LucideIcons.navigation, UiIcons.navigation,
size: 16, size: 16,
color: Color(0xFF475569), // slate-600 color: UiColors.textSecondary,
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: UiConstants.space2),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Row( Row(
children: <Widget>[ children: <Widget>[
const Text( Text(
'On My Way', 'On My Way',
style: TextStyle( style: UiTypography.body2m.textPrimary,
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF0F172A), // slate-900
),
), ),
const SizedBox(width: 8), const SizedBox(width: UiConstants.space2),
Row( Row(
children: <Widget>[ children: <Widget>[
const Icon( const Icon(
LucideIcons.clock, UiIcons.clock,
size: 12, size: 12,
color: Color(0xFF64748B), // slate-500 color: UiColors.textInactive,
), ),
const SizedBox(width: 2), const SizedBox(width: 2),
Text( Text(
'Shift starts in ${_getMinutesUntilShift()} min', 'Shift starts in ${_getMinutesUntilShift()} min',
style: const TextStyle( style: UiTypography.titleUppercase4m.textSecondary,
fontSize: 11,
color: Color(0xFF64748B), // slate-500
),
), ),
], ],
), ),
], ],
), ),
const Text( Text(
'Track arrival', 'Track arrival',
style: TextStyle( style: UiTypography.titleUppercase4m.textSecondary,
fontSize: 10,
color: Color(0xFF64748B), // slate-500
),
), ),
], ],
), ),
@@ -342,7 +328,7 @@ class _CommuteTrackerState extends State<CommuteTracker> {
setState(() => _localIsCommuteOn = value); setState(() => _localIsCommuteOn = value);
widget.onCommuteToggled?.call(value); widget.onCommuteToggled?.call(value);
}, },
activeThumbColor: AppColors.krowBlue, activeThumbColor: UiColors.primary,
), ),
], ],
), ),
@@ -357,8 +343,8 @@ class _CommuteTrackerState extends State<CommuteTracker> {
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: <Color>[ colors: <Color>[
Color(0xFF2563EB), // blue-600 UiColors.primary,
Color(0xFF0891B2), // cyan-600 UiColors.iconActive,
], ],
), ),
), ),
@@ -368,7 +354,7 @@ class _CommuteTrackerState extends State<CommuteTracker> {
Expanded( Expanded(
child: Center( child: Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(UiConstants.space5),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
@@ -383,13 +369,13 @@ class _CommuteTrackerState extends State<CommuteTracker> {
width: 96, width: 96,
height: 96, height: 96,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2), color: UiColors.white.withValues(alpha: 0.2),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: const Icon( child: const Icon(
LucideIcons.navigation, UiIcons.navigation,
size: 48, size: 48,
color: Colors.white, color: UiColors.white,
), ),
), ),
); );
@@ -399,100 +385,84 @@ class _CommuteTrackerState extends State<CommuteTracker> {
setState(() {}); setState(() {});
}, },
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
const Text( Text(
'On My Way', 'On My Way',
style: TextStyle( style: UiTypography.displayMb.white,
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.white,
),
), ),
const SizedBox(height: 8), const SizedBox(height: UiConstants.space2),
Text( Text(
'Your manager can see you\'re heading to the site', 'Your manager can see you\'re heading to the site',
style: TextStyle( style: UiTypography.body2r.copyWith(
fontSize: 14, color: UiColors.primaryForeground.withValues(alpha: 0.8),
color: Colors.blue.shade100,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: 32), const SizedBox(height: UiConstants.space8),
if (widget.distanceMeters != null) ...<Widget>[ if (widget.distanceMeters != null) ...<Widget>[
Container( Container(
width: double.infinity, width: double.infinity,
constraints: const BoxConstraints(maxWidth: 300), constraints: const BoxConstraints(maxWidth: 300),
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(UiConstants.space5),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1), color: UiColors.white.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(16), borderRadius: UiConstants.radiusLg,
border: Border.all( border: Border.all(
color: Colors.white.withOpacity(0.2), color: UiColors.white.withValues(alpha: 0.2),
), ),
), ),
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
Text( Text(
'Distance to Site', 'Distance to Site',
style: TextStyle( style: UiTypography.body2r.copyWith(
fontSize: 14, color: UiColors.primaryForeground.withValues(alpha: 0.8),
color: Colors.blue.shade100,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: UiConstants.space1),
Text( Text(
_formatDistance(widget.distanceMeters!), _formatDistance(widget.distanceMeters!),
style: const TextStyle( style: UiTypography.displayM.white,
fontSize: 36,
fontWeight: FontWeight.bold,
color: Colors.white,
),
), ),
], ],
), ),
), ),
if (widget.etaMinutes != null) ...<Widget>[ if (widget.etaMinutes != null) ...<Widget>[
const SizedBox(height: 12), const SizedBox(height: UiConstants.space3),
Container( Container(
width: double.infinity, width: double.infinity,
constraints: const BoxConstraints(maxWidth: 300), constraints: const BoxConstraints(maxWidth: 300),
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(UiConstants.space5),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1), color: UiColors.white.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(16), borderRadius: UiConstants.radiusLg,
border: Border.all( border: Border.all(
color: Colors.white.withOpacity(0.2), color: UiColors.white.withValues(alpha: 0.2),
), ),
), ),
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
Text( Text(
'Estimated Arrival', 'Estimated Arrival',
style: TextStyle( style: UiTypography.body2r.copyWith(
fontSize: 14, color: UiColors.primaryForeground.withValues(alpha: 0.8),
color: Colors.blue.shade100,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: UiConstants.space1),
Text( Text(
'${widget.etaMinutes} min', '${widget.etaMinutes} min',
style: const TextStyle( style: UiTypography.headline1m.white,
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
),
), ),
], ],
), ),
), ),
], ],
], ],
const SizedBox(height: 32), const SizedBox(height: UiConstants.space8),
Text( Text(
'Most app features are locked while commute mode is on. You\'ll be able to clock in once you arrive.', 'Most app features are locked while commute mode is on. You\'ll be able to clock in once you arrive.',
style: TextStyle( style: UiTypography.footnote1r.copyWith(
fontSize: 12, color: UiColors.primaryForeground.withValues(alpha: 0.8),
color: Colors.blue.shade100,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
@@ -502,18 +472,20 @@ class _CommuteTrackerState extends State<CommuteTracker> {
), ),
), ),
Padding( Padding(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(UiConstants.space5),
child: OutlinedButton( child: OutlinedButton(
onPressed: () { onPressed: () {
setState(() => _localIsCommuteOn = false); setState(() => _localIsCommuteOn = false);
}, },
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
foregroundColor: Colors.white, foregroundColor: UiColors.white,
side: BorderSide(color: Colors.white.withOpacity(0.3)), side: BorderSide(color: UiColors.white.withValues(alpha: 0.3)),
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(
vertical: UiConstants.space4,
),
minimumSize: const Size(double.infinity, 48), minimumSize: const Size(double.infinity, 48),
), ),
child: const Text('Turn Off Commute Mode'), child: Text('Turn Off Commute Mode', style: UiTypography.buttonL),
), ),
), ),
], ],
@@ -524,21 +496,21 @@ class _CommuteTrackerState extends State<CommuteTracker> {
Widget _buildArrivedCard() { Widget _buildArrivedCard() {
return Container( return Container(
margin: const EdgeInsets.only(bottom: 20), margin: const EdgeInsets.only(bottom: UiConstants.space5),
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(UiConstants.space5),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: const LinearGradient( gradient: LinearGradient(
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: <Color>[ colors: <Color>[
Color(0xFFECFDF5), // emerald-50 UiColors.tagSuccess,
Color(0xFFD1FAE5), // green-50 UiColors.tagActive,
], ],
), ),
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
boxShadow: <BoxShadow>[ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.1), color: UiColors.black.withValues(alpha: 0.1),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -550,31 +522,24 @@ class _CommuteTrackerState extends State<CommuteTracker> {
width: 64, width: 64,
height: 64, height: 64,
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: Color(0xFF10B981), // emerald-500 color: UiColors.success,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: const Icon( child: const Icon(
LucideIcons.checkCircle, UiIcons.check,
size: 32, size: 32,
color: Colors.white, color: UiColors.white,
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: UiConstants.space4),
const Text( Text(
'You\'ve Arrived! 🎉', 'You\'ve Arrived! 🎉',
style: TextStyle( style: UiTypography.headline3m.textPrimary,
fontSize: 20,
fontWeight: FontWeight.bold,
color: Color(0xFF0F172A), // slate-900
),
), ),
const SizedBox(height: 8), const SizedBox(height: UiConstants.space2),
const Text( Text(
'You\'re at the shift location. Ready to clock in?', 'You\'re at the shift location. Ready to clock in?',
style: TextStyle( style: UiTypography.body2r.textSecondary,
fontSize: 14,
color: Color(0xFF475569), // slate-600
),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
], ],

View File

@@ -1,8 +1,8 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
class DateSelector extends StatelessWidget { class DateSelector extends StatelessWidget {
const DateSelector({ const DateSelector({
super.key, super.key,
required this.selectedDate, required this.selectedDate,
@@ -34,14 +34,16 @@ class DateSelector extends StatelessWidget {
onTap: () => onSelect(date), onTap: () => onSelect(date),
child: AnimatedContainer( child: AnimatedContainer(
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
margin: const EdgeInsets.symmetric(horizontal: 4), margin: const EdgeInsets.symmetric(
horizontal: UiConstants.space1,
),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected ? const Color(0xFF0032A0) : Colors.white, color: isSelected ? UiColors.primary : UiColors.white,
borderRadius: BorderRadius.circular(16), borderRadius: UiConstants.radiusLg,
boxShadow: isSelected boxShadow: isSelected
? <BoxShadow>[ ? <BoxShadow>[
BoxShadow( BoxShadow(
color: const Color(0xFF0032A0).withOpacity(0.3), color: UiColors.primary.withValues(alpha: 0.3),
blurRadius: 10, blurRadius: 10,
offset: const Offset(0, 4), offset: const Offset(0, 4),
), ),
@@ -53,33 +55,28 @@ class DateSelector extends StatelessWidget {
children: <Widget>[ children: <Widget>[
Text( Text(
DateFormat('d').format(date), DateFormat('d').format(date),
style: TextStyle( style: UiTypography.title1m.copyWith(
fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: isSelected color:
? Colors.white isSelected ? UiColors.white : UiColors.foreground,
: const Color(0xFF0F172A),
), ),
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
DateFormat('E').format(date), DateFormat('E').format(date),
style: TextStyle( style: UiTypography.footnote2r.copyWith(
fontSize: 12,
color: isSelected color: isSelected
? Colors.white.withOpacity(0.8) ? UiColors.white.withValues(alpha: 0.8)
: const Color(0xFF94A3B8), : UiColors.textInactive,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: UiConstants.space1),
if (hasShift) if (hasShift)
Container( Container(
width: 6, width: 6,
height: 6, height: 6,
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected color: isSelected ? UiColors.white : UiColors.primary,
? Colors.white
: const Color(0xFF0032A0),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
) )
@@ -87,8 +84,8 @@ class DateSelector extends StatelessWidget {
Container( Container(
width: 6, width: 6,
height: 6, height: 6,
decoration: BoxDecoration( decoration: const BoxDecoration(
color: Colors.grey.shade300, color: UiColors.border,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
) )

View File

@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:lucide_icons/lucide_icons.dart'; import 'package:flutter/material.dart';
class LocationMapPlaceholder extends StatelessWidget { class LocationMapPlaceholder extends StatelessWidget {
@@ -18,8 +17,8 @@ class LocationMapPlaceholder extends StatelessWidget {
height: 200, height: 200,
width: double.infinity, width: double.infinity,
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFE2E8F0), color: UiColors.border,
borderRadius: BorderRadius.circular(16), borderRadius: UiConstants.radiusLg,
image: DecorationImage( image: DecorationImage(
image: const NetworkImage( image: const NetworkImage(
'https://maps.googleapis.com/maps/api/staticmap?center=40.7128,-74.0060&zoom=15&size=600x300&maptype=roadmap&markers=color:red%7C40.7128,-74.0060&key=YOUR_API_KEY', 'https://maps.googleapis.com/maps/api/staticmap?center=40.7128,-74.0060&zoom=15&size=600x300&maptype=roadmap&markers=color:red%7C40.7128,-74.0060&key=YOUR_API_KEY',
@@ -33,30 +32,37 @@ class LocationMapPlaceholder extends StatelessWidget {
child: Stack( child: Stack(
children: <Widget>[ children: <Widget>[
// Fallback UI if image fails (which it will without key) // Fallback UI if image fails (which it will without key)
const Center( Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Icon(LucideIcons.mapPin, size: 48, color: UiColors.iconSecondary), const Icon(
SizedBox(height: 8), UiIcons.mapPin,
Text('Map View (GPS)', style: TextStyle(color: UiColors.textSecondary)), size: 48,
color: UiColors.iconSecondary,
),
const SizedBox(height: UiConstants.space2),
Text('Map View (GPS)', style: UiTypography.body2r.textSecondary),
], ],
), ),
), ),
// Status Overlay // Status Overlay
Positioned( Positioned(
bottom: 16, bottom: UiConstants.space4,
left: 16, left: UiConstants.space4,
right: 16, right: UiConstants.space4,
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space4,
vertical: UiConstants.space3,
),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
boxShadow: <BoxShadow>[ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.1), color: UiColors.black.withValues(alpha: 0.1),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 4), offset: const Offset(0, 4),
), ),
@@ -65,23 +71,25 @@ class LocationMapPlaceholder extends StatelessWidget {
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
Icon( Icon(
isVerified ? LucideIcons.checkCircle : LucideIcons.alertCircle, isVerified ? UiIcons.checkCircle : UiIcons.warning,
color: isVerified ? UiColors.textSuccess : UiColors.destructive, color: isVerified
? UiColors.textSuccess
: UiColors.destructive,
size: 20, size: 20,
), ),
const SizedBox(width: 12), const SizedBox(width: UiConstants.space3),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text( Text(
isVerified ? 'Location Verified' : 'Location Check', isVerified ? 'Location Verified' : 'Location Check',
style: UiTypography.body1b.copyWith(color: UiColors.textPrimary), style: UiTypography.body1b.textPrimary,
), ),
if (distance != null) if (distance != null)
Text( Text(
'${distance!.toStringAsFixed(0)}m from venue', '${distance!.toStringAsFixed(0)}m from venue',
style: UiTypography.body2r.copyWith(color: UiColors.textSecondary), style: UiTypography.body2r.textSecondary,
), ),
], ],
), ),

View File

@@ -1,8 +1,7 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
class LunchBreakDialog extends StatefulWidget { class LunchBreakDialog extends StatefulWidget {
const LunchBreakDialog({super.key, required this.onComplete}); const LunchBreakDialog({super.key, required this.onComplete});
final VoidCallback onComplete; final VoidCallback onComplete;
@@ -47,8 +46,10 @@ class _LunchBreakDialogState extends State<LunchBreakDialog> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Dialog( return Dialog(
backgroundColor: Colors.white, backgroundColor: UiColors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(UiConstants.space6),
),
child: AnimatedSwitcher( child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
child: _buildCurrentStep(), child: _buildCurrentStep(),
@@ -75,34 +76,30 @@ class _LunchBreakDialogState extends State<LunchBreakDialog> {
Widget _buildStep1() { Widget _buildStep1() {
return Padding( return Padding(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(UiConstants.space6),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Container( Container(
width: 80, width: 80,
height: 80, height: 80,
decoration: BoxDecoration( decoration: const BoxDecoration(
color: Colors.grey.shade100, color: UiColors.bgSecondary,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: const Icon( child: const Icon(
LucideIcons.coffee, UiIcons.coffee,
size: 40, size: 40,
color: Color(0xFF6A7382), color: UiColors.iconSecondary,
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
const Text( Text(
"Did You Take\na Lunch?", "Did You Take\na Lunch?",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: UiTypography.headline1m.textPrimary,
fontSize: 24,
fontWeight: FontWeight.bold,
color: Color(0xFF121826),
),
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
Row( Row(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
@@ -114,25 +111,23 @@ class _LunchBreakDialogState extends State<LunchBreakDialog> {
}); });
}, },
child: Container( child: Container(
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(
vertical: UiConstants.space4,
),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300), border: Border.all(color: UiColors.border),
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
color: Colors.transparent, color: UiColors.transparent,
), ),
alignment: Alignment.center, alignment: Alignment.center,
child: const Text( child: Text(
"No", "No",
style: TextStyle( style: UiTypography.body1m.textPrimary,
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF121826),
),
), ),
), ),
), ),
), ),
const SizedBox(width: 16), const SizedBox(width: UiConstants.space4),
Expanded( Expanded(
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: () {
@@ -142,19 +137,17 @@ class _LunchBreakDialogState extends State<LunchBreakDialog> {
}); });
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0032A0), backgroundColor: UiColors.primary,
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(
vertical: UiConstants.space4,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
), ),
), ),
child: const Text( child: Text(
"Yes", "Yes",
style: TextStyle( style: UiTypography.body1m.white,
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
),
), ),
), ),
), ),
@@ -168,155 +161,183 @@ class _LunchBreakDialogState extends State<LunchBreakDialog> {
Widget _buildStep2() { Widget _buildStep2() {
// Time input // Time input
return Padding( return Padding(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(UiConstants.space6),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
const Text( Text(
"When did you take lunch?", "When did you take lunch?",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), style: UiTypography.headline4m,
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
// Mock Inputs // Mock Inputs
Row( Row(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: DropdownButtonFormField<String>( child: DropdownButtonFormField<String>(
isExpanded: true, isExpanded: true,
initialValue: _breakStart, value: _breakStart,
items: _timeOptions.map((String t) => DropdownMenuItem(value: t, child: Text(t, style: const TextStyle(fontSize: 13)))).toList(), items: _timeOptions
onChanged: (String? v) => setState(() => _breakStart = v), .map(
decoration: const InputDecoration( (String t) => DropdownMenuItem(
labelText: 'Start', value: t,
contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8), child: Text(t, style: UiTypography.body3r),
), ),
), )
), .toList(),
const SizedBox(width: 10), onChanged: (String? v) => setState(() => _breakStart = v),
Expanded( decoration: const InputDecoration(
child: DropdownButtonFormField<String>( labelText: 'Start',
isExpanded: true, contentPadding: EdgeInsets.symmetric(
initialValue: _breakEnd, horizontal: 10,
items: _timeOptions.map((String t) => DropdownMenuItem(value: t, child: Text(t, style: const TextStyle(fontSize: 13)))).toList(), vertical: 8,
onChanged: (String? v) => setState(() => _breakEnd = v), ),
decoration: const InputDecoration( ),
labelText: 'End',
contentPadding: EdgeInsets.symmetric(horizontal: 10, vertical: 8),
),
),
),
],
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () {
setState(() => _step = 3);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0032A0),
minimumSize: const Size(double.infinity, 48),
), ),
child: const Text("Next", style: TextStyle(color: Colors.white)), ),
const SizedBox(width: 10),
Expanded(
child: DropdownButtonFormField<String>(
isExpanded: true,
value: _breakEnd,
items: _timeOptions
.map(
(String t) => DropdownMenuItem(
value: t,
child: Text(t, style: UiTypography.body3r),
),
)
.toList(),
onChanged: (String? v) => setState(() => _breakEnd = v),
decoration: const InputDecoration(
labelText: 'End',
contentPadding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 8,
),
),
),
),
],
),
const SizedBox(height: UiConstants.space6),
ElevatedButton(
onPressed: () {
setState(() => _step = 3);
},
style: ElevatedButton.styleFrom(
backgroundColor: UiColors.primary,
minimumSize: const Size(double.infinity, 48),
), ),
], child: Text("Next", style: UiTypography.body1m.white),
)); ),
],
),
);
} }
Widget _buildStep2b() { Widget _buildStep2b() {
// No lunch reason // No lunch reason
return Padding( return Padding(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(UiConstants.space6),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[ children: <Widget>[
const Text( Text(
"Why didn't you take lunch?", "Why didn't you take lunch?",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), style: UiTypography.headline4m,
),
const SizedBox(height: UiConstants.space4),
..._noLunchReasons.map(
(String reason) => RadioListTile<String>(
title: Text(reason, style: UiTypography.body2r),
value: reason,
groupValue: _noLunchReason,
onChanged: (String? val) => setState(() => _noLunchReason = val),
activeColor: UiColors.primary,
), ),
const SizedBox(height: 16), ),
..._noLunchReasons.map((String reason) => RadioListTile<String>(
title: Text(reason),
value: reason,
groupValue: _noLunchReason,
onChanged: (String? val) => setState(() => _noLunchReason = val),
)),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
setState(() => _step = 3); setState(() => _step = 3);
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0032A0), backgroundColor: UiColors.primary,
minimumSize: const Size(double.infinity, 48), minimumSize: const Size(double.infinity, 48),
),
child: const Text("Next", style: TextStyle(color: Colors.white)),
), ),
], child: Text("Next", style: UiTypography.body1m.white),
)); ),
],
),
);
} }
Widget _buildStep3() { Widget _buildStep3() {
// Additional Notes // Additional Notes
return Padding( return Padding(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(UiConstants.space6),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
const Text( Text(
"Additional Notes", "Additional Notes",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), style: UiTypography.headline4m,
),
const SizedBox(height: UiConstants.space4),
TextField(
onChanged: (String v) => _additionalNotes = v,
style: UiTypography.body2r,
decoration: const InputDecoration(
hintText: 'Add any details...',
border: OutlineInputBorder(),
), ),
const SizedBox(height: 16), maxLines: 3,
TextField( ),
onChanged: (String v) => _additionalNotes = v,
decoration: const InputDecoration(
hintText: 'Add any details...',
border: OutlineInputBorder(),
),
maxLines: 3,
),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
setState(() => _step = 4); setState(() => _step = 4);
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0032A0), backgroundColor: UiColors.primary,
minimumSize: const Size(double.infinity, 48), minimumSize: const Size(double.infinity, 48),
),
child: const Text("Submit", style: TextStyle(color: Colors.white)),
), ),
], child: Text("Submit", style: UiTypography.body1m.white),
)); ),
],
),
);
} }
Widget _buildStep4() { Widget _buildStep4() {
// Success // Success
return Padding( return Padding(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(UiConstants.space6),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
const Icon(LucideIcons.checkCircle, size: 64, color: Colors.green), const Icon(UiIcons.checkCircle, size: 64, color: UiColors.success),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
const Text( Text(
"Break Logged!", "Break Logged!",
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), style: UiTypography.headline1m,
),
const SizedBox(height: UiConstants.space6),
ElevatedButton(
onPressed: widget.onComplete,
style: ElevatedButton.styleFrom(
backgroundColor: UiColors.primary,
minimumSize: const Size(double.infinity, 48),
), ),
const SizedBox(height: 24), child: Text("Close", style: UiTypography.body1m.white),
ElevatedButton( ),
onPressed: widget.onComplete, ],
style: ElevatedButton.styleFrom( ),
backgroundColor: const Color(0xFF0032A0), );
minimumSize: const Size(double.infinity, 48),
),
child: const Text("Close", style: TextStyle(color: Colors.white)),
),
],
));
} }
} }

View File

@@ -1,8 +1,7 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
class SwipeToCheckIn extends StatefulWidget { class SwipeToCheckIn extends StatefulWidget {
const SwipeToCheckIn({ const SwipeToCheckIn({
super.key, super.key,
this.onCheckIn, this.onCheckIn,
@@ -73,8 +72,8 @@ class _SwipeToCheckInState extends State<SwipeToCheckIn>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Color baseColor = widget.isCheckedIn final Color baseColor = widget.isCheckedIn
? const Color(0xFF10B981) ? UiColors.success
: const Color(0xFF0032A0); : UiColors.primary;
if (widget.mode == 'nfc') { if (widget.mode == 'nfc') {
return GestureDetector( return GestureDetector(
@@ -93,10 +92,10 @@ class _SwipeToCheckInState extends State<SwipeToCheckIn>
height: 56, height: 56,
decoration: BoxDecoration( decoration: BoxDecoration(
color: baseColor, color: baseColor,
borderRadius: BorderRadius.circular(16), borderRadius: UiConstants.radiusLg,
boxShadow: <BoxShadow>[ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: baseColor.withOpacity(0.4), color: baseColor.withValues(alpha: 0.4),
blurRadius: 25, blurRadius: 25,
offset: const Offset(0, 10), offset: const Offset(0, 10),
spreadRadius: -5, spreadRadius: -5,
@@ -106,19 +105,15 @@ class _SwipeToCheckInState extends State<SwipeToCheckIn>
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
const Icon(LucideIcons.wifi, color: Colors.white), const Icon(UiIcons.wifi, color: UiColors.white),
const SizedBox(width: 12), const SizedBox(width: UiConstants.space3),
Text( Text(
widget.isLoading widget.isLoading
? (widget.isCheckedIn ? (widget.isCheckedIn
? "Checking out..." ? "Checking out..."
: "Checking in...") : "Checking in...")
: (widget.isCheckedIn ? "NFC Check Out" : "NFC Check In"), : (widget.isCheckedIn ? "NFC Check Out" : "NFC Check In"),
style: const TextStyle( style: UiTypography.body1b.white,
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18,
),
), ),
], ],
), ),
@@ -134,11 +129,11 @@ class _SwipeToCheckInState extends State<SwipeToCheckIn>
// Calculate background color based on drag // Calculate background color based on drag
final double progress = _dragValue / maxDrag; final double progress = _dragValue / maxDrag;
final Color startColor = widget.isCheckedIn final Color startColor = widget.isCheckedIn
? const Color(0xFF10B981) ? UiColors.success
: const Color(0xFF0032A0); : UiColors.primary;
final Color endColor = widget.isCheckedIn final Color endColor = widget.isCheckedIn
? const Color(0xFF0032A0) ? UiColors.primary
: const Color(0xFF10B981); : UiColors.success;
final Color currentColor = final Color currentColor =
Color.lerp(startColor, endColor, progress) ?? startColor; Color.lerp(startColor, endColor, progress) ?? startColor;
@@ -146,10 +141,10 @@ class _SwipeToCheckInState extends State<SwipeToCheckIn>
height: 56, height: 56,
decoration: BoxDecoration( decoration: BoxDecoration(
color: currentColor, color: currentColor,
borderRadius: BorderRadius.circular(16), borderRadius: UiConstants.radiusLg,
boxShadow: <BoxShadow>[ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.1), color: UiColors.black.withValues(alpha: 0.1),
blurRadius: 4, blurRadius: 4,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -164,11 +159,7 @@ class _SwipeToCheckInState extends State<SwipeToCheckIn>
widget.isCheckedIn widget.isCheckedIn
? "Swipe to Check Out" ? "Swipe to Check Out"
: "Swipe to Check In", : "Swipe to Check In",
style: TextStyle( style: UiTypography.body1b,
color: Colors.white.withOpacity(0.8),
fontWeight: FontWeight.w600,
fontSize: 18,
),
), ),
), ),
), ),
@@ -176,28 +167,26 @@ class _SwipeToCheckInState extends State<SwipeToCheckIn>
Center( Center(
child: Text( child: Text(
widget.isCheckedIn ? "Check Out!" : "Check In!", widget.isCheckedIn ? "Check Out!" : "Check In!",
style: const TextStyle( style: UiTypography.body1b,
color: Colors.white,
fontWeight: FontWeight.w600,
fontSize: 18,
),
), ),
), ),
Positioned( Positioned(
left: 4 + _dragValue, left: 4 + _dragValue,
top: 4, top: 4,
child: GestureDetector( child: GestureDetector(
onHorizontalDragUpdate: (DragUpdateDetails d) => _onDragUpdate(d, maxWidth), onHorizontalDragUpdate: (DragUpdateDetails d) =>
onHorizontalDragEnd: (DragEndDetails d) => _onDragEnd(d, maxWidth), _onDragUpdate(d, maxWidth),
onHorizontalDragEnd: (DragEndDetails d) =>
_onDragEnd(d, maxWidth),
child: Container( child: Container(
width: _handleSize, width: _handleSize,
height: _handleSize, height: _handleSize,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
boxShadow: <BoxShadow>[ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.1), color: UiColors.black.withValues(alpha: 0.1),
blurRadius: 2, blurRadius: 2,
offset: const Offset(0, 1), offset: const Offset(0, 1),
), ),
@@ -205,9 +194,7 @@ class _SwipeToCheckInState extends State<SwipeToCheckIn>
), ),
child: Center( child: Center(
child: Icon( child: Icon(
_isComplete _isComplete ? UiIcons.check : UiIcons.arrowRight,
? LucideIcons.check
: LucideIcons.arrowRight,
color: startColor, color: startColor,
), ),
), ),

View File

@@ -46,10 +46,8 @@ class HomeRepositoryImpl
.dayEnd(_toTimestamp(end)) .dayEnd(_toTimestamp(end))
.execute()); .execute());
// Filter for ACCEPTED applications (same logic as shifts_repository_impl) // Filter for CONFIRMED applications (same logic as shifts_repository_impl)
final apps = response.data.applications.where((app) => final apps = response.data.applications.where((app) =>
(app.status is Known &&
(app.status as Known).value == ApplicationStatus.ACCEPTED) ||
(app.status is Known && (app.status is Known &&
(app.status as Known).value == ApplicationStatus.CONFIRMED)); (app.status as Known).value == ApplicationStatus.CONFIRMED));
@@ -145,4 +143,3 @@ class HomeRepositoryImpl
); );
} }
} }

View File

@@ -3,9 +3,9 @@ import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:staff_home/src/presentation/blocs/home_cubit.dart';
import 'package:krow_core/core.dart'; import 'package:krow_core/core.dart';
import 'package:staff_home/src/presentation/blocs/home_cubit.dart';
import 'package:staff_home/src/presentation/widgets/home_page/empty_state_widget.dart'; import 'package:staff_home/src/presentation/widgets/home_page/empty_state_widget.dart';
import 'package:staff_home/src/presentation/widgets/home_page/home_header.dart'; import 'package:staff_home/src/presentation/widgets/home_page/home_header.dart';
import 'package:staff_home/src/presentation/widgets/home_page/placeholder_banner.dart'; import 'package:staff_home/src/presentation/widgets/home_page/placeholder_banner.dart';
@@ -84,28 +84,28 @@ class WorkerHomePage extends StatelessWidget {
children: [ children: [
Expanded( Expanded(
child: QuickActionItem( child: QuickActionItem(
icon: LucideIcons.search, icon: UiIcons.search,
label: quickI18n.find_shifts, label: quickI18n.find_shifts,
onTap: () => Modular.to.toShifts(), onTap: () => Modular.to.toShifts(),
), ),
), ),
Expanded( Expanded(
child: QuickActionItem( child: QuickActionItem(
icon: LucideIcons.calendar, icon: UiIcons.calendar,
label: quickI18n.availability, label: quickI18n.availability,
onTap: () => Modular.to.toAvailability(), onTap: () => Modular.to.toAvailability(),
), ),
), ),
Expanded( Expanded(
child: QuickActionItem( child: QuickActionItem(
icon: LucideIcons.dollarSign, icon: UiIcons.dollar,
label: quickI18n.earnings, label: quickI18n.earnings,
onTap: () => Modular.to.toPayments(), onTap: () => Modular.to.toPayments(),
), ),
), ),
], ],
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
// Today's Shifts // Today's Shifts
BlocBuilder<HomeCubit, HomeState>( BlocBuilder<HomeCubit, HomeState>(
@@ -124,9 +124,11 @@ class WorkerHomePage extends StatelessWidget {
if (state.status == HomeStatus.loading) if (state.status == HomeStatus.loading)
const Center( const Center(
child: SizedBox( child: SizedBox(
height: 40, height: UiConstants.space10,
width: 40, width: UiConstants.space10,
child: CircularProgressIndicator(), child: CircularProgressIndicator(
color: UiColors.primary,
),
), ),
) )
else if (shifts.isEmpty) else if (shifts.isEmpty)
@@ -150,7 +152,7 @@ class WorkerHomePage extends StatelessWidget {
); );
}, },
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
// Tomorrow's Shifts // Tomorrow's Shifts
BlocBuilder<HomeCubit, HomeState>( BlocBuilder<HomeCubit, HomeState>(
@@ -178,7 +180,7 @@ class WorkerHomePage extends StatelessWidget {
); );
}, },
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
// Recommended Shifts // Recommended Shifts
SectionHeader( SectionHeader(
@@ -198,7 +200,8 @@ class WorkerHomePage extends StatelessWidget {
itemCount: state.recommendedShifts.length, itemCount: state.recommendedShifts.length,
clipBehavior: Clip.none, clipBehavior: Clip.none,
itemBuilder: (context, index) => Padding( itemBuilder: (context, index) => Padding(
padding: const EdgeInsets.only(right: 12), padding: const EdgeInsets.only(
right: UiConstants.space3),
child: RecommendedShiftCard( child: RecommendedShiftCard(
shift: state.recommendedShifts[index], shift: state.recommendedShifts[index],
), ),
@@ -207,7 +210,7 @@ class WorkerHomePage extends StatelessWidget {
); );
}, },
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
], ],
), ),
), ),

View File

@@ -28,23 +28,20 @@ class HomeHeader extends StatelessWidget {
spacing: UiConstants.space3, spacing: UiConstants.space3,
children: [ children: [
Container( Container(
width: 48, width: UiConstants.space12,
height: 48, height: UiConstants.space12,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all( border: Border.all(
color: UiColors.primary.withOpacity(0.2), color: UiColors.primary.withValues(alpha: 0.2),
width: 2, width: 2,
), ),
), ),
child: CircleAvatar( child: CircleAvatar(
backgroundColor: UiColors.primary.withOpacity(0.1), backgroundColor: UiColors.primary.withValues(alpha: 0.1),
child: Text( child: Text(
initial, initial,
style: const TextStyle( style: UiTypography.body1b.textPrimary,
color: UiColors.primary,
fontWeight: FontWeight.bold,
),
), ),
), ),
), ),

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:core_localization/core_localization.dart'; import 'package:core_localization/core_localization.dart';
@@ -42,7 +42,7 @@ class PendingPaymentCard extends StatelessWidget {
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: const Icon( child: const Icon(
LucideIcons.dollarSign, UiIcons.dollar,
color: UiColors.primary, color: UiColors.primary,
size: 20, size: 20,
), ),
@@ -76,7 +76,7 @@ class PendingPaymentCard extends StatelessWidget {
), ),
SizedBox(width: UiConstants.space2), SizedBox(width: UiConstants.space2),
Icon( Icon(
LucideIcons.chevronRight, UiIcons.chevronRight,
color: UiColors.mutedForeground, color: UiColors.mutedForeground,
size: 20, size: 20,
), ),

View File

@@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
@@ -29,19 +29,19 @@ class PlaceholderBanner extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: bg, color: bg,
borderRadius: BorderRadius.circular(UiConstants.radiusBase), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
border: Border.all(color: accent.withOpacity(0.3)), border: Border.all(color: accent.withValues(alpha: 0.3)),
), ),
child: Row( child: Row(
children: [ children: [
Container( Container(
width: 40, width: UiConstants.space10,
height: 40, height: UiConstants.space10,
padding: const EdgeInsets.all(UiConstants.space2), padding: const EdgeInsets.all(UiConstants.space2),
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: UiColors.bgBanner, color: UiColors.bgBanner,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: Icon(LucideIcons.sparkles, color: accent, size: 20), child: Icon(UiIcons.sparkles, color: accent, size: UiConstants.space5),
), ),
const SizedBox(width: UiConstants.space3), const SizedBox(width: UiConstants.space3),
Expanded( Expanded(
@@ -59,7 +59,7 @@ class PlaceholderBanner extends StatelessWidget {
], ],
), ),
), ),
Icon(LucideIcons.chevronRight, color: accent), Icon(UiIcons.chevronRight, color: accent),
], ],
), ),
), ),

View File

@@ -22,8 +22,8 @@ class QuickActionItem extends StatelessWidget {
child: Column( child: Column(
children: [ children: [
Container( Container(
width: 64, width: UiConstants.space16,
height: 64, height: UiConstants.space16,
padding: const EdgeInsets.all(UiConstants.space4), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.bgBanner, color: UiColors.bgBanner,
@@ -31,13 +31,13 @@ class QuickActionItem extends StatelessWidget {
border: Border.all(color: UiColors.bgSecondary), border: Border.all(color: UiColors.bgSecondary),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: UiColors.foreground.withOpacity(0.05), color: UiColors.foreground.withValues(alpha: 0.05),
blurRadius: 4, blurRadius: 4,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
], ],
), ),
child: Icon(icon, color: UiColors.primary, size: 24), child: Icon(icon, color: UiColors.primary, size: UiConstants.space6),
), ),
const SizedBox(height: UiConstants.space2), const SizedBox(height: UiConstants.space2),
Text( Text(

View File

@@ -3,7 +3,7 @@ import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:krow_core/core.dart'; import 'package:krow_core/core.dart';
class RecommendedShiftCard extends StatelessWidget { class RecommendedShiftCard extends StatelessWidget {
@@ -14,8 +14,6 @@ class RecommendedShiftCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final recI18n = t.staff.home.recommended_card; final recI18n = t.staff.home.recommended_card;
final duration = 8;
final totalPay = duration * shift.hourlyRate;
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
@@ -23,14 +21,14 @@ class RecommendedShiftCard extends StatelessWidget {
}, },
child: Container( child: Container(
width: 300, width: 300,
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
border: Border.all(color: UiColors.border), border: Border.all(color: UiColors.border),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.02), color: UiColors.black.withValues(alpha: 0.02),
blurRadius: 4, blurRadius: 4,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -45,51 +43,43 @@ class RecommendedShiftCard extends StatelessWidget {
children: [ children: [
Text( Text(
recI18n.act_now, recI18n.act_now,
style: const TextStyle( style: UiTypography.body3m.copyWith(color: UiColors.textError),
fontSize: 10,
fontWeight: FontWeight.bold,
color: Color(0xFFDC2626),
),
), ),
const SizedBox(width: 8), const SizedBox(width: UiConstants.space2),
Container( Container(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 8, horizontal: UiConstants.space2,
vertical: 2, vertical: 2,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFE8F0FF), color: UiColors.tagInProgress,
borderRadius: BorderRadius.circular(999), borderRadius: UiConstants.radiusFull,
), ),
child: Text( child: Text(
recI18n.one_day, recI18n.one_day,
style: const TextStyle( style: UiTypography.body3m.textPrimary,
fontSize: 10,
fontWeight: FontWeight.w500,
color: Color(0xFF0047FF),
),
), ),
), ),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: UiConstants.space3),
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( Container(
width: 44, width: UiConstants.space10,
height: 44, height: UiConstants.space10,
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFE8F0FF), color: UiColors.tagInProgress,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
), ),
child: const Icon( child: const Icon(
LucideIcons.calendar, UiIcons.calendar,
color: Color(0xFF0047FF), color: UiColors.primary,
size: 20, size: UiConstants.space5,
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: UiConstants.space3),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -97,25 +87,16 @@ class RecommendedShiftCard extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded( Flexible(
child: Text( child: Text(
shift.title, shift.title,
style: const TextStyle( style: UiTypography.body1m.textPrimary,
fontWeight: FontWeight.w600,
fontSize: 16,
color: UiColors.foreground,
),
maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
Text( Text(
'\$${totalPay.round()}', '\$${shift.hourlyRate}/h',
style: const TextStyle( style: UiTypography.headline4m.textPrimary,
fontSize: 18,
fontWeight: FontWeight.bold,
color: UiColors.foreground,
),
), ),
], ],
), ),
@@ -125,17 +106,11 @@ class RecommendedShiftCard extends StatelessWidget {
children: [ children: [
Text( Text(
shift.clientName, shift.clientName,
style: const TextStyle( style: UiTypography.body3r.textSecondary,
fontSize: 12,
color: UiColors.mutedForeground,
),
), ),
Text( Text(
'\$${shift.hourlyRate.toStringAsFixed(0)}/hr${duration}h', '\$${shift.hourlyRate.toStringAsFixed(0)}/hr',
style: const TextStyle( style: UiTypography.body3r.textSecondary,
fontSize: 10,
color: UiColors.mutedForeground,
),
), ),
], ],
), ),
@@ -144,57 +119,48 @@ class RecommendedShiftCard extends StatelessWidget {
), ),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: UiConstants.space3),
Row( Row(
children: [ children: [
const Icon( const Icon(
LucideIcons.calendar, UiIcons.calendar,
size: 14, size: UiConstants.space3,
color: UiColors.mutedForeground, color: UiColors.mutedForeground,
), ),
const SizedBox(width: 4), const SizedBox(width: UiConstants.space1),
Text( Text(
recI18n.today, recI18n.today,
style: const TextStyle( style: UiTypography.body3r.textSecondary,
fontSize: 12,
color: UiColors.mutedForeground,
),
), ),
const SizedBox(width: 12), const SizedBox(width: UiConstants.space3),
const Icon( const Icon(
LucideIcons.clock, UiIcons.clock,
size: 14, size: UiConstants.space3,
color: UiColors.mutedForeground, color: UiColors.mutedForeground,
), ),
const SizedBox(width: 4), const SizedBox(width: UiConstants.space1),
Text( Text(
recI18n.time_range( recI18n.time_range(
start: shift.startTime, start: shift.startTime,
end: shift.endTime, end: shift.endTime,
), ),
style: const TextStyle( style: UiTypography.body3r.textSecondary,
fontSize: 12,
color: UiColors.mutedForeground,
),
), ),
], ],
), ),
const SizedBox(height: 4), const SizedBox(height: UiConstants.space1),
Row( Row(
children: [ children: [
const Icon( const Icon(
LucideIcons.mapPin, UiIcons.mapPin,
size: 14, size: UiConstants.space3,
color: UiColors.mutedForeground, color: UiColors.mutedForeground,
), ),
const SizedBox(width: 4), const SizedBox(width: UiConstants.space1),
Expanded( Expanded(
child: Text( child: Text(
shift.locationAddress, shift.locationAddress,
style: const TextStyle( style: UiTypography.body3r.textSecondary,
fontSize: 12,
color: UiColors.mutedForeground,
),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),

View File

@@ -1,9 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
/// Section header widget for home page sections, using design system tokens. /// Section header widget for home page sections, using design system tokens.
class SectionHeader extends StatelessWidget { class SectionHeader extends StatelessWidget {
/// Section title /// Section title
@@ -23,43 +22,58 @@ class SectionHeader extends StatelessWidget {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Expanded(
title, child: action != null
style: UiTypography.headline4m, ? Row(
), mainAxisAlignment: MainAxisAlignment.spaceBetween,
if (action != null) children: [
if (onAction != null) Text(
GestureDetector( title,
onTap: onAction, style: UiTypography.body2m.textPrimary,
child: Row( ),
children: [ if (onAction != null)
Text( GestureDetector(
action!, onTap: onAction,
style: UiTypography.body2m.copyWith(color: UiColors.primary), child: Row(
), children: [
const Icon( Text(
LucideIcons.chevronRight, action ?? '',
size: 16, style: UiTypography.body3r.textPrimary,
color: UiColors.primary, ),
), const Icon(
], UiIcons.chevronRight,
), size: UiConstants.space4,
) color: UiColors.primary,
else ),
Container( ],
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), ),
decoration: BoxDecoration( )
color: UiColors.primary.withOpacity(0.08), else
borderRadius: BorderRadius.circular(12), Container(
border: Border.all( padding: const EdgeInsets.symmetric(
color: UiColors.primary.withOpacity(0.2), horizontal: UiConstants.space2,
vertical: 2,
),
decoration: BoxDecoration(
color: UiColors.primary.withValues(alpha: 0.08),
borderRadius:
BorderRadius.circular(UiConstants.radiusBase),
border: Border.all(
color: UiColors.primary.withValues(alpha: 0.2),
),
),
child: Text(
action!,
style: UiTypography.body3r.textPrimary,
),
),
],
)
: Text(
title,
style: UiTypography.body2m.textPrimary,
), ),
), ),
child: Text(
action!,
style: UiTypography.body3r.copyWith(color: UiColors.primary),
),
),
], ],
), ),
); );

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
@@ -77,15 +77,15 @@ class _ShiftCardState extends State<ShiftCard> {
Modular.to.pushShiftDetails(widget.shift); Modular.to.pushShiftDetails(widget.shift);
}, },
child: Container( child: Container(
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: UiConstants.space3),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
border: Border.all(color: UiColors.border), border: Border.all(color: UiColors.border),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.05), color: UiColors.black.withValues(alpha: 0.05),
blurRadius: 2, blurRadius: 2,
offset: const Offset(0, 1), offset: const Offset(0, 1),
), ),
@@ -94,27 +94,28 @@ class _ShiftCardState extends State<ShiftCard> {
child: Row( child: Row(
children: [ children: [
Container( Container(
width: 48, width: UiConstants.space12,
height: 48, height: UiConstants.space12,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
border: Border.all(color: UiColors.border), border: Border.all(color: UiColors.border),
), ),
child: widget.shift.logoUrl != null child: widget.shift.logoUrl != null
? ClipRRect( ? ClipRRect(
borderRadius: BorderRadius.circular(12), borderRadius:
BorderRadius.circular(UiConstants.radiusBase),
child: Image.network( child: Image.network(
widget.shift.logoUrl!, widget.shift.logoUrl!,
fit: BoxFit.contain, fit: BoxFit.contain,
), ),
) )
: const Icon( : Icon(
LucideIcons.building2, UiIcons.building,
color: UiColors.mutedForeground, color: UiColors.mutedForeground,
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: UiConstants.space3),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -125,28 +126,18 @@ class _ShiftCardState extends State<ShiftCard> {
Flexible( Flexible(
child: Text( child: Text(
widget.shift.title, widget.shift.title,
style: const TextStyle( style: UiTypography.body1m.textPrimary,
fontWeight: FontWeight.w600,
color: UiColors.foreground,
),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
Text.rich( Text.rich(
TextSpan( TextSpan(
text: '\$${widget.shift.hourlyRate}', text: '\$${widget.shift.hourlyRate}',
style: const TextStyle( style: UiTypography.body1b.textPrimary,
fontWeight: FontWeight.bold, children: [
fontSize: 16,
color: UiColors.foreground,
),
children: const [
TextSpan( TextSpan(
text: '/h', text: '/h',
style: TextStyle( style: UiTypography.body3r,
fontWeight: FontWeight.normal,
fontSize: 12,
),
), ),
], ],
), ),
@@ -155,19 +146,13 @@ class _ShiftCardState extends State<ShiftCard> {
), ),
Text( Text(
widget.shift.clientName, widget.shift.clientName,
style: const TextStyle( style: UiTypography.body2r.textSecondary,
color: UiColors.mutedForeground,
fontSize: 13,
),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
const SizedBox(height: 4), const SizedBox(height: UiConstants.space1),
Text( Text(
'${_formatTime(widget.shift.startTime)}${widget.shift.location}', '${_formatTime(widget.shift.startTime)}${widget.shift.location}',
style: const TextStyle( style: UiTypography.body3r.textSecondary,
color: UiColors.mutedForeground,
fontSize: 12,
),
), ),
], ],
), ),
@@ -179,14 +164,14 @@ class _ShiftCardState extends State<ShiftCard> {
} }
return Container( return Container(
margin: const EdgeInsets.only(bottom: 16), margin: const EdgeInsets.only(bottom: UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
border: Border.all(color: UiColors.border), border: Border.all(color: UiColors.border),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.05), color: UiColors.black.withValues(alpha: 0.05),
blurRadius: 4, blurRadius: 4,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -195,7 +180,7 @@ class _ShiftCardState extends State<ShiftCard> {
child: Column( child: Column(
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(UiConstants.space5),
child: Column( child: Column(
children: [ children: [
// Header // Header
@@ -203,48 +188,45 @@ class _ShiftCardState extends State<ShiftCard> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Container( Container(
width: 56, width: UiConstants.space14,
height: 56, height: UiConstants.space14,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
border: Border.all(color: UiColors.border), border: Border.all(color: UiColors.border),
), ),
child: widget.shift.logoUrl != null child: widget.shift.logoUrl != null
? ClipRRect( ? ClipRRect(
borderRadius: BorderRadius.circular(12), borderRadius:
BorderRadius.circular(UiConstants.radiusBase),
child: Image.network( child: Image.network(
widget.shift.logoUrl!, widget.shift.logoUrl!,
fit: BoxFit.contain, fit: BoxFit.contain,
), ),
) )
: const Icon( : Icon(
LucideIcons.building2, UiIcons.building,
size: 28, size: 28,
color: UiColors.primary, color: UiColors.primary,
), ),
), ),
Container( Container(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 16, horizontal: UiConstants.space4,
vertical: 6, vertical: 6,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.primary, color: UiColors.primary,
borderRadius: BorderRadius.circular(20), borderRadius: UiConstants.radiusFull,
), ),
child: Text( child: Text(
'Assigned ${_getTimeAgo(widget.shift.createdDate).replaceAll('Pending ', '')}', 'Assigned ${_getTimeAgo(widget.shift.createdDate).replaceAll('Pending ', '')}',
style: const TextStyle( style: UiTypography.body3m.white,
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w600,
),
), ),
), ),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: UiConstants.space4),
// Title and Rate // Title and Rate
Row( Row(
@@ -257,18 +239,11 @@ class _ShiftCardState extends State<ShiftCard> {
children: [ children: [
Text( Text(
widget.shift.title, widget.shift.title,
style: const TextStyle( style: UiTypography.headline3m.textPrimary,
fontSize: 20,
fontWeight: FontWeight.bold,
color: UiColors.foreground,
),
), ),
Text( Text(
widget.shift.clientName, widget.shift.clientName,
style: const TextStyle( style: UiTypography.body2r.textSecondary,
color: UiColors.mutedForeground,
fontSize: 14,
),
), ),
], ],
), ),
@@ -276,31 +251,24 @@ class _ShiftCardState extends State<ShiftCard> {
Text.rich( Text.rich(
TextSpan( TextSpan(
text: '\$${widget.shift.hourlyRate}', text: '\$${widget.shift.hourlyRate}',
style: const TextStyle( style: UiTypography.headline3m.textPrimary,
fontWeight: FontWeight.bold, children: [
fontSize: 20,
color: UiColors.foreground,
),
children: const [
TextSpan( TextSpan(
text: '/h', text: '/h',
style: TextStyle( style: UiTypography.body1r,
fontWeight: FontWeight.normal,
fontSize: 16,
),
), ),
], ],
), ),
), ),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: UiConstants.space4),
// Location and Date // Location and Date
Row( Row(
children: [ children: [
const Icon( Icon(
LucideIcons.mapPin, UiIcons.mapPin,
size: 16, size: 16,
color: UiColors.mutedForeground, color: UiColors.mutedForeground,
), ),
@@ -308,30 +276,24 @@ class _ShiftCardState extends State<ShiftCard> {
Expanded( Expanded(
child: Text( child: Text(
widget.shift.location, widget.shift.location,
style: const TextStyle( style: UiTypography.body2r.textSecondary,
color: UiColors.mutedForeground,
fontSize: 14,
),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
const SizedBox(width: 16), const SizedBox(width: UiConstants.space4),
const Icon( Icon(
LucideIcons.calendar, UiIcons.calendar,
size: 16, size: 16,
color: UiColors.mutedForeground, color: UiColors.mutedForeground,
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
Text( Text(
'${_formatDate(widget.shift.date)}, ${_formatTime(widget.shift.startTime)}', '${_formatDate(widget.shift.date)}, ${_formatTime(widget.shift.startTime)}',
style: const TextStyle( style: UiTypography.body2r.textSecondary,
color: UiColors.mutedForeground,
fontSize: 14,
),
), ),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: UiConstants.space4),
// Tags // Tags
Wrap( Wrap(
@@ -339,21 +301,21 @@ class _ShiftCardState extends State<ShiftCard> {
runSpacing: 8, runSpacing: 8,
children: [ children: [
_buildTag( _buildTag(
LucideIcons.zap, UiIcons.zap,
'Immediate start', 'Immediate start',
UiColors.accent.withValues(alpha: 0.3), UiColors.accent.withValues(alpha: 0.3),
UiColors.foreground, UiColors.foreground,
), ),
_buildTag( _buildTag(
LucideIcons.timer, UiIcons.timer,
'No experience', 'No experience',
const Color(0xFFFEE2E2), UiColors.tagError,
const Color(0xFFDC2626), UiColors.textError,
), ),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: UiConstants.space4),
], ],
), ),
), ),
@@ -361,46 +323,48 @@ class _ShiftCardState extends State<ShiftCard> {
// Actions // Actions
if (!widget.compact) if (!widget.compact)
Padding( Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0), padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space5,
),
child: Column( child: Column(
children: [ children: [
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
height: 48, height: UiConstants.space12,
child: ElevatedButton( child: ElevatedButton(
onPressed: widget.onApply, onPressed: widget.onApply,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: UiColors.foreground, backgroundColor: UiColors.primary,
foregroundColor: Colors.white, foregroundColor: UiColors.white,
elevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius:
BorderRadius.circular(UiConstants.radiusBase),
), ),
), ),
child: const Text( child: const Text('Accept shift'),
'Accept shift',
style: TextStyle(fontWeight: FontWeight.w600),
),
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: UiConstants.space2),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
height: 48, height: UiConstants.space12,
child: OutlinedButton( child: OutlinedButton(
onPressed: widget.onDecline, onPressed: widget.onDecline,
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
foregroundColor: const Color(0xFFEF4444), foregroundColor: UiColors.destructive,
side: const BorderSide(color: Color(0xFFFCA5A5)), side: BorderSide(
color: UiColors.destructive.withValues(alpha: 0.3),
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius:
BorderRadius.circular(UiConstants.radiusBase),
), ),
), ),
child: const Text( child: const Text('Decline shift'),
'Decline shift',
style: TextStyle(fontWeight: FontWeight.w600),
),
), ),
), ),
const SizedBox(height: UiConstants.space5),
], ],
), ),
), ),
@@ -414,7 +378,7 @@ class _ShiftCardState extends State<ShiftCard> {
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration( decoration: BoxDecoration(
color: bg, color: bg,
borderRadius: BorderRadius.circular(20), borderRadius: UiConstants.radiusFull,
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -424,11 +388,7 @@ class _ShiftCardState extends State<ShiftCard> {
Flexible( Flexible(
child: Text( child: Text(
label, label,
style: TextStyle( style: UiTypography.body3m.copyWith(color: text),
color: text,
fontSize: 12,
fontWeight: FontWeight.w600,
),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),

View File

@@ -1,5 +1,6 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:core_localization/core_localization.dart'; import 'package:core_localization/core_localization.dart';
@@ -17,29 +18,33 @@ class AutoMatchToggle extends StatefulWidget {
State<AutoMatchToggle> createState() => _AutoMatchToggleState(); State<AutoMatchToggle> createState() => _AutoMatchToggleState();
} }
class _AutoMatchToggleState extends State<AutoMatchToggle> with SingleTickerProviderStateMixin { class _AutoMatchToggleState extends State<AutoMatchToggle>
with SingleTickerProviderStateMixin {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final i18n = t.staff.home.auto_match; final i18n = t.staff.home.auto_match;
final Color primary = Theme.of(context).colorScheme.primary;
return AnimatedContainer( return AnimatedContainer(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
gradient: widget.enabled gradient: widget.enabled
? LinearGradient( ? LinearGradient(
colors: [primary, primary.withOpacity(0.8)], colors: [
UiColors.primary,
UiColors.primary.withValues(alpha: 0.8),
],
begin: Alignment.centerLeft, begin: Alignment.centerLeft,
end: Alignment.centerRight, end: Alignment.centerRight,
) )
: null, : null,
color: widget.enabled ? null : Colors.white, color: widget.enabled ? null : UiColors.white,
border: widget.enabled ? null : Border.all(color: Colors.grey.shade200), border:
widget.enabled ? null : Border.all(color: UiColors.border),
boxShadow: widget.enabled boxShadow: widget.enabled
? [ ? [
BoxShadow( BoxShadow(
color: primary.withOpacity(0.3), color: UiColors.primary.withValues(alpha: 0.3),
blurRadius: 10, blurRadius: 10,
offset: const Offset(0, 4), offset: const Offset(0, 4),
), ),
@@ -54,36 +59,39 @@ class _AutoMatchToggleState extends State<AutoMatchToggle> with SingleTickerProv
Row( Row(
children: [ children: [
Container( Container(
width: 40, width: UiConstants.space10,
height: 40, height: UiConstants.space10,
decoration: BoxDecoration( decoration: BoxDecoration(
color: widget.enabled color: widget.enabled
? Colors.white.withOpacity(0.2) ? UiColors.white.withValues(alpha: 0.2)
: primary.withOpacity(0.1), : UiColors.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12), borderRadius:
BorderRadius.circular(UiConstants.radiusBase),
), ),
child: Icon( child: Icon(
LucideIcons.zap, UiIcons.zap,
color: widget.enabled ? Colors.white : primary, color: widget.enabled ? UiColors.white : UiColors.primary,
size: 20, size: 20,
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: UiConstants.space3),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
i18n.title, i18n.title,
style: TextStyle( style: UiTypography.body1b.copyWith(
fontWeight: FontWeight.bold, color: widget.enabled
color: widget.enabled ? Colors.white : const Color(0xFF0F172A), ? UiColors.white
: UiColors.textPrimary,
), ),
), ),
Text( Text(
widget.enabled ? i18n.finding_shifts : i18n.get_matched, widget.enabled ? i18n.finding_shifts : i18n.get_matched,
style: TextStyle( style: UiTypography.body3r.copyWith(
fontSize: 12, color: widget.enabled
color: widget.enabled ? const Color(0xFFF8E08E) : Colors.grey.shade500, ? UiColors.accent
: UiColors.textInactive,
), ),
), ),
], ],
@@ -93,10 +101,10 @@ class _AutoMatchToggleState extends State<AutoMatchToggle> with SingleTickerProv
Switch( Switch(
value: widget.enabled, value: widget.enabled,
onChanged: widget.onToggle, onChanged: widget.onToggle,
activeThumbColor: Colors.white, activeThumbColor: UiColors.white,
activeTrackColor: Colors.white.withValues(alpha: 0.3), activeTrackColor: UiColors.white.withValues(alpha: 0.3),
inactiveThumbColor: Colors.white, inactiveThumbColor: UiColors.white,
inactiveTrackColor: Colors.grey.shade300, inactiveTrackColor: UiColors.border,
), ),
], ],
), ),
@@ -106,29 +114,28 @@ class _AutoMatchToggleState extends State<AutoMatchToggle> with SingleTickerProv
? Column( ? Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const SizedBox(height: 16), const SizedBox(height: UiConstants.space4),
Container( Container(
height: 1, height: 1,
color: Colors.white.withValues(alpha: 0.2), color: UiColors.white.withValues(alpha: 0.2),
), ),
const SizedBox(height: 16), const SizedBox(height: UiConstants.space4),
Text( Text(
i18n.matching_based_on, i18n.matching_based_on,
style: const TextStyle( style: UiTypography.body3r.copyWith(
color: Color(0xFFF8E08E), color: UiColors.accent,
fontSize: 12,
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: UiConstants.space3),
Wrap( Wrap(
spacing: 8, spacing: 8,
children: [ children: [
_buildChip(LucideIcons.mapPin, i18n.chips.location), _buildChip(UiIcons.mapPin, i18n.chips.location),
_buildChip( _buildChip(
LucideIcons.clock, UiIcons.clock,
i18n.chips.availability, i18n.chips.availability,
), ),
_buildChip(LucideIcons.briefcase, i18n.chips.skills), _buildChip(UiIcons.briefcase, i18n.chips.skills),
], ],
), ),
], ],
@@ -144,17 +151,17 @@ class _AutoMatchToggleState extends State<AutoMatchToggle> with SingleTickerProv
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.2), color: UiColors.white.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(UiConstants.radiusMdValue),
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon(icon, size: 12, color: Colors.white), Icon(icon, size: 12, color: UiColors.white),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
label, label,
style: const TextStyle(color: Colors.white, fontSize: 12), style: UiTypography.body3r.white,
), ),
], ],
), ),

View File

@@ -1,6 +1,7 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:core_localization/core_localization.dart'; import 'package:core_localization/core_localization.dart';
@@ -15,14 +16,14 @@ class BenefitsWidget extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final i18n = t.staff.home.benefits; final i18n = t.staff.home.benefits;
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.background, color: UiColors.white,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
border: Border.all(color: Theme.of(context).dividerColor), border: Border.all(color: UiColors.border),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Theme.of(context).colorScheme.onBackground.withOpacity(0.05), color: UiColors.black.withValues(alpha: 0.05),
blurRadius: 2, blurRadius: 2,
offset: const Offset(0, 1), offset: const Offset(0, 1),
), ),
@@ -35,7 +36,7 @@ class BenefitsWidget extends StatelessWidget {
children: [ children: [
Text( Text(
i18n.title, i18n.title,
style: Theme.of(context).textTheme.titleMedium, style: UiTypography.title1m.textPrimary,
), ),
GestureDetector( GestureDetector(
onTap: () => Modular.to.pushNamed('/benefits'), onTap: () => Modular.to.pushNamed('/benefits'),
@@ -43,19 +44,19 @@ class BenefitsWidget extends StatelessWidget {
children: [ children: [
Text( Text(
i18n.view_all, i18n.view_all,
style: Theme.of(context).textTheme.labelLarge?.copyWith(color: Theme.of(context).colorScheme.primary), style: UiTypography.buttonL.textPrimary,
), ),
Icon( Icon(
LucideIcons.chevronRight, UiIcons.chevronRight,
size: 16, size: UiConstants.space4,
color: Theme.of(context).colorScheme.primary, color: UiColors.primary,
), ),
], ],
), ),
), ),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: UiConstants.space4),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@@ -63,19 +64,19 @@ class BenefitsWidget extends StatelessWidget {
label: i18n.items.sick_days, label: i18n.items.sick_days,
current: 10, current: 10,
total: 40, total: 40,
color: Theme.of(context).colorScheme.primary, color: UiColors.primary,
), ),
_BenefitItem( _BenefitItem(
label: i18n.items.vacation, label: i18n.items.vacation,
current: 40, current: 40,
total: 40, total: 40,
color: Theme.of(context).colorScheme.primary, color: UiColors.primary,
), ),
_BenefitItem( _BenefitItem(
label: i18n.items.holidays, label: i18n.items.holidays,
current: 24, current: 24,
total: 24, total: 24,
color: Theme.of(context).colorScheme.primary, color: UiColors.primary,
), ),
], ],
), ),
@@ -104,13 +105,13 @@ class _BenefitItem extends StatelessWidget {
return Column( return Column(
children: [ children: [
SizedBox( SizedBox(
width: 56, width: UiConstants.space14,
height: 56, height: UiConstants.space14,
child: CustomPaint( child: CustomPaint(
painter: _CircularProgressPainter( painter: _CircularProgressPainter(
progress: current / total, progress: current / total,
color: color, color: color,
backgroundColor: const Color(0xFFE5E7EB), backgroundColor: UiColors.border,
strokeWidth: 4, strokeWidth: 4,
), ),
child: Center( child: Center(
@@ -119,32 +120,21 @@ class _BenefitItem extends StatelessWidget {
children: [ children: [
Text( Text(
'${current.toInt()}/${total.toInt()}', '${current.toInt()}/${total.toInt()}',
style: const TextStyle( style: UiTypography.body3m.textPrimary,
fontSize: 12,
fontWeight: FontWeight.bold,
color: Color(0xFF1E293B),
),
), ),
Text( Text(
i18n.hours_label, i18n.hours_label,
style: const TextStyle( style: UiTypography.footnote1r.textTertiary,
fontSize: 8,
color: Color(0xFF94A3B8),
),
), ),
], ],
), ),
), ),
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: UiConstants.space2),
Text( Text(
label, label,
style: const TextStyle( style: UiTypography.body3m.textSecondary,
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF475569),
),
), ),
], ],
); );

View File

@@ -1,3 +1,4 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:core_localization/core_localization.dart'; import 'package:core_localization/core_localization.dart';
@@ -32,9 +33,9 @@ class ImproveYourselfWidget extends StatelessWidget {
children: [ children: [
Text( Text(
i18n.title, i18n.title,
style: Theme.of(context).textTheme.titleMedium, style: UiTypography.title1m.textPrimary,
), ),
const SizedBox(height: 12), const SizedBox(height: UiConstants.space3),
SingleChildScrollView( SingleChildScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
clipBehavior: Clip.none, clipBehavior: Clip.none,
@@ -51,14 +52,14 @@ class ImproveYourselfWidget extends StatelessWidget {
onTap: () => Modular.to.pushNamed(item['page']!), onTap: () => Modular.to.pushNamed(item['page']!),
child: Container( child: Container(
width: 160, width: 160,
margin: const EdgeInsets.only(right: 12), margin: const EdgeInsets.only(right: UiConstants.space3),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.background, color: UiColors.white,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
border: Border.all(color: Theme.of(context).dividerColor), border: Border.all(color: UiColors.border),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Theme.of(context).colorScheme.onBackground.withOpacity(0.05), color: UiColors.black.withValues(alpha: 0.05),
blurRadius: 2, blurRadius: 2,
offset: const Offset(0, 1), offset: const Offset(0, 1),
), ),
@@ -69,36 +70,33 @@ class ImproveYourselfWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox( SizedBox(
height: 96, height: UiConstants.space24,
width: double.infinity, width: double.infinity,
child: Image.network( child: Image.network(
item['image']!, item['image']!,
fit: BoxFit.cover, fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) => Container( errorBuilder: (context, error, stackTrace) => Container(
color: Theme.of(context).colorScheme.surfaceVariant, color: UiColors.background,
child: const Icon( child: Icon(
Icons.image_not_supported, UiIcons.zap,
color: Colors.grey, color: UiColors.mutedForeground,
), ),
), ),
), ),
), ),
Padding( Padding(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(UiConstants.space3),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
item['title']!, item['title']!,
style: Theme.of(context).textTheme.titleSmall, style: UiTypography.body1m.textPrimary,
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
item['description']!, item['description']!,
style: const TextStyle( style: UiTypography.body3r.textSecondary,
fontSize: 12,
color: Color(0xFF64748B),
),
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),

View File

@@ -1,8 +1,8 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:core_localization/core_localization.dart'; import 'package:core_localization/core_localization.dart';
/// Widget for displaying more ways to use Krow, using design system tokens. /// Widget for displaying more ways to use Krow, using design system tokens.
class MoreWaysToUseKrowWidget extends StatelessWidget { class MoreWaysToUseKrowWidget extends StatelessWidget {
/// Creates a [MoreWaysToUseKrowWidget]. /// Creates a [MoreWaysToUseKrowWidget].
@@ -31,9 +31,9 @@ class MoreWaysToUseKrowWidget extends StatelessWidget {
children: [ children: [
Text( Text(
i18n.title, i18n.title,
style: Theme.of(context).textTheme.titleMedium, style: UiTypography.title1m.textPrimary,
), ),
const SizedBox(height: 12), const SizedBox(height: UiConstants.space3),
SingleChildScrollView( SingleChildScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
clipBehavior: Clip.none, clipBehavior: Clip.none,
@@ -50,14 +50,14 @@ class MoreWaysToUseKrowWidget extends StatelessWidget {
onTap: () => Modular.to.pushNamed(item['page']!), onTap: () => Modular.to.pushNamed(item['page']!),
child: Container( child: Container(
width: 160, width: 160,
margin: const EdgeInsets.only(right: 12), margin: const EdgeInsets.only(right: UiConstants.space3),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.background, color: UiColors.white,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
border: Border.all(color: Theme.of(context).dividerColor), border: Border.all(color: UiColors.border),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Theme.of(context).colorScheme.onBackground.withOpacity(0.05), color: UiColors.black.withValues(alpha: 0.05),
blurRadius: 2, blurRadius: 2,
offset: const Offset(0, 1), offset: const Offset(0, 1),
), ),
@@ -68,25 +68,25 @@ class MoreWaysToUseKrowWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox( SizedBox(
height: 96, height: UiConstants.space24,
width: double.infinity, width: double.infinity,
child: Image.network( child: Image.network(
item['image']!, item['image']!,
fit: BoxFit.cover, fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) => Container( errorBuilder: (context, error, stackTrace) => Container(
color: Theme.of(context).colorScheme.surfaceVariant, color: UiColors.background,
child: const Icon( child: Icon(
Icons.image_not_supported, UiIcons.zap,
color: Colors.grey, color: UiColors.mutedForeground,
), ),
), ),
), ),
), ),
Padding( Padding(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(UiConstants.space3),
child: Text( child: Text(
item['title']!, item['title']!,
style: Theme.of(context).textTheme.titleSmall, style: UiTypography.body1m.textPrimary,
), ),
), ),
], ],

View File

@@ -5,20 +5,10 @@ publish_to: none
resolution: workspace resolution: workspace
environment: environment:
sdk: '>=3.10.0 <4.0.0' sdk: ">=3.10.0 <4.0.0"
flutter: ">=3.0.0" flutter: ">=3.0.0"
dependencies: dependencies:
flutter:
sdk: flutter
flutter_bloc: ^8.1.0
bloc: ^8.1.0
flutter_modular: ^6.3.0
equatable: ^2.0.5
lucide_icons: ^0.257.0
intl: ^0.20.0
google_fonts: ^7.0.0
# Architecture Packages # Architecture Packages
design_system: design_system:
path: ../../../design_system path: ../../../design_system
@@ -32,6 +22,15 @@ dependencies:
path: ../shifts path: ../shifts
krow_data_connect: krow_data_connect:
path: ../../../data_connect path: ../../../data_connect
flutter:
sdk: flutter
flutter_bloc: ^8.1.0
bloc: ^8.1.0
flutter_modular: ^6.3.0
equatable: ^2.0.5
intl: ^0.20.0
google_fonts: ^7.0.0
firebase_data_connect: firebase_data_connect:
dev_dependencies: dev_dependencies:

View File

@@ -1,7 +1,7 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart'; import 'package:flutter_modular/flutter_modular.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import 'package:core_localization/core_localization.dart'; import 'package:core_localization/core_localization.dart';
@@ -35,7 +35,7 @@ class _PaymentsPageState extends State<PaymentsPage> {
return BlocProvider<PaymentsBloc>.value( return BlocProvider<PaymentsBloc>.value(
value: _bloc, value: _bloc,
child: Scaffold( child: Scaffold(
backgroundColor: const Color(0xFFF8FAFC), backgroundColor: UiColors.background,
body: BlocConsumer<PaymentsBloc, PaymentsState>( body: BlocConsumer<PaymentsBloc, PaymentsState>(
listener: (context, state) { listener: (context, state) {
if (state is PaymentsError) { if (state is PaymentsError) {
@@ -58,7 +58,7 @@ class _PaymentsPageState extends State<PaymentsPage> {
child: Text( child: Text(
translateErrorKey(state.message), translateErrorKey(state.message),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle(color: Color(0xFF64748B)), // TextSecondary style: UiTypography.body2r.copyWith(color: UiColors.textSecondary),
), ),
), ),
); );
@@ -67,6 +67,14 @@ class _PaymentsPageState extends State<PaymentsPage> {
} }
return const SizedBox.shrink(); return const SizedBox.shrink();
}, },
),
),
);
} else if (state is PaymentsLoaded) {
return _buildContent(context, state);
}
return const SizedBox.shrink();
},
), ),
), ),
); );
@@ -78,63 +86,57 @@ class _PaymentsPageState extends State<PaymentsPage> {
children: <Widget>[ children: <Widget>[
// Header Section with Gradient // Header Section with Gradient
Container( Container(
decoration: const BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: <Color>[Color(0xFF0032A0), Color(0xFF333F48)], colors: <Color>[
UiColors.primary,
UiColors.primary.withValues(alpha: 0.8),
],
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
), ),
), ),
padding: EdgeInsets.fromLTRB( padding: EdgeInsets.fromLTRB(
20, UiConstants.space5,
MediaQuery.of(context).padding.top + 24, MediaQuery.of(context).padding.top + UiConstants.space6,
20, UiConstants.space5,
32, UiConstants.space8,
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
const Text( Text(
"Earnings", "Earnings",
style: TextStyle( style: UiTypography.displayMb.white,
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
// Main Balance // Main Balance
Center( Center(
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
const Text( Text(
"Total Earnings", "Total Earnings",
style: TextStyle( style: UiTypography.body2r.copyWith(
color: Color(0xFFF8E08E), color: UiColors.accent,
fontSize: 14,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: UiConstants.space1),
Text( Text(
"\$${state.summary.totalEarnings.toStringAsFixed(0).replaceAllMapped(RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), (Match m) => '${m[1]},')}", "\$${state.summary.totalEarnings.toStringAsFixed(0).replaceAllMapped(RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), (Match m) => '${m[1]},')}",
style: const TextStyle( style: UiTypography.displayL.white,
fontSize: 36,
fontWeight: FontWeight.bold,
color: Colors.white,
),
), ),
], ],
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: UiConstants.space4),
// Period Tabs // Period Tabs
Container( Container(
padding: const EdgeInsets.all(4), padding: const EdgeInsets.all(UiConstants.space1),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2), color: UiColors.white.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
), ),
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
@@ -150,9 +152,9 @@ class _PaymentsPageState extends State<PaymentsPage> {
// Main Content - Offset upwards // Main Content - Offset upwards
Transform.translate( Transform.translate(
offset: const Offset(0, -16), offset: const Offset(0, -UiConstants.space4),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(horizontal: UiConstants.space5),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
@@ -161,76 +163,75 @@ class _PaymentsPageState extends State<PaymentsPage> {
payments: state.history, payments: state.history,
period: state.activePeriod, period: state.activePeriod,
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
// Quick Stats // Quick Stats
Row( Row(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: PaymentStatsCard( child: PaymentStatsCard(
icon: LucideIcons.trendingUp, icon: UiIcons.chart,
iconColor: const Color(0xFF059669), iconColor: UiColors.success,
label: "This Week", label: "This Week",
amount: "\$${state.summary.weeklyEarnings}", amount: "\$${state.summary.weeklyEarnings}",
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: UiConstants.space3),
Expanded( Expanded(
child: PaymentStatsCard( child: PaymentStatsCard(
icon: LucideIcons.calendar, icon: UiIcons.calendar,
iconColor: const Color(0xFF2563EB), iconColor: UiColors.primary,
label: "This Month", label: "This Month",
amount: "\$${state.summary.monthlyEarnings.toStringAsFixed(0)}", amount: "\$${state.summary.monthlyEarnings.toStringAsFixed(0)}",
), ),
), ),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: UiConstants.space4),
// Pending Pay // Pending Pay
if(state.summary.pendingEarnings > 0) PendingPayCard( if (state.summary.pendingEarnings > 0)
amount: state.summary.pendingEarnings, PendingPayCard(
onCashOut: () { amount: state.summary.pendingEarnings,
Modular.to.pushNamed('/early-pay'); onCashOut: () {
}, Modular.to.pushNamed('/early-pay');
), },
const SizedBox(height: 24), ),
const SizedBox(height: UiConstants.space6),
// Recent Payments // Recent Payments
if (state.history.isNotEmpty) Column( if (state.history.isNotEmpty)
children: <Widget>[ Column(
const Text( crossAxisAlignment: CrossAxisAlignment.start,
"Recent Payments", children: <Widget>[
style: TextStyle( Text(
fontSize: 14, "Recent Payments",
fontWeight: FontWeight.w600, style: UiTypography.body2m.textPrimary,
color: Color(0xFF0F172A),
), ),
), const SizedBox(height: UiConstants.space3),
const SizedBox(height: 12), Column(
Column( children: state.history.map((StaffPayment payment) {
children: state.history.map((StaffPayment payment) { return Padding(
return Padding( padding: const EdgeInsets.only(
padding: const EdgeInsets.only(bottom: 8), bottom: UiConstants.space2),
child: PaymentHistoryItem( child: PaymentHistoryItem(
amount: payment.amount, amount: payment.amount,
title: "Shift Payment", title: "Shift Payment",
location: "Varies", location: "Varies",
address: "Payment ID: ${payment.id}", address: "Payment ID: ${payment.id}",
date: payment.paidAt != null date: payment.paidAt != null
? DateFormat('E, MMM d').format(payment.paidAt!) ? DateFormat('E, MMM d')
: 'Pending', .format(payment.paidAt!)
workedTime: "Completed", : 'Pending',
hours: 0, workedTime: "Completed",
rate: 0.0, hours: 0,
status: payment.status.name.toUpperCase(), rate: 0.0,
), status: payment.status.name.toUpperCase(),
); ),
}).toList(), );
), }).toList(),
], ),
), ],
),
const SizedBox(height: 100), const SizedBox(height: 100),
], ],
@@ -248,19 +249,17 @@ class _PaymentsPageState extends State<PaymentsPage> {
child: GestureDetector( child: GestureDetector(
onTap: () => _bloc.add(ChangePeriodEvent(value)), onTap: () => _bloc.add(ChangePeriodEvent(value)),
child: Container( child: Container(
padding: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.symmetric(vertical: UiConstants.space2),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected ? Colors.white : Colors.transparent, color: isSelected ? UiColors.white : Colors.transparent,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(UiConstants.radiusMdValue),
), ),
child: Center( child: Center(
child: Text( child: Text(
label, label,
style: TextStyle( style: isSelected
fontSize: 14, ? UiTypography.body2m.copyWith(color: UiColors.primary)
fontWeight: FontWeight.w500, : UiTypography.body2m.white,
color: isSelected ? const Color(0xFF0032A0) : Colors.white,
),
), ),
), ),
), ),

View File

@@ -1,3 +1,4 @@
import 'package:design_system/design_system.dart';
import 'package:fl_chart/fl_chart.dart'; import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
@@ -25,26 +26,30 @@ class EarningsGraph extends StatelessWidget {
return Container( return Container(
height: 200, height: 200,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
),
child: Center(
child: Text(
"No sufficient data for graph",
style: UiTypography.body2r.textSecondary,
),
), ),
child: const Center(child: Text("No sufficient data for graph")),
); );
} }
final List<FlSpot> spots = _generateSpots(validPayments); final List<FlSpot> spots = _generateSpots(validPayments);
final double maxX = spots.isNotEmpty ? spots.last.x : 0.0;
final double maxY = spots.isNotEmpty ? spots.map((FlSpot s) => s.y).reduce((double a, double b) => a > b ? a : b) : 0.0; final double maxY = spots.isNotEmpty ? spots.map((FlSpot s) => s.y).reduce((double a, double b) => a > b ? a : b) : 0.0;
return Container( return Container(
height: 220, height: 220,
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
boxShadow: <BoxShadow>[ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.05), color: UiColors.black.withValues(alpha: 0.05),
offset: const Offset(0, 4), offset: const Offset(0, 4),
blurRadius: 12, blurRadius: 12,
), ),
@@ -53,15 +58,11 @@ class EarningsGraph extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
const Text( Text(
"Earnings Trend", "Earnings Trend",
style: TextStyle( style: UiTypography.body2b.textPrimary,
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF0F172A),
),
), ),
const SizedBox(height: 16), const SizedBox(height: UiConstants.space4),
Expanded( Expanded(
child: LineChart( child: LineChart(
LineChartData( LineChartData(
@@ -79,7 +80,7 @@ class EarningsGraph extends StatelessWidget {
padding: const EdgeInsets.only(top: 8.0), padding: const EdgeInsets.only(top: 8.0),
child: Text( child: Text(
DateFormat('d').format(validPayments[index].paidAt!), DateFormat('d').format(validPayments[index].paidAt!),
style: const TextStyle(fontSize: 10, color: Colors.grey), style: UiTypography.footnote1r.textSecondary,
), ),
); );
} }
@@ -96,13 +97,13 @@ class EarningsGraph extends StatelessWidget {
LineChartBarData( LineChartBarData(
spots: spots, spots: spots,
isCurved: true, isCurved: true,
color: const Color(0xFF0032A0), color: UiColors.primary,
barWidth: 3, barWidth: 3,
isStrokeCapRound: true, isStrokeCapRound: true,
dotData: const FlDotData(show: false), dotData: const FlDotData(show: false),
belowBarData: BarAreaData( belowBarData: BarAreaData(
show: true, show: true,
color: const Color(0xFF0032A0).withOpacity(0.1), color: UiColors.primary.withValues(alpha: 0.1),
), ),
), ),
], ],
@@ -121,7 +122,7 @@ class EarningsGraph extends StatelessWidget {
List<FlSpot> _generateSpots(List<StaffPayment> data) { List<FlSpot> _generateSpots(List<StaffPayment> data) {
// Generate spots based on index in the list for simplicity in this demo // Generate spots based on index in the list for simplicity in this demo
// Real implementation would map to actual dates on X-axis // Real implementation would map to actual dates on X-axis
return List.generate(data.length, (int index) { return List<FlSpot>.generate(data.length, (int index) {
return FlSpot(index.toDouble(), data[index].amount); return FlSpot(index.toDouble(), data[index].amount);
}); });
} }

View File

@@ -1,5 +1,5 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
class PaymentHistoryItem extends StatelessWidget { class PaymentHistoryItem extends StatelessWidget {
@@ -28,13 +28,13 @@ class PaymentHistoryItem extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
boxShadow: <BoxShadow>[ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.05), color: UiColors.black.withValues(alpha: 0.05),
blurRadius: 2, blurRadius: 2,
offset: const Offset(0, 1), offset: const Offset(0, 1),
), ),
@@ -50,23 +50,20 @@ class PaymentHistoryItem extends StatelessWidget {
width: 6, width: 6,
height: 6, height: 6,
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: Color(0xFF3B82F6), // blue-500 color: UiColors.primary,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
const Text( Text(
"PAID", "PAID",
style: TextStyle( style: UiTypography.titleUppercase4b.copyWith(
fontSize: 10, color: UiColors.primary,
fontWeight: FontWeight.w700,
color: Color(0xFF2563EB), // blue-600
letterSpacing: 0.5,
), ),
), ),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: UiConstants.space3),
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -76,16 +73,16 @@ class PaymentHistoryItem extends StatelessWidget {
width: 44, width: 44,
height: 44, height: 44,
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFF1F5F9), // slate-100 color: UiColors.secondary,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
), ),
child: const Icon( child: const Icon(
LucideIcons.dollarSign, UiIcons.chart,
color: Color(0xFF334155), // slate-700 color: UiColors.mutedForeground,
size: 24, size: 24,
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: UiConstants.space3),
// Content // Content
Expanded( Expanded(
@@ -101,18 +98,11 @@ class PaymentHistoryItem extends StatelessWidget {
children: <Widget>[ children: <Widget>[
Text( Text(
title, title,
style: const TextStyle( style: UiTypography.body2b.textPrimary,
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF0F172A), // slate-900
),
), ),
Text( Text(
location, location,
style: const TextStyle( style: UiTypography.body3r.textSecondary,
fontSize: 12,
color: Color(0xFF475569), // slate-600
),
), ),
], ],
), ),
@@ -122,75 +112,59 @@ class PaymentHistoryItem extends StatelessWidget {
children: <Widget>[ children: <Widget>[
Text( Text(
"\$${amount.toStringAsFixed(0)}", "\$${amount.toStringAsFixed(0)}",
style: const TextStyle( style: UiTypography.headline4m.textPrimary,
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF0F172A), // slate-900
),
), ),
Text( Text(
"\$${rate.toStringAsFixed(0)}/hr · ${hours}h", "\$${rate.toStringAsFixed(0)}/hr · ${hours}h",
style: const TextStyle( style: UiTypography.footnote1r.textSecondary,
fontSize: 10,
color: Color(0xFF64748B), // slate-500
),
), ),
], ],
), ),
], ],
), ),
const SizedBox(height: 8), const SizedBox(height: UiConstants.space2),
// Date and Time // Date and Time
Row( Row(
children: <Widget>[ children: <Widget>[
const Icon( const Icon(
LucideIcons.calendar, UiIcons.calendar,
size: 12, size: 12,
color: Color(0xFF64748B), color: UiColors.mutedForeground,
), ),
const SizedBox(width: 8), const SizedBox(width: UiConstants.space2),
Text( Text(
date, date,
style: const TextStyle( style: UiTypography.body3r.textSecondary,
fontSize: 12,
color: Color(0xFF64748B),
),
), ),
const SizedBox(width: 8), const SizedBox(width: UiConstants.space2),
const Icon( const Icon(
LucideIcons.clock, UiIcons.clock,
size: 12, size: 12,
color: Color(0xFF64748B), color: UiColors.mutedForeground,
), ),
const SizedBox(width: 8), const SizedBox(width: UiConstants.space2),
Text( Text(
workedTime, workedTime,
style: const TextStyle( style: UiTypography.body3r.textSecondary,
fontSize: 12,
color: Color(0xFF64748B),
),
), ),
], ],
), ),
const SizedBox(height: 4), const SizedBox(height: 1),
// Address // Address
Row( Row(
children: <Widget>[ children: <Widget>[
const Icon( const Icon(
LucideIcons.mapPin, UiIcons.mapPin,
size: 12, size: 12,
color: Color(0xFF64748B), color: UiColors.mutedForeground,
), ),
const SizedBox(width: 8), const SizedBox(width: UiConstants.space2),
Expanded( Expanded(
child: Text( child: Text(
address, address,
style: const TextStyle( style: UiTypography.body3r.textSecondary,
fontSize: 12,
color: Color(0xFF64748B),
),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),

View File

@@ -1,7 +1,7 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class PaymentStatsCard extends StatelessWidget { class PaymentStatsCard extends StatelessWidget {
const PaymentStatsCard({ const PaymentStatsCard({
super.key, super.key,
required this.icon, required this.icon,
@@ -9,6 +9,7 @@ class PaymentStatsCard extends StatelessWidget {
required this.label, required this.label,
required this.amount, required this.amount,
}); });
final IconData icon; final IconData icon;
final Color iconColor; final Color iconColor;
final String label; final String label;
@@ -17,13 +18,13 @@ class PaymentStatsCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
boxShadow: <BoxShadow>[ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.05), color: UiColors.black.withValues(alpha: 0.05),
blurRadius: 2, blurRadius: 2,
offset: const Offset(0, 1), offset: const Offset(0, 1),
), ),
@@ -35,24 +36,17 @@ class PaymentStatsCard extends StatelessWidget {
Row( Row(
children: <Widget>[ children: <Widget>[
Icon(icon, size: 16, color: iconColor), Icon(icon, size: 16, color: iconColor),
const SizedBox(width: 8), const SizedBox(width: UiConstants.space2),
Text( Text(
label, label,
style: const TextStyle( style: UiTypography.body3r.textSecondary,
fontSize: 12,
color: Color(0xFF64748B), // slate-500
),
), ),
], ],
), ),
const SizedBox(height: 8), const SizedBox(height: UiConstants.space2),
Text( Text(
amount, amount,
style: const TextStyle( style: UiTypography.headline1m.textPrimary,
fontSize: 20,
fontWeight: FontWeight.bold,
color: Color(0xFF0F172A), // slate-900
),
), ),
], ],
), ),

View File

@@ -1,5 +1,5 @@
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
class PendingPayCard extends StatelessWidget { class PendingPayCard extends StatelessWidget {
@@ -14,17 +14,13 @@ class PendingPayCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.all(14), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: const LinearGradient( color: UiColors.tagInProgress,
colors: <Color>[Color(0xFFEFF6FF), Color(0xFFEFF6FF)], // blue-50 to blue-50 borderRadius: BorderRadius.circular(UiConstants.radiusBase),
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
boxShadow: <BoxShadow>[ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.05), color: UiColors.black.withValues(alpha: 0.05),
blurRadius: 2, blurRadius: 2,
offset: const Offset(0, 1), offset: const Offset(0, 1),
), ),
@@ -36,63 +32,34 @@ class PendingPayCard extends StatelessWidget {
Row( Row(
children: <Widget>[ children: <Widget>[
Container( Container(
width: 40, width: UiConstants.space10,
height: 40, height: UiConstants.space10,
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFE8F0FF), color: UiColors.white.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(UiConstants.radiusMdValue),
), ),
child: const Icon( child: const Icon(
LucideIcons.dollarSign, UiIcons.chart,
color: Color(0xFF0047FF), color: UiColors.primary,
size: 20, size: 20,
), ),
), ),
const SizedBox(width: 10), const SizedBox(width: UiConstants.space3),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
const Text( Text(
"Pending", "Pending",
style: TextStyle( style: UiTypography.body2b.textPrimary,
fontWeight: FontWeight.bold,
color: Color(0xFF0F172A), // slate-900
fontSize: 14,
),
), ),
Text( Text(
"\$${amount.toStringAsFixed(0)} available", "\$${amount.toStringAsFixed(0)} available",
style: const TextStyle( style: UiTypography.body3m.textSecondary,
fontSize: 12,
color: Color(0xFF475569), // slate-600
fontWeight: FontWeight.w500,
),
), ),
], ],
), ),
], ],
), ),
/*
ElevatedButton.icon(
onPressed: onCashOut,
icon: const Icon(LucideIcons.zap, size: 14),
label: const Text("Early Pay"),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0047FF),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
elevation: 4,
shadowColor: Colors.black.withOpacity(0.2),
textStyle: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
),
*/
], ],
), ),
); );

View File

@@ -1,23 +1,14 @@
name: staff_payments name: staff_payments
description: Staff Payments feature description: Staff Payments feature
version: 0.0.1 version: 0.0.1
publish_to: 'none' publish_to: "none"
resolution: workspace resolution: workspace
environment: environment:
sdk: '>=3.10.0 <4.0.0' sdk: ">=3.10.0 <4.0.0"
flutter: ">=3.0.0" flutter: ">=3.0.0"
dependencies: dependencies:
flutter:
sdk: flutter
firebase_data_connect: ^0.2.2+2
firebase_auth: ^6.1.4
flutter_modular: ^6.3.2
lucide_icons: ^0.257.0
intl: ^0.20.0
fl_chart: ^0.66.0
# Internal packages # Internal packages
design_system: design_system:
path: ../../../design_system path: ../../../design_system
@@ -30,10 +21,17 @@ dependencies:
krow_data_connect: krow_data_connect:
path: ../../../data_connect path: ../../../data_connect
flutter:
sdk: flutter
firebase_data_connect: ^0.2.2+2
firebase_auth: ^6.1.4
flutter_modular: ^6.3.2
lucide_icons: ^0.257.0
intl: ^0.20.0
fl_chart: ^0.66.0
flutter_bloc: any flutter_bloc: any
equatable: any equatable: any
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_lints: ^6.0.0 flutter_lints: ^6.0.0

View File

@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:core_localization/core_localization.dart'; import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// The sign-out button widget. /// The sign-out button widget.
/// ///
@@ -14,31 +13,33 @@ class LogoutButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final i18n = t.staff.profile.header; final i18n = t.staff.profile.header;
return Container( return Container(
width: double.infinity, width: double.infinity,
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.bgPopup, color: UiColors.bgPopup,
borderRadius: BorderRadius.circular(UiConstants.radiusBase), borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border), border: Border.all(color: UiColors.border),
), ),
child: Material( child: Material(
color: const Color(0x00000000), color: const Color(0x00000000),
child: InkWell( child: InkWell(
onTap: onTap, onTap: onTap,
borderRadius: BorderRadius.circular(UiConstants.radiusBase), borderRadius: UiConstants.radiusLg,
child: Padding( child: Padding(
padding: EdgeInsets.symmetric(vertical: UiConstants.space4), padding: const EdgeInsets.symmetric(vertical: UiConstants.space4),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Icon(LucideIcons.logOut, color: UiColors.destructive, size: 20), const Icon(
SizedBox(width: UiConstants.space2), UiIcons.logOut,
color: UiColors.destructive,
size: 20,
),
const SizedBox(width: UiConstants.space2),
Text( Text(
i18n.sign_out, i18n.sign_out,
style: UiTypography.body1m.copyWith( style: UiTypography.body1m.textError,
color: UiColors.destructive,
),
), ),
], ],
), ),

View File

@@ -34,13 +34,13 @@ class ProfileHeader extends StatelessWidget {
return Container( return Container(
width: double.infinity, width: double.infinity,
padding: EdgeInsets.fromLTRB( padding: const EdgeInsets.fromLTRB(
UiConstants.space5, UiConstants.space5,
UiConstants.space5, UiConstants.space5,
UiConstants.space5, UiConstants.space5,
UiConstants.space16, UiConstants.space16,
), ),
decoration: BoxDecoration( decoration: const BoxDecoration(
color: UiColors.primary, color: UiColors.primary,
borderRadius: BorderRadius.vertical( borderRadius: BorderRadius.vertical(
bottom: Radius.circular(UiConstants.space6), bottom: Radius.circular(UiConstants.space6),
@@ -56,22 +56,20 @@ class ProfileHeader extends StatelessWidget {
children: [ children: [
Text( Text(
i18n.title, i18n.title,
style: UiTypography.headline4m.copyWith( style: UiTypography.headline4m.textSecondary,
color: UiColors.primaryForeground,
),
), ),
GestureDetector( GestureDetector(
onTap: onSignOutTap, onTap: onSignOutTap,
child: Text( child: Text(
i18n.sign_out, i18n.sign_out,
style: UiTypography.body2m.copyWith( style: UiTypography.body2m.copyWith(
color: UiColors.primaryForeground.withOpacity(0.8), color: UiColors.primaryForeground.withValues(alpha: 0.8),
), ),
), ),
), ),
], ],
), ),
SizedBox(height: UiConstants.space8), const SizedBox(height: UiConstants.space8),
// Avatar Section // Avatar Section
Stack( Stack(
alignment: Alignment.bottomRight, alignment: Alignment.bottomRight,
@@ -79,7 +77,7 @@ class ProfileHeader extends StatelessWidget {
Container( Container(
width: 112, width: 112,
height: 112, height: 112,
padding: EdgeInsets.all(UiConstants.space1), padding: const EdgeInsets.all(UiConstants.space1),
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
gradient: LinearGradient( gradient: LinearGradient(
@@ -87,13 +85,13 @@ class ProfileHeader extends StatelessWidget {
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [ colors: [
UiColors.accent, UiColors.accent,
UiColors.accent.withOpacity(0.5), UiColors.accent.withValues(alpha: 0.5),
UiColors.primaryForeground, UiColors.primaryForeground,
], ],
), ),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: UiColors.foreground.withOpacity(0.2), color: UiColors.foreground.withValues(alpha: 0.2),
blurRadius: 10, blurRadius: 10,
offset: const Offset(0, 4), offset: const Offset(0, 4),
), ),
@@ -103,7 +101,7 @@ class ProfileHeader extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all( border: Border.all(
color: UiColors.primaryForeground.withOpacity(0.2), color: UiColors.primaryForeground.withValues(alpha: 0.2),
width: 4, width: 4,
), ),
), ),
@@ -123,16 +121,16 @@ class ProfileHeader extends StatelessWidget {
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [ colors: [
UiColors.accent, UiColors.accent,
UiColors.accent.withOpacity(0.7), UiColors.accent.withValues(alpha: 0.7),
], ],
), ),
), ),
alignment: Alignment.center, alignment: Alignment.center,
child: Text( child: Text(
fullName.isNotEmpty ? fullName[0].toUpperCase() : 'K', fullName.isNotEmpty
style: UiTypography.displayM.copyWith( ? fullName[0].toUpperCase()
color: UiColors.primary, : 'K',
), style: UiTypography.displayM.primary,
), ),
) )
: null, : null,
@@ -148,7 +146,7 @@ class ProfileHeader extends StatelessWidget {
border: Border.all(color: UiColors.primary, width: 2), border: Border.all(color: UiColors.primary, width: 2),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: UiColors.foreground.withOpacity(0.1), color: UiColors.foreground.withValues(alpha: 0.1),
blurRadius: 4, blurRadius: 4,
), ),
], ],
@@ -161,28 +159,24 @@ class ProfileHeader extends StatelessWidget {
), ),
], ],
), ),
SizedBox(height: UiConstants.space4), const SizedBox(height: UiConstants.space4),
Text( Text(
fullName, fullName,
style: UiTypography.headline3m.copyWith( style: UiTypography.headline3m.textPlaceholder,
color: UiColors.primaryForeground,
),
), ),
SizedBox(height: UiConstants.space1), const SizedBox(height: UiConstants.space1),
Container( Container(
padding: EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space3, horizontal: UiConstants.space3,
vertical: UiConstants.space1, vertical: UiConstants.space1,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.accent.withOpacity(0.2), color: UiColors.accent.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(UiConstants.space5), borderRadius: BorderRadius.circular(UiConstants.space5),
), ),
child: Text( child: Text(
level, level,
style: UiTypography.footnote1b.copyWith( style: UiTypography.footnote1b.accent,
color: UiColors.accent,
),
), ),
), ),
], ],

View File

@@ -25,10 +25,10 @@ class ProfileMenuItem extends StatelessWidget {
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.bgPopup, color: UiColors.bgPopup,
borderRadius: BorderRadius.circular(UiConstants.radiusBase), borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border), border: Border.all(color: UiColors.border),
), ),
padding: EdgeInsets.all(UiConstants.space2), padding: const EdgeInsets.all(UiConstants.space2),
child: AspectRatio( child: AspectRatio(
aspectRatio: 1.0, aspectRatio: 1.0,
child: Stack( child: Stack(
@@ -42,24 +42,23 @@ class ProfileMenuItem extends StatelessWidget {
width: 36, width: 36,
height: 36, height: 36,
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.primary.withOpacity(0.08), color: UiColors.primary.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(UiConstants.radiusBase), borderRadius: UiConstants.radiusLg,
), ),
alignment: Alignment.center, alignment: Alignment.center,
child: Icon(icon, color: UiColors.primary, size: 20), child: Icon(icon, color: UiColors.primary, size: 20),
), ),
SizedBox(height: UiConstants.space1), const SizedBox(height: UiConstants.space1),
Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: UiConstants.space1), padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space1,
),
child: Text( child: Text(
label, label,
textAlign: TextAlign.center, textAlign: TextAlign.center,
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: UiTypography.footnote1m.copyWith( style: UiTypography.footnote1m.textSecondary,
color: UiColors.foreground,
height: 1.2,
),
), ),
), ),
], ],
@@ -76,16 +75,18 @@ class ProfileMenuItem extends StatelessWidget {
shape: BoxShape.circle, shape: BoxShape.circle,
color: completed! color: completed!
? UiColors.primary ? UiColors.primary
: UiColors.primary.withOpacity(0.1), : UiColors.primary.withValues(alpha: 0.1),
), ),
alignment: Alignment.center, alignment: Alignment.center,
child: completed! child: completed!
? const Icon(Icons.check, size: 10, color: UiColors.primaryForeground) ? const Icon(
UiIcons.check,
size: 10,
color: UiColors.primaryForeground,
)
: Text( : Text(
"!", "!",
style: UiTypography.footnote2b.copyWith( style: UiTypography.footnote2b.primary,
color: UiColors.primary,
),
), ),
), ),
), ),

View File

@@ -19,10 +19,10 @@ class ReliabilityScoreBar extends StatelessWidget {
final score = (reliabilityScore ?? 0) / 100; final score = (reliabilityScore ?? 0) / 100;
return Container( return Container(
padding: EdgeInsets.all(UiConstants.space4), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.primary.withOpacity(0.1), color: UiColors.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(UiConstants.radiusBase), borderRadius: UiConstants.radiusLg,
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -32,19 +32,15 @@ class ReliabilityScoreBar extends StatelessWidget {
children: [ children: [
Text( Text(
i18n.title, i18n.title,
style: UiTypography.body2m.copyWith( style: UiTypography.body2m.primary,
color: UiColors.primary,
),
), ),
Text( Text(
"${reliabilityScore ?? 0}%", "${reliabilityScore ?? 0}%",
style: UiTypography.headline4m.copyWith( style: UiTypography.headline4m.primary,
color: UiColors.primary,
),
), ),
], ],
), ),
SizedBox(height: UiConstants.space2), const SizedBox(height: UiConstants.space2),
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular(UiConstants.space1), borderRadius: BorderRadius.circular(UiConstants.space1),
child: LinearProgressIndicator( child: LinearProgressIndicator(
@@ -55,12 +51,10 @@ class ReliabilityScoreBar extends StatelessWidget {
), ),
), ),
Padding( Padding(
padding: EdgeInsets.only(top: UiConstants.space2), padding: const EdgeInsets.only(top: UiConstants.space2),
child: Text( child: Text(
i18n.description, i18n.description,
style: UiTypography.footnote2r.copyWith( style: UiTypography.footnote2r.textSecondary,
color: UiColors.mutedForeground,
),
), ),
), ),
], ],

View File

@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// Displays the staff member's reliability statistics (Shifts, Rating, On Time, etc.). /// Displays the staff member's reliability statistics (Shifts, Rating, On Time, etc.).
/// ///
@@ -24,14 +23,14 @@ class ReliabilityStatsCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
padding: EdgeInsets.all(UiConstants.space4), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.bgPopup, color: UiColors.bgPopup,
borderRadius: BorderRadius.circular(UiConstants.radiusBase), borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border), border: Border.all(color: UiColors.border),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: UiColors.foreground.withOpacity(0.05), color: UiColors.foreground.withValues(alpha: 0.05),
blurRadius: 4, blurRadius: 4,
offset: const Offset(0, 1), offset: const Offset(0, 1),
), ),
@@ -42,31 +41,31 @@ class ReliabilityStatsCard extends StatelessWidget {
children: [ children: [
_buildStatItem( _buildStatItem(
context, context,
LucideIcons.briefcase, UiIcons.briefcase,
"${totalShifts ?? 0}", "${totalShifts ?? 0}",
"Shifts", "Shifts",
), ),
_buildStatItem( _buildStatItem(
context, context,
LucideIcons.star, UiIcons.star,
(averageRating ?? 0.0).toStringAsFixed(1), (averageRating ?? 0.0).toStringAsFixed(1),
"Rating", "Rating",
), ),
_buildStatItem( _buildStatItem(
context, context,
LucideIcons.clock, UiIcons.clock,
"${onTimeRate ?? 0}%", "${onTimeRate ?? 0}%",
"On Time", "On Time",
), ),
_buildStatItem( _buildStatItem(
context, context,
LucideIcons.xCircle, UiIcons.xCircle,
"${noShowCount ?? 0}", "${noShowCount ?? 0}",
"No Shows", "No Shows",
), ),
_buildStatItem( _buildStatItem(
context, context,
LucideIcons.ban, UiIcons.ban,
"${cancellationCount ?? 0}", "${cancellationCount ?? 0}",
"Cancel.", "Cancel.",
), ),
@@ -88,26 +87,22 @@ class ReliabilityStatsCard extends StatelessWidget {
width: 40, width: 40,
height: 40, height: 40,
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.primary.withOpacity(0.1), color: UiColors.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(UiConstants.radiusMdValue), borderRadius: BorderRadius.circular(UiConstants.radiusMdValue),
), ),
alignment: Alignment.center, alignment: Alignment.center,
child: Icon(icon, size: 20, color: UiColors.primary), child: Icon(icon, size: 20, color: UiColors.primary),
), ),
SizedBox(height: UiConstants.space1), const SizedBox(height: UiConstants.space1),
Text( Text(
value, value,
style: UiTypography.body1b.copyWith( style: UiTypography.body1b.textSecondary,
color: UiColors.foreground,
),
), ),
FittedBox( FittedBox(
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
child: Text( child: Text(
label, label,
style: UiTypography.footnote2r.copyWith( style: UiTypography.footnote2r.textSecondary,
color: UiColors.mutedForeground,
),
), ),
), ),
], ],

View File

@@ -13,14 +13,11 @@ class SectionTitle extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
width: double.infinity, width: double.infinity,
padding: EdgeInsets.only(left: UiConstants.space1), padding: const EdgeInsets.only(left: UiConstants.space1),
margin: EdgeInsets.only(bottom: UiConstants.space3), margin: const EdgeInsets.only(bottom: UiConstants.space3),
child: Text( child: Text(
title.toUpperCase(), title.toUpperCase(),
style: UiTypography.footnote1b.copyWith( style: UiTypography.footnote1b.textSecondary,
color: UiColors.mutedForeground,
letterSpacing: 0.5,
),
), ),
); );
} }

View File

@@ -65,7 +65,7 @@ class CertificatesPage extends StatelessWidget {
Transform.translate( Transform.translate(
offset: const Offset(0, -48), offset: const Offset(0, -48),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(horizontal: UiConstants.space5),
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
...documents.map((StaffDocument doc) => CertificateCard( ...documents.map((StaffDocument doc) => CertificateCard(
@@ -82,11 +82,11 @@ class CertificatesPage extends StatelessWidget {
); );
}, },
)), )),
const SizedBox(height: 16), const SizedBox(height: UiConstants.space4),
AddCertificateCard( AddCertificateCard(
onTap: () => _showUploadModal(context, null), onTap: () => _showUploadModal(context, null),
), ),
const SizedBox(height: 32), const SizedBox(height: UiConstants.space8),
], ],
), ),
), ),

View File

@@ -12,37 +12,36 @@ class AddCertificateCard extends StatelessWidget {
return GestureDetector( return GestureDetector(
onTap: onTap, onTap: onTap,
child: Container( child: Container(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(UiConstants.space5),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: <Color>[Colors.grey[50]!, Colors.grey[100]!], // Keep prototype style colors: <Color>[
UiColors.bgSecondary.withValues(alpha: 0.5),
UiColors.bgSecondary,
],
), ),
borderRadius: BorderRadius.circular(16), borderRadius: UiConstants.radiusLg,
border: Border.all( border: Border.all(
color: Colors.grey[300]!, color: UiColors.border,
style: BorderStyle.solid, style: BorderStyle.solid,
), ),
), ),
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
const Icon(UiIcons.add, color: UiColors.primary, size: 24), const Icon(UiIcons.add, color: UiColors.primary, size: 24),
const SizedBox(width: 16), const SizedBox(width: UiConstants.space4),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text( Text(
t.staff_certificates.add_more.title, t.staff_certificates.add_more.title,
style: UiTypography.body1b.copyWith( // 16px Bold style: UiTypography.body1b.textPrimary,
color: UiColors.textPrimary,
),
), ),
Text( Text(
t.staff_certificates.add_more.subtitle, t.staff_certificates.add_more.subtitle,
style: UiTypography.body3r.copyWith( // 12px Regular style: UiTypography.body3r.textSecondary,
color: UiColors.textSecondary,
),
), ),
], ],
), ),

View File

@@ -1,8 +1,8 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import 'package:core_localization/core_localization.dart';
class CertificateCard extends StatelessWidget { class CertificateCard extends StatelessWidget {
final StaffDocument document; final StaffDocument document;
@@ -39,13 +39,13 @@ class CertificateCard extends StatelessWidget {
final _CertificateUiProps uiProps = _getUiProps(document.documentId); final _CertificateUiProps uiProps = _getUiProps(document.documentId);
return Container( return Container(
margin: const EdgeInsets.only(bottom: 16), margin: const EdgeInsets.only(bottom: UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.white, color: UiColors.white,
borderRadius: BorderRadius.circular(UiConstants.space4), borderRadius: UiConstants.radiusLg,
boxShadow: <BoxShadow>[ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: UiColors.black.withOpacity(0.05), color: UiColors.black.withValues(alpha: 0.05),
blurRadius: 4, blurRadius: 4,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -57,11 +57,14 @@ class CertificateCard extends StatelessWidget {
children: <Widget>[ children: <Widget>[
if (isExpiring || isExpired) if (isExpiring || isExpired)
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space4,
vertical: UiConstants.space2,
),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFF9E547).withOpacity(0.2), // Yellow tint color: UiColors.accent.withValues(alpha: 0.2), // Yellow tint
border: const Border( border: Border(
bottom: BorderSide(color: Color(0x66F9E547)), bottom: BorderSide(color: UiColors.accent.withValues(alpha: 0.4)),
), ),
), ),
child: Row( child: Row(
@@ -76,16 +79,14 @@ class CertificateCard extends StatelessWidget {
isExpired isExpired
? t.staff_certificates.card.expired ? t.staff_certificates.card.expired
: t.staff_certificates.card.expires_in_days(days: _daysUntilExpiry(document.expiryDate)), : t.staff_certificates.card.expires_in_days(days: _daysUntilExpiry(document.expiryDate)),
style: UiTypography.body3m.copyWith( // 12px Medium style: UiTypography.body3m.textPrimary,
color: UiColors.textPrimary,
),
), ),
], ],
), ),
), ),
Padding( Padding(
padding: const EdgeInsets.all(20), padding: const EdgeInsets.all(UiConstants.space5),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
@@ -96,8 +97,8 @@ class CertificateCard extends StatelessWidget {
width: 64, width: 64,
height: 64, height: 64,
decoration: BoxDecoration( decoration: BoxDecoration(
color: uiProps.color.withOpacity(0.1), color: uiProps.color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(16), borderRadius: UiConstants.radiusLg,
), ),
child: Center( child: Center(
child: Icon( child: Icon(
@@ -137,7 +138,7 @@ class CertificateCard extends StatelessWidget {
), ),
], ],
), ),
const SizedBox(width: 16), const SizedBox(width: UiConstants.space4),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -151,16 +152,12 @@ class CertificateCard extends StatelessWidget {
children: <Widget>[ children: <Widget>[
Text( Text(
document.name, document.name,
style: UiTypography.body1m.copyWith( // 16px Medium style: UiTypography.body1m.textPrimary,
color: UiColors.textPrimary,
),
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
document.description ?? '', // Optional description document.description ?? '', // Optional description
style: UiTypography.body3r.copyWith( // 12px Regular style: UiTypography.body3r.textSecondary,
color: UiColors.textSecondary,
),
), ),
], ],
), ),
@@ -172,7 +169,7 @@ class CertificateCard extends StatelessWidget {
), ),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: UiConstants.space4),
if (showComplete) _buildCompleteStatus(document.expiryDate), if (showComplete) _buildCompleteStatus(document.expiryDate),
@@ -186,9 +183,11 @@ class CertificateCard extends StatelessWidget {
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: UiColors.primary, backgroundColor: UiColors.primary,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
),
padding: const EdgeInsets.symmetric(
vertical: UiConstants.space3,
), ),
padding: const EdgeInsets.symmetric(vertical: 12),
elevation: 0, elevation: 0,
), ),
child: Row( child: Row(
@@ -202,9 +201,7 @@ class CertificateCard extends StatelessWidget {
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
t.staff_certificates.card.upload_button, t.staff_certificates.card.upload_button,
style: UiTypography.body2m.copyWith( // 14px Medium style: UiTypography.body2m.white,
color: UiColors.white,
),
), ),
], ],
), ),
@@ -212,7 +209,7 @@ class CertificateCard extends StatelessWidget {
), ),
if (showComplete || isExpiring || isExpired) ...<Widget>[ if (showComplete || isExpiring || isExpired) ...<Widget>[
const SizedBox(height: 12), const SizedBox(height: UiConstants.space3),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: OutlinedButton.icon( child: OutlinedButton.icon(
@@ -223,13 +220,15 @@ class CertificateCard extends StatelessWidget {
foregroundColor: UiColors.textPrimary, foregroundColor: UiColors.textPrimary,
side: const BorderSide(color: UiColors.border), side: const BorderSide(color: UiColors.border),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
),
padding: const EdgeInsets.symmetric(
vertical: UiConstants.space3,
), ),
padding: const EdgeInsets.symmetric(vertical: 12),
), ),
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: UiConstants.space2),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: TextButton.icon( child: TextButton.icon(
@@ -238,9 +237,11 @@ class CertificateCard extends StatelessWidget {
label: Text(t.staff_certificates.card.remove), label: Text(t.staff_certificates.card.remove),
style: TextButton.styleFrom( style: TextButton.styleFrom(
foregroundColor: UiColors.destructive, foregroundColor: UiColors.destructive,
padding: const EdgeInsets.symmetric(vertical: 12), padding: const EdgeInsets.symmetric(
vertical: UiConstants.space3,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
), ),
), ),
), ),
@@ -274,16 +275,14 @@ class CertificateCard extends StatelessWidget {
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
t.staff_certificates.card.verified, t.staff_certificates.card.verified,
style: UiTypography.body2m.copyWith( style: UiTypography.body2m.textPrimary,
color: UiColors.primary,
),
), ),
], ],
), ),
if (expiryDate != null) if (expiryDate != null)
Text( Text(
t.staff_certificates.card.exp(date: DateFormat('MMM d, yyyy').format(expiryDate)), t.staff_certificates.card.exp(date: DateFormat('MMM d, yyyy').format(expiryDate)),
style: UiTypography.body3r.copyWith(color: UiColors.textSecondary), style: UiTypography.body3r.textSecondary,
), ),
], ],
); );

View File

@@ -6,7 +6,8 @@ import 'package:flutter/material.dart';
class CertificateUploadModal extends StatelessWidget { class CertificateUploadModal extends StatelessWidget {
/// The document being edited, or null for a new upload. /// The document being edited, or null for a new upload.
// ignore: unused_field // ignore: unused_field
final dynamic document; // Using dynamic for now as we don't import domain here to avoid direct coupling if possible, but actually we should import domain. final dynamic
document; // Using dynamic for now as we don't import domain here to avoid direct coupling if possible, but actually we should import domain.
// Ideally, widgets should be dumb. Let's import domain. // Ideally, widgets should be dumb. Let's import domain.
final VoidCallback onSave; final VoidCallback onSave;
@@ -24,13 +25,13 @@ class CertificateUploadModal extends StatelessWidget {
return Container( return Container(
height: MediaQuery.of(context).size.height * 0.75, height: MediaQuery.of(context).size.height * 0.75,
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: Colors.white, color: UiColors.bgPopup,
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topLeft: Radius.circular(24), topLeft: Radius.circular(UiConstants.radiusBase),
topRight: Radius.circular(24), topRight: Radius.circular(UiConstants.radiusBase),
), ),
), ),
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(UiConstants.space6),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
@@ -39,7 +40,7 @@ class CertificateUploadModal extends StatelessWidget {
children: <Widget>[ children: <Widget>[
Text( Text(
t.staff_certificates.upload_modal.title, t.staff_certificates.upload_modal.title,
style: UiTypography.headline3m.copyWith(color: UiColors.textPrimary), style: UiTypography.headline3m.textPrimary,
), ),
IconButton( IconButton(
onPressed: onCancel, onPressed: onCancel,
@@ -47,35 +48,42 @@ class CertificateUploadModal extends StatelessWidget {
), ),
], ],
), ),
const SizedBox(height: 32), const SizedBox(height: UiConstants.space8),
Text( Text(
t.staff_certificates.upload_modal.expiry_label, t.staff_certificates.upload_modal.expiry_label,
style: UiTypography.body1m, style: UiTypography.body1m,
), ),
const SizedBox(height: 8), const SizedBox(height: UiConstants.space2),
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space4,
vertical: UiConstants.space3,
),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(color: UiColors.border), border: Border.all(color: UiColors.border),
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
), ),
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
const Icon(UiIcons.calendar, size: 20, color: UiColors.textSecondary), const Icon(
const SizedBox(width: 12), UiIcons.calendar,
size: 20,
color: UiColors.textSecondary,
),
const SizedBox(width: UiConstants.space3),
Text( Text(
t.staff_certificates.upload_modal.select_date, t.staff_certificates.upload_modal.select_date,
style: UiTypography.body1m.copyWith(color: UiColors.textSecondary), style: UiTypography.body1m.textSecondary,
), ),
], ],
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
Text( Text(
t.staff_certificates.upload_modal.upload_file, t.staff_certificates.upload_modal.upload_file,
style: UiTypography.body1m, style: UiTypography.body1m,
), ),
const SizedBox(height: 8), const SizedBox(height: UiConstants.space2),
Expanded( Expanded(
child: Container( child: Container(
width: double.infinity, width: double.infinity,
@@ -84,16 +92,16 @@ class CertificateUploadModal extends StatelessWidget {
color: UiColors.border, color: UiColors.border,
style: BorderStyle.solid, style: BorderStyle.solid,
), ),
borderRadius: BorderRadius.circular(16), borderRadius: UiConstants.radiusLg,
color: UiColors.background, color: UiColors.background,
), ),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: const BoxDecoration( decoration: BoxDecoration(
color: Color(0xFFEFF6FF), // Light blue color: UiColors.tagActive,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: const Icon( child: const Icon(
@@ -102,7 +110,7 @@ class CertificateUploadModal extends StatelessWidget {
color: UiColors.primary, color: UiColors.primary,
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: UiConstants.space4),
Text( Text(
t.staff_certificates.upload_modal.drag_drop, t.staff_certificates.upload_modal.drag_drop,
style: UiTypography.body1m, style: UiTypography.body1m,
@@ -110,43 +118,51 @@ class CertificateUploadModal extends StatelessWidget {
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
t.staff_certificates.upload_modal.supported_formats, t.staff_certificates.upload_modal.supported_formats,
style: UiTypography.body3r.copyWith(color: UiColors.textSecondary), style: UiTypography.body3r.textSecondary,
), ),
], ],
), ),
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
Row( Row(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: OutlinedButton( child: OutlinedButton(
onPressed: onCancel, onPressed: onCancel,
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(
vertical: UiConstants.space4,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
), ),
side: const BorderSide(color: UiColors.border), side: const BorderSide(color: UiColors.border),
), ),
child: Text(t.staff_certificates.upload_modal.cancel, child: Text(
style: UiTypography.body1m.copyWith(color: UiColors.textPrimary)), t.staff_certificates.upload_modal.cancel,
style: UiTypography.body1m.textPrimary,
),
), ),
), ),
const SizedBox(width: 16), const SizedBox(width: UiConstants.space4),
Expanded( Expanded(
child: ElevatedButton( child: ElevatedButton(
onPressed: onSave, onPressed: onSave,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: UiColors.primary, backgroundColor: UiColors.primary,
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(
vertical: UiConstants.space4,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
), ),
elevation: 0, elevation: 0,
), ),
child: Text(t.staff_certificates.upload_modal.save, child: Text(
style: UiTypography.body1m.copyWith(color: Colors.white)), t.staff_certificates.upload_modal.save,
style: UiTypography.body1m.white,
),
), ),
), ),
], ],

View File

@@ -20,13 +20,21 @@ class CertificatesHeader extends StatelessWidget {
final int progressPercent = totalCount == 0 ? 0 : (progressValue * 100).round(); final int progressPercent = totalCount == 0 ? 0 : (progressValue * 100).round();
return Container( return Container(
padding: const EdgeInsets.fromLTRB(20, 60, 20, 80), padding: const EdgeInsets.fromLTRB(
UiConstants.space5,
60,
UiConstants.space5,
80,
),
// Keeping gradient as per prototype layout requirement // Keeping gradient as per prototype layout requirement
decoration: const BoxDecoration( decoration: const BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: <Color>[UiColors.primary, Color(0xFF1E40AF)], // Using Primary and a darker shade colors: <Color>[
UiColors.primary,
Color(0xFF1E40AF),
], // Using Primary and a darker shade
), ),
), ),
child: Column( child: Column(
@@ -39,7 +47,7 @@ class CertificatesHeader extends StatelessWidget {
width: 40, width: 40,
height: 40, height: 40,
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.white.withOpacity(0.1), color: UiColors.white.withValues(alpha: 0.1),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: const Icon( child: const Icon(
@@ -49,16 +57,14 @@ class CertificatesHeader extends StatelessWidget {
), ),
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: UiConstants.space3),
Text( Text(
t.staff_certificates.title, t.staff_certificates.title,
style: UiTypography.headline3m.copyWith( // 18px Bold style: UiTypography.headline3m.white,
color: UiColors.white,
),
), ),
], ],
), ),
const SizedBox(height: 32), const SizedBox(height: UiConstants.space8),
Row( Row(
children: <Widget>[ children: <Widget>[
SizedBox( SizedBox(
@@ -70,53 +76,48 @@ class CertificatesHeader extends StatelessWidget {
CircularProgressIndicator( CircularProgressIndicator(
value: progressValue, value: progressValue,
strokeWidth: 8, strokeWidth: 8,
backgroundColor: UiColors.white.withOpacity(0.2), backgroundColor: UiColors.white.withValues(alpha: 0.2),
valueColor: const AlwaysStoppedAnimation<Color>( valueColor: const AlwaysStoppedAnimation<Color>(
Color(0xFFF9E547), // Yellow from prototype UiColors.accent, // Yellow from prototype
), ),
), ),
Center( Center(
child: Text( child: Text(
'$progressPercent%', '$progressPercent%',
style: UiTypography.display1b.copyWith( // 26px Bold style: UiTypography.display1b.white,
color: UiColors.white,
),
), ),
), ),
], ],
), ),
), ),
const SizedBox(width: 24), const SizedBox(width: UiConstants.space6),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text( Text(
t.staff_certificates.progress.title, t.staff_certificates.progress.title,
style: UiTypography.body1b.copyWith( // 16px Bold style: UiTypography.body1b.white,
color: UiColors.white,
),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
t.staff_certificates.progress.verified_count(completed: completedCount, total: totalCount), t.staff_certificates.progress.verified_count(
style: UiTypography.body3r.copyWith( // 12px Regular completed: completedCount, total: totalCount),
color: UiColors.white.withOpacity(0.7), style: UiTypography.body3r.copyWith(
color: UiColors.white.withValues(alpha: 0.7),
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: UiConstants.space2),
Row( Row(
children: <Widget>[ children: <Widget>[
const Icon( const Icon(
UiIcons.shield, UiIcons.shield,
color: Color(0xFFF9E547), color: UiColors.accent,
size: 16, size: 16,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
t.staff_certificates.progress.active, t.staff_certificates.progress.active,
style: UiTypography.body3m.copyWith( // 12px Medium style: UiTypography.body3m.accent,
color: const Color(0xFFF9E547),
),
), ),
], ],
), ),

View File

@@ -1,7 +1,6 @@
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:krow_domain/krow_domain.dart'; import 'package:krow_domain/krow_domain.dart';
import 'package:lucide_icons/lucide_icons.dart';
// ignore: depend_on_referenced_packages // ignore: depend_on_referenced_packages
import 'package:core_localization/core_localization.dart'; import 'package:core_localization/core_localization.dart';
@@ -18,11 +17,11 @@ class DocumentCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
margin: const EdgeInsets.only(bottom: 12), margin: const EdgeInsets.only(bottom: UiConstants.space3),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.bgPopup, color: UiColors.bgPopup,
borderRadius: BorderRadius.circular(8), borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border), border: Border.all(color: UiColors.border),
), ),
child: Row( child: Row(
@@ -32,7 +31,7 @@ class DocumentCard extends StatelessWidget {
width: 40, width: 40,
height: 40, height: 40,
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.primary.withOpacity(0.1), color: UiColors.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: const Center( child: const Center(
@@ -43,7 +42,7 @@ class DocumentCard extends StatelessWidget {
), ),
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: UiConstants.space3),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -53,9 +52,7 @@ class DocumentCard extends StatelessWidget {
children: <Widget>[ children: <Widget>[
Text( Text(
document.name, document.name,
style: UiTypography.body1m.copyWith( style: UiTypography.body1m.textPrimary,
color: UiColors.textPrimary,
),
), ),
_getStatusIcon(document.status), _getStatusIcon(document.status),
], ],
@@ -64,15 +61,13 @@ class DocumentCard extends StatelessWidget {
if (document.description != null) if (document.description != null)
Text( Text(
document.description!, document.description!,
style: UiTypography.body2r.copyWith( style: UiTypography.body2r.textSecondary,
color: UiColors.textSecondary,
),
), ),
const SizedBox(height: 12), const SizedBox(height: UiConstants.space3),
Row( Row(
children: <Widget>[ children: <Widget>[
_buildStatusBadge(document.status), _buildStatusBadge(document.status),
const SizedBox(width: 8), const SizedBox(width: UiConstants.space2),
_buildActionButton(document.status), _buildActionButton(document.status),
], ],
), ),
@@ -114,27 +109,27 @@ class DocumentCard extends StatelessWidget {
switch (status) { switch (status) {
case DocumentStatus.verified: case DocumentStatus.verified:
bg = UiColors.textSuccess.withOpacity(0.2); bg = UiColors.tagSuccess;
text = UiColors.textSuccess; text = UiColors.textSuccess;
label = t.staff_documents.card.verified; label = t.staff_documents.card.verified;
break; break;
case DocumentStatus.pending: case DocumentStatus.pending:
bg = UiColors.textWarning.withOpacity(0.2); bg = UiColors.tagPending;
text = UiColors.textWarning; text = UiColors.textWarning;
label = t.staff_documents.card.pending; label = t.staff_documents.card.pending;
break; break;
case DocumentStatus.missing: case DocumentStatus.missing:
bg = UiColors.textError.withOpacity(0.2); bg = UiColors.textError.withValues(alpha: 0.1);
text = UiColors.textError; text = UiColors.textError;
label = t.staff_documents.card.missing; label = t.staff_documents.card.missing;
break; break;
case DocumentStatus.rejected: case DocumentStatus.rejected:
bg = UiColors.textError.withOpacity(0.2); bg = UiColors.textError.withValues(alpha: 0.1);
text = UiColors.textError; text = UiColors.textError;
label = t.staff_documents.card.rejected; label = t.staff_documents.card.rejected;
break; break;
case DocumentStatus.expired: case DocumentStatus.expired:
bg = UiColors.textError.withOpacity(0.2); bg = UiColors.textError.withValues(alpha: 0.1);
text = UiColors.textError; text = UiColors.textError;
label = t.staff_documents.card.rejected; // Or define "Expired" string label = t.staff_documents.card.rejected; // Or define "Expired" string
break; break;
@@ -165,7 +160,7 @@ class DocumentCard extends StatelessWidget {
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
Icon( Icon(
isVerified ? UiIcons.eye : LucideIcons.upload, isVerified ? UiIcons.eye : UiIcons.upload,
size: 16, size: 16,
color: UiColors.primary, color: UiColors.primary,
), ),
@@ -174,9 +169,7 @@ class DocumentCard extends StatelessWidget {
isVerified isVerified
? t.staff_documents.card.view ? t.staff_documents.card.view
: t.staff_documents.card.upload, : t.staff_documents.card.upload,
style: UiTypography.body3m.copyWith( style: UiTypography.body3m.primary,
color: UiColors.primary,
),
), ),
], ],
), ),

View File

@@ -24,10 +24,10 @@ class DocumentsProgressCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.bgPopup, color: UiColors.bgPopup,
borderRadius: BorderRadius.circular(8), borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border), border: Border.all(color: UiColors.border),
), ),
child: Column( child: Column(
@@ -37,16 +37,14 @@ class DocumentsProgressCard extends StatelessWidget {
children: <Widget>[ children: <Widget>[
Text( Text(
t.staff_documents.verification_card.title, t.staff_documents.verification_card.title,
style: UiTypography.body1m.copyWith( style: UiTypography.body1m.textPrimary,
color: UiColors.textPrimary,
),
), ),
Text( Text(
t.staff_documents.verification_card.progress( t.staff_documents.verification_card.progress(
completed: completedCount, completed: completedCount,
total: totalCount, total: totalCount,
), ),
style: UiTypography.body2r.copyWith(color: UiColors.primary), style: UiTypography.body2r.primary,
), ),
], ],
), ),

View File

@@ -83,10 +83,13 @@ class _FormI9PageState extends State<FormI9Page> {
if (state.status == FormI9Status.success) { if (state.status == FormI9Status.success) {
// Success view is handled by state check in build or we can navigate // Success view is handled by state check in build or we can navigate
} else if (state.status == FormI9Status.failure) { } else if (state.status == FormI9Status.failure) {
final ScaffoldMessengerState messenger = ScaffoldMessenger.of(context); final ScaffoldMessengerState messenger =
ScaffoldMessenger.of(context);
messenger.hideCurrentSnackBar(); messenger.hideCurrentSnackBar();
messenger.showSnackBar( messenger.showSnackBar(
SnackBar(content: Text(state.errorMessage ?? 'An error occurred')), SnackBar(
content: Text(state.errorMessage ?? 'An error occurred'),
),
); );
} }
}, },
@@ -100,7 +103,10 @@ class _FormI9PageState extends State<FormI9Page> {
_buildHeader(context, state), _buildHeader(context, state),
Expanded( Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24), padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space5,
vertical: UiConstants.space6,
),
child: _buildCurrentStep(context, state), child: _buildCurrentStep(context, state),
), ),
), ),
@@ -118,9 +124,9 @@ class _FormI9PageState extends State<FormI9Page> {
backgroundColor: UiColors.background, backgroundColor: UiColors.background,
body: Center( body: Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(24.0), padding: const EdgeInsets.all(UiConstants.space6),
child: Container( child: Container(
padding: const EdgeInsets.all(32), padding: const EdgeInsets.all(UiConstants.space8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.bgPopup, color: UiColors.bgPopup,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
@@ -132,40 +138,40 @@ class _FormI9PageState extends State<FormI9Page> {
Container( Container(
width: 64, width: 64,
height: 64, height: 64,
decoration: const BoxDecoration( decoration: BoxDecoration(
color: Color(0xFFDCFCE7), color: UiColors.tagSuccess,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: const Icon( child: const Icon(
UiIcons.success, UiIcons.success,
color: Color(0xFF16A34A), color: UiColors.textSuccess,
size: 32, size: 32,
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: UiConstants.space4),
Text( Text(
'Form I-9 Submitted!', 'Form I-9 Submitted!',
style: UiTypography.headline4m.copyWith( style: UiTypography.headline4m.textPrimary,
color: UiColors.textPrimary,
),
), ),
const SizedBox(height: 8), const SizedBox(height: UiConstants.space2),
Text( Text(
'Your employment eligibility verification has been submitted.', 'Your employment eligibility verification has been submitted.',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: UiTypography.body2r.copyWith(color: UiColors.textSecondary), style: UiTypography.body2r.textSecondary,
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
onPressed: () => Modular.to.pop(true), onPressed: () => Modular.to.pop(true),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: UiColors.primary, backgroundColor: UiColors.primary,
foregroundColor: UiColors.bgPopup, foregroundColor: UiColors.white,
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(
vertical: UiConstants.space4,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
), ),
elevation: 0, elevation: 0,
), ),
@@ -183,7 +189,12 @@ class _FormI9PageState extends State<FormI9Page> {
Widget _buildHeader(BuildContext context, FormI9State state) { Widget _buildHeader(BuildContext context, FormI9State state) {
return Container( return Container(
color: UiColors.primary, color: UiColors.primary,
padding: const EdgeInsets.only(top: 60, bottom: 24, left: 20, right: 20), padding: const EdgeInsets.only(
top: 60,
bottom: UiConstants.space6,
left: UiConstants.space5,
right: UiConstants.space5,
),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
@@ -193,31 +204,34 @@ class _FormI9PageState extends State<FormI9Page> {
onTap: () => Modular.to.pop(), onTap: () => Modular.to.pop(),
child: const Icon( child: const Icon(
UiIcons.arrowLeft, UiIcons.arrowLeft,
color: UiColors.bgPopup, color: UiColors.white,
size: 24, size: 24,
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: UiConstants.space3),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text( Text(
'Form I-9', 'Form I-9',
style: UiTypography.headline4m.copyWith( style: UiTypography.headline4m.white,
color: UiColors.bgPopup,
),
), ),
Text( Text(
'Employment Eligibility Verification', 'Employment Eligibility Verification',
style: UiTypography.body3r.copyWith(color: UiColors.bgPopup.withOpacity(0.7)), style: UiTypography.body3r.copyWith(
color: UiColors.white.withValues(alpha: 0.7),
),
), ),
], ],
), ),
], ],
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
Row( Row(
children: _steps.asMap().entries.map((MapEntry<int, Map<String, String>> entry) { children: _steps
.asMap()
.entries
.map((MapEntry<int, Map<String, String>> entry) {
final int idx = entry.key; final int idx = entry.key;
final bool isLast = idx == _steps.length - 1; final bool isLast = idx == _steps.length - 1;
return Expanded( return Expanded(
@@ -228,8 +242,8 @@ class _FormI9PageState extends State<FormI9Page> {
height: 4, height: 4,
decoration: BoxDecoration( decoration: BoxDecoration(
color: idx <= state.currentStep color: idx <= state.currentStep
? UiColors.bgPopup ? UiColors.white
: UiColors.bgPopup.withOpacity(0.3), : UiColors.white.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(2), borderRadius: BorderRadius.circular(2),
), ),
), ),
@@ -240,20 +254,21 @@ class _FormI9PageState extends State<FormI9Page> {
); );
}).toList(), }).toList(),
), ),
const SizedBox(height: 8), const SizedBox(height: UiConstants.space2),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[ children: <Widget>[
Text( Text(
'Step ${state.currentStep + 1} of ${_steps.length}', 'Step ${state.currentStep + 1} of ${_steps.length}',
style: UiTypography.body3r.copyWith(color: UiColors.bgPopup.withOpacity(0.7)), style: UiTypography.body3r.copyWith(
color: UiColors.white.withValues(alpha: 0.7),
),
), ),
Expanded( Expanded(
child: Text( child: Text(
_steps[state.currentStep]['title']!, _steps[state.currentStep]['title']!,
textAlign: TextAlign.end, textAlign: TextAlign.end,
style: UiTypography.body3m.copyWith( style: UiTypography.body3m.white.copyWith(
color: UiColors.bgPopup,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
@@ -292,8 +307,7 @@ class _FormI9PageState extends State<FormI9Page> {
children: <Widget>[ children: <Widget>[
Text( Text(
label, label,
style: UiTypography.body3m.copyWith( style: UiTypography.body3m.textSecondary.copyWith(
color: UiColors.textSecondary,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
@@ -305,26 +319,26 @@ class _FormI9PageState extends State<FormI9Page> {
), ),
onChanged: onChanged, onChanged: onChanged,
keyboardType: keyboardType, keyboardType: keyboardType,
style: UiTypography.body2r.copyWith(color: UiColors.textPrimary), style: UiTypography.body2r.textPrimary,
decoration: InputDecoration( decoration: InputDecoration(
hintText: placeholder, hintText: placeholder,
hintStyle: TextStyle(color: Colors.grey[400]), hintStyle: const TextStyle(color: UiColors.textPlaceholder),
filled: true, filled: true,
fillColor: UiColors.bgPopup, fillColor: UiColors.bgPopup,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 16, horizontal: UiConstants.space4,
vertical: 16, vertical: UiConstants.space4,
), ),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
borderSide: const BorderSide(color: UiColors.border), borderSide: const BorderSide(color: UiColors.border),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
borderSide: const BorderSide(color: UiColors.border), borderSide: const BorderSide(color: UiColors.border),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
borderSide: const BorderSide(color: UiColors.primary), borderSide: const BorderSide(color: UiColors.primary),
), ),
), ),
@@ -455,15 +469,15 @@ class _FormI9PageState extends State<FormI9Page> {
children: <Widget>[ children: <Widget>[
Text( Text(
'State *', 'State *',
style: UiTypography.body3m.copyWith( style: UiTypography.body3m.textSecondary.copyWith(
color: UiColors.textSecondary,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
DropdownButtonFormField<String>( DropdownButtonFormField<String>(
value: state.state.isEmpty ? null : state.state, value: state.state.isEmpty ? null : state.state,
onChanged: (String? val) => context.read<FormI9Cubit>().stateChanged(val ?? ''), onChanged: (String? val) =>
context.read<FormI9Cubit>().stateChanged(val ?? ''),
items: _usStates.map((String stateAbbr) { items: _usStates.map((String stateAbbr) {
return DropdownMenuItem<String>( return DropdownMenuItem<String>(
value: stateAbbr, value: stateAbbr,
@@ -473,13 +487,15 @@ class _FormI9PageState extends State<FormI9Page> {
decoration: InputDecoration( decoration: InputDecoration(
filled: true, filled: true,
fillColor: UiColors.bgPopup, fillColor: UiColors.bgPopup,
contentPadding: const EdgeInsets.symmetric(horizontal: 16), contentPadding: const EdgeInsets.symmetric(
horizontal: UiConstants.space4,
),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
borderSide: const BorderSide(color: UiColors.border), borderSide: const BorderSide(color: UiColors.border),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
borderSide: const BorderSide(color: UiColors.border), borderSide: const BorderSide(color: UiColors.border),
), ),
), ),
@@ -507,9 +523,9 @@ class _FormI9PageState extends State<FormI9Page> {
children: <Widget>[ children: <Widget>[
Text( Text(
'I attest, under penalty of perjury, that I am (check one of the following boxes):', 'I attest, under penalty of perjury, that I am (check one of the following boxes):',
style: UiTypography.body2m.copyWith(color: UiColors.textPrimary), style: UiTypography.body2m.textPrimary,
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
_buildRadioOption( _buildRadioOption(
context, context,
state, state,
@@ -578,15 +594,21 @@ class _FormI9PageState extends State<FormI9Page> {
); );
} }
Widget _buildRadioOption(BuildContext context, FormI9State state, String value, String label, {Widget? child}) { Widget _buildRadioOption(
BuildContext context,
FormI9State state,
String value,
String label, {
Widget? child,
}) {
final bool isSelected = state.citizenshipStatus == value; final bool isSelected = state.citizenshipStatus == value;
return GestureDetector( return GestureDetector(
onTap: () => context.read<FormI9Cubit>().citizenshipStatusChanged(value), onTap: () => context.read<FormI9Cubit>().citizenshipStatusChanged(value),
child: Container( child: Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.bgPopup, color: UiColors.bgPopup,
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
border: Border.all( border: Border.all(
color: isSelected ? UiColors.primary : UiColors.border, color: isSelected ? UiColors.primary : UiColors.border,
width: isSelected ? 2 : 1, width: isSelected ? 2 : 1,
@@ -602,18 +624,16 @@ class _FormI9PageState extends State<FormI9Page> {
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all( border: Border.all(
color: isSelected ? UiColors.primary : Colors.grey, color: isSelected ? UiColors.primary : UiColors.border,
width: isSelected ? 6 : 2, width: isSelected ? 6 : 2,
), ),
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: UiConstants.space3),
Expanded( Expanded(
child: Text( child: Text(
label, label,
style: UiTypography.body2m.copyWith( style: UiTypography.body2m.textPrimary,
color: UiColors.textPrimary,
),
), ),
), ),
], ],
@@ -630,10 +650,10 @@ class _FormI9PageState extends State<FormI9Page> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.bgPopup, color: UiColors.bgPopup,
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border), border: Border.all(color: UiColors.border),
), ),
child: Column( child: Column(
@@ -643,15 +663,21 @@ class _FormI9PageState extends State<FormI9Page> {
'Summary', 'Summary',
style: UiTypography.headline4m.copyWith(fontSize: 14), style: UiTypography.headline4m.copyWith(fontSize: 14),
), ),
const SizedBox(height: 12), const SizedBox(height: UiConstants.space3),
_buildSummaryRow('Name', '${state.firstName} ${state.lastName}'), _buildSummaryRow('Name', '${state.firstName} ${state.lastName}'),
_buildSummaryRow('Address', '${state.address}, ${state.city}'), _buildSummaryRow('Address', '${state.address}, ${state.city}'),
_buildSummaryRow('SSN', '***-**-${state.ssn.length >= 4 ? state.ssn.substring(state.ssn.length - 4) : '****'}'), _buildSummaryRow(
_buildSummaryRow('Citizenship', _getReadableCitizenship(state.citizenshipStatus)), 'SSN',
'***-**-${state.ssn.length >= 4 ? state.ssn.substring(state.ssn.length - 4) : '****'}',
),
_buildSummaryRow(
'Citizenship',
_getReadableCitizenship(state.citizenshipStatus),
),
], ],
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
CheckboxListTile( CheckboxListTile(
value: state.preparerUsed, value: state.preparerUsed,
onChanged: (bool? val) { onChanged: (bool? val) {
@@ -660,29 +686,27 @@ class _FormI9PageState extends State<FormI9Page> {
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
title: Text( title: Text(
'I used a preparer or translator', 'I used a preparer or translator',
style: UiTypography.body2r.copyWith(color: UiColors.textPrimary), style: UiTypography.body2r.textPrimary,
), ),
controlAffinity: ListTileControlAffinity.leading, controlAffinity: ListTileControlAffinity.leading,
activeColor: UiColors.primary, activeColor: UiColors.primary,
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.amber[50], color: UiColors.accent.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
), ),
child: const Text( child: Text(
'I am aware that federal law provides for imprisonment and/or fines for false statements or use of false documents in connection with the completion of this form.', 'I am aware that federal law provides for imprisonment and/or fines for false statements or use of false documents in connection with the completion of this form.',
style: TextStyle(fontSize: 12, color: Color(0xFFB45309)), style: UiTypography.body3r.textWarning.copyWith(fontSize: 12),
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
Text( Text(
'Signature (type your full name) *', 'Signature (type your full name) *',
style: UiTypography.body3m.copyWith( style: UiTypography.body3m.textSecondary,
color: UiColors.textSecondary,
),
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
TextField( TextField(
@@ -690,44 +714,46 @@ class _FormI9PageState extends State<FormI9Page> {
..selection = TextSelection.fromPosition( ..selection = TextSelection.fromPosition(
TextPosition(offset: state.signature.length), TextPosition(offset: state.signature.length),
), ),
onChanged: (String val) => context.read<FormI9Cubit>().signatureChanged(val), onChanged: (String val) =>
context.read<FormI9Cubit>().signatureChanged(val),
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Type your full name', hintText: 'Type your full name',
filled: true, filled: true,
fillColor: UiColors.bgPopup, fillColor: UiColors.bgPopup,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 16, horizontal: UiConstants.space4,
vertical: 16, vertical: UiConstants.space4,
), ),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
borderSide: const BorderSide(color: UiColors.border), borderSide: const BorderSide(color: UiColors.border),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
borderSide: const BorderSide(color: UiColors.border), borderSide: const BorderSide(color: UiColors.border),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
borderSide: const BorderSide(color: UiColors.primary), borderSide: const BorderSide(color: UiColors.primary),
), ),
), ),
style: const TextStyle(fontFamily: 'Cursive', fontSize: 18), style: const TextStyle(fontFamily: 'Cursive', fontSize: 18),
), ),
const SizedBox(height: 16), const SizedBox(height: UiConstants.space4),
Text( Text(
'Date', 'Date',
style: UiTypography.body3m.copyWith( style: UiTypography.body3m.textSecondary,
color: UiColors.textSecondary,
),
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
Container( Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space4,
vertical: UiConstants.space4,
),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFF3F4F6), color: UiColors.bgSecondary,
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border), border: Border.all(color: UiColors.border),
), ),
child: Text( child: Text(
@@ -747,15 +773,13 @@ class _FormI9PageState extends State<FormI9Page> {
children: <Widget>[ children: <Widget>[
Text( Text(
label, label,
style: UiTypography.body2r.copyWith(color: UiColors.textSecondary), style: UiTypography.body2r.textSecondary,
), ),
Expanded( Expanded(
child: Text( child: Text(
value, value,
textAlign: TextAlign.end, textAlign: TextAlign.end,
style: UiTypography.body2m.copyWith( style: UiTypography.body2m.textPrimary,
color: UiColors.textPrimary,
),
), ),
), ),
], ],
@@ -780,7 +804,7 @@ class _FormI9PageState extends State<FormI9Page> {
Widget _buildFooter(BuildContext context, FormI9State state) { Widget _buildFooter(BuildContext context, FormI9State state) {
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: UiColors.bgPopup, color: UiColors.bgPopup,
border: Border(top: BorderSide(color: UiColors.border)), border: Border(top: BorderSide(color: UiColors.border)),
@@ -791,24 +815,30 @@ class _FormI9PageState extends State<FormI9Page> {
if (state.currentStep > 0) if (state.currentStep > 0)
Expanded( Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.only(right: 12), padding: const EdgeInsets.only(right: UiConstants.space3),
child: OutlinedButton( child: OutlinedButton(
onPressed: () => _handleBack(context), onPressed: () => _handleBack(context),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(
vertical: UiConstants.space4,
),
side: const BorderSide(color: UiColors.border), side: const BorderSide(color: UiColors.border),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
), ),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
const Icon(UiIcons.arrowLeft, size: 16, color: UiColors.textPrimary), const Icon(
UiIcons.arrowLeft,
size: 16,
color: UiColors.textPrimary,
),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
'Back', 'Back',
style: UiTypography.body2r.copyWith(color: UiColors.textPrimary), style: UiTypography.body2r.textPrimary,
), ),
], ],
), ),
@@ -818,16 +848,20 @@ class _FormI9PageState extends State<FormI9Page> {
Expanded( Expanded(
flex: 2, flex: 2,
child: ElevatedButton( child: ElevatedButton(
onPressed: (_canProceed(state) && state.status != FormI9Status.submitting) onPressed: (
_canProceed(state) &&
state.status != FormI9Status.submitting)
? () => _handleNext(context, state.currentStep) ? () => _handleNext(context, state.currentStep)
: null, : null,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: UiColors.primary, backgroundColor: UiColors.primary,
disabledBackgroundColor: Colors.grey[300], disabledBackgroundColor: UiColors.bgSecondary,
foregroundColor: UiColors.bgPopup, foregroundColor: UiColors.white,
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(
vertical: UiConstants.space4,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
), ),
elevation: 0, elevation: 0,
), ),
@@ -836,7 +870,7 @@ class _FormI9PageState extends State<FormI9Page> {
width: 20, width: 20,
height: 20, height: 20,
child: CircularProgressIndicator( child: CircularProgressIndicator(
color: UiColors.bgPopup, color: UiColors.white,
strokeWidth: 2, strokeWidth: 2,
), ),
) )
@@ -850,7 +884,7 @@ class _FormI9PageState extends State<FormI9Page> {
), ),
if (state.currentStep < _steps.length - 1) ...<Widget>[ if (state.currentStep < _steps.length - 1) ...<Widget>[
const SizedBox(width: 8), const SizedBox(width: 8),
const Icon(UiIcons.arrowRight, size: 16, color: UiColors.bgPopup), const Icon(UiIcons.arrowRight, size: 16, color: UiColors.white),
], ],
], ],
), ),

View File

@@ -146,7 +146,10 @@ class _FormW4PageState extends State<FormW4Page> {
_buildHeader(context, state), _buildHeader(context, state),
Expanded( Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24), padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space5,
vertical: UiConstants.space6,
),
child: _buildCurrentStep(context, state), child: _buildCurrentStep(context, state),
), ),
), ),
@@ -164,9 +167,9 @@ class _FormW4PageState extends State<FormW4Page> {
backgroundColor: UiColors.background, backgroundColor: UiColors.background,
body: Center( body: Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(24.0), padding: const EdgeInsets.all(UiConstants.space6),
child: Container( child: Container(
padding: const EdgeInsets.all(32), padding: const EdgeInsets.all(UiConstants.space8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.bgPopup, color: UiColors.bgPopup,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
@@ -178,40 +181,40 @@ class _FormW4PageState extends State<FormW4Page> {
Container( Container(
width: 64, width: 64,
height: 64, height: 64,
decoration: const BoxDecoration( decoration: BoxDecoration(
color: Color(0xFFDCFCE7), color: UiColors.tagSuccess,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: const Icon( child: const Icon(
UiIcons.success, UiIcons.success,
color: Color(0xFF16A34A), color: UiColors.textSuccess,
size: 32, size: 32,
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: UiConstants.space4),
Text( Text(
'Form W-4 Submitted!', 'Form W-4 Submitted!',
style: UiTypography.headline4m.copyWith( style: UiTypography.headline4m.textPrimary,
color: UiColors.textPrimary,
),
), ),
const SizedBox(height: 8), const SizedBox(height: UiConstants.space2),
Text( Text(
'Your withholding certificate has been submitted to your employer.', 'Your withholding certificate has been submitted to your employer.',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: UiTypography.body2r.copyWith(color: UiColors.textSecondary), style: UiTypography.body2r.textSecondary,
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
onPressed: () => Modular.to.pop(true), onPressed: () => Modular.to.pop(true),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: UiColors.primary, backgroundColor: UiColors.primary,
foregroundColor: UiColors.bgPopup, foregroundColor: UiColors.white,
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(
vertical: UiConstants.space4,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
), ),
elevation: 0, elevation: 0,
), ),
@@ -229,7 +232,12 @@ class _FormW4PageState extends State<FormW4Page> {
Widget _buildHeader(BuildContext context, FormW4State state) { Widget _buildHeader(BuildContext context, FormW4State state) {
return Container( return Container(
color: UiColors.primary, color: UiColors.primary,
padding: const EdgeInsets.only(top: 60, bottom: 24, left: 20, right: 20), padding: const EdgeInsets.only(
top: 60,
bottom: UiConstants.space6,
left: UiConstants.space5,
right: UiConstants.space5,
),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
@@ -239,31 +247,34 @@ class _FormW4PageState extends State<FormW4Page> {
onTap: () => Modular.to.pop(), onTap: () => Modular.to.pop(),
child: const Icon( child: const Icon(
UiIcons.arrowLeft, UiIcons.arrowLeft,
color: UiColors.bgPopup, color: UiColors.white,
size: 24, size: 24,
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: UiConstants.space3),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text( Text(
'Form W-4', 'Form W-4',
style: UiTypography.headline4m.copyWith( style: UiTypography.headline4m.white,
color: UiColors.bgPopup,
),
), ),
Text( Text(
'Employee\'s Withholding Certificate', 'Employee\'s Withholding Certificate',
style: UiTypography.body3r.copyWith(color: UiColors.bgPopup.withOpacity(0.7)), style: UiTypography.body3r.copyWith(
color: UiColors.white.withValues(alpha: 0.7),
),
), ),
], ],
), ),
], ],
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
Row( Row(
children: _steps.asMap().entries.map((MapEntry<int, Map<String, String>> entry) { children: _steps
.asMap()
.entries
.map((MapEntry<int, Map<String, String>> entry) {
final int idx = entry.key; final int idx = entry.key;
final bool isLast = idx == _steps.length - 1; final bool isLast = idx == _steps.length - 1;
return Expanded( return Expanded(
@@ -274,8 +285,8 @@ class _FormW4PageState extends State<FormW4Page> {
height: 4, height: 4,
decoration: BoxDecoration( decoration: BoxDecoration(
color: idx <= state.currentStep color: idx <= state.currentStep
? UiColors.bgPopup ? UiColors.white
: UiColors.bgPopup.withOpacity(0.3), : UiColors.white.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(2), borderRadius: BorderRadius.circular(2),
), ),
), ),
@@ -286,18 +297,19 @@ class _FormW4PageState extends State<FormW4Page> {
); );
}).toList(), }).toList(),
), ),
const SizedBox(height: 8), const SizedBox(height: UiConstants.space2),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[ children: <Widget>[
Text( Text(
'Step ${state.currentStep + 1} of ${_steps.length}', 'Step ${state.currentStep + 1} of ${_steps.length}',
style: UiTypography.body3r.copyWith(color: UiColors.bgPopup.withOpacity(0.7)), style: UiTypography.body3r.copyWith(
color: UiColors.white.withValues(alpha: 0.7),
),
), ),
Text( Text(
_steps[state.currentStep]['title']!, _steps[state.currentStep]['title']!,
style: UiTypography.body3m.copyWith( style: UiTypography.body3m.white.copyWith(
color: UiColors.bgPopup,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
@@ -339,8 +351,7 @@ class _FormW4PageState extends State<FormW4Page> {
children: <Widget>[ children: <Widget>[
Text( Text(
label, label,
style: UiTypography.body3m.copyWith( style: UiTypography.body3m.textSecondary.copyWith(
color: UiColors.textSecondary,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
@@ -352,26 +363,26 @@ class _FormW4PageState extends State<FormW4Page> {
), ),
onChanged: onChanged, onChanged: onChanged,
keyboardType: keyboardType, keyboardType: keyboardType,
style: UiTypography.body2r.copyWith(color: UiColors.textPrimary), style: UiTypography.body2r.textPrimary,
decoration: InputDecoration( decoration: InputDecoration(
hintText: placeholder, hintText: placeholder,
hintStyle: TextStyle(color: Colors.grey[400]), hintStyle: const TextStyle(color: UiColors.textPlaceholder),
filled: true, filled: true,
fillColor: UiColors.bgPopup, fillColor: UiColors.bgPopup,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 16, horizontal: UiConstants.space4,
vertical: 16, vertical: UiConstants.space4,
), ),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
borderSide: const BorderSide(color: UiColors.border), borderSide: const BorderSide(color: UiColors.border),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
borderSide: const BorderSide(color: UiColors.border), borderSide: const BorderSide(color: UiColors.border),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
borderSide: const BorderSide(color: UiColors.primary), borderSide: const BorderSide(color: UiColors.primary),
), ),
), ),
@@ -438,25 +449,25 @@ class _FormW4PageState extends State<FormW4Page> {
return Column( return Column(
children: <Widget>[ children: <Widget>[
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.blue[50], color: UiColors.tagActive,
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
), ),
child: Row( child: Row(
children: const <Widget>[ children: <Widget>[
Icon(UiIcons.info, color: Color(0xFF2563EB), size: 20), const Icon(UiIcons.info, color: UiColors.primary, size: 20),
SizedBox(width: 12), const SizedBox(width: UiConstants.space3),
Expanded( Expanded(
child: Text( child: Text(
'Your filing status determines your standard deduction and tax rates.', 'Your filing status determines your standard deduction and tax rates.',
style: TextStyle(fontSize: 14, color: Color(0xFF1D4ED8)), style: UiTypography.body2r.textPrimary,
), ),
), ),
], ],
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
_buildRadioOption( _buildRadioOption(
context, context,
state, state,
@@ -484,15 +495,21 @@ class _FormW4PageState extends State<FormW4Page> {
); );
} }
Widget _buildRadioOption(BuildContext context, FormW4State state, String value, String label, String? subLabel) { Widget _buildRadioOption(
BuildContext context,
FormW4State state,
String value,
String label,
String? subLabel,
) {
final bool isSelected = state.filingStatus == value; final bool isSelected = state.filingStatus == value;
return GestureDetector( return GestureDetector(
onTap: () => context.read<FormW4Cubit>().filingStatusChanged(value), onTap: () => context.read<FormW4Cubit>().filingStatusChanged(value),
child: Container( child: Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.bgPopup, color: UiColors.bgPopup,
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
border: Border.all( border: Border.all(
color: isSelected ? UiColors.primary : UiColors.border, color: isSelected ? UiColors.primary : UiColors.border,
width: isSelected ? 2 : 1, width: isSelected ? 2 : 1,
@@ -508,29 +525,25 @@ class _FormW4PageState extends State<FormW4Page> {
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
border: Border.all( border: Border.all(
color: isSelected ? UiColors.primary : Colors.grey, color: isSelected ? UiColors.primary : UiColors.border,
width: isSelected ? 6 : 2, width: isSelected ? 6 : 2,
), ),
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: UiConstants.space3),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text( Text(
label, label,
style: UiTypography.body2m.copyWith( style: UiTypography.body2m.textPrimary,
color: UiColors.textPrimary,
),
), ),
if (subLabel != null) ...<Widget>[ if (subLabel != null) ...<Widget>[
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
subLabel, subLabel,
style: UiTypography.body3r.copyWith( style: UiTypography.body3r.textSecondary,
color: UiColors.textSecondary,
),
), ),
], ],
], ],
@@ -546,36 +559,32 @@ class _FormW4PageState extends State<FormW4Page> {
return Column( return Column(
children: <Widget>[ children: <Widget>[
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.amber[50], color: UiColors.accent.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
), ),
child: const Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Icon( const Icon(
UiIcons.help, UiIcons.help,
color: Color(0xFFD97706), color: UiColors.accent,
size: 20, size: 20,
), ),
SizedBox(width: 12), const SizedBox(width: UiConstants.space3),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text( Text(
'When to complete this step?', 'When to complete this step?',
style: TextStyle( style: UiTypography.body2m.accent,
fontWeight: FontWeight.w600,
color: Color(0xFF92400E),
fontSize: 14,
),
), ),
SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
'Complete this step only if you hold more than one job at a time, or are married filing jointly and your spouse also works.', 'Complete this step only if you hold more than one job at a time, or are married filing jointly and your spouse also works.',
style: TextStyle(fontSize: 12, color: Color(0xFFB45309)), style: UiTypography.body3r.accent,
), ),
], ],
), ),
@@ -583,18 +592,17 @@ class _FormW4PageState extends State<FormW4Page> {
], ],
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
GestureDetector( GestureDetector(
onTap: () => context.read<FormW4Cubit>().multipleJobsChanged(!state.multipleJobs), onTap: () =>
context.read<FormW4Cubit>().multipleJobsChanged(!state.multipleJobs),
child: Container( child: Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.bgPopup, color: UiColors.bgPopup,
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
border: Border.all( border: Border.all(
color: state.multipleJobs color: state.multipleJobs ? UiColors.primary : UiColors.border,
? UiColors.primary
: UiColors.border,
), ),
), ),
child: Row( child: Row(
@@ -604,20 +612,16 @@ class _FormW4PageState extends State<FormW4Page> {
width: 24, width: 24,
height: 24, height: 24,
decoration: BoxDecoration( decoration: BoxDecoration(
color: state.multipleJobs color: state.multipleJobs ? UiColors.primary : UiColors.bgPopup,
? UiColors.primary
: UiColors.bgPopup,
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
border: Border.all( border: Border.all(
color: state.multipleJobs color: state.multipleJobs ? UiColors.primary : UiColors.border,
? UiColors.primary
: Colors.grey,
), ),
), ),
child: state.multipleJobs child: state.multipleJobs
? const Icon( ? const Icon(
UiIcons.check, UiIcons.check,
color: UiColors.bgPopup, color: UiColors.white,
size: 16, size: 16,
) )
: null, : null,
@@ -629,16 +633,12 @@ class _FormW4PageState extends State<FormW4Page> {
children: <Widget>[ children: <Widget>[
Text( Text(
'I have multiple jobs or my spouse works', 'I have multiple jobs or my spouse works',
style: UiTypography.body2m.copyWith( style: UiTypography.body2m.textPrimary,
color: UiColors.textPrimary,
),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
'Check this box if there are only two jobs total', 'Check this box if there are only two jobs total',
style: UiTypography.body3r.copyWith( style: UiTypography.body3r.textSecondary,
color: UiColors.textSecondary,
),
), ),
], ],
), ),
@@ -651,7 +651,7 @@ class _FormW4PageState extends State<FormW4Page> {
Text( Text(
'If this does not apply, you can continue to the next step', 'If this does not apply, you can continue to the next step',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: UiTypography.body3r.copyWith(color: UiColors.textSecondary), style: UiTypography.body3r.textSecondary,
), ),
], ],
); );
@@ -661,30 +661,30 @@ class _FormW4PageState extends State<FormW4Page> {
return Column( return Column(
children: <Widget>[ children: <Widget>[
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.blue[50], // Same note about blue migration color: UiColors.tagActive,
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
), ),
child: const Row( child: Row(
children: <Widget>[ children: <Widget>[
Icon(UiIcons.info, color: Color(0xFF2563EB), size: 20), const Icon(UiIcons.info, color: UiColors.primary, size: 20),
SizedBox(width: 12), const SizedBox(width: UiConstants.space3),
Expanded( Expanded(
child: Text( child: Text(
'If your total income will be \$200,000 or less (\$400,000 if married filing jointly), you may claim credits for dependents.', 'If your total income will be \$200,000 or less (\$400,000 if married filing jointly), you may claim credits for dependents.',
style: TextStyle(fontSize: 14, color: Color(0xFF1D4ED8)), style: UiTypography.body2r.textPrimary,
), ),
), ),
], ],
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.bgPopup, color: UiColors.bgPopup,
borderRadius: BorderRadius.circular(16), borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border), border: Border.all(color: UiColors.border),
), ),
child: Column( child: Column(
@@ -715,10 +715,10 @@ class _FormW4PageState extends State<FormW4Page> {
if (_totalCredits(state) > 0) ...<Widget>[ if (_totalCredits(state) > 0) ...<Widget>[
const SizedBox(height: 16), const SizedBox(height: 16),
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFDCFCE7), color: const Color(0xFFDCFCE7),
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -770,16 +770,12 @@ class _FormW4PageState extends State<FormW4Page> {
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFDCFCE7), color: UiColors.tagSuccess,
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
), ),
child: Text( child: Text(
badge, badge,
style: const TextStyle( style: UiTypography.footnote2b.textSuccess,
fontSize: 10,
color: Color(0xFF15803D),
fontWeight: FontWeight.bold,
),
), ),
), ),
], ],
@@ -834,7 +830,7 @@ class _FormW4PageState extends State<FormW4Page> {
children: <Widget>[ children: <Widget>[
Text( Text(
'These adjustments are optional. You can skip them if they don\'t apply.', 'These adjustments are optional. You can skip them if they don\'t apply.',
style: UiTypography.body2r.copyWith(color: UiColors.textSecondary), style: UiTypography.body2r.textSecondary,
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
_buildTextField( _buildTextField(
@@ -848,7 +844,7 @@ class _FormW4PageState extends State<FormW4Page> {
padding: const EdgeInsets.only(top: 4, bottom: 16), padding: const EdgeInsets.only(top: 4, bottom: 16),
child: Text( child: Text(
'Include interest, dividends, retirement income', 'Include interest, dividends, retirement income',
style: UiTypography.body3r.copyWith(color: UiColors.textSecondary), style: UiTypography.body3r.textSecondary,
), ),
), ),
@@ -863,7 +859,7 @@ class _FormW4PageState extends State<FormW4Page> {
padding: const EdgeInsets.only(top: 4, bottom: 16), padding: const EdgeInsets.only(top: 4, bottom: 16),
child: Text( child: Text(
'If you expect to claim deductions other than the standard deduction', 'If you expect to claim deductions other than the standard deduction',
style: UiTypography.body3r.copyWith(color: UiColors.textSecondary), style: UiTypography.body3r.textSecondary,
), ),
), ),
@@ -878,7 +874,7 @@ class _FormW4PageState extends State<FormW4Page> {
padding: const EdgeInsets.only(top: 4, bottom: 16), padding: const EdgeInsets.only(top: 4, bottom: 16),
child: Text( child: Text(
'Any additional tax you want withheld each pay period', 'Any additional tax you want withheld each pay period',
style: UiTypography.body3r.copyWith(color: UiColors.textSecondary), style: UiTypography.body3r.textSecondary,
), ),
), ),
], ],
@@ -890,10 +886,10 @@ class _FormW4PageState extends State<FormW4Page> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.bgPopup, color: UiColors.bgPopup,
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border), border: Border.all(color: UiColors.border),
), ),
child: Column( child: Column(
@@ -927,22 +923,20 @@ class _FormW4PageState extends State<FormW4Page> {
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.amber[50], color: UiColors.accent.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
), ),
child: const Text( child: Text(
'Under penalties of perjury, I declare that this certificate, to the best of my knowledge and belief, is true, correct, and complete.', 'Under penalties of perjury, I declare that this certificate, to the best of my knowledge and belief, is true, correct, and complete.',
style: TextStyle(fontSize: 12, color: Color(0xFFB45309)), style: UiTypography.body3r.textWarning.copyWith(fontSize: 12),
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
Text( Text(
'Signature (type your full name) *', 'Signature (type your full name) *',
style: UiTypography.body3m.copyWith( style: UiTypography.body3m.textSecondary,
color: UiColors.textSecondary,
),
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
TextField( TextField(
@@ -950,44 +944,46 @@ class _FormW4PageState extends State<FormW4Page> {
..selection = TextSelection.fromPosition( ..selection = TextSelection.fromPosition(
TextPosition(offset: state.signature.length), TextPosition(offset: state.signature.length),
), ),
onChanged: (String val) => context.read<FormW4Cubit>().signatureChanged(val), onChanged: (String val) =>
context.read<FormW4Cubit>().signatureChanged(val),
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Type your full name', hintText: 'Type your full name',
filled: true, filled: true,
fillColor: UiColors.bgPopup, fillColor: UiColors.bgPopup,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 16, horizontal: UiConstants.space4,
vertical: 16, vertical: UiConstants.space4,
), ),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
borderSide: const BorderSide(color: UiColors.border), borderSide: const BorderSide(color: UiColors.border),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
borderSide: const BorderSide(color: UiColors.border), borderSide: const BorderSide(color: UiColors.border),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
borderSide: const BorderSide(color: UiColors.primary), borderSide: const BorderSide(color: UiColors.primary),
), ),
), ),
style: const TextStyle(fontFamily: 'Cursive', fontSize: 18), style: const TextStyle(fontFamily: 'Cursive', fontSize: 18),
), ),
const SizedBox(height: 16), const SizedBox(height: UiConstants.space4),
Text( Text(
'Date', 'Date',
style: UiTypography.body3m.copyWith( style: UiTypography.body3m.textSecondary,
color: UiColors.textSecondary,
),
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
Container( Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space4,
vertical: UiConstants.space4,
),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFF3F4F6), color: UiColors.bgSecondary,
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
border: Border.all(color: UiColors.border), border: Border.all(color: UiColors.border),
), ),
child: Text( child: Text(
@@ -1007,7 +1003,7 @@ class _FormW4PageState extends State<FormW4Page> {
children: <Widget>[ children: <Widget>[
Text( Text(
label, label,
style: UiTypography.body2r.copyWith(color: UiColors.textSecondary), style: UiTypography.body2r.textSecondary,
), ),
Text( Text(
value, value,
@@ -1035,7 +1031,7 @@ class _FormW4PageState extends State<FormW4Page> {
Widget _buildFooter(BuildContext context, FormW4State state) { Widget _buildFooter(BuildContext context, FormW4State state) {
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: UiColors.bgPopup, color: UiColors.bgPopup,
border: Border(top: BorderSide(color: UiColors.border)), border: Border(top: BorderSide(color: UiColors.border)),
@@ -1046,24 +1042,30 @@ class _FormW4PageState extends State<FormW4Page> {
if (state.currentStep > 0) if (state.currentStep > 0)
Expanded( Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.only(right: 12), padding: const EdgeInsets.only(right: UiConstants.space3),
child: OutlinedButton( child: OutlinedButton(
onPressed: () => _handleBack(context), onPressed: () => _handleBack(context),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(
vertical: UiConstants.space4,
),
side: const BorderSide(color: UiColors.border), side: const BorderSide(color: UiColors.border),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
), ),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
const Icon(UiIcons.arrowLeft, size: 16, color: UiColors.textPrimary), const Icon(
UiIcons.arrowLeft,
size: 16,
color: UiColors.textPrimary,
),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
'Back', 'Back',
style: UiTypography.body2r.copyWith(color: UiColors.textPrimary), style: UiTypography.body2r.textPrimary,
), ),
], ],
), ),
@@ -1073,16 +1075,20 @@ class _FormW4PageState extends State<FormW4Page> {
Expanded( Expanded(
flex: 2, flex: 2,
child: ElevatedButton( child: ElevatedButton(
onPressed: (_canProceed(state) && state.status != FormW4Status.submitting) onPressed: (
_canProceed(state) &&
state.status != FormW4Status.submitting)
? () => _handleNext(context, state.currentStep) ? () => _handleNext(context, state.currentStep)
: null, : null,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: UiColors.primary, backgroundColor: UiColors.primary,
disabledBackgroundColor: Colors.grey[300], disabledBackgroundColor: UiColors.bgSecondary,
foregroundColor: UiColors.bgPopup, foregroundColor: UiColors.white,
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(
vertical: UiConstants.space4,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: UiConstants.radiusLg,
), ),
elevation: 0, elevation: 0,
), ),
@@ -1091,7 +1097,7 @@ class _FormW4PageState extends State<FormW4Page> {
width: 20, width: 20,
height: 20, height: 20,
child: CircularProgressIndicator( child: CircularProgressIndicator(
color: UiColors.bgPopup, color: UiColors.white,
strokeWidth: 2, strokeWidth: 2,
), ),
) )
@@ -1105,7 +1111,7 @@ class _FormW4PageState extends State<FormW4Page> {
), ),
if (state.currentStep < _steps.length - 1) ...<Widget>[ if (state.currentStep < _steps.length - 1) ...<Widget>[
const SizedBox(width: 8), const SizedBox(width: 8),
const Icon(UiIcons.arrowRight, size: 16, color: UiColors.bgPopup), const Icon(UiIcons.arrowRight, size: 16, color: UiColors.white),
], ],
], ],
), ),

View File

@@ -22,7 +22,7 @@ class TaxFormsPage extends StatelessWidget {
), ),
title: Text( title: Text(
'Tax Documents', 'Tax Documents',
style: UiTypography.headline3m.copyWith(color: UiColors.bgPopup), style: UiTypography.headline3m.textSecondary,
), ),
bottom: PreferredSize( bottom: PreferredSize(
preferredSize: const Size.fromHeight(24), preferredSize: const Size.fromHeight(24),
@@ -38,7 +38,7 @@ class TaxFormsPage extends StatelessWidget {
child: Text( child: Text(
'Complete required forms to start working', 'Complete required forms to start working',
style: UiTypography.body3r.copyWith( style: UiTypography.body3r.copyWith(
color: UiColors.bgPopup.withOpacity(0.8), color: UiColors.primaryForeground.withValues(alpha: 0.8),
), ),
), ),
), ),
@@ -120,15 +120,11 @@ class TaxFormsPage extends StatelessWidget {
children: <Widget>[ children: <Widget>[
Text( Text(
'Document Progress', 'Document Progress',
style: UiTypography.body2m.copyWith( style: UiTypography.body2m.textPrimary,
color: UiColors.textPrimary,
),
), ),
Text( Text(
'$completedCount/$totalCount', '$completedCount/$totalCount',
style: UiTypography.body2m.copyWith( style: UiTypography.body2m.textSecondary,
color: UiColors.textSecondary,
),
), ),
], ],
), ),
@@ -180,7 +176,7 @@ class TaxFormsPage extends StatelessWidget {
width: 48, width: 48,
height: 48, height: 48,
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.primary.withOpacity(0.1), color: UiColors.primary.withValues(alpha: 0.1),
borderRadius: UiConstants.radiusLg, borderRadius: UiConstants.radiusLg,
), ),
child: Center(child: Text(icon, style: UiTypography.headline1m)), child: Center(child: Text(icon, style: UiTypography.headline1m)),
@@ -195,9 +191,7 @@ class TaxFormsPage extends StatelessWidget {
children: <Widget>[ children: <Widget>[
Text( Text(
form.title, form.title,
style: UiTypography.headline4m.copyWith( style: UiTypography.headline4m.textPrimary,
color: UiColors.textPrimary,
),
), ),
_buildStatusBadge(form.status), _buildStatusBadge(form.status),
], ],
@@ -205,17 +199,14 @@ class TaxFormsPage extends StatelessWidget {
const SizedBox(height: UiConstants.space1), const SizedBox(height: UiConstants.space1),
Text( Text(
form.subtitle ?? '', form.subtitle ?? '',
style: UiTypography.body2m.copyWith( style: UiTypography.body2m.textSecondary.copyWith(
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: UiColors.textSecondary,
), ),
), ),
const SizedBox(height: UiConstants.space1), const SizedBox(height: UiConstants.space1),
Text( Text(
form.description ?? '', form.description ?? '',
style: UiTypography.body3r.copyWith( style: UiTypography.body3r.textSecondary,
color: UiColors.textSecondary,
),
), ),
], ],
), ),
@@ -256,9 +247,7 @@ class TaxFormsPage extends StatelessWidget {
const SizedBox(width: UiConstants.space1), const SizedBox(width: UiConstants.space1),
Text( Text(
'Completed', 'Completed',
style: UiTypography.footnote2b.copyWith( style: UiTypography.footnote2b.textSuccess,
color: UiColors.textSuccess,
),
), ),
], ],
), ),
@@ -280,9 +269,7 @@ class TaxFormsPage extends StatelessWidget {
const SizedBox(width: UiConstants.space1), const SizedBox(width: UiConstants.space1),
Text( Text(
'In Progress', 'In Progress',
style: UiTypography.footnote2b.copyWith( style: UiTypography.footnote2b.textWarning,
color: UiColors.textWarning,
),
), ),
], ],
), ),
@@ -299,9 +286,7 @@ class TaxFormsPage extends StatelessWidget {
), ),
child: Text( child: Text(
'Not Started', 'Not Started',
style: UiTypography.footnote2b.copyWith( style: UiTypography.footnote2b.textSecondary,
color: UiColors.textSecondary,
),
), ),
); );
} }
@@ -325,16 +310,12 @@ class TaxFormsPage extends StatelessWidget {
children: <Widget>[ children: <Widget>[
Text( Text(
'Why are these needed?', 'Why are these needed?',
style: UiTypography.headline4m.copyWith( style: UiTypography.headline4m.textPrimary,
color: UiColors.textPrimary,
),
), ),
const SizedBox(height: UiConstants.space1), const SizedBox(height: UiConstants.space1),
Text( Text(
'I-9 and W-4 forms are required by federal law to verify your employment eligibility and set up correct tax withholding.', 'I-9 and W-4 forms are required by federal law to verify your employment eligibility and set up correct tax withholding.',
style: UiTypography.body3r.copyWith( style: UiTypography.body3r.textSecondary,
color: UiColors.textSecondary,
),
), ),
], ],
), ),

View File

@@ -37,7 +37,7 @@ class BankAccountPage extends StatelessWidget {
), ),
title: Text( title: Text(
strings.title, strings.title,
style: UiTypography.headline3m.copyWith(color: UiColors.textPrimary), style: UiTypography.headline3m.textPrimary,
), ),
bottom: PreferredSize( bottom: PreferredSize(
preferredSize: const Size.fromHeight(1.0), preferredSize: const Size.fromHeight(1.0),
@@ -165,7 +165,7 @@ class BankAccountPage extends StatelessWidget {
return Container( return Container(
padding: const EdgeInsets.all(UiConstants.space4), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.primary.withOpacity(0.08), color: UiColors.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(UiConstants.radiusBase), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
), ),
child: Row( child: Row(
@@ -179,15 +179,12 @@ class BankAccountPage extends StatelessWidget {
children: <Widget>[ children: <Widget>[
Text( Text(
strings.secure_title, strings.secure_title,
style: UiTypography.body2r.copyWith( // Was body2 style: UiTypography.body2m.textPrimary,
fontWeight: FontWeight.w500,
color: UiColors.textPrimary,
),
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
strings.secure_subtitle, strings.secure_subtitle,
style: UiTypography.body3r.copyWith(color: UiColors.textSecondary), // Was bodySmall style: UiTypography.body3r.textSecondary,
), ),
], ],
), ),
@@ -221,7 +218,7 @@ class BankAccountPage extends StatelessWidget {
width: 48, width: 48,
height: 48, height: 48,
decoration: BoxDecoration( decoration: BoxDecoration(
color: primaryColor.withOpacity(0.1), color: primaryColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(UiConstants.radiusBase), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
), ),
child: const Center( child: const Center(
@@ -238,10 +235,7 @@ class BankAccountPage extends StatelessWidget {
children: <Widget>[ children: <Widget>[
Text( Text(
account.bankName, account.bankName,
style: UiTypography.body2r.copyWith( // Was body2 style: UiTypography.body2m.textPrimary,
fontWeight: FontWeight.w500,
color: UiColors.textPrimary,
),
), ),
Text( Text(
strings.account_ending( strings.account_ending(
@@ -249,9 +243,7 @@ class BankAccountPage extends StatelessWidget {
? account.last4! ? account.last4!
: '----', : '----',
), ),
style: UiTypography.body2r.copyWith( // Was body2 style: UiTypography.body2r.textSecondary,
color: UiColors.textSecondary,
),
), ),
], ],
), ),
@@ -261,7 +253,7 @@ class BankAccountPage extends StatelessWidget {
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: primaryColor.withOpacity(0.15), color: primaryColor.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
child: Row( child: Row(
@@ -270,10 +262,7 @@ class BankAccountPage extends StatelessWidget {
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
strings.primary, strings.primary,
style: UiTypography.body3r.copyWith( // Was bodySmall style: UiTypography.body3m.primary,
fontWeight: FontWeight.w500,
color: primaryColor,
),
), ),
], ],
), ),

View File

@@ -43,7 +43,7 @@ class _AddAccountFormState extends State<AddAccountForm> {
children: <Widget>[ children: <Widget>[
Text( Text(
widget.strings.add_new_account, widget.strings.add_new_account,
style: UiTypography.headline4m.copyWith(color: UiColors.textPrimary), // Was header4 style: UiTypography.headline4m.textPrimary, // Was header4
), ),
const SizedBox(height: UiConstants.space4), const SizedBox(height: UiConstants.space4),
UiTextField( UiTextField(
@@ -71,8 +71,7 @@ class _AddAccountFormState extends State<AddAccountForm> {
padding: const EdgeInsets.only(bottom: UiConstants.space2), padding: const EdgeInsets.only(bottom: UiConstants.space2),
child: Text( child: Text(
widget.strings.account_type, widget.strings.account_type,
style: UiTypography.body2r.copyWith( // Was body2 style: UiTypography.body2m.textSecondary,
color: UiColors.textSecondary, fontWeight: FontWeight.w500),
), ),
), ),
Row( Row(
@@ -122,7 +121,7 @@ class _AddAccountFormState extends State<AddAccountForm> {
padding: const EdgeInsets.symmetric(vertical: 12), padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected color: isSelected
? UiColors.primary.withOpacity(0.05) ? UiColors.primary.withValues(alpha: 0.05)
: UiColors.bgPopup, // Was surface : UiColors.bgPopup, // Was surface
borderRadius: BorderRadius.circular(UiConstants.radiusBase), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
border: Border.all( border: Border.all(
@@ -133,8 +132,7 @@ class _AddAccountFormState extends State<AddAccountForm> {
child: Center( child: Center(
child: Text( child: Text(
label, label,
style: UiTypography.body2r.copyWith( // Was body2 style: UiTypography.body2b.copyWith(
fontWeight: FontWeight.w600,
color: isSelected ? UiColors.primary : UiColors.textSecondary, color: isSelected ? UiColors.primary : UiColors.textSecondary,
), ),
), ),

View File

@@ -41,9 +41,7 @@ class _TimeCardPageState extends State<TimeCardPage> {
), ),
title: Text( title: Text(
t.staff_time_card.title, t.staff_time_card.title,
style: UiTypography.headline4m.copyWith( style: UiTypography.headline4m.textPrimary,
color: UiColors.textPrimary,
),
), ),
bottom: PreferredSize( bottom: PreferredSize(
preferredSize: const Size.fromHeight(1.0), preferredSize: const Size.fromHeight(1.0),

View File

@@ -19,22 +19,22 @@ class TimesheetCard extends StatelessWidget {
switch (status) { switch (status) {
case TimeCardStatus.approved: case TimeCardStatus.approved:
statusBg = UiColors.textSuccess.withOpacity(0.12); statusBg = UiColors.tagSuccess;
statusColor = UiColors.textSuccess; statusColor = UiColors.textSuccess;
statusText = t.staff_time_card.status.approved; statusText = t.staff_time_card.status.approved;
break; break;
case TimeCardStatus.disputed: case TimeCardStatus.disputed:
statusBg = UiColors.destructive.withOpacity(0.12); statusBg = UiColors.destructive.withValues(alpha: 0.12);
statusColor = UiColors.destructive; statusColor = UiColors.destructive;
statusText = t.staff_time_card.status.disputed; statusText = t.staff_time_card.status.disputed;
break; break;
case TimeCardStatus.paid: case TimeCardStatus.paid:
statusBg = UiColors.primary.withOpacity(0.12); statusBg = UiColors.primary.withValues(alpha: 0.12);
statusColor = UiColors.primary; statusColor = UiColors.primary;
statusText = t.staff_time_card.status.paid; statusText = t.staff_time_card.status.paid;
break; break;
case TimeCardStatus.pending: case TimeCardStatus.pending:
statusBg = UiColors.textWarning.withOpacity(0.12); statusBg = UiColors.tagPending;
statusColor = UiColors.textWarning; statusColor = UiColors.textWarning;
statusText = t.staff_time_card.status.pending; statusText = t.staff_time_card.status.pending;
break; break;
@@ -61,15 +61,11 @@ class TimesheetCard extends StatelessWidget {
children: [ children: [
Text( Text(
timesheet.shiftTitle, timesheet.shiftTitle,
style: UiTypography.body1m.copyWith( style: UiTypography.body1m.textPrimary,
color: UiColors.textPrimary,
),
), ),
Text( Text(
timesheet.clientName, timesheet.clientName,
style: UiTypography.body2r.copyWith( style: UiTypography.body2r.textSecondary,
color: UiColors.textSecondary,
),
), ),
], ],
), ),
@@ -116,13 +112,11 @@ class TimesheetCard extends StatelessWidget {
children: [ children: [
Text( Text(
'${timesheet.totalHours.toStringAsFixed(1)} ${t.staff_time_card.hours} @ \$${timesheet.hourlyRate.toStringAsFixed(2)}${t.staff_time_card.per_hr}', '${timesheet.totalHours.toStringAsFixed(1)} ${t.staff_time_card.hours} @ \$${timesheet.hourlyRate.toStringAsFixed(2)}${t.staff_time_card.per_hr}',
style: UiTypography.body2r.copyWith(color: UiColors.textSecondary), style: UiTypography.body2r.textSecondary,
), ),
Text( Text(
'\$${timesheet.totalPay.toStringAsFixed(2)}', '\$${timesheet.totalPay.toStringAsFixed(2)}',
style: UiTypography.title2b.copyWith( style: UiTypography.title2b.primary,
color: UiColors.primary,
),
), ),
], ],
), ),
@@ -163,7 +157,7 @@ class _IconText extends StatelessWidget {
const SizedBox(width: UiConstants.space1), const SizedBox(width: UiConstants.space1),
Text( Text(
text, text,
style: UiTypography.body2r.copyWith(color: UiColors.textSecondary), style: UiTypography.body2r.textSecondary,
), ),
], ],
); );

View File

@@ -32,9 +32,7 @@ class AttirePage extends StatelessWidget {
), ),
title: Text( title: Text(
t.staff_profile_attire.title, t.staff_profile_attire.title,
style: UiTypography.headline3m.copyWith( style: UiTypography.headline3m.textPrimary,
color: UiColors.textPrimary,
),
), ),
bottom: PreferredSize( bottom: PreferredSize(
preferredSize: const Size.fromHeight(1.0), preferredSize: const Size.fromHeight(1.0),

View File

@@ -11,8 +11,8 @@ class AttireInfoCard extends StatelessWidget {
return Container( return Container(
padding: const EdgeInsets.all(UiConstants.space4), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.primary.withOpacity(0.08), color: UiColors.primary.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(UiConstants.radiusBase), borderRadius: UiConstants.radiusLg,
), ),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -26,16 +26,12 @@ class AttireInfoCard extends StatelessWidget {
children: <Widget>[ children: <Widget>[
Text( Text(
t.staff_profile_attire.info_card.title, t.staff_profile_attire.info_card.title,
style: UiTypography.body2m.copyWith( style: UiTypography.body2m.textPrimary,
color: UiColors.textPrimary,
),
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
t.staff_profile_attire.info_card.description, t.staff_profile_attire.info_card.description,
style: UiTypography.body2r.copyWith( style: UiTypography.body2r.textSecondary,
color: UiColors.textSecondary,
),
), ),
], ],
), ),

View File

@@ -24,12 +24,12 @@ class EmergencyContactScreen extends StatelessWidget {
appBar: AppBar( appBar: AppBar(
elevation: 0, elevation: 0,
leading: IconButton( leading: IconButton(
icon: Icon(UiIcons.chevronLeft, color: UiColors.textSecondary), icon: const Icon(UiIcons.chevronLeft, color: UiColors.textSecondary),
onPressed: () => Modular.to.pop(), onPressed: () => Modular.to.pop(),
), ),
title: Text( title: Text(
'Emergency Contact', 'Emergency Contact',
style: UiTypography.title1m.copyWith(color: UiColors.textPrimary), style: UiTypography.title1m.textPrimary,
), ),
bottom: PreferredSize( bottom: PreferredSize(
preferredSize: const Size.fromHeight(1.0), preferredSize: const Size.fromHeight(1.0),
@@ -62,11 +62,11 @@ class EmergencyContactScreen extends StatelessWidget {
children: [ children: [
Expanded( Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
padding: EdgeInsets.all(UiConstants.space6), padding: const EdgeInsets.all(UiConstants.space6),
child: Column( child: Column(
children: [ children: [
const EmergencyContactInfoBanner(), const EmergencyContactInfoBanner(),
SizedBox(height: UiConstants.space6), const SizedBox(height: UiConstants.space6),
...state.contacts.asMap().entries.map( ...state.contacts.asMap().entries.map(
(entry) => EmergencyContactFormItem( (entry) => EmergencyContactFormItem(
index: entry.key, index: entry.key,
@@ -75,7 +75,7 @@ class EmergencyContactScreen extends StatelessWidget {
), ),
), ),
const EmergencyContactAddButton(), const EmergencyContactAddButton(),
SizedBox(height: UiConstants.space16), const SizedBox(height: UiConstants.space16),
], ],
), ),
), ),

View File

@@ -12,20 +12,20 @@ class EmergencyContactAddButton extends StatelessWidget {
child: TextButton.icon( child: TextButton.icon(
onPressed: () => onPressed: () =>
context.read<EmergencyContactBloc>().add(EmergencyContactAdded()), context.read<EmergencyContactBloc>().add(EmergencyContactAdded()),
icon: Icon(UiIcons.add, size: 20.0), icon: const Icon(UiIcons.add, size: 20.0),
label: Text( label: Text(
'Add Another Contact', 'Add Another Contact',
style: UiTypography.title2b, style: UiTypography.title2b,
), ),
style: TextButton.styleFrom( style: TextButton.styleFrom(
foregroundColor: UiColors.primary, foregroundColor: UiColors.primary,
padding: EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space6, horizontal: UiConstants.space6,
vertical: UiConstants.space3, vertical: UiConstants.space3,
), ),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: UiConstants.radiusFull, borderRadius: UiConstants.radiusFull,
side: BorderSide(color: UiColors.primary), side: const BorderSide(color: UiColors.primary),
), ),
), ),
), ),

View File

@@ -19,8 +19,8 @@ class EmergencyContactFormItem extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
margin: EdgeInsets.only(bottom: UiConstants.space4), margin: const EdgeInsets.only(bottom: UiConstants.space4),
padding: EdgeInsets.all(UiConstants.space4), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.bgPopup, color: UiColors.bgPopup,
borderRadius: UiConstants.radiusLg, borderRadius: UiConstants.radiusLg,
@@ -30,33 +30,27 @@ class EmergencyContactFormItem extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildHeader(context), _buildHeader(context),
SizedBox(height: UiConstants.space4), const SizedBox(height: UiConstants.space4),
_buildLabel('Full Name'), _buildLabel('Full Name'),
_buildTextField( _buildTextField(
initialValue: contact.name, initialValue: contact.name,
hint: 'Contact name', hint: 'Contact name',
icon: UiIcons.user, icon: UiIcons.user,
onChanged: (val) => context.read<EmergencyContactBloc>().add( onChanged: (val) => context.read<EmergencyContactBloc>().add(
EmergencyContactUpdated( EmergencyContactUpdated(index, contact.copyWith(name: val)),
index, ),
contact.copyWith(name: val),
),
),
), ),
SizedBox(height: UiConstants.space4), const SizedBox(height: UiConstants.space4),
_buildLabel('Phone Number'), _buildLabel('Phone Number'),
_buildTextField( _buildTextField(
initialValue: contact.phone, initialValue: contact.phone,
hint: '+1 (555) 000-0000', hint: '+1 (555) 000-0000',
icon: UiIcons.phone, icon: UiIcons.phone,
onChanged: (val) => context.read<EmergencyContactBloc>().add( onChanged: (val) => context.read<EmergencyContactBloc>().add(
EmergencyContactUpdated( EmergencyContactUpdated(index, contact.copyWith(phone: val)),
index, ),
contact.copyWith(phone: val),
),
),
), ),
SizedBox(height: UiConstants.space4), const SizedBox(height: UiConstants.space4),
_buildLabel('Relationship'), _buildLabel('Relationship'),
_buildDropdown( _buildDropdown(
context, context,
@@ -65,11 +59,11 @@ class EmergencyContactFormItem extends StatelessWidget {
onChanged: (val) { onChanged: (val) {
if (val != null) { if (val != null) {
context.read<EmergencyContactBloc>().add( context.read<EmergencyContactBloc>().add(
EmergencyContactUpdated( EmergencyContactUpdated(
index, index,
contact.copyWith(relationship: val), contact.copyWith(relationship: val),
), ),
); );
} }
}, },
), ),
@@ -85,7 +79,7 @@ class EmergencyContactFormItem extends StatelessWidget {
required ValueChanged<RelationshipType?> onChanged, required ValueChanged<RelationshipType?> onChanged,
}) { }) {
return Container( return Container(
padding: EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: UiConstants.space4, horizontal: UiConstants.space4,
vertical: UiConstants.space2, vertical: UiConstants.space2,
), ),
@@ -99,13 +93,13 @@ class EmergencyContactFormItem extends StatelessWidget {
value: value, value: value,
isExpanded: true, isExpanded: true,
dropdownColor: UiColors.bgPopup, dropdownColor: UiColors.bgPopup,
icon: Icon(UiIcons.chevronDown, color: UiColors.iconSecondary), icon: const Icon(UiIcons.chevronDown, color: UiColors.iconSecondary),
items: items.map((type) { items: items.map((type) {
return DropdownMenuItem<RelationshipType>( return DropdownMenuItem<RelationshipType>(
value: type, value: type,
child: Text( child: Text(
_formatRelationship(type), _formatRelationship(type),
style: UiTypography.body1r.copyWith(color: UiColors.textPrimary), style: UiTypography.body1r.textPrimary,
), ),
); );
}).toList(), }).toList(),
@@ -116,11 +110,15 @@ class EmergencyContactFormItem extends StatelessWidget {
} }
String _formatRelationship(RelationshipType type) { String _formatRelationship(RelationshipType type) {
switch(type) { switch (type) {
case RelationshipType.family: return 'Family'; case RelationshipType.family:
case RelationshipType.spouse: return 'Spouse'; return 'Family';
case RelationshipType.friend: return 'Friend'; case RelationshipType.spouse:
case RelationshipType.other: return 'Other'; return 'Spouse';
case RelationshipType.friend:
return 'Friend';
case RelationshipType.other:
return 'Other';
} }
} }
@@ -128,22 +126,17 @@ class EmergencyContactFormItem extends StatelessWidget {
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text('Contact ${index + 1}', style: UiTypography.title2m.textPrimary),
'Contact ${index + 1}',
style: UiTypography.title2m.copyWith(
color: UiColors.textPrimary,
),
),
if (totalContacts > 1) if (totalContacts > 1)
IconButton( IconButton(
icon: Icon( icon: const Icon(
UiIcons.delete, UiIcons.delete,
color: UiColors.textError, color: UiColors.textError,
size: 20.0, size: 20.0,
), ),
onPressed: () => context onPressed: () => context.read<EmergencyContactBloc>().add(
.read<EmergencyContactBloc>() EmergencyContactRemoved(index),
.add(EmergencyContactRemoved(index)), ),
), ),
], ],
); );
@@ -151,13 +144,8 @@ class EmergencyContactFormItem extends StatelessWidget {
Widget _buildLabel(String label) { Widget _buildLabel(String label) {
return Padding( return Padding(
padding: EdgeInsets.only(bottom: UiConstants.space2), padding: const EdgeInsets.only(bottom: UiConstants.space2),
child: Text( child: Text(label, style: UiTypography.body2m.textSecondary),
label,
style: UiTypography.body2m.copyWith(
color: UiColors.textSecondary,
),
),
); );
} }
@@ -169,16 +157,16 @@ class EmergencyContactFormItem extends StatelessWidget {
}) { }) {
return TextFormField( return TextFormField(
initialValue: initialValue, initialValue: initialValue,
style: UiTypography.body1r.copyWith( style: UiTypography.body1r.textPrimary,
color: UiColors.textPrimary,
),
decoration: InputDecoration( decoration: InputDecoration(
hintText: hint, hintText: hint,
hintStyle: TextStyle(color: UiColors.textPlaceholder), hintStyle: const TextStyle(color: UiColors.textPlaceholder),
prefixIcon: Icon(icon, color: UiColors.textSecondary, size: 20.0), prefixIcon: Icon(icon, color: UiColors.textSecondary, size: 20.0),
filled: true, filled: true,
fillColor: UiColors.bgPopup, fillColor: UiColors.bgPopup,
contentPadding: EdgeInsets.symmetric(vertical: UiConstants.space4), contentPadding: const EdgeInsets.symmetric(
vertical: UiConstants.space4,
),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: UiConstants.radiusLg, borderRadius: UiConstants.radiusLg,
borderSide: BorderSide(color: UiColors.border), borderSide: BorderSide(color: UiColors.border),
@@ -196,4 +184,3 @@ class EmergencyContactFormItem extends StatelessWidget {
); );
} }
} }

View File

@@ -7,14 +7,14 @@ class EmergencyContactInfoBanner extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
padding: EdgeInsets.all(UiConstants.space4), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: UiColors.accent.withOpacity(0.2), color: UiColors.accent.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(UiConstants.radiusBase), borderRadius: UiConstants.radiusLg,
), ),
child: Text( child: Text(
'Please provide at least one emergency contact. This information will only be used in case of an emergency during your shifts.', 'Please provide at least one emergency contact. This information will only be used in case of an emergency during your shifts.',
style: UiTypography.body2r.copyWith(color: UiColors.textPrimary), style: UiTypography.body2r.textPrimary,
), ),
); );
} }

View File

@@ -30,19 +30,18 @@ class EmergencyContactSaveButton extends StatelessWidget {
builder: (context, state) { builder: (context, state) {
final isLoading = state.status == EmergencyContactStatus.saving; final isLoading = state.status == EmergencyContactStatus.saving;
return Container( return Container(
padding: EdgeInsets.all(UiConstants.space4), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: const BoxDecoration(
color: UiColors.bgPopup, color: UiColors.bgPopup,
border: Border(top: BorderSide(color: UiColors.border)), border: Border(top: BorderSide(color: UiColors.border)),
), ),
child: SafeArea( child: SafeArea(
child: UiButton.primary( child: UiButton.primary(
fullWidth: true, fullWidth: true,
onPressed: state.isValid && !isLoading onPressed:
? () => _onSave(context) state.isValid && !isLoading ? () => _onSave(context) : null,
: null,
child: isLoading child: isLoading
? SizedBox( ? const SizedBox(
height: 20.0, height: 20.0,
width: 20.0, width: 20.0,
child: CircularProgressIndicator( child: CircularProgressIndicator(

View File

@@ -80,16 +80,16 @@ class ExperiencePage extends StatelessWidget {
children: [ children: [
Expanded( Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
padding: EdgeInsets.all(UiConstants.space5), padding: const EdgeInsets.all(UiConstants.space5),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ExperienceSectionTitle(title: i18n.industries_title), ExperienceSectionTitle(title: i18n.industries_title),
Text( Text(
i18n.industries_subtitle, i18n.industries_subtitle,
style: UiTypography.body2m.copyWith(color: UiColors.textSecondary), style: UiTypography.body2m.textSecondary,
), ),
SizedBox(height: UiConstants.space3), const SizedBox(height: UiConstants.space3),
Wrap( Wrap(
spacing: UiConstants.space2, spacing: UiConstants.space2,
runSpacing: UiConstants.space2, runSpacing: UiConstants.space2,
@@ -97,23 +97,26 @@ class ExperiencePage extends StatelessWidget {
.map( .map(
(i) => UiChip( (i) => UiChip(
label: _getIndustryLabel(i18n.industries, i), label: _getIndustryLabel(i18n.industries, i),
isSelected: state.selectedIndustries.contains(i), isSelected:
onTap: () => BlocProvider.of<ExperienceBloc>(context) state.selectedIndustries.contains(i),
.add(ExperienceIndustryToggled(i)), onTap: () =>
variant: state.selectedIndustries.contains(i) BlocProvider.of<ExperienceBloc>(context)
? UiChipVariant.primary .add(ExperienceIndustryToggled(i)),
: UiChipVariant.secondary, variant:
state.selectedIndustries.contains(i)
? UiChipVariant.primary
: UiChipVariant.secondary,
), ),
) )
.toList(), .toList(),
), ),
SizedBox(height: UiConstants.space6), const SizedBox(height: UiConstants.space6),
ExperienceSectionTitle(title: i18n.skills_title), ExperienceSectionTitle(title: i18n.skills_title),
Text( Text(
i18n.skills_subtitle, i18n.skills_subtitle,
style: UiTypography.body2m.copyWith(color: UiColors.textSecondary), style: UiTypography.body2m.textSecondary,
), ),
SizedBox(height: UiConstants.space3), const SizedBox(height: UiConstants.space3),
Wrap( Wrap(
spacing: UiConstants.space2, spacing: UiConstants.space2,
runSpacing: UiConstants.space2, runSpacing: UiConstants.space2,
@@ -121,19 +124,22 @@ class ExperiencePage extends StatelessWidget {
.map( .map(
(s) => UiChip( (s) => UiChip(
label: _getSkillLabel(i18n.skills, s), label: _getSkillLabel(i18n.skills, s),
isSelected: state.selectedSkills.contains(s.value), isSelected:
onTap: () => BlocProvider.of<ExperienceBloc>(context) state.selectedSkills.contains(s.value),
.add(ExperienceSkillToggled(s.value)), onTap: () =>
variant: state.selectedSkills.contains(s.value) BlocProvider.of<ExperienceBloc>(context)
? UiChipVariant.primary .add(ExperienceSkillToggled(s.value)),
: UiChipVariant.secondary, variant:
state.selectedSkills.contains(s.value)
? UiChipVariant.primary
: UiChipVariant.secondary,
), ),
) )
.toList(), .toList(),
), ),
], ],
), ),
), ),
), ),
_buildSaveButton(context, state, i18n), _buildSaveButton(context, state, i18n),
], ],
@@ -155,9 +161,9 @@ class ExperiencePage extends StatelessWidget {
children: [ children: [
Text( Text(
i18n.custom_skills_title, i18n.custom_skills_title,
style: UiTypography.body2m.copyWith(color: UiColors.textSecondary), style: UiTypography.body2m.textSecondary,
), ),
SizedBox(height: UiConstants.space2), const SizedBox(height: UiConstants.space2),
Wrap( Wrap(
spacing: UiConstants.space2, spacing: UiConstants.space2,
runSpacing: UiConstants.space2, runSpacing: UiConstants.space2,
@@ -172,10 +178,14 @@ class ExperiencePage extends StatelessWidget {
); );
} }
Widget _buildSaveButton(BuildContext context, ExperienceState state, dynamic i18n) { Widget _buildSaveButton(
BuildContext context,
ExperienceState state,
dynamic i18n,
) {
return Container( return Container(
padding: EdgeInsets.all(UiConstants.space4), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: const BoxDecoration(
color: UiColors.bgPopup, color: UiColors.bgPopup,
border: Border(top: BorderSide(color: UiColors.border)), border: Border(top: BorderSide(color: UiColors.border)),
), ),
@@ -183,16 +193,21 @@ class ExperiencePage extends StatelessWidget {
child: UiButton.primary( child: UiButton.primary(
onPressed: state.status == ExperienceStatus.loading onPressed: state.status == ExperienceStatus.loading
? null ? null
: () => BlocProvider.of<ExperienceBloc>(context).add(ExperienceSubmitted()), : () => BlocProvider.of<ExperienceBloc>(context)
.add(ExperienceSubmitted()),
fullWidth: true, fullWidth: true,
text: state.status == ExperienceStatus.loading ? null : i18n.save_button, text: state.status == ExperienceStatus.loading
? null
: i18n.save_button,
child: state.status == ExperienceStatus.loading child: state.status == ExperienceStatus.loading
? SizedBox( ? const SizedBox(
height: 20.0, height: 20.0,
width: 20.0, width: 20.0,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 2, strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(UiColors.white), // UiColors.primaryForeground is white mostly valueColor: AlwaysStoppedAnimation<Color>(
UiColors.white,
), // UiColors.primaryForeground is white mostly
), ),
) )
: null, : null,

View File

@@ -1,4 +1,3 @@
import 'package:core_localization/core_localization.dart'; import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart'; import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -56,13 +55,16 @@ class PersonalInfoPage extends StatelessWidget {
backgroundColor: UiColors.bgPopup, backgroundColor: UiColors.bgPopup,
elevation: 0, elevation: 0,
leading: IconButton( leading: IconButton(
icon: const Icon(UiIcons.chevronLeft, color: UiColors.textSecondary), icon: const Icon(
UiIcons.chevronLeft,
color: UiColors.textSecondary,
),
onPressed: () => Modular.to.pop(), onPressed: () => Modular.to.pop(),
tooltip: MaterialLocalizations.of(context).backButtonTooltip, tooltip: MaterialLocalizations.of(context).backButtonTooltip,
), ),
title: Text( title: Text(
i18n.title, i18n.title,
style: UiTypography.title1m.copyWith(color: UiColors.textPrimary), style: UiTypography.title1m.textPrimary,
), ),
bottom: PreferredSize( bottom: PreferredSize(
preferredSize: const Size.fromHeight(1.0), preferredSize: const Size.fromHeight(1.0),
@@ -86,9 +88,7 @@ class PersonalInfoPage extends StatelessWidget {
return Center( return Center(
child: Text( child: Text(
'Failed to load personal information', 'Failed to load personal information',
style: UiTypography.body1r.copyWith( style: UiTypography.body1r.textSecondary,
color: UiColors.textSecondary,
),
), ),
); );
} }

View File

@@ -93,7 +93,7 @@ class _FieldLabel extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Text( return Text(
text, text,
style: UiTypography.body2m.copyWith(color: UiColors.textPrimary), style: UiTypography.body2m.textPrimary,
); );
} }
} }
@@ -101,7 +101,6 @@ class _FieldLabel extends StatelessWidget {
/// A read-only field widget for displaying non-editable information. /// A read-only field widget for displaying non-editable information.
/// A read-only field widget for displaying non-editable information. /// A read-only field widget for displaying non-editable information.
class _ReadOnlyField extends StatelessWidget { class _ReadOnlyField extends StatelessWidget {
const _ReadOnlyField({required this.value}); const _ReadOnlyField({required this.value});
final String value; final String value;
@@ -120,7 +119,7 @@ class _ReadOnlyField extends StatelessWidget {
), ),
child: Text( child: Text(
value, value,
style: UiTypography.body2r.copyWith(color: UiColors.textPrimary), style: UiTypography.body2r.textPrimary,
), ),
); );
} }
@@ -129,7 +128,6 @@ class _ReadOnlyField extends StatelessWidget {
/// An editable text field widget. /// An editable text field widget.
/// An editable text field widget. /// An editable text field widget.
class _EditableField extends StatelessWidget { class _EditableField extends StatelessWidget {
const _EditableField({ const _EditableField({
required this.controller, required this.controller,
required this.hint, required this.hint,
@@ -150,10 +148,10 @@ class _EditableField extends StatelessWidget {
enabled: enabled, enabled: enabled,
keyboardType: keyboardType, keyboardType: keyboardType,
autofillHints: autofillHints, autofillHints: autofillHints,
style: UiTypography.body2r.copyWith(color: UiColors.textPrimary), style: UiTypography.body2r.textPrimary,
decoration: InputDecoration( decoration: InputDecoration(
hintText: hint, hintText: hint,
hintStyle: UiTypography.body2r.copyWith(color: UiColors.textSecondary), hintStyle: UiTypography.body2r.textSecondary,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: UiConstants.space3, horizontal: UiConstants.space3,
vertical: UiConstants.space3, vertical: UiConstants.space3,

View File

@@ -28,7 +28,8 @@ class ProfilePhotoWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final TranslationsStaffOnboardingPersonalInfoEn i18n = t.staff.onboarding.personal_info; final TranslationsStaffOnboardingPersonalInfoEn i18n =
t.staff.onboarding.personal_info;
return Column( return Column(
children: <Widget>[ children: <Widget>[
@@ -41,7 +42,7 @@ class ProfilePhotoWidget extends StatelessWidget {
height: 96, height: 96,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: UiColors.primary.withOpacity(0.1), color: UiColors.primary.withValues(alpha: 0.1),
), ),
child: photoUrl != null child: photoUrl != null
? ClipOval( ? ClipOval(
@@ -53,9 +54,7 @@ class ProfilePhotoWidget extends StatelessWidget {
: Center( : Center(
child: Text( child: Text(
fullName.isNotEmpty ? fullName[0].toUpperCase() : '?', fullName.isNotEmpty ? fullName[0].toUpperCase() : '?',
style: UiTypography.displayL.copyWith( style: UiTypography.displayL.primary,
color: UiColors.primary,
),
), ),
), ),
), ),
@@ -71,7 +70,7 @@ class ProfilePhotoWidget extends StatelessWidget {
border: Border.all(color: UiColors.border), border: Border.all(color: UiColors.border),
boxShadow: <BoxShadow>[ boxShadow: <BoxShadow>[
BoxShadow( BoxShadow(
color: UiColors.textPrimary.withOpacity(0.1), color: UiColors.textPrimary.withValues(alpha: 0.1),
blurRadius: UiConstants.space1, blurRadius: UiConstants.space1,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -92,7 +91,7 @@ class ProfilePhotoWidget extends StatelessWidget {
const SizedBox(height: UiConstants.space3), const SizedBox(height: UiConstants.space3),
Text( Text(
i18n.change_photo_hint, i18n.change_photo_hint,
style: UiTypography.body2r.copyWith(color: UiColors.textSecondary), style: UiTypography.body2r.textSecondary,
), ),
], ],
); );

View File

@@ -176,7 +176,7 @@ class ShiftsRepositoryImpl
final DateTime? endDt = _toDateTime(app.shiftRole.endTime); final DateTime? endDt = _toDateTime(app.shiftRole.endTime);
final DateTime? createdDt = _toDateTime(app.createdAt); final DateTime? createdDt = _toDateTime(app.createdAt);
// Override status to reflect the application state (e.g., CHECKED_OUT, ACCEPTED) // Override status to reflect the application state (e.g., CHECKED_OUT, CONFIRMED)
final bool hasCheckIn = app.checkInTime != null; final bool hasCheckIn = app.checkInTime != null;
final bool hasCheckOut = app.checkOutTime != null; final bool hasCheckOut = app.checkOutTime != null;
dc.ApplicationStatus? appStatus; dc.ApplicationStatus? appStatus;
@@ -187,7 +187,7 @@ class ShiftsRepositoryImpl
? 'completed' ? 'completed'
: hasCheckIn : hasCheckIn
? 'checked_in' ? 'checked_in'
: _mapStatus(appStatus ?? dc.ApplicationStatus.ACCEPTED); : _mapStatus(appStatus ?? dc.ApplicationStatus.CONFIRMED);
shifts.add( shifts.add(
Shift( Shift(
id: app.shift.id, id: app.shift.id,
@@ -223,7 +223,6 @@ class ShiftsRepositoryImpl
String _mapStatus(dc.ApplicationStatus status) { String _mapStatus(dc.ApplicationStatus status) {
switch (status) { switch (status) {
case dc.ApplicationStatus.ACCEPTED:
case dc.ApplicationStatus.CONFIRMED: case dc.ApplicationStatus.CONFIRMED:
return 'confirmed'; return 'confirmed';
case dc.ApplicationStatus.PENDING: case dc.ApplicationStatus.PENDING:
@@ -477,7 +476,7 @@ class ShiftsRepositoryImpl
shiftId: shiftId, shiftId: shiftId,
staffId: staffId, staffId: staffId,
roleId: targetRoleId, roleId: targetRoleId,
status: dc.ApplicationStatus.ACCEPTED, status: dc.ApplicationStatus.CONFIRMED,
origin: dc.ApplicationOrigin.STAFF, origin: dc.ApplicationOrigin.STAFF,
) )
// TODO: this should be PENDING so a vendor can accept it. // TODO: this should be PENDING so a vendor can accept it.
@@ -518,7 +517,7 @@ class ShiftsRepositoryImpl
@override @override
Future<void> acceptShift(String shiftId) async { Future<void> acceptShift(String shiftId) async {
await _updateApplicationStatus(shiftId, dc.ApplicationStatus.ACCEPTED); await _updateApplicationStatus(shiftId, dc.ApplicationStatus.CONFIRMED);
} }
@override @override

View File

@@ -281,15 +281,20 @@ class ShiftsBloc extends Bloc<ShiftsEvent, ShiftsState>
List<Shift> _filterPastShifts(List<Shift> shifts) { List<Shift> _filterPastShifts(List<Shift> shifts) {
final now = DateTime.now(); final now = DateTime.now();
final today = DateTime(now.year, now.month, now.day);
return shifts.where((shift) { return shifts.where((shift) {
if (shift.date.isEmpty) return false; if (shift.date.isEmpty) return false;
try { try {
final shiftDate = DateTime.parse(shift.date); final shiftDate = DateTime.parse(shift.date);
return shiftDate.isAfter(now); final dateOnly = DateTime(
shiftDate.year,
shiftDate.month,
shiftDate.day,
);
return !dateOnly.isBefore(today);
} catch (_) { } catch (_) {
return false; return false;
} }
}).toList(); }).toList();
} }
} }

View File

@@ -64,10 +64,10 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
Widget _buildStatCard(IconData icon, String value, String label) { Widget _buildStatCard(IconData icon, String value, String label) {
return Container( return Container(
padding: const EdgeInsets.symmetric(vertical: 12), padding: const EdgeInsets.symmetric(vertical: UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFF8FAFC), color: UiColors.background,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
border: Border.all(color: UiColors.border), border: Border.all(color: UiColors.border),
), ),
child: Column( child: Column(
@@ -76,21 +76,19 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
width: 40, width: 40,
height: 40, height: 40,
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: Colors.white, color: UiColors.white,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: Icon(icon, size: 20, color: UiColors.iconSecondary), child: Icon(icon, size: 20, color: UiColors.iconSecondary),
), ),
const SizedBox(height: 8), const SizedBox(height: UiConstants.space2),
Text( Text(
value, value,
style: UiTypography.title1m.copyWith(color: UiColors.textPrimary), style: UiTypography.title1m.textPrimary,
), ),
Text( Text(
label, label,
style: UiTypography.footnote2r.copyWith( style: UiTypography.footnote2r.textSecondary,
color: UiColors.textSecondary,
),
), ),
], ],
), ),
@@ -99,29 +97,21 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
Widget _buildTimeBox(String label, String time) { Widget _buildTimeBox(String label, String time) {
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFF8FAFC), color: UiColors.background,
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
), ),
child: Column( child: Column(
children: [ children: [
Text( Text(
label, label,
style: const TextStyle( style: UiTypography.titleUppercase4b.textSecondary,
fontSize: 10,
fontWeight: FontWeight.bold,
color: UiColors.textSecondary,
letterSpacing: 0.5,
),
), ),
const SizedBox(height: 4), const SizedBox(height: UiConstants.space1),
Text( Text(
_formatTime(time), _formatTime(time),
style: UiTypography.display2m.copyWith( style: UiTypography.headline2m.textPrimary,
fontSize: 20,
color: UiColors.textPrimary,
),
), ),
], ],
), ),
@@ -149,7 +139,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(state.message), content: Text(state.message),
backgroundColor: UiColors.tagSuccess, backgroundColor: UiColors.success,
), ),
); );
Modular.to.toShifts(selectedDate: state.shiftDate); Modular.to.toShifts(selectedDate: state.shiftDate);
@@ -158,7 +148,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(state.message), content: Text(state.message),
backgroundColor: const Color(0xFFEF4444), backgroundColor: UiColors.destructive,
), ),
); );
} }
@@ -203,7 +193,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
children: [ children: [
Expanded( Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
padding: const EdgeInsets.all(20.0), padding: const EdgeInsets.all(UiConstants.space5),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -211,16 +201,11 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
"VENDOR", "VENDOR",
style: TextStyle( style: UiTypography.titleUppercase4b.textSecondary,
fontSize: 10,
fontWeight: FontWeight.bold,
color: UiColors.textSecondary,
letterSpacing: 0.5,
),
), ),
const SizedBox(height: 8), const SizedBox(height: UiConstants.space2),
Row( Row(
children: [ children: [
SizedBox( SizedBox(
@@ -229,7 +214,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
child: displayShift.logoUrl != null child: displayShift.logoUrl != null
? ClipRRect( ? ClipRRect(
borderRadius: BorderRadius.circular( borderRadius: BorderRadius.circular(
6, UiConstants.radiusMdValue,
), ),
child: Image.network( child: Image.network(
displayShift.logoUrl!, displayShift.logoUrl!,
@@ -244,33 +229,26 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
), ),
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: UiConstants.space2),
Text( Text(
displayShift.clientName, displayShift.clientName,
style: UiTypography.headline5m.copyWith( style: UiTypography.headline5m.textPrimary,
color: UiColors.textPrimary,
),
), ),
], ],
), ),
], ],
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
// Date Section // Date Section
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
"SHIFT DATE", "SHIFT DATE",
style: TextStyle( style: UiTypography.titleUppercase4b.textSecondary,
fontSize: 10,
fontWeight: FontWeight.bold,
color: UiColors.textSecondary,
letterSpacing: 0.5,
),
), ),
const SizedBox(height: 8), const SizedBox(height: UiConstants.space2),
Row( Row(
children: [ children: [
const Icon( const Icon(
@@ -278,104 +256,44 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
size: 20, size: 20,
color: UiColors.primary, color: UiColors.primary,
), ),
const SizedBox(width: 8), const SizedBox(width: UiConstants.space2),
Text( Text(
_formatDate(displayShift.date), _formatDate(displayShift.date),
style: UiTypography.headline5m.copyWith( style: UiTypography.headline5m.textPrimary,
color: UiColors.textPrimary,
),
), ),
], ],
), ),
], ],
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
// Worker Capacity / Open Slots // Worker Capacity / Open Slots
if ((displayShift.requiredSlots ?? 0) > 0) if ((displayShift.requiredSlots ?? 0) > 0)
Container( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(UiConstants.space4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFF0FDF4), // green-50 color: UiColors.success.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(UiConstants.radiusBase),
border: Border.all(
color: const Color(0xFFBBF7D0),
), // green-200
), ),
child: Row( child: Row(
children: [ children: [
const Icon( const Icon(
Icons.people_alt_outlined, UiIcons.users,
size: 20, size: 16,
color: Color(0xFF15803D), color: UiColors.success,
), // green-700, using Material Icon as generic fallback
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
"$openSlots spots remaining",
style: UiTypography.body2b.copyWith(
color: const Color(0xFF15803D),
),
),
Text(
"${displayShift.filledSlots ?? 0} filled out of ${displayShift.requiredSlots}",
style: UiTypography.body3r.copyWith(
color: const Color(0xFF166534),
),
),
],
),
), ),
SizedBox( const SizedBox(width: UiConstants.space2),
width: 60, Text(
child: LinearProgressIndicator( "$openSlots slots remaining",
value: (displayShift.requiredSlots! > 0) style: UiTypography.footnote1m.textSuccess,
? (displayShift.filledSlots ?? 0) /
displayShift.requiredSlots!
: 0,
backgroundColor: Colors.white,
color: const Color(0xFF15803D),
minHeight: 6,
borderRadius: BorderRadius.circular(3),
),
), ),
], ],
), ),
), ),
const SizedBox(height: 24),
// Stats Grid const SizedBox(height: UiConstants.space6),
GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 3,
crossAxisSpacing: 12,
childAspectRatio: 0.72,
children: [
_buildStatCard(
UiIcons.dollar,
"\$${estimatedTotal.toStringAsFixed(0)}",
"Total Pay",
),
_buildStatCard(
UiIcons.dollar,
"\$${displayShift.hourlyRate.toInt()}",
"Per Hour",
),
_buildStatCard(
UiIcons.clock,
"${duration.toInt()}h",
"Duration",
),
],
),
const SizedBox(height: 24),
// Shift Timing // Time Section
Row( Row(
children: [ children: [
Expanded( Expanded(
@@ -384,7 +302,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
displayShift.startTime, displayShift.startTime,
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: UiConstants.space4),
Expanded( Expanded(
child: _buildTimeBox( child: _buildTimeBox(
"END TIME", "END TIME",
@@ -393,129 +311,142 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
), ),
], ],
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space6),
// Location // Quick Info Grid
Column( Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Expanded(
"LOCATION", child: _buildStatCard(
style: TextStyle( UiIcons.dollar,
fontSize: 10, "\$${displayShift.hourlyRate.toStringAsFixed(0)}/hr",
fontWeight: FontWeight.bold, "Base Rate",
color: UiColors.textSecondary,
letterSpacing: 0.5,
), ),
), ),
const SizedBox(height: 8), const SizedBox(width: UiConstants.space4),
Column( Expanded(
crossAxisAlignment: CrossAxisAlignment.start, child: _buildStatCard(
children: [ UiIcons.clock,
Text( "${duration.toInt()} hours",
displayShift.location.isEmpty "Duration",
? "TBD" ),
: displayShift.location, ),
style: UiTypography.title1m.copyWith( const SizedBox(width: UiConstants.space4),
color: UiColors.textPrimary, Expanded(
), child: _buildStatCard(
), UiIcons.wallet,
Text( "\$${estimatedTotal.toStringAsFixed(0)}",
displayShift.location.isEmpty "Est. Total",
? "TBD" ),
: displayShift.locationAddress,
style: UiTypography.title1m.copyWith(
color: UiColors.textPrimary,
),
),
],
), ),
], ],
), ),
const SizedBox(height: 24), const SizedBox(height: UiConstants.space8),
// Additional Info // Location Section
if (displayShift.description != null) ...[ Column(
SizedBox( crossAxisAlignment: CrossAxisAlignment.start,
width: double.infinity, children: [
child: Column( Text(
crossAxisAlignment: CrossAxisAlignment.start, "LOCATION",
children: [ style: UiTypography.titleUppercase4b.textSecondary,
const Text(
"ADDITIONAL INFO",
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: UiColors.textSecondary,
letterSpacing: 0.5,
),
),
const SizedBox(height: 8),
Text(
displayShift.description!,
style: UiTypography.body2m.copyWith(
color: UiColors.textPrimary,
),
),
],
), ),
), const SizedBox(height: UiConstants.space3),
], Container(
const SizedBox(height: 20), padding: const EdgeInsets.all(UiConstants.space4),
if (displayShift.status != 'confirmed' && decoration: BoxDecoration(
displayShift.hasApplied != true && color: UiColors.white,
(displayShift.requiredSlots == null || borderRadius: BorderRadius.circular(UiConstants.radiusBase),
displayShift.filledSlots == null || border: Border.all(color: UiColors.border),
displayShift.filledSlots! <
displayShift.requiredSlots!))
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () => _declineShift(
context,
displayShift!.id,
),
style: OutlinedButton.styleFrom(
foregroundColor: const Color(0xFFEF4444),
side: const BorderSide(
color: Color(0xFFEF4444),
),
padding: const EdgeInsets.symmetric(
vertical: 16,
),
),
child: const Text("Decline"),
),
), ),
const SizedBox(width: 16), child: Column(
Expanded( children: [
child: ElevatedButton( Row(
onPressed: () => _bookShift( children: [
context, const Icon(
displayShift!, UiIcons.mapPin,
color: UiColors.primary,
size: 20,
),
const SizedBox(width: UiConstants.space3),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
displayShift.location,
style: UiTypography.body2b.textPrimary,
),
Text(
displayShift.locationAddress,
style: UiTypography.body3r.textSecondary,
),
],
),
),
],
), ),
style: ElevatedButton.styleFrom( const SizedBox(height: UiConstants.space4),
backgroundColor: const Color( const Divider(),
0xFF10B981, const SizedBox(height: UiConstants.space2),
TextButton.icon(
onPressed: () {},
icon: const Icon(
UiIcons.arrowRight,
size: 16,
), ),
foregroundColor: Colors.white, label: const Text("Open in Maps"),
padding: const EdgeInsets.symmetric( style: TextButton.styleFrom(
vertical: 16, foregroundColor: UiColors.primary,
padding: EdgeInsets.zero,
), ),
), ),
child: const Text("Book Shift"), ],
),
), ),
], ),
), ],
SizedBox(
height: MediaQuery.of(context).padding.bottom + 10,
), ),
const SizedBox(height: UiConstants.space8),
// Description / Instructions
if ((displayShift.description ?? '').isNotEmpty) ...[
Text(
"JOB DESCRIPTION",
style: UiTypography.titleUppercase4b.textSecondary,
),
const SizedBox(height: UiConstants.space2),
Text(
displayShift.description!,
style: UiTypography.body2r.textSecondary,
),
const SizedBox(height: UiConstants.space8),
],
], ],
), ),
), ),
), ),
// Bottom Action Bar
Container(
padding: EdgeInsets.fromLTRB(
UiConstants.space5,
UiConstants.space4,
UiConstants.space5,
MediaQuery.of(context).padding.bottom + UiConstants.space4,
),
decoration: BoxDecoration(
color: UiColors.white,
border: Border(top: BorderSide(color: UiColors.border)),
boxShadow: [
BoxShadow(
color: UiColors.black.withValues(alpha: 0.05),
blurRadius: 10,
offset: const Offset(0, -4),
),
],
),
child: _buildBottomButton(displayShift, context),
),
], ],
), ),
); );
@@ -552,7 +483,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
); );
}, },
style: TextButton.styleFrom( style: TextButton.styleFrom(
foregroundColor: const Color(0xFF10B981), foregroundColor: UiColors.success,
), ),
child: const Text('Book'), child: const Text('Book'),
), ),
@@ -581,7 +512,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
).add(DeclineShiftDetailsEvent(id)); ).add(DeclineShiftDetailsEvent(id));
}, },
style: TextButton.styleFrom( style: TextButton.styleFrom(
foregroundColor: const Color(0xFFEF4444), foregroundColor: UiColors.destructive,
), ),
child: const Text('Decline'), child: const Text('Decline'),
), ),
@@ -608,29 +539,23 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
width: 36, width: 36,
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
), ),
const SizedBox(height: 16), const SizedBox(height: UiConstants.space4),
Text( Text(
shift.title, shift.title,
style: UiTypography.body2b.copyWith( style: UiTypography.body2b.textPrimary,
color: UiColors.textPrimary,
),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
Text( Text(
'${_formatDate(shift.date)}${_formatTime(shift.startTime)} - ${_formatTime(shift.endTime)}', '${_formatDate(shift.date)}${_formatTime(shift.startTime)} - ${_formatTime(shift.endTime)}',
style: UiTypography.body3r.copyWith( style: UiTypography.body3r.textSecondary,
color: UiColors.textSecondary,
),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
if (shift.clientName.isNotEmpty) ...[ if (shift.clientName.isNotEmpty) ...[
const SizedBox(height: 6), const SizedBox(height: 6),
Text( Text(
shift.clientName, shift.clientName,
style: UiTypography.body3r.copyWith( style: UiTypography.body3r.textSecondary,
color: UiColors.textSecondary,
),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
], ],
@@ -647,4 +572,150 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
Navigator.of(context, rootNavigator: true).pop(); Navigator.of(context, rootNavigator: true).pop();
_actionDialogOpen = false; _actionDialogOpen = false;
} }
Widget _buildBottomButton(Shift shift, BuildContext context) {
final String status = shift.status ?? 'open';
if (status == 'confirmed') {
return Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () => _openCancelDialog(context),
style: ElevatedButton.styleFrom(
backgroundColor: UiColors.destructive,
foregroundColor: UiColors.white,
padding: const EdgeInsets.symmetric(vertical: UiConstants.space4),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
),
elevation: 0,
),
child: Text("CANCEL SHIFT", style: UiTypography.body2b.white),
),
),
const SizedBox(width: UiConstants.space4),
Expanded(
child: ElevatedButton(
onPressed: () => Modular.to.toClockIn(),
style: ElevatedButton.styleFrom(
backgroundColor: UiColors.success,
foregroundColor: UiColors.white,
padding: const EdgeInsets.symmetric(vertical: UiConstants.space4),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
),
elevation: 0,
),
child: Text("CLOCK IN", style: UiTypography.body2b.white),
),
),
],
);
}
if (status == 'pending') {
return Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () => BlocProvider.of<ShiftDetailsBloc>(context).add(DeclineShiftDetailsEvent(shift.id)),
style: OutlinedButton.styleFrom(
foregroundColor: UiColors.destructive,
padding: const EdgeInsets.symmetric(vertical: UiConstants.space4),
side: const BorderSide(color: UiColors.destructive),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
),
),
child: Text("DECLINE", style: UiTypography.body2b.textError),
),
),
const SizedBox(width: UiConstants.space4),
Expanded(
child: ElevatedButton(
onPressed: () => BlocProvider.of<ShiftDetailsBloc>(context).add(BookShiftDetailsEvent(shift.id, roleId: shift.roleId)),
style: ElevatedButton.styleFrom(
backgroundColor: UiColors.primary,
foregroundColor: UiColors.white,
padding: const EdgeInsets.symmetric(vertical: UiConstants.space4),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
),
elevation: 0,
),
child: Text("ACCEPT SHIFT", style: UiTypography.body2b.white),
),
),
],
);
}
if (status == 'open' || status == 'available') {
return Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () => _declineShift(context, shift.id),
style: OutlinedButton.styleFrom(
foregroundColor: UiColors.textSecondary,
padding: const EdgeInsets.symmetric(vertical: UiConstants.space4),
side: const BorderSide(color: UiColors.border),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
),
),
child: Text("DECLINE", style: UiTypography.body2b.textSecondary),
),
),
const SizedBox(width: UiConstants.space4),
Expanded(
child: ElevatedButton(
onPressed: () => _bookShift(context, shift),
style: ElevatedButton.styleFrom(
backgroundColor: UiColors.primary,
foregroundColor: UiColors.white,
padding: const EdgeInsets.symmetric(vertical: UiConstants.space4),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
),
elevation: 0,
),
child: Text("APPLY NOW", style: UiTypography.body2b.white),
),
),
],
);
}
return const SizedBox();
}
void _openCancelDialog(BuildContext context) {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('Cancel Shift'),
content: const Text('Are you sure you want to cancel this shift?'),
actions: [
TextButton(
onPressed: () => Modular.to.pop(),
child: const Text('No'),
),
TextButton(
onPressed: () {
Modular.to.pop();
BlocProvider.of<ShiftDetailsBloc>(context).add(
DeclineShiftDetailsEvent(widget.shiftId),
);
},
style: TextButton.styleFrom(
foregroundColor: UiColors.destructive,
),
child: const Text('Yes, cancel it'),
),
],
),
);
}
} }

Some files were not shown because too many files have changed in this diff Show More