Files
Krow-workspace/.claude/agent-memory/mobile-builder/MEMORY.md
Achintha Isuru b31a615092 feat: Migrate staff profile features from Data Connect to V2 REST API
- 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.
2026-03-16 22:45:06 -04:00

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.itemBuilder takes (int index) -- single parameter, not (BuildContext, int)
  • UiShimmerBox.borderRadius accepts BorderRadius? (nullable), uses UiConstants.radiusMd as default
  • All shimmer shapes render as solid white containers; the parent UiShimmer applies the animated gradient
  • Exported via design_system.dart barrel

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: HomeStatus enum (initial, loading, loaded, error)
  • Shifts bloc: ShiftsStatus enum + sub-loading flags (availableLoading, historyLoading)
  • Payments bloc: uses sealed state classes (PaymentsLoading, PaymentsLoaded, PaymentsError)

UiConstants Spacing Tokens

  • Use UiConstants.space1 through UiConstants.space24 for spacing
  • Radius: UiConstants.radiusSm, radiusMd, radiusLg, radiusFull, radiusBase, radiusMdValue (double)
  • UiConstants.radiusFull is a BorderRadius, UiConstants.radiusMdValue is a double

Barrel Files (Staff Features)

  • Shifts: lib/staff_shifts.dart exports modules only
  • Payments: lib/staff_payements.dart (note: typo in filename) exports module only
  • Home: lib/staff_home.dart exports 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

  • BaseApiService is registered in CoreModule as a lazy singleton (injected as i.get<BaseApiService>())
  • BaseApiService type lives in krow_domain; ApiService impl lives in krow_core
  • V2 endpoints: V2ApiEndpoints.staffDashboard etc. from krow_core/core.dart
  • V2 domain shift entities: TodayShift, AssignedShift, OpenShift (separate from core Shift)
  • V2 Benefit: uses targetHours/trackedHours/remainingHours (int) -- old used entitlementHours/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
  • StaffDashboard entity 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 Shift entity: startsAt/endsAt (DateTime), locationName (String?), no startTime/endTime/clientName/hourlyRate/location
    • V2 AttendanceStatus: isClockedIn getter (not isCheckedIn), clockInAt (not checkInTime), no checkOutTime/activeApplicationId
    • AttendanceStatus constructor requires attendanceStatus: AttendanceStatusType.notClockedIn for default
    • Clock-out uses shiftId (not applicationId) -- V2 API resolves assignment from shiftId
    • listTodayShifts endpoint returns { items: [...] } with TodayShift-like shape (no lat/lng, hourlyRate, clientName)
    • getCurrentAttendanceStatus returns 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)
    • BaseApiService not exported from krow_core/core.dart -- must import from krow_domain/krow_domain.dart

Staff Shifts Feature Migration (Phase 3 -- completed)

  • Migrated from krow_data_connect + DataConnectService to BaseApiService + 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 DateTime fields (not String) -- no more DateTime.parse() in widgets
  • AssignmentStatus enum drives bottom bar logic (accepted=clock-in, assigned=accept/decline, null=apply)
  • Old Shift entity still exists in domain but only used by clock-in feature -- shift list/detail pages use V2 entities
  • ShiftDetailsModule route no longer receives Shift data argument -- uses shiftId param 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: GetMyShiftsUseCaseGetAssignedShiftsUseCase, GetAvailableShiftsUseCaseGetOpenShiftsUseCase, GetHistoryShiftsUseCaseGetCompletedShiftsUseCase, GetShiftDetailsUseCaseGetShiftDetailUseCase

Client Home Feature Migration (Phase 4 -- completed)

  • Migrated from krow_data_connect + DataConnectService to BaseApiService + V2ApiEndpoints
  • Removed deps: krow_data_connect, firebase_data_connect, intl
  • V2 entities: ClientDashboard (replaces HomeDashboardData), RecentOrder (replaces ReorderItem)
  • ClientDashboard contains nested SpendingSummary, CoverageMetrics, LiveActivityMetrics
  • Money: weeklySpendCents, projectedNext7DaysCents, averageShiftCostCents (int) / 100 for display
  • Two API calls: GET /client/dashboard (all metrics + user/biz info) and GET /client/reorders (returns { items: [...] })
  • Removed GetUserSessionDataUseCase -- user/business info now part of ClientDashboard
  • LiveActivityWidget rewritten from StatefulWidget with direct DC calls to StatelessWidget consuming BLoC state
  • Dead code removed: ShiftOrderFormSheet, ClientHomeSheets, CoverageDashboard (all unused)
  • RecentOrder entity: id, title, date (DateTime?), hubName (String?), positionCount (int), orderType (OrderType)
  • Module imports CoreModule() (not DataConnectModule()), injects BaseApiService into repo
  • State has dashboard (ClientDashboard?) with computed getters businessName, userName
  • No photoUrl in V2 dashboard response -- header shows letter avatar only

Client Billing Feature Migration (Phase 4 -- completed)

  • Migrated from krow_data_connect + BillingConnectorRepository to BaseApiService + 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, BillingPeriod enum
  • Money: all amounts in cents (int). State has computed currentBillDollars, savingsDollars, spendTotalCents getters
  • Invoice V2 entity: invoiceId, invoiceNumber, amountCents (int), status (InvoiceStatus enum), dueDate, paymentDate, vendorId, vendorName
  • BillingAccount V2 entity: accountId, bankName, providerReference, last4, isPrimary, accountType (AccountType enum)
  • SpendItem V2 entity: category, amountCents (int), percentage (double) -- server-side aggregation by role
  • Spend breakdown: replaced BillingPeriod enum with BillingPeriodTab (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: BillingInvoice replaced with Invoice -- worker-level data not available in V2 (widget placeholder)
  • InvoiceStatus enum has .value property for display and fromJson factory with safe fallback to unknown

Client Reports Feature Migration (Phase 4 -- completed)

  • Migrated from krow_data_connect + ReportsConnectorRepository to BaseApiService + V2ApiEndpoints
  • Removed deps: krow_data_connect
  • 7 report endpoints: summary, daily-ops, spend, coverage, forecast, performance, no-show
  • Old ReportsSummary entity replaced with V2 ReportSummary (different fields: totalShifts, totalSpendCents, averageCoveragePercentage, averagePerformanceScore, noShowCount, forecastAccuracyPercentage)
  • businessId removed from all events/repo -- V2 API resolves from auth token
  • DailyOps: old DailyOpsShift replaced with ShiftWithWorkers (from coverage_domain). TimeRange has startsAt/endsAt (not start/end)
  • Spend: SpendReport uses totalSpendCents (int), chart (List with bucket/amountCents), breakdown (List with category/amountCents/percentage)
  • Coverage: CoverageReport uses averageCoveragePercentage, filledWorkers, neededWorkers, chart (List with day/needed/filled/coveragePercentage)
  • Forecast: ForecastReport uses forecastSpendCents, averageWeeklySpendCents, totalWorkerHours, weeks (List with week/shiftCount/workerHours/forecastSpendCents/averageShiftCostCents)
  • Performance: V2 uses int percentages (fillRatePercentage, completionRatePercentage, onTimeRatePercentage) and averageFillTimeMinutes (double) -- convert to hours: /60
  • NoShow: NoShowReport uses totalNoShowCount, noShowRatePercentage, workersWhoNoShowed, items (List with staffId/staffName/incidentCount/riskStatus/incidents)
  • Module injects BaseApiService via i.get<BaseApiService>() -- no more DataConnectModule import

Client Hubs Feature Migration (Phase 5 -- completed)

  • Migrated from krow_data_connect + HubsConnectorRepository + DataConnectService to BaseApiService + V2ApiEndpoints
  • Removed deps: krow_data_connect, firebase_auth, firebase_data_connect, http
  • V2 Hub entity: hubId (not id), fullAddress (not address), costCenterId/costCenterName (flat, not nested CostCenter object)
  • V2 CostCenter entity: costCenterId (not id), name only (no code field)
  • V2 HubManager entity: managerAssignmentId, businessMembershipId, managerId, name
  • API response shapes: GET /client/hubs returns { items: [...] }, GET /client/cost-centers returns { 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-nfc with { nfcTagId }
  • Module no longer imports DataConnectModule() -- BaseApiService available from parent CoreModule()
  • UpdateHubArguments.id renamed to UpdateHubArguments.hubId; CreateHubArguments.address renamed to .fullAddress
  • HubDetailsDeleteRequested.id renamed to .hubId; EditHubAddRequested.address renamed to .fullAddress
  • Navigator still passes full Hub entity 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_auth from all; kept intl in create_order and orders_common
  • V2 OrderItem entity: 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 Vendor entity: field is companyName (not name) -- old code used vendor.name
  • V2 ShiftStatus enum: only has draft, open, pendingConfirmation, assigned, active, completed, cancelled, unknown -- no filled/confirmed/pending
  • OrderType enum has unknown variant -- 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 (requires orderDate), /recurring (requires startDate/endDate/recurrenceDays), /permanent (requires startDate/daysOfWeek)
  • V2 edit endpoint: POST /client/orders/:orderId/edit -- creates edited copy, cancels original
  • V2 cancel endpoint: POST /client/orders/:orderId/cancel with optional reason
  • Reorder uses OrderPreview (from V2ApiEndpoints.clientOrderReorderPreview) instead of old ReorderData
  • OrderPreview has nested OrderPreviewShift > OrderPreviewRole structure
  • Query repo: getHubs() replaces getHubsByOwner(businessId) -- V2 resolves business from auth token
  • OneTimeOrderPosition is now a typedef for OrderPositionUiModel from orders_common
  • OrderEditSheet (1700 lines) fully rewritten: delegates to IViewOrdersRepository instead of direct DC calls

Staff Authentication Feature Migration (Phase 6 -- completed)

  • Migrated from krow_data_connect + DataConnectService + firebase_data_connect to BaseApiService + V2ApiEndpoints
  • Removed deps: krow_data_connect, firebase_data_connect, firebase_core
  • KEPT firebase_auth -- V2 backend startStaffPhoneAuth returns CLIENT_FIREBASE_SDK mode 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/verify with { 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 }
  • requiresProfileSetup boolean replaces old signup logic (create user/staff via DC mutations)
  • Profile setup: POST /staff/profile/setup with { fullName, bio, preferredLocations, maxDistanceMiles, industries, skills }
  • Sign out: POST /auth/sign-out (server-side token revocation) + local FirebaseAuth.signOut()
  • AuthInterceptor in DioClient stays as-is -- attaches Firebase Bearer tokens to all V2 API requests
  • AuthInterceptor in DioClient stays as-is -- attaches Firebase Bearer tokens to all V2 API requests
  • Pre-existing issue: ExperienceSkill and Industry enums deleted from domain but still referenced in profile_setup_experience.dart

Client Authentication Feature Migration (Phase 6 -- completed)

  • Migrated from krow_data_connect + DataConnectService + firebase_data_connect to BaseApiService + V2ApiEndpoints
  • Removed deps: firebase_data_connect, firebase_core from pubspec
  • KEPT firebase_auth -- client-side sign-in needed so AuthInterceptor can attach Bearer tokens
  • KEPT krow_data_connect -- only for ClientSessionStore/ClientSession/ClientBusinessSession (not yet extracted)
  • Auth flow (Option A -- hybrid):
    1. Firebase Auth client-side signInWithEmailAndPassword (sets FirebaseAuth.instance.currentUser)
    2. GET /auth/session via V2 API (returns user + business + tenant context)
    3. Populate ClientSessionStore from V2 session response
  • Sign-up flow:
    1. POST /auth/client/sign-up via V2 API (server-side: creates Firebase account + user/tenant/business/memberships in one transaction)
    2. Local signInWithEmailAndPassword (sets local auth state)
    3. GET /auth/session to 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_ERROR with message containing EMAIL_EXISTS or WEAK_PASSWORD, FORBIDDEN for 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 User entity: V2 uses status: UserStatus (not role: String) -- constructor: User(id:, email:, displayName:, phone:, status:)
  • Module: CoreModule() (not DataConnectModule()), injects BaseApiService into AuthRepositoryImpl

Client Settings Feature Migration (Phase 6 -- completed)

  • Migrated sign-out from DataConnectService.signOut() to V2 API + local Firebase Auth
  • Removed DataConnectModule import from module, replaced with CoreModule()
  • SettingsRepositoryImpl now takes BaseApiService (not DataConnectService)
  • Sign-out: POST /auth/client/sign-out + FirebaseAuth.instance.signOut() + ClientSessionStore.instance.clear()
  • settings_profile_header.dart still reads from ClientSessionStore (now from krow_core)

V2SessionService (Final Phase -- completed)

  • V2SessionService singleton at packages/core/lib/src/services/session/v2_session_service.dart
  • Replaces DataConnectService for session state management in both apps
  • Uses SessionHandlerMixin from core (same interface as old DC version)
  • fetchUserRole() calls GET /auth/session via BaseApiService (not DC connector)
  • signOut() calls POST /auth/sign-out + FirebaseAuth.signOut() + handleSignOut()
  • Registered in CoreModule via i.addLazySingleton<V2SessionService>() -- calls setApiService()
  • Both main.dart files use V2SessionService.instance.initializeAuthListener() instead of DataConnectService
  • Both SessionListener widgets subscribe to V2SessionService.instance.onSessionStateChanged
  • staff_main package migrated: local repo/usecase via V2ApiEndpoints.staffProfileCompletion + ProfileCompletion.fromJson
  • krow_data_connect removed from: staff app, client app, staff_main package pubspecs
  • Session stores (StaffSessionStore, ClientSessionStore) now live in core, not data_connect