PULSE-31: PWA support and offline banner #4

Merged
uz1mani merged 12 commits from staging into main 2026-02-04 11:46:26 +00:00
8 changed files with 3169 additions and 169 deletions
Showing only changes of commit 787eac2151 - Show all commits

5
.gitignore vendored
View File

@@ -34,3 +34,8 @@ yarn-error.log*
# typescript
*.tsbuildinfo
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 { AuthProvider } from '@/lib/auth/context'
import type { Metadata, Viewport } from 'next'
@@ -31,6 +32,7 @@ export const metadata: Metadata = {
shortcut: '/favicon.ico',
apple: '/pulse_icon_no_margins.png',
},
manifest: '/manifest.json',
robots: {
index: true,
follow: true,
@@ -45,6 +47,7 @@ export default function RootLayout({
return (
<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">
<OfflineBanner />
<ThemeProviders>
<AuthProvider>
<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);
greptile-apps[bot] commented 2026-02-04 11:30:24 +00:00 (Migrated from github.com)
Review

window.addEventListener is called without checking if window exists (like line 8 does), which will cause SSR errors in Next.js.

    if (typeof window !== 'undefined') {
      window.addEventListener('online', handleOnline);
      window.addEventListener('offline', handleOffline);
    }
Prompt To Fix With AI
This is a comment left during a code review.
Path: lib/hooks/useOnlineStatus.ts
Line: 15:16

Comment:
`window.addEventListener` is called without checking if `window` exists (like line 8 does), which will cause SSR errors in Next.js.

```suggestion
    if (typeof window !== 'undefined') {
      window.addEventListener('online', handleOnline);
      window.addEventListener('offline', handleOffline);
    }
```

How can I resolve this? If you propose a fix, please make it concise.
`window.addEventListener` is called without checking if `window` exists (like line 8 does), which will cause SSR errors in Next.js. ```suggestion if (typeof window !== 'undefined') { window.addEventListener('online', handleOnline); window.addEventListener('offline', handleOffline); } ``` <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: lib/hooks/useOnlineStatus.ts Line: 15:16 Comment: `window.addEventListener` is called without checking if `window` exists (like line 8 does), which will cause SSR errors in Next.js. ```suggestion if (typeof window !== 'undefined') { window.addEventListener('online', handleOnline); window.addEventListener('offline', handleOffline); } ``` How can I resolve this? If you propose a fix, please make it concise. ````` </details>
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'
const withPWA = require("@ducanh2912/next-pwa").default({
dest: "public",
register: true,
skipWaiting: true,
disable: process.env.NODE_ENV === "development",
});
const nextConfig: NextConfig = {
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": {
"@ciphera-net/ui": "^0.0.39",
"@ducanh2912/next-pwa": "^10.2.9",
"axios": "^1.13.2",
"country-flag-icons": "^1.6.4",
"d3-scale": "^4.0.2",

21
public/manifest.json Normal file
View File

@@ -0,0 +1,21 @@
{
greptile-apps[bot] commented 2026-02-04 11:30:20 +00:00 (Migrated from github.com)
Review

Icon paths use URL encoding (%20 and %26) which may cause issues with some browsers or PWA installers. Use hyphens or underscores in filenames instead.

    {
      "src": "/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
Prompt To Fix With AI
This is a comment left during a code review.
Path: public/manifest.json
Line: 11:18

Comment:
Icon paths use URL encoding (`%20` and `%26`) which may cause issues with some browsers or PWA installers. Use hyphens or underscores in filenames instead.

```suggestion
    {
      "src": "/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
```

How can I resolve this? If you propose a fix, please make it concise.
Icon paths use URL encoding (`%20` and `%26`) which may cause issues with some browsers or PWA installers. Use hyphens or underscores in filenames instead. ```suggestion { "src": "/icon-192x192.png", "sizes": "192x192", "type": "image/png" }, { "src": "/icon-512x512.png", "sizes": "512x512", "type": "image/png" } ``` <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: public/manifest.json Line: 11:18 Comment: Icon paths use URL encoding (`%20` and `%26`) which may cause issues with some browsers or PWA installers. Use hyphens or underscores in filenames instead. ```suggestion { "src": "/icon-192x192.png", "sizes": "192x192", "type": "image/png" }, { "src": "/icon-512x512.png", "sizes": "512x512", "type": "image/png" } ``` How can I resolve this? If you propose a fix, please make it concise. ````` </details>
greptile-apps[bot] commented 2026-02-04 11:30:23 +00:00 (Migrated from github.com)
Review

theme_color is #000000 but layout.tsx:19 sets themeColor: '#FD5E0F'. These should match for consistency.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: public/manifest.json
Line: 8:8

Comment:
`theme_color` is `#000000` but `layout.tsx:19` sets `themeColor: '#FD5E0F'`. These should match for consistency.

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.
`theme_color` is `#000000` but `layout.tsx:19` sets `themeColor: '#FD5E0F'`. These should match for consistency. <sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub> <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: public/manifest.json Line: 8:8 Comment: `theme_color` is `#000000` but `layout.tsx:19` sets `themeColor: '#FD5E0F'`. These should match for consistency. <sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub> How can I resolve this? If you propose a fix, please make it concise. ````` </details>
"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"
}
]
}