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
uz1mani commented 2026-02-04 11:25:45 +00:00 (Migrated from github.com)

Summary

Implements optional PWA support and a clear "You're offline" experience when the dashboard is unreachable (PULSE-31).

Changes

PWA

  • Add @ducanh2912/next-pwa with service worker (disabled in dev, active in production).
  • Add public/manifest.json: name "Pulse", 192×192 and 512×512 icons, standalone display.
  • Build: use next build --webpack for next-pwa compatibility with Next.js 16.

Offline banner

  • New OfflineBanner component and useOnlineStatus hook; banner only when logged in and offline.
  • Fixed bar at top of viewport (Option 1): fixed top-0, z-[100], rounded-b-xl, no header integration.
  • Header gets optional topOffset when the bar is visible so layout stays correct; main uses pt-[8.5rem] when offline.

ciphera-ui

  • Header: optional bottomContent and topOffset; overflow-hidden on bottom strip for rounded corners.
  • Version bumped to 0.0.42; all frontends (Pulse, drop-frontend, auth-frontend, website) use ^0.0.42.

Verification

  • Offline: Log in, set Network to Offline in DevTools; yellow "You're offline" bar appears at top, header below it.
  • Install: Install prompt shows "Pulse" and app icon from manifest icons.
  • Logged out: Offline bar does not show.
## Summary Implements optional PWA support and a clear "You're offline" experience when the dashboard is unreachable (PULSE-31). ## Changes ### PWA - Add `@ducanh2912/next-pwa` with service worker (disabled in dev, active in production). - Add `public/manifest.json`: name "Pulse", 192×192 and 512×512 icons, standalone display. - Build: use `next build --webpack` for next-pwa compatibility with Next.js 16. ### Offline banner - New `OfflineBanner` component and `useOnlineStatus` hook; banner only when **logged in** and offline. - Fixed bar at **top of viewport** (Option 1): `fixed top-0`, `z-[100]`, `rounded-b-xl`, no header integration. - Header gets optional `topOffset` when the bar is visible so layout stays correct; main uses `pt-[8.5rem]` when offline. ### ciphera-ui - Header: optional `bottomContent` and `topOffset`; `overflow-hidden` on bottom strip for rounded corners. - Version bumped to 0.0.42; all frontends (Pulse, drop-frontend, auth-frontend, website) use `^0.0.42`. ### Verification - **Offline:** Log in, set Network to Offline in DevTools; yellow "You're offline" bar appears at top, header below it. - **Install:** Install prompt shows "Pulse" and app icon from manifest icons. - **Logged out:** Offline bar does not show.
greptile-apps[bot] commented 2026-02-04 11:30:16 +00:00 (Migrated from github.com)

Greptile Overview

Greptile Summary

This PR implements PWA support and an offline status banner for authenticated users. The implementation adds @ducanh2912/next-pwa for service worker functionality (disabled in development), creates a fixed yellow banner at the viewport top when users go offline, and adjusts the header and main content padding dynamically to accommodate the banner.

Key Changes:

  • Added useOnlineStatus hook that listens to browser online/offline events with proper SSR handling
  • Created OfflineBanner component that accepts isOnline as a prop for clean separation of concerns
  • Integrated offline banner into layout with auth-gated rendering (only shown when logged in)
  • Added PWA manifest with correct theme color matching layout.tsx
  • Updated build script to use --webpack flag for next-pwa compatibility

Previous Issues Resolved:

  • Theme color mismatch between manifest and layout.tsx has been fixed
  • Icon paths no longer use URL encoding
  • Duplicate useOnlineStatus calls eliminated by lifting state to parent
  • Dynamic padding calculation now uses the barHeightRem constant correctly
  • SSR safety confirmed with proper window checks in useOnlineStatus hook

The implementation is clean, follows React best practices, and all previously identified issues have been addressed.

Confidence Score: 5/5

  • This PR is safe to merge with no blocking issues
  • All code is well-structured with proper SSR handling, clean component separation, and correct integration with the UI library. Previous review issues have been fully addressed. The implementation follows React and Next.js best practices.
  • No files require special attention

Important Files Changed

Filename Overview
app/layout-content.tsx Added offline banner integration with proper auth gating and dynamic layout adjustments
components/OfflineBanner.tsx New component displaying offline status with clean conditional rendering
lib/hooks/useOnlineStatus.ts Custom hook tracking browser online/offline state with proper SSR handling
next.config.ts Added PWA wrapper with service worker disabled in development
public/manifest.json PWA manifest configured with correct theme color and icon paths

Sequence Diagram

sequenceDiagram
    participant Browser
    participant LayoutContent
    participant useOnlineStatus
    participant OfflineBanner
    participant Header
    participant Main

    Browser->>LayoutContent: Mount component
    LayoutContent->>useOnlineStatus: Initialize hook
    useOnlineStatus->>Browser: Check navigator.onLine
    Browser-->>useOnlineStatus: Online status
    useOnlineStatus->>Browser: Add 'online'/'offline' listeners
    useOnlineStatus-->>LayoutContent: Return isOnline state
    
    LayoutContent->>LayoutContent: Calculate showOfflineBar<br/>(auth.user && !isOnline)
    
    alt User logged in
        LayoutContent->>OfflineBanner: Render with isOnline prop
        
        alt User is offline
            OfflineBanner->>OfflineBanner: Display yellow banner at top
            LayoutContent->>Header: Apply topOffset (2.5rem)
            LayoutContent->>Main: Apply dynamic padding (8.5rem)
        else User is online
            OfflineBanner->>OfflineBanner: Return null (hidden)
            LayoutContent->>Header: No topOffset
            LayoutContent->>Main: Apply static padding (pt-24)
        end
    else User logged out
        LayoutContent->>OfflineBanner: Don't render
        LayoutContent->>Header: No topOffset
        LayoutContent->>Main: Apply static padding (pt-24)
    end
    
    Browser->>Browser: Network status changes
    Browser->>useOnlineStatus: Trigger 'online'/'offline' event
    useOnlineStatus->>LayoutContent: Update isOnline state
    LayoutContent->>LayoutContent: Recalculate layout
    LayoutContent->>OfflineBanner: Re-render with new state
<h2>Greptile Overview</h2> <h3>Greptile Summary</h3> This PR implements PWA support and an offline status banner for authenticated users. The implementation adds `@ducanh2912/next-pwa` for service worker functionality (disabled in development), creates a fixed yellow banner at the viewport top when users go offline, and adjusts the header and main content padding dynamically to accommodate the banner. **Key Changes:** - Added `useOnlineStatus` hook that listens to browser online/offline events with proper SSR handling - Created `OfflineBanner` component that accepts `isOnline` as a prop for clean separation of concerns - Integrated offline banner into layout with auth-gated rendering (only shown when logged in) - Added PWA manifest with correct theme color matching layout.tsx - Updated build script to use `--webpack` flag for next-pwa compatibility **Previous Issues Resolved:** - Theme color mismatch between manifest and layout.tsx has been fixed - Icon paths no longer use URL encoding - Duplicate `useOnlineStatus` calls eliminated by lifting state to parent - Dynamic padding calculation now uses the `barHeightRem` constant correctly - SSR safety confirmed with proper window checks in useOnlineStatus hook The implementation is clean, follows React best practices, and all previously identified issues have been addressed. <h3>Confidence Score: 5/5</h3> - This PR is safe to merge with no blocking issues - All code is well-structured with proper SSR handling, clean component separation, and correct integration with the UI library. Previous review issues have been fully addressed. The implementation follows React and Next.js best practices. - No files require special attention <details><summary><h3>Important Files Changed</h3></summary> | Filename | Overview | |----------|----------| | app/layout-content.tsx | Added offline banner integration with proper auth gating and dynamic layout adjustments | | components/OfflineBanner.tsx | New component displaying offline status with clean conditional rendering | | lib/hooks/useOnlineStatus.ts | Custom hook tracking browser online/offline state with proper SSR handling | | next.config.ts | Added PWA wrapper with service worker disabled in development | | public/manifest.json | PWA manifest configured with correct theme color and icon paths | </details> </details> <h3>Sequence Diagram</h3> ```mermaid sequenceDiagram participant Browser participant LayoutContent participant useOnlineStatus participant OfflineBanner participant Header participant Main Browser->>LayoutContent: Mount component LayoutContent->>useOnlineStatus: Initialize hook useOnlineStatus->>Browser: Check navigator.onLine Browser-->>useOnlineStatus: Online status useOnlineStatus->>Browser: Add 'online'/'offline' listeners useOnlineStatus-->>LayoutContent: Return isOnline state LayoutContent->>LayoutContent: Calculate showOfflineBar<br/>(auth.user && !isOnline) alt User logged in LayoutContent->>OfflineBanner: Render with isOnline prop alt User is offline OfflineBanner->>OfflineBanner: Display yellow banner at top LayoutContent->>Header: Apply topOffset (2.5rem) LayoutContent->>Main: Apply dynamic padding (8.5rem) else User is online OfflineBanner->>OfflineBanner: Return null (hidden) LayoutContent->>Header: No topOffset LayoutContent->>Main: Apply static padding (pt-24) end else User logged out LayoutContent->>OfflineBanner: Don't render LayoutContent->>Header: No topOffset LayoutContent->>Main: Apply static padding (pt-24) end Browser->>Browser: Network status changes Browser->>useOnlineStatus: Trigger 'online'/'offline' event useOnlineStatus->>LayoutContent: Update isOnline state LayoutContent->>LayoutContent: Recalculate layout LayoutContent->>OfflineBanner: Re-render with new state ```
greptile-apps[bot] (Migrated from github.com) reviewed 2026-02-04 11:30:25 +00:00
greptile-apps[bot] (Migrated from github.com) left a comment

4 files reviewed, 5 comments

Edit Code Review Agent Settings | Greptile

<sub>4 files reviewed, 5 comments</sub> <sub>[Edit Code Review Agent Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews)</sub>
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 11:30:22 +00:00

useOnlineStatus is called here and also inside OfflineBanner (line 48), creating duplicate event listeners for online/offline events. Consider lifting the hook call to this component and passing isOnline as a prop to OfflineBanner.

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: 16:16

Comment:
`useOnlineStatus` is called here and also inside `OfflineBanner` (line 48), creating duplicate event listeners for `online`/`offline` events. Consider lifting the hook call to this component and passing `isOnline` as a prop to `OfflineBanner`.

<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.
`useOnlineStatus` is called here and also inside `OfflineBanner` (line 48), creating duplicate event listeners for `online`/`offline` events. Consider lifting the hook call to this component and passing `isOnline` as a prop to `OfflineBanner`. <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: 16:16 Comment: `useOnlineStatus` is called here and also inside `OfflineBanner` (line 48), creating duplicate event listeners for `online`/`offline` events. Consider lifting the hook call to this component and passing `isOnline` as a prop to `OfflineBanner`. <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>
@@ -37,8 +40,14 @@ export default function LayoutContent({ children }: { children: React.ReactNode
router.push('/onboarding')
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 11:30:21 +00:00

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>
@@ -0,0 +12,4 @@
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 11:30:24 +00:00

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>
@@ -0,0 +1,21 @@
{
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 11:30:20 +00:00

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] (Migrated from github.com) commented 2026-02-04 11:30:23 +00:00

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>
greptile-apps[bot] (Migrated from github.com) reviewed 2026-02-04 11:38:20 +00:00
greptile-apps[bot] (Migrated from github.com) left a comment

5 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

<sub>5 files reviewed, 1 comment</sub> <sub>[Edit Code Review Agent Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews)</sub>
@@ -37,8 +40,14 @@ export default function LayoutContent({ children }: { children: React.ReactNode
router.push('/onboarding')
greptile-apps[bot] (Migrated from github.com) commented 2026-02-04 11:38:19 +00:00

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>
Sign in to join this conversation.
No description provided.