This implements the mobile optimization agent (P3 - Enhancement) with: PWA Configuration: - Add next-pwa integration with offline caching strategies - Create web app manifest for installability - Add service worker with background sync support - Create offline fallback page Mobile Components: - BottomNav: Touch-friendly bottom navigation bar - MobileHeader: Responsive header with back navigation - InstallPrompt: Smart PWA install prompt (iOS & Android) - SwipeableCard: Gesture-based swipeable cards - PullToRefresh: Native-like pull to refresh - QRScanner: Camera-based QR code scanning Mobile Library: - camera.ts: Camera access and photo capture utilities - offline.ts: IndexedDB-based offline storage and sync - gestures.ts: Touch gesture detection (swipe, pinch, tap) - pwa.ts: PWA status, install prompts, service worker management Mobile Pages: - /m: Mobile dashboard with quick actions and stats - /m/scan: QR code scanner for plant lookup - /m/quick-add: Streamlined plant registration form - /m/profile: User profile with offline status Dependencies added: next-pwa, idb
174 lines
3.9 KiB
JavaScript
174 lines
3.9 KiB
JavaScript
const withPWA = require('next-pwa')({
|
|
dest: 'public',
|
|
register: true,
|
|
skipWaiting: true,
|
|
disable: process.env.NODE_ENV === 'development',
|
|
runtimeCaching: [
|
|
{
|
|
urlPattern: /^https:\/\/fonts\.(?:gstatic)\.com\/.*/i,
|
|
handler: 'CacheFirst',
|
|
options: {
|
|
cacheName: 'google-fonts-webfonts',
|
|
expiration: {
|
|
maxEntries: 4,
|
|
maxAgeSeconds: 365 * 24 * 60 * 60, // 1 year
|
|
},
|
|
},
|
|
},
|
|
{
|
|
urlPattern: /^https:\/\/fonts\.(?:googleapis)\.com\/.*/i,
|
|
handler: 'StaleWhileRevalidate',
|
|
options: {
|
|
cacheName: 'google-fonts-stylesheets',
|
|
expiration: {
|
|
maxEntries: 4,
|
|
maxAgeSeconds: 7 * 24 * 60 * 60, // 1 week
|
|
},
|
|
},
|
|
},
|
|
{
|
|
urlPattern: /\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,
|
|
handler: 'StaleWhileRevalidate',
|
|
options: {
|
|
cacheName: 'static-font-assets',
|
|
expiration: {
|
|
maxEntries: 4,
|
|
maxAgeSeconds: 7 * 24 * 60 * 60, // 1 week
|
|
},
|
|
},
|
|
},
|
|
{
|
|
urlPattern: /\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,
|
|
handler: 'StaleWhileRevalidate',
|
|
options: {
|
|
cacheName: 'static-image-assets',
|
|
expiration: {
|
|
maxEntries: 64,
|
|
maxAgeSeconds: 24 * 60 * 60, // 1 day
|
|
},
|
|
},
|
|
},
|
|
{
|
|
urlPattern: /\/_next\/image\?url=.+$/i,
|
|
handler: 'StaleWhileRevalidate',
|
|
options: {
|
|
cacheName: 'next-image',
|
|
expiration: {
|
|
maxEntries: 64,
|
|
maxAgeSeconds: 24 * 60 * 60, // 1 day
|
|
},
|
|
},
|
|
},
|
|
{
|
|
urlPattern: /\.(?:mp3|wav|ogg)$/i,
|
|
handler: 'CacheFirst',
|
|
options: {
|
|
rangeRequests: true,
|
|
cacheName: 'static-audio-assets',
|
|
expiration: {
|
|
maxEntries: 32,
|
|
maxAgeSeconds: 24 * 60 * 60, // 1 day
|
|
},
|
|
},
|
|
},
|
|
{
|
|
urlPattern: /\.(?:mp4)$/i,
|
|
handler: 'CacheFirst',
|
|
options: {
|
|
rangeRequests: true,
|
|
cacheName: 'static-video-assets',
|
|
expiration: {
|
|
maxEntries: 32,
|
|
maxAgeSeconds: 24 * 60 * 60, // 1 day
|
|
},
|
|
},
|
|
},
|
|
{
|
|
urlPattern: /\.(?:js)$/i,
|
|
handler: 'StaleWhileRevalidate',
|
|
options: {
|
|
cacheName: 'static-js-assets',
|
|
expiration: {
|
|
maxEntries: 32,
|
|
maxAgeSeconds: 24 * 60 * 60, // 1 day
|
|
},
|
|
},
|
|
},
|
|
{
|
|
urlPattern: /\.(?:css|less)$/i,
|
|
handler: 'StaleWhileRevalidate',
|
|
options: {
|
|
cacheName: 'static-style-assets',
|
|
expiration: {
|
|
maxEntries: 32,
|
|
maxAgeSeconds: 24 * 60 * 60, // 1 day
|
|
},
|
|
},
|
|
},
|
|
{
|
|
urlPattern: /\/_next\/data\/.+\/.+\.json$/i,
|
|
handler: 'StaleWhileRevalidate',
|
|
options: {
|
|
cacheName: 'next-data',
|
|
expiration: {
|
|
maxEntries: 32,
|
|
maxAgeSeconds: 24 * 60 * 60, // 1 day
|
|
},
|
|
},
|
|
},
|
|
{
|
|
urlPattern: /\/api\/.*$/i,
|
|
handler: 'NetworkFirst',
|
|
method: 'GET',
|
|
options: {
|
|
cacheName: 'apis',
|
|
expiration: {
|
|
maxEntries: 16,
|
|
maxAgeSeconds: 24 * 60 * 60, // 1 day
|
|
},
|
|
networkTimeoutSeconds: 10,
|
|
},
|
|
},
|
|
{
|
|
urlPattern: /.*/i,
|
|
handler: 'NetworkFirst',
|
|
options: {
|
|
cacheName: 'others',
|
|
expiration: {
|
|
maxEntries: 32,
|
|
maxAgeSeconds: 24 * 60 * 60, // 1 day
|
|
},
|
|
networkTimeoutSeconds: 10,
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
module.exports = withPWA({
|
|
swcMinify: true,
|
|
i18n: {
|
|
locales: ["en", "es"],
|
|
defaultLocale: "en",
|
|
},
|
|
images: {
|
|
domains: [process.env.NEXT_IMAGE_DOMAIN],
|
|
},
|
|
async rewrites() {
|
|
return [
|
|
{
|
|
source: "/blog",
|
|
destination: "/blog/page/0",
|
|
},
|
|
{
|
|
source: "/es",
|
|
destination: "/es/home",
|
|
locale: false,
|
|
},
|
|
{
|
|
source: "/en/principal",
|
|
destination: "/",
|
|
locale: false,
|
|
},
|
|
]
|
|
},
|
|
});
|