import React, { type Dispatch, type MutableRefObject, type SetStateAction } from 'react'; import { AlertTriangle, Cloud, Database, Download, History, Key, Loader2, ShieldCheck, Trash2 } from 'lucide-react'; import type { CloudProvider, ConflictResolution, SyncPayload, SyncResult, WebDAVAuthType } from '../../domain/sync'; import type { ShrinkFinding } from '../../domain/syncGuards'; import { stripSyncPayloadEncryptedCredentials } from '../../domain/credentials'; import type { useCloudSync } from '../../application/state/useCloudSync'; import { cn } from '../../lib/utils'; import { Button } from '../ui/button'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '../ui/dialog'; import { Input } from '../ui/input'; import { Label } from '../ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'; import { toast } from '../ui/toast'; import { ConflictModal, GitHubDeviceFlowModal } from './CloudSyncControls'; type SyncController = ReturnType; type TextValues = Record; type Translate = (key: string, values?: TextValues) => string; type StringSetter = Dispatch>; type BooleanSetter = Dispatch>; type HistoryPreview = { sha: string; payload: SyncPayload; preview: { hostCount: number; keyCount: number; snippetCount: number; noteCount: number; identityCount: number; portForwardingRuleCount: number; }; deviceName?: string; version?: number; } | null; interface CloudSyncDialogsProps { t: Translate; sync: SyncController; showGitHubModal: boolean; gitHubUserCode: string; gitHubVerificationUri: string; isPollingGitHub: boolean; activeGitHubAttemptIdRef: MutableRefObject; setShowGitHubModal: BooleanSetter; setIsPollingGitHub: BooleanSetter; endPendingConnect: (provider: CloudProvider) => void; showConflictModal: boolean; setShowConflictModal: BooleanSetter; handleResolveConflict: (resolution: ConflictResolution) => Promise; showHistoryModal: boolean; setShowHistoryModal: BooleanSetter; historyError: string | null; historyLoading: boolean; historyPreview: HistoryPreview; setHistoryPreview: Dispatch>; historyPreviewLoading: boolean; historyRevisions: Array<{ version: string; date: Date }>; handlePreviewRevision: (sha: string) => Promise; handleRestoreRevision: () => Promise; showWebdavDialog: boolean; setShowWebdavDialog: BooleanSetter; webdavEndpoint: string; setWebdavEndpoint: StringSetter; webdavAuthType: WebDAVAuthType; setWebdavAuthType: Dispatch>; webdavUsername: string; setWebdavUsername: StringSetter; webdavPassword: string; setWebdavPassword: StringSetter; webdavToken: string; setWebdavToken: StringSetter; showWebdavSecret: boolean; setShowWebdavSecret: BooleanSetter; webdavAllowInsecure: boolean; setWebdavAllowInsecure: BooleanSetter; webdavError: string | null; webdavErrorDetail: string | null; isSavingWebdav: boolean; handleSaveWebdav: () => Promise; showS3Dialog: boolean; setShowS3Dialog: BooleanSetter; s3Endpoint: string; setS3Endpoint: StringSetter; s3Region: string; setS3Region: StringSetter; s3Bucket: string; setS3Bucket: StringSetter; s3AccessKeyId: string; setS3AccessKeyId: StringSetter; s3SecretAccessKey: string; setS3SecretAccessKey: StringSetter; s3SessionToken: string; setS3SessionToken: StringSetter; s3Prefix: string; setS3Prefix: StringSetter; s3ForcePathStyle: boolean; setS3ForcePathStyle: BooleanSetter; s3AllowInsecure: boolean; setS3AllowInsecure: BooleanSetter; showS3Secret: boolean; setShowS3Secret: BooleanSetter; s3Error: string | null; s3ErrorDetail: string | null; isSavingS3: boolean; handleSaveS3: () => Promise; showChangeKeyDialog: boolean; setShowChangeKeyDialog: BooleanSetter; currentMasterKey: string; setCurrentMasterKey: StringSetter; newMasterKey: string; setNewMasterKey: StringSetter; confirmNewMasterKey: string; setConfirmNewMasterKey: StringSetter; showMasterKey: boolean; setShowMasterKey: BooleanSetter; changeKeyError: string | null; setChangeKeyError: Dispatch>; isChangingKey: boolean; setIsChangingKey: BooleanSetter; showUnlockDialog: boolean; setShowUnlockDialog: BooleanSetter; unlockMasterKey: string; setUnlockMasterKey: StringSetter; showUnlockMasterKey: boolean; setShowUnlockMasterKey: BooleanSetter; unlockError: string | null; setUnlockError: Dispatch>; isUnlocking: boolean; setIsUnlocking: BooleanSetter; showClearLocalDialog: boolean; setShowClearLocalDialog: BooleanSetter; onBuildPayload: () => SyncPayload | Promise; onApplyPayload: (payload: SyncPayload) => void | Promise; onApplyConvergentPayload: ( payload: SyncPayload, commitReplica: () => Promise, ) => Promise; onClearLocalData?: () => void; ensureSyncablePayload: (payload: SyncPayload) => boolean; showForcePushConfirm: boolean; setShowForcePushConfirm: BooleanSetter; blockedFinding: Extract | null; setBlockedFinding: Dispatch | null>>; } export const CloudSyncDialogs: React.FC = ({ t, sync, showGitHubModal, gitHubUserCode, gitHubVerificationUri, isPollingGitHub, activeGitHubAttemptIdRef, setShowGitHubModal, setIsPollingGitHub, endPendingConnect, showConflictModal, setShowConflictModal, handleResolveConflict, showHistoryModal, setShowHistoryModal, historyError, historyLoading, historyPreview, setHistoryPreview, historyPreviewLoading, historyRevisions, handlePreviewRevision, handleRestoreRevision, showWebdavDialog, setShowWebdavDialog, webdavEndpoint, setWebdavEndpoint, webdavAuthType, setWebdavAuthType, webdavUsername, setWebdavUsername, webdavPassword, setWebdavPassword, webdavToken, setWebdavToken, showWebdavSecret, setShowWebdavSecret, webdavAllowInsecure, setWebdavAllowInsecure, webdavError, webdavErrorDetail, isSavingWebdav, handleSaveWebdav, showS3Dialog, setShowS3Dialog, s3Endpoint, setS3Endpoint, s3Region, setS3Region, s3Bucket, setS3Bucket, s3AccessKeyId, setS3AccessKeyId, s3SecretAccessKey, setS3SecretAccessKey, s3SessionToken, setS3SessionToken, s3Prefix, setS3Prefix, s3ForcePathStyle, setS3ForcePathStyle, s3AllowInsecure, setS3AllowInsecure, showS3Secret, setShowS3Secret, s3Error, s3ErrorDetail, isSavingS3, handleSaveS3, showChangeKeyDialog, setShowChangeKeyDialog, currentMasterKey, setCurrentMasterKey, newMasterKey, setNewMasterKey, confirmNewMasterKey, setConfirmNewMasterKey, showMasterKey, setShowMasterKey, changeKeyError, setChangeKeyError, isChangingKey, setIsChangingKey, showUnlockDialog, setShowUnlockDialog, unlockMasterKey, setUnlockMasterKey, showUnlockMasterKey, setShowUnlockMasterKey, unlockError, setUnlockError, isUnlocking, setIsUnlocking, showClearLocalDialog, setShowClearLocalDialog, onBuildPayload, onApplyPayload, onApplyConvergentPayload, onClearLocalData, ensureSyncablePayload, showForcePushConfirm, setShowForcePushConfirm, blockedFinding, setBlockedFinding }) => ( <> {/* Modals */} { activeGitHubAttemptIdRef.current = null; setShowGitHubModal(false); setIsPollingGitHub(false); endPendingConnect('github'); sync.cancelOAuthConnect(); }} /> setShowConflictModal(false)} /> {/* Gist Revision History Modal (#679) */} {t('cloudSync.revisionHistory.title')} {t('cloudSync.revisionHistory.description')} {historyError && (
{historyError}
)} {historyLoading ? (
) : historyPreview ? ( // Preview of a selected revision
{t('cloudSync.revisionHistory.revisionPreview')}
{historyPreview.deviceName && (
{t('cloudSync.revisionHistory.device')}: {historyPreview.deviceName} {historyPreview.version != null && ` · v${historyPreview.version}`}
)}
{t('cloudSync.revisionHistory.hosts')} {historyPreview.preview.hostCount}
{t('cloudSync.revisionHistory.keys')} {historyPreview.preview.keyCount}
{t('cloudSync.revisionHistory.snippets')} {historyPreview.preview.snippetCount}
{t('cloudSync.revisionHistory.notes')} {historyPreview.preview.noteCount}
{t('cloudSync.revisionHistory.identities')} {historyPreview.preview.identityCount}
) : ( // Revision list
{historyRevisions.length === 0 ? (
{t('cloudSync.revisionHistory.empty')}
) : (
{historyRevisions.map((rev, index) => ( ))}
)}
)} {historyPreviewLoading && (
)}
{t('cloudSync.webdav.title')} {t('cloudSync.webdav.desc')}
setWebdavEndpoint(e.target.value)} placeholder="https://dav.example.com/remote.php/webdav/" />
{webdavAuthType !== 'token' ? ( <>
setWebdavUsername(e.target.value)} autoComplete="username" />
setWebdavPassword(e.target.value)} autoComplete="current-password" />
) : (
setWebdavToken(e.target.value)} />
)} {webdavError && (

{webdavError}

)} {webdavErrorDetail && (
                                {webdavErrorDetail}
                            
)}
{t('cloudSync.s3.title')} {t('cloudSync.s3.desc')}
setS3Endpoint(e.target.value)} placeholder="https://s3.example.com" />
setS3Region(e.target.value)} placeholder="us-east-1" />
setS3Bucket(e.target.value)} placeholder="netcatty-backups" />
setS3AccessKeyId(e.target.value)} autoComplete="off" />
setS3SecretAccessKey(e.target.value)} autoComplete="off" />
setS3SessionToken(e.target.value)} autoComplete="off" />
setS3Prefix(e.target.value)} placeholder="backups/netcatty" />
{s3Error && (

{s3Error}

)} {s3ErrorDetail && (
                                {s3ErrorDetail}
                            
)}
{t('cloudSync.changeKey.title')} {t('cloudSync.changeKey.desc')}
setCurrentMasterKey(e.target.value)} placeholder={t('cloudSync.changeKey.currentPlaceholder')} autoFocus />
setNewMasterKey(e.target.value)} placeholder={t('cloudSync.changeKey.newPlaceholder')} />
setConfirmNewMasterKey(e.target.value)} placeholder={t('cloudSync.changeKey.confirmPlaceholder')} />
{changeKeyError && (

{changeKeyError}

)}
{t('cloudSync.unlock.title')} {t('cloudSync.unlock.desc')}
setUnlockMasterKey(e.target.value)} placeholder={t('cloudSync.unlock.placeholder')} autoFocus />
{unlockError && (

{unlockError}

)}
{/* Clear Local Data Confirmation Dialog */} {t('cloudSync.clearLocal.dialog.title')} {t('cloudSync.clearLocal.dialog.desc')} {/* Force-push confirmation modal (Task 8) */} {showForcePushConfirm && blockedFinding && ( !open && setShowForcePushConfirm(false)}> {t('sync.forcePush.title')}

{t('sync.forcePush.body', { lost: blockedFinding.lost, entityType: t(`sync.entityType.${blockedFinding.entityType}`), })}

)} );