Implement comprehensive PWA with offline Bible reading capabilities
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -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<BibleVerse[]>([]) // 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<HTMLDivElement>(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() {
|
||||
<Share />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="Offline Downloads">
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => setOfflineDialogOpen(true)}
|
||||
sx={{ color: !isOnline ? 'warning.main' : 'inherit' }}
|
||||
>
|
||||
{isOnline ? <CloudDownload /> : <WifiOff />}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
{canInstall && !isInstalled && (
|
||||
<Tooltip title="Install App">
|
||||
<IconButton size="small" onClick={showInstallPrompt}>
|
||||
<Storage />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
</Paper>
|
||||
)
|
||||
@@ -1386,6 +1451,38 @@ export default function BibleReaderNew() {
|
||||
{/* Settings Dialog */}
|
||||
{renderSettings()}
|
||||
|
||||
{/* Offline Downloads Dialog */}
|
||||
<Dialog
|
||||
open={offlineDialogOpen}
|
||||
onClose={() => setOfflineDialogOpen(false)}
|
||||
maxWidth="md"
|
||||
fullWidth
|
||||
fullScreen={isMobile}
|
||||
>
|
||||
<DialogTitle>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<Storage color="primary" />
|
||||
Offline Bible Downloads
|
||||
</Box>
|
||||
</DialogTitle>
|
||||
<DialogContent sx={{ p: 0 }}>
|
||||
<OfflineDownloadManager
|
||||
availableVersions={versions}
|
||||
onVersionDownloaded={(versionId) => {
|
||||
console.log(`Version ${versionId} downloaded successfully`)
|
||||
}}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setOfflineDialogOpen(false)}>
|
||||
Close
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* PWA Install Prompt */}
|
||||
<InstallPrompt autoShow={true} />
|
||||
|
||||
{/* Copy Feedback */}
|
||||
<Snackbar
|
||||
open={copyFeedback.open}
|
||||
|
||||
Reference in New Issue
Block a user