diff --git a/src/app/(map)/signaler/report-form.tsx b/src/app/(map)/signaler/report-form.tsx
index 35a085e..a251904 100644
--- a/src/app/(map)/signaler/report-form.tsx
+++ b/src/app/(map)/signaler/report-form.tsx
@@ -31,7 +31,8 @@ import {
validateReport,
type ReportErrors,
} from '@/lib/reports/validation'
-import { submitReport, type ReportFormState } from './actions'
+import { sendReport } from '@/lib/reports/send'
+import type { ReportFormState } from '@/lib/reports/submit'
import { REPORT_THEMES } from './report-theme'
const initialState: ReportFormState = {}
@@ -79,10 +80,7 @@ export function ReportForm({
onBack: () => void
photoRequired: boolean
}) {
- const [state, formAction, pending] = useActionState(
- submitReport,
- initialState,
- )
+ const [state, formAction, pending] = useActionState(sendReport, initialState)
return (
@@ -122,7 +120,9 @@ export function ReportForm({
aria-live="polite"
className="rounded-lg bg-canopee-green/10 px-3 py-2.5 text-sm font-medium text-canopee-forest"
>
- Signalement envoyé. Merci!
+ {state.queued
+ ? 'Signalement enregistré. Il partira au retour du réseau.'
+ : 'Signalement envoyé. Merci!'}
)}
diff --git a/src/app/(map)/signaler/report-overlay.tsx b/src/app/(map)/signaler/report-overlay.tsx
index 7c45430..180230b 100644
--- a/src/app/(map)/signaler/report-overlay.tsx
+++ b/src/app/(map)/signaler/report-overlay.tsx
@@ -1,11 +1,31 @@
'use client'
-import { useEffect } from 'react'
+import { useCallback, useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'
+import { drainQueuedReports, pendingReportCount } from '@/lib/reports/send'
import { ReportFlow } from './report-flow'
export function ReportOverlay({ photoRequired }: { photoRequired: boolean }) {
const router = useRouter()
+ const [pendingReports, setPendingReports] = useState(0)
+
+ const drain = useCallback(() => {
+ drainQueuedReports()
+ .then(setPendingReports)
+ .catch(() => setPendingReports(0))
+ }, [])
+
+ useEffect(() => {
+ pendingReportCount()
+ .then(setPendingReports)
+ .catch(() => setPendingReports(0))
+
+ drain()
+
+ window.addEventListener('online', drain)
+
+ return () => window.removeEventListener('online', drain)
+ }, [drain])
useEffect(() => {
function onKeyDown(event: KeyboardEvent) {
@@ -67,6 +87,17 @@ export function ReportOverlay({ photoRequired }: { photoRequired: boolean }) {
+ {pendingReports > 0 && (
+
+ {pendingReports === 1
+ ? '1 signalement en attente d’envoi.'
+ : `${pendingReports} signalements en attente d’envoi.`}
+
+ )}
+
diff --git a/src/app/api/reports/route.ts b/src/app/api/reports/route.ts
index 4ebbdf0..f35a1f4 100644
--- a/src/app/api/reports/route.ts
+++ b/src/app/api/reports/route.ts
@@ -2,6 +2,7 @@ import type { NextRequest } from 'next/server'
import { getCurrentUserProfile } from '@/lib/auth/current-user'
import { canAccess } from '@/lib/auth/roles'
import { listReportPins } from '@/lib/reports/queries'
+import { createReport } from '@/lib/reports/submit'
import {
parseCategoriesParam,
parseStatusParam,
@@ -49,3 +50,27 @@ export async function GET(request: NextRequest) {
reports,
})
}
+
+export async function POST(request: NextRequest) {
+ const profile = await getCurrentUserProfile()
+
+ if (!profile || !canAccess(profile, 'volunteer')) {
+ return Response.json({ error: 'Unauthorized.' }, { status: 401 })
+ }
+
+ let formData: FormData
+
+ try {
+ formData = await request.formData()
+ } catch {
+ return Response.json({ error: 'Expected a form body.' }, { status: 400 })
+ }
+
+ const result = await createReport(profile, formData)
+
+ if (result.errors || result.message) {
+ return Response.json(result, { status: 422 })
+ }
+
+ return Response.json(result)
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 651473d..13eb00e 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -2,6 +2,7 @@ import type { Metadata, Viewport } from 'next'
import { Averia_Serif_Libre, Geist_Mono } from 'next/font/google'
import localFont from 'next/font/local'
import { PatrolControls } from '@/components/patrol-controls'
+import { ReportSync } from '@/components/report-sync'
import { getCurrentUserProfile } from '@/lib/auth/current-user'
import { getActivePatrol } from '@/lib/patrols/queries'
import 'mapbox-gl/dist/mapbox-gl.css'
@@ -96,6 +97,7 @@ export default async function RootLayout({
{children}
+