feat: add Offline Mode Indicator (Bunker Status)
This commit is contained in:
20
public/sw.js
20
public/sw.js
@@ -43,10 +43,19 @@ self.addEventListener('install', (event) => {
|
|||||||
});
|
});
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
console.log('✅ PWA: Bunker build finished');
|
console.log('✅ PWA: Bunker build finished');
|
||||||
|
|
||||||
|
// Signal to clients that pre-caching is complete
|
||||||
|
broadcast({ type: 'PRECACHE_COMPLETE', version: CACHE_NAME });
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Helper: Broadcast to all clients
|
||||||
|
async function broadcast(message) {
|
||||||
|
const clients = await self.clients.matchAll();
|
||||||
|
clients.forEach(client => client.postMessage(message));
|
||||||
|
}
|
||||||
|
|
||||||
// Activate: Alte Bunker räumen
|
// Activate: Alte Bunker räumen
|
||||||
self.addEventListener('activate', (event) => {
|
self.addEventListener('activate', (event) => {
|
||||||
event.waitUntil(
|
event.waitUntil(
|
||||||
@@ -64,6 +73,17 @@ self.addEventListener('activate', (event) => {
|
|||||||
self.clients.claim();
|
self.clients.claim();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Communication: Listen for status checks
|
||||||
|
self.addEventListener('message', (event) => {
|
||||||
|
if (event.data?.type === 'CHECK_BUNKER_STATUS') {
|
||||||
|
event.source.postMessage({
|
||||||
|
type: 'BUNKER_STATUS',
|
||||||
|
isReady: true,
|
||||||
|
version: CACHE_NAME
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
self.addEventListener('fetch', (event) => {
|
self.addEventListener('fetch', (event) => {
|
||||||
// Nur GET-Requests cachen
|
// Nur GET-Requests cachen
|
||||||
if (event.request.method !== 'GET') return;
|
if (event.request.method !== 'GET') return;
|
||||||
|
|||||||
@@ -1,32 +1,84 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { WifiOff } from 'lucide-react';
|
import { WifiOff, ShieldCheck } from 'lucide-react';
|
||||||
|
|
||||||
export default function OfflineIndicator() {
|
export default function OfflineIndicator() {
|
||||||
const [isOffline, setIsOffline] = useState(false);
|
const [isOffline, setIsOffline] = useState(false);
|
||||||
|
const [isBunkerReady, setIsBunkerReady] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsOffline(!navigator.onLine);
|
setIsOffline(!navigator.onLine);
|
||||||
|
// Check if bunker was already ready from previous session
|
||||||
|
const savedReady = localStorage.getItem('whisky_bunker_ready') === 'true';
|
||||||
|
setIsBunkerReady(savedReady);
|
||||||
|
|
||||||
const handleOnline = () => setIsOffline(false);
|
const handleOnline = () => setIsOffline(false);
|
||||||
const handleOffline = () => setIsOffline(true);
|
const handleOffline = () => setIsOffline(true);
|
||||||
|
|
||||||
|
const handleMessage = (event: MessageEvent) => {
|
||||||
|
if (event.data?.type === 'PRECACHE_COMPLETE' || event.data?.type === 'BUNKER_STATUS') {
|
||||||
|
console.log('🛡️ PWA: Bunker is ready for offline use!');
|
||||||
|
setIsBunkerReady(true);
|
||||||
|
localStorage.setItem('whisky_bunker_ready', 'true');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
window.addEventListener('online', handleOnline);
|
window.addEventListener('online', handleOnline);
|
||||||
window.addEventListener('offline', handleOffline);
|
window.addEventListener('offline', handleOffline);
|
||||||
|
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.addEventListener('message', handleMessage);
|
||||||
|
|
||||||
|
// Proactively check status if SW is already active
|
||||||
|
if (navigator.serviceWorker.controller) {
|
||||||
|
navigator.serviceWorker.controller.postMessage({ type: 'CHECK_BUNKER_STATUS' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('online', handleOnline);
|
window.removeEventListener('online', handleOnline);
|
||||||
window.removeEventListener('offline', handleOffline);
|
window.removeEventListener('offline', handleOffline);
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.removeEventListener('message', handleMessage);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!isOffline) return null;
|
if (isOffline) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed top-0 left-0 w-full bg-red-600 text-white text-[10px] font-black uppercase tracking-widest py-1 flex items-center justify-center gap-2 z-[9999] animate-pulse">
|
<div className="fixed top-0 left-0 w-full bg-red-600 text-white text-[10px] font-black uppercase tracking-widest py-1 flex items-center justify-center gap-2 z-[9999] animate-pulse">
|
||||||
<WifiOff size={12} />
|
<WifiOff size={12} />
|
||||||
Offline-Modus: Du siehst eine gespeicherte Version
|
Offline-Modus: Bunker aktiv 🛡️
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBunkerReady) {
|
||||||
|
return (
|
||||||
|
<div className="fixed bottom-20 right-4 z-[90] animate-in fade-in slide-in-from-right-4 duration-500">
|
||||||
|
<div className="bg-zinc-900/80 backdrop-blur-md border border-green-500/30 px-3 py-1.5 rounded-full flex items-center gap-2 shadow-lg shadow-green-500/10 group hover:bg-zinc-900 transition-colors cursor-help">
|
||||||
|
<ShieldCheck size={14} className="text-green-500" />
|
||||||
|
<span className="text-[9px] font-black uppercase tracking-widest text-zinc-300">
|
||||||
|
Bunker Aktiv
|
||||||
|
</span>
|
||||||
|
<div className="absolute bottom-full right-0 mb-2 w-48 p-2 bg-zinc-900 text-[10px] text-zinc-400 rounded-xl border border-white/10 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none shadow-2xl">
|
||||||
|
Die App ist vollständig im "Bunker" gespeichert und funktioniert auch ohne Internet.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show nothing if online and not ready yet (or show a loading state?)
|
||||||
|
return (
|
||||||
|
<div className="fixed bottom-20 right-4 z-[90] animate-in fade-in slide-in-from-right-4">
|
||||||
|
<div className="bg-zinc-900/80 backdrop-blur-md border border-amber-500/30 px-3 py-1.5 rounded-full flex items-center gap-2 shadow-lg shadow-amber-500/10">
|
||||||
|
<div className="w-1.5 h-1.5 bg-amber-500 rounded-full animate-pulse" />
|
||||||
|
<span className="text-[9px] font-black uppercase tracking-widest text-zinc-300">
|
||||||
|
Bunker wird geladen...
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user