story-research-zapwall/components/SyncProgressBar.tsx
2026-01-07 03:45:01 +01:00

269 lines
9.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useEffect } from 'react'
import { nostrAuthService } from '@/lib/nostrAuth'
import { getLastSyncDate, setLastSyncDate as setLastSyncDateStorage, getCurrentTimestamp, calculateDaysBetween } from '@/lib/syncStorage'
import { MIN_EVENT_DATE } from '@/lib/platformConfig'
import { objectCache } from '@/lib/objectCache'
import { t } from '@/lib/i18n'
import { useSyncProgress } from '@/lib/hooks/useSyncProgress'
export function SyncProgressBar(): React.ReactElement | null {
console.warn('[SyncProgressBar] Component function called')
const [lastSyncDate, setLastSyncDate] = useState<number | null>(null)
const [totalDays, setTotalDays] = useState<number>(0)
const [isInitialized, setIsInitialized] = useState(false)
const [connectionState, setConnectionState] = useState<{ connected: boolean; pubkey: string | null }>({ connected: false, pubkey: null })
const [error, setError] = useState<string | null>(null)
const { syncProgress, isSyncing, startMonitoring, stopMonitoring } = useSyncProgress({
onComplete: async () => {
await loadSyncStatus()
},
})
async function loadSyncStatus(): Promise<void> {
try {
const state = nostrAuthService.getState()
if (!state.connected || !state.pubkey) {
return
}
const storedLastSyncDate = await getLastSyncDate()
const currentTimestamp = getCurrentTimestamp()
const days = calculateDaysBetween(storedLastSyncDate, currentTimestamp)
setLastSyncDate(storedLastSyncDate)
setTotalDays(days)
} catch (loadError) {
console.error('Error loading sync status:', loadError)
}
}
useEffect(() => {
// Check connection state
const checkConnection = (): void => {
const state = nostrAuthService.getState()
console.warn('[SyncProgressBar] Initial connection check:', { connected: state.connected, pubkey: state.pubkey })
setConnectionState({ connected: state.connected ?? false, pubkey: state.pubkey ?? null })
setIsInitialized(true)
}
// Initial check
checkConnection()
// Listen to connection changes
const unsubscribe = nostrAuthService.subscribe((state) => {
console.warn('[SyncProgressBar] Connection state changed:', { connected: state.connected, pubkey: state.pubkey })
setConnectionState({ connected: state.connected ?? false, pubkey: state.pubkey ?? null })
})
return () => {
unsubscribe()
}
}, [])
useEffect(() => {
console.warn('[SyncProgressBar] Effect triggered:', { isInitialized, connected: connectionState.connected, pubkey: connectionState.pubkey, isSyncing })
if (!isInitialized) {
console.warn('[SyncProgressBar] Not initialized yet')
return
}
if (!connectionState.connected) {
console.warn('[SyncProgressBar] Not connected')
return
}
if (!connectionState.pubkey) {
console.warn('[SyncProgressBar] No pubkey')
return
}
void (async () => {
console.warn('[SyncProgressBar] Starting sync check...')
await loadSyncStatus()
// Auto-start sync if not recently synced
const storedLastSyncDate = await getLastSyncDate()
const currentTimestamp = getCurrentTimestamp()
const isRecentlySynced = storedLastSyncDate >= currentTimestamp - 3600
console.warn('[SyncProgressBar] Sync status:', { storedLastSyncDate, currentTimestamp, isRecentlySynced, isSyncing })
// Only auto-start if not recently synced
if (!isRecentlySynced && !isSyncing && connectionState.pubkey) {
console.warn('[SyncProgressBar] Starting auto-sync...')
try {
const { swClient } = await import('@/lib/swClient')
const isReady = await swClient.isReady()
if (isReady) {
await swClient.startUserSync(connectionState.pubkey)
startMonitoring()
} else {
stopMonitoring()
}
} catch (autoSyncError) {
console.error('[SyncProgressBar] Error during auto-sync:', autoSyncError)
stopMonitoring()
setError(autoSyncError instanceof Error ? autoSyncError.message : 'Erreur de synchronisation')
}
} else {
console.warn('[SyncProgressBar] Skipping auto-sync:', { isRecentlySynced, isSyncing, hasPubkey: Boolean(connectionState.pubkey) })
}
})()
}, [isInitialized, connectionState.connected, connectionState.pubkey, isSyncing])
async function resynchronize(): Promise<void> {
try {
const state = nostrAuthService.getState()
if (!state.connected || !state.pubkey) {
return
}
// Clear cache for user content (but keep other data)
await Promise.all([
objectCache.clear('author'),
objectCache.clear('series'),
objectCache.clear('publication'),
objectCache.clear('review'),
objectCache.clear('purchase'),
objectCache.clear('sponsoring'),
objectCache.clear('review_tip'),
])
// Reset last sync date to force full resync
await setLastSyncDateStorage(MIN_EVENT_DATE)
// Reload sync status
await loadSyncStatus()
// Start full resynchronization via Service Worker
if (state.pubkey !== null) {
const { swClient } = await import('@/lib/swClient')
const isReady = await swClient.isReady()
if (isReady) {
await swClient.startUserSync(state.pubkey)
startMonitoring()
} else {
stopMonitoring()
}
}
} catch (resyncError) {
console.error('Error resynchronizing:', resyncError)
stopMonitoring()
}
}
// Don't show if not initialized or not connected
if (!isInitialized || !connectionState.connected || !connectionState.pubkey) {
console.warn('[SyncProgressBar] Not rendering:', { isInitialized, connected: connectionState.connected, pubkey: connectionState.pubkey })
return null
}
console.warn('[SyncProgressBar] Rendering component')
// Check if sync is recently completed (within last hour)
const isRecentlySynced = lastSyncDate !== null && lastSyncDate >= getCurrentTimestamp() - 3600
const progressPercentage = syncProgress && syncProgress.totalSteps > 0
? Math.min(100, (syncProgress.currentStep / syncProgress.totalSteps) * 100)
: 0
const formatDate = (timestamp: number): string => {
const date = new Date(timestamp * 1000)
const locale = typeof window !== 'undefined' ? navigator.language : 'fr-FR'
return date.toLocaleDateString(locale, { day: '2-digit', month: '2-digit', year: 'numeric' })
}
const getStartDate = (): number => {
if (lastSyncDate !== null) {
return lastSyncDate
}
return MIN_EVENT_DATE
}
const startDate = getStartDate()
const endDate = getCurrentTimestamp()
return (
<div className="bg-cyber-darker border border-neon-cyan/30 rounded-lg p-4 mt-6">
{error && (
<div className="mb-4 bg-red-900/30 border border-red-500/50 rounded p-3 text-red-300 text-sm">
{error}
<button
onClick={() => {
setError(null)
}}
className="ml-2 text-red-400 hover:text-red-200"
>
×
</button>
</div>
)}
<div className="flex items-center justify-between mb-2">
<h3 className="text-lg font-semibold text-neon-cyan">
{t('settings.sync.title')}
</h3>
{!isSyncing && (
<button
onClick={() => {
void resynchronize()
}}
className="px-3 py-1 text-xs bg-neon-cyan/20 hover:bg-neon-cyan/30 text-neon-cyan rounded border border-neon-cyan/50 hover:border-neon-cyan transition-colors"
>
{t('settings.sync.resync')}
</button>
)}
</div>
{totalDays > 0 && (
<div className="mb-2">
<p className="text-sm text-cyber-accent">
{t('settings.sync.daysRange', {
startDate: formatDate(startDate),
endDate: formatDate(endDate),
days: totalDays,
})}
</p>
</div>
)}
{isSyncing && syncProgress && (
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<span className="text-cyber-accent">
{t('settings.sync.progress', {
current: syncProgress.currentStep,
total: syncProgress.totalSteps,
})}
</span>
<span className="text-neon-cyan font-semibold">
{Math.round(progressPercentage)}%
</span>
</div>
<div className="w-full bg-cyber-dark rounded-full h-2 overflow-hidden">
<div
className="bg-neon-cyan h-full transition-all duration-300"
style={{ width: `${progressPercentage}%` }}
/>
</div>
</div>
)}
{!isSyncing && totalDays === 0 && isRecentlySynced && (
<p className="text-sm text-green-400">
{t('settings.sync.completed')}
</p>
)}
{!isSyncing && totalDays === 0 && !isRecentlySynced && (
<p className="text-sm text-cyber-accent">
{t('settings.sync.ready')}
</p>
)}
</div>
)
}