Skip to content

Conversation

@wayzeek
Copy link

@wayzeek wayzeek commented Sep 25, 2025

CLR-S (2)

Pull Request | GrantChain

1. Issue Link


2. Brief Description of the Issue

Implement Progressive Web App (PWA) functionality to enable app installation, offline support, and native-like experience across all platforms. This will improve user engagement and provide a mobile app-like experience without requiring app store distribution.


3. Type of Change

Mark with an x all the checkboxes that apply (like [x]).

  • 📝 Documentation (updates to README, docs, or comments)
  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • 👌 Enhancement (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to change)

4. Changes Made

  • Added Next.js built-in manifest API (src/app/manifest.ts) with complete PWA metadata
  • Implemented custom service worker (public/sw-custom.js) with advanced caching strategies
  • Created PWA install prompt component with iOS-specific instructions and cross-platform support
  • Added PWA state management hook (src/hooks/usePWA.ts) for service worker lifecycle management
  • Configured PWA settings in next.config.ts with next-pwa integration
  • Added complete icon set (8 sizes) with maskable icons for Android adaptive icons
  • Updated layout with PWA metadata, security headers, and offline fallback page
  • Implemented PWA status component for real-time PWA state indicators

5. Important Notes

  • Requires HTTPS deployment for full PWA features to work properly
  • Service worker will be automatically registered on production builds
  • Install prompts will appear based on browser support (Chrome, Edge, Safari iOS 16.4+)
  • Offline functionality includes intelligent caching with fallback page
  • All PWA files are optimized and follow Next.js best practices
  • No breaking changes to existing functionality

Summary by CodeRabbit

  • New Features
    • Progressive Web App support: installable app with offline page, caching, and improved performance.
    • Push notifications with actions and notification permission handling.
    • In-app install prompt with dismissal and update flow; iOS guidance included.
    • PWA status indicators for online/offline, installed state, and available updates.
    • App manifest, icons, and metadata for richer OS integration.
    • Enhanced security and caching headers across routes.
    • Payout creation initializes additional optional fields with sensible defaults.
  • Chores
    • Added PWA-related dependencies.

…ompt

- Add PWA configuration in next.config.ts using next-pwa
- Create custom service worker (sw-custom.js) for caching and push notifications
- Implement PWA install prompt component for user experience
- Add manifest file for PWA metadata and icons
- Include offline.html for offline access
- Update layout and metadata for PWA compatibility
- Add hooks for PWA state management and notifications
- Introduce new icons for PWA in public/icons directory
…ion and service worker files

- Update next.config.ts and sw-custom.js to use consistent double quotes for strings
- Refactor layout.tsx and manifest.ts for improved readability with multiline strings
- Adjust PWAInstallPrompt and PWAStatus components for better code organization and clarity
- Enhance usePWA hook with consistent formatting and improved event handling
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 25, 2025

Walkthrough

Adds PWA support: configures next-pwa in Next.js, registers a custom service worker, provides offline page and icons, defines a web app manifest, injects PWA meta in layout, and introduces hooks/components for install prompts and status. Also extends payout creation payload with additional default fields.

Changes

Cohort / File(s) Summary
PWA Config
next.config.ts, package.json
Integrates next-pwa via withPWA wrapper and headers(); adds dependencies next-pwa and @types/next-pwa.
Public PWA Assets
public/offline.html, public/sw-custom.js, public/icons/browserconfig.xml
Adds offline page, custom service worker with caching/push/notifications, and Windows browserconfig for tiles.
App Integration
src/app/layout.tsx, src/app/manifest.ts
Injects PWA/meta tags and renders PWAInstallPrompt; adds Next.js manifest endpoint with icons, colors, and shortcuts.
PWA UI & Hooks
src/components/shared/PWAInstallPrompt.tsx, src/components/shared/PWAStatus.tsx, src/hooks/usePWA.ts
Adds install prompt component, status badges, and a hook for SW registration, update handling, and notifications.
Payout Payload Defaults
src/components/modules/payouts/hooks/usePayoutMutations.ts
Extends create payload with defaulted fields: rewards null, skills [], social_media/application_deadline/announcement_deadline/notes/files null.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant App as Next.js App
  participant Hook as usePWA()
  participant SW as Service Worker
  participant Net as Network

  U->>App: Load page
  App->>Hook: Initialize PWA
  Hook->>SW: navigator.serviceWorker.register('/sw-custom.js')
  SW-->>Hook: registration + updatefound events
  alt Update available
    SW-->>Hook: statechange -> installed (waiting)
    Hook-->>App: hasUpdate = true
  else No update
    Hook-->>App: isSupported / isOnline / isInstalled
  end
  App-->>U: Show PWAStatus / PWAInstallPrompt
  U->>App: Click "Update"
  App->>Hook: updateServiceWorker()
  Hook->>SW: postMessage('SKIP_WAITING')
  SW-->>App: controllerchange
  App->>U: Reload
Loading
sequenceDiagram
  participant SW as Service Worker
  participant Client as Page
  participant Cache as Cache Storage
  participant Net as Network

  Note over SW: Install
  SW->>Cache: Add offline.html, icons, root
  SW->>SW: skipWaiting()

  Note over SW: Activate
  SW->>Cache: Delete old caches
  SW->>Client: clients.claim()

  Note over SW: Fetch
  Client->>SW: GET request
  alt navigation
    SW->>Net: fetch()
    alt net fail
      SW->>Cache: match(offline.html)
      SW-->>Client: offline.html
    else success
      SW-->>Client: response
    end
  else /api/*
    SW->>Net: fetch()
    alt success
      SW->>Cache: put()
      SW-->>Client: response
    else fail
      SW->>Cache: match()
      SW-->>Client: cached or error
    end
  else static assets
    SW->>Cache: match()
    alt hit
      SW-->>Client: cached
    else miss
      SW->>Net: fetch() then cache
      SW-->>Client: response
    end
  end
Loading
sequenceDiagram
  actor U as User
  participant App as PWAInstallPrompt
  participant Hook as usePWA()
  participant SW as Service Worker

  App-->>U: Show install/update prompt (if eligible)
  U->>App: Click "Install"
  App->>App: deferredPrompt.prompt()
  App-->>U: Await choice
  alt accepted
    App->>App: Clear deferredPrompt
  else dismissed
    App->>App: Store suppression timestamp (7 days)
  end
  alt Update available
    U->>App: Click "Update"
    App->>Hook: updateServiceWorker()
    Hook->>SW: postMessage('SKIP_WAITING')
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

enhancement, External Dev

Poem

I cached some carrots for offline delight,
A service worker hums through the night.
Tap to install—our burrow’s a app! 🥕
Icons gleam, signals snap.
When updates hop in, I’ll nudge you to see—
Fresh bytes, warm lights, PWA jubilee!

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning The change to src/components/modules/payouts/hooks/usePayoutMutations.ts adds default payload fields for payouts, which is unrelated to the PWA implementation objectives defined in issue #115. Please remove or relocate the payout mutation changes into a separate pull request focused on that domain to keep this PR scoped solely to the PWA implementation.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues Check ✅ Passed The changes fully implement the requirements from issue #115 by installing and configuring next-pwa, updating next.config.ts, adding the manifest endpoint, service worker with caching strategies, install prompt, and related hooks and components that satisfy PWA functionality and expected validations.
Description Check ✅ Passed The description covers all core template sections including issue link, brief description, type of change, changes made, and important notes, but it omits the evidence before and after solution sections.
Title Check ✅ Passed The title "feat: add progressive web app implementation" concisely describes the primary enhancement of integrating PWA support into the application, including service worker, manifest, and install prompt additions. It aligns directly with the PR objectives of adding PWA functionality via next-pwa and related components. The Conventional Commit prefix is used correctly and no extraneous details or vague terms are included.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/modules/payouts/hooks/usePayoutMutations.ts (1)

55-69: Restore form values when creating a payout.

These new defaults overwrite whatever the user submits (e.g., notes, deadlines, social links) because we spread restData first and then unconditionally clobber the same fields. Any non-empty input will be dropped on create, so the API receives null instead. Please default only when the field is missing.

-        rewards: null,
-        skills: [],
-        social_media: null,
-        application_deadline: null,
-        announcement_deadline: null,
-        notes: null,
-        files: null,
+        rewards: data.rewards ?? null,
+        skills: data.skills ?? [],
+        social_media:
+          data.social_media !== undefined ? data.social_media : null,
+        application_deadline:
+          data.application_deadline !== undefined
+            ? data.application_deadline
+            : null,
+        announcement_deadline:
+          data.announcement_deadline !== undefined
+            ? data.announcement_deadline
+            : null,
+        notes: data.notes ?? null,
+        files: data.files ?? null,
🧹 Nitpick comments (12)
next.config.ts (1)

67-110: Review PWA caching strategies for potential performance issues.

The runtime caching configuration has some concerns:

  1. Line 76: The catch-all pattern /^https?.*/ with NetworkFirst could cache external resources indiscriminately
  2. Lines 81-82: 24-hour cache expiration for network requests may be too long for dynamic content
  3. Lines 88-97: Image caching strategy looks appropriate

Consider these improvements:

 const pwaConfig = withPWA({
   dest: "public",
   register: true,
   skipWaiting: true,
   disable: process.env.NODE_ENV === "development",
   buildExcludes: [/middleware-manifest\.json$/],
   sw: "sw-custom.js", // Use our custom service worker
   runtimeCaching: [
     {
-      urlPattern: /^https?.*/,
+      urlPattern: ({ url }) => url.origin === self.location.origin,
       handler: "NetworkFirst",
       options: {
         cacheName: "offlineCache",
         expiration: {
           maxEntries: 200,
-          maxAgeSeconds: 24 * 60 * 60, // 24 hours
+          maxAgeSeconds: 60 * 60, // 1 hour
         },
         networkTimeoutSeconds: 10,
       },
     },

This limits caching to same-origin requests and reduces cache duration for dynamic content.

package.json (1)

59-59: Migrate to @serwist/next for maintained PWA support
Replace next-pwa (v5.6.0) with @serwist/next (v9.2.1), as recommended by both the original maintainer and community fork; alternatively, use Next.js’s native PWA approach per the official docs.

src/components/shared/PWAInstallPrompt.tsx (4)

53-70: Record suppression on OS prompt dismissal to honor the 7‑day cooldown

If the user dismisses the browser’s install prompt, we should respect the same cooldown used by the “Not now” button.

Apply this diff:

   // Wait for the user to respond to the prompt
   const { outcome } = await deferredPrompt.userChoice;

   if (outcome === "accepted") {
     console.log("User accepted the install prompt");
   } else {
     console.log("User dismissed the install prompt");
+    // Suppress further prompts for 7 days
+    localStorage.setItem("pwa-install-dismissed", Date.now().toString());
   }

   // Clear the deferredPrompt so it can only be used once
   setDeferredPrompt(null);

144-167: Improve accessibility: use dialog semantics and labels

Treat these as lightweight dialogs so screen readers announce them and users can identify the title.

Apply these minimal changes within each prompt block:

  • Add role and labeling on the container.
  • Add an id to the heading and reference it.

Example (apply similarly to all three prompts):

-<div className="fixed bottom-4 left-4 right-4 z-50 mx-auto max-w-sm">
+<div
+  className="fixed bottom-4 left-4 right-4 z-50 mx-auto max-w-sm"
+  role="dialog"
+  aria-modal="false"
+  aria-labelledby="pwa-install-title"
+>
   <div className="rounded-lg border bg-background p-4 shadow-lg">
     <div className="flex items-start justify-between">
       <div className="flex-1">
-        <h3 className="text-sm font-semibold">Install GrantFox</h3>
+        <h3 id="pwa-install-title" className="text-sm font-semibold">Install GrantFox</h3>

Optional: add onKeyDown handlers to close on Escape.

Also applies to: 186-227, 82-123


131-141: Avoid reading localStorage during render; derive a memoized/single source of truth

Accessing localStorage on every render is unnecessary and brittle. Load once and derive booleans.

Consider:

const [dismissedAt, setDismissedAt] = useState<number | null>(null);
useEffect(() => {
  const t = localStorage.getItem("pwa-install-dismissed");
  setDismissedAt(t ? Number.parseInt(t) : null);
}, []);
const installSuppressed =
  dismissedAt !== null && (Date.now() - dismissedAt) / (1000 * 60 * 60 * 24) < 7;

Then replace both render-time reads with installSuppressed.

Also applies to: 175-183


35-39: iOS detection can be brittle on newer iPads (desktop UA)

UA sniffing often misclassifies iPadOS. Prefer feature checks when possible.

Example:

const isIOSDevice =
  typeof navigator !== "undefined" &&
  (/iPhone|iPad|iPod/.test(navigator.userAgent) ||
    // iPadOS 13+ reports as Mac; check touch support
    (navigator.platform === "MacIntel" && (navigator as any).maxTouchPoints > 1));
setIsIOS(!!isIOSDevice);
public/sw-custom.js (6)

71-89: Broaden success check to response.ok and guard cache puts

Status 200 only misses 2xx range and can throw if response is undefined. Wrap cache.put with response.ok.

-        .then((response) => {
-          // Cache successful responses
-          if (response.status === 200) {
+        .then((response) => {
+          // Cache successful responses
+          if (response && response.ok) {
             const responseClone = response.clone();
             caches.open(CACHE_NAME).then((cache) => {
               cache.put(event.request, responseClone);
             });
           }
           return response;
         })

Apply the same response && response.ok pattern in Lines 104-110 and 122-127.


92-115: Static asset detection misses common formats; prefer destination checks

Rely on request.destination (style, script, font, image) and extend formats like webp/avif/ico/json.

-  if (
-    event.request.destination === "image" ||
-    event.request.url.match(/\.(css|js|png|jpg|jpeg|gif|svg|woff|woff2)$/)
-  ) {
+  if (
+    ["style", "script", "font", "image"].includes(event.request.destination) ||
+    event.request.url.match(/\.(css|js|png|jpg|jpeg|gif|svg|webp|avif|ico|woff|woff2|json|map)$/)
+  ) {

60-68: Use navigation preload to improve first-load latency

If supported, use event.preloadResponse before falling back to network or offline.

-  if (event.request.mode === "navigate") {
-    event.respondWith(
-      fetch(event.request).catch(() => {
-        return caches.match(OFFLINE_URL);
-      }),
-    );
+  if (event.request.mode === "navigate") {
+    event.respondWith((async () => {
+      try {
+        const preload = 'preloadResponse' in event ? await event.preloadResponse : null;
+        if (preload) return preload;
+        return await fetch(event.request);
+      } catch {
+        return caches.match(OFFLINE_URL);
+      }
+    })());
     return;
   }

And during activate, enable preload:

 self.addEventListener("activate", (event) => {
   console.log("Service Worker: Activate event");

   event.waitUntil(
-    caches.keys().then((cacheNames) => {
+    (async () => {
+      if ('navigationPreload' in self.registration) {
+        try { await self.registration.navigationPreload.enable(); } catch {}
+      }
+      const cacheNames = await caches.keys();
       return Promise.all(
-        cacheNames.map((cacheName) => {
+        cacheNames.map((cacheName) => {
           if (cacheName !== CACHE_NAME) {
             console.log("Service Worker: Deleting old cache:", cacheName);
             return caches.delete(cacheName);
           }
-        }),
-      );
-    }),
+          return Promise.resolve();
+        })
+      );
+    })(),
   );

11-21: Install should not fail wholesale if a single resource can’t be cached

cache.addAll rejects the entire install if one URL fails (e.g., offline or a missing icon). Cache defensively.

-  event.waitUntil(
-    caches.open(CACHE_NAME).then((cache) => {
-      console.log("Service Worker: Caching essential files");
-      return cache.addAll([
-        "/",
-        "/offline.html",
-        "/icons/icon-192x192.png",
-        "/icons/icon-512x512.png",
-      ]);
-    }),
-  );
+  event.waitUntil((async () => {
+    const cache = await caches.open(CACHE_NAME);
+    console.log("Service Worker: Caching essential files");
+    const assets = ["/", "/offline.html", "/icons/icon-192x192.png", "/icons/icon-512x512.png"];
+    await Promise.allSettled(assets.map((url) => cache.add(url)));
+  })());

141-171: Harden push handler against malformed data

event.data.json() can throw. Add a safe parse and defaults.

-  if (event.data) {
-    const data = event.data.json();
+  if (event.data) {
+    let data = {};
+    try {
+      data = event.data.json();
+    } catch {
+      try { data = JSON.parse(event.data.text()); } catch { data = {}; }
+    }
     const options = {
       body: data.body || "New notification from GrantFox",
       icon: data.icon || "/icons/icon-192x192.png",

173-197: Focus existing client more reliably and include uncontrolled windows

Exact URL equality often fails due to differing origins, paths, or search. Include uncontrolled clients and compare by origin+path.

-    event.waitUntil(
-      clients.matchAll({ type: "window" }).then((clientList) => {
+    event.waitUntil(
+      clients.matchAll({ type: "window", includeUncontrolled: true }).then((clientList) => {
         // Check if there's already a window/tab open with the target URL
-        for (const client of clientList) {
-          if (client.url === urlToOpen && "focus" in client) {
-            return client.focus();
-          }
-        }
+        const target = new URL(urlToOpen, self.location.origin);
+        for (const client of clientList) {
+          const u = new URL(client.url);
+          if (u.origin === target.origin && u.pathname === target.pathname && "focus" in client) {
+            return client.focus();
+          }
+        }
         // If no existing window, open a new one
         if (clients.openWindow) {
           return clients.openWindow(urlToOpen);
         }
       }),
     );
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 853cf00 and 0c166a8.

⛔ Files ignored due to path filters (10)
  • package-lock.json is excluded by !**/package-lock.json
  • public/favicon.ico is excluded by !**/*.ico
  • public/icons/icon-128x128.png is excluded by !**/*.png
  • public/icons/icon-144x144.png is excluded by !**/*.png
  • public/icons/icon-152x152.png is excluded by !**/*.png
  • public/icons/icon-192x192.png is excluded by !**/*.png
  • public/icons/icon-384x384.png is excluded by !**/*.png
  • public/icons/icon-512x512.png is excluded by !**/*.png
  • public/icons/icon-72x72.png is excluded by !**/*.png
  • public/icons/icon-96x96.png is excluded by !**/*.png
📒 Files selected for processing (11)
  • next.config.ts (2 hunks)
  • package.json (2 hunks)
  • public/icons/browserconfig.xml (1 hunks)
  • public/offline.html (1 hunks)
  • public/sw-custom.js (1 hunks)
  • src/app/layout.tsx (3 hunks)
  • src/app/manifest.ts (1 hunks)
  • src/components/modules/payouts/hooks/usePayoutMutations.ts (1 hunks)
  • src/components/shared/PWAInstallPrompt.tsx (1 hunks)
  • src/components/shared/PWAStatus.tsx (1 hunks)
  • src/hooks/usePWA.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/frontend-rule.mdc)

**/*.{js,jsx,ts,tsx}: Use early returns whenever possible to make the code more readable.
Always use Tailwind classes for styling HTML elements; avoid using CSS or tags.
Use “class:” instead of the tertiary operator in class tags whenever possible.
Use descriptive variable and function/const names. Also, event functions should be named with a “handle” prefix, like “handleClick” for onClick and “handleKeyDown” for onKeyDown.
Implement accessibility features on elements. For example, a tag should have a tabindex=“0”, aria-label, on:click, and on:keydown, and similar attributes.
Everything must be 100% responsive (using all the sizes of tailwind) and compatible with dark/light shadcn's mode.
Use consts instead of functions, for example, “const toggle = () =>”. Also, define a type if possible.
Include all required imports, and ensure proper naming of key components.

**/*.{js,jsx,ts,tsx}: enum/const object members should be written UPPERCASE_WITH_UNDERSCORE.
Gate flag-dependent code on a check that verifies the flag's values are valid and expected.
If a custom property for a person or event is at any point referenced in two or more files or two or more callsites in the same file, use an enum or const object, as above in feature flags.

Files:

  • src/components/modules/payouts/hooks/usePayoutMutations.ts
  • src/app/manifest.ts
  • src/components/shared/PWAStatus.tsx
  • src/hooks/usePWA.ts
  • public/sw-custom.js
  • src/components/shared/PWAInstallPrompt.tsx
  • src/app/layout.tsx
  • next.config.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/frontend-rule.mdc)

You can never use anys.

If using TypeScript, use an enum to store flag names.

Files:

  • src/components/modules/payouts/hooks/usePayoutMutations.ts
  • src/app/manifest.ts
  • src/components/shared/PWAStatus.tsx
  • src/hooks/usePWA.ts
  • src/components/shared/PWAInstallPrompt.tsx
  • src/app/layout.tsx
  • next.config.ts
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/posthog-integration.mdc)

Never hallucinate an API key. Instead, always use the API key populated in the .env file.

Files:

  • src/components/modules/payouts/hooks/usePayoutMutations.ts
  • src/app/manifest.ts
  • src/components/shared/PWAStatus.tsx
  • src/hooks/usePWA.ts
  • public/sw-custom.js
  • src/components/shared/PWAInstallPrompt.tsx
  • src/app/layout.tsx
  • next.config.ts
**/*.{js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/posthog-integration.mdc)

If using JavaScript, store flag names as strings to an object declared as a constant, to simulate an enum. Use a consistent naming convention for this storage.

Files:

  • public/sw-custom.js
🧬 Code graph analysis (5)
src/components/modules/payouts/hooks/usePayoutMutations.ts (2)
src/components/modules/payouts/ui/components/PayoutFormModal.tsx (1)
  • data (122-151)
src/components/modules/payouts/ui/components/PayoutCard.tsx (1)
  • PayoutCard (37-356)
src/components/shared/PWAStatus.tsx (2)
src/hooks/usePWA.ts (1)
  • usePWA (13-117)
src/components/ui/badge.tsx (3)
  • Badge (40-40)
  • BadgeProps (30-32)
  • Badge (34-38)
src/hooks/usePWA.ts (1)
public/sw-custom.js (1)
  • options (143-165)
src/components/shared/PWAInstallPrompt.tsx (1)
src/hooks/usePWA.ts (1)
  • usePWA (13-117)
src/app/layout.tsx (2)
src/components/providers/global.provider.tsx (1)
  • GlobalProvider (8-18)
src/components/shared/PWAInstallPrompt.tsx (1)
  • PWAInstallPrompt (17-228)
🔇 Additional comments (17)
package.json (1)

85-85: LGTM!

The types package matches the next-pwa version correctly.

src/app/manifest.ts (2)

1-11: LGTM!

The manifest function properly imports Next.js types and defines core PWA metadata with appropriate defaults.


79-92: LGTM!

The shortcuts configuration for Dashboard is correctly structured and provides a good user experience enhancement.

next.config.ts (2)

2-2: LGTM!

The withPWA import aligns with the next-pwa integration pattern.


27-64: LGTM!

The security headers configuration follows best practices for PWA security, including proper CSP for the service worker and essential security headers for all routes.

src/app/layout.tsx (4)

5-5: LGTM!

The PWAInstallPrompt import is correctly added for the new PWA functionality.


26-56: LGTM!

The extended metadata configuration properly covers PWA requirements including theme colors, viewport settings, Apple Web App configuration, and comprehensive icon definitions.


66-72: LGTM!

The additional PWA meta tags complement the Next.js metadata API and provide proper Windows/iOS integration.


78-78: LGTM!

The PWAInstallPrompt component is properly placed alongside other global components for PWA install functionality.

public/offline.html (1)

1-54: LGTM!

The offline fallback page provides a clean, self-contained experience with appropriate styling and functionality. The inline CSS and simple retry mechanism work well for an offline scenario.

src/components/shared/PWAStatus.tsx (2)

1-12: LGTM!

The component properly uses the "use client" directive and has correct imports for the PWA status functionality.


14-47: LGTM!

The component provides clear visual indicators for PWA status with appropriate conditional rendering and accessibility-friendly icons. The layout uses proper Tailwind classes and follows the component patterns.

src/hooks/usePWA.ts (5)

1-11: LGTM!

The "use client" directive and PWAState interface are properly defined with appropriate TypeScript types.


22-84: LGTM!

The useEffect properly handles service worker registration, event listeners, and cleanup. The implementation includes appropriate error handling and follows React best practices.


86-91: LGTM!

The updateServiceWorker function correctly handles the skip waiting pattern and page reload for PWA updates.


93-109: LGTM!

The notification permission and sending functions are well-implemented with proper checks and sensible defaults. The icon paths align with the manifest configuration.


111-117: LGTM!

The hook return statement properly spreads the state and exposes all necessary functions for PWA management.

@wayzeek wayzeek changed the title Fix 115 feat add progressive web app pwa implementation with next js feat: add progressive web app implementation Sep 25, 2025
@JoelVR17 JoelVR17 self-assigned this Nov 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT]: Add Progressive Web App (PWA) Implementation with Next.js

2 participants