Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/citizen-pwa/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="theme-color" content="#d64933" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Bantayog Alert" />
<link rel="apple-touch-icon" href="/icons/icon-192.png" />
<meta name="description" content="Bantayog Alert lets Camarines Norte residents report disasters, track emergencies, and receive real-time alerts from local responders." />
<meta property="og:title" content="Bantayog Alert — Disaster Reporting for Camarines Norte" />
<meta property="og:description" content="Report disasters, track emergencies, and receive real-time alerts from local responders." />
Expand Down
4 changes: 2 additions & 2 deletions apps/citizen-pwa/src/components/AlertsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ function AlertCard({
<button
type="button"
onClick={onClick}
className={`bg-white rounded-xl mx-3 my-2 overflow-hidden border-l-4 ${borderClass} text-left w-full cursor-pointer hover:bg-surface-50 transition-colors relative`}
className={`bg-white rounded-xl mx-3 my-2 overflow-hidden border-l-2 ${borderClass} text-left w-full cursor-pointer hover:bg-surface-50 transition-colors relative`}
>
{/* Unread indicator dot */}
{isUnread && (
Expand Down Expand Up @@ -135,7 +135,7 @@ function AlertCard({

function SkeletonCard() {
return (
<div className="bg-white rounded-xl mx-3 my-2 overflow-hidden border-l-4 border-l-[#d5dedd] animate-pulse">
<div className="bg-white rounded-xl mx-3 my-2 overflow-hidden border-l-4 border-l-[#d5dedd] motion-safe:animate-pulse">
<div className="p-4 flex gap-2">
<div className="w-6 h-6 rounded-full bg-[#e0e3e5] flex-shrink-0" />
<div className="flex-1">
Expand Down
161 changes: 117 additions & 44 deletions apps/citizen-pwa/src/components/DeleteAccountFlow.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useCallback } from 'react'
import { useState, useCallback, useEffect } from 'react'
import { requestDataErasureAndSignOut } from '../services/erasure.js'

interface Props {
Expand Down Expand Up @@ -32,64 +32,137 @@ export function DeleteAccountFlow({ onGoodbye }: Props) {
setError(null)
}, [])

// Escape key closes dialog (but not while submitting)
useEffect(() => {
if (step === 'idle' || step === 'submitting') return
function onKey(e: KeyboardEvent) {
if (e.key === 'Escape') goIdle()
}
document.addEventListener('keydown', onKey)
return () => {
document.removeEventListener('keydown', onKey)
}
}, [step, goIdle])

if (step === 'idle') {
return (
<button
type="button"
onClick={() => {
setStep('warn')
setTyped('')
setError(null)
}}
style={{ color: 'red' }}
className="text-sm font-medium bg-transparent border-none p-0 cursor-pointer text-left text-danger-600"
aria-label="Delete my account"
>
Delete my account
</button>
)
}

if (step === 'warn') {
return (
<div role="dialog" aria-modal="true" aria-labelledby="delete-warn-title">
<h2 id="delete-warn-title">Delete your account?</h2>
<p>This will permanently:</p>
<ul>
<li>Remove your name, contact info, and account</li>
<li>Anonymize your reports (they remain as public record)</li>
<li>Sign you out immediately</li>
</ul>
<p>This cannot be undone. Your request will be reviewed before deletion is complete.</p>
<button onClick={goIdle}>Cancel</button>
<button
onClick={() => {
setStep('confirm')
setTyped('')
setError(null)
}}
>
Yes, delete my account →
</button>
</div>
)
}

return (
<div role="dialog" aria-modal="true" aria-labelledby="delete-confirm-title">
<h2 id="delete-confirm-title">Are you sure?</h2>
<label htmlFor="delete-confirm">Type DELETE to confirm</label>
<input
id="delete-confirm"
placeholder="Type DELETE"
value={typed}
onChange={(e) => {
setTyped(e.target.value)
}}
autoComplete="off"
/>
{error && <p role="alert">{error}</p>}
<button onClick={goIdle}>Cancel</button>
<button disabled={typed !== 'DELETE' || step === 'submitting'} onClick={handleConfirm}>
Confirm deletion
</button>
<div
className="fixed inset-0 z-50 flex items-end sm:items-center justify-center bg-black/50 backdrop-blur-sm"
role="presentation"
onClick={(e) => {
if (step !== 'submitting' && e.target === e.currentTarget) goIdle()
}}
>
{step === 'warn' ? (
<div
role="dialog"
aria-modal="true"
aria-labelledby="delete-warn-title"
className="bg-white rounded-t-2xl sm:rounded-2xl w-full sm:max-w-sm p-6 shadow-xl"
>
<h2 id="delete-warn-title" className="text-base font-bold text-surface-900 mb-3">
Delete your account?
</h2>
<p className="text-sm text-surface-600 mb-2">This will permanently:</p>
<ul className="text-sm text-surface-600 mb-4 pl-5 space-y-1 list-disc">
<li>Remove your name, contact info, and account</li>
<li>Anonymize your reports (they remain as public record)</li>
<li>Sign you out immediately</li>
</ul>
<p className="text-sm text-surface-500 mb-6">
This cannot be undone. Your request will be reviewed before deletion is complete.
</p>
<div className="flex flex-col gap-2">
<button
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus
type="button"
onClick={() => {
setStep('confirm')
setTyped('')
setError(null)
}}
className="w-full h-12 rounded-xl bg-danger-600 text-white font-semibold text-sm border-none cursor-pointer"
>
Yes, delete my account →
</button>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
<button
type="button"
onClick={goIdle}
className="w-full h-12 rounded-xl bg-surface-100 text-surface-700 font-semibold text-sm border-none cursor-pointer"
>
Cancel
</button>
</div>
</div>
) : (
<div
role="dialog"
aria-modal="true"
aria-labelledby="delete-confirm-title"
className="bg-white rounded-t-2xl sm:rounded-2xl w-full sm:max-w-sm p-6 shadow-xl"
>
<h2 id="delete-confirm-title" className="text-base font-bold text-surface-900 mb-4">
Are you sure?
</h2>
<label
htmlFor="delete-confirm"
className="block text-sm font-medium text-surface-700 mb-2"
>
Type DELETE to confirm
</label>
<input
id="delete-confirm"
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus
placeholder="Type DELETE"
value={typed}
onChange={(e) => {
setTyped(e.target.value)
}}
autoComplete="off"
className="w-full h-12 rounded-xl border-2 border-surface-200 px-4 text-sm font-mono focus:border-danger-500 focus:outline-none mb-4"
/>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
{error && (
<p role="alert" className="text-xs text-danger-600 mb-4">
{error}
</p>
)}
<div className="flex flex-col gap-2">
<button
type="button"
disabled={typed !== 'DELETE' || step === 'submitting'}
onClick={handleConfirm}
className="w-full h-12 rounded-xl bg-danger-600 text-white font-semibold text-sm border-none cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
>
{step === 'submitting' ? 'Deleting…' : 'Confirm deletion'}
</button>
<button
type="button"
onClick={goIdle}
className="w-full h-12 rounded-xl bg-surface-100 text-surface-700 font-semibold text-sm border-none cursor-pointer"
>
Cancel
</button>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</div>
</div>
)}
</div>
)
}
32 changes: 16 additions & 16 deletions apps/citizen-pwa/src/components/FeedTab.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState } from 'react'
import { CheckCircle, MapPin } from 'lucide-react'
import { MapPin, Info } from 'lucide-react'
import { useNavigate } from 'react-router-dom'
import { usePublicIncidents } from '../hooks/usePublicIncidents.js'
import { incidentIcon, incidentLabel } from '../utils/incident-meta.js'
Expand All @@ -16,12 +16,12 @@ function timeAgo(timestamp: number): string {

function severityBadgeClass(severity: string): string {
if (severity === 'high')
return 'px-2 py-0.5 rounded-full text-[10px] font-semibold bg-red-100 text-red-800'
return 'px-2 py-0.5 rounded-full text-xs font-semibold bg-red-100 text-red-800'
if (severity === 'medium')
return 'px-2 py-0.5 rounded-full text-[10px] font-semibold bg-orange-50 text-orange-800'
return 'px-2 py-0.5 rounded-full text-xs font-semibold bg-orange-50 text-orange-800'
if (severity === 'low')
return 'px-2 py-0.5 rounded-full text-[10px] font-semibold bg-surface-100 text-surface-700'
return 'px-2 py-0.5 rounded-full text-[10px] font-semibold bg-blue-50 text-blue-900'
return 'px-2 py-0.5 rounded-full text-xs font-semibold bg-surface-100 text-surface-700'
return 'px-2 py-0.5 rounded-full text-xs font-semibold bg-blue-50 text-blue-900'
}

function FeedCard({ incident, onTap }: { incident: PublicIncident; onTap: () => void }) {
Expand All @@ -45,7 +45,7 @@ function FeedCard({ incident, onTap }: { incident: PublicIncident; onTap: () =>
<div className="ml-3 flex-1 min-w-0">
<div className="flex justify-between items-start gap-2">
<p className="m-0 font-semibold text-[#25292a] text-sm leading-snug">{label}</p>
<span className="flex-shrink-0 text-[10px] text-[#768081]">
<span className="flex-shrink-0 text-xs text-[#768081]">
{timeAgo(incident.submittedAt)}
</span>
</div>
Expand Down Expand Up @@ -76,11 +76,11 @@ function SkeletonCard() {
return (
<div className="bg-white rounded-xl mx-3 my-2 overflow-hidden shadow-[0_1px_2px_rgba(0,0,0,0.05)] w-[calc(100%-1.5rem)]">
<div className="p-4 flex gap-3">
<div className="w-10 h-10 rounded-full bg-[#d5dedd] animate-pulse flex-shrink-0" />
<div className="w-10 h-10 rounded-full bg-[#d5dedd] motion-safe:animate-pulse flex-shrink-0" />
<div className="flex-1">
<div className="h-3.5 w-[55%] bg-[#d5dedd] rounded animate-pulse mb-2" />
<div className="h-3 w-[40%] bg-[#d5dedd] rounded animate-pulse mb-3" />
<div className="h-4 w-14 bg-[#d5dedd] rounded-full animate-pulse" />
<div className="h-3.5 w-[55%] bg-[#d5dedd] rounded motion-safe:animate-pulse mb-2" />
<div className="h-3 w-[40%] bg-[#d5dedd] rounded motion-safe:animate-pulse mb-3" />
<div className="h-4 w-14 bg-[#d5dedd] rounded-full motion-safe:animate-pulse" />
</div>
</div>
</div>
Expand Down Expand Up @@ -124,7 +124,7 @@ export function FeedTab() {
}}
className={
filters.severity === value
? 'bg-[#0f9488] text-white rounded-full px-3 py-1.5 text-xs font-medium flex-shrink-0 border-none cursor-pointer'
? 'bg-[#001e40] text-white rounded-full px-3 py-1.5 text-xs font-medium flex-shrink-0 border-none cursor-pointer'
: 'bg-[#f0f4f4] text-[#5e6667] rounded-full px-3 py-1.5 text-xs font-medium flex-shrink-0 border-none cursor-pointer'
}
>
Expand All @@ -145,7 +145,7 @@ export function FeedTab() {
}}
className={
filters.window === value
? 'bg-[#0f9488] text-white rounded-full px-3 py-1.5 text-xs font-medium flex-shrink-0 border-none cursor-pointer'
? 'bg-[#001e40] text-white rounded-full px-3 py-1.5 text-xs font-medium flex-shrink-0 border-none cursor-pointer'
: 'bg-[#f0f4f4] text-[#5e6667] rounded-full px-3 py-1.5 text-xs font-medium flex-shrink-0 border-none cursor-pointer'
}
>
Expand Down Expand Up @@ -178,13 +178,13 @@ export function FeedTab() {
aria-atomic="true"
className="flex flex-col items-center justify-center min-h-[50vh] text-[#768081] px-4"
>
<span className="text-green-600 mb-3">
<CheckCircle size={40} />
<span className="text-surface-400 mb-3">
<Info size={40} />
</span>
<p className="m-0 mb-1 font-bold text-[#25292a] text-[15px]">All clear</p>
<p className="m-0 mb-1 font-bold text-[#25292a] text-[15px]">No incidents</p>
<p className="m-0 text-[13px] text-[#52606d] text-center">
No incidents reported in the selected time window.
<span className="block text-[11px] text-[#768081] mt-1 italic">
<span className="block text-xs text-[#768081] mt-1 italic">
Walang naiulat na insidente sa panahong ito.
</span>
</p>
Expand Down
17 changes: 16 additions & 1 deletion apps/citizen-pwa/src/components/LookupScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { useState } from 'react'
import { httpsCallable } from 'firebase/functions'
import { ArrowLeft } from 'lucide-react'
import { fns, hasFirebaseConfig, FIREBASE_ENV_ERROR_MESSAGE } from '../services/firebase.js'
import {
fns,
hasFirebaseConfig,
ensureSignedIn,
FIREBASE_ENV_ERROR_MESSAGE,
} from '../services/firebase.js'

interface LookupResult {
status: string
Expand Down Expand Up @@ -50,6 +55,7 @@ export function LookupScreen() {
if (!hasFirebaseConfig()) {
throw new Error(FIREBASE_ENV_ERROR_MESSAGE)
}
await ensureSignedIn()
const res = await httpsCallable(
fns(),
'requestLookup',
Expand Down Expand Up @@ -170,6 +176,15 @@ export function LookupScreen() {
{`${result.municipalityLabel} MDRRMO`}
</span>
</div>
<button
type="button"
onClick={() => {
setResult(null)
}}
className="mt-4 w-full h-10 rounded-xl bg-surface-100 text-surface-700 text-sm font-medium border-none cursor-pointer"
>
Check another report
</button>
</div>
)}
</div>
Expand Down
2 changes: 1 addition & 1 deletion apps/citizen-pwa/src/components/MapTab/FilterBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface Props {
const SEVERITIES: { value: SeverityFilter; label: string; dot?: string }[] = [
{ value: 'all', label: 'All' },
{ value: 'high', label: 'High', dot: '#dc2626' },
{ value: 'medium', label: 'Medium', dot: '#a73400' },
{ value: 'medium', label: 'Medium', dot: '#7c3500' },
{ value: 'low', label: 'Low', dot: '#414849' },
]

Expand Down
4 changes: 2 additions & 2 deletions apps/citizen-pwa/src/components/MapTab/IncidentLayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface Props {
onPinTap: (incident: PublicIncident) => void
}

const COLORS = { high: '#dc2626', medium: '#a73400', low: '#414849' } as const
const COLORS = { high: '#dc2626', medium: '#7c3500', low: '#414849' } as const

function isValidCoordinate(lat: number, lng: number): boolean {
return (
Expand All @@ -29,7 +29,7 @@ function makeIcon(color: string, pulse: boolean): L.DivIcon {
iconSize: [44, 44],
iconAnchor: [22, 22],
html: `
<div style="position:relative;width:44px;height:44px;display:grid;place-items:center;">
<div style="position:relative;width:44px;height:44px;display:grid;place-items:center;cursor:pointer;">
${
pulse
? `<div style="position:absolute;width:18px;height:18px;border-radius:50%;background:${color};opacity:0.24;animation:ripple 2s cubic-bezier(0.4,0,0.6,1) infinite;"></div>`
Expand Down
2 changes: 1 addition & 1 deletion apps/citizen-pwa/src/components/MapTab/MyReportLayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function makeIcon(color: string, queued: boolean): L.DivIcon {
iconSize: [44, 44],
iconAnchor: [22, 22],
html: `
<div style="position:relative;width:44px;height:44px;display:grid;place-items:center;">
<div style="position:relative;width:44px;height:44px;display:grid;place-items:center;cursor:pointer;">
<div style="width:18px;height:18px;border-radius:50%;border:3px solid ${color};background:transparent;${queued ? '' : 'animation:ringPulse 2s ease-in-out infinite;'}"></div>
${
queued
Expand Down
Loading
Loading