Skip to content

feat(citizen-pwa): 3-step report wizard wiring + GPS fixes + auto-fill#59

Merged
Exc1D merged 10 commits intomainfrom
feature/3-step-wizard-wiring
Apr 23, 2026
Merged

feat(citizen-pwa): 3-step report wizard wiring + GPS fixes + auto-fill#59
Exc1D merged 10 commits intomainfrom
feature/3-step-wizard-wiring

Conversation

@Exc1D
Copy link
Copy Markdown
Owner

@Exc1D Exc1D commented Apr 23, 2026

Summary

  • Rewired /report to a 3-step wizard (Step1Evidence → Step2WhoWhere → Step3Review) with data accumulation across steps
  • Removed legacy SubmitReportForm.tsx and /report/new route; consolidated to single /report entry point
  • Fixed blank-page bugs after submission (auto-start machine.submit() on mount, RevealSheet for all terminal states)
  • Fixed GPS location services: guard for missing navigator.geolocation, specific error messages per GeolocationPositionError code (permission denied, timed out)
  • Added reporter name/phone auto-fill from localStorage (bantayog.reporter.*) with "Pre-filled from your last report" hint; saves on Continue

Changes

  • SubmitReportForm/index.tsx — rewritten as WizardContainer (step state) + SubmissionPanel (submission machine); auto-starts submission; all failure states render RevealSheet
  • SubmitReportForm/Step2WhoWhere.tsx — GPS guard + specific error messages + localStorage pre-fill on mount + persist on Continue
  • routes.tsx/report → new wizard (no CitizenShell wrapper, hideBottomNav: true); removed /report/new
  • App.routes.test.tsx — updated mock path and test assertions for new route structure

Test plan

  • pnpm --filter @bantayog/citizen-pwa typecheck — PASS
  • pnpm --filter @bantayog/citizen-pwa lint — PASS
  • pnpm --filter @bantayog/citizen-pwa exec vitest run — 101 passed, 2 todo
  • Manual: GPS prompt appears on Step 2 mount; denial shows actionable error and falls through to manual selector
  • Manual: name/phone pre-fill from previous report; "Pre-filled" hint visible
  • Manual: Step 3 → Submit → RevealSheet success/queued/failed states render (no blank page)

Summary by Sourcery

Implement a new three-step report submission wizard with improved submission handling, GPS robustness, and reporter detail persistence.

New Features:

  • Introduce a three-step report wizard that accumulates data across steps and creates report drafts from the collected data.
  • Auto-fill reporter name and phone from previous submissions using localStorage with a visible pre-fill hint.
  • Add icon-based bottom navigation tabs using lucide-react icons in the citizen shell.

Bug Fixes:

  • Handle missing geolocation support and provide clearer, code-specific GPS error messages that fall back to manual location selection.
  • Ensure submission flow always shows a terminal RevealSheet state and avoids blank pages by auto-starting the submission machine.

Enhancements:

  • Refactor the report form into a wizard container and dedicated submission panel that integrates with the submission state machine.
  • Persist reporter details to localStorage on continue in the wizard to streamline future reports.
  • Consolidate report routing to a single /report entry point with hidden bottom navigation during the wizard.

Documentation:

  • Update project progress documentation to reflect completion of the three-step report wizard wiring work.

Tests:

  • Adjust app route tests and mocks to target the new SubmitReportForm entry point at /report.

Summary by CodeRabbit

  • New Features

    • Report submission converted to a 3-step wizard (Evidence → Who/Where → Review) with auto-submit on completion.
    • Reporter name/phone prefill from saved local data.
  • Improvements

    • Consolidated /report flow into the new wizard; submission UI now uses sheet-based states for confirmations and failures.
    • Navigation icons updated for clearer visuals.
  • Style

    • Added urgent field-group styling.
  • Tests

    • Adjusted route tests to match the new wizard structure.
  • Documentation

    • Added progress notes describing the wizard and routing updates.

claude and others added 6 commits April 23, 2026 08:45
Rewrites SubmitReportForm/index.tsx from a single-page form into a
3-step wizard (Step1Evidence → Step2WhoWhere → Step3Review). On step
3 submit, creates a draft via createDraft(), then hands off to
SubmissionPanel which drives RevealSheet state via useSubmissionMachine.

- Step state 1/2/3 with data accumulation across steps
- createDraft called with hashed MSISDN, severity='medium', patientCount→description
- RevealSheet shown for server_confirmed/queued/failed_retryable states
- queued/failed onClose navigates to /; success onSuccess navigates to /reports/:ref
- failed_terminal shows SmsFallbackButton + OfflineBanner

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cy form

- /report now renders the 3-step wizard directly (no CitizenShell wrapper)
- /report/new route removed — wizard is canonical at /report
- SubmitReportForm.tsx (old single-page form) deleted
- App.routes.test.tsx: update mock path and drop nav-button assertion
  (wizard hides bottom nav, so aria-current check no longer applies)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two post-submit UX bugs fixed:

1. SubmissionPanel now auto-fires machine.submit() on mount via
   useEffect. The wizard already captured user consent in Step3 —
   showing a second raw 'Submit report' button was a double-confirm
   that violated the wizard flow.

2. failed_terminal now renders RevealSheet (failed_retryable variant)
   instead of raw OfflineBanner + SmsFallbackButton. This matches the
   styled sheet users see for failed_retryable, which already includes
   hotline/SMS fallback cards.

Removes: SmsFallbackButton import (no longer needed), reporterMsisdn
prop from SubmissionPanel (only consumer was SmsFallbackButton).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Guard navigator.geolocation for browsers without Geolocation API
- Map GeolocationPositionError codes to specific user messages
  (PERMISSION_DENIED, TIMEOUT vs generic fallback)
- Pre-fill reporter name and phone from bantayog.reporter.* localStorage
  keys on mount; show "Pre-filled from your last report" hint
- Persist name/phone to localStorage on Continue so next report skips re-entry
- Fix stale eslint-disable-next-line placement for exhaustive-deps in SubmissionPanel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…m nav

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Apr 23, 2026

Reviewer's Guide

Replaces the legacy one-page report form with a 3-step wizard flow, updates submission handling to auto-start and always surface terminal states via RevealSheet, improves GPS error handling and reporter info persistence, and adjusts routing, navigation shell, tests, and docs to match the new flow.

Sequence diagram for 3-step wizard submission flow

sequenceDiagram
  actor User
  participant Step1Evidence
  participant WizardContainer
  participant Step2WhoWhere
  participant Step3Review
  participant DraftService as createDraft
  participant SubmissionPanel
  participant SubmissionMachine as useSubmissionMachine
  participant RevealSheet

  User->>Step1Evidence: fill reportType, photoFile
  User->>Step1Evidence: click Continue
  Step1Evidence->>WizardContainer: onNext(step1Data)
  WizardContainer->>WizardContainer: save formData.step1
  WizardContainer->>WizardContainer: setStep(2)

  User->>Step2WhoWhere: fill location, reporterName, reporterMsisdn, patientCount
  User->>Step2WhoWhere: click Continue
  Step2WhoWhere->>WizardContainer: onNext(step2Data)
  WizardContainer->>WizardContainer: save formData.step2
  WizardContainer->>WizardContainer: setStep(3)

  WizardContainer->>Step3Review: render with reportData
  User->>Step3Review: click Submit
  Step3Review->>WizardContainer: onSubmit()

  WizardContainer->>WizardContainer: handleStep3Submit()
  WizardContainer->>DraftService: createDraft(reportType, location, reporterName, reporterMsisdnHash, patientCount, barangayId, nearestLandmark, photo)
  DraftService-->>WizardContainer: draft
  WizardContainer->>WizardContainer: setDraft(draft)

  WizardContainer->>SubmissionPanel: render with draft
  SubmissionPanel->>SubmissionMachine: useSubmissionMachine(draft)
  SubmissionPanel->>SubmissionMachine: submit() on mount

  alt server_confirmed
    SubmissionMachine-->>SubmissionPanel: state server_confirmed
    SubmissionPanel->>RevealSheet: state success, referenceCode draft.id
  else queued
    SubmissionMachine-->>SubmissionPanel: state queued
    SubmissionPanel->>RevealSheet: state queued, onClose nav('/')
  else failed_retryable
    SubmissionMachine-->>SubmissionPanel: state failed_retryable
    SubmissionPanel->>RevealSheet: state failed_retryable, onClose nav('/')
  else failed_terminal
    SubmissionMachine-->>SubmissionPanel: state failed_terminal
    SubmissionPanel->>RevealSheet: state failed_retryable, onClose nav('/')
  end
Loading

Sequence diagram for GPS capture and reporter auto-fill in Step2WhoWhere

sequenceDiagram
  actor User
  participant Step2WhoWhere
  participant Geolocation as navigator.geolocation
  participant LocalStore as localStorage

  Note over Step2WhoWhere: On mount
  Step2WhoWhere->>LocalStore: getItem(bantayog.reporter.name)
  LocalStore-->>Step2WhoWhere: savedName or null
  Step2WhoWhere->>LocalStore: getItem(bantayog.reporter.msisdn)
  LocalStore-->>Step2WhoWhere: savedMsisdn or null
  alt savedName or savedMsisdn exists
    Step2WhoWhere->>Step2WhoWhere: setReporterName, setReporterMsisdn
    Step2WhoWhere->>Step2WhoWhere: setHasMemory(true)
  end

  Note over Step2WhoWhere: Auto-attempt GPS on mount
  Step2WhoWhere->>Step2WhoWhere: attemptGps()
  alt geolocation not supported
    Step2WhoWhere->>Step2WhoWhere: setLocationError("GPS not supported on this device.")
    Step2WhoWhere->>Step2WhoWhere: setLocationMethod('manual')
  else geolocation supported
    Step2WhoWhere->>Geolocation: getCurrentPosition(success, error, options)
    alt success
      Geolocation-->>Step2WhoWhere: position
      Step2WhoWhere->>Step2WhoWhere: setLocation(position.coords)
      Step2WhoWhere->>Step2WhoWhere: setLocationMethod('gps')
    else error
      Geolocation-->>Step2WhoWhere: error(code)
      alt code == 1
        Step2WhoWhere->>Step2WhoWhere: setLocationError("Location access denied. Choose municipality manually.")
      else code == 3
        Step2WhoWhere->>Step2WhoWhere: setLocationError("Location timed out. Choose municipality manually.")
      else other error
        Step2WhoWhere->>Step2WhoWhere: setLocationError("Could not get location. Choose municipality manually.")
      end
      Step2WhoWhere->>Step2WhoWhere: setLocationMethod('manual')
    end
  end

  User->>Step2WhoWhere: fill or edit reporterName, reporterMsisdn
  User->>Step2WhoWhere: click Continue
  Step2WhoWhere->>Step2WhoWhere: validate fields
  Step2WhoWhere->>LocalStore: setItem(bantayog.reporter.name, reporterName)
  Step2WhoWhere->>LocalStore: setItem(bantayog.reporter.msisdn, reporterMsisdn)
  Step2WhoWhere-->>User: proceed to Step3 via onNext(step2Data)
Loading

Class diagram for new wizard container and related data types

classDiagram
  class Step1Data {
    string reportType
    File photoFile
  }

  class Step2Data {
    Location location
    string reporterName
    string reporterMsisdn
    number patientCount
    string locationMethod
    string municipalityId
    string municipalityLabel
    string barangayId
    string nearestLandmark
  }

  class Location {
    number lat
    number lng
  }

  class FormData {
    Step1Data step1
    Step2Data step2
  }

  class WizardContainer {
    - number step
    - FormData formData
    - Draft draft
    - boolean isCreatingDraft
    - string draftError
    + handleStep1Next(data Step1Data) void
    + handleStep2Next(data Step2Data) void
    + handleStep1Back() void
    + handleStep2Back() void
    + handleStep3Back() void
    + handleStep3Submit() Promise~void~
  }

  class SubmitReportForm {
    + SubmitReportForm() WizardContainer
  }

  class SubmissionPanel {
    - Draft draft
    - number now
    + SubmissionPanel(draft Draft, onSuccess function) void
  }

  class Step1Evidence {
    + onNext(data Step1Data) void
    + onBack() void
    + isSubmitting boolean
  }

  class Step2WhoWhere {
    + onNext(data Step2Data) void
    + onBack() void
    + isSubmitting boolean
  }

  class Step3Review {
    + onBack() void
    + onSubmit() void
    + isSubmitting boolean
  }

  class useSubmissionMachine {
    + useSubmissionMachine(draft Draft, now number, onSuccess function, onError function) SubmissionMachine
  }

  class SubmissionMachine {
    string state
    + submit() Promise~void~
  }

  class RevealSheet {
    + RevealSheet(state string, referenceCode string, onClose function) void
  }

  SubmitReportForm --> WizardContainer : returns
  WizardContainer --> Step1Evidence : renders
  WizardContainer --> Step2WhoWhere : renders
  WizardContainer --> Step3Review : renders
  WizardContainer --> FormData : manages
  FormData --> Step1Data
  FormData --> Step2Data
  Step2Data --> Location
  WizardContainer --> SubmissionPanel : renders when draft

  SubmissionPanel --> useSubmissionMachine : uses
  useSubmissionMachine --> SubmissionMachine : creates
  SubmissionPanel --> SubmissionMachine : controls
  SubmissionPanel --> RevealSheet : renders for all terminal states
Loading

Flow diagram for updated /report routing and shell behavior

flowchart LR
  AppRouter["App router"]
  CitizenShell["CitizenShell (tabs, bottom nav)"]
  MapTab["MapTab route '/'"]
  FeedTab["Feed route '/feed'"]
  AlertsTab["Alerts route '/alerts'"]
  ProfileTab["Profile route '/profile'"]
  ReportRoute["Report route '/report'"]
  Wizard["SubmitReportForm wizard (WizardContainer)"]

  AppRouter --> CitizenShell
  CitizenShell --> MapTab
  CitizenShell --> FeedTab
  CitizenShell --> AlertsTab
  CitizenShell --> ProfileTab

  AppRouter --> ReportRoute
  ReportRoute --> Wizard

  subgraph Bottom_nav_visible
    CitizenShell
  end

  subgraph Bottom_nav_hidden
    ReportRoute
  end
Loading

File-Level Changes

Change Details Files
Refactor report submission into a 3-step wizard with accumulated form state and a dedicated submission panel that auto-starts submission and routes to status sheets.
  • Replace FormCollector with WizardContainer that tracks step 1–3 and accumulates step1/step2 data in local state before draft creation.
  • Add step transition handlers (next/back) and a Step3 submit handler that constructs the draft payload from prior step data, including optional photo and location metadata.
  • Create SubmissionPanel that uses useSubmissionMachine, auto-calls machine.submit() on mount, and renders RevealSheet for success, queued, retryable, and terminal failure states instead of raw banners or SMS fallback.
apps/citizen-pwa/src/components/SubmitReportForm/index.tsx
Improve GPS handling and add reporter info auto-fill with persistence across sessions in step 2 of the wizard.
  • Guard GPS usage when navigator.geolocation is unavailable and fall back to manual location entry with a clear error message.
  • Map GeolocationPositionError codes to specific user-facing error messages for permission denied and timeout, defaulting to a generic manual-choice message otherwise.
  • On mount, read reporter name and phone from localStorage, pre-fill the form, and show a 'Pre-filled from your last report' hint; on continue, validate and persist the current reporter name and phone back to localStorage.
apps/citizen-pwa/src/components/SubmitReportForm/Step2WhoWhere.tsx
Update navigation UI to use consistent iconography and adjust routing to point /report directly at the new wizard without the shell or legacy route.
  • Change CitizenShell bottom-nav tabs to use lucide-react icons instead of emoji, styling them based on the active route.
  • Route /report to the new SubmitReportForm wizard component with hideBottomNav: true and remove the legacy /report/new route and shell-wrapped report path.
  • Delete the old SubmitReportForm.tsx implementation that wired the legacy form into CitizenShell.
apps/citizen-pwa/src/components/CitizenShell.tsx
apps/citizen-pwa/src/routes.tsx
apps/citizen-pwa/src/components/SubmitReportForm.tsx
Align tests and documentation with the new report wizard entry point and behavior.
  • Update App.routes.test.tsx to mock the SubmitReportForm/index module, rename the /report test to reference the form, and drop the expectation that the report tab is the active shell tab.
  • Add a progress.md entry describing the 3-step wizard implementation, routing consolidation, and verification steps for typecheck, lint, and tests.
apps/citizen-pwa/src/App.routes.test.tsx
docs/progress.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

Warning

Rate limit exceeded

@Exc1D has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 5 minutes and 20 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 5 minutes and 20 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 320dab1e-0516-4086-87f7-91308abf5079

📥 Commits

Reviewing files that changed from the base of the PR and between 6ff6a20 and bfbf9e3.

📒 Files selected for processing (3)
  • apps/citizen-pwa/src/App.routes.test.tsx
  • apps/citizen-pwa/src/components/SubmitReportForm/Step2WhoWhere.tsx
  • apps/citizen-pwa/src/components/SubmitReportForm/index.tsx
📝 Walkthrough

Walkthrough

Refactors the single-page SubmitReportForm into a 3-step wizard with localStorage reporter persistence, replaces emoji nav icons with lucide-react icons, consolidates /report routing to the new wizard, removes the legacy SubmitReportForm implementation, and updates tests and submission UI to use a RevealSheet-driven state machine.

Changes

Cohort / File(s) Summary
Form Wizard Refactor
apps/citizen-pwa/src/components/SubmitReportForm/index.tsx, apps/citizen-pwa/src/components/SubmitReportForm/Step2WhoWhere.tsx
Replaces single-page form with 3-step wizard (Step1Evidence → Step2WhoWhere → Step3Review); adds localStorage pre-fill for reporter name/MSISDN; enhances GPS error mapping and manual fallback; auto-triggers submission via state machine; swaps SMS fallback for RevealSheet outcomes.
Removed Legacy Form
apps/citizen-pwa/src/components/SubmitReportForm.tsx
Deletes original SubmitReportForm implementation including form state, geolocation handling, Firebase/upload/firestore submission flow, local persistence, and navigation to receipt.
Routing Consolidation
apps/citizen-pwa/src/routes.tsx
Removes legacy /report and /report/new routes and points /report to the new SubmitReportForm index implementation.
Navigation Icon System
apps/citizen-pwa/src/components/CitizenShell.tsx
Replaces hardcoded emoji icons with lucide-react Icon components; applies dynamic stroke width and color based on active tab using CSS variables.
Tests Updated
apps/citizen-pwa/src/App.routes.test.tsx
Updates mock path to ./components/SubmitReportForm/index.js and adjusts /report route test (removes aria-current="page" assertion, renames expectation).
Styles
apps/citizen-pwa/src/styles/globals.css
Adds .field-group--urgent CSS class for visually marking urgent field groups.
Docs
docs/progress.md
Adds progress entry documenting the 3-step wizard refactor, route/test updates, RevealSheet submission changes, and verification counts.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant Wizard as WizardContainer
    participant Step1 as Step1Evidence
    participant Step2 as Step2WhoWhere
    participant Step3 as Step3Review
    participant Submission as SubmissionPanel
    participant Reveal as RevealSheet

    User->>Wizard: open /report
    Wizard->>Step1: render evidence UI
    User->>Step1: provide evidence, next
    Step1->>Step2: pass step1 data
    Step2->>Step2: load localStorage (name, msisdn)
    User->>Step2: enter reporter info, handle GPS
    Step2->>Step3: pass step2 data
    User->>Step3: confirm submission
    Step3->>Submission: finalize draft
    Submission->>Submission: useEffect auto-submit()
    Submission->>Reveal: update outcome (confirmed/queued/failed)
    Reveal->>User: show result and redirect options
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 Three hops from one to three, I mend the trail,
Emoji traded for lucide detail,
Names remembered in the nest I keep,
GPS winks, or we choose manual leap,
A wizard hums — the report takes flight. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: introducing a 3-step report wizard, fixing GPS issues, and adding auto-fill functionality—all primary objectives reflected in the file modifications.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/3-step-wizard-wiring

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • In SubmissionPanel, the failed_terminal state renders RevealSheet with state="failed_retryable", which is likely a copy‑paste error and should probably use a distinct failed_terminal/failed state to avoid misleading messaging.
  • When creating the draft you pass barangay: formData.step2.municipalityLabel ?? '', which looks semantically mismatched; consider adding a dedicated municipality field to the payload or wiring the correct barangay label/id into barangay to avoid confusing downstream consumers.
  • The new localStorage usage in Step2WhoWhere runs unguarded in a useEffect; to make this more robust, wrap the gets/sets in a try/catch (and/or check for typeof window !== 'undefined') to avoid runtime errors when storage is unavailable or in non‑browser environments.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `SubmissionPanel`, the `failed_terminal` state renders `RevealSheet` with `state="failed_retryable"`, which is likely a copy‑paste error and should probably use a distinct `failed_terminal`/`failed` state to avoid misleading messaging.
- When creating the draft you pass `barangay: formData.step2.municipalityLabel ?? ''`, which looks semantically mismatched; consider adding a dedicated municipality field to the payload or wiring the correct barangay label/id into `barangay` to avoid confusing downstream consumers.
- The new `localStorage` usage in `Step2WhoWhere` runs unguarded in a `useEffect`; to make this more robust, wrap the gets/sets in a try/catch (and/or check for `typeof window !== 'undefined'`) to avoid runtime errors when storage is unavailable or in non‑browser environments.

## Individual Comments

### Comment 1
<location path="apps/citizen-pwa/src/components/SubmitReportForm/index.tsx" line_range="75-84" />
<code_context>
+      const msisdnHash = await hashPhone(formData.step2.reporterMsisdn)
</code_context>
<issue_to_address>
**issue:** Guard `hashPhone` against empty or optional phone numbers to avoid unnecessary errors.

The old flow only set `reporterMsisdnHash` when a valid phone was present; now `hashPhone` runs unconditionally on `formData.step2.reporterMsisdn`. If `hashPhone` expects a non-empty MSISDN, an empty/optional value could throw and block submission. Either enforce `reporterMsisdn` as required and validated earlier, or guard the call as before:

```ts
const msisdnHash = formData.step2.reporterMsisdn
  ? await hashPhone(formData.step2.reporterMsisdn)
  : undefined

// ...
...(msisdnHash ? { reporterMsisdnHash: msisdnHash } : {}),
```
</issue_to_address>

### Comment 2
<location path="apps/citizen-pwa/src/components/SubmitReportForm/index.tsx" line_range="83-84" />
<code_context>
-        severity,
-        location: { lat, lng },
-        ...(phone && smsConsent ? { reporterMsisdnHash: await hashPhone(phone) } : {}),
+        reportType: formData.step1.reportType as ReportType,
+        barangay: formData.step2.municipalityLabel ?? '',
+        description:
+          formData.step2.patientCount > 0 ? `Patients: ${String(formData.step2.patientCount)}` : '',
</code_context>
<issue_to_address>
**question (bug_risk):** Double-check that `barangay` is meant to contain the municipality label rather than a barangay label.

The new draft sets `barangay` from `step2.municipalityLabel`, which inverts the usual hierarchy (barangay is more granular than municipality). Since `Step2Data` already distinguishes `barangayId` and `municipalityLabel`, this risks overloading the `barangay` field and degrading data quality if consumers expect an actual barangay name. Consider either adding a separate `municipality`/`municipalityLabel` field to the payload, or populating `barangay` with a barangay label and keeping municipality distinct.
</issue_to_address>

### Comment 3
<location path="apps/citizen-pwa/src/components/SubmitReportForm/Step2WhoWhere.tsx" line_range="414-415" />
<code_context>
       }
     }

+    localStorage.setItem('bantayog.reporter.name', reporterName)
+    localStorage.setItem('bantayog.reporter.msisdn', reporterMsisdn)
+
     onNext({
</code_context>
<issue_to_address>
**🚨 question (security):** Persisting reporter name and phone in `localStorage` has privacy and security implications.

This PII will persist indefinitely in the browser and be readable by any script on this origin. That may be fine, but the tradeoff should be explicit. Depending on your privacy/threat model, consider instead:

- Storing a hashed or partially masked MSISDN,
- Making this a user opt‑in (“remember my details”), and/or
- Using shorter-lived storage (e.g. sessionStorage) if long-term persistence isn’t required.

If you keep this behavior, a clear UX indication and opt‑out would help avoid privacy surprises.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread apps/citizen-pwa/src/components/SubmitReportForm/index.tsx Outdated
Comment thread apps/citizen-pwa/src/components/SubmitReportForm/index.tsx Outdated
Comment thread apps/citizen-pwa/src/components/SubmitReportForm/Step2WhoWhere.tsx Outdated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/citizen-pwa/src/App.routes.test.tsx`:
- Around line 44-47: The test "shows the report form at /report" currently only
asserts the form mounts; update it to also assert that the CitizenShell UI is
not present by adding a negative assertion after renderAppAt('/report') — e.g.,
use screen.queryByText or queryByRole to confirm elements rendered by
CitizenShell (banner title, bottom nav label, or the component name) are
null/absent; locate the test case in App.routes.test.tsx and modify the
it('shows the report form at /report', ...) block to include the negative
assertion so the route fails if CitizenShell is reintroduced.

In `@apps/citizen-pwa/src/components/SubmitReportForm/index.tsx`:
- Around line 190-194: The useEffect auto-submit calls machine.submit() on mount
which can run twice in React Strict Mode and bypass the machine's state !==
'idle' guard due to stale closure, causing duplicate Firebase writes; fix by
adding an idempotency guard in the component (e.g., a ref like
hasAutoSubmittedRef or checking draft.syncing/draft.id) and consult it inside
the useEffect before calling machine.submit(), or augment machine.submit() to be
idempotent by early-returning when the draft id is already syncing; reference
useEffect, machine.submit, state !== 'idle', and the draft/syncing flag when
implementing the guard.

In `@apps/citizen-pwa/src/components/SubmitReportForm/Step2WhoWhere.tsx`:
- Around line 414-415: Do not persist raw reporter PII indefinitely: remove the
long-lived localStorage write for reporterMsisdn (the 'bantayog.reporter.msisdn'
setItem) so the phone number is never stored in persistent browser storage, and
for reporterName either switch to sessionStorage or implement a retention TTL by
storing a companion timestamp key (e.g., 'bantayog.reporter.name.timestamp') and
clear the name when expired; update the Step2WhoWhere component to only use
ephemeral state/sessionStorage for PII and add logic to clear keys after the TTL
or on explicit user opt-out.
- Around line 369-378: The useEffect that reads localStorage (calling
localStorage.getItem) can throw in private/blocked modes; wrap those reads in a
try/catch and degrade gracefully by not setting persistent values (i.e., still
call setReporterName/setReporterMsisdn/setHasMemory only when reads succeed),
and similarly wrap any writes performed on the "Continue" path so they don't
throw (catch SecurityError/QuotaExceededError and skip persisting). Update the
effect that calls localStorage.getItem and the Continue-path code that writes to
localStorage so both use try/catch, avoid crashing, and fall back to
non-persistent behavior while still updating component state (functions:
setReporterName, setReporterMsisdn, setHasMemory).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d24b8675-7d14-4e4f-8442-fda1110c7397

📥 Commits

Reviewing files that changed from the base of the PR and between 15a233a and bd4ab27.

📒 Files selected for processing (7)
  • apps/citizen-pwa/src/App.routes.test.tsx
  • apps/citizen-pwa/src/components/CitizenShell.tsx
  • apps/citizen-pwa/src/components/SubmitReportForm.tsx
  • apps/citizen-pwa/src/components/SubmitReportForm/Step2WhoWhere.tsx
  • apps/citizen-pwa/src/components/SubmitReportForm/index.tsx
  • apps/citizen-pwa/src/routes.tsx
  • docs/progress.md
💤 Files with no reviewable changes (1)
  • apps/citizen-pwa/src/components/SubmitReportForm.tsx

Comment thread apps/citizen-pwa/src/App.routes.test.tsx
Comment thread apps/citizen-pwa/src/components/SubmitReportForm/index.tsx Outdated
Comment thread apps/citizen-pwa/src/components/SubmitReportForm/Step2WhoWhere.tsx
Comment thread apps/citizen-pwa/src/components/SubmitReportForm/Step2WhoWhere.tsx Outdated
claude and others added 2 commits April 23, 2026 09:22
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
apps/citizen-pwa/src/components/SubmitReportForm/Step2WhoWhere.tsx (2)

369-378: ⚠️ Potential issue | 🟠 Major

Guard browser-storage access on both prefill and persist paths.

Line 370-Line 371 and Line 414-Line 415 call localStorage directly; this can throw (SecurityError, quota errors) and crash Step 2 in restricted environments.

Proposed defensive fix
  useEffect(() => {
-    const savedName = localStorage.getItem('bantayog.reporter.name')
-    const savedMsisdn = localStorage.getItem('bantayog.reporter.msisdn')
-    if (savedName || savedMsisdn) {
-      // eslint-disable-next-line react-hooks/set-state-in-effect
-      if (savedName) setReporterName(savedName)
-      if (savedMsisdn) setReporterMsisdn(savedMsisdn)
-      setHasMemory(true)
-    }
+    try {
+      const savedName = localStorage.getItem('bantayog.reporter.name')
+      const savedMsisdn = localStorage.getItem('bantayog.reporter.msisdn')
+      if (savedName || savedMsisdn) {
+        // eslint-disable-next-line react-hooks/set-state-in-effect
+        if (savedName) setReporterName(savedName)
+        if (savedMsisdn) setReporterMsisdn(savedMsisdn)
+        setHasMemory(true)
+      }
+    } catch (err) {
+      console.warn('[Step2WhoWhere] localStorage read skipped:', err)
+    }
   }, [])
@@
-    localStorage.setItem('bantayog.reporter.name', reporterName)
-    localStorage.setItem('bantayog.reporter.msisdn', reporterMsisdn)
+    try {
+      localStorage.setItem('bantayog.reporter.name', reporterName)
+      localStorage.setItem('bantayog.reporter.msisdn', reporterMsisdn)
+    } catch (err) {
+      console.warn('[Step2WhoWhere] localStorage write skipped:', err)
+    }

As per coding guidelines, "Defensive Programming: Assume external input is malicious/broken. Validate at the boundary. Never swallow errors with an empty catch block."

Also applies to: 414-416

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/citizen-pwa/src/components/SubmitReportForm/Step2WhoWhere.tsx` around
lines 369 - 378, The component Step2WhoWhere directly accesses localStorage in
the effect that prefills reporterName/reporterMsisdn (where setReporterName,
setReporterMsisdn, setHasMemory are used) and in the persist path, which can
throw in restricted environments; wrap both reads and writes to
localStorage.getItem/setItem in try/catch, validate returned values (e.g.,
ensure they are strings/non-empty) before calling
setReporterName/setReporterMsisdn, and on error fall back to not setting state
and optionally log the error (do not use an empty catch). Remove the
eslint-disable comment and ensure all localStorage usage in this component is
guarded this way.

414-415: ⚠️ Potential issue | 🟠 Major

Avoid indefinite persistence of raw reporter PII in localStorage.

Saving name + phone in long-lived browser storage is a privacy/compliance risk. At minimum, add retention/expiry and avoid persistent storage for raw MSISDN.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/citizen-pwa/src/components/SubmitReportForm/Step2WhoWhere.tsx` around
lines 414 - 415, The code in Step2WhoWhere.tsx currently writes raw PII into
localStorage via localStorage.setItem('bantayog.reporter.name', reporterName)
and localStorage.setItem('bantayog.reporter.msisdn', reporterMsisdn); change
this to avoid indefinite persistence by (1) not storing raw MSISDN—store a
hashed value or only non-sensitive derivative (e.g., last 4 digits) under a
different key, (2) include an expiry timestamp alongside any stored reporter
data and ensure the component (Step2WhoWhere) clears/ignores the entry when
expired, and (3) prefer sessionStorage or in-memory state for short-lived data;
update code paths that read those keys to handle the new format (value+expiry or
hashed/partial MSISDN) and to delete the storage when expired or after
submission.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@apps/citizen-pwa/src/components/SubmitReportForm/Step2WhoWhere.tsx`:
- Around line 369-378: The component Step2WhoWhere directly accesses
localStorage in the effect that prefills reporterName/reporterMsisdn (where
setReporterName, setReporterMsisdn, setHasMemory are used) and in the persist
path, which can throw in restricted environments; wrap both reads and writes to
localStorage.getItem/setItem in try/catch, validate returned values (e.g.,
ensure they are strings/non-empty) before calling
setReporterName/setReporterMsisdn, and on error fall back to not setting state
and optionally log the error (do not use an empty catch). Remove the
eslint-disable comment and ensure all localStorage usage in this component is
guarded this way.
- Around line 414-415: The code in Step2WhoWhere.tsx currently writes raw PII
into localStorage via localStorage.setItem('bantayog.reporter.name',
reporterName) and localStorage.setItem('bantayog.reporter.msisdn',
reporterMsisdn); change this to avoid indefinite persistence by (1) not storing
raw MSISDN—store a hashed value or only non-sensitive derivative (e.g., last 4
digits) under a different key, (2) include an expiry timestamp alongside any
stored reporter data and ensure the component (Step2WhoWhere) clears/ignores the
entry when expired, and (3) prefer sessionStorage or in-memory state for
short-lived data; update code paths that read those keys to handle the new
format (value+expiry or hashed/partial MSISDN) and to delete the storage when
expired or after submission.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 96891b79-bd9f-47ef-bb45-e8f0038428f8

📥 Commits

Reviewing files that changed from the base of the PR and between bd4ab27 and 6ff6a20.

📒 Files selected for processing (2)
  • apps/citizen-pwa/src/components/SubmitReportForm/Step2WhoWhere.tsx
  • apps/citizen-pwa/src/styles/globals.css

- barangay field: use barangayId (name) ?? municipalityLabel ?? '' — fixes
  semantic inversion where municipality was stored as barangay
- hashPhone: guard against empty MSISDN (defensive; field is required in Step 2)
- Strict Mode double-submit: add hasAutoSubmittedRef guard in SubmissionPanel
- localStorage: wrap reads and writes in try/catch for restricted/private mode
- MSISDN storage: move to sessionStorage to avoid long-lived PII in localStorage;
  name stays in localStorage (lower sensitivity)
- Test: assert /report renders without CitizenShell banner/nav
- Comment: clarify failed_terminal → RevealSheet state="failed_retryable" is intentional

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants