diff --git a/cannaiq/package.json b/cannaiq/package.json index 4d434907..99920529 100755 --- a/cannaiq/package.json +++ b/cannaiq/package.json @@ -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" } } diff --git a/cannaiq/scripts/generate-icons.js b/cannaiq/scripts/generate-icons.js new file mode 100644 index 00000000..cfafb5b1 --- /dev/null +++ b/cannaiq/scripts/generate-icons.js @@ -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); diff --git a/cannaiq/vite.config.ts b/cannaiq/vite.config.ts index 474295a0..5a33e98c 100755 --- a/cannaiq/vite.config.ts +++ b/cannaiq/vite.config.ts @@ -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,