Something went wrong. Try again.
A local-first note taking app
Something went wrong. Try again.
3.8 kB · 120 lines
TSX
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121import { useEffect, useState } from 'react';
import { Modal } from './Modal';import { CloudDownloadIcon } from '../icons';import type { RemoteVault } from '../vault/registry';
type SyncRemoteVaultModalProps = { /** The remote vault to sync, or null when the modal is closed. */ target: RemoteVault | null; onClose: () => void; /** Clone the vault into `path`. Rejects on failure (surfaced in the modal). */ onConfirm: (path: string) => Promise<void>;};
export function SyncRemoteVaultModal({ target, onClose, onConfirm,}: SyncRemoteVaultModalProps) { const [path, setPath] = useState(''); const [error, setError] = useState<string | null>(null); const [syncing, setSyncing] = useState(false);
// Reset state each time a new target opens the modal. useEffect(() => { if (target) { setPath(''); setError(null); setSyncing(false); } }, [target]);
const handleSelectDirectory = async () => { try { const result = await window.textile?.vault?.showOpenDialog(); if (result && !result.canceled && result.filePaths.length > 0) { setPath(result.filePaths[0]!); setError(null); } } catch { // Dialog may be unavailable in browser dev environment. } };
const handleConfirm = async () => { if (!path.trim()) { setError('Please select a directory'); return; } setError(null); setSyncing(true); try { await onConfirm(path.trim()); onClose(); } catch (e) { setError(e instanceof Error ? e.message : String(e)); } finally { setSyncing(false); } };
return ( <Modal open={!!target} onClose={syncing ? () => {} : onClose} ariaLabel="Sync Remote Vault dialog" title="Sync Remote Vault" panelClassName="w-full max-w-md" bodyClassName="flex min-h-0 flex-1 flex-col gap-4 p-5" > <div className="space-y-5"> <div className="flex items-center gap-2"> <CloudDownloadIcon className="text-foreground/60 size-4 shrink-0" /> <span className="text-foreground/80 truncate text-sm font-medium"> {target?.name ?? ''} </span> </div>
<div> <span className="text-foreground/80 mb-1 block text-xs font-medium"> Sync into directory </span> <div className="flex items-center gap-2"> <span className="text-foreground/60 flex-1 truncate text-xs"> {path || 'No directory selected'} </span> <button type="button" onClick={handleSelectDirectory} disabled={syncing} className="bg-primary/10 text-primary hover:bg-primary/20 border-primary/30 shrink-0 cursor-pointer rounded-md border px-2.5 py-1.5 text-xs transition-colors disabled:opacity-50" > Browse… </button> </div> {error && <p className="text-destructive mt-1 text-xs">{error}</p>} </div> </div>
<div className="mt-auto flex justify-end gap-2"> <button type="button" onClick={onClose} disabled={syncing} className="border-border bg-background text-foreground hover:bg-border/30 cursor-pointer rounded-md border px-3 py-1.5 text-xs disabled:opacity-50" > Cancel </button> <button type="button" onClick={handleConfirm} disabled={syncing || !path} className="bg-primary text-primary-foreground cursor-pointer rounded-md px-3 py-1.5 text-xs hover:opacity-90 disabled:opacity-50" > {syncing ? 'Syncing…' : 'Sync Vault'} </button> </div> </Modal> );}