story-research-zapwall/components/SyncProgressBar.tsx
2026-01-07 02:11:40 +01:00

320 lines
11 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 type { SyncProgress } from '@/lib/userContentSync'
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'
export function SyncProgressBar(): React.ReactElement | null {
console.warn('[SyncProgressBar] Component function called')
const [syncProgress, setSyncProgress] = useState<SyncProgress | null>(null)
const [isSyncing, setIsSyncing] = useState(false)
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)
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...')
setIsSyncing(true)
setSyncProgress({ currentStep: 0, totalSteps: 6, completed: false })
try {
const { swClient } = await import('@/lib/swClient')
const isReady = await swClient.isReady()
if (isReady) {
await swClient.startUserSync(connectionState.pubkey)
// Progress is tracked via syncProgressManager
// Listen to syncProgressManager for updates
const { syncProgressManager } = await import('@/lib/syncProgressManager')
const checkProgress = (): void => {
const currentProgress = syncProgressManager.getProgress()
if (currentProgress) {
setSyncProgress(currentProgress)
if (currentProgress.completed) {
setIsSyncing(false)
void loadSyncStatus()
}
}
}
// Check progress periodically
const progressInterval = setInterval(() => {
checkProgress()
const currentProgress = syncProgressManager.getProgress()
if (currentProgress?.completed) {
clearInterval(progressInterval)
}
}, 500)
// Cleanup after 60 seconds max
setTimeout(() => {
clearInterval(progressInterval)
setIsSyncing(false)
}, 60000)
} else {
setIsSyncing(false)
}
} catch (autoSyncError) {
console.error('[SyncProgressBar] Error during auto-sync:', autoSyncError)
setIsSyncing(false)
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
}
setIsSyncing(true)
setSyncProgress({ currentStep: 0, totalSteps: 6, completed: false })
// 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)
// Progress is tracked via syncProgressManager
// Listen to syncProgressManager for updates
const { syncProgressManager } = await import('@/lib/syncProgressManager')
const checkProgress = (): void => {
const currentProgress = syncProgressManager.getProgress()
if (currentProgress) {
setSyncProgress(currentProgress)
if (currentProgress.completed) {
setIsSyncing(false)
void loadSyncStatus()
}
}
}
// Check progress periodically
const progressInterval = setInterval(() => {
checkProgress()
const currentProgress = syncProgressManager.getProgress()
if (currentProgress?.completed) {
clearInterval(progressInterval)
}
}, 500)
// Cleanup after 60 seconds max
setTimeout(() => {
clearInterval(progressInterval)
setIsSyncing(false)
}, 60000)
} else {
setIsSyncing(false)
}
}
} catch (resyncError) {
console.error('Error resynchronizing:', resyncError)
setIsSyncing(false)
}
}
// 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>
)
}