From 6ed4601f1177275638ae8258beaba2ea724cb183 Mon Sep 17 00:00:00 2001 From: jtricerolph Date: Wed, 29 Jul 2026 17:28:47 +0000 Subject: [PATCH] Add PWA manifest support and icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit App had no manifest, no service worker, and no icons — not installable as a PWA at all. --- frontend/package.json | 3 ++- frontend/public/icons/icon-192.png | Bin 0 -> 1888 bytes frontend/public/icons/icon-512.png | Bin 0 -> 5276 bytes frontend/vite.config.ts | 26 +++++++++++++++++++++++++- 4 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 frontend/public/icons/icon-192.png create mode 100644 frontend/public/icons/icon-512.png diff --git a/frontend/package.json b/frontend/package.json index 1d3b28f..07353b7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,6 +19,7 @@ "@types/react-dom": "^18.3.1", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "vite": "^6.0.5" + "vite": "^6.0.5", + "vite-plugin-pwa": "^1.3.0" } } diff --git a/frontend/public/icons/icon-192.png b/frontend/public/icons/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..baa250402af75324042f5dca10319701392cc0e6 GIT binary patch literal 1888 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 F{{&sk^*R6m literal 0 HcmV?d00001 diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index d2cf4cc..530ecd5 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,7 +1,31 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' +import { VitePWA } from 'vite-plugin-pwa' export default defineConfig({ base: '/twin-optimiser/', - plugins: [react()], + plugins: [ + react(), + VitePWA({ + registerType: 'autoUpdate', + manifest: { + name: 'Twin Optimiser', + short_name: 'Twin Optimiser', + start_url: '/twin-optimiser/', + scope: '/twin-optimiser/', + display: 'standalone', + theme_color: '#c9841a', + background_color: '#c9841a', + icons: [ + { src: '/twin-optimiser/icons/icon-192.png', sizes: '192x192', type: 'image/png' }, + { src: '/twin-optimiser/icons/icon-512.png', sizes: '512x512', type: 'image/png' }, + ], + }, + workbox: { + navigateFallback: '/twin-optimiser/index.html', + navigateFallbackDenylist: [/\/api\//], + globPatterns: ['**/*.{js,css,html,ico,png,svg}'], + }, + }), + ], })