feat(cannaiq): Add PWA support with vite-plugin-pwa

- Add vite-plugin-pwa for service worker and manifest generation
- Configure workbox for asset caching and API runtime caching
- Add sharp for icon generation from SVG
- Create generate-icons.js script to create 192x192 and 512x512 PNGs
- Update build script to auto-generate icons before build

App is now installable as a PWA with offline support.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Kelly
2025-12-13 19:49:15 -07:00
parent a020e31a46
commit ad09aadcc9
3 changed files with 96 additions and 3 deletions

View File

@@ -5,7 +5,8 @@
"scripts": {
"dev": "vite --host",
"dev:admin": "vite --host --port 8080",
"build": "tsc && vite build",
"generate-icons": "node scripts/generate-icons.js",
"build": "node scripts/generate-icons.js && tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
@@ -26,6 +27,8 @@
"postcss": "^8.4.32",
"tailwindcss": "^3.4.1",
"typescript": "^5.3.3",
"vite": "^5.0.8"
"vite": "^5.0.8",
"vite-plugin-pwa": "^0.21.1",
"sharp": "^0.33.5"
}
}

View File

@@ -0,0 +1,37 @@
/**
* Generate PWA icons from favicon.svg
*
* Run: node scripts/generate-icons.js
*/
import sharp from 'sharp';
import { readFileSync, writeFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const publicDir = join(__dirname, '..', 'public');
const svgPath = join(publicDir, 'favicon.svg');
const svg = readFileSync(svgPath);
const sizes = [192, 512];
async function generateIcons() {
console.log('Generating PWA icons from favicon.svg...');
for (const size of sizes) {
const outputPath = join(publicDir, `icon-${size}.png`);
await sharp(svg)
.resize(size, size)
.png()
.toFile(outputPath);
console.log(` Created icon-${size}.png`);
}
console.log('Done!');
}
generateIcons().catch(console.error);

View File

@@ -1,8 +1,61 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { VitePWA } from 'vite-plugin-pwa';
export default defineConfig({
plugins: [react()],
plugins: [
react(),
VitePWA({
registerType: 'autoUpdate',
includeAssets: ['favicon.svg'],
manifest: {
name: 'CannaIQ',
short_name: 'CannaIQ',
description: 'Cannabis dispensary analytics and management',
theme_color: '#059669',
background_color: '#1f2937',
display: 'standalone',
scope: '/',
start_url: '/',
icons: [
{
src: '/icon-192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: '/icon-512.png',
sizes: '512x512',
type: 'image/png',
},
{
src: '/icon-512.png',
sizes: '512x512',
type: 'image/png',
purpose: 'maskable',
},
],
},
workbox: {
// Cache all assets
globPatterns: ['**/*.{js,css,html,ico,png,svg,woff,woff2}'],
// Runtime caching for API calls
runtimeCaching: [
{
urlPattern: /^https:\/\/cannaiq\.co\/api\/.*/i,
handler: 'NetworkFirst',
options: {
cacheName: 'api-cache',
expiration: {
maxEntries: 100,
maxAgeSeconds: 60 * 5, // 5 minutes
},
},
},
],
},
}),
],
server: {
host: true,
port: 8080,