Make cashup installable as a PWA

vite-plugin-pwa with manifest scoped to /cashup/, purple £ icons matching
portal style, auto install prompt when opened with ?install=1 from the
portal dashboard, nginx no-cache rules for sw.js and manifest.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-11 10:54:50 +00:00
parent dec7ac2f3e
commit 4a46bcf45f
7 changed files with 6327 additions and 2 deletions

View file

@ -8,6 +8,23 @@ server {
try_files $uri =404; try_files $uri =404;
} }
location = /cashup/manifest.webmanifest {
default_type application/manifest+json;
add_header Cache-Control "no-cache";
try_files $uri =404;
}
# Service worker scripts must revalidate so new deploys reach installed PWAs
location = /cashup/sw.js {
add_header Cache-Control "no-cache";
try_files $uri =404;
}
location = /cashup/registerSW.js {
add_header Cache-Control "no-cache";
try_files $uri =404;
}
location /cashup/api/ { location /cashup/api/ {
proxy_pass http://backend:3001/api/; proxy_pass http://backend:3001/api/;
proxy_set_header Host $host; proxy_set_header Host $host;

6272
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -19,6 +19,7 @@
"@types/react-dom": "^18.3.1", "@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
"typescript": "^5.7.2", "typescript": "^5.7.2",
"vite": "^6.0.5" "vite": "^6.0.5",
"vite-plugin-pwa": "^1.3.0"
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

@ -3,6 +3,15 @@ import { createRoot } from 'react-dom/client'
import App from './App' import App from './App'
import './index.css' import './index.css'
// When opened from the portal's install button (?install=1), trigger the
// PWA install prompt as soon as the browser offers it (Chrome/Edge only).
if (new URLSearchParams(window.location.search).has('install')) {
window.addEventListener('beforeinstallprompt', e => {
e.preventDefault()
;(e as Event & { prompt: () => Promise<void> }).prompt()
}, { once: true })
}
createRoot(document.getElementById('root')!).render( createRoot(document.getElementById('root')!).render(
<StrictMode> <StrictMode>
<App /> <App />

View file

@ -1,7 +1,33 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react'
import { VitePWA } from 'vite-plugin-pwa'
export default defineConfig({ export default defineConfig({
base: '/cashup/', base: '/cashup/',
plugins: [react()], plugins: [
react(),
VitePWA({
registerType: 'autoUpdate',
manifest: {
name: 'Cash Up',
short_name: 'Cash Up',
start_url: '/cashup/',
scope: '/cashup/',
display: 'standalone',
theme_color: '#6b2d8b',
background_color: '#6b2d8b',
icons: [
{ src: '/cashup/icons/icon-192.png', sizes: '192x192', type: 'image/png' },
{ src: '/cashup/icons/icon-512.png', sizes: '512x512', type: 'image/png' },
],
},
workbox: {
navigateFallback: '/cashup/index.html',
// Never let the service worker intercept API calls — auth and data
// must always hit the backend fresh.
navigateFallbackDenylist: [/\/api\//],
globPatterns: ['**/*.{js,css,html,ico,png,svg}'],
},
}),
],
}) })