feat: integrate PWA support using next-pwa; update configuration in next.config.ts, add service worker files to .gitignore, and include manifest in layout.tsx

This commit is contained in:
Usman Baig
2026-02-04 10:45:00 +01:00
parent acbd8ef8bc
commit 787eac2151
8 changed files with 3164 additions and 164 deletions

5
.gitignore vendored
View File

@@ -34,3 +34,8 @@ yarn-error.log*
# typescript # typescript
*.tsbuildinfo *.tsbuildinfo
next-env.d.ts next-env.d.ts
# PWA
public/sw.js
public/workbox-*.js
public/swe-worker-*.js

View File

@@ -1,3 +1,4 @@
import { OfflineBanner } from '@/components/OfflineBanner'
import { ThemeProviders, Toaster } from '@ciphera-net/ui' import { ThemeProviders, Toaster } from '@ciphera-net/ui'
import { AuthProvider } from '@/lib/auth/context' import { AuthProvider } from '@/lib/auth/context'
import type { Metadata, Viewport } from 'next' import type { Metadata, Viewport } from 'next'
@@ -31,6 +32,7 @@ export const metadata: Metadata = {
shortcut: '/favicon.ico', shortcut: '/favicon.ico',
apple: '/pulse_icon_no_margins.png', apple: '/pulse_icon_no_margins.png',
}, },
manifest: '/manifest.json',
robots: { robots: {
index: true, index: true,
follow: true, follow: true,
@@ -45,6 +47,7 @@ export default function RootLayout({
return ( return (
<html lang="en" className={plusJakartaSans.variable} suppressHydrationWarning> <html lang="en" className={plusJakartaSans.variable} suppressHydrationWarning>
<body className="antialiased min-h-screen flex flex-col bg-white dark:bg-neutral-950 text-neutral-900 dark:text-neutral-50"> <body className="antialiased min-h-screen flex flex-col bg-white dark:bg-neutral-950 text-neutral-900 dark:text-neutral-50">
<OfflineBanner />
<ThemeProviders> <ThemeProviders>
<AuthProvider> <AuthProvider>
<LayoutContent>{children}</LayoutContent> <LayoutContent>{children}</LayoutContent>

View File

@@ -0,0 +1,17 @@
'use client';
import { useOnlineStatus } from '@/lib/hooks/useOnlineStatus';
import { FiWifiOff } from 'react-icons/fi';
export function OfflineBanner() {
const isOnline = useOnlineStatus();
if (isOnline) return null;
return (
<div className="bg-yellow-500/10 border-b border-yellow-500/20 text-yellow-600 px-4 py-2 text-sm flex items-center justify-center gap-2 font-medium z-50 relative">
<FiWifiOff className="w-4 h-4" />
<span>You are currently offline. Changes may not be saved.</span>
</div>
);
}

View File

@@ -0,0 +1,25 @@
import { useState, useEffect } from 'react';
export function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
// Check initial status
if (typeof window !== 'undefined') {
setIsOnline(navigator.onLine);
}
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return isOnline;
}

View File

@@ -1,4 +1,10 @@
import type { NextConfig } from 'next' import type { NextConfig } from 'next'
const withPWA = require("@ducanh2912/next-pwa").default({
dest: "public",
register: true,
skipWaiting: true,
disable: process.env.NODE_ENV === "development",
});
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
reactStrictMode: true, reactStrictMode: true,
@@ -17,4 +23,4 @@ const nextConfig: NextConfig = {
}, },
} }
export default nextConfig export default withPWA(nextConfig)

3248
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@
}, },
"dependencies": { "dependencies": {
"@ciphera-net/ui": "^0.0.39", "@ciphera-net/ui": "^0.0.39",
"@ducanh2912/next-pwa": "^10.2.9",
"axios": "^1.13.2", "axios": "^1.13.2",
"country-flag-icons": "^1.6.4", "country-flag-icons": "^1.6.4",
"d3-scale": "^4.0.2", "d3-scale": "^4.0.2",

21
public/manifest.json Normal file
View File

@@ -0,0 +1,21 @@
{
"name": "Pulse Analytics",
"short_name": "Pulse",
"description": "Privacy-friendly website analytics",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#000000",
"icons": [
{
"src": "/pulse_icon_no_margins.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/pulse_icon_no_margins.png",
"sizes": "512x512",
"type": "image/png"
}
]
}