From a01b2490dc1197e88afaa51ef52e2d01b71a75d2 Mon Sep 17 00:00:00 2001 From: Andrei Date: Sun, 28 Sep 2025 22:20:44 +0000 Subject: [PATCH] Implement comprehensive PWA with offline Bible reading capabilities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Web App Manifest with app metadata, icons, and installation support - Create Service Worker with intelligent caching strategies for Bible content, static assets, and dynamic content - Implement IndexedDB-based offline storage system for Bible versions, books, chapters, and verses - Add offline download manager component for browsing and downloading Bible versions - Create offline Bible reader component for seamless offline reading experience - Integrate PWA install prompt with platform-specific instructions - Add offline reading interface to existing Bible reader with download buttons - Create dedicated offline page with tabbed interface for reading and downloading - Add PWA and offline-related translations for English and Romanian locales - Implement background sync for Bible downloads and cache management - Add storage usage monitoring and management utilities - Ensure SSR-safe implementation with dynamic imports for client-side components 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- app/[locale]/bible/reader.tsx | 99 +++- app/[locale]/layout.tsx | 16 +- app/[locale]/offline/page.tsx | 111 ++++ components/bible/offline-bible-reader.tsx | 371 +++++++++++++ components/bible/offline-download-manager.tsx | 377 +++++++++++++ components/pwa/install-prompt.tsx | 331 ++++++++++++ components/pwa/service-worker-provider.tsx | 238 +++++++++ lib/offline-storage.ts | 499 ++++++++++++++++++ messages/en.json | 10 + messages/ro.json | 10 + public/icon-192.png | Bin 0 -> 30288 bytes public/icon-512.png | Bin 0 -> 189777 bytes public/manifest.json | 91 ++++ public/offline.html | 152 ++++++ public/sw.js | 432 +++++++++++++++ 15 files changed, 2730 insertions(+), 7 deletions(-) create mode 100644 app/[locale]/offline/page.tsx create mode 100644 components/bible/offline-bible-reader.tsx create mode 100644 components/bible/offline-download-manager.tsx create mode 100644 components/pwa/install-prompt.tsx create mode 100644 components/pwa/service-worker-provider.tsx create mode 100644 lib/offline-storage.ts create mode 100644 public/icon-192.png create mode 100644 public/icon-512.png create mode 100644 public/manifest.json create mode 100644 public/offline.html create mode 100644 public/sw.js diff --git a/app/[locale]/bible/reader.tsx b/app/[locale]/bible/reader.tsx index 8a4c833..a837bf5 100644 --- a/app/[locale]/bible/reader.tsx +++ b/app/[locale]/bible/reader.tsx @@ -4,6 +4,9 @@ import { useState, useEffect, useRef, useCallback } from 'react' import { useTranslations, useLocale } from 'next-intl' import { useAuth } from '@/hooks/use-auth' import { useSearchParams, useRouter } from 'next/navigation' +import { OfflineDownloadManager } from '@/components/bible/offline-download-manager' +import { OfflineBibleReader } from '@/components/bible/offline-bible-reader' +import { InstallPrompt, useInstallPrompt } from '@/components/pwa/install-prompt' import { Box, Typography, @@ -69,7 +72,10 @@ import { MenuBook, Visibility, Speed, - Chat + Chat, + CloudDownload, + WifiOff, + Storage } from '@mui/icons-material' interface BibleVerse { @@ -149,6 +155,11 @@ export default function BibleReaderNew() { const [showScrollTop, setShowScrollTop] = useState(false) const [previousVerses, setPreviousVerses] = useState([]) // Keep previous content during loading + // Offline/PWA state + const [isOnline, setIsOnline] = useState(true) + const [isOfflineMode, setIsOfflineMode] = useState(false) + const [offlineDialogOpen, setOfflineDialogOpen] = useState(false) + // Bookmark state const [isChapterBookmarked, setIsChapterBookmarked] = useState(false) const [verseBookmarks, setVerseBookmarks] = useState<{[key: string]: any}>({}) @@ -177,6 +188,9 @@ export default function BibleReaderNew() { const contentRef = useRef(null) const verseRefs = useRef<{[key: number]: HTMLDivElement}>({}) + // PWA install prompt + const { canInstall, isInstalled, showInstallPrompt } = useInstallPrompt() + // Load user preferences from localStorage useEffect(() => { const savedPrefs = localStorage.getItem('bibleReaderPreferences') @@ -235,6 +249,39 @@ export default function BibleReaderNew() { return () => window.removeEventListener('scroll', handleScroll) }, []) + // Online/offline detection + useEffect(() => { + const handleOnline = () => { + setIsOnline(true) + if (isOfflineMode) { + // Show notification that connection is restored + console.log('Connection restored, you can now access all features') + } + } + + const handleOffline = () => { + setIsOnline(false) + console.log('You are now offline. Only downloaded content is available.') + } + + // Set initial state + setIsOnline(navigator.onLine) + + // Check for offline mode preference + const offlineParam = new URLSearchParams(window.location.search).get('offline') + if (offlineParam === 'true') { + setIsOfflineMode(true) + } + + window.addEventListener('online', handleOnline) + window.addEventListener('offline', handleOffline) + + return () => { + window.removeEventListener('online', handleOnline) + window.removeEventListener('offline', handleOffline) + } + }, [isOfflineMode]) + // Fetch versions based on showAllVersions state and locale useEffect(() => { setVersionsLoading(true) @@ -1093,6 +1140,24 @@ export default function BibleReaderNew() { + + + setOfflineDialogOpen(true)} + sx={{ color: !isOnline ? 'warning.main' : 'inherit' }} + > + {isOnline ? : } + + + + {canInstall && !isInstalled && ( + + + + + + )} ) @@ -1386,6 +1451,38 @@ export default function BibleReaderNew() { {/* Settings Dialog */} {renderSettings()} + {/* Offline Downloads Dialog */} + setOfflineDialogOpen(false)} + maxWidth="md" + fullWidth + fullScreen={isMobile} + > + + + + Offline Bible Downloads + + + + { + console.log(`Version ${versionId} downloaded successfully`) + }} + /> + + + + + + + {/* PWA Install Prompt */} + + {/* Copy Feedback */} }): Promise { @@ -53,6 +54,7 @@ export async function generateMetadata({ params }: { params: Promise<{ locale: s description: t('twitterDescription'), images: [ogImageUrl], }, + manifest: '/manifest.json', other: { 'application/ld+json': JSON.stringify({ "@context": "https://schema.org", @@ -119,12 +121,14 @@ export default async function LocaleLayout({ - - - {children} -