- Phosphor Icons (Icon.svelte, svg-Registry) - Schema-Abgleich: alle Felder zwischen PB-Migrations und types.ts konsistent - Stripe entfernt, SEPA pain.008 XML-Export implementiert (sepa.ts) - Beiträge: vollständiges CRUD + SEPA-Einzug-Sheet mit Vorschau - Termine: vollständiges CRUD (upcoming/vergangen, datetime-local) - Mitglieder: Formulare um alle Felder erweitert (Adresse, SEPA-Mandat, Notizen) - Nachrichten: Brevo E-Mail via PocketBase-Hook, UI mit Gruppen-Filter - Push-Notifications: VAPID, Custom Service Worker (injectManifest), Subscribe/Send API-Routen, automatische Subscription nach Login - Onboarding: 3-Schritt-Flow für neue Vereine, Guard im App-Layout - Makefile: .env wird vollständig zur DS übertragen
45 lines
1.4 KiB
TypeScript
45 lines
1.4 KiB
TypeScript
/// <reference lib="webworker" />
|
|
import { cleanupOutdatedCaches, precacheAndRoute } from 'workbox-precaching';
|
|
import { registerRoute } from 'workbox-routing';
|
|
import { NetworkFirst } from 'workbox-strategies';
|
|
|
|
declare const self: ServiceWorkerGlobalScope;
|
|
|
|
cleanupOutdatedCaches();
|
|
precacheAndRoute(self.__WB_MANIFEST);
|
|
|
|
// PocketBase API: NetworkFirst (offline-fähig aus Cache)
|
|
registerRoute(
|
|
({ url }) => url.hostname === 'api.vereins.haus',
|
|
new NetworkFirst({
|
|
cacheName: 'pocketbase-api',
|
|
networkTimeoutSeconds: 5,
|
|
}),
|
|
);
|
|
|
|
self.addEventListener('push', (event) => {
|
|
const data = event.data?.json() ?? {};
|
|
const title = data.title ?? 'vereins.haus';
|
|
const options: NotificationOptions = {
|
|
body: data.body ?? '',
|
|
icon: '/icons/icon-192.png',
|
|
badge: '/icons/icon-192.png',
|
|
tag: data.tag ?? 'vereinshaus',
|
|
data: { url: data.url ?? '/nachrichten' },
|
|
};
|
|
event.waitUntil(self.registration.showNotification(title, options));
|
|
});
|
|
|
|
self.addEventListener('notificationclick', (event) => {
|
|
event.notification.close();
|
|
const url = (event.notification.data as { url: string })?.url ?? '/';
|
|
event.waitUntil(
|
|
self.clients
|
|
.matchAll({ type: 'window', includeUncontrolled: true })
|
|
.then((list) => {
|
|
const existing = list.find((c) => c.url.includes(url));
|
|
if (existing) return existing.focus();
|
|
return self.clients.openWindow(url);
|
|
}),
|
|
);
|
|
});
|