Feature: SEPA-Export, Push-Notifications, Onboarding + vollständige UI
- 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
This commit is contained in:
parent
c2c4dfd518
commit
77c6f513b5
32 changed files with 3012 additions and 399 deletions
45
app/src/sw.ts
Normal file
45
app/src/sw.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/// <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);
|
||||
}),
|
||||
);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue