story-research-zapwall/components/SyncProgressBar.tsx
2026-01-06 16:44:12 +01:00

154 lines
4.9 KiB
TypeScript

import { useState, useEffect } from 'react'
import { nostrAuthService } from '@/lib/nostrAuth'
import { syncUserContentToCache, type SyncProgress } from '@/lib/userContentSync'
import { getLastSyncDate, getCurrentTimestamp, calculateDaysBetween } from '@/lib/syncStorage'
import { MIN_EVENT_DATE } from '@/lib/platformConfig'
import { t } from '@/lib/i18n'
export function SyncProgressBar(): React.ReactElement | null {
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)
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 (error) {
console.error('Error loading sync status:', error)
}
}
useEffect(() => {
void loadSyncStatus()
}, [])
async function startSync(): Promise<void> {
try {
const state = nostrAuthService.getState()
if (!state.connected || !state.pubkey) {
return
}
setIsSyncing(true)
setSyncProgress({ currentStep: 0, totalSteps: 6, completed: false })
await syncUserContentToCache(state.pubkey, (progress) => {
setSyncProgress(progress)
if (progress.completed) {
setIsSyncing(false)
void loadSyncStatus()
}
})
} catch (error) {
console.error('Error starting sync:', error)
setIsSyncing(false)
}
}
// Don't show if not connected
const state = nostrAuthService.getState()
if (!state.connected || !state.pubkey) {
return null
}
// 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">
<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 startSync()
}}
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.start')}
</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>
)
}