feat(pwa): hors-ligne (app shell + lecture API) et suppression depuis la page d'édition

- sw.ts : API GET, navigation et payloads RSC en network-first (cache en
  secours hors-ligne) ; écritures non interceptées ; fallback ~offline
- edit page : bouton Supprimer avec confirmation (CardDeleteButton)
This commit is contained in:
devcodetools committed 2026-09-22 10:04:47 +02:00
1 parent ba5bd8288d
commit 31283ada26
2 files changed
+37 -4

No files matched your search

+5 -1
View File
@@ -1,6 +1,7 @@
import type { Metadata } from 'next' import type { Metadata } from 'next'
import Link from 'next/link' import Link from 'next/link'
import { notFound } from 'next/navigation' import { notFound } from 'next/navigation'
import { CardDeleteButton } from '@/components/card-delete'
import { CardForm } from '@/components/card-form' import { CardForm } from '@/components/card-form'
import type { Card, CardInput } from '@/domain/card' import type { Card, CardInput } from '@/domain/card'
import { ApiClientError, cardsApi } from '@/lib/api-client' import { ApiClientError, cardsApi } from '@/lib/api-client'
@@ -33,7 +34,10 @@ export default async function EditCardPage({ params }: { params: Promise<{ id: s
> >
← Retour au détail ← Retour au détail
</Link> </Link>
<h1 className="mt-2 text-2xl font-bold text-slate-900">{card.question}</h1> <div className="mt-2 flex items-center justify-between gap-4">
<h1 className="text-2xl font-bold text-slate-900">{card.question}</h1>
<CardDeleteButton cardId={card.id} question={card.question} />
</div>
</div> </div>
<CardForm mode="edit" cardId={card.id} initial={initial} /> <CardForm mode="edit" cardId={card.id} initial={initial} />
</main> </main>
+32 -3
View File
@@ -1,7 +1,7 @@
/// <reference lib="webworker" /> /// <reference lib="webworker" />
import { defaultCache } from '@serwist/turbopack/worker' import { defaultCache } from '@serwist/turbopack/worker'
import type { PrecacheEntry, RuntimeCaching, SerwistGlobalConfig } from 'serwist' import type { PrecacheEntry, RuntimeCaching, SerwistGlobalConfig } from 'serwist'
import { NetworkOnly, Serwist } from 'serwist' import { ExpirationPlugin, NetworkFirst, Serwist } from 'serwist'
declare global { declare global {
interface WorkerGlobalScope extends SerwistGlobalConfig { interface WorkerGlobalScope extends SerwistGlobalConfig {
@@ -11,11 +11,40 @@ declare global {
declare const self: ServiceWorkerGlobalScope declare const self: ServiceWorkerGlobalScope
// Ne jamais mettre en cache les appels API // API : lectures (GET) en réseau d'abord, servies depuis le cache en secours
// hors-ligne. Les écritures (POST/PUT/DELETE) ne sont pas interceptées par le
// service worker et passent toujours par le réseau.
const runtimeCaching: RuntimeCaching[] = [ const runtimeCaching: RuntimeCaching[] = [
{ {
matcher: ({ sameOrigin, url }) => sameOrigin && url.pathname.startsWith('/api/'), matcher: ({ sameOrigin, url }) => sameOrigin && url.pathname.startsWith('/api/'),
handler: new NetworkOnly(), handler: new NetworkFirst({
networkTimeoutSeconds: 3,
cacheName: 'api-network-first',
plugins: [
new ExpirationPlugin({
maxEntries: 200,
maxAgeSeconds: 7 * 24 * 60 * 60,
}),
],
}),
},
// Navigation : réseau d'abord (pages toujours fraîches après déploiement),
// cache en secours uniquement hors-ligne
{
matcher: ({ request }) => request.mode === 'navigate',
handler: new NetworkFirst({
networkTimeoutSeconds: 3,
cacheName: 'pages-network-first',
}),
},
// Payloads RSC des navigations côté client : réseau d'abord
{
matcher: ({ request, sameOrigin }) =>
sameOrigin && request.headers.get('rsc') === '1',
handler: new NetworkFirst({
networkTimeoutSeconds: 3,
cacheName: 'rsc-network-first',
}),
}, },
...defaultCache, ...defaultCache,
] ]