From 5c15db1695d2e8e80188c01a0ccae33092681c68 Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Sun, 1 Feb 2026 20:22:14 -0500 Subject: [PATCH] Integrate Google Maps Places Autocomplete for hub address validation and enhance UI button styles --- .../lib/src/widgets/ui_button.dart | 68 +++++++++++++- .../presentation/widgets/reorder_widget.dart | 76 ++++++--------- docs/QA_TESTING_CHECKLIST.md | 92 ++++--------------- 3 files changed, 111 insertions(+), 125 deletions(-) diff --git a/apps/mobile/packages/design_system/lib/src/widgets/ui_button.dart b/apps/mobile/packages/design_system/lib/src/widgets/ui_button.dart index 7867798c..d2dc3abb 100644 --- a/apps/mobile/packages/design_system/lib/src/widgets/ui_button.dart +++ b/apps/mobile/packages/design_system/lib/src/widgets/ui_button.dart @@ -132,10 +132,14 @@ class UiButton extends StatelessWidget { @override /// Builds the button UI. Widget build(BuildContext context) { + final ButtonStyle? mergedStyle = style != null + ? _getSizeStyle().merge(style) + : _getSizeStyle(); + final Widget button = buttonBuilder( context, onPressed, - style, + mergedStyle, _buildButtonContent(), ); @@ -146,6 +150,65 @@ class UiButton extends StatelessWidget { return button; } + /// Gets the style based on the button size. + ButtonStyle _getSizeStyle() { + switch (size) { + case UiButtonSize.extraSmall: + return ButtonStyle( + padding: WidgetStateProperty.all( + const EdgeInsets.symmetric( + horizontal: UiConstants.space2, + vertical: UiConstants.space1, + ), + ), + minimumSize: WidgetStateProperty.all(const Size(0, 28)), + maximumSize: WidgetStateProperty.all(const Size(double.infinity, 28)), + textStyle: WidgetStateProperty.all( + const TextStyle(fontSize: 12, fontWeight: FontWeight.w500), + ), + ); + case UiButtonSize.small: + return ButtonStyle( + padding: WidgetStateProperty.all( + const EdgeInsets.symmetric( + horizontal: UiConstants.space3, + vertical: UiConstants.space2, + ), + ), + minimumSize: WidgetStateProperty.all(const Size(0, 36)), + maximumSize: WidgetStateProperty.all(const Size(double.infinity, 36)), + textStyle: WidgetStateProperty.all( + const TextStyle(fontSize: 13, fontWeight: FontWeight.w500), + ), + ); + case UiButtonSize.medium: + return ButtonStyle( + padding: WidgetStateProperty.all( + const EdgeInsets.symmetric( + horizontal: UiConstants.space4, + vertical: UiConstants.space3, + ), + ), + minimumSize: WidgetStateProperty.all(const Size(0, 44)), + maximumSize: WidgetStateProperty.all(const Size(double.infinity, 44)), + ); + case UiButtonSize.large: + return ButtonStyle( + padding: WidgetStateProperty.all( + const EdgeInsets.symmetric( + horizontal: UiConstants.space6, + vertical: UiConstants.space4, + ), + ), + minimumSize: WidgetStateProperty.all(const Size(0, 52)), + maximumSize: WidgetStateProperty.all(const Size(double.infinity, 52)), + textStyle: WidgetStateProperty.all( + const TextStyle(fontSize: 16, fontWeight: FontWeight.w600), + ), + ); + } + } + /// Builds the button content with optional leading and trailing icons. Widget _buildButtonContent() { if (child != null) { @@ -229,6 +292,9 @@ class UiButton extends StatelessWidget { /// Defines the size of a [UiButton]. enum UiButtonSize { + /// Extra small button (very compact) + extraSmall, + /// Small button (compact) small, diff --git a/apps/mobile/packages/features/client/home/lib/src/presentation/widgets/reorder_widget.dart b/apps/mobile/packages/features/client/home/lib/src/presentation/widgets/reorder_widget.dart index fe9274b8..1dfa8353 100644 --- a/apps/mobile/packages/features/client/home/lib/src/presentation/widgets/reorder_widget.dart +++ b/apps/mobile/packages/features/client/home/lib/src/presentation/widgets/reorder_widget.dart @@ -43,14 +43,11 @@ class ReorderWidget extends StatelessWidget { ), if (subtitle != null) ...[ const SizedBox(height: UiConstants.space1), - Text( - subtitle!, - style: UiTypography.body2r.textSecondary, - ), + Text(subtitle!, style: UiTypography.body2r.textSecondary), ], const SizedBox(height: UiConstants.space2), SizedBox( - height: 140, + height: 164, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: recentOrders.length, @@ -67,13 +64,7 @@ class ReorderWidget extends StatelessWidget { decoration: BoxDecoration( color: UiColors.white, borderRadius: UiConstants.radiusLg, - border: Border.all(color: UiColors.border), - boxShadow: [ - BoxShadow( - color: UiColors.black.withValues(alpha: 0.02), - blurRadius: 4, - ), - ], + border: Border.all(color: UiColors.border, width: 0.6), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -129,10 +120,7 @@ class ReorderWidget extends StatelessWidget { style: UiTypography.body1b, ), Text( - i18n.per_hr( - amount: order.hourlyRate.toString(), - ) + - ' ยท ${order.hours}h', + '${i18n.per_hr(amount: order.hourlyRate.toString())} ยท ${order.hours}h', style: UiTypography.footnote2r.textSecondary, ), ], @@ -145,49 +133,37 @@ class ReorderWidget extends StatelessWidget { _Badge( icon: UiIcons.success, text: order.type, - color: const Color(0xFF2563EB), - bg: const Color(0xFF2563EB), - textColor: UiColors.white, + color: UiColors.primary, + bg: UiColors.buttonSecondaryStill, + textColor: UiColors.primary, ), const SizedBox(width: UiConstants.space2), _Badge( icon: UiIcons.building, text: '${order.workers}', - color: const Color(0xFF334155), - bg: const Color(0xFFF1F5F9), - textColor: const Color(0xFF334155), + color: UiColors.textSecondary, + bg: UiColors.buttonSecondaryStill, + textColor: UiColors.textSecondary, ), ], ), const Spacer(), - SizedBox( - height: 28, - width: double.infinity, - child: ElevatedButton.icon( - onPressed: () => onReorderPressed({ - 'orderId': order.orderId, - 'title': order.title, - 'location': order.location, - 'hourlyRate': order.hourlyRate, - 'hours': order.hours, - 'workers': order.workers, - 'type': order.type, - }), - style: ElevatedButton.styleFrom( - backgroundColor: UiColors.primary, - foregroundColor: UiColors.white, - padding: EdgeInsets.zero, - shape: RoundedRectangleBorder( - borderRadius: UiConstants.radiusMd, - ), - elevation: 0, - ), - icon: const Icon(UiIcons.zap, size: 12), - label: Text( - i18n.reorder_button, - style: UiTypography.footnote1m, - ), - ), + + UiButton.secondary( + size: UiButtonSize.small, + text: i18n.reorder_button, + leadingIcon: UiIcons.zap, + iconSize: 12, + fullWidth: true, + onPressed: () => onReorderPressed({ + 'orderId': order.orderId, + 'title': order.title, + 'location': order.location, + 'hourlyRate': order.hourlyRate, + 'hours': order.hours, + 'workers': order.workers, + 'type': order.type, + }), ), ], ), diff --git a/docs/QA_TESTING_CHECKLIST.md b/docs/QA_TESTING_CHECKLIST.md index aedccbc1..ff272efa 100644 --- a/docs/QA_TESTING_CHECKLIST.md +++ b/docs/QA_TESTING_CHECKLIST.md @@ -34,9 +34,6 @@ **Happy Path Test Cases:** - [ ] Sign in with valid email and password displays home dashboard - [ ] Sign up with business details creates account and navigates to home -- [ ] Sign in with Google OAuth completes authentication flow -- [ ] Sign in with Apple OAuth completes authentication flow -- [ ] Session persists after app restart **Validation & Error States:** - [ ] Invalid email format shows validation error @@ -44,24 +41,10 @@ - [ ] Weak password in sign-up shows strength requirements - [ ] Duplicate email in sign-up shows "already registered" error - [ ] Empty fields show required field errors -- [ ] Network error displays retry option **Loading & Empty States:** - [ ] Loading spinner displays during authentication - [ ] OAuth redirect shows appropriate loading state -- [ ] Backend timeout shows error message - -**State Persistence:** -- [ ] Authenticated session persists after app background โ†’ foreground -- [ ] Session expires appropriately after logout -- [ ] Device restart maintains logged-in state - -**Backend Dependency Validation:** -- [ ] `getUserById` returns user data for authenticated UID -- [ ] `createBusiness` successfully creates business entity -- [ ] `createUser` links user to business -- [ ] `getBusinessesByUserId` retrieves business profile -- [ ] Failed business creation triggers `deleteBusiness` rollback --- @@ -77,28 +60,14 @@ - [ ] Recent reorders display completed shift roles - [ ] Quick action buttons navigate to correct features - [ ] Drag-and-drop widget reordering works correctly -- [ ] Dashboard refreshes on pull-to-refresh gesture **Validation & Error States:** - [ ] Empty state shows "No data available" when no orders exist -- [ ] API error shows retry option -- [ ] Negative spending values display correctly **Loading & Empty States:** -- [ ] Skeleton loaders display while fetching data - [ ] Empty coverage shows "No shifts today" - [ ] Empty reorders shows "No recent orders" -**State Persistence:** -- [ ] Widget order persists after app restart -- [ ] Dashboard data refreshes after returning from background - -**Backend Dependency Validation:** -- [ ] `getCompletedShiftsByBusinessId` returns spending data for date ranges -- [ ] `listShiftRolesByBusinessAndDateRange` returns coverage stats -- [ ] `listShiftRolesByBusinessDateRangeCompletedOrders` returns reorder suggestions -- [ ] Business ID correctly retrieved from session - --- #### ๐Ÿ“ฑ CLIENT-003: Create Order @@ -107,12 +76,11 @@ **Entry Points:** - Home โ†’ Create Order button - Orders tab โ†’ + FAB button -- Order type โ†’ Rapid / One-Time / Recurring / Permanent +- Order type โ†’ One-Time **Happy Path Test Cases:** -- [ ] Order type selection displays all available types +- [ ] Order type selection displays. - [ ] Hub selection shows list of business hubs -- [ ] Google Places autocomplete suggests valid addresses - [ ] Role selection displays vendor roles - [ ] Position quantity can be incremented/decremented (min 1) - [ ] Date picker displays correct calendar @@ -130,7 +98,6 @@ - [ ] Invalid date (past) shows validation error - [ ] Start time after end time shows validation error - [ ] Missing required fields prevent submission -- [ ] Network error during submission shows retry option - [ ] Backend validation errors display appropriately **Loading & Empty States:** @@ -139,17 +106,6 @@ - [ ] Loading spinner displays during submission - [ ] Submission progress indicator updates -**State Persistence:** -- [ ] Form data persists when navigating away and back -- [ ] Draft order data clears after successful submission - -**Backend Dependency Validation:** -- [ ] `createOrder` creates order with ONE_TIME type -- [ ] `createShift` creates shift with location and time details -- [ ] `createShiftRole` creates positions with correct rates -- [ ] `updateOrder` links shift to order -- [ ] All operations complete or rollback on failure - --- #### ๐Ÿ“ฑ CLIENT-004: View Orders @@ -171,23 +127,13 @@ **Validation & Error States:** - [ ] Invalid date selection shows error -- [ ] Network error shows retry option + - [ ] Missing staff data shows placeholder **Loading & Empty States:** -- [ ] Skeleton loaders display while fetching orders - [ ] Empty date shows "No orders for this date" - [ ] Empty accepted applications shows "No confirmed staff" -**State Persistence:** -- [ ] Selected date persists after navigating away -- [ ] Order list refreshes after returning from background - -**Backend Dependency Validation:** -- [ ] `listShiftRolesByBusinessAndDateRange` returns orders for date range -- [ ] `listAcceptedApplicationsByBusinessForDay` returns confirmed staff -- [ ] Business ID correctly filtered in queries - --- #### ๐Ÿ“ฑ CLIENT-005: Coverage Monitoring @@ -207,7 +153,6 @@ **Validation & Error States:** - [ ] Missing worker photo shows default avatar -- [ ] Network error shows retry option **Loading & Empty States:** - [ ] Skeleton loaders display while fetching data @@ -245,7 +190,6 @@ - [ ] Zero billing shows $0.00 (not error) - [ ] Negative savings shows correctly - [ ] Missing invoice data shows placeholder -- [ ] Network error shows retry option **Loading & Empty States:** - [ ] Skeleton loaders display while fetching data @@ -289,7 +233,7 @@ - [ ] Invalid address format shows error - [ ] Duplicate hub name shows warning - [ ] Hub with active orders prevents deletion (validation error) -- [ ] Network error shows retry option + **Loading & Empty States:** - [ ] Skeleton loaders display while fetching hubs @@ -430,7 +374,7 @@ **Validation & Error States:** - [ ] Missing shift data shows placeholder -- [ ] Network error shows retry option + **Loading & Empty States:** - [ ] Skeleton loaders display while fetching data @@ -502,7 +446,7 @@ **Validation & Error States:** - [ ] Empty tabs show appropriate empty state messages -- [ ] Network error shows retry option + - [ ] Already applied shift prevents duplicate application - [ ] Past shifts cannot be applied to - [ ] Cancelled shifts show cancellation reason @@ -546,7 +490,7 @@ - [ ] Changes save automatically **Validation & Error States:** -- [ ] Network error shows retry option + - [ ] Save failure shows error message **Loading & Empty States:** @@ -584,7 +528,7 @@ **Validation & Error States:** - [ ] No shift today shows "No shifts to clock in" - [ ] Already clocked in prevents duplicate clock in -- [ ] Network error shows retry option + - [ ] Clock in outside shift time shows warning **Loading & Empty States:** @@ -620,7 +564,7 @@ **Validation & Error States:** - [ ] Zero earnings show $0.00 (not error) - [ ] Missing payment data shows placeholder -- [ ] Network error shows retry option + **Loading & Empty States:** - [ ] Skeleton loaders display while fetching payments @@ -657,7 +601,7 @@ - [ ] Empty required fields show validation errors - [ ] Invalid email format shows error - [ ] Invalid phone format shows error -- [ ] Network error shows retry option + - [ ] Photo upload failure shows error **Loading & Empty States:** @@ -695,7 +639,7 @@ - [ ] Empty name shows validation error - [ ] Invalid phone format shows error - [ ] At least one contact required (if applicable) -- [ ] Network error shows retry option + **Loading & Empty States:** - [ ] Loading spinner displays while fetching contacts @@ -731,7 +675,7 @@ **Validation & Error States:** - [ ] At least one industry required (if applicable) - [ ] At least one skill required (if applicable) -- [ ] Network error shows retry option + **Loading & Empty States:** - [ ] Loading spinner displays while fetching data @@ -764,7 +708,7 @@ **Validation & Error States:** - [ ] At least one attire item required (if applicable) - [ ] Photo upload failure shows error -- [ ] Network error shows retry option + **Loading & Empty States:** - [ ] Loading spinner displays while fetching options @@ -802,7 +746,7 @@ - [ ] Empty account number shows validation error - [ ] Invalid account number format shows error - [ ] Duplicate account shows warning -- [ ] Network error shows retry option + **Loading & Empty States:** - [ ] Loading spinner displays while fetching accounts @@ -835,7 +779,7 @@ **Validation & Error States:** - [ ] Missing attendance data shows "Not recorded" -- [ ] Network error shows retry option + **Loading & Empty States:** - [ ] Skeleton loaders display while fetching records @@ -872,7 +816,7 @@ - [ ] Invalid SSN format shows error - [ ] Invalid date format shows error - [ ] Signature required validation -- [ ] Network error shows retry option + **Loading & Empty States:** - [ ] Loading spinner displays while fetching forms @@ -906,7 +850,7 @@ **Validation & Error States:** - [ ] Missing documents show incomplete status -- [ ] Network error shows retry option + **Loading & Empty States:** - [ ] Skeleton loaders display while fetching documents @@ -936,7 +880,7 @@ **Validation & Error States:** - [ ] Missing certificates show placeholder -- [ ] Network error shows retry option + **Loading & Empty States:** - [ ] Skeleton loaders display while fetching certificates