- Removed data_connect package from mobile pubspec.yaml. - Added documentation for V2 profile migration status and QA findings. - Implemented new session management with ClientSessionStore and StaffSessionStore. - Created V2SessionService for handling user sessions via the V2 API. - Developed use cases for cancelling late worker assignments and submitting worker reviews. - Added arguments and use cases for payment chart retrieval and profile completion checks. - Implemented repository interfaces and their implementations for staff main and profile features. - Ensured proper error handling and validation in use cases.
21 KiB
21 KiB
Mobile Builder Agent Memory
Design System - Shimmer Primitives
- Shimmer widgets are in
packages/design_system/lib/src/widgets/shimmer/ - Available:
UiShimmer,UiShimmerBox,UiShimmerCircle,UiShimmerLine,UiShimmerListItem,UiShimmerStatsCard,UiShimmerSectionHeader,UiShimmerList UiShimmerList.itemBuildertakes(int index)-- single parameter, not(BuildContext, int)UiShimmerBox.borderRadiusacceptsBorderRadius?(nullable), usesUiConstants.radiusMdas default- All shimmer shapes render as solid white containers; the parent
UiShimmerapplies the animated gradient - Exported via
design_system.dartbarrel
Staff App Feature Locations
- Shifts:
packages/features/staff/shifts/-- has ShiftsPage (tabbed: MyShifts/Find/History) + ShiftDetailsPage - Home:
packages/features/staff/home/-- WorkerHomePage with sections (TodaysShifts, TomorrowsShifts, Recommended, Benefits, QuickActions) - Payments:
packages/features/staff/payments/-- PaymentsPage with gradient header + stats + payment history - Home cubit:
HomeStatusenum (initial, loading, loaded, error) - Shifts bloc:
ShiftsStatusenum + sub-loading flags (availableLoading,historyLoading) - Payments bloc: uses sealed state classes (
PaymentsLoading,PaymentsLoaded,PaymentsError)
UiConstants Spacing Tokens
- Use
UiConstants.space1throughUiConstants.space24for spacing - Radius:
UiConstants.radiusSm,radiusMd,radiusLg,radiusFull,radiusBase,radiusMdValue(double) UiConstants.radiusFullis aBorderRadius,UiConstants.radiusMdValueis adouble
Barrel Files (Staff Features)
- Shifts:
lib/staff_shifts.dartexports modules only - Payments:
lib/staff_payements.dart(note: typo in filename) exports module only - Home:
lib/staff_home.dartexports module only - These barrel files only export modules, not individual widgets -- skeleton widgets don't need to be added
Client App Feature Locations
- Coverage:
packages/features/client/client_coverage/ - Home:
packages/features/client/home/(no loading spinner -- renders default data during load) - Billing:
packages/features/client/billing/(billing_page, pending_invoices_page, invoice_ready_page) - Reports:
packages/features/client/reports/(reports_page with metrics_grid, plus 6 sub-report pages) - Reports barrel:
widgets/reports_page/index.dart - Hubs:
packages/features/client/hubs/(client_hubs_page + hub_details_page + edit_hub_page)
Staff Profile Sections (shimmer done)
- Compliance: certificates, documents, tax_forms -- all have shimmer skeletons
- Finances: staff_bank_account, time_card -- all have shimmer skeletons
- Onboarding: attire, profile_info (personal_info_page only) -- have shimmer skeletons
- Support: faqs, privacy_security (including legal sub-pages) -- have shimmer skeletons
- Pages that intentionally keep CircularProgressIndicator (action/submit spinners):
- form_i9_page, form_w4_page (submit button spinners)
- experience_page (save button spinner)
- preferred_locations_page (save button + overlay spinner)
- certificate_upload_page, document_upload_page, attire_capture_page (form/upload pages, no initial load)
- language_selection_page (no loading state, static list)
- LegalDocumentSkeleton is shared between PrivacyPolicyPage and TermsOfServicePage
Key Patterns Observed
- BenefitsOverviewPage also has CircularProgressIndicator (not shimmer-ified yet)
- ShiftDetailsPage has a dialog-level spinner in the "applying" dialog -- this is intentional, not a page loading state
- Hub details/edit pages use CircularProgressIndicator as action overlays (save/delete) -- keep as-is, not initial load
- Client home page uses shimmer skeleton during loading (ClientHomePageSkeleton + ClientHomeHeaderSkeleton)
V2 API Migration Patterns
BaseApiServiceis registered inCoreModuleas a lazy singleton (injected asi.get<BaseApiService>())BaseApiServicetype lives inkrow_domain;ApiServiceimpl lives inkrow_core- V2 endpoints:
V2ApiEndpoints.staffDashboardetc. fromkrow_core/core.dart - V2 domain shift entities:
TodayShift,AssignedShift,OpenShift(separate from coreShift) - V2
Benefit: usestargetHours/trackedHours/remainingHours(int) -- old usedentitlementHours/usedHours(double) - Staff dashboard endpoint returns all home data in one call (todaysShifts, tomorrowsShifts, recommendedShifts, benefits, staffName)
- Navigator has
toShiftDetailsById(String shiftId)for cases where only the ID is available StaffDashboardentity updated to use typed lists:List<TodayShift>,List<AssignedShift>,List<OpenShift>- Staff home feature migrated (Phase 2): removed krow_data_connect, firebase_data_connect, staff_shifts deps
- V2 Profile Migration -- entity mappings and DI patterns for all profile sub-packages
- Staff clock-in migrated (Phase 3): repo impl → V2 API, removed Data Connect deps
- V2
Shiftentity:startsAt/endsAt(DateTime),locationName(String?), nostartTime/endTime/clientName/hourlyRate/location - V2
AttendanceStatus:isClockedIngetter (notisCheckedIn),clockInAt(notcheckInTime), nocheckOutTime/activeApplicationId AttendanceStatusconstructor requiresattendanceStatus: AttendanceStatusType.notClockedInfor default- Clock-out uses
shiftId(notapplicationId) -- V2 API resolves assignment from shiftId listTodayShiftsendpoint returns{ items: [...] }with TodayShift-like shape (no lat/lng, hourlyRate, clientName)getCurrentAttendanceStatusreturns flat object{ activeShiftId, attendanceStatus, clockInAt }- Clock-in/out POST endpoints return
{ attendanceEventId, assignmentId, sessionId, status, validationStatus }-- repo re-fetches status after - Geofence: lat/lng not available from listTodayShifts or shiftDetail endpoints (lives on clock_points table, not returned by BE)
BaseApiServicenot exported fromkrow_core/core.dart-- must import fromkrow_domain/krow_domain.dart
- V2
Staff Shifts Feature Migration (Phase 3 -- completed)
- Migrated from
krow_data_connect+DataConnectServicetoBaseApiService+V2ApiEndpoints - Removed deps:
krow_data_connect,firebase_auth,firebase_data_connect,geolocator,google_maps_flutter,meta - State uses 5 typed lists:
List<AssignedShift>,List<OpenShift>,List<PendingAssignment>,List<CancelledShift>,List<CompletedShift> - ShiftDetail (not Shift) used for detail page -- loaded by BLoC via API, not passed as route argument
- Money:
hourlyRateCents(int) / 100 for display -- all V2 shift entities use cents - Dates: All V2 entities have
DateTimefields (notString) -- no moreDateTime.parse()in widgets - AssignmentStatus enum drives bottom bar logic (accepted=clock-in, assigned=accept/decline, null=apply)
- Old
Shiftentity still exists in domain but only used by clock-in feature -- shift list/detail pages use V2 entities - ShiftDetailsModule route no longer receives
Shiftdata argument -- usesshiftIdparam only toShiftDetailsById(String)is the standard navigation for V2 (no entity passing)- Profile completion: moved into feature repo impl via
V2ApiEndpoints.staffProfileCompletion+ProfileCompletion.fromJson - Find Shifts tab: removed geolocator distance filter and multi-day grouping (V2 API handles server-side)
- Renamed use cases:
GetMyShiftsUseCase→GetAssignedShiftsUseCase,GetAvailableShiftsUseCase→GetOpenShiftsUseCase,GetHistoryShiftsUseCase→GetCompletedShiftsUseCase,GetShiftDetailsUseCase→GetShiftDetailUseCase
Client Home Feature Migration (Phase 4 -- completed)
- Migrated from
krow_data_connect+DataConnectServicetoBaseApiService+V2ApiEndpoints - Removed deps:
krow_data_connect,firebase_data_connect,intl - V2 entities:
ClientDashboard(replacesHomeDashboardData),RecentOrder(replacesReorderItem) ClientDashboardcontains nestedSpendingSummary,CoverageMetrics,LiveActivityMetrics- Money:
weeklySpendCents,projectedNext7DaysCents,averageShiftCostCents(int) / 100 for display - Two API calls:
GET /client/dashboard(all metrics + user/biz info) andGET /client/reorders(returns{ items: [...] }) - Removed
GetUserSessionDataUseCase-- user/business info now part ofClientDashboard LiveActivityWidgetrewritten from StatefulWidget with direct DC calls to StatelessWidget consuming BLoC state- Dead code removed:
ShiftOrderFormSheet,ClientHomeSheets,CoverageDashboard(all unused) RecentOrderentity:id,title,date(DateTime?),hubName(String?),positionCount(int),orderType(OrderType)- Module imports
CoreModule()(notDataConnectModule()), injectsBaseApiServiceinto repo - State has
dashboard(ClientDashboard?) with computed gettersbusinessName,userName - No photoUrl in V2 dashboard response -- header shows letter avatar only
Client Billing Feature Migration (Phase 4 -- completed)
- Migrated from
krow_data_connect+BillingConnectorRepositorytoBaseApiService+V2ApiEndpoints - Removed deps:
krow_data_connect,firebase_data_connect - Deleted old presentation models:
BillingInvoice,BillingWorkerRecord,SpendingBreakdownItem - V2 domain entities used directly:
Invoice,BillingAccount,SpendItem,CurrentBill,Savings - Old domain types removed:
BusinessBankAccount,InvoiceItem,InvoiceWorker,BillingPeriodenum - Money: all amounts in cents (int). State has computed
currentBillDollars,savingsDollars,spendTotalCentsgetters InvoiceV2 entity:invoiceId,invoiceNumber,amountCents(int),status(InvoiceStatus enum),dueDate,paymentDate,vendorId,vendorNameBillingAccountV2 entity:accountId,bankName,providerReference,last4,isPrimary,accountType(AccountType enum)SpendItemV2 entity:category,amountCents(int),percentage(double) -- server-side aggregation by role- Spend breakdown: replaced
BillingPeriodenum withBillingPeriodTab(local) +SpendBreakdownParams(startDate/endDate ISO strings) - API response shapes: list endpoints return
{ items: [...] }, scalar endpoints spread data ({ currentBillCents, requestId }) - Approve/dispute: POST to
V2ApiEndpoints.clientInvoiceApprove(id)/clientInvoiceDispute(id) - Completion review page:
BillingInvoicereplaced withInvoice-- worker-level data not available in V2 (widget placeholder) InvoiceStatusenum has.valueproperty for display andfromJsonfactory with safe fallback tounknown
Client Reports Feature Migration (Phase 4 -- completed)
- Migrated from
krow_data_connect+ReportsConnectorRepositorytoBaseApiService+V2ApiEndpoints - Removed deps:
krow_data_connect - 7 report endpoints: summary, daily-ops, spend, coverage, forecast, performance, no-show
- Old
ReportsSummaryentity replaced with V2ReportSummary(different fields: totalShifts, totalSpendCents, averageCoveragePercentage, averagePerformanceScore, noShowCount, forecastAccuracyPercentage) businessIdremoved from all events/repo -- V2 API resolves from auth token- DailyOps: old
DailyOpsShiftreplaced withShiftWithWorkers(from coverage_domain).TimeRangehasstartsAt/endsAt(notstart/end) - Spend:
SpendReportusestotalSpendCents(int),chart(List withbucket/amountCents),breakdown(List withcategory/amountCents/percentage) - Coverage:
CoverageReportusesaverageCoveragePercentage,filledWorkers,neededWorkers,chart(List withday/needed/filled/coveragePercentage) - Forecast:
ForecastReportusesforecastSpendCents,averageWeeklySpendCents,totalWorkerHours,weeks(List withweek/shiftCount/workerHours/forecastSpendCents/averageShiftCostCents) - Performance: V2 uses int percentages (
fillRatePercentage,completionRatePercentage,onTimeRatePercentage) andaverageFillTimeMinutes(double) -- convert to hours:/60 - NoShow:
NoShowReportusestotalNoShowCount,noShowRatePercentage,workersWhoNoShowed,items(List withstaffId/staffName/incidentCount/riskStatus/incidents) - Module injects
BaseApiServiceviai.get<BaseApiService>()-- no moreDataConnectModuleimport
Client Hubs Feature Migration (Phase 5 -- completed)
- Migrated from
krow_data_connect+HubsConnectorRepository+DataConnectServicetoBaseApiService+V2ApiEndpoints - Removed deps:
krow_data_connect,firebase_auth,firebase_data_connect,http - V2
Hubentity:hubId(notid),fullAddress(notaddress),costCenterId/costCenterName(flat, not nestedCostCenterobject) - V2
CostCenterentity:costCenterId(notid),nameonly (nocodefield) - V2
HubManagerentity:managerAssignmentId,businessMembershipId,managerId,name - API response shapes:
GET /client/hubsreturns{ items: [...] },GET /client/cost-centersreturns{ items: [...] } - Create/update return
{ hubId, created: true }/{ hubId, updated: true }-- repo returns hubId String - Delete: soft-delete (sets status=INACTIVE). Backend rejects if hub has active orders (409 HUB_DELETE_BLOCKED)
- Assign NFC:
POST /client/hubs/:hubId/assign-nfcwith{ nfcTagId } - Module no longer imports
DataConnectModule()--BaseApiServiceavailable from parentCoreModule() UpdateHubArguments.idrenamed toUpdateHubArguments.hubId;CreateHubArguments.addressrenamed to.fullAddressHubDetailsDeleteRequested.idrenamed to.hubId;EditHubAddRequested.addressrenamed to.fullAddress- Navigator still passes full
Hubentity via route args (not just hubId)
Client Orders Feature Migration (Phase 5 -- completed)
- 3 sub-packages migrated:
orders_common,view_orders,create_order - Removed deps:
krow_data_connect,firebase_data_connect,firebase_authfrom all; keptintlin create_order and orders_common - V2
OrderItementity:itemId,orderId,orderType(OrderType enum),roleName,date(DateTime),startsAt/endsAt(DateTime),requiredWorkerCount,filledCount,hourlyRateCents,totalCostCents(int cents),locationName(String?),status(ShiftStatus enum),workers(List) - Old entities deleted:
OneTimeOrder,RecurringOrder,PermanentOrder,ReorderData,OneTimeOrderHubDetails,RecurringOrderHubDetails AssignedWorkerSummary:applicationId(String?),workerName(String? -- nullable!),role(String?),confirmationStatus(ApplicationStatus?)- V2
Vendorentity: field iscompanyName(notname) -- old code usedvendor.name - V2
ShiftStatusenum: only hasdraft,open,pendingConfirmation,assigned,active,completed,cancelled,unknown-- nofilled/confirmed/pending OrderTypeenum hasunknownvariant -- must handle in switch statements- View orders: removed
GetAcceptedApplicationsForDayUseCase-- V2 returns workers inline with order items - View orders cubit: date filtering now uses
_isSameDay(DateTime, DateTime)instead of string comparison - Create order BLoCs: build
Map<String, dynamic>V2 payloads instead of old entity objects - V2 create endpoints:
POST /client/orders/one-time(requiresorderDate),/recurring(requiresstartDate/endDate/recurrenceDays),/permanent(requiresstartDate/daysOfWeek) - V2 edit endpoint:
POST /client/orders/:orderId/edit-- creates edited copy, cancels original - V2 cancel endpoint:
POST /client/orders/:orderId/cancelwith optionalreason - Reorder uses
OrderPreview(fromV2ApiEndpoints.clientOrderReorderPreview) instead of oldReorderData OrderPreviewhas nestedOrderPreviewShift>OrderPreviewRolestructure- Query repo:
getHubs()replacesgetHubsByOwner(businessId)-- V2 resolves business from auth token OneTimeOrderPositionis now a typedef forOrderPositionUiModelfromorders_commonOrderEditSheet(1700 lines) fully rewritten: delegates toIViewOrdersRepositoryinstead of direct DC calls
Staff Authentication Feature Migration (Phase 6 -- completed)
- Migrated from
krow_data_connect+DataConnectService+firebase_data_connecttoBaseApiService+V2ApiEndpoints - Removed deps:
krow_data_connect,firebase_data_connect,firebase_core - KEPT
firebase_auth-- V2 backendstartStaffPhoneAuthreturnsCLIENT_FIREBASE_SDKmode for mobile, meaning phone verification stays client-side via Firebase SDK - Auth flow: Firebase SDK phone verify (client-side) -> get idToken ->
POST /auth/staff/phone/verifywith{ idToken, mode }-> V2 hydrates session (upserts user, loads actor context) - V2 verify response:
{ sessionToken, refreshToken, expiresInSeconds, user: { id, email, displayName, phone }, staff: { staffId, tenantId, fullName, ... }, tenant, requiresProfileSetup } requiresProfileSetupboolean replaces old signup logic (create user/staff via DC mutations)- Profile setup:
POST /staff/profile/setupwith{ fullName, bio, preferredLocations, maxDistanceMiles, industries, skills } - Sign out:
POST /auth/sign-out(server-side token revocation) + localFirebaseAuth.signOut() AuthInterceptorinDioClientstays as-is -- attaches Firebase Bearer tokens to all V2 API requestsAuthInterceptorinDioClientstays as-is -- attaches Firebase Bearer tokens to all V2 API requests- Pre-existing issue:
ExperienceSkillandIndustryenums deleted from domain but still referenced inprofile_setup_experience.dart
Client Authentication Feature Migration (Phase 6 -- completed)
- Migrated from
krow_data_connect+DataConnectService+firebase_data_connecttoBaseApiService+V2ApiEndpoints - Removed deps:
firebase_data_connect,firebase_corefrom pubspec - KEPT
firebase_auth-- client-side sign-in needed soAuthInterceptorcan attach Bearer tokens - KEPT
krow_data_connect-- only forClientSessionStore/ClientSession/ClientBusinessSession(not yet extracted) - Auth flow (Option A -- hybrid):
- Firebase Auth client-side
signInWithEmailAndPassword(setsFirebaseAuth.instance.currentUser) GET /auth/sessionvia V2 API (returns user + business + tenant context)- Populate
ClientSessionStorefrom V2 session response
- Firebase Auth client-side
- Sign-up flow:
POST /auth/client/sign-upvia V2 API (server-side: creates Firebase account + user/tenant/business/memberships in one transaction)- Local
signInWithEmailAndPassword(sets local auth state) GET /auth/sessionto load context + populate session store
- V2 session response shape:
{ user: { userId, email, displayName, phone, status }, business: { businessId, businessName, businessSlug, role, tenantId, membershipId }, tenant: {...}, vendor: null, staff: null } - Sign-out:
POST /auth/client/sign-out(server-side revocation) +FirebaseAuth.instance.signOut()+ClientSessionStore.instance.clear() - V2 sign-up error codes:
AUTH_PROVIDER_ERRORwith message containingEMAIL_EXISTSorWEAK_PASSWORD,FORBIDDENfor role mismatch - Old Data Connect calls removed:
getUserById,getBusinessesByUserId,createBusiness,createUser,updateUser,deleteBusiness - Old rollback logic removed -- V2 API handles rollback server-side in one transaction
- Domain
Userentity: V2 usesstatus: UserStatus(notrole: String) -- constructor:User(id:, email:, displayName:, phone:, status:) - Module:
CoreModule()(notDataConnectModule()), injectsBaseApiServiceintoAuthRepositoryImpl
Client Settings Feature Migration (Phase 6 -- completed)
- Migrated sign-out from
DataConnectService.signOut()to V2 API + local Firebase Auth - Removed
DataConnectModuleimport from module, replaced withCoreModule() SettingsRepositoryImplnow takesBaseApiService(notDataConnectService)- Sign-out:
POST /auth/client/sign-out+FirebaseAuth.instance.signOut()+ClientSessionStore.instance.clear() settings_profile_header.dartstill reads fromClientSessionStore(now fromkrow_core)
V2SessionService (Final Phase -- completed)
V2SessionServicesingleton atpackages/core/lib/src/services/session/v2_session_service.dart- Replaces
DataConnectServicefor session state management in both apps - Uses
SessionHandlerMixinfrom core (same interface as old DC version) fetchUserRole()callsGET /auth/sessionviaBaseApiService(not DC connector)signOut()callsPOST /auth/sign-out+FirebaseAuth.signOut()+handleSignOut()- Registered in
CoreModuleviai.addLazySingleton<V2SessionService>()-- callssetApiService() - Both
main.dartfiles useV2SessionService.instance.initializeAuthListener()instead ofDataConnectService - Both
SessionListenerwidgets subscribe toV2SessionService.instance.onSessionStateChanged staff_mainpackage migrated: local repo/usecase viaV2ApiEndpoints.staffProfileCompletion+ProfileCompletion.fromJsonkrow_data_connectremoved from: staff app, client app, staff_main package pubspecs- Session stores (
StaffSessionStore,ClientSessionStore) now live in core, not data_connect