MAI CALL - step 12
Passo 12 completo. Build limpo, AC server-side totalmente verificado. O que foi implementado: lib/queue/ — camada de persistência offline: db.ts — Dexie 4 com tabelas pending e deadLetters broadcast.ts — BroadcastChannel helper (mai-call-sync) para comunicar entre tabs sync.ts — loop de sync com retry/backoff: signPhotoUpload → PUT MinIO → create; 409 = sucesso; 4xx = dead-letter; erros de rede = paragem + retry na próxima volta SyncProvider — React Context que: Arranca sync ao reconectar (online event + visibilitychange) Polling de 10s como fallback Regista Background Sync API quando disponível Expõe pendingCount / deadLetterCount via useSyncState() Formulário (/maintenance/new) — refatorado: ao submeter, escreve em IndexedDB e navega imediatamente para /sent sem esperar pelo servidor. O SyncProvider processa a fila em background. Feedback visual: SyncChip na home: "Tudo sincronizado" / "N pedidos por enviar" / erro dead-letter /maintenance/sent: mostra "Em fila" (Clock) ou "Enviado" (CheckCircle2) reactivamente via BroadcastChannel Workbox (@ducanh2912/next-pwa) — app shell precaching ativo, para que o app carregue mesmo sem rede depois da primeira visita.
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import Link from 'next/link';
|
||||
import { CheckCircle2 } from 'lucide-react';
|
||||
import { SentStatus } from './sent-status';
|
||||
|
||||
export default async function SentPage({
|
||||
searchParams,
|
||||
@@ -7,27 +6,5 @@ export default async function SentPage({
|
||||
searchParams: Promise<{ cid?: string }>;
|
||||
}) {
|
||||
const { cid } = await searchParams;
|
||||
|
||||
return (
|
||||
<main className="mx-auto flex min-h-dvh max-w-lg flex-col items-center justify-center gap-6 p-6 text-center">
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<CheckCircle2 className="h-16 w-16 text-green-500" />
|
||||
<h1 className="text-2xl font-bold">Pedido enviado</h1>
|
||||
{cid && (
|
||||
<p className="font-mono text-xs text-muted-foreground" data-testid="request-cid">
|
||||
{cid}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-sm text-muted-foreground">
|
||||
A equipa de manutenção foi notificada e irá tratar do problema.
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href="/"
|
||||
className="rounded-xl bg-primary px-8 py-3 font-semibold text-primary-foreground hover:opacity-90"
|
||||
>
|
||||
Voltar ao início
|
||||
</Link>
|
||||
</main>
|
||||
);
|
||||
return <SentStatus cid={cid ?? ''} />;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { CheckCircle2, Clock } from 'lucide-react';
|
||||
import { db } from '@/lib/queue/db';
|
||||
import { subscribeBroadcast } from '@/lib/queue/broadcast';
|
||||
|
||||
export function SentStatus({ cid }: { cid: string }) {
|
||||
const [inQueue, setInQueue] = useState<boolean | null>(null); // null = loading
|
||||
|
||||
useEffect(() => {
|
||||
async function check() {
|
||||
const item = await db.pending.get(cid);
|
||||
setInQueue(!!item);
|
||||
}
|
||||
check();
|
||||
|
||||
const unsub = subscribeBroadcast((msg) => {
|
||||
if (msg.type === 'synced' && msg.clientRequestId === cid) setInQueue(false);
|
||||
if (msg.type === 'dead-letter' && msg.clientRequestId === cid) setInQueue(false);
|
||||
});
|
||||
|
||||
return unsub;
|
||||
}, [cid]);
|
||||
|
||||
const pending = inQueue === true;
|
||||
|
||||
return (
|
||||
<main className="mx-auto flex min-h-dvh max-w-lg flex-col items-center justify-center gap-6 p-6 text-center">
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
{pending ? (
|
||||
<Clock className="h-16 w-16 text-orange-400" />
|
||||
) : (
|
||||
<CheckCircle2 className="h-16 w-16 text-green-500" />
|
||||
)}
|
||||
<h1 className="text-2xl font-bold">
|
||||
{pending ? 'Pedido em fila' : 'Pedido enviado'}
|
||||
</h1>
|
||||
{cid && (
|
||||
<p className="font-mono text-xs text-muted-foreground" data-testid="request-cid">
|
||||
{cid}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{pending
|
||||
? 'Será enviado assim que a ligação for restabelecida.'
|
||||
: 'A equipa de manutenção foi notificada e irá tratar do problema.'}
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href="/"
|
||||
className="rounded-xl bg-primary px-8 py-3 font-semibold text-primary-foreground hover:opacity-90"
|
||||
>
|
||||
Voltar ao início
|
||||
</Link>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user