From e38dcde9f30cc4843958c8cdc49704976602f8aa Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 23 Apr 2026 20:39:20 +0800 Subject: [PATCH 01/31] refactor(citizen-pwa): extract useGpsLocation hook from Step2WhoWhere --- .../src/hooks/useGpsLocation.test.ts | 167 ++++++++++++++++++ apps/citizen-pwa/src/hooks/useGpsLocation.ts | 81 +++++++++ 2 files changed, 248 insertions(+) create mode 100644 apps/citizen-pwa/src/hooks/useGpsLocation.test.ts create mode 100644 apps/citizen-pwa/src/hooks/useGpsLocation.ts diff --git a/apps/citizen-pwa/src/hooks/useGpsLocation.test.ts b/apps/citizen-pwa/src/hooks/useGpsLocation.test.ts new file mode 100644 index 00000000..b53ec6df --- /dev/null +++ b/apps/citizen-pwa/src/hooks/useGpsLocation.test.ts @@ -0,0 +1,167 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' +import { renderHook, waitFor, act } from '@testing-library/react' + +import { useGpsLocation } from './useGpsLocation.js' + +describe('useGpsLocation', () => { + let mockGetCurrentPosition: ReturnType + + beforeEach(() => { + mockGetCurrentPosition = vi.fn() + Object.defineProperty(globalThis.navigator, 'geolocation', { + value: { getCurrentPosition: mockGetCurrentPosition }, + configurable: true, + writable: true, + }) + vi.spyOn(console, 'error').mockImplementation(() => { + return void 0 + }) + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + it('starts with gpsLoading false and null state', () => { + const { result } = renderHook(() => useGpsLocation()) + expect(result.current.gpsLoading).toBe(false) + expect(result.current.location).toBeNull() + expect(result.current.locationMethod).toBeNull() + expect(result.current.locationError).toBeNull() + }) + + it('sets gpsLoading true during attempt', () => { + mockGetCurrentPosition.mockImplementation(() => { + // intentionally never resolve so loading stays true + }) + const { result } = renderHook(() => useGpsLocation()) + act(() => { + void result.current.attemptGps() + }) + expect(result.current.gpsLoading).toBe(true) + }) + + it('resolves with location on successful GPS', async () => { + mockGetCurrentPosition.mockImplementation((success: PositionCallback) => { + success({ + coords: { latitude: 14.1, longitude: 122.9 }, + } as GeolocationPosition) + }) + const { result } = renderHook(() => useGpsLocation()) + await act(async () => { + await result.current.attemptGps() + }) + expect(result.current.location).toEqual({ lat: 14.1, lng: 122.9 }) + expect(result.current.locationMethod).toBe('gps') + expect(result.current.gpsLoading).toBe(false) + expect(result.current.locationError).toBeNull() + }) + + it('handles permission denied (code 1)', async () => { + mockGetCurrentPosition.mockImplementation( + (_success: PositionCallback, error: PositionErrorCallback) => { + error({ code: 1 } as GeolocationPositionError) + }, + ) + const { result } = renderHook(() => useGpsLocation()) + await act(async () => { + await result.current.attemptGps() + }) + expect(result.current.locationError).toBe( + 'Location access denied. Choose municipality manually.', + ) + expect(result.current.locationMethod).toBe('manual') + expect(result.current.gpsLoading).toBe(false) + }) + + it('handles timeout (code 3)', async () => { + mockGetCurrentPosition.mockImplementation( + (_success: PositionCallback, error: PositionErrorCallback) => { + error({ code: 3 } as GeolocationPositionError) + }, + ) + const { result } = renderHook(() => useGpsLocation()) + await act(async () => { + await result.current.attemptGps() + }) + expect(result.current.locationError).toBe('Location timed out. Choose municipality manually.') + expect(result.current.locationMethod).toBe('manual') + expect(result.current.gpsLoading).toBe(false) + }) + + it('resetGps clears location, method, and error', async () => { + mockGetCurrentPosition.mockImplementation((success: PositionCallback) => { + success({ + coords: { latitude: 14.1, longitude: 122.9 }, + } as GeolocationPosition) + }) + const { result } = renderHook(() => useGpsLocation()) + await act(async () => { + await result.current.attemptGps() + }) + expect(result.current.location).not.toBeNull() + + act(() => { + result.current.resetGps() + }) + expect(result.current.location).toBeNull() + expect(result.current.locationMethod).toBeNull() + expect(result.current.locationError).toBeNull() + }) + + it('auto-attempts on mount when enabled', async () => { + mockGetCurrentPosition.mockImplementation((success: PositionCallback) => { + success({ + coords: { latitude: 14.1, longitude: 122.9 }, + } as GeolocationPosition) + }) + const { result } = renderHook(() => useGpsLocation(true)) + await waitFor(() => { + expect(result.current.location).not.toBeNull() + }) + expect(result.current.location).toEqual({ lat: 14.1, lng: 122.9 }) + expect(result.current.locationMethod).toBe('gps') + expect(result.current.gpsLoading).toBe(false) + }) + + it('does not auto-attempt on mount when disabled', () => { + mockGetCurrentPosition.mockImplementation((success: PositionCallback) => { + success({ + coords: { latitude: 14.1, longitude: 122.9 }, + } as GeolocationPosition) + }) + const { result } = renderHook(() => useGpsLocation(false)) + expect(result.current.location).toBeNull() + expect(result.current.gpsLoading).toBe(false) + expect(mockGetCurrentPosition).not.toHaveBeenCalled() + }) + + it('handles missing navigator.geolocation', async () => { + Object.defineProperty(globalThis.navigator, 'geolocation', { + value: undefined, + configurable: true, + writable: true, + }) + const { result } = renderHook(() => useGpsLocation()) + await act(async () => { + await result.current.attemptGps() + }) + expect(result.current.locationError).toBe('GPS not supported on this device.') + expect(result.current.locationMethod).toBe('manual') + expect(result.current.gpsLoading).toBe(false) + }) + + it('logs error to console on failure', async () => { + const fakeErr = new Error('boom') + mockGetCurrentPosition.mockImplementation( + (_success: PositionCallback, error: PositionErrorCallback) => { + error(fakeErr as unknown as GeolocationPositionError) + }, + ) + const { result } = renderHook(() => useGpsLocation()) + await act(async () => { + await result.current.attemptGps() + }) + expect(console.error).toHaveBeenCalledWith('[useGpsLocation] attemptGps failed:', fakeErr) + }) +}) diff --git a/apps/citizen-pwa/src/hooks/useGpsLocation.ts b/apps/citizen-pwa/src/hooks/useGpsLocation.ts new file mode 100644 index 00000000..1224e5d1 --- /dev/null +++ b/apps/citizen-pwa/src/hooks/useGpsLocation.ts @@ -0,0 +1,81 @@ +import { useState, useEffect, useCallback } from 'react' + +export interface UseGpsLocationResult { + location: { lat: number; lng: number } | null + locationMethod: 'gps' | 'manual' | null + gpsLoading: boolean + locationError: string | null + attemptGps: () => Promise + resetGps: () => void +} + +/** + * GPS location acquisition with fallback to manual entry. + * + * - `location` / `locationMethod` — set when GPS resolves successfully + * - `gpsLoading` — true while waiting for navigator.geolocation + * - `locationError` — human-readable message on failure; triggers manual mode + * - `attemptGps()` — retry GPS on demand + * - `resetGps()` — clear location state (e.g. when user switches input method) + */ +export function useGpsLocation(autoAttemptOnMount = false): UseGpsLocationResult { + const [location, setLocation] = useState<{ lat: number; lng: number } | null>(null) + const [locationMethod, setLocationMethod] = useState<'gps' | 'manual' | null>(null) + const [gpsLoading, setGpsLoading] = useState(false) + const [locationError, setLocationError] = useState(null) + + const attemptGps = useCallback(async () => { + setLocationError(null) + setGpsLoading(true) + try { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (!navigator.geolocation) { + setLocationError('GPS not supported on this device.') + setLocationMethod('manual') + return + } + const pos = await new Promise((resolve, reject) => { + navigator.geolocation.getCurrentPosition(resolve, reject, { + enableHighAccuracy: true, + timeout: 10000, + }) + }) + setLocation({ lat: pos.coords.latitude, lng: pos.coords.longitude }) + setLocationMethod('gps') + } catch (err: unknown) { + console.error('[useGpsLocation] attemptGps failed:', err) + let msg = 'Could not get location. Choose municipality manually.' + if (err && typeof err === 'object' && 'code' in err) { + const code = (err as GeolocationPositionError).code + if (code === 1) msg = 'Location access denied. Choose municipality manually.' + else if (code === 3) msg = 'Location timed out. Choose municipality manually.' + } + setLocationError(msg) + setLocationMethod('manual') + } finally { + setGpsLoading(false) + } + }, []) + + const resetGps = useCallback(() => { + setLocation(null) + setLocationMethod(null) + setLocationError(null) + }, []) + + useEffect(() => { + if (autoAttemptOnMount) { + // eslint-disable-next-line react-hooks/set-state-in-effect + void attemptGps() + } + }, [autoAttemptOnMount, attemptGps]) + + return { + location, + locationMethod, + gpsLoading, + locationError, + attemptGps, + resetGps, + } +} From eef40e9763c6ba0d77bbeca9289d82ffbc913fd8 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 23 Apr 2026 20:47:48 +0800 Subject: [PATCH 02/31] refactor(citizen-pwa): address review feedback on useGpsLocation hook --- .../src/hooks/useGpsLocation.test.ts | 20 +++++++++---------- apps/citizen-pwa/src/hooks/useGpsLocation.ts | 17 +++++++++------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/apps/citizen-pwa/src/hooks/useGpsLocation.test.ts b/apps/citizen-pwa/src/hooks/useGpsLocation.test.ts index b53ec6df..8e4e8653 100644 --- a/apps/citizen-pwa/src/hooks/useGpsLocation.test.ts +++ b/apps/citizen-pwa/src/hooks/useGpsLocation.test.ts @@ -22,15 +22,15 @@ describe('useGpsLocation', () => { vi.restoreAllMocks() }) - it('starts with gpsLoading false and null state', () => { + it('starts with isLoading false and null state', () => { const { result } = renderHook(() => useGpsLocation()) - expect(result.current.gpsLoading).toBe(false) + expect(result.current.isLoading).toBe(false) expect(result.current.location).toBeNull() expect(result.current.locationMethod).toBeNull() expect(result.current.locationError).toBeNull() }) - it('sets gpsLoading true during attempt', () => { + it('sets isLoading true during attempt', () => { mockGetCurrentPosition.mockImplementation(() => { // intentionally never resolve so loading stays true }) @@ -38,7 +38,7 @@ describe('useGpsLocation', () => { act(() => { void result.current.attemptGps() }) - expect(result.current.gpsLoading).toBe(true) + expect(result.current.isLoading).toBe(true) }) it('resolves with location on successful GPS', async () => { @@ -53,7 +53,7 @@ describe('useGpsLocation', () => { }) expect(result.current.location).toEqual({ lat: 14.1, lng: 122.9 }) expect(result.current.locationMethod).toBe('gps') - expect(result.current.gpsLoading).toBe(false) + expect(result.current.isLoading).toBe(false) expect(result.current.locationError).toBeNull() }) @@ -71,7 +71,7 @@ describe('useGpsLocation', () => { 'Location access denied. Choose municipality manually.', ) expect(result.current.locationMethod).toBe('manual') - expect(result.current.gpsLoading).toBe(false) + expect(result.current.isLoading).toBe(false) }) it('handles timeout (code 3)', async () => { @@ -86,7 +86,7 @@ describe('useGpsLocation', () => { }) expect(result.current.locationError).toBe('Location timed out. Choose municipality manually.') expect(result.current.locationMethod).toBe('manual') - expect(result.current.gpsLoading).toBe(false) + expect(result.current.isLoading).toBe(false) }) it('resetGps clears location, method, and error', async () => { @@ -121,7 +121,7 @@ describe('useGpsLocation', () => { }) expect(result.current.location).toEqual({ lat: 14.1, lng: 122.9 }) expect(result.current.locationMethod).toBe('gps') - expect(result.current.gpsLoading).toBe(false) + expect(result.current.isLoading).toBe(false) }) it('does not auto-attempt on mount when disabled', () => { @@ -132,7 +132,7 @@ describe('useGpsLocation', () => { }) const { result } = renderHook(() => useGpsLocation(false)) expect(result.current.location).toBeNull() - expect(result.current.gpsLoading).toBe(false) + expect(result.current.isLoading).toBe(false) expect(mockGetCurrentPosition).not.toHaveBeenCalled() }) @@ -148,7 +148,7 @@ describe('useGpsLocation', () => { }) expect(result.current.locationError).toBe('GPS not supported on this device.') expect(result.current.locationMethod).toBe('manual') - expect(result.current.gpsLoading).toBe(false) + expect(result.current.isLoading).toBe(false) }) it('logs error to console on failure', async () => { diff --git a/apps/citizen-pwa/src/hooks/useGpsLocation.ts b/apps/citizen-pwa/src/hooks/useGpsLocation.ts index 1224e5d1..404c3272 100644 --- a/apps/citizen-pwa/src/hooks/useGpsLocation.ts +++ b/apps/citizen-pwa/src/hooks/useGpsLocation.ts @@ -1,9 +1,11 @@ import { useState, useEffect, useCallback } from 'react' +const GPS_TIMEOUT_MS = 10_000 + export interface UseGpsLocationResult { location: { lat: number; lng: number } | null locationMethod: 'gps' | 'manual' | null - gpsLoading: boolean + isLoading: boolean locationError: string | null attemptGps: () => Promise resetGps: () => void @@ -13,7 +15,7 @@ export interface UseGpsLocationResult { * GPS location acquisition with fallback to manual entry. * * - `location` / `locationMethod` — set when GPS resolves successfully - * - `gpsLoading` — true while waiting for navigator.geolocation + * - `isLoading` — true while waiting for navigator.geolocation * - `locationError` — human-readable message on failure; triggers manual mode * - `attemptGps()` — retry GPS on demand * - `resetGps()` — clear location state (e.g. when user switches input method) @@ -21,13 +23,14 @@ export interface UseGpsLocationResult { export function useGpsLocation(autoAttemptOnMount = false): UseGpsLocationResult { const [location, setLocation] = useState<{ lat: number; lng: number } | null>(null) const [locationMethod, setLocationMethod] = useState<'gps' | 'manual' | null>(null) - const [gpsLoading, setGpsLoading] = useState(false) + const [isLoading, setIsLoading] = useState(false) const [locationError, setLocationError] = useState(null) const attemptGps = useCallback(async () => { setLocationError(null) - setGpsLoading(true) + setIsLoading(true) try { + // Some WebViews and older browsers lack the geolocation API // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!navigator.geolocation) { setLocationError('GPS not supported on this device.') @@ -37,7 +40,7 @@ export function useGpsLocation(autoAttemptOnMount = false): UseGpsLocationResult const pos = await new Promise((resolve, reject) => { navigator.geolocation.getCurrentPosition(resolve, reject, { enableHighAccuracy: true, - timeout: 10000, + timeout: GPS_TIMEOUT_MS, }) }) setLocation({ lat: pos.coords.latitude, lng: pos.coords.longitude }) @@ -53,7 +56,7 @@ export function useGpsLocation(autoAttemptOnMount = false): UseGpsLocationResult setLocationError(msg) setLocationMethod('manual') } finally { - setGpsLoading(false) + setIsLoading(false) } }, []) @@ -73,7 +76,7 @@ export function useGpsLocation(autoAttemptOnMount = false): UseGpsLocationResult return { location, locationMethod, - gpsLoading, + isLoading, locationError, attemptGps, resetGps, From 19132c0ad45a7838c837eb124585ac4c5833d6fd Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 23 Apr 2026 20:56:58 +0800 Subject: [PATCH 03/31] refactor(citizen-pwa): extract MunicipalitySelector and BarangaySelector from Step2WhoWhere --- .../BarangaySelector.test.tsx | 45 +++ .../SubmitReportForm/BarangaySelector.tsx | 44 +++ .../MunicipalitySelector.test.tsx | 51 +++ .../SubmitReportForm/MunicipalitySelector.tsx | 30 ++ .../SubmitReportForm/location-constants.ts | 294 ++++++++++++++++++ 5 files changed, 464 insertions(+) create mode 100644 apps/citizen-pwa/src/components/SubmitReportForm/BarangaySelector.test.tsx create mode 100644 apps/citizen-pwa/src/components/SubmitReportForm/BarangaySelector.tsx create mode 100644 apps/citizen-pwa/src/components/SubmitReportForm/MunicipalitySelector.test.tsx create mode 100644 apps/citizen-pwa/src/components/SubmitReportForm/MunicipalitySelector.tsx create mode 100644 apps/citizen-pwa/src/components/SubmitReportForm/location-constants.ts diff --git a/apps/citizen-pwa/src/components/SubmitReportForm/BarangaySelector.test.tsx b/apps/citizen-pwa/src/components/SubmitReportForm/BarangaySelector.test.tsx new file mode 100644 index 00000000..a93edd30 --- /dev/null +++ b/apps/citizen-pwa/src/components/SubmitReportForm/BarangaySelector.test.tsx @@ -0,0 +1,45 @@ +import { describe, it, expect, vi } from 'vitest' +import { fireEvent, render, screen } from '@testing-library/react' +import { BarangaySelector } from './BarangaySelector.js' + +describe('BarangaySelector', () => { + it('renders nothing when municipalityId is empty', () => { + const { container } = render() + + expect(container.firstChild).toBeNull() + }) + + it('renders barangay options for the given municipality', () => { + render() + + const select = screen.getByRole('combobox') + expect(select).toBeInTheDocument() + + const options = screen.getAllByRole('option') + expect(options[0]).toHaveTextContent('Select barangay (optional)...') + + // Daet has specific barangays — spot-check a couple + expect(screen.getByRole('option', { name: 'Alawihao' })).toBeInTheDocument() + expect(screen.getByRole('option', { name: 'Bagasbas' })).toBeInTheDocument() + }) + + it('calls onChange when selection changes', () => { + const onChange = vi.fn() + render() + + const select = screen.getByRole('combobox') + fireEvent.change(select, { target: { value: 'Bagasbas' } }) + + expect(onChange).toHaveBeenCalledExactlyOnceWith('Bagasbas') + }) + + it('shows optional label', () => { + render() + + expect( + screen.getByText((content, element) => { + return element?.tagName.toLowerCase() === 'span' && content.includes('optional') + }), + ).toBeInTheDocument() + }) +}) diff --git a/apps/citizen-pwa/src/components/SubmitReportForm/BarangaySelector.tsx b/apps/citizen-pwa/src/components/SubmitReportForm/BarangaySelector.tsx new file mode 100644 index 00000000..f03987ff --- /dev/null +++ b/apps/citizen-pwa/src/components/SubmitReportForm/BarangaySelector.tsx @@ -0,0 +1,44 @@ +import { useMemo } from 'react' +import { FALLBACK_BARANGAYS, MUNICIPALITY_LABELS } from './location-constants.js' + +interface BarangaySelectorProps { + municipalityId: string + value: string | undefined + onChange: (barangayId: string) => void +} + +export function BarangaySelector({ municipalityId, value, onChange }: BarangaySelectorProps) { + const barangayOptions = useMemo(() => { + if (!municipalityId) return [] + return FALLBACK_BARANGAYS.filter( + (b) => MUNICIPALITY_LABELS[municipalityId] === b.municipality, + ).sort((a, b) => a.name.localeCompare(b.name)) + }, [municipalityId]) + + if (!municipalityId) { + return null + } + + return ( +
+

+ Barangay + — optional +

+ +
+ ) +} diff --git a/apps/citizen-pwa/src/components/SubmitReportForm/MunicipalitySelector.test.tsx b/apps/citizen-pwa/src/components/SubmitReportForm/MunicipalitySelector.test.tsx new file mode 100644 index 00000000..2b0fbdd4 --- /dev/null +++ b/apps/citizen-pwa/src/components/SubmitReportForm/MunicipalitySelector.test.tsx @@ -0,0 +1,51 @@ +import { describe, it, expect, vi } from 'vitest' +import { fireEvent, render, screen } from '@testing-library/react' +import { MunicipalitySelector } from './MunicipalitySelector.js' +import { MUNI_LABELS_SORTED } from './location-constants.js' + +describe('MunicipalitySelector', () => { + it('renders with sorted municipality options', () => { + render() + + const select = screen.getByRole('combobox') + expect(select).toBeInTheDocument() + + // Should have default option + all municipalities + const options = screen.getAllByRole('option') + expect(options[0]).toHaveTextContent('Select municipality...') + expect(options).toHaveLength(1 + MUNI_LABELS_SORTED.length) + + // Verify sorted order: first should be Basud alphabetically + expect(options[1]).toHaveTextContent('Basud') + }) + + it('calls onChange when selection changes', () => { + const onChange = vi.fn() + render() + + const select = screen.getByRole('combobox') + fireEvent.change(select, { target: { value: 'daet' } }) + + expect(onChange).toHaveBeenCalledExactlyOnceWith('daet') + }) + + it('displays error message when provided', () => { + render( + , + ) + + expect(screen.getByText('Please select a municipality')).toBeInTheDocument() + }) + + it('does not display error when error is undefined', () => { + const { container } = render() + + expect(container.querySelector('.field-error')).not.toBeInTheDocument() + }) + + it('does not display error when error is null', () => { + const { container } = render() + + expect(container.querySelector('.field-error')).not.toBeInTheDocument() + }) +}) diff --git a/apps/citizen-pwa/src/components/SubmitReportForm/MunicipalitySelector.tsx b/apps/citizen-pwa/src/components/SubmitReportForm/MunicipalitySelector.tsx new file mode 100644 index 00000000..905518c9 --- /dev/null +++ b/apps/citizen-pwa/src/components/SubmitReportForm/MunicipalitySelector.tsx @@ -0,0 +1,30 @@ +import { MUNI_LABELS_SORTED } from './location-constants.js' + +interface MunicipalitySelectorProps { + value: string + onChange: (municipalityId: string) => void + error?: string | null +} + +export function MunicipalitySelector({ value, onChange, error }: MunicipalitySelectorProps) { + return ( +
+

Municipality

+ + {error ?

{error}

: null} +
+ ) +} diff --git a/apps/citizen-pwa/src/components/SubmitReportForm/location-constants.ts b/apps/citizen-pwa/src/components/SubmitReportForm/location-constants.ts new file mode 100644 index 00000000..887903c0 --- /dev/null +++ b/apps/citizen-pwa/src/components/SubmitReportForm/location-constants.ts @@ -0,0 +1,294 @@ +import { CAMARINES_NORTE_MUNICIPALITIES } from '@bantayog/shared-validators' + +export const FALLBACK_BARANGAYS: { name: string; municipality: string }[] = [ + { name: 'Angas', municipality: 'Basud' }, + { name: 'Bactas', municipality: 'Basud' }, + { name: 'Binatagan', municipality: 'Basud' }, + { name: 'Caayunan', municipality: 'Basud' }, + { name: 'Guinatungan', municipality: 'Basud' }, + { name: 'Hinampacan', municipality: 'Basud' }, + { name: 'Langa', municipality: 'Basud' }, + { name: 'Laniton', municipality: 'Basud' }, + { name: 'Lidong', municipality: 'Basud' }, + { name: 'Mampili', municipality: 'Basud' }, + { name: 'Mandazo', municipality: 'Basud' }, + { name: 'Mangcamagong', municipality: 'Basud' }, + { name: 'Manmuntay', municipality: 'Basud' }, + { name: 'Mantugawe', municipality: 'Basud' }, + { name: 'Matnog', municipality: 'Basud' }, + { name: 'Mocong', municipality: 'Basud' }, + { name: 'Oliva', municipality: 'Basud' }, + { name: 'Pagsangahan', municipality: 'Basud' }, + { name: 'Pinagwarasan', municipality: 'Basud' }, + { name: 'Plaridel', municipality: 'Basud' }, + { name: 'Poblacion 1', municipality: 'Basud' }, + { name: 'Poblacion 2', municipality: 'Basud' }, + { name: 'San Felipe', municipality: 'Basud' }, + { name: 'San Jose', municipality: 'Basud' }, + { name: 'San Pascual', municipality: 'Basud' }, + { name: 'Taba-taba', municipality: 'Basud' }, + { name: 'Tacad', municipality: 'Basud' }, + { name: 'Taisan', municipality: 'Basud' }, + { name: 'Tuaca', municipality: 'Basud' }, + { name: 'Alayao', municipality: 'Capalonga' }, + { name: 'Binawangan', municipality: 'Capalonga' }, + { name: 'Calabaca', municipality: 'Capalonga' }, + { name: 'Camagsaan', municipality: 'Capalonga' }, + { name: 'Catabaguangan', municipality: 'Capalonga' }, + { name: 'Catioan', municipality: 'Capalonga' }, + { name: 'Del Pilar', municipality: 'Capalonga' }, + { name: 'Itok', municipality: 'Capalonga' }, + { name: 'Lucbanan', municipality: 'Capalonga' }, + { name: 'Mabini', municipality: 'Capalonga' }, + { name: 'Mactang', municipality: 'Capalonga' }, + { name: 'Magsaysay', municipality: 'Capalonga' }, + { name: 'Mataque', municipality: 'Capalonga' }, + { name: 'Old Camp', municipality: 'Capalonga' }, + { name: 'Poblacion', municipality: 'Capalonga' }, + { name: 'San Antonio', municipality: 'Capalonga' }, + { name: 'San Isidro', municipality: 'Capalonga' }, + { name: 'San Roque', municipality: 'Capalonga' }, + { name: 'Tanawan', municipality: 'Capalonga' }, + { name: 'Ubang', municipality: 'Capalonga' }, + { name: 'Villa Aurora', municipality: 'Capalonga' }, + { name: 'Villa Belen', municipality: 'Capalonga' }, + { name: 'Alawihao', municipality: 'Daet' }, + { name: 'Awitan', municipality: 'Daet' }, + { name: 'Bagasbas', municipality: 'Daet' }, + { name: 'Barangay I', municipality: 'Daet' }, + { name: 'Barangay II', municipality: 'Daet' }, + { name: 'Barangay III', municipality: 'Daet' }, + { name: 'Barangay IV', municipality: 'Daet' }, + { name: 'Barangay V', municipality: 'Daet' }, + { name: 'Barangay VI', municipality: 'Daet' }, + { name: 'Barangay VII', municipality: 'Daet' }, + { name: 'Barangay VIII', municipality: 'Daet' }, + { name: 'Bibirao', municipality: 'Daet' }, + { name: 'Borabod', municipality: 'Daet' }, + { name: 'Calasgasan', municipality: 'Daet' }, + { name: 'Camambugan', municipality: 'Daet' }, + { name: 'Cobangbang', municipality: 'Daet' }, + { name: 'Dogongan', municipality: 'Daet' }, + { name: 'Gahonon', municipality: 'Daet' }, + { name: 'Gubat', municipality: 'Daet' }, + { name: 'Lag-on', municipality: 'Daet' }, + { name: 'Magang', municipality: 'Daet' }, + { name: 'Mambalite', municipality: 'Daet' }, + { name: 'Mancruz', municipality: 'Daet' }, + { name: 'Pamorangon', municipality: 'Daet' }, + { name: 'San Isidro', municipality: 'Daet' }, + { name: 'Bagong Bayan', municipality: 'Jose Panganiban' }, + { name: 'Calero', municipality: 'Jose Panganiban' }, + { name: 'Dahican', municipality: 'Jose Panganiban' }, + { name: 'Dayhagan', municipality: 'Jose Panganiban' }, + { name: 'Larap', municipality: 'Jose Panganiban' }, + { name: 'Luklukan Norte', municipality: 'Jose Panganiban' }, + { name: 'Luklukan Sur', municipality: 'Jose Panganiban' }, + { name: 'Motherlode', municipality: 'Jose Panganiban' }, + { name: 'Nakalaya', municipality: 'Jose Panganiban' }, + { name: 'North Poblacion', municipality: 'Jose Panganiban' }, + { name: 'Osmeña', municipality: 'Jose Panganiban' }, + { name: 'Pag-asa', municipality: 'Jose Panganiban' }, + { name: 'Parang', municipality: 'Jose Panganiban' }, + { name: 'Plaridel', municipality: 'Jose Panganiban' }, + { name: 'Salvacion', municipality: 'Jose Panganiban' }, + { name: 'San Isidro', municipality: 'Jose Panganiban' }, + { name: 'San Jose', municipality: 'Jose Panganiban' }, + { name: 'San Martin', municipality: 'Jose Panganiban' }, + { name: 'San Pedro', municipality: 'Jose Panganiban' }, + { name: 'San Rafael', municipality: 'Jose Panganiban' }, + { name: 'Santa Cruz', municipality: 'Jose Panganiban' }, + { name: 'Santa Elena', municipality: 'Jose Panganiban' }, + { name: 'Santa Milagrosa', municipality: 'Jose Panganiban' }, + { name: 'Santa Rosa Norte', municipality: 'Jose Panganiban' }, + { name: 'Santa Rosa Sur', municipality: 'Jose Panganiban' }, + { name: 'South Poblacion', municipality: 'Jose Panganiban' }, + { name: 'Tamisan', municipality: 'Jose Panganiban' }, + { name: 'Anahaw', municipality: 'Labo' }, + { name: 'Anameam', municipality: 'Labo' }, + { name: 'Awitan', municipality: 'Labo' }, + { name: 'Baay', municipality: 'Labo' }, + { name: 'Bagacay', municipality: 'Labo' }, + { name: 'Bagong Silang I', municipality: 'Labo' }, + { name: 'Bagong Silang II', municipality: 'Labo' }, + { name: 'Bagong Silang III', municipality: 'Labo' }, + { name: 'Bakiad', municipality: 'Labo' }, + { name: 'Bautista', municipality: 'Labo' }, + { name: 'Bayabas', municipality: 'Labo' }, + { name: 'Bayan-bayan', municipality: 'Labo' }, + { name: 'Benit', municipality: 'Labo' }, + { name: 'Bulhao', municipality: 'Labo' }, + { name: 'Cabatuhan', municipality: 'Labo' }, + { name: 'Cabusay', municipality: 'Labo' }, + { name: 'Calabasa', municipality: 'Labo' }, + { name: 'Canapawan', municipality: 'Labo' }, + { name: 'Daguit', municipality: 'Labo' }, + { name: 'Dalas', municipality: 'Labo' }, + { name: 'Dumagmang', municipality: 'Labo' }, + { name: 'Exciban', municipality: 'Labo' }, + { name: 'Fundado', municipality: 'Labo' }, + { name: 'Guinacutan', municipality: 'Labo' }, + { name: 'Guisican', municipality: 'Labo' }, + { name: 'Gumamela', municipality: 'Labo' }, + { name: 'Iberica', municipality: 'Labo' }, + { name: 'Kalamunding', municipality: 'Labo' }, + { name: 'Lugui', municipality: 'Labo' }, + { name: 'Mabilo I', municipality: 'Labo' }, + { name: 'Mabilo II', municipality: 'Labo' }, + { name: 'Macogon', municipality: 'Labo' }, + { name: 'Mahawan-hawan', municipality: 'Labo' }, + { name: 'Malangcao-Basud', municipality: 'Labo' }, + { name: 'Malasugui', municipality: 'Labo' }, + { name: 'Malatap', municipality: 'Labo' }, + { name: 'Malaya', municipality: 'Labo' }, + { name: 'Malibago', municipality: 'Labo' }, + { name: 'Maot', municipality: 'Labo' }, + { name: 'Masalong', municipality: 'Labo' }, + { name: 'Matanlang', municipality: 'Labo' }, + { name: 'Napaod', municipality: 'Labo' }, + { name: 'Pag-asa', municipality: 'Labo' }, + { name: 'Pangpang', municipality: 'Labo' }, + { name: 'Pinya', municipality: 'Labo' }, + { name: 'San Antonio', municipality: 'Labo' }, + { name: 'San Francisco', municipality: 'Labo' }, + { name: 'Santa Cruz', municipality: 'Labo' }, + { name: 'Submakin', municipality: 'Labo' }, + { name: 'Talobatib', municipality: 'Labo' }, + { name: 'Tigbinan', municipality: 'Labo' }, + { name: 'Tulay na Lupa', municipality: 'Labo' }, + { name: 'Apuao', municipality: 'Mercedes' }, + { name: 'Barangay I', municipality: 'Mercedes' }, + { name: 'Barangay II', municipality: 'Mercedes' }, + { name: 'Barangay III', municipality: 'Mercedes' }, + { name: 'Barangay IV', municipality: 'Mercedes' }, + { name: 'Barangay V', municipality: 'Mercedes' }, + { name: 'Barangay VI', municipality: 'Mercedes' }, + { name: 'Barangay VII', municipality: 'Mercedes' }, + { name: 'Caringo', municipality: 'Mercedes' }, + { name: 'Catandunganon', municipality: 'Mercedes' }, + { name: 'Cayucyucan', municipality: 'Mercedes' }, + { name: 'Colasi', municipality: 'Mercedes' }, + { name: 'Del Rosario', municipality: 'Mercedes' }, + { name: 'Gaboc', municipality: 'Mercedes' }, + { name: 'Hamoraon', municipality: 'Mercedes' }, + { name: 'Hinipaan', municipality: 'Mercedes' }, + { name: 'Lalawigan', municipality: 'Mercedes' }, + { name: 'Lanot', municipality: 'Mercedes' }, + { name: 'Mambungalon', municipality: 'Mercedes' }, + { name: 'Manguisoc', municipality: 'Mercedes' }, + { name: 'Masalongsalong', municipality: 'Mercedes' }, + { name: 'Matoogtoog', municipality: 'Mercedes' }, + { name: 'Pambuhan', municipality: 'Mercedes' }, + { name: 'Quinapaguian', municipality: 'Mercedes' }, + { name: 'San Roque', municipality: 'Mercedes' }, + { name: 'Tarum', municipality: 'Mercedes' }, + { name: 'Awitan', municipality: 'Paracale' }, + { name: 'Bagumbayan', municipality: 'Paracale' }, + { name: 'Bakal', municipality: 'Paracale' }, + { name: 'Batobalani', municipality: 'Paracale' }, + { name: 'Calaburnay', municipality: 'Paracale' }, + { name: 'Capacuan', municipality: 'Paracale' }, + { name: 'Casalugan', municipality: 'Paracale' }, + { name: 'Dagang', municipality: 'Paracale' }, + { name: 'Dalnac', municipality: 'Paracale' }, + { name: 'Dancalan', municipality: 'Paracale' }, + { name: 'Gumaus', municipality: 'Paracale' }, + { name: 'Labnig', municipality: 'Paracale' }, + { name: 'Macolabo Island', municipality: 'Paracale' }, + { name: 'Malacbang', municipality: 'Paracale' }, + { name: 'Malaguit', municipality: 'Paracale' }, + { name: 'Mampungo', municipality: 'Paracale' }, + { name: 'Mangkasay', municipality: 'Paracale' }, + { name: 'Maybato', municipality: 'Paracale' }, + { name: 'Palanas', municipality: 'Paracale' }, + { name: 'Pinagbirayan Malaki', municipality: 'Paracale' }, + { name: 'Pinagbirayan Munti', municipality: 'Paracale' }, + { name: 'Poblacion Norte', municipality: 'Paracale' }, + { name: 'Poblacion Sur', municipality: 'Paracale' }, + { name: 'Tabas', municipality: 'Paracale' }, + { name: 'Talusan', municipality: 'Paracale' }, + { name: 'Tawig', municipality: 'Paracale' }, + { name: 'Tugos', municipality: 'Paracale' }, + { name: 'Daculang Bolo', municipality: 'San Lorenzo Ruiz' }, + { name: 'Dagotdotan', municipality: 'San Lorenzo Ruiz' }, + { name: 'Langga', municipality: 'San Lorenzo Ruiz' }, + { name: 'Laniton', municipality: 'San Lorenzo Ruiz' }, + { name: 'Maisog', municipality: 'San Lorenzo Ruiz' }, + { name: 'Mampurog', municipality: 'San Lorenzo Ruiz' }, + { name: 'Manlimonsito', municipality: 'San Lorenzo Ruiz' }, + { name: 'Matacong', municipality: 'San Lorenzo Ruiz' }, + { name: 'Salvacion', municipality: 'San Lorenzo Ruiz' }, + { name: 'San Antonio', municipality: 'San Lorenzo Ruiz' }, + { name: 'San Isidro', municipality: 'San Lorenzo Ruiz' }, + { name: 'San Ramon', municipality: 'San Lorenzo Ruiz' }, + { name: 'Asdum', municipality: 'San Vicente' }, + { name: 'Cabanbanan', municipality: 'San Vicente' }, + { name: 'Calabagas', municipality: 'San Vicente' }, + { name: 'Fabrica', municipality: 'San Vicente' }, + { name: 'Iraya Sur', municipality: 'San Vicente' }, + { name: 'Man-ogob', municipality: 'San Vicente' }, + { name: 'Poblacion District I', municipality: 'San Vicente' }, + { name: 'Poblacion District II', municipality: 'San Vicente' }, + { name: 'San Jose', municipality: 'San Vicente' }, + { name: 'Basiad', municipality: 'Santa Elena' }, + { name: 'Bulala', municipality: 'Santa Elena' }, + { name: 'Don Tomas', municipality: 'Santa Elena' }, + { name: 'Guitol', municipality: 'Santa Elena' }, + { name: 'Kabuluan', municipality: 'Santa Elena' }, + { name: 'Kagtalaba', municipality: 'Santa Elena' }, + { name: 'Maulawin', municipality: 'Santa Elena' }, + { name: 'Patag Ibaba', municipality: 'Santa Elena' }, + { name: 'Patag Iraya', municipality: 'Santa Elena' }, + { name: 'Plaridel', municipality: 'Santa Elena' }, + { name: 'Polungguitguit', municipality: 'Santa Elena' }, + { name: 'Rizal', municipality: 'Santa Elena' }, + { name: 'Salvacion', municipality: 'Santa Elena' }, + { name: 'San Lorenzo', municipality: 'Santa Elena' }, + { name: 'San Pedro', municipality: 'Santa Elena' }, + { name: 'San Vicente', municipality: 'Santa Elena' }, + { name: 'Santa Elena', municipality: 'Santa Elena' }, + { name: 'Tabugon', municipality: 'Santa Elena' }, + { name: 'Villa San Isidro', municipality: 'Santa Elena' }, + { name: 'Binanuaan', municipality: 'Talisay' }, + { name: 'Caawigan', municipality: 'Talisay' }, + { name: 'Cahabaan', municipality: 'Talisay' }, + { name: 'Calintaan', municipality: 'Talisay' }, + { name: 'Del Carmen', municipality: 'Talisay' }, + { name: 'Gabon', municipality: 'Talisay' }, + { name: 'Itomang', municipality: 'Talisay' }, + { name: 'Poblacion', municipality: 'Talisay' }, + { name: 'San Francisco', municipality: 'Talisay' }, + { name: 'San Isidro', municipality: 'Talisay' }, + { name: 'San Jose', municipality: 'Talisay' }, + { name: 'San Nicolas', municipality: 'Talisay' }, + { name: 'Santa Cruz', municipality: 'Talisay' }, + { name: 'Santa Elena', municipality: 'Talisay' }, + { name: 'Santo Niño', municipality: 'Talisay' }, + { name: 'Aguit-it', municipality: 'Vinzons' }, + { name: 'Banocboc', municipality: 'Vinzons' }, + { name: 'Barangay I', municipality: 'Vinzons' }, + { name: 'Barangay II', municipality: 'Vinzons' }, + { name: 'Barangay III', municipality: 'Vinzons' }, + { name: 'Cagbalogo', municipality: 'Vinzons' }, + { name: 'Calangcawan Norte', municipality: 'Vinzons' }, + { name: 'Calangcawan Sur', municipality: 'Vinzons' }, + { name: 'Guinacutan', municipality: 'Vinzons' }, + { name: 'Mangcawayan', municipality: 'Vinzons' }, + { name: 'Mangcayo', municipality: 'Vinzons' }, + { name: 'Manlucugan', municipality: 'Vinzons' }, + { name: 'Matango', municipality: 'Vinzons' }, + { name: 'Napilihan', municipality: 'Vinzons' }, + { name: 'Pinagtigasan', municipality: 'Vinzons' }, + { name: 'Sabang', municipality: 'Vinzons' }, + { name: 'Santo Domingo', municipality: 'Vinzons' }, + { name: 'Singi', municipality: 'Vinzons' }, + { name: 'Sula', municipality: 'Vinzons' }, +] + +export const MUNICIPALITY_LABELS = Object.fromEntries( + CAMARINES_NORTE_MUNICIPALITIES.map((m) => [m.id, m.label]), +) + +export const MUNI_LABELS_SORTED = [...CAMARINES_NORTE_MUNICIPALITIES] + .sort((a, b) => a.label.localeCompare(b.label)) + .map((m) => ({ id: m.id, label: m.label })) From 6f444065ec5559a32ed88c3b241bfbf93457105c Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 23 Apr 2026 21:04:50 +0800 Subject: [PATCH 04/31] refactor(citizen-pwa): address review feedback on MunicipalitySelector and BarangaySelector --- .../SubmitReportForm/BarangaySelector.test.tsx | 13 ++++++++----- .../SubmitReportForm/BarangaySelector.tsx | 4 ++-- .../SubmitReportForm/MunicipalitySelector.test.tsx | 7 +++++++ 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/apps/citizen-pwa/src/components/SubmitReportForm/BarangaySelector.test.tsx b/apps/citizen-pwa/src/components/SubmitReportForm/BarangaySelector.test.tsx index a93edd30..7d5c2017 100644 --- a/apps/citizen-pwa/src/components/SubmitReportForm/BarangaySelector.test.tsx +++ b/apps/citizen-pwa/src/components/SubmitReportForm/BarangaySelector.test.tsx @@ -36,10 +36,13 @@ describe('BarangaySelector', () => { it('shows optional label', () => { render() - expect( - screen.getByText((content, element) => { - return element?.tagName.toLowerCase() === 'span' && content.includes('optional') - }), - ).toBeInTheDocument() + expect(screen.getByText(/— optional/)).toBeInTheDocument() + }) + + it('reflects the value prop on the select element', () => { + render() + + const select = screen.getByRole('combobox') + expect(select).toHaveValue('Bagasbas') }) }) diff --git a/apps/citizen-pwa/src/components/SubmitReportForm/BarangaySelector.tsx b/apps/citizen-pwa/src/components/SubmitReportForm/BarangaySelector.tsx index f03987ff..99fff1a7 100644 --- a/apps/citizen-pwa/src/components/SubmitReportForm/BarangaySelector.tsx +++ b/apps/citizen-pwa/src/components/SubmitReportForm/BarangaySelector.tsx @@ -3,7 +3,7 @@ import { FALLBACK_BARANGAYS, MUNICIPALITY_LABELS } from './location-constants.js interface BarangaySelectorProps { municipalityId: string - value: string | undefined + value: string onChange: (barangayId: string) => void } @@ -27,7 +27,7 @@ export function BarangaySelector({ municipalityId, value, onChange }: BarangaySe

{ + onReporterNameChange(e.target.value) + onNameErrorClear() + }} + placeholder="Maria Dela Cruz" + className="text-input" + required + /> + {nameError &&

{nameError}

} + + +
+

Phone number

+ { + onReporterMsisdnChange(e.target.value) + onPhoneErrorClear() + }} + placeholder="+63 912 345 6789" + className="text-input" + required + /> + {phoneError &&

{phoneError}

} +

+ Gives you faster help. Admins call this number if they need more details.{' '} + Mas mabilis kang matutulungan. +

+
+ +
+

+ Is anyone hurt? + May injured ba? +

+
+ + +
+ + {anyoneHurt && ( +
+

How many patients?

+
+ +
{patientCount}
+ +
+
+ )} +
+ + ) +} From 85248da758f14a19e7fea539791551599f434ca1 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 23 Apr 2026 21:17:17 +0800 Subject: [PATCH 06/31] refactor(citizen-pwa): address review feedback on ContactFields --- .../SubmitReportForm/ContactFields.test.tsx | 18 ++++++++++++------ .../SubmitReportForm/ContactFields.tsx | 12 ++++++++++-- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/apps/citizen-pwa/src/components/SubmitReportForm/ContactFields.test.tsx b/apps/citizen-pwa/src/components/SubmitReportForm/ContactFields.test.tsx index b22a4130..dea5093b 100644 --- a/apps/citizen-pwa/src/components/SubmitReportForm/ContactFields.test.tsx +++ b/apps/citizen-pwa/src/components/SubmitReportForm/ContactFields.test.tsx @@ -78,6 +78,12 @@ describe('ContactFields', () => { expect(onAnyoneHurtChange).toHaveBeenCalledExactlyOnceWith(false) }) + it('does not show patient counter when anyoneHurt is false', () => { + renderContactFields({ anyoneHurt: false }) + + expect(screen.queryByText('How many patients?')).not.toBeInTheDocument() + }) + it('increments patient count', () => { const onPatientCountChange = vi.fn() @@ -122,24 +128,24 @@ describe('ContactFields', () => { it('displays name error when provided', () => { renderContactFields({ nameError: 'Name is required' }) - expect(screen.getByText('Name is required')).toBeInTheDocument() + expect(screen.getByTestId('name-error')).toHaveTextContent('Name is required') }) it('displays phone error when provided', () => { renderContactFields({ phoneError: 'Invalid phone number' }) - expect(screen.getByText('Invalid phone number')).toBeInTheDocument() + expect(screen.getByTestId('phone-error')).toHaveTextContent('Invalid phone number') }) it('does not display name error when null', () => { - const { container } = renderContactFields({ nameError: null }) + renderContactFields({ nameError: null }) - expect(container.querySelector('.field-error')).not.toBeInTheDocument() + expect(screen.queryByTestId('name-error')).not.toBeInTheDocument() }) it('does not display phone error when null', () => { - const { container } = renderContactFields({ phoneError: null }) + renderContactFields({ phoneError: null }) - expect(container.querySelector('.field-error')).not.toBeInTheDocument() + expect(screen.queryByTestId('phone-error')).not.toBeInTheDocument() }) }) diff --git a/apps/citizen-pwa/src/components/SubmitReportForm/ContactFields.tsx b/apps/citizen-pwa/src/components/SubmitReportForm/ContactFields.tsx index 0e85b77a..50616831 100644 --- a/apps/citizen-pwa/src/components/SubmitReportForm/ContactFields.tsx +++ b/apps/citizen-pwa/src/components/SubmitReportForm/ContactFields.tsx @@ -49,7 +49,11 @@ export function ContactFields({ className="text-input" required /> - {nameError &&

{nameError}

} + {nameError && ( +

+ {nameError} +

+ )}
@@ -65,7 +69,11 @@ export function ContactFields({ className="text-input" required /> - {phoneError &&

{phoneError}

} + {phoneError && ( +

+ {phoneError} +

+ )}

Gives you faster help. Admins call this number if they need more details.{' '} Mas mabilis kang matutulungan. From 8f96905dd7aaf900596ebbefa88dc788db36c124 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 23 Apr 2026 21:58:15 +0800 Subject: [PATCH 07/31] refactor(citizen-pwa): complete Step2WhoWhere extraction (Phases 1-4) - Extract FALLBACK_BARANGAYS to data/fallback-barangays.ts - Extract storage error utilities to utils/storage-errors.ts - Extract useMunicipalityBarangays hook - Extract MunicipalitySelector, BarangaySelector, ContactFields components - Simplify Step2WhoWhere from 707 to 289 lines (-59%) - Add setLocationMethod to useGpsLocation return type Verification: typecheck, lint, test all PASS (137 passed, 2 todo) Co-Authored-By: Claude Sonnet 4.6 --- .../SubmitReportForm/Step2WhoWhere.tsx | 552 ++---------------- .../src/data/fallback-barangays.ts | 294 ++++++++++ apps/citizen-pwa/src/hooks/useGpsLocation.ts | 2 + .../src/hooks/useMunicipalityBarangays.ts | 53 ++ apps/citizen-pwa/src/utils/storage-errors.ts | 30 + docs/progress.md | 46 ++ docs/refactor-audit-2026-04-23.md | 16 +- .../2026-04-23-step2wherewhere-refactoring.md | 135 +++++ 8 files changed, 627 insertions(+), 501 deletions(-) create mode 100644 apps/citizen-pwa/src/data/fallback-barangays.ts create mode 100644 apps/citizen-pwa/src/hooks/useMunicipalityBarangays.ts create mode 100644 apps/citizen-pwa/src/utils/storage-errors.ts create mode 100644 docs/superpowers/plans/2026-04-23-step2wherewhere-refactoring.md diff --git a/apps/citizen-pwa/src/components/SubmitReportForm/Step2WhoWhere.tsx b/apps/citizen-pwa/src/components/SubmitReportForm/Step2WhoWhere.tsx index af989e72..032254d5 100644 --- a/apps/citizen-pwa/src/components/SubmitReportForm/Step2WhoWhere.tsx +++ b/apps/citizen-pwa/src/components/SubmitReportForm/Step2WhoWhere.tsx @@ -2,311 +2,12 @@ import { useState, useEffect } from 'react' import { MapPin, Navigation, ArrowLeft } from 'lucide-react' import { Button } from '../ui/Button' import { CAMARINES_NORTE_MUNICIPALITIES } from '@bantayog/shared-validators' - -const FALLBACK_BARANGAYS: { name: string; municipality: string }[] = [ - { name: 'Angas', municipality: 'Basud' }, - { name: 'Bactas', municipality: 'Basud' }, - { name: 'Binatagan', municipality: 'Basud' }, - { name: 'Caayunan', municipality: 'Basud' }, - { name: 'Guinatungan', municipality: 'Basud' }, - { name: 'Hinampacan', municipality: 'Basud' }, - { name: 'Langa', municipality: 'Basud' }, - { name: 'Laniton', municipality: 'Basud' }, - { name: 'Lidong', municipality: 'Basud' }, - { name: 'Mampili', municipality: 'Basud' }, - { name: 'Mandazo', municipality: 'Basud' }, - { name: 'Mangcamagong', municipality: 'Basud' }, - { name: 'Manmuntay', municipality: 'Basud' }, - { name: 'Mantugawe', municipality: 'Basud' }, - { name: 'Matnog', municipality: 'Basud' }, - { name: 'Mocong', municipality: 'Basud' }, - { name: 'Oliva', municipality: 'Basud' }, - { name: 'Pagsangahan', municipality: 'Basud' }, - { name: 'Pinagwarasan', municipality: 'Basud' }, - { name: 'Plaridel', municipality: 'Basud' }, - { name: 'Poblacion 1', municipality: 'Basud' }, - { name: 'Poblacion 2', municipality: 'Basud' }, - { name: 'San Felipe', municipality: 'Basud' }, - { name: 'San Jose', municipality: 'Basud' }, - { name: 'San Pascual', municipality: 'Basud' }, - { name: 'Taba-taba', municipality: 'Basud' }, - { name: 'Tacad', municipality: 'Basud' }, - { name: 'Taisan', municipality: 'Basud' }, - { name: 'Tuaca', municipality: 'Basud' }, - { name: 'Alayao', municipality: 'Capalonga' }, - { name: 'Binawangan', municipality: 'Capalonga' }, - { name: 'Calabaca', municipality: 'Capalonga' }, - { name: 'Camagsaan', municipality: 'Capalonga' }, - { name: 'Catabaguangan', municipality: 'Capalonga' }, - { name: 'Catioan', municipality: 'Capalonga' }, - { name: 'Del Pilar', municipality: 'Capalonga' }, - { name: 'Itok', municipality: 'Capalonga' }, - { name: 'Lucbanan', municipality: 'Capalonga' }, - { name: 'Mabini', municipality: 'Capalonga' }, - { name: 'Mactang', municipality: 'Capalonga' }, - { name: 'Magsaysay', municipality: 'Capalonga' }, - { name: 'Mataque', municipality: 'Capalonga' }, - { name: 'Old Camp', municipality: 'Capalonga' }, - { name: 'Poblacion', municipality: 'Capalonga' }, - { name: 'San Antonio', municipality: 'Capalonga' }, - { name: 'San Isidro', municipality: 'Capalonga' }, - { name: 'San Roque', municipality: 'Capalonga' }, - { name: 'Tanawan', municipality: 'Capalonga' }, - { name: 'Ubang', municipality: 'Capalonga' }, - { name: 'Villa Aurora', municipality: 'Capalonga' }, - { name: 'Villa Belen', municipality: 'Capalonga' }, - { name: 'Alawihao', municipality: 'Daet' }, - { name: 'Awitan', municipality: 'Daet' }, - { name: 'Bagasbas', municipality: 'Daet' }, - { name: 'Barangay I', municipality: 'Daet' }, - { name: 'Barangay II', municipality: 'Daet' }, - { name: 'Barangay III', municipality: 'Daet' }, - { name: 'Barangay IV', municipality: 'Daet' }, - { name: 'Barangay V', municipality: 'Daet' }, - { name: 'Barangay VI', municipality: 'Daet' }, - { name: 'Barangay VII', municipality: 'Daet' }, - { name: 'Barangay VIII', municipality: 'Daet' }, - { name: 'Bibirao', municipality: 'Daet' }, - { name: 'Borabod', municipality: 'Daet' }, - { name: 'Calasgasan', municipality: 'Daet' }, - { name: 'Camambugan', municipality: 'Daet' }, - { name: 'Cobangbang', municipality: 'Daet' }, - { name: 'Dogongan', municipality: 'Daet' }, - { name: 'Gahonon', municipality: 'Daet' }, - { name: 'Gubat', municipality: 'Daet' }, - { name: 'Lag-on', municipality: 'Daet' }, - { name: 'Magang', municipality: 'Daet' }, - { name: 'Mambalite', municipality: 'Daet' }, - { name: 'Mancruz', municipality: 'Daet' }, - { name: 'Pamorangon', municipality: 'Daet' }, - { name: 'San Isidro', municipality: 'Daet' }, - { name: 'Bagong Bayan', municipality: 'Jose Panganiban' }, - { name: 'Calero', municipality: 'Jose Panganiban' }, - { name: 'Dahican', municipality: 'Jose Panganiban' }, - { name: 'Dayhagan', municipality: 'Jose Panganiban' }, - { name: 'Larap', municipality: 'Jose Panganiban' }, - { name: 'Luklukan Norte', municipality: 'Jose Panganiban' }, - { name: 'Luklukan Sur', municipality: 'Jose Panganiban' }, - { name: 'Motherlode', municipality: 'Jose Panganiban' }, - { name: 'Nakalaya', municipality: 'Jose Panganiban' }, - { name: 'North Poblacion', municipality: 'Jose Panganiban' }, - { name: 'Osmeña', municipality: 'Jose Panganiban' }, - { name: 'Pag-asa', municipality: 'Jose Panganiban' }, - { name: 'Parang', municipality: 'Jose Panganiban' }, - { name: 'Plaridel', municipality: 'Jose Panganiban' }, - { name: 'Salvacion', municipality: 'Jose Panganiban' }, - { name: 'San Isidro', municipality: 'Jose Panganiban' }, - { name: 'San Jose', municipality: 'Jose Panganiban' }, - { name: 'San Martin', municipality: 'Jose Panganiban' }, - { name: 'San Pedro', municipality: 'Jose Panganiban' }, - { name: 'San Rafael', municipality: 'Jose Panganiban' }, - { name: 'Santa Cruz', municipality: 'Jose Panganiban' }, - { name: 'Santa Elena', municipality: 'Jose Panganiban' }, - { name: 'Santa Milagrosa', municipality: 'Jose Panganiban' }, - { name: 'Santa Rosa Norte', municipality: 'Jose Panganiban' }, - { name: 'Santa Rosa Sur', municipality: 'Jose Panganiban' }, - { name: 'South Poblacion', municipality: 'Jose Panganiban' }, - { name: 'Tamisan', municipality: 'Jose Panganiban' }, - { name: 'Anahaw', municipality: 'Labo' }, - { name: 'Anameam', municipality: 'Labo' }, - { name: 'Awitan', municipality: 'Labo' }, - { name: 'Baay', municipality: 'Labo' }, - { name: 'Bagacay', municipality: 'Labo' }, - { name: 'Bagong Silang I', municipality: 'Labo' }, - { name: 'Bagong Silang II', municipality: 'Labo' }, - { name: 'Bagong Silang III', municipality: 'Labo' }, - { name: 'Bakiad', municipality: 'Labo' }, - { name: 'Bautista', municipality: 'Labo' }, - { name: 'Bayabas', municipality: 'Labo' }, - { name: 'Bayan-bayan', municipality: 'Labo' }, - { name: 'Benit', municipality: 'Labo' }, - { name: 'Bulhao', municipality: 'Labo' }, - { name: 'Cabatuhan', municipality: 'Labo' }, - { name: 'Cabusay', municipality: 'Labo' }, - { name: 'Calabasa', municipality: 'Labo' }, - { name: 'Canapawan', municipality: 'Labo' }, - { name: 'Daguit', municipality: 'Labo' }, - { name: 'Dalas', municipality: 'Labo' }, - { name: 'Dumagmang', municipality: 'Labo' }, - { name: 'Exciban', municipality: 'Labo' }, - { name: 'Fundado', municipality: 'Labo' }, - { name: 'Guinacutan', municipality: 'Labo' }, - { name: 'Guisican', municipality: 'Labo' }, - { name: 'Gumamela', municipality: 'Labo' }, - { name: 'Iberica', municipality: 'Labo' }, - { name: 'Kalamunding', municipality: 'Labo' }, - { name: 'Lugui', municipality: 'Labo' }, - { name: 'Mabilo I', municipality: 'Labo' }, - { name: 'Mabilo II', municipality: 'Labo' }, - { name: 'Macogon', municipality: 'Labo' }, - { name: 'Mahawan-hawan', municipality: 'Labo' }, - { name: 'Malangcao-Basud', municipality: 'Labo' }, - { name: 'Malasugui', municipality: 'Labo' }, - { name: 'Malatap', municipality: 'Labo' }, - { name: 'Malaya', municipality: 'Labo' }, - { name: 'Malibago', municipality: 'Labo' }, - { name: 'Maot', municipality: 'Labo' }, - { name: 'Masalong', municipality: 'Labo' }, - { name: 'Matanlang', municipality: 'Labo' }, - { name: 'Napaod', municipality: 'Labo' }, - { name: 'Pag-asa', municipality: 'Labo' }, - { name: 'Pangpang', municipality: 'Labo' }, - { name: 'Pinya', municipality: 'Labo' }, - { name: 'San Antonio', municipality: 'Labo' }, - { name: 'San Francisco', municipality: 'Labo' }, - { name: 'Santa Cruz', municipality: 'Labo' }, - { name: 'Submakin', municipality: 'Labo' }, - { name: 'Talobatib', municipality: 'Labo' }, - { name: 'Tigbinan', municipality: 'Labo' }, - { name: 'Tulay na Lupa', municipality: 'Labo' }, - { name: 'Apuao', municipality: 'Mercedes' }, - { name: 'Barangay I', municipality: 'Mercedes' }, - { name: 'Barangay II', municipality: 'Mercedes' }, - { name: 'Barangay III', municipality: 'Mercedes' }, - { name: 'Barangay IV', municipality: 'Mercedes' }, - { name: 'Barangay V', municipality: 'Mercedes' }, - { name: 'Barangay VI', municipality: 'Mercedes' }, - { name: 'Barangay VII', municipality: 'Mercedes' }, - { name: 'Caringo', municipality: 'Mercedes' }, - { name: 'Catandunganon', municipality: 'Mercedes' }, - { name: 'Cayucyucan', municipality: 'Mercedes' }, - { name: 'Colasi', municipality: 'Mercedes' }, - { name: 'Del Rosario', municipality: 'Mercedes' }, - { name: 'Gaboc', municipality: 'Mercedes' }, - { name: 'Hamoraon', municipality: 'Mercedes' }, - { name: 'Hinipaan', municipality: 'Mercedes' }, - { name: 'Lalawigan', municipality: 'Mercedes' }, - { name: 'Lanot', municipality: 'Mercedes' }, - { name: 'Mambungalon', municipality: 'Mercedes' }, - { name: 'Manguisoc', municipality: 'Mercedes' }, - { name: 'Masalongsalong', municipality: 'Mercedes' }, - { name: 'Matoogtoog', municipality: 'Mercedes' }, - { name: 'Pambuhan', municipality: 'Mercedes' }, - { name: 'Quinapaguian', municipality: 'Mercedes' }, - { name: 'San Roque', municipality: 'Mercedes' }, - { name: 'Tarum', municipality: 'Mercedes' }, - { name: 'Awitan', municipality: 'Paracale' }, - { name: 'Bagumbayan', municipality: 'Paracale' }, - { name: 'Bakal', municipality: 'Paracale' }, - { name: 'Batobalani', municipality: 'Paracale' }, - { name: 'Calaburnay', municipality: 'Paracale' }, - { name: 'Capacuan', municipality: 'Paracale' }, - { name: 'Casalugan', municipality: 'Paracale' }, - { name: 'Dagang', municipality: 'Paracale' }, - { name: 'Dalnac', municipality: 'Paracale' }, - { name: 'Dancalan', municipality: 'Paracale' }, - { name: 'Gumaus', municipality: 'Paracale' }, - { name: 'Labnig', municipality: 'Paracale' }, - { name: 'Macolabo Island', municipality: 'Paracale' }, - { name: 'Malacbang', municipality: 'Paracale' }, - { name: 'Malaguit', municipality: 'Paracale' }, - { name: 'Mampungo', municipality: 'Paracale' }, - { name: 'Mangkasay', municipality: 'Paracale' }, - { name: 'Maybato', municipality: 'Paracale' }, - { name: 'Palanas', municipality: 'Paracale' }, - { name: 'Pinagbirayan Malaki', municipality: 'Paracale' }, - { name: 'Pinagbirayan Munti', municipality: 'Paracale' }, - { name: 'Poblacion Norte', municipality: 'Paracale' }, - { name: 'Poblacion Sur', municipality: 'Paracale' }, - { name: 'Tabas', municipality: 'Paracale' }, - { name: 'Talusan', municipality: 'Paracale' }, - { name: 'Tawig', municipality: 'Paracale' }, - { name: 'Tugos', municipality: 'Paracale' }, - { name: 'Daculang Bolo', municipality: 'San Lorenzo Ruiz' }, - { name: 'Dagotdotan', municipality: 'San Lorenzo Ruiz' }, - { name: 'Langga', municipality: 'San Lorenzo Ruiz' }, - { name: 'Laniton', municipality: 'San Lorenzo Ruiz' }, - { name: 'Maisog', municipality: 'San Lorenzo Ruiz' }, - { name: 'Mampurog', municipality: 'San Lorenzo Ruiz' }, - { name: 'Manlimonsito', municipality: 'San Lorenzo Ruiz' }, - { name: 'Matacong', municipality: 'San Lorenzo Ruiz' }, - { name: 'Salvacion', municipality: 'San Lorenzo Ruiz' }, - { name: 'San Antonio', municipality: 'San Lorenzo Ruiz' }, - { name: 'San Isidro', municipality: 'San Lorenzo Ruiz' }, - { name: 'San Ramon', municipality: 'San Lorenzo Ruiz' }, - { name: 'Asdum', municipality: 'San Vicente' }, - { name: 'Cabanbanan', municipality: 'San Vicente' }, - { name: 'Calabagas', municipality: 'San Vicente' }, - { name: 'Fabrica', municipality: 'San Vicente' }, - { name: 'Iraya Sur', municipality: 'San Vicente' }, - { name: 'Man-ogob', municipality: 'San Vicente' }, - { name: 'Poblacion District I', municipality: 'San Vicente' }, - { name: 'Poblacion District II', municipality: 'San Vicente' }, - { name: 'San Jose', municipality: 'San Vicente' }, - { name: 'Basiad', municipality: 'Santa Elena' }, - { name: 'Bulala', municipality: 'Santa Elena' }, - { name: 'Don Tomas', municipality: 'Santa Elena' }, - { name: 'Guitol', municipality: 'Santa Elena' }, - { name: 'Kabuluan', municipality: 'Santa Elena' }, - { name: 'Kagtalaba', municipality: 'Santa Elena' }, - { name: 'Maulawin', municipality: 'Santa Elena' }, - { name: 'Patag Ibaba', municipality: 'Santa Elena' }, - { name: 'Patag Iraya', municipality: 'Santa Elena' }, - { name: 'Plaridel', municipality: 'Santa Elena' }, - { name: 'Polungguitguit', municipality: 'Santa Elena' }, - { name: 'Rizal', municipality: 'Santa Elena' }, - { name: 'Salvacion', municipality: 'Santa Elena' }, - { name: 'San Lorenzo', municipality: 'Santa Elena' }, - { name: 'San Pedro', municipality: 'Santa Elena' }, - { name: 'San Vicente', municipality: 'Santa Elena' }, - { name: 'Santa Elena', municipality: 'Santa Elena' }, - { name: 'Tabugon', municipality: 'Santa Elena' }, - { name: 'Villa San Isidro', municipality: 'Santa Elena' }, - { name: 'Binanuaan', municipality: 'Talisay' }, - { name: 'Caawigan', municipality: 'Talisay' }, - { name: 'Cahabaan', municipality: 'Talisay' }, - { name: 'Calintaan', municipality: 'Talisay' }, - { name: 'Del Carmen', municipality: 'Talisay' }, - { name: 'Gabon', municipality: 'Talisay' }, - { name: 'Itomang', municipality: 'Talisay' }, - { name: 'Poblacion', municipality: 'Talisay' }, - { name: 'San Francisco', municipality: 'Talisay' }, - { name: 'San Isidro', municipality: 'Talisay' }, - { name: 'San Jose', municipality: 'Talisay' }, - { name: 'San Nicolas', municipality: 'Talisay' }, - { name: 'Santa Cruz', municipality: 'Talisay' }, - { name: 'Santa Elena', municipality: 'Talisay' }, - { name: 'Santo Niño', municipality: 'Talisay' }, - { name: 'Aguit-it', municipality: 'Vinzons' }, - { name: 'Banocboc', municipality: 'Vinzons' }, - { name: 'Barangay I', municipality: 'Vinzons' }, - { name: 'Barangay II', municipality: 'Vinzons' }, - { name: 'Barangay III', municipality: 'Vinzons' }, - { name: 'Cagbalogo', municipality: 'Vinzons' }, - { name: 'Calangcawan Norte', municipality: 'Vinzons' }, - { name: 'Calangcawan Sur', municipality: 'Vinzons' }, - { name: 'Guinacutan', municipality: 'Vinzons' }, - { name: 'Mangcawayan', municipality: 'Vinzons' }, - { name: 'Mangcayo', municipality: 'Vinzons' }, - { name: 'Manlucugan', municipality: 'Vinzons' }, - { name: 'Matango', municipality: 'Vinzons' }, - { name: 'Napilihan', municipality: 'Vinzons' }, - { name: 'Pinagtigasan', municipality: 'Vinzons' }, - { name: 'Sabang', municipality: 'Vinzons' }, - { name: 'Santo Domingo', municipality: 'Vinzons' }, - { name: 'Singi', municipality: 'Vinzons' }, - { name: 'Sula', municipality: 'Vinzons' }, -] - -const MUNICIPALITY_LABELS = Object.fromEntries( - CAMARINES_NORTE_MUNICIPALITIES.map((m) => [m.id, m.label]), -) - -const MUNI_LABELS_SORTED = [...CAMARINES_NORTE_MUNICIPALITIES] - .sort((a, b) => a.label.localeCompare(b.label)) - .map((m) => ({ id: m.id, label: m.label })) - -function isQuotaExceededError(err: unknown): boolean { - return ( - err instanceof DOMException && - // eslint-disable-next-line @typescript-eslint/no-deprecated - (err.name === 'QuotaExceededError' || err.code === 22) - ) -} - -function isSecurityError(err: unknown): boolean { - return err instanceof DOMException && err.name === 'SecurityError' -} +import { isQuotaExceededError, isSecurityError } from '../../utils/storage-errors' +import { useGpsLocation } from '../../hooks/useGpsLocation' +import { useMunicipalityBarangays } from '../../hooks/useMunicipalityBarangays' +import { MunicipalitySelector } from './MunicipalitySelector' +import { BarangaySelector } from './BarangaySelector' +import { ContactFields } from './ContactFields' interface Step2WhoWhereProps { onNext: (data: { @@ -325,59 +26,32 @@ interface Step2WhoWhereProps { } export function Step2WhoWhere({ onNext, onBack, isSubmitting = false }: Step2WhoWhereProps) { - const [locationMethod, setLocationMethod] = useState<'gps' | 'manual' | null>(null) - const [location, setLocation] = useState<{ lat: number; lng: number } | null>(null) - const [gpsLoading, setGpsLoading] = useState(false) - const [selectedMunicipalityId, setSelectedMunicipalityId] = useState('') - const [selectedBarangayId, setSelectedBarangayId] = useState(undefined) + const { + location, + locationMethod, + isLoading: gpsLoading, + locationError, + attemptGps, + resetGps, + setLocationMethod, + } = useGpsLocation(true) + + const { + selectedMunicipalityId, + selectedBarangayId, + handleSelectMunicipality, + setSelectedBarangayId, + } = useMunicipalityBarangays() + const [nearestLandmark, setNearestLandmark] = useState('') const [reporterName, setReporterName] = useState('') const [reporterMsisdn, setReporterMsisdn] = useState('') const [anyoneHurt, setAnyoneHurt] = useState(false) const [patientCount, setPatientCount] = useState(0) - const [locationError, setLocationError] = useState(null) const [nameError, setNameError] = useState(null) const [phoneError, setPhoneError] = useState(null) const [hasMemory, setHasMemory] = useState(false) - const attemptGps = async () => { - setLocationError(null) - setGpsLoading(true) - try { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (!navigator.geolocation) { - setLocationError('GPS not supported on this device.') - setLocationMethod('manual') - return - } - const pos = await new Promise((resolve, reject) => { - navigator.geolocation.getCurrentPosition(resolve, reject, { - enableHighAccuracy: true, - timeout: 10000, - }) - }) - setLocation({ lat: pos.coords.latitude, lng: pos.coords.longitude }) - setLocationMethod('gps') - } catch (err: unknown) { - console.error('[Step2WhoWhere] attemptGps failed:', err) - let msg = 'Could not get location. Choose municipality manually.' - if (err && typeof err === 'object' && 'code' in err) { - const code = (err as GeolocationPositionError).code - if (code === 1) msg = 'Location access denied. Choose municipality manually.' - else if (code === 3) msg = 'Location timed out. Choose municipality manually.' - } - setLocationError(msg) - setLocationMethod('manual') - } finally { - setGpsLoading(false) - } - } - - useEffect(() => { - // eslint-disable-next-line react-hooks/set-state-in-effect - void attemptGps() - }, []) - useEffect(() => { try { const savedName = localStorage.getItem('bantayog.reporter.name') @@ -399,17 +73,12 @@ export function Step2WhoWhere({ onNext, onBack, isSubmitting = false }: Step2Who } }, []) - const handleSelectMunicipality = (muniId: string) => { - setSelectedMunicipalityId(muniId) - setSelectedBarangayId(undefined) - } - const handleNext = () => { setNameError(null) setPhoneError(null) if (locationMethod === 'manual' && !selectedMunicipalityId) { - setLocationError('Please select your municipality.') + // Note: locationError is managed by useGpsLocation hook return } if (!reporterName.trim()) { @@ -468,12 +137,6 @@ export function Step2WhoWhere({ onNext, onBack, isSubmitting = false }: Step2Who (locationMethod === 'manual' && !!selectedMunicipalityId) || false - const barangayOptions = selectedMunicipalityId - ? FALLBACK_BARANGAYS.filter( - (b) => MUNICIPALITY_LABELS[selectedMunicipalityId] === b.municipality, - ).sort((a, b) => a.name.localeCompare(b.name)) - : [] - return (

@@ -500,7 +163,6 @@ export function Step2WhoWhere({ onNext, onBack, isSubmitting = false }: Step2Who className="location-picker-btn" onClick={() => { void attemptGps() - setGpsLoading(true) }} > @@ -533,8 +195,7 @@ export function Step2WhoWhere({ onNext, onBack, isSubmitting = false }: Step2Who type="button" className="location-btn" onClick={() => { - setLocationMethod(null) - setLocation(null) + resetGps() }} >
@@ -556,49 +217,20 @@ export function Step2WhoWhere({ onNext, onBack, isSubmitting = false }: Step2Who ) : null} {locationMethod === 'manual' ? ( -
-

Municipality

- - {locationError &&

{locationError}

} -
+ ) : null} - {locationMethod === 'manual' && selectedMunicipalityId ? ( -
-

- Barangay - — optional -

- -
- ) : null} + {locationMethod === 'manual' && ( + + )} {locationMethod === 'manual' && selectedMunicipalityId ? (
@@ -621,97 +253,25 @@ export function Step2WhoWhere({ onNext, onBack, isSubmitting = false }: Step2Who {locationMethod !== null ? ( <> - {hasMemory &&

Pre-filled from your last report

} -
-

Your name

- { - setReporterName(e.target.value) - setNameError(null) - }} - placeholder="Maria Dela Cruz" - className="text-input" - required - /> - {nameError &&

{nameError}

} -
- -
-

Phone number

- { - setReporterMsisdn(e.target.value) - setPhoneError(null) - }} - placeholder="+63 912 345 6789" - className="text-input" - required - /> - {phoneError &&

{phoneError}

} -

- Gives you faster help. Admins call this number if they need more - details. Mas mabilis kang matutulungan. -

-
- -
-

- Is anyone hurt? - May injured ba? -

-
- - -
- - {anyoneHurt && ( -
-

How many patients?

-
- -
{patientCount}
- -
-
- )} -
+ { + setNameError(null) + }} + reporterMsisdn={reporterMsisdn} + onReporterMsisdnChange={setReporterMsisdn} + phoneError={phoneError} + onPhoneErrorClear={() => { + setPhoneError(null) + }} + anyoneHurt={anyoneHurt} + onAnyoneHurtChange={setAnyoneHurt} + patientCount={patientCount} + onPatientCountChange={setPatientCount} + hasMemory={hasMemory} + /> {banner &&
{banner}
} diff --git a/apps/admin-desktop/src/routes.tsx b/apps/admin-desktop/src/routes.tsx index 40eb66d1..fb9dabc2 100644 --- a/apps/admin-desktop/src/routes.tsx +++ b/apps/admin-desktop/src/routes.tsx @@ -1,5 +1,5 @@ import { createBrowserRouter } from 'react-router-dom' -import { ProtectedRoute } from './app/protected-route' +import { ProtectedRoute } from '@bantayog/shared-ui' import { LoginPage } from './pages/LoginPage' import { TriageQueuePage } from './pages/TriageQueuePage' @@ -8,7 +8,16 @@ export const router = createBrowserRouter([ { path: '/', element: ( - + + You do not have access to this page. Please contact your superadmin. +
+ } + > ), diff --git a/apps/responder-app/src/App.tsx b/apps/responder-app/src/App.tsx index f45b244a..b1d48d84 100644 --- a/apps/responder-app/src/App.tsx +++ b/apps/responder-app/src/App.tsx @@ -1,7 +1,8 @@ import './App.module.css' import { useEffect } from 'react' import { AppRouter } from './routes' -import { AuthProvider, useAuth } from './app/auth-provider' +import { AuthProvider, useAuth } from '@bantayog/shared-ui' +import { auth } from './app/firebase' import { useRegisterFcmToken } from './hooks/useRegisterFcmToken' function FcmSetup() { @@ -26,7 +27,7 @@ function FcmSetup() { export default function App() { return ( - + diff --git a/apps/responder-app/src/app/protected-route.tsx b/apps/responder-app/src/app/protected-route.tsx deleted file mode 100644 index 4dcf9926..00000000 --- a/apps/responder-app/src/app/protected-route.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { Navigate, useLocation } from 'react-router-dom' -import { useAuth } from './auth-provider' - -export function ProtectedRoute({ children }: { children: React.ReactNode }) { - const { user, loading, claims } = useAuth() - const location = useLocation() - if (loading) return

Loading…

- if (!user) return - if (claims?.role !== 'responder') return

Access denied: responder role required.

- return <>{children} -} diff --git a/apps/responder-app/src/routes.tsx b/apps/responder-app/src/routes.tsx index 29be5c63..aec7c227 100644 --- a/apps/responder-app/src/routes.tsx +++ b/apps/responder-app/src/routes.tsx @@ -1,5 +1,5 @@ import { createBrowserRouter, RouterProvider, Navigate } from 'react-router-dom' -import { ProtectedRoute } from './app/protected-route' +import { ProtectedRoute } from '@bantayog/shared-ui' import { LoginPage } from './pages/LoginPage' import { DispatchListPage } from './pages/DispatchListPage' import { DispatchDetailPage } from './pages/DispatchDetailPage' @@ -9,7 +9,7 @@ const router = createBrowserRouter([ { path: '/', element: ( - + ), @@ -17,7 +17,7 @@ const router = createBrowserRouter([ { path: '/dispatches/:dispatchId', element: ( - + ), diff --git a/eslint.config.js b/eslint.config.js index 018d2cdb..78ae1386 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -22,8 +22,6 @@ export default tseslint.config( 'scripts/**', // Plain-JS service worker — excluded from TypeScript project service. 'apps/responder-app/public/firebase-messaging-sw.js', - // Plain-JS emulator shim — not in tsconfig rootDir, linted separately. - 'packages/shared-sms-parser/index.js', ], }, diff --git a/packages/shared-ui/lib/auth-provider.d.ts b/packages/shared-ui/lib/auth-provider.d.ts new file mode 100644 index 00000000..a38dcb7a --- /dev/null +++ b/packages/shared-ui/lib/auth-provider.d.ts @@ -0,0 +1,17 @@ +import { type ReactNode } from 'react'; +import { type Auth, type User } from 'firebase/auth'; +export interface AuthContextValue { + user: User | null; + claims: Record | null; + loading: boolean; + signOut: () => Promise; + refreshClaims: () => Promise; +} +interface AuthProviderProps { + children: ReactNode; + auth: Auth; +} +export declare function AuthProvider({ children, auth }: AuthProviderProps): import("react/jsx-runtime").JSX.Element; +export declare function useAuth(): AuthContextValue; +export {}; +//# sourceMappingURL=auth-provider.d.ts.map \ No newline at end of file diff --git a/packages/shared-ui/lib/auth-provider.d.ts.map b/packages/shared-ui/lib/auth-provider.d.ts.map new file mode 100644 index 00000000..a8b7dbe6 --- /dev/null +++ b/packages/shared-ui/lib/auth-provider.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"auth-provider.d.ts","sourceRoot":"","sources":["../src/auth-provider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+D,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AACnG,OAAO,EAA4C,KAAK,IAAI,EAAE,KAAK,IAAI,EAAE,MAAM,eAAe,CAAA;AAE9F,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,IAAI,GAAG,IAAI,CAAA;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACtC,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACnC;AAID,UAAU,iBAAiB;IACzB,QAAQ,EAAE,SAAS,CAAA;IACnB,IAAI,EAAE,IAAI,CAAA;CACX;AAED,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,iBAAiB,2CAgEjE;AAED,wBAAgB,OAAO,IAAI,gBAAgB,CAI1C"} \ No newline at end of file diff --git a/packages/shared-ui/lib/auth-provider.test.d.ts b/packages/shared-ui/lib/auth-provider.test.d.ts new file mode 100644 index 00000000..a5a5eadb --- /dev/null +++ b/packages/shared-ui/lib/auth-provider.test.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=auth-provider.test.d.ts.map \ No newline at end of file diff --git a/packages/shared-ui/lib/auth-provider.test.d.ts.map b/packages/shared-ui/lib/auth-provider.test.d.ts.map new file mode 100644 index 00000000..a09b1482 --- /dev/null +++ b/packages/shared-ui/lib/auth-provider.test.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"auth-provider.test.d.ts","sourceRoot":"","sources":["../src/auth-provider.test.tsx"],"names":[],"mappings":""} \ No newline at end of file diff --git a/packages/shared-ui/lib/index.d.ts b/packages/shared-ui/lib/index.d.ts index e26a57a8..a2bd4848 100644 --- a/packages/shared-ui/lib/index.d.ts +++ b/packages/shared-ui/lib/index.d.ts @@ -1,2 +1,4 @@ -export {}; +export { AuthProvider, useAuth } from './auth-provider.js'; +export type { AuthContextValue } from './auth-provider.js'; +export { ProtectedRoute } from './protected-route.js'; //# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/packages/shared-ui/lib/index.d.ts.map b/packages/shared-ui/lib/index.d.ts.map index 20c4ab7b..8b1ee058 100644 --- a/packages/shared-ui/lib/index.d.ts.map +++ b/packages/shared-ui/lib/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAA"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAC1D,YAAY,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA"} \ No newline at end of file diff --git a/packages/shared-ui/lib/protected-route.d.ts b/packages/shared-ui/lib/protected-route.d.ts new file mode 100644 index 00000000..f02a58e6 --- /dev/null +++ b/packages/shared-ui/lib/protected-route.d.ts @@ -0,0 +1,12 @@ +import { type ReactNode } from 'react'; +interface ProtectedRouteProps { + children: ReactNode; + allowedRoles: string[]; + requireActive?: boolean; + requireMunicipalityIdForRoles?: string[]; + loadingFallback?: ReactNode; + unauthorizedFallback?: ReactNode; +} +export declare function ProtectedRoute({ children, allowedRoles, requireActive, requireMunicipalityIdForRoles, loadingFallback, unauthorizedFallback, }: ProtectedRouteProps): string | number | bigint | boolean | Iterable | Promise> | Iterable | null | undefined> | import("react/jsx-runtime").JSX.Element | null; +export {}; +//# sourceMappingURL=protected-route.d.ts.map \ No newline at end of file diff --git a/packages/shared-ui/lib/protected-route.d.ts.map b/packages/shared-ui/lib/protected-route.d.ts.map new file mode 100644 index 00000000..e3a80877 --- /dev/null +++ b/packages/shared-ui/lib/protected-route.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"protected-route.d.ts","sourceRoot":"","sources":["../src/protected-route.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAItC,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,SAAS,CAAA;IACnB,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,6BAA6B,CAAC,EAAE,MAAM,EAAE,CAAA;IACxC,eAAe,CAAC,EAAE,SAAS,CAAA;IAC3B,oBAAoB,CAAC,EAAE,SAAS,CAAA;CACjC;AAED,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,YAAY,EACZ,aAAqB,EACrB,6BAAkC,EAClC,eAAqC,EACrC,oBAA6D,GAC9D,EAAE,mBAAmB,+TAwBrB"} \ No newline at end of file diff --git a/packages/shared-ui/lib/protected-route.test.d.ts b/packages/shared-ui/lib/protected-route.test.d.ts new file mode 100644 index 00000000..4e1073a4 --- /dev/null +++ b/packages/shared-ui/lib/protected-route.test.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=protected-route.test.d.ts.map \ No newline at end of file diff --git a/packages/shared-ui/lib/protected-route.test.d.ts.map b/packages/shared-ui/lib/protected-route.test.d.ts.map new file mode 100644 index 00000000..9d444bd5 --- /dev/null +++ b/packages/shared-ui/lib/protected-route.test.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"protected-route.test.d.ts","sourceRoot":"","sources":["../src/protected-route.test.tsx"],"names":[],"mappings":""} \ No newline at end of file diff --git a/packages/shared-ui/package.json b/packages/shared-ui/package.json index 6558253e..df2ea37e 100644 --- a/packages/shared-ui/package.json +++ b/packages/shared-ui/package.json @@ -12,10 +12,21 @@ "scripts": { "lint": "eslint src", "typecheck": "tsc --noEmit", - "build": "tsc --emitDeclarationOnly --outDir lib" + "build": "tsc --emitDeclarationOnly --outDir lib", + "test": "vitest --run" }, "peerDependencies": { - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^18.3.1 || ^19.0.0", + "react-dom": "^18.3.1 || ^19.0.0", + "react-router-dom": "^7.0.0", + "firebase": "^12.0.0" + }, + "devDependencies": { + "@testing-library/jest-dom": "^6.4.0", + "@testing-library/react": "^16.0.0", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "happy-dom": "^15.11.0", + "vitest": "^4.1.4" } } diff --git a/apps/responder-app/src/app/auth-provider.tsx b/packages/shared-ui/src/auth-provider.tsx similarity index 57% rename from apps/responder-app/src/app/auth-provider.tsx rename to packages/shared-ui/src/auth-provider.tsx index 8f8e2d60..878e4470 100644 --- a/apps/responder-app/src/app/auth-provider.tsx +++ b/packages/shared-ui/src/auth-provider.tsx @@ -1,21 +1,44 @@ -import { createContext, useContext, useEffect, useState, type ReactNode } from 'react' -import { onAuthStateChanged, type User } from 'firebase/auth' -import { auth } from './firebase' +import { createContext, useCallback, useContext, useEffect, useState, type ReactNode } from 'react' +import { onAuthStateChanged, signOut as fbSignOut, type Auth, type User } from 'firebase/auth' -interface AuthContextValue { +export interface AuthContextValue { user: User | null claims: Record | null loading: boolean signOut: () => Promise + refreshClaims: () => Promise } const AuthContext = createContext(null) -export function AuthProvider({ children }: { children: ReactNode }) { +interface AuthProviderProps { + children: ReactNode + auth: Auth +} + +export function AuthProvider({ children, auth }: AuthProviderProps) { const [user, setUser] = useState(null) const [claims, setClaims] = useState | null>(null) const [loading, setLoading] = useState(true) + const refreshClaims = useCallback(async () => { + const currentUser = auth.currentUser + if (!currentUser) { + setClaims(null) + return + } + try { + const token = await currentUser.getIdTokenResult(true) + // Guard against stale user (sign-out or account switch during refresh) + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (auth.currentUser?.uid !== currentUser.uid) return + setClaims(token.claims as Record) + } catch (err: unknown) { + console.error('[AuthProvider] token refresh failed:', err) + setClaims(null) + } + }, [auth]) + useEffect(() => { let active = true const unsub = onAuthStateChanged(auth, (u) => { @@ -46,15 +69,14 @@ export function AuthProvider({ children }: { children: ReactNode }) { active = false unsub() } - }, []) + }, [auth]) - async function signOut() { - const { signOut: fbSignOut } = await import('firebase/auth') + const signOut = useCallback(async () => { await fbSignOut(auth) - } + }, [auth]) return ( - + {children} ) diff --git a/packages/shared-ui/src/index.ts b/packages/shared-ui/src/index.ts index 04914a33..2cfd6036 100644 --- a/packages/shared-ui/src/index.ts +++ b/packages/shared-ui/src/index.ts @@ -1,2 +1,3 @@ -// Primitive components filled in Phase 2+. -export {} +export { AuthProvider, useAuth } from './auth-provider.js' +export type { AuthContextValue } from './auth-provider.js' +export { ProtectedRoute } from './protected-route.js' diff --git a/packages/shared-ui/src/protected-route.tsx b/packages/shared-ui/src/protected-route.tsx new file mode 100644 index 00000000..6058c008 --- /dev/null +++ b/packages/shared-ui/src/protected-route.tsx @@ -0,0 +1,45 @@ +import { type ReactNode } from 'react' +import { Navigate, useLocation } from 'react-router-dom' +import { useAuth } from './auth-provider.js' + +interface ProtectedRouteProps { + children: ReactNode + allowedRoles: string[] + requireActive?: boolean + requireMunicipalityIdForRoles?: string[] + loadingFallback?: ReactNode + unauthorizedFallback?: ReactNode +} + +export function ProtectedRoute({ + children, + allowedRoles, + requireActive = false, + requireMunicipalityIdForRoles = [], + loadingFallback =
Loading…
, + unauthorizedFallback =
Access denied.
, +}: ProtectedRouteProps) { + const { user, claims, loading } = useAuth() + const location = useLocation() + + if (loading) return loadingFallback + if (!user) return + + const role = typeof claims?.role === 'string' ? claims.role : '' + if (!allowedRoles.includes(role)) { + return unauthorizedFallback + } + + if (requireActive && claims?.active !== true) { + return unauthorizedFallback + } + + if ( + requireMunicipalityIdForRoles.includes(role) && + (typeof claims?.municipalityId !== 'string' || !claims.municipalityId) + ) { + return unauthorizedFallback + } + + return <>{children} +} diff --git a/packages/shared-ui/vitest.config.ts b/packages/shared-ui/vitest.config.ts new file mode 100644 index 00000000..5eaa352f --- /dev/null +++ b/packages/shared-ui/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + globals: true, + environment: 'happy-dom', + include: ['src/**/*.test.ts', 'src/**/*.test.tsx'], + setupFiles: ['@testing-library/jest-dom/vitest'], + }, +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0e3c39ad..be29965f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -308,12 +308,37 @@ importers: packages/shared-ui: dependencies: + firebase: + specifier: ^12.0.0 + version: 12.12.0 react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^18.3.1 || ^19.0.0 + version: 19.2.5 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^18.3.1 || ^19.0.0 + version: 19.2.5(react@19.2.5) + react-router-dom: + specifier: ^7.0.0 + version: 7.14.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + devDependencies: + '@testing-library/jest-dom': + specifier: ^6.4.0 + version: 6.9.1 + '@testing-library/react': + specifier: ^16.0.0 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@types/react': + specifier: ^19.2.14 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) + happy-dom: + specifier: ^15.11.0 + version: 15.11.7 + vitest: + specifier: ^4.1.4 + version: 4.1.4(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4)(happy-dom@15.11.7)(msw@2.13.4(@types/node@25.6.0)(typescript@6.0.3))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0)(yaml@2.8.3)) packages/shared-validators: dependencies: @@ -2457,6 +2482,10 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + entities@7.0.1: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} @@ -2924,6 +2953,10 @@ packages: resolution: {integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==} engines: {node: '>=14.0.0'} + happy-dom@15.11.7: + resolution: {integrity: sha512-KyrFvnl+J9US63TEzwoiJOQzZBJY7KgBushJA8X61DMbNsH+2ONkDuLDnCnwUiPTF42tLoEmrPyoqbenVA5zrg==} + engines: {node: '>=18.0.0'} + happy-dom@20.9.0: resolution: {integrity: sha512-GZZ9mKe8r646NUAf/zemnGbjYh4Bt8/MqASJY+pSm5ZDtc3YQox+4gsLI7yi1hba6o+eCsGxpHn5+iEVn31/FQ==} engines: {node: '>=20.0.0'} @@ -4001,11 +4034,6 @@ packages: resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} engines: {node: '>= 0.8'} - react-dom@18.3.1: - resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} - peerDependencies: - react: ^18.3.1 - react-dom@19.2.5: resolution: {integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==} peerDependencies: @@ -4044,10 +4072,6 @@ packages: react-dom: optional: true - react@18.3.1: - resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} - engines: {node: '>=0.10.0'} - react@19.2.5: resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==} engines: {node: '>=0.10.0'} @@ -4148,9 +4172,6 @@ packages: resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} engines: {node: '>=11.0.0'} - scheduler@0.23.2: - resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} - scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} @@ -4691,6 +4712,10 @@ packages: webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + websocket-driver@0.7.4: resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==} engines: {node: '>=0.8.0'} @@ -7199,6 +7224,8 @@ snapshots: once: 1.4.0 optional: true + entities@4.5.0: {} + entities@7.0.1: {} env-paths@2.2.1: {} @@ -7957,6 +7984,12 @@ snapshots: - supports-color optional: true + happy-dom@15.11.7: + dependencies: + entities: 4.5.0 + webidl-conversions: 7.0.0 + whatwg-mimetype: 3.0.0 + happy-dom@20.9.0: dependencies: '@types/node': 20.19.39 @@ -9244,12 +9277,6 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 - react-dom@18.3.1(react@18.3.1): - dependencies: - loose-envify: 1.4.0 - react: 18.3.1 - scheduler: 0.23.2 - react-dom@19.2.5(react@19.2.5): dependencies: react: 19.2.5 @@ -9282,10 +9309,6 @@ snapshots: optionalDependencies: react-dom: 19.2.5(react@19.2.5) - react@18.3.1: - dependencies: - loose-envify: 1.4.0 - react@19.2.5: {} readable-stream@3.6.2: @@ -9417,10 +9440,6 @@ snapshots: sax@1.6.0: {} - scheduler@0.23.2: - dependencies: - loose-envify: 1.4.0 - scheduler@0.27.0: {} semver@6.3.1: {} @@ -10026,6 +10045,36 @@ snapshots: transitivePeerDependencies: - msw + vitest@4.1.4(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4)(happy-dom@15.11.7)(msw@2.13.4(@types/node@25.6.0)(typescript@6.0.3))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0)(yaml@2.8.3)): + dependencies: + '@vitest/expect': 4.1.4 + '@vitest/mocker': 4.1.4(msw@2.13.4(@types/node@25.6.0)(typescript@6.0.3))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/pretty-format': 4.1.4 + '@vitest/runner': 4.1.4 + '@vitest/snapshot': 4.1.4 + '@vitest/spy': 4.1.4 + '@vitest/utils': 4.1.4 + es-module-lexer: 2.0.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.1.1 + tinyglobby: 0.2.16 + tinyrainbow: 3.1.0 + vite: 8.0.8(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0)(yaml@2.8.3) + why-is-node-running: 2.3.0 + optionalDependencies: + '@opentelemetry/api': 1.9.1 + '@types/node': 25.6.0 + '@vitest/coverage-v8': 4.1.4(vitest@4.1.4) + happy-dom: 15.11.7 + transitivePeerDependencies: + - msw + vitest@4.1.4(@opentelemetry/api@1.9.1)(@types/node@25.6.0)(@vitest/coverage-v8@4.1.4)(happy-dom@20.9.0)(msw@2.13.4(@types/node@25.6.0)(typescript@6.0.3))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.7)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@vitest/expect': 4.1.4 @@ -10067,6 +10116,8 @@ snapshots: webidl-conversions@3.0.1: optional: true + webidl-conversions@7.0.0: {} + websocket-driver@0.7.4: dependencies: http-parser-js: 0.5.10 From c2f495174e8fdca6abd544186707dfed47645331 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 24 Apr 2026 07:07:32 +0800 Subject: [PATCH 11/31] test(shared-ui): add characterization tests for AuthProvider and ProtectedRoute - P2 #5: Add 11 tests covering AuthProvider (6) and ProtectedRoute (5) - AuthProvider: null user, authenticated user, signOut, refreshClaims, getIdTokenResult error, useAuth guard - ProtectedRoute: redirect to login, allowed role, denied role, requireActive deny, requireMunicipalityIdForRoles deny --- docs/learnings.md | 8 + docs/progress.md | 51 +++++ packages/shared-ui/src/auth-provider.test.tsx | 192 ++++++++++++++++++ .../shared-ui/src/protected-route.test.tsx | 167 +++++++++++++++ 4 files changed, 418 insertions(+) create mode 100644 packages/shared-ui/src/auth-provider.test.tsx create mode 100644 packages/shared-ui/src/protected-route.test.tsx diff --git a/docs/learnings.md b/docs/learnings.md index 60296563..42b45473 100644 --- a/docs/learnings.md +++ b/docs/learnings.md @@ -64,3 +64,11 @@ Durable rules worth keeping across sessions. - `navigator.clipboard` in happy-dom often needs to be defined as an own property before spying. - Risky backend changes need emulator verification first and should not go to prod in the same session. + +## Refactoring / Monorepo Hygiene + +- When extracting a module and renaming the original file (e.g., `inbound.ts` → `parser.ts`), stale build artifacts (`lib/inbound.js`, `.d.ts`, `.map`) must be manually removed or they will confuse future readers and tooling. +- Consolidating duplicated React components across apps into a shared package requires adding the consuming app's runtime dependencies (e.g., `firebase`, `react-router-dom`) as `peerDependencies` in the shared package; otherwise typecheck passes locally but breaks in isolation. +- A shared `AuthProvider` that uses `Record` for claims pushes type-narrowing burden to every consumer. This is acceptable for a shared boundary, but consumers should validate with `typeof` checks rather than casting. +- `useCallback` is required for functions exposed through context (like `refreshClaims` and `signOut`) to prevent infinite re-render loops in consumers that include them in `useEffect` dependency arrays. +- When mocking Firebase Auth's `onAuthStateChanged` with `vi.mock('firebase/auth', ...)`, the mock must return an unsubscribe function; the real `onAuthStateChanged` is a module-level function, not a method on the `Auth` instance. diff --git a/docs/progress.md b/docs/progress.md index be64d267..3b38a769 100644 --- a/docs/progress.md +++ b/docs/progress.md @@ -2,6 +2,57 @@ ## Current +### Refactor Audit 2026-04-23 — Implementation Continuation (2026-04-24) + +**Branch:** `refactor/audit-2026-04-23-continuation` + +**Completed Items:** + +- **P1 #3:** `inbound.ts` split into modules — `parser.ts` (192 lines), `gazetteer.ts`, `levenshtein.ts`, `auto-reply.ts`. All 6 `@ts-expect-error` comments eliminated. Stale `lib/inbound.js` build artifacts cleaned up. +- **P1 #4:** `dispatch-responder.ts` extraction — validation (`dispatch-responder-validation.ts`), notification (`dispatch-responder-notify.ts`), Firestore writes (`dispatch-responder-writes.ts`). Added `municipalityId` runtime validation and fixed spread-order bug in validatedResponder. +- **P2 #7:** Standardized all remaining implicit-any catch patterns — 14 occurrences across 10 production source files converted to `catch (err: unknown)` / `catch (error: unknown)`. One missed occurrence in `sms-inbound-processor.ts:184` also fixed. +- **P2 #8:** Replaced `console.log` with `console.info` in `shared-validators/src/logging.ts` with clarifying comment about Cloud Functions stdout ingestion. +- **P3 #9:** Confirmed `batchResponse: any` already removed from `fcm-send.ts`; no source `any` types remain in production code. +- **P3 #10:** Converted TODO to `TICKET(BANTAYOG-PHASE6)` with deferral rationale. +- **P2 #6:** Consolidated auth provider + protected route into `shared-ui` — new `AuthProvider` (with `active` guard, `refreshClaims`, `useCallback` memoization) and `ProtectedRoute` (configurable via `allowedRoles`, `requireActive`, `requireMunicipalityIdForRoles` props). Updated both `admin-desktop` and `responder-app` to consume from `@bantayog/shared-ui`. Deleted 4 duplicated files. +- **P2 #5 (shared-ui):** Added test infrastructure (vitest, testing-library, happy-dom) and 11 characterization tests for `AuthProvider` and `ProtectedRoute`. + +**Files Created:** + +- `packages/shared-ui/src/auth-provider.tsx` +- `packages/shared-ui/src/protected-route.tsx` +- `packages/shared-ui/src/auth-provider.test.tsx` +- `packages/shared-ui/src/protected-route.test.tsx` +- `packages/shared-ui/vitest.config.ts` +- `functions/src/callables/dispatch-responder-validation.ts` +- `functions/src/callables/dispatch-responder-notify.ts` +- `functions/src/callables/dispatch-responder-writes.ts` + +**Files Deleted:** + +- `apps/admin-desktop/src/app/auth-provider.tsx` +- `apps/admin-desktop/src/app/protected-route.tsx` +- `apps/responder-app/src/app/auth-provider.tsx` +- `apps/responder-app/src/app/protected-route.tsx` +- `packages/shared-sms-parser/src/inbound.ts` (renamed to `parser.ts`) +- `packages/shared-sms-parser/lib/inbound.js` (stale build artifact) +- `packages/shared-sms-parser/lib/inbound.d.ts` (stale build artifact) + +**Verification:** + +- `npx turbo run lint typecheck` — PASS (25/25) +- `pnpm --filter @bantayog/shared-sms-parser test` — PASS (13/13) +- `pnpm --filter @bantayog/shared-ui test` — PASS (11/11) + +**Remaining (deferred to future sprints):** + +- `apps/admin-desktop` — 0 tests (LoginPage, TriageQueuePage, DispatchModal) +- `apps/responder-app` — 1 test (DispatchListPage, DispatchDetailPage, useAcceptDispatch) +- `packages/shared-data` — 0 tests +- `packages/shared-types` — 0 tests + +--- + ### Step2WhoWhere Refactoring — Phase 3 & 4: Components & Simplification (2026-04-23) - Status: Phases 3-4 COMPLETE — integrated extracted components and simplified main component diff --git a/packages/shared-ui/src/auth-provider.test.tsx b/packages/shared-ui/src/auth-provider.test.tsx new file mode 100644 index 00000000..3af23ee1 --- /dev/null +++ b/packages/shared-ui/src/auth-provider.test.tsx @@ -0,0 +1,192 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { render, screen, waitFor, act } from '@testing-library/react' +import { AuthProvider, useAuth } from './auth-provider.js' + +let mockOnAuthStateChanged = vi.fn() +let mockSignOut = vi.fn() + +vi.mock('firebase/auth', () => ({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + onAuthStateChanged: (...args: unknown[]) => mockOnAuthStateChanged(...args), + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + signOut: (...args: unknown[]) => mockSignOut(...args), +})) + +function TestConsumer() { + const { user, claims, loading, signOut, refreshClaims } = useAuth() + return ( +
+
{loading ? 'loading' : 'ready'}
+
{user ? user.uid : 'none'}
+
{claims ? JSON.stringify(claims) : 'none'}
+ + +
+ ) +} + +describe('AuthProvider', () => { + beforeEach(() => { + mockOnAuthStateChanged = vi.fn() + mockSignOut = vi.fn().mockResolvedValue(undefined) + }) + + it('shows ready with no user when auth state is null', async () => { + const unsubscribe = vi.fn() + mockOnAuthStateChanged.mockImplementation((_auth, cb) => { + cb(null) + return unsubscribe + }) + + const mockAuth = { currentUser: null } as unknown as import('firebase/auth').Auth + + render( + + + , + ) + + await waitFor(() => { + expect(screen.getByTestId('loading').textContent).toBe('ready') + }) + expect(screen.getByTestId('user').textContent).toBe('none') + expect(screen.getByTestId('claims').textContent).toBe('none') + }) + + it('sets user and claims when authenticated', async () => { + const unsubscribe = vi.fn() + const mockUser = { + uid: 'test-uid', + getIdTokenResult: vi.fn().mockResolvedValue({ + claims: { role: 'responder', municipalityId: 'daet' }, + }), + } + + mockOnAuthStateChanged.mockImplementation((_auth, cb) => { + cb(mockUser) + return unsubscribe + }) + + const mockAuth = { currentUser: mockUser } as unknown as import('firebase/auth').Auth + + render( + + + , + ) + + await waitFor(() => { + expect(screen.getByTestId('loading').textContent).toBe('ready') + }) + expect(screen.getByTestId('user').textContent).toBe('test-uid') + expect(screen.getByTestId('claims').textContent).toBe( + JSON.stringify({ role: 'responder', municipalityId: 'daet' }), + ) + }) + + it('calls signOut when signOut button is clicked', async () => { + mockOnAuthStateChanged.mockImplementation((_auth, cb) => { + cb(null) + return vi.fn() + }) + + const mockAuth = { currentUser: null } as unknown as import('firebase/auth').Auth + + render( + + + , + ) + + await waitFor(() => { + expect(screen.getByTestId('loading').textContent).toBe('ready') + }) + + act(() => { + screen.getByTestId('signout').click() + }) + + expect(mockSignOut).toHaveBeenCalledWith(mockAuth) + }) + + it('refreshes claims when refreshClaims is called', async () => { + const unsubscribe = vi.fn() + const mockUser = { + uid: 'test-uid', + getIdTokenResult: vi.fn().mockResolvedValue({ + claims: { role: 'responder' }, + }), + } + + mockOnAuthStateChanged.mockImplementation((_auth, cb) => { + cb(mockUser) + return unsubscribe + }) + + const mockAuth = { currentUser: mockUser } as unknown as import('firebase/auth').Auth + + render( + + + , + ) + + await waitFor(() => { + expect(screen.getByTestId('loading').textContent).toBe('ready') + }) + expect(screen.getByTestId('claims').textContent).toBe(JSON.stringify({ role: 'responder' })) + + mockUser.getIdTokenResult.mockResolvedValue({ + claims: { role: 'responder', municipalityId: 'daet' }, + }) + + act(() => { + screen.getByTestId('refresh').click() + }) + + await waitFor(() => { + expect(screen.getByTestId('claims').textContent).toBe( + JSON.stringify({ role: 'responder', municipalityId: 'daet' }), + ) + }) + }) + + it('sets claims to null when getIdTokenResult fails', async () => { + const unsubscribe = vi.fn() + const mockUser = { + uid: 'test-uid', + getIdTokenResult: vi.fn().mockRejectedValue(new Error('token expired')), + } + + mockOnAuthStateChanged.mockImplementation((_auth, cb) => { + cb(mockUser) + return unsubscribe + }) + + const mockAuth = { currentUser: mockUser } as unknown as import('firebase/auth').Auth + + render( + + + , + ) + + await waitFor(() => { + expect(screen.getByTestId('loading').textContent).toBe('ready') + }) + expect(screen.getByTestId('claims').textContent).toBe('none') + }) + + it('throws when useAuth is called outside AuthProvider', () => { + function BadComponent() { + useAuth() + return null + } + + expect(() => render()).toThrow('useAuth must be used inside ') + }) +}) diff --git a/packages/shared-ui/src/protected-route.test.tsx b/packages/shared-ui/src/protected-route.test.tsx new file mode 100644 index 00000000..22aa9b48 --- /dev/null +++ b/packages/shared-ui/src/protected-route.test.tsx @@ -0,0 +1,167 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { render, screen } from '@testing-library/react' +import { MemoryRouter, Routes, Route } from 'react-router-dom' +import { ProtectedRoute } from './protected-route.js' +import { AuthProvider } from './auth-provider.js' + +let mockOnAuthStateChanged = vi.fn() + +vi.mock('firebase/auth', () => ({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + onAuthStateChanged: (...args: unknown[]) => mockOnAuthStateChanged(...args), + signOut: vi.fn().mockResolvedValue(undefined), +})) + +function TestApp({ + auth, + initialEntry = '/', +}: { + auth: import('firebase/auth').Auth + initialEntry?: string +}) { + return ( + + + + Login
} /> + +
Protected
+ + } + /> + + + + ) +} + +describe('ProtectedRoute', () => { + beforeEach(() => { + mockOnAuthStateChanged = vi.fn() + }) + + it('redirects to login when not authenticated', async () => { + mockOnAuthStateChanged.mockImplementation((_auth, cb) => { + cb(null) + return vi.fn() + }) + + const mockAuth = { currentUser: null } as unknown as import('firebase/auth').Auth + render() + + expect(await screen.findByTestId('login')).toBeDefined() + }) + + it('renders children when user has allowed role', async () => { + const mockUser = { + uid: 'u1', + getIdTokenResult: vi.fn().mockResolvedValue({ + claims: { role: 'responder' }, + }), + } + + mockOnAuthStateChanged.mockImplementation((_auth, cb) => { + cb(mockUser) + return vi.fn() + }) + + const mockAuth = { currentUser: mockUser } as unknown as import('firebase/auth').Auth + render() + + expect(await screen.findByTestId('protected')).toBeDefined() + }) + + it('renders unauthorized fallback when role is not allowed', async () => { + const mockUser = { + uid: 'u1', + getIdTokenResult: vi.fn().mockResolvedValue({ + claims: { role: 'municipal_admin' }, + }), + } + + mockOnAuthStateChanged.mockImplementation((_auth, cb) => { + cb(mockUser) + return vi.fn() + }) + + const mockAuth = { currentUser: mockUser } as unknown as import('firebase/auth').Auth + render() + + expect(await screen.findByText('Access denied.')).toBeDefined() + }) + + it('renders unauthorized when requireActive is true and user is inactive', async () => { + const mockUser = { + uid: 'u1', + getIdTokenResult: vi.fn().mockResolvedValue({ + claims: { role: 'responder', active: false }, + }), + } + + mockOnAuthStateChanged.mockImplementation((_auth, cb) => { + cb(mockUser) + return vi.fn() + }) + + const mockAuth = { currentUser: mockUser } as unknown as import('firebase/auth').Auth + render( + + + + +
Protected
+ + } + /> +
+
+
, + ) + + expect(await screen.findByText('Access denied.')).toBeDefined() + }) + + it('renders unauthorized when municipalityId is required but missing', async () => { + const mockUser = { + uid: 'u1', + getIdTokenResult: vi.fn().mockResolvedValue({ + claims: { role: 'municipal_admin', active: true }, + }), + } + + mockOnAuthStateChanged.mockImplementation((_auth, cb) => { + cb(mockUser) + return vi.fn() + }) + + const mockAuth = { currentUser: mockUser } as unknown as import('firebase/auth').Auth + render( + + + + +
Protected
+ + } + /> +
+
+
, + ) + + expect(await screen.findByText('Access denied.')).toBeDefined() + }) +}) From 29e4eeee428ea1cc8ff9e497f37c940e86d9cc94 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 24 Apr 2026 07:07:51 +0800 Subject: [PATCH 12/31] chore(functions): rebuild compiled artifacts after source changes --- .../__tests__/callables/https-error.test.d.ts | 2 + .../callables/https-error.test.d.ts.map | 1 + .../__tests__/callables/https-error.test.js | 72 +++++++++++++++++++ .../callables/https-error.test.js.map | 1 + .../dispatch-mirror-to-report.test.js | 15 ++-- .../dispatch-mirror-to-report.test.js.map | 2 +- .../firestore/sms-inbound-processor.d.ts.map | 2 +- .../lib/firestore/sms-inbound-processor.js | 10 ++- .../firestore/sms-inbound-processor.js.map | 2 +- functions/lib/http/sms-inbound.d.ts.map | 2 +- functions/lib/http/sms-inbound.js | 7 +- functions/lib/http/sms-inbound.js.map | 2 +- functions/lib/services/fcm-send.d.ts.map | 2 +- functions/lib/services/fcm-send.js | 4 +- functions/lib/services/fcm-send.js.map | 2 +- .../services/sms-providers/semaphore.d.ts.map | 2 +- .../lib/services/sms-providers/semaphore.js | 9 ++- .../services/sms-providers/semaphore.js.map | 2 +- .../lib/triggers/on-media-finalize.d.ts.map | 2 +- functions/lib/triggers/on-media-finalize.js | 3 +- .../lib/triggers/on-media-finalize.js.map | 2 +- 21 files changed, 123 insertions(+), 23 deletions(-) create mode 100644 functions/lib/__tests__/callables/https-error.test.d.ts create mode 100644 functions/lib/__tests__/callables/https-error.test.d.ts.map create mode 100644 functions/lib/__tests__/callables/https-error.test.js create mode 100644 functions/lib/__tests__/callables/https-error.test.js.map diff --git a/functions/lib/__tests__/callables/https-error.test.d.ts b/functions/lib/__tests__/callables/https-error.test.d.ts new file mode 100644 index 00000000..6961d1ef --- /dev/null +++ b/functions/lib/__tests__/callables/https-error.test.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=https-error.test.d.ts.map \ No newline at end of file diff --git a/functions/lib/__tests__/callables/https-error.test.d.ts.map b/functions/lib/__tests__/callables/https-error.test.d.ts.map new file mode 100644 index 00000000..17d66b71 --- /dev/null +++ b/functions/lib/__tests__/callables/https-error.test.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"https-error.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/callables/https-error.test.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/functions/lib/__tests__/callables/https-error.test.js b/functions/lib/__tests__/callables/https-error.test.js new file mode 100644 index 00000000..d1788487 --- /dev/null +++ b/functions/lib/__tests__/callables/https-error.test.js @@ -0,0 +1,72 @@ +import { describe, it, expect } from 'vitest'; +import { HttpsError } from 'firebase-functions/v2/https'; +import { BANTAYOG_TO_HTTPS_CODE, bantayogErrorToHttps, requireAuth, } from '../../callables/https-error.js'; +import { BantayogError, BantayogErrorCode } from '@bantayog/shared-validators'; +describe('BANTAYOG_TO_HTTPS_CODE', () => { + it('maps every BantayogErrorCode to a FunctionsErrorCode', () => { + // Iterate actual enum values, not map keys, to catch unmapped entries + const codes = Object.values(BantayogErrorCode).filter((value) => typeof value === 'string'); + expect(codes.length).toBeGreaterThan(0); + for (const code of codes) { + expect(BANTAYOG_TO_HTTPS_CODE[code]).toBeDefined(); + expect(typeof BANTAYOG_TO_HTTPS_CODE[code]).toBe('string'); + } + }); +}); +describe('bantayogErrorToHttps', () => { + it('converts a BantayogError to an HttpsError with the right code', () => { + const err = new BantayogError(BantayogErrorCode.VALIDATION_ERROR, 'bad input', { field: 'x' }); + const httpsErr = bantayogErrorToHttps(err); + expect(httpsErr).toBeInstanceOf(HttpsError); + expect(httpsErr.code).toBe('invalid-argument'); + expect(httpsErr.message).toBe('bad input'); + expect(httpsErr.details).toEqual({ field: 'x' }); + }); + it('converts NOT_FOUND to not-found', () => { + const err = new BantayogError(BantayogErrorCode.NOT_FOUND, 'missing'); + const httpsErr = bantayogErrorToHttps(err); + expect(httpsErr.code).toBe('not-found'); + }); +}); +describe('requireAuth', () => { + it('throws unauthenticated when request.auth is null', () => { + expect(() => requireAuth({ auth: null }, ['municipal_admin'])).toThrow(HttpsError); + expect(() => requireAuth({ auth: null }, ['municipal_admin'])).toThrow('sign-in required'); + }); + it('throws unauthenticated when request.auth is undefined', () => { + expect(() => requireAuth({}, ['municipal_admin'])).toThrow(HttpsError); + }); + it('throws permission-denied when role is not in allowed list', () => { + const request = { + auth: { + uid: 'u1', + token: { role: 'citizen' }, + }, + }; + expect(() => requireAuth(request, ['municipal_admin'])).toThrow('role citizen is not allowed'); + }); + it('throws permission-denied when role is missing', () => { + const request = { + auth: { + uid: 'u1', + token: {}, + }, + }; + expect(() => requireAuth(request, ['municipal_admin'])).toThrow('role undefined is not allowed'); + }); + it('returns uid and claims when role is allowed', () => { + const request = { + auth: { + uid: 'u1', + token: { role: 'municipal_admin', municipalityId: 'm1' }, + }, + }; + const result = requireAuth(request, ['municipal_admin', 'superadmin']); + expect(result.uid).toBe('u1'); + expect(result.claims).toEqual({ + role: 'municipal_admin', + municipalityId: 'm1', + }); + }); +}); +//# sourceMappingURL=https-error.test.js.map \ No newline at end of file diff --git a/functions/lib/__tests__/callables/https-error.test.js.map b/functions/lib/__tests__/callables/https-error.test.js.map new file mode 100644 index 00000000..bb7093a7 --- /dev/null +++ b/functions/lib/__tests__/callables/https-error.test.js.map @@ -0,0 +1 @@ +{"version":3,"file":"https-error.test.js","sourceRoot":"","sources":["../../../src/__tests__/callables/https-error.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/functions/lib/__tests__/triggers/dispatch-mirror-to-report.test.js b/functions/lib/__tests__/triggers/dispatch-mirror-to-report.test.js index 294097ff..75763987 100644 --- a/functions/lib/__tests__/triggers/dispatch-mirror-to-report.test.js +++ b/functions/lib/__tests__/triggers/dispatch-mirror-to-report.test.js @@ -6,7 +6,9 @@ import { getFirestore } from 'firebase-admin/firestore'; import { dispatchMirrorToReportCore } from '../../triggers/dispatch-mirror-to-report.js'; const ts = 1713350400000; process.env.FIRESTORE_EMULATOR_HOST ??= 'localhost:8081'; -const app = getApps()[0] ?? initializeApp({ projectId: 'dispatch-mirror-test' }); +const appName = 'dispatch-mirror-test'; +const app = getApps().find((a) => a.name === appName) ?? + initializeApp({ projectId: 'dispatch-mirror-test' }, appName); const adminDb = getFirestore(app); // --------------------------------------------------------------------------- // Test environment @@ -55,7 +57,10 @@ async function seedReportAtStatusJS(reportId, status) { } /** Seeds a dispatch using JS SDK via withSecurityRulesDisabled. */ async function seedDispatchJS(dispatchId, reportId, status, correlationId) { - await adminDb.collection('dispatches').doc(dispatchId).set({ + await adminDb + .collection('dispatches') + .doc(dispatchId) + .set({ dispatchId, reportId, status, @@ -130,8 +135,8 @@ describe('dispatchMirrorToReport', () => { const dispatchId = `dispatch-${crypto.randomUUID()}`; await seedDispatchJS(dispatchId, 'nonexistent-report', 'pending'); await withAdminDb(async (db) => { - // Should not throw — trigger skips gracefully - await expect(dispatchMirrorToReportCore({ + // Should not throw — trigger skips gracefully when report is missing + await dispatchMirrorToReportCore({ db, dispatchId, beforeData: { status: 'pending' }, @@ -140,7 +145,7 @@ describe('dispatchMirrorToReport', () => { reportId: 'nonexistent-report', correlationId: crypto.randomUUID(), }, - })).resolves.not.toThrow(); + }); }); }); it('reverts declined dispatches back to verified and clears currentDispatchId', async () => { diff --git a/functions/lib/__tests__/triggers/dispatch-mirror-to-report.test.js.map b/functions/lib/__tests__/triggers/dispatch-mirror-to-report.test.js.map index 9cf4b1e4..e4451952 100644 --- a/functions/lib/__tests__/triggers/dispatch-mirror-to-report.test.js.map +++ b/functions/lib/__tests__/triggers/dispatch-mirror-to-report.test.js.map @@ -1 +1 @@ -{"version":3,"file":"dispatch-mirror-to-report.test.js","sourceRoot":"","sources":["../../../src/__tests__/triggers/dispatch-mirror-to-report.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"dispatch-mirror-to-report.test.js","sourceRoot":"","sources":["../../../src/__tests__/triggers/dispatch-mirror-to-report.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/functions/lib/firestore/sms-inbound-processor.d.ts.map b/functions/lib/firestore/sms-inbound-processor.d.ts.map index 9d0dd860..d4f30bae 100644 --- a/functions/lib/firestore/sms-inbound-processor.d.ts.map +++ b/functions/lib/firestore/sms-inbound-processor.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"sms-inbound-processor.d.ts","sourceRoot":"","sources":["../../src/firestore/sms-inbound-processor.ts"],"names":[],"mappings":"AAgCA,eAAO,MAAM,mBAAmB;;GA8J/B,CAAA"} \ No newline at end of file +{"version":3,"file":"sms-inbound-processor.d.ts","sourceRoot":"","sources":["../../src/firestore/sms-inbound-processor.ts"],"names":[],"mappings":"AA2CA,eAAO,MAAM,mBAAmB;;GA+J/B,CAAA"} \ No newline at end of file diff --git a/functions/lib/firestore/sms-inbound-processor.js b/functions/lib/firestore/sms-inbound-processor.js index bd9654d8..4bf5f846 100644 --- a/functions/lib/firestore/sms-inbound-processor.js +++ b/functions/lib/firestore/sms-inbound-processor.js @@ -13,7 +13,14 @@ function generatePublicRef() { } function decryptMsisdn(encrypted) { if (!encrypted.startsWith('unencrypted:')) { + // Validate encryption key format BEFORE using it + if (!/^[0-9a-fA-F]{64}$/.test(ENCRYPTION_KEY)) { + throw new Error('SMS_MSISDN_ENCRYPTION_KEY must be 64 hex characters (32 bytes for AES-256-GCM)'); + } const key = Buffer.from(ENCRYPTION_KEY, 'hex'); + if (key.length !== 32) { + throw new Error(`SMS_MSISDN_ENCRYPTION_KEY decoded to ${String(key.length)} bytes, expected 32`); + } const parsed = JSON.parse(encrypted); const decipher = createDecipheriv('aes-256-gcm', key, Buffer.from(parsed.iv, 'hex')); decipher.setAuthTag(Buffer.from(parsed.tag, 'hex')); @@ -126,11 +133,12 @@ export const smsInboundProcessor = onDocumentCreated({ try { recipientMsisdn = decryptMsisdn(senderMsisdnEnc); } - catch { + catch (err) { log({ severity: 'WARNING', code: 'decrypt.failed', message: `MSISDN decryption failed for ${msgId} — skipping pending_review reply`, + data: { error: String(err) }, }); } if (recipientMsisdn) { diff --git a/functions/lib/firestore/sms-inbound-processor.js.map b/functions/lib/firestore/sms-inbound-processor.js.map index c00d68f9..991adb46 100644 --- a/functions/lib/firestore/sms-inbound-processor.js.map +++ b/functions/lib/firestore/sms-inbound-processor.js.map @@ -1 +1 @@ -{"version":3,"file":"sms-inbound-processor.js","sourceRoot":"","sources":["../../src/firestore/sms-inbound-processor.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"sms-inbound-processor.js","sourceRoot":"","sources":["../../src/firestore/sms-inbound-processor.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/functions/lib/http/sms-inbound.d.ts.map b/functions/lib/http/sms-inbound.d.ts.map index d4a4c35e..31361688 100644 --- a/functions/lib/http/sms-inbound.d.ts.map +++ b/functions/lib/http/sms-inbound.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"sms-inbound.d.ts","sourceRoot":"","sources":["../../src/http/sms-inbound.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAA;AA4BvE,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,SAAS,CAAA;IACb,IAAI,EAAE,OAAO,CAAA;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;IAC3C,EAAE,EAAE,MAAM,CAAA;IACV,GAAG,EAAE,MAAM,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,2BAA2B;IAC1C,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;IAC7B,IAAI,CAAC,EAAE;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAA;CACvB;AAED,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,yBAAyB,GAC9B,OAAO,CAAC,2BAA2B,CAAC,CA0FtC;AAED,eAAO,MAAM,iBAAiB,kDAc7B,CAAA"} \ No newline at end of file +{"version":3,"file":"sms-inbound.d.ts","sourceRoot":"","sources":["../../src/http/sms-inbound.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAA;AA4BvE,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,SAAS,CAAA;IACb,IAAI,EAAE,OAAO,CAAA;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;IAC3C,EAAE,EAAE,MAAM,CAAA;IACV,GAAG,EAAE,MAAM,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,2BAA2B;IAC1C,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;IAC7B,IAAI,CAAC,EAAE;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAA;CACvB;AAED,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,yBAAyB,GAC9B,OAAO,CAAC,2BAA2B,CAAC,CA6FtC;AAED,eAAO,MAAM,iBAAiB,kDAc7B,CAAA"} \ No newline at end of file diff --git a/functions/lib/http/sms-inbound.js b/functions/lib/http/sms-inbound.js index ee856119..a6bccd37 100644 --- a/functions/lib/http/sms-inbound.js +++ b/functions/lib/http/sms-inbound.js @@ -58,13 +58,16 @@ export async function smsInboundWebhookCore(deps) { const salt = process.env.SMS_MSISDN_HASH_SALT ?? ''; msisdnHash = hashMsisdn(normalized, salt); } - catch { + catch (err) { msisdnHash = crypto.createHash('sha256').update(rawFrom).digest('hex'); log({ severity: 'WARNING', code: 'msisdn.invalid', message: 'Invalid MSISDN received', - data: { rawFrom: rawFrom.slice(0, 6) + '****' }, + data: { + rawFrom: rawFrom.slice(0, 6) + '****', + errorType: err instanceof Error ? err.name : 'UnknownError', + }, }); } const msgId = globeMsgId ?? buildMsgId(); diff --git a/functions/lib/http/sms-inbound.js.map b/functions/lib/http/sms-inbound.js.map index d66fde84..28f07141 100644 --- a/functions/lib/http/sms-inbound.js.map +++ b/functions/lib/http/sms-inbound.js.map @@ -1 +1 @@ -{"version":3,"file":"sms-inbound.js","sourceRoot":"","sources":["../../src/http/sms-inbound.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"sms-inbound.js","sourceRoot":"","sources":["../../src/http/sms-inbound.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/functions/lib/services/fcm-send.d.ts.map b/functions/lib/services/fcm-send.d.ts.map index cff18574..618c9f3b 100644 --- a/functions/lib/services/fcm-send.d.ts.map +++ b/functions/lib/services/fcm-send.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"fcm-send.d.ts","sourceRoot":"","sources":["../../src/services/fcm-send.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,eAAO,MAAM,qBAAqB,iDAAwC,CAAA;AAE1E,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAmExF"} \ No newline at end of file +{"version":3,"file":"fcm-send.d.ts","sourceRoot":"","sources":["../../src/services/fcm-send.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,eAAO,MAAM,qBAAqB,iDAAwC,CAAA;AAE1E,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAqExF"} \ No newline at end of file diff --git a/functions/lib/services/fcm-send.js b/functions/lib/services/fcm-send.js index 9b44ca7c..394c0715 100644 --- a/functions/lib/services/fcm-send.js +++ b/functions/lib/services/fcm-send.js @@ -53,7 +53,9 @@ export async function sendFcmToResponder(payload) { msg.data = data; result = await messaging.sendEachForMulticast(msg); } - catch { + catch (err) { + // Log full error server-side for debugging; keep warnings as stable codes + console.error('FCM send failed after retry:', err); warnings.push('fcm_network_error'); return { warnings }; } diff --git a/functions/lib/services/fcm-send.js.map b/functions/lib/services/fcm-send.js.map index f21c20dc..5c90a9a3 100644 --- a/functions/lib/services/fcm-send.js.map +++ b/functions/lib/services/fcm-send.js.map @@ -1 +1 @@ -{"version":3,"file":"fcm-send.js","sourceRoot":"","sources":["../../src/services/fcm-send.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"fcm-send.js","sourceRoot":"","sources":["../../src/services/fcm-send.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/functions/lib/services/sms-providers/semaphore.d.ts.map b/functions/lib/services/sms-providers/semaphore.d.ts.map index 4fb81808..0d8ae120 100644 --- a/functions/lib/services/sms-providers/semaphore.d.ts.map +++ b/functions/lib/services/sms-providers/semaphore.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"semaphore.d.ts","sourceRoot":"","sources":["../../../src/services/sms-providers/semaphore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAkBrD,wBAAgB,0BAA0B,IAAI,WAAW,CAmGxD"} \ No newline at end of file +{"version":3,"file":"semaphore.d.ts","sourceRoot":"","sources":["../../../src/services/sms-providers/semaphore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAkBrD,wBAAgB,0BAA0B,IAAI,WAAW,CAwGxD"} \ No newline at end of file diff --git a/functions/lib/services/sms-providers/semaphore.js b/functions/lib/services/sms-providers/semaphore.js index fe5c11e4..b7a44d7b 100644 --- a/functions/lib/services/sms-providers/semaphore.js +++ b/functions/lib/services/sms-providers/semaphore.js @@ -42,8 +42,13 @@ export function createSemaphoreSmsProvider() { try { data = (await res.json()); } - catch { - throw new SmsProviderRetryableError(`semaphore ${res.status.toString()}: unparseable response`, res.ok || res.status >= 500 ? 'provider_error' : 'network'); + catch (err) { + // 4xx errors are non-retryable (client request issues) + if (res.status >= 400 && res.status < 500 && res.status !== 429) { + return { accepted: false, reason: 'other', latencyMs: 0 }; + } + // 5xx and network errors are retryable + throw new SmsProviderRetryableError(`semaphore ${res.status.toString()}: unparseable response (${String(err)})`, res.status === 429 ? 'rate_limited' : 'provider_error'); } const status = data.status ?? ''; const errorsArr = data.errors ?? []; diff --git a/functions/lib/services/sms-providers/semaphore.js.map b/functions/lib/services/sms-providers/semaphore.js.map index 83d7e548..55314c83 100644 --- a/functions/lib/services/sms-providers/semaphore.js.map +++ b/functions/lib/services/sms-providers/semaphore.js.map @@ -1 +1 @@ -{"version":3,"file":"semaphore.js","sourceRoot":"","sources":["../../../src/services/sms-providers/semaphore.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"semaphore.js","sourceRoot":"","sources":["../../../src/services/sms-providers/semaphore.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/functions/lib/triggers/on-media-finalize.d.ts.map b/functions/lib/triggers/on-media-finalize.d.ts.map index f698b6c0..ff6c850b 100644 --- a/functions/lib/triggers/on-media-finalize.d.ts.map +++ b/functions/lib/triggers/on-media-finalize.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"on-media-finalize.d.ts","sourceRoot":"","sources":["../../src/triggers/on-media-finalize.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE;QAAE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAAA;KAAE,CAAA;IAC1C,UAAU,EAAE,MAAM,CAAA;IAClB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,YAAY,EAAE,CAAC,GAAG,EAAE;QAClB,QAAQ,EAAE,MAAM,CAAA;QAChB,WAAW,EAAE,MAAM,CAAA;QACnB,UAAU,EAAE,MAAM,CAAA;QAClB,QAAQ,EAAE,MAAM,CAAA;KACjB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,UAAU,GAAG,eAAe,CAAA;CACrC;AAED,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC,qBAAqB,CAAC,CA8DhC;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC,CAAA;IAC1E,IAAI,CACF,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GAClF,OAAO,CAAC,IAAI,CAAC,CAAA;IAChB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACxB"} \ No newline at end of file +{"version":3,"file":"on-media-finalize.d.ts","sourceRoot":"","sources":["../../src/triggers/on-media-finalize.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE;QAAE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAAA;KAAE,CAAA;IAC1C,UAAU,EAAE,MAAM,CAAA;IAClB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,YAAY,EAAE,CAAC,GAAG,EAAE;QAClB,QAAQ,EAAE,MAAM,CAAA;QAChB,WAAW,EAAE,MAAM,CAAA;QACnB,UAAU,EAAE,MAAM,CAAA;QAClB,QAAQ,EAAE,MAAM,CAAA;KACjB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,UAAU,GAAG,eAAe,CAAA;CACrC;AAED,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC,qBAAqB,CAAC,CA+DhC;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC,CAAA;IAC1E,IAAI,CACF,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GAClF,OAAO,CAAC,IAAI,CAAC,CAAA;IAChB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACxB"} \ No newline at end of file diff --git a/functions/lib/triggers/on-media-finalize.js b/functions/lib/triggers/on-media-finalize.js index fef9c17e..b07f812c 100644 --- a/functions/lib/triggers/on-media-finalize.js +++ b/functions/lib/triggers/on-media-finalize.js @@ -42,12 +42,13 @@ export async function onMediaFinalizeCore(input) { // in some sharp/libvips version combinations, defeating the strip. cleaned = await sharp(buf).rotate().toBuffer(); } - catch { + catch (err) { await file.delete(); log({ severity: 'WARNING', code: 'MEDIA_REJECTED_CORRUPT', message: `Deleted corrupt image: ${input.objectName}`, + data: { error: String(err) }, }); return { status: 'rejected_mime' }; } diff --git a/functions/lib/triggers/on-media-finalize.js.map b/functions/lib/triggers/on-media-finalize.js.map index b5d372f0..48ad9d70 100644 --- a/functions/lib/triggers/on-media-finalize.js.map +++ b/functions/lib/triggers/on-media-finalize.js.map @@ -1 +1 @@ -{"version":3,"file":"on-media-finalize.js","sourceRoot":"","sources":["../../src/triggers/on-media-finalize.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"on-media-finalize.js","sourceRoot":"","sources":["../../src/triggers/on-media-finalize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAE1D,MAAM,GAAG,GAAG,YAAY,CAAC,iBAAiB,CAAC,CAAA;AAE3C,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,YAAY,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC,CAAA;AAkBlE,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,KAA2B;IAE3B,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC7C,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAA;IAC/B,CAAC;IACD,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAA;IAChD,sEAAsE;IACtE,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAA;IAC5C,IAAI,CAAC,cAAc;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAA;IACnE,kEAAkE;IAClE,MAAM,GAAG,GAAG,cAAc,CAAC,CAAC,CAAC,CAAA;IAE7B,mEAAmE;IACnE,MAAM,QAAQ,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAA;IACjC,IAAI,GAAG,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC;QAC1B,MAAM,IAAI,CAAC,MAAM,EAAE,CAAA;QACnB,GAAG,CAAC;YACF,QAAQ,EAAE,SAAS;YACnB,IAAI,EAAE,qBAAqB;YAC3B,OAAO,EAAE,6BAA6B,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,YAAY,KAAK,CAAC,UAAU,EAAE;SACvF,CAAC,CAAA;QACF,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,CAAA;IACpC,CAAC;IAED,MAAM,EAAE,GAAG,MAAM,kBAAkB,CAAC,GAAG,CAAC,CAAA;IACxC,IAAI,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,CAAC,MAAM,EAAE,CAAA;QACnB,GAAG,CAAC;YACF,QAAQ,EAAE,SAAS;YACnB,IAAI,EAAE,qBAAqB;YAC3B,OAAO,EAAE,sBAAsB,KAAK,CAAC,UAAU,EAAE;SAClD,CAAC,CAAA;QACF,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,CAAA;IACpC,CAAC;IACD,IAAI,OAAe,CAAA;IACnB,IAAI,CAAC;QACH,kEAAkE;QAClE,sEAAsE;QACtE,mEAAmE;QACnE,OAAO,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAA;IAChD,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,MAAM,IAAI,CAAC,MAAM,EAAE,CAAA;QACnB,GAAG,CAAC;YACF,QAAQ,EAAE,SAAS;YACnB,IAAI,EAAE,wBAAwB;YAC9B,OAAO,EAAE,0BAA0B,KAAK,CAAC,UAAU,EAAE;YACrD,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE;SAC7B,CAAC,CAAA;QACF,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,CAAA;IACpC,CAAC;IACD,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE;QACvB,SAAS,EAAE,KAAK;QAChB,WAAW,EAAE,EAAE,CAAC,IAAI;QACpB,QAAQ,EAAE,EAAE,YAAY,EAAE,uBAAuB,EAAE;KACpD,CAAC,CAAA;IACF,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;IAC1D,MAAM,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAA;IACvD,MAAM,KAAK,CAAC,YAAY,CAAC;QACvB,QAAQ;QACR,WAAW,EAAE,KAAK,CAAC,UAAU;QAC7B,UAAU;QACV,QAAQ,EAAE,EAAE,CAAC,IAAI;KAClB,CAAC,CAAA;IACF,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAA;AAC/B,CAAC"} \ No newline at end of file From b62a7d6a00ee614d6ee1899be2410fed427f57e8 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 24 Apr 2026 07:46:38 +0800 Subject: [PATCH 13/31] refactor(functions,sms-parser,shared-ui): address CodeRabbit PR review comments - dispatch-responder-validation: reorder checks so NOT_FOUND/FORBIDDEN surface before assertResponderOnShift - dispatch-responder-writes: add trim() + empty check for MSISDN - dispatch-responder: remove dead typeof claims.role !== 'string' check - index.ts: defensive err narrowing before accessing .code/.message - gazetteer.ts: tighten MODULE_NOT_FOUND to regex match - parser.ts: remove unreachable branch, fix details extraction with lastIndexOf - levenshtein.ts: fix lint/typecheck loop with ?? 0 narrowing - protected-route.tsx: add .trim().length > 0 for municipalityId --- .../dispatch-responder-validation.ts | 15 ++++--- .../callables/dispatch-responder-writes.ts | 6 +-- functions/src/callables/dispatch-responder.ts | 3 -- functions/src/index.ts | 8 +++- packages/shared-sms-parser/src/gazetteer.ts | 2 +- packages/shared-sms-parser/src/levenshtein.ts | 43 +++++++++++-------- packages/shared-sms-parser/src/parser.ts | 17 +++----- packages/shared-ui/src/protected-route.tsx | 2 +- 8 files changed, 49 insertions(+), 47 deletions(-) diff --git a/functions/src/callables/dispatch-responder-validation.ts b/functions/src/callables/dispatch-responder-validation.ts index a88bb9b9..8875f075 100644 --- a/functions/src/callables/dispatch-responder-validation.ts +++ b/functions/src/callables/dispatch-responder-validation.ts @@ -50,16 +50,9 @@ export async function validateDispatchTransaction({ }> { const [reportSnap, responderSnap] = await Promise.all([tx.get(reportRef), tx.get(responderRef)]) - // Re-check shift status inside transaction scope to mitigate TOCTOU race if (!deps.actor.claims.municipalityId) { throw new BantayogError(BantayogErrorCode.INVALID_ARGUMENT, 'municipalityId is required') } - await assertResponderOnShift( - rtdb, - deps.actor.claims.municipalityId, - deps.responderUid, - 'Responder went off-shift before dispatch could be created', - ) if (!reportSnap.exists) { throw new BantayogError(BantayogErrorCode.NOT_FOUND, 'Report not found') @@ -89,6 +82,14 @@ export async function validateDispatchTransaction({ throw new BantayogError(BantayogErrorCode.INVALID_ARGUMENT, 'Responder missing agencyId') } + // Re-check shift status after identity + municipality checks to preserve correct error classes. + await assertResponderOnShift( + rtdb, + deps.actor.claims.municipalityId, + deps.responderUid, + 'Responder went off-shift before dispatch could be created', + ) + const rawStatus = report.status if (typeof rawStatus !== 'string') { throw new BantayogError( diff --git a/functions/src/callables/dispatch-responder-writes.ts b/functions/src/callables/dispatch-responder-writes.ts index c29a9738..c1a66e55 100644 --- a/functions/src/callables/dispatch-responder-writes.ts +++ b/functions/src/callables/dispatch-responder-writes.ts @@ -24,9 +24,9 @@ export async function buildSmsPayload(args: BuildSmsPayloadArgs): Promise t.length) { + ;[s, t] = [t, s] + } + + const m = s.length + const n = t.length - // Flat 1D array for memory efficiency and to avoid noUncheckedIndexedAccess - const dp = new Uint32Array((m + 1) * (n + 1)) + let prev = new Uint32Array(n + 1) + let curr = new Uint32Array(n + 1) - for (let i = 0; i <= m; i++) dp[i * (n + 1)] = i - for (let j = 0; j <= n; j++) dp[j] = j + for (let j = 0; j <= n; j++) { + prev[j] = j + } for (let i = 1; i <= m; i++) { + curr[0] = i + const sChar = s[i - 1] for (let j = 1; j <= n; j++) { - const idx = i * (n + 1) + j - const cost = a[i - 1] === b[j - 1] ? 0 : 1 - const deletion = dp[(i - 1) * (n + 1) + j] - const insertion = dp[i * (n + 1) + (j - 1)] - const substitution = dp[(i - 1) * (n + 1) + (j - 1)] - if (deletion === undefined || insertion === undefined || substitution === undefined) { - throw new Error('Levenshtein DP index out of bounds') - } - dp[idx] = Math.min(deletion + 1, insertion + 1, substitution + cost) + const cost = sChar === t[j - 1] ? 0 : 1 + const deletion = (prev[j] ?? 0) + 1 + const insertion = (curr[j - 1] ?? 0) + 1 + const substitution = (prev[j - 1] ?? 0) + cost + curr[j] = Math.min(deletion, insertion, substitution) } + const tmp = prev + prev = curr + curr = tmp } - const result = dp[m * (n + 1) + n] - return result ?? 0 + return prev[n] ?? 0 } diff --git a/packages/shared-sms-parser/src/parser.ts b/packages/shared-sms-parser/src/parser.ts index d1ba3f26..cbff13e9 100644 --- a/packages/shared-sms-parser/src/parser.ts +++ b/packages/shared-sms-parser/src/parser.ts @@ -106,7 +106,10 @@ export function parseInboundSms(body: string): ParseResult { detailsStartIndex = barangayToken.length } - const barangayIndex = originalRest.toUpperCase().indexOf(barangayToken.toUpperCase()) + // Find the barangay token in originalRest using lastIndexOf to prefer the occurrence + // closest to where details would start (avoids matching an earlier instance of the token). + const barangayTokenUpper = barangayToken.toUpperCase() + const barangayIndex = originalRest.toUpperCase().lastIndexOf(barangayTokenUpper) const details = barangayIndex !== -1 && barangayIndex + detailsStartIndex < originalRest.length ? originalRest.slice(barangayIndex + detailsStartIndex).trim() @@ -149,16 +152,8 @@ export function parseInboundSms(body: string): ParseResult { } if (fuzzyMatches.length === 1) { - const match = fuzzyMatches[0] - if (match === undefined) { - return { - confidence: 'none', - parsed: null, - candidates: [], - autoReplyText: buildAutoReply('none'), - } - } - const { entry, distance: dist } = match + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const { entry, distance: dist } = fuzzyMatches[0]! return { confidence: dist <= 1 ? 'medium' : 'low', parsed: { diff --git a/packages/shared-ui/src/protected-route.tsx b/packages/shared-ui/src/protected-route.tsx index 6058c008..82943fcb 100644 --- a/packages/shared-ui/src/protected-route.tsx +++ b/packages/shared-ui/src/protected-route.tsx @@ -36,7 +36,7 @@ export function ProtectedRoute({ if ( requireMunicipalityIdForRoles.includes(role) && - (typeof claims?.municipalityId !== 'string' || !claims.municipalityId) + (typeof claims?.municipalityId !== 'string' || !claims.municipalityId.trim()) ) { return unauthorizedFallback } From 5b5784037983499f4d840bad8a95b239613f93dd Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 24 Apr 2026 07:46:46 +0800 Subject: [PATCH 14/31] chore(shared-sms-parser): rebuild compiled lib artifacts after source changes --- packages/shared-sms-parser/lib/gazetteer.js | 2 +- .../shared-sms-parser/lib/gazetteer.js.map | 2 +- packages/shared-sms-parser/lib/inbound.d.ts | 25 --- .../shared-sms-parser/lib/inbound.d.ts.map | 1 - packages/shared-sms-parser/lib/inbound.js | 155 ------------------ packages/shared-sms-parser/lib/inbound.js.map | 1 - .../lib/levenshtein.d.ts.map | 2 +- packages/shared-sms-parser/lib/levenshtein.js | 47 +++--- .../shared-sms-parser/lib/levenshtein.js.map | 2 +- .../shared-sms-parser/lib/parser.d.ts.map | 2 +- packages/shared-sms-parser/lib/parser.js | 17 +- packages/shared-sms-parser/lib/parser.js.map | 2 +- 12 files changed, 36 insertions(+), 222 deletions(-) delete mode 100644 packages/shared-sms-parser/lib/inbound.d.ts delete mode 100644 packages/shared-sms-parser/lib/inbound.d.ts.map delete mode 100644 packages/shared-sms-parser/lib/inbound.js delete mode 100644 packages/shared-sms-parser/lib/inbound.js.map diff --git a/packages/shared-sms-parser/lib/gazetteer.js b/packages/shared-sms-parser/lib/gazetteer.js index 9092e1d7..6ab520b2 100644 --- a/packages/shared-sms-parser/lib/gazetteer.js +++ b/packages/shared-sms-parser/lib/gazetteer.js @@ -310,7 +310,7 @@ export function getBarangayGazetteer() { 'code' in err && err.code === 'MODULE_NOT_FOUND'; const message = err instanceof Error ? err.message : ''; - const isSharedDataLoadFailure = message.includes('@bantayog/shared-data'); + const isSharedDataLoadFailure = /Cannot find module .@bantayog\/shared-data./.test(message); if (isModuleNotFound && isSharedDataLoadFailure) { return FALLBACK_BARANGAYS; } diff --git a/packages/shared-sms-parser/lib/gazetteer.js.map b/packages/shared-sms-parser/lib/gazetteer.js.map index da5ee987..c57d56a3 100644 --- a/packages/shared-sms-parser/lib/gazetteer.js.map +++ b/packages/shared-sms-parser/lib/gazetteer.js.map @@ -1 +1 @@ -{"version":3,"file":"gazetteer.js","sourceRoot":"","sources":["../src/gazetteer.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"gazetteer.js","sourceRoot":"","sources":["../src/gazetteer.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/shared-sms-parser/lib/inbound.d.ts b/packages/shared-sms-parser/lib/inbound.d.ts deleted file mode 100644 index b5fd103b..00000000 --- a/packages/shared-sms-parser/lib/inbound.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { z } from 'zod'; -export type Confidence = 'high' | 'medium' | 'low' | 'none'; -export declare const reportTypeSchema: z.ZodEnum<{ - flood: "flood"; - fire: "fire"; - landslide: "landslide"; - accident: "accident"; - medical: "medical"; - other: "other"; -}>; -export type ReportType = z.infer; -export interface ParsedFields { - reportType: ReportType; - barangay: string; - rawBarangay?: string; - details: string | undefined; -} -export interface ParseResult { - confidence: Confidence; - parsed: ParsedFields | null; - candidates: string[]; - autoReplyText: string; -} -export declare function parseInboundSms(body: string): ParseResult; -//# sourceMappingURL=inbound.d.ts.map \ No newline at end of file diff --git a/packages/shared-sms-parser/lib/inbound.d.ts.map b/packages/shared-sms-parser/lib/inbound.d.ts.map deleted file mode 100644 index 5c675532..00000000 --- a/packages/shared-sms-parser/lib/inbound.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"inbound.d.ts","sourceRoot":"","sources":["../src/inbound.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAKvB,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAA;AAE3D,eAAO,MAAM,gBAAgB;;;;;;;EAO3B,CAAA;AACF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAA;AAEzD,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;CAC5B;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,UAAU,CAAA;IACtB,MAAM,EAAE,YAAY,GAAG,IAAI,CAAA;IAC3B,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;CACtB;AAuBD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAyIzD"} \ No newline at end of file diff --git a/packages/shared-sms-parser/lib/inbound.js b/packages/shared-sms-parser/lib/inbound.js deleted file mode 100644 index 7e1416d0..00000000 --- a/packages/shared-sms-parser/lib/inbound.js +++ /dev/null @@ -1,155 +0,0 @@ -// ─── Types ──────────────────────────────────────────────────────────────────── -import { z } from 'zod'; -import { buildAutoReply } from './auto-reply.js'; -import { getBarangayGazetteer } from './gazetteer.js'; -import { levenshtein } from './levenshtein.js'; -export const reportTypeSchema = z.enum([ - 'flood', - 'fire', - 'landslide', - 'accident', - 'medical', - 'other', -]); -// ─── Type synonym map ───────────────────────────────────────────────────────── -const TYPE_SYNONYMS = { - FLOOD: 'flood', - BAHA: 'flood', - FIRE: 'fire', - SUNOG: 'fire', - LANDSLIDE: 'landslide', - GUHO: 'landslide', - ACCIDENT: 'accident', - AKSIDENTE: 'accident', - MEDICAL: 'medical', - MEDIKAL: 'medical', - OTHER: 'other', - IBA: 'other', -}; -const MUNICIPALITY_PREFIXES = new Set(['SAN', 'STA', 'SANTA']); -// ─── Main parser ────────────────────────────────────────────────────────────── -export function parseInboundSms(body) { - if (typeof body !== 'string') { - return { - confidence: 'none', - parsed: null, - candidates: [], - autoReplyText: buildAutoReply('none'), - }; - } - const normalized = body.trim().replace(/\s+/g, ' ').toUpperCase(); - const originalRest = body.trim().replace(/\s+/g, ' '); - const KEYWORD = 'BANTAYOG'; - if (!normalized.startsWith(KEYWORD)) { - return { - confidence: 'none', - parsed: null, - candidates: [], - autoReplyText: buildAutoReply('none'), - }; - } - const rest = normalized.slice(KEYWORD.length).trim(); - if (!rest) { - return { - confidence: 'none', - parsed: null, - candidates: [], - autoReplyText: buildAutoReply('none'), - }; - } - const tokens = rest.split(/\s+/); - const token0 = tokens[0]; - const token1 = tokens[1]; - if (tokens.length < 2 || !token0 || !token1) { - return { - confidence: 'none', - parsed: null, - candidates: [], - autoReplyText: buildAutoReply('none'), - }; - } - const typeToken = token0; - let barangayToken = token1; - let detailsStartIndex = barangayToken.length; - const token2 = tokens[2]; - if (tokens.length >= 3 && token2 && MUNICIPALITY_PREFIXES.has(token1)) { - barangayToken = token1 + ' ' + token2; - detailsStartIndex = barangayToken.length; - } - const barangayIndex = originalRest.toUpperCase().indexOf(barangayToken.toUpperCase()); - const details = barangayIndex !== -1 && barangayIndex + detailsStartIndex < originalRest.length - ? originalRest.slice(barangayIndex + detailsStartIndex).trim() - : undefined; - const rawType = typeToken.toUpperCase(); - const reportType = TYPE_SYNONYMS[rawType]; - if (!reportType) { - return { - confidence: 'none', - parsed: null, - candidates: [], - autoReplyText: buildAutoReply('none'), - }; - } - const gazetteer = getBarangayGazetteer(); - const barangayLower = barangayToken.toLowerCase(); - const exact = gazetteer.find((b) => b.name.toLowerCase() === barangayLower); - if (exact) { - return { - confidence: 'high', - parsed: { - reportType, - barangay: exact.name, - details, - }, - candidates: [], - autoReplyText: buildAutoReply('high'), - }; - } - const fuzzyMatches = []; - for (const entry of gazetteer) { - const dist = levenshtein(barangayLower, entry.name.toLowerCase()); - if (dist <= 2) { - fuzzyMatches.push({ entry, distance: dist }); - } - } - if (fuzzyMatches.length === 1) { - const match = fuzzyMatches[0]; - if (match === undefined) { - return { - confidence: 'none', - parsed: null, - candidates: [], - autoReplyText: buildAutoReply('none'), - }; - } - const { entry, distance: dist } = match; - return { - confidence: dist <= 1 ? 'medium' : 'low', - parsed: { - reportType, - barangay: entry.name, - rawBarangay: barangayToken, - details, - }, - candidates: [], - autoReplyText: buildAutoReply(dist <= 1 ? 'medium' : 'low'), - }; - } - if (fuzzyMatches.length > 1) { - fuzzyMatches.sort((a, b) => a.distance - b.distance); - const candidates = fuzzyMatches.slice(0, 3).map((m) => m.entry.name); - return { - confidence: 'low', - parsed: null, - candidates, - autoReplyText: buildAutoReply('low'), - }; - } - return { - confidence: 'none', - parsed: null, - candidates: [], - autoReplyText: buildAutoReply('none'), - }; -} -//# sourceMappingURL=inbound.js.map \ No newline at end of file diff --git a/packages/shared-sms-parser/lib/inbound.js.map b/packages/shared-sms-parser/lib/inbound.js.map deleted file mode 100644 index d9b8ac61..00000000 --- a/packages/shared-sms-parser/lib/inbound.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"inbound.js","sourceRoot":"","sources":["../src/inbound.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/shared-sms-parser/lib/levenshtein.d.ts.map b/packages/shared-sms-parser/lib/levenshtein.d.ts.map index e11131b1..c63e3756 100644 --- a/packages/shared-sms-parser/lib/levenshtein.d.ts.map +++ b/packages/shared-sms-parser/lib/levenshtein.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"levenshtein.d.ts","sourceRoot":"","sources":["../src/levenshtein.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CA4BxD"} \ No newline at end of file +{"version":3,"file":"levenshtein.d.ts","sourceRoot":"","sources":["../src/levenshtein.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAiCxD"} \ No newline at end of file diff --git a/packages/shared-sms-parser/lib/levenshtein.js b/packages/shared-sms-parser/lib/levenshtein.js index f5b24f87..a7026a2f 100644 --- a/packages/shared-sms-parser/lib/levenshtein.js +++ b/packages/shared-sms-parser/lib/levenshtein.js @@ -1,30 +1,31 @@ export function levenshtein(a, b) { - const m = a.length; - const n = b.length; - if (m === 0) - return n; - if (n === 0) - return m; - // Flat 1D array for memory efficiency and to avoid noUncheckedIndexedAccess - const dp = new Uint32Array((m + 1) * (n + 1)); - for (let i = 0; i <= m; i++) - dp[i * (n + 1)] = i; - for (let j = 0; j <= n; j++) - dp[j] = j; + let s = a; + let t = b; + if (s.length > t.length) { + ; + [s, t] = [t, s]; + } + const m = s.length; + const n = t.length; + let prev = new Uint32Array(n + 1); + let curr = new Uint32Array(n + 1); + for (let j = 0; j <= n; j++) { + prev[j] = j; + } for (let i = 1; i <= m; i++) { + curr[0] = i; + const sChar = s[i - 1]; for (let j = 1; j <= n; j++) { - const idx = i * (n + 1) + j; - const cost = a[i - 1] === b[j - 1] ? 0 : 1; - const deletion = dp[(i - 1) * (n + 1) + j]; - const insertion = dp[i * (n + 1) + (j - 1)]; - const substitution = dp[(i - 1) * (n + 1) + (j - 1)]; - if (deletion === undefined || insertion === undefined || substitution === undefined) { - throw new Error('Levenshtein DP index out of bounds'); - } - dp[idx] = Math.min(deletion + 1, insertion + 1, substitution + cost); + const cost = sChar === t[j - 1] ? 0 : 1; + const deletion = (prev[j] ?? 0) + 1; + const insertion = (curr[j - 1] ?? 0) + 1; + const substitution = (prev[j - 1] ?? 0) + cost; + curr[j] = Math.min(deletion, insertion, substitution); } + const tmp = prev; + prev = curr; + curr = tmp; } - const result = dp[m * (n + 1) + n]; - return result ?? 0; + return prev[n] ?? 0; } //# sourceMappingURL=levenshtein.js.map \ No newline at end of file diff --git a/packages/shared-sms-parser/lib/levenshtein.js.map b/packages/shared-sms-parser/lib/levenshtein.js.map index 83009340..24ae9108 100644 --- a/packages/shared-sms-parser/lib/levenshtein.js.map +++ b/packages/shared-sms-parser/lib/levenshtein.js.map @@ -1 +1 @@ -{"version":3,"file":"levenshtein.js","sourceRoot":"","sources":["../src/levenshtein.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,WAAW,CAAC,CAAS,EAAE,CAAS;IAC9C,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAA;IAClB,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAA;IAClB,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAA;IACrB,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAA;IAErB,4EAA4E;IAC5E,MAAM,EAAE,GAAG,IAAI,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;IAE7C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE;QAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IAChD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE;QAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IAEtC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC5B,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAA;YAC3B,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;YAC1C,MAAM,QAAQ,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;YAC1C,MAAM,SAAS,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;YAC3C,MAAM,YAAY,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;YACpD,IAAI,QAAQ,KAAK,SAAS,IAAI,SAAS,KAAK,SAAS,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;gBACpF,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAA;YACvD,CAAC;YACD,EAAE,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,CAAC,EAAE,SAAS,GAAG,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC,CAAA;QACtE,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IAClC,OAAO,MAAM,IAAI,CAAC,CAAA;AACpB,CAAC"} \ No newline at end of file +{"version":3,"file":"levenshtein.js","sourceRoot":"","sources":["../src/levenshtein.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,WAAW,CAAC,CAAS,EAAE,CAAS;IAC9C,IAAI,CAAC,GAAG,CAAC,CAAA;IACT,IAAI,CAAC,GAAG,CAAC,CAAA;IACT,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC;QACxB,CAAC;QAAA,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAClB,CAAC;IAED,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAA;IAClB,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAA;IAElB,IAAI,IAAI,GAAG,IAAI,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IACjC,IAAI,IAAI,GAAG,IAAI,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IAEjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5B,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IACb,CAAC;IAED,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5B,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;QACX,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;QACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;YACvC,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAA;YACnC,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAA;YACxC,MAAM,YAAY,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAA;YAC9C,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,YAAY,CAAC,CAAA;QACvD,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAA;QAChB,IAAI,GAAG,IAAI,CAAA;QACX,IAAI,GAAG,GAAG,CAAA;IACZ,CAAC;IAED,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;AACrB,CAAC"} \ No newline at end of file diff --git a/packages/shared-sms-parser/lib/parser.d.ts.map b/packages/shared-sms-parser/lib/parser.d.ts.map index 1419989f..1bd69a11 100644 --- a/packages/shared-sms-parser/lib/parser.d.ts.map +++ b/packages/shared-sms-parser/lib/parser.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAKvB,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAA;AAE3D,eAAO,MAAM,gBAAgB;;;;;;;EAO3B,CAAA;AACF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAA;AAEzD,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;CAC5B;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,UAAU,CAAA;IACtB,MAAM,EAAE,YAAY,GAAG,IAAI,CAAA;IAC3B,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;CACtB;AAuBD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAyIzD"} \ No newline at end of file +{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAKvB,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAA;AAE3D,eAAO,MAAM,gBAAgB;;;;;;;EAO3B,CAAA;AACF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAA;AAEzD,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,UAAU,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;CAC5B;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,UAAU,CAAA;IACtB,MAAM,EAAE,YAAY,GAAG,IAAI,CAAA;IAC3B,UAAU,EAAE,MAAM,EAAE,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;CACtB;AAuBD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAoIzD"} \ No newline at end of file diff --git a/packages/shared-sms-parser/lib/parser.js b/packages/shared-sms-parser/lib/parser.js index 43480e9d..288cce29 100644 --- a/packages/shared-sms-parser/lib/parser.js +++ b/packages/shared-sms-parser/lib/parser.js @@ -76,7 +76,10 @@ export function parseInboundSms(body) { barangayToken = token1 + ' ' + token2; detailsStartIndex = barangayToken.length; } - const barangayIndex = originalRest.toUpperCase().indexOf(barangayToken.toUpperCase()); + // Find the barangay token in originalRest using lastIndexOf to prefer the occurrence + // closest to where details would start (avoids matching an earlier instance of the token). + const barangayTokenUpper = barangayToken.toUpperCase(); + const barangayIndex = originalRest.toUpperCase().lastIndexOf(barangayTokenUpper); const details = barangayIndex !== -1 && barangayIndex + detailsStartIndex < originalRest.length ? originalRest.slice(barangayIndex + detailsStartIndex).trim() : undefined; @@ -113,16 +116,8 @@ export function parseInboundSms(body) { } } if (fuzzyMatches.length === 1) { - const match = fuzzyMatches[0]; - if (match === undefined) { - return { - confidence: 'none', - parsed: null, - candidates: [], - autoReplyText: buildAutoReply('none'), - }; - } - const { entry, distance: dist } = match; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const { entry, distance: dist } = fuzzyMatches[0]; return { confidence: dist <= 1 ? 'medium' : 'low', parsed: { diff --git a/packages/shared-sms-parser/lib/parser.js.map b/packages/shared-sms-parser/lib/parser.js.map index afdb1423..ec816b2b 100644 --- a/packages/shared-sms-parser/lib/parser.js.map +++ b/packages/shared-sms-parser/lib/parser.js.map @@ -1 +1 @@ -{"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"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"} \ No newline at end of file From 1e2a23b6ca216f5df77f968f37b79b869129b762 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 24 Apr 2026 07:46:53 +0800 Subject: [PATCH 15/31] a11y(citizen-pwa): label association and aria-pressed on ContactFields - Replace

with

Pre-filled from your last report

}
-

Your name

+ { @@ -57,8 +60,11 @@ export function ContactFields({
-

Phone number

+ { @@ -88,6 +94,7 @@ export function ContactFields({