injectManifest, a new typed SW source, runtime caching, and a non‑blocking update toast with an action button. The
guest shell now links a dedicated manifest and theme color, and background upload sync is managed in a single
PwaManager component.
Key changes (where/why)
- vite.config.ts: added VitePWA injectManifest config, guest manifest, and output to /public so the SW can control /
scope.
- resources/js/guest/guest-sw.ts: new Workbox SW (precache + runtime caching for guest navigation, GET /api/v1/*,
images, fonts) and preserves push/sync/notification logic.
- resources/js/guest/components/PwaManager.tsx: registers SW, shows update/offline toasts, and processes the upload
queue on sync/online.
- resources/js/guest/components/ToastHost.tsx: action-capable toasts so update prompts can include a CTA.
- resources/js/guest/i18n/messages.ts: added common.updateAvailable, common.updateAction, common.offlineReady.
- resources/views/guest.blade.php: manifest + theme color + apple touch icon.
- .gitignore: ignore generated public/guest-sw.js and public/guest.webmanifest; public/guest-sw.js removed since it’s
now build output.
110 lines
5.9 KiB
PHP
110 lines
5.9 KiB
PHP
<!doctype html>
|
||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>{{ config('app.name', 'Fotospiel') }}</title>
|
||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||
<link rel="icon" href="{{ asset('favicon.ico') }}" type="image/x-icon">
|
||
<link rel="manifest" href="{{ asset('guest.webmanifest') }}">
|
||
<link rel="apple-touch-icon" href="{{ asset('apple-touch-icon.png') }}">
|
||
<meta name="theme-color" content="#0f172a">
|
||
@viteReactRefresh
|
||
@vite(['resources/css/app.css', 'resources/js/guest/main.tsx'])
|
||
@php
|
||
$guestRuntimeConfig = [
|
||
'push' => [
|
||
'enabled' => config('push.enabled', false),
|
||
'vapidPublicKey' => config('push.vapid.public_key'),
|
||
],
|
||
];
|
||
$matomoConfig = config('services.matomo');
|
||
$matomoGuest = ($matomoConfig['enabled'] ?? false) && !empty($matomoConfig['url']) && !empty($matomoConfig['site_id_guest'])
|
||
? [
|
||
'enabled' => true,
|
||
'url' => rtrim($matomoConfig['url'], '/'),
|
||
'siteId' => (string) $matomoConfig['site_id_guest'],
|
||
]
|
||
: ['enabled' => false];
|
||
@endphp
|
||
<script nonce="{{ $cspNonce }}">
|
||
window.__GUEST_RUNTIME_CONFIG__ = {!! json_encode($guestRuntimeConfig) !!};
|
||
window.__MATOMO_GUEST__ = {!! json_encode($matomoGuest) !!};
|
||
</script>
|
||
<style nonce="{{ $cspStyleNonce }}">
|
||
#root { min-height: 100vh; }
|
||
.ns-bg { background: linear-gradient(180deg,#0f172a 0%,#111827 50%,#0b1224 100%); color: #fff; }
|
||
.ns-btn-primary { color: #fff; text-decoration: none; background: #ec4899; }
|
||
.ns-btn-outline { color: #e5e7eb; text-decoration: none; border: 1px solid rgba(255,255,255,0.2); }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
@php
|
||
$noscriptLocale = in_array(app()->getLocale(), ['de', 'en'], true) ? app()->getLocale() : 'de';
|
||
@endphp
|
||
<noscript>
|
||
<style nonce="{{ $cspStyleNonce }}">
|
||
#root { display: none !important; }
|
||
</style>
|
||
<div class="min-h-screen bg-gradient-to-b from-[#0f172a] via-[#111827] to-[#0b1224] text-white ns-bg">
|
||
<div class="mx-auto flex max-w-5xl flex-col gap-12 px-6 py-14">
|
||
<header class="space-y-3 text-center">
|
||
<p class="text-sm font-semibold uppercase tracking-[0.18em] text-pink-300">Fotospiel</p>
|
||
<h1 class="text-3xl font-semibold sm:text-4xl">Diese Event-Galerie braucht JavaScript</h1>
|
||
<p class="text-base text-white/70 sm:text-lg">
|
||
Aktiviere JavaScript, um Fotos anzusehen, zu liken und zu teilen. Ohne JavaScript zeigen wir dir eine schnelle Übersicht.
|
||
</p>
|
||
</header>
|
||
|
||
<section class="grid gap-4 sm:grid-cols-2">
|
||
<div class="rounded-2xl border border-white/10 bg-white/5 p-5 shadow-lg backdrop-blur">
|
||
<h2 class="text-xl font-semibold text-white">Was du verpasst</h2>
|
||
<ul class="mt-3 space-y-2 text-sm text-white/80">
|
||
<li>• Live-Fotogalerie mit schnellen Filtern</li>
|
||
<li>• Likes & Teilen via WhatsApp, Nachrichten & Link</li>
|
||
<li>• Offline-fähige PWA zum Installieren</li>
|
||
<li>• Sichere QR-Zugänge ohne Anmeldung</li>
|
||
</ul>
|
||
</div>
|
||
<div class="rounded-2xl border border-white/10 bg-white/5 p-5 shadow-lg backdrop-blur">
|
||
<h2 class="text-xl font-semibold text-white">So geht’s weiter</h2>
|
||
<ol class="mt-3 space-y-2 text-sm text-white/80">
|
||
<li>1) Aktiviere JavaScript in deinem Browser</li>
|
||
<li>2) Lade die Seite neu</li>
|
||
<li>3) Optional: Füge die App deinem Homescreen hinzu</li>
|
||
</ol>
|
||
<div class="mt-4 flex flex-wrap gap-3">
|
||
<a href="{{ route('marketing.contact', ['locale' => $noscriptLocale]) }}" class="inline-flex items-center justify-center rounded-full bg-pink-500 px-4 py-2 text-sm font-semibold text-white shadow-lg transition hover:bg-pink-400 ns-btn-primary">
|
||
Support kontaktieren
|
||
</a>
|
||
<a href="{{ route('impressum', ['locale' => $noscriptLocale]) }}" class="inline-flex items-center justify-center rounded-full border border-white/20 px-4 py-2 text-sm font-semibold text-white/80 transition hover:border-white/40 ns-btn-outline">
|
||
Impressum
|
||
</a>
|
||
<a href="{{ route('datenschutz', ['locale' => $noscriptLocale]) }}" class="inline-flex items-center justify-center rounded-full border border-white/20 px-4 py-2 text-sm font-semibold text-white/80 transition hover:border-white/40 ns-btn-outline">
|
||
Datenschutz
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="grid gap-4 sm:grid-cols-3">
|
||
<div class="rounded-xl border border-white/10 bg-white/5 p-4 text-sm text-white/80 shadow-sm">
|
||
<h3 class="text-base font-semibold text-white">Offline-ready</h3>
|
||
<p class="mt-2">Fotos bleiben verfügbar, auch wenn das Event-WLAN wackelt.</p>
|
||
</div>
|
||
<div class="rounded-xl border border-white/10 bg-white/5 p-4 text-sm text-white/80 shadow-sm">
|
||
<h3 class="text-base font-semibold text-white">Privat & sicher</h3>
|
||
<p class="mt-2">Keine öffentlichen Profile, keine Gesichtserkennung, nur Event-Teilnehmende.</p>
|
||
</div>
|
||
<div class="rounded-xl border border-white/10 bg-white/5 p-4 text-sm text-white/80 shadow-sm">
|
||
<h3 class="text-base font-semibold text-white">Schnelles Teilen</h3>
|
||
<p class="mt-2">Direkter Link für Freund:innen, mit Ablaufsteuerung durch das Event-Team.</p>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
</noscript>
|
||
<div id="root"></div>
|
||
</body>
|
||
</html>
|