Browse Source

Add branded offline page for PWA and hide install banner in dev (#37)

* improved info screen when offline

* images
Bernt Christian Egeland 8 months ago
parent
commit
7b128f74e9
4 changed files with 93 additions and 2 deletions
  1. 61 0
      public/offline.html
  2. 25 2
      public/sw.js
  3. 6 0
      src/app/manifest.ts
  4. 1 0
      src/components/pwa-install-prompt.tsx

+ 61 - 0
public/offline.html

@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <title>TorqVoice - Offline</title>
+  <style>
+    * { margin: 0; padding: 0; box-sizing: border-box; }
+    body {
+      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+      background: #09090b;
+      color: #fafafa;
+      min-height: 100dvh;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      padding: 1.5rem;
+    }
+    .container {
+      text-align: center;
+      max-width: 24rem;
+    }
+    .icon {
+      width: 72px;
+      height: 72px;
+      margin: 0 auto 1.5rem;
+      border-radius: 1rem;
+    }
+    h1 {
+      font-size: 1.25rem;
+      font-weight: 600;
+      margin-bottom: 0.5rem;
+    }
+    p {
+      color: #a1a1aa;
+      font-size: 0.875rem;
+      line-height: 1.5;
+      margin-bottom: 1.5rem;
+    }
+    button {
+      background: #fafafa;
+      color: #09090b;
+      border: none;
+      padding: 0.625rem 1.25rem;
+      border-radius: 0.5rem;
+      font-size: 0.875rem;
+      font-weight: 500;
+      cursor: pointer;
+    }
+    button:active { opacity: 0.8; }
+  </style>
+</head>
+<body>
+  <div class="container">
+    <img src="/icons/icon-192.png" alt="TorqVoice" class="icon" />
+    <h1>You're offline</h1>
+    <p>TorqVoice needs an internet connection to work. Please check your connection and try again.</p>
+    <button onclick="location.reload()">Try again</button>
+  </div>
+</body>
+</html>

+ 25 - 2
public/sw.js

@@ -1,2 +1,25 @@
-self.addEventListener('install', () => self.skipWaiting())
-self.addEventListener('activate', (e) => e.waitUntil(self.clients.claim()))
+const CACHE_NAME = 'torqvoice-offline-v1'
+const OFFLINE_URL = '/offline.html'
+
+self.addEventListener('install', (e) => {
+  e.waitUntil(
+    caches.open(CACHE_NAME).then((cache) => cache.addAll([OFFLINE_URL, '/icons/icon-192.png']))
+  )
+  self.skipWaiting()
+})
+
+self.addEventListener('activate', (e) => {
+  e.waitUntil(
+    caches.keys().then((keys) =>
+      Promise.all(keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k)))
+    )
+  )
+  self.clients.claim()
+})
+
+self.addEventListener('fetch', (e) => {
+  if (e.request.mode !== 'navigate') return
+  e.respondWith(
+    fetch(e.request).catch(() => caches.match(OFFLINE_URL))
+  )
+})

+ 6 - 0
src/app/manifest.ts

@@ -2,15 +2,21 @@ import type { MetadataRoute } from 'next'
 
 export default function manifest(): MetadataRoute.Manifest {
   return {
+    id: '/',
     name: 'TorqVoice - Workshop Management',
     short_name: 'TorqVoice',
+    description:
+      'Workshop management platform for automotive service businesses. Manage work orders, invoices, customers, inventory, and vehicle service history.',
     start_url: '/',
+    scope: '/',
     display: 'standalone',
+    orientation: 'any',
     background_color: '#09090b',
     theme_color: '#09090b',
     icons: [
       { src: '/icons/icon-192.png', sizes: '192x192', type: 'image/png' },
       { src: '/icons/icon-512.png', sizes: '512x512', type: 'image/png' },
+      { src: '/icons/icon-512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' },
     ],
   }
 }

+ 1 - 0
src/components/pwa-install-prompt.tsx

@@ -56,6 +56,7 @@ function useInstallPrompt() {
 
   useEffect(() => {
     if (isStandalone() || isDismissed()) return
+    if (process.env.NODE_ENV === 'development') return
 
     if (isIOSSafari()) {
       setIsIOS(true)