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
13 changed files with 3179 additions and 171 deletions

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,7 +1,9 @@
'use client'
import { OfflineBanner } from '@/components/OfflineBanner'
import { Header, Footer } from '@ciphera-net/ui'
import { useAuth } from '@/lib/auth/context'
import { useOnlineStatus } from '@/lib/hooks/useOnlineStatus'
import Link from 'next/link'
import { useEffect, useState } from 'react'
import { getUserOrganizations, switchContext } from '@/lib/api/organization'
@@ -11,6 +13,7 @@ import { useRouter } from 'next/navigation'
export default function LayoutContent({ children }: { children: React.ReactNode }) {
const auth = useAuth()
const router = useRouter()
const isOnline = useOnlineStatus()
const [orgs, setOrgs] = useState<any[]>([])
// * Fetch organizations for the header workspace switcher
@@ -37,8 +40,14 @@ export default function LayoutContent({ children }: { children: React.ReactNode
router.push('/onboarding')
greptile-apps[bot] commented 2026-02-04 11:30:21 +00:00 (Migrated from github.com)
Review

Moved auth check to parent instead of keeping it in OfflineBanner - the banner now only handles online/offline state while this component controls when it shows based on authentication.

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: app/layout-content.tsx
Line: 48:48

Comment:
Moved auth check to parent instead of keeping it in `OfflineBanner` - the banner now only handles online/offline state while this component controls when it shows based on authentication.

<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.
Moved auth check to parent instead of keeping it in `OfflineBanner` - the banner now only handles online/offline state while this component controls when it shows based on authentication. <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: app/layout-content.tsx Line: 48:48 Comment: Moved auth check to parent instead of keeping it in `OfflineBanner` - the banner now only handles online/offline state while this component controls when it shows based on authentication. <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>
greptile-apps[bot] commented 2026-02-04 11:38:19 +00:00 (Migrated from github.com)
Review

barHeightRem is defined but not used in the pt-[8.5rem] calculation on line 64. The hardcoded value could get out of sync if the banner height changes.

Prompt To Fix With AI
This is a comment left during a code review.
Path: app/layout-content.tsx
Line: 44:44

Comment:
`barHeightRem` is defined but not used in the `pt-[8.5rem]` calculation on line 64. The hardcoded value could get out of sync if the banner height changes.

How can I resolve this? If you propose a fix, please make it concise.
`barHeightRem` is defined but not used in the `pt-[8.5rem]` calculation on line 64. The hardcoded value could get out of sync if the banner height changes. <details><summary>Prompt To Fix With AI</summary> `````markdown This is a comment left during a code review. Path: app/layout-content.tsx Line: 44:44 Comment: `barHeightRem` is defined but not used in the `pt-[8.5rem]` calculation on line 64. The hardcoded value could get out of sync if the banner height changes. How can I resolve this? If you propose a fix, please make it concise. ````` </details>
}
const showOfflineBar = Boolean(auth.user && !isOnline);
const barHeightRem = 2.5;
const headerHeightRem = 6;
const mainTopPaddingRem = barHeightRem + headerHeightRem;
return (
<>
{auth.user && <OfflineBanner isOnline={isOnline} />}
<Header
auth={auth}
LinkComponent={Link}
@@ -52,8 +61,12 @@ export default function LayoutContent({ children }: { children: React.ReactNode
showFaq={false}
showSecurity={false}
showPricing={true}
topOffset={showOfflineBar ? `${barHeightRem}rem` : undefined}
/>
<main className="flex-1 pt-24 pb-8">
<main
className={`flex-1 pb-8 ${showOfflineBar ? '' : 'pt-24'}`}
style={showOfflineBar ? { paddingTop: `${mainTopPaddingRem}rem` } : undefined}
>
{children}
</main>
<Footer

View File

@@ -31,6 +31,7 @@ export const metadata: Metadata = {
shortcut: '/favicon.ico',
apple: '/pulse_icon_no_margins.png',
},
manifest: '/manifest.json',
robots: {
index: true,
follow: true,

View File

@@ -0,0 +1,14 @@
'use client';
import { FiWifiOff } from 'react-icons/fi';
export function OfflineBanner({ isOnline }: { isOnline: boolean }) {
if (isOnline) return null;
return (
<div className="fixed top-0 left-0 right-0 z-[100] rounded-b-xl bg-yellow-500/15 dark:bg-yellow-500/25 border-b border-yellow-500/30 dark:border-yellow-500/40 text-yellow-700 dark:text-yellow-300 px-4 sm:px-8 py-2.5 text-sm flex items-center justify-center gap-2 font-medium shadow-md">
<FiWifiOff className="w-4 h-4 shrink-0" />
<span>You are currently offline. Changes may not be saved.</span>
</div>
);
}

View File

@@ -0,0 +1,24 @@
import { useState, useEffect } from 'react';
export function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
if (typeof window === 'undefined') return;
setIsOnline(navigator.onLine);
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
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>
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)

3257
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,13 +4,14 @@
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"build": "next build --webpack",
"start": "next start",
"lint": "next lint",
"type-check": "tsc --noEmit"
},
"dependencies": {
"@ciphera-net/ui": "^0.0.38",
"@ciphera-net/ui": "^0.0.42",
"@ducanh2912/next-pwa": "^10.2.9",
"axios": "^1.13.2",
"country-flag-icons": "^1.6.4",
"d3-scale": "^4.0.2",

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
public/icon-192x192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

BIN
public/icon-512x512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

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",
"short_name": "Pulse",
"description": "Privacy-friendly website analytics",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#FD5E0F",
"icons": [
{
"src": "/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}