feat: pwa manifest, icon and minimal service worker
This commit is contained in:
@@ -4,6 +4,9 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||
<meta name="theme-color" content="#0a0a0a" />
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<link rel="icon" href="/icon.svg" type="image/svg+xml" />
|
||||
<link rel="apple-touch-icon" href="/icon.svg" />
|
||||
<title>record-shop</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
7
web/public/icon.svg
Normal file
7
web/public/icon.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<rect width="512" height="512" rx="96" fill="#0a0a0a"/>
|
||||
<circle cx="256" cy="256" r="160" fill="#171717" stroke="#34d399" stroke-width="16"/>
|
||||
<circle cx="256" cy="256" r="96" fill="none" stroke="#262626" stroke-width="8"/>
|
||||
<circle cx="256" cy="256" r="40" fill="#34d399"/>
|
||||
<circle cx="256" cy="256" r="12" fill="#0a0a0a"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 403 B |
13
web/public/manifest.webmanifest
Normal file
13
web/public/manifest.webmanifest
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "record-shop",
|
||||
"short_name": "record-shop",
|
||||
"description": "Track your physical music collection",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#0a0a0a",
|
||||
"theme_color": "#0a0a0a",
|
||||
"icons": [
|
||||
{ "src": "/icon.svg", "sizes": "any", "type": "image/svg+xml", "purpose": "any" },
|
||||
{ "src": "/icon.svg", "sizes": "any", "type": "image/svg+xml", "purpose": "maskable" }
|
||||
]
|
||||
}
|
||||
11
web/public/sw.js
Normal file
11
web/public/sw.js
Normal file
@@ -0,0 +1,11 @@
|
||||
self.addEventListener('install', () => {
|
||||
self.skipWaiting()
|
||||
})
|
||||
|
||||
self.addEventListener('activate', (event) => {
|
||||
event.waitUntil(self.clients.claim())
|
||||
})
|
||||
|
||||
self.addEventListener('fetch', () => {
|
||||
// no-op: presence of a fetch handler enables PWA install
|
||||
})
|
||||
@@ -8,3 +8,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
)
|
||||
|
||||
if ('serviceWorker' in navigator && import.meta.env.PROD) {
|
||||
void navigator.serviceWorker.register('/sw.js')
|
||||
}
|
||||
|
||||
26
web/test/pwa.test.ts
Normal file
26
web/test/pwa.test.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
const pub = path.resolve(__dirname, '../public')
|
||||
|
||||
describe('PWA assets', () => {
|
||||
it('manifest has required fields and the icon exists', () => {
|
||||
const manifest = JSON.parse(readFileSync(path.join(pub, 'manifest.webmanifest'), 'utf8')) as {
|
||||
name: string
|
||||
display: string
|
||||
start_url: string
|
||||
icons: { src: string; sizes: string; type: string }[]
|
||||
}
|
||||
expect(manifest.name).toContain('record-shop')
|
||||
expect(manifest.display).toBe('standalone')
|
||||
expect(manifest.start_url).toBe('/')
|
||||
expect(manifest.icons.some((i) => i.src === '/icon.svg')).toBe(true)
|
||||
expect(() => readFileSync(path.join(pub, 'icon.svg'))).not.toThrow()
|
||||
})
|
||||
|
||||
it('service worker registers a fetch handler', () => {
|
||||
const sw = readFileSync(path.join(pub, 'sw.js'), 'utf8')
|
||||
expect(sw).toContain("addEventListener('fetch'")
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user