import React from 'react'; import { AlertTriangle, FlaskConical, RotateCcw, ShieldCheck } from 'lucide-react'; import type { ConvergentFieldConflict, ConvergentMigrationPreview, } from '../../domain/sync'; import { convergentConflictAddressKey, dotKey, isConvergentConflictSecret, } from '../../domain/convergentSync'; import { Button } from '../ui/button'; type Translate = (key: string, values?: Record) => string; const ConvergentToggle: React.FC<{ checked: boolean; disabled: boolean; label: string; onChange: (checked: boolean) => void | Promise; }> = ({ checked, disabled, label, onChange }) => ( ); function conflictLabel(conflict: ConvergentFieldConflict, t: Translate): string { const { address } = conflict; switch (address.kind) { case 'entity-presence': return `${address.collection}/${address.entityId} · ${t('cloudSync.convergent.field.presence')}`; case 'entity-position': return `${address.collection}/${address.entityId} · ${t('cloudSync.convergent.field.position')}`; case 'entity-field': return `${address.collection}/${address.entityId} · ${address.field}`; case 'setting': return `settings.${address.path.join('.')}`; case 'setting-structure': return address.paths.map((path) => `settings.${path.join('.')}`).join(' ↔ '); case 'string-entry-presence': return `${address.collection}/${address.value} · ${t('cloudSync.convergent.field.presence')}`; case 'string-entry-position': return `${address.collection}/${address.value} · ${t('cloudSync.convergent.field.position')}`; } } function candidateValue( conflict: ConvergentFieldConflict, candidate: ConvergentFieldConflict['candidates'][number], t: Translate, ): string { if (candidate.tombstone) return t('cloudSync.convergent.conflict.empty'); if (isConvergentConflictSecret(conflict)) { return candidate.value == null ? t('cloudSync.convergent.conflict.empty') : t('cloudSync.convergent.conflict.secretSet'); } const encoded = JSON.stringify(candidate.value); if (!encoded) return t('cloudSync.convergent.conflict.empty'); return encoded.length > 180 ? `${encoded.slice(0, 177)}...` : encoded; } export interface ConvergentSyncPanelProps { t: Translate; resolvedLocale: string | null; config: { enabled: boolean; initialized: boolean }; preview: ConvergentMigrationPreview | null; busy: boolean; error: string | null; conflicts: ConvergentFieldConflict[]; onToggle: (enabled: boolean) => void | Promise; onConfirmMigration: () => void | Promise; onCancelMigration: () => void; onResolveConflict: (addressKey: string, candidateDot: string) => void | Promise; onDowngrade: () => void | Promise; } export const ConvergentSyncPanel: React.FC = ({ t, resolvedLocale, config, preview, busy, error, conflicts, onToggle, onConfirmMigration, onCancelMigration, onResolveConflict, onDowngrade, }) => (
{t('cloudSync.convergent.title')} {t('cloudSync.convergent.experimental')}
{t('cloudSync.convergent.desc')}
{config.initialized && (
{config.enabled ? t('cloudSync.convergent.active') : t('cloudSync.convergent.paused')}
)} {preview && (
{t('cloudSync.convergent.preview.title')}
{Object.values(preview.entityCounts).reduce((sum, count) => sum + (count ?? 0), 0)}
{t('cloudSync.convergent.preview.entities')}
{preview.providers.length}
{t('cloudSync.convergent.preview.providers')}
{preview.conflictCount}
{t('cloudSync.convergent.preview.conflicts')}
{t('cloudSync.convergent.preview.compatibility')}
{preview.providers.length > 0 && (
{preview.providers.map((provider) => (
{provider.provider} {t(`cloudSync.convergent.preview.status.${provider.status}`)} · {t('cloudSync.convergent.preview.schema')} {provider.schemaVersion} {provider.message ? ` · ${provider.message}` : ''}
))}
)} {preview.blockedReasons.length > 0 && (
{preview.blockedReasons.join(' · ')}
)}
)} {error && (
{error}
)} {config.initialized && conflicts.length > 0 && (
{t('cloudSync.convergent.conflicts.title', { count: conflicts.length })}
{conflicts.map((conflict) => { const addressKey = convergentConflictAddressKey(conflict.address); return (
{conflictLabel(conflict, t)}
{conflict.candidates.map((candidate) => (
{candidateValue(conflict, candidate, t)}
{candidate.dot.deviceId} · {new Date(candidate.hlc.wallTime).toLocaleString(resolvedLocale || undefined)} {candidate.selected ? ` · ${t('cloudSync.convergent.conflict.current')}` : ''}
))}
); })}
)} {config.initialized && (
{t('cloudSync.convergent.downgrade.desc')}
)}
);