import { useState, useCallback } from "react";
import { syncFileFromRepo } from "../../lib/api";
interface RepoSyncIndicatorProps {
fileId: string;
repoFilePath: string | null | undefined;
repoSyncStatus: string | null | undefined;
repoSyncedAt: string | null | undefined;
onSyncComplete?: () => void;
/** Callback to push file content to repo (creates a task) */
onPushToRepo?: () => void;
/** Whether a push operation is in progress */
isPushing?: boolean;
}
/**
* Shows repository file link status and provides sync functionality.
* Displays the linked file path and allows updating from the repo via daemon,
* or pushing local changes back to the repo.
*/
export function RepoSyncIndicator({
fileId,
repoFilePath,
repoSyncStatus,
repoSyncedAt,
onSyncComplete,
onPushToRepo,
isPushing = false,
}: RepoSyncIndicatorProps) {
const [isSyncing, setIsSyncing] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleSync = useCallback(async () => {
setIsSyncing(true);
setError(null);
try {
await syncFileFromRepo(fileId);
// The actual update happens via WebSocket notification
// Give a brief delay then notify parent
setTimeout(() => {
onSyncComplete?.();
}, 500);
} catch (err) {
setError(err instanceof Error ? err.message : "Sync failed");
} finally {
setIsSyncing(false);
}
}, [fileId, onSyncComplete]);
// Don't render if no repo file path is set
if (!repoFilePath) {
return null;
}
const isActuallySyncing = isSyncing || repoSyncStatus === "syncing";
const isSynced = repoSyncStatus === "synced";
const isModified = repoSyncStatus === "modified";
// Format the synced timestamp
const syncedAtFormatted = repoSyncedAt
? new Date(repoSyncedAt).toLocaleString()
: null;
return (
<div className="flex items-center gap-2 text-xs font-mono">
{/* File path icon and link */}
<div className="flex items-center gap-1 text-[#555]">
<svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
className="flex-shrink-0"
>
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<polyline points="14 2 14 8 20 8" />
</svg>
<span className="text-[#75aafc]" title={`Linked to repository file: ${repoFilePath}`}>
{repoFilePath}
</span>
</div>
{/* Status indicator */}
{isSynced && (
<span
className="text-green-500 flex items-center gap-1"
title={syncedAtFormatted ? `Last synced: ${syncedAtFormatted}` : "Synced"}
>
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3">
<polyline points="20 6 9 17 4 12" />
</svg>
</span>
)}
{isModified && (
<span className="text-yellow-500" title="File modified, may need sync">
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3">
<circle cx="12" cy="12" r="10" />
<line x1="12" y1="8" x2="12" y2="12" />
<line x1="12" y1="16" x2="12.01" y2="16" />
</svg>
</span>
)}
{/* Pull from repo button */}
<button
onClick={handleSync}
disabled={isActuallySyncing || isPushing}
className={`flex items-center gap-1 px-1.5 py-0.5 rounded transition-colors ${
isActuallySyncing || isPushing
? "text-[#555] cursor-wait"
: "text-[#555] hover:text-[#75aafc] hover:bg-[rgba(117,170,252,0.1)]"
}`}
title="Pull latest from repository"
>
{isActuallySyncing ? (
<>
<svg
width="10"
height="10"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
className="animate-spin"
>
<circle cx="12" cy="12" r="10" strokeDasharray="32" strokeDashoffset="8" />
</svg>
<span>Pulling...</span>
</>
) : (
<>
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M12 19V5" />
<path d="M5 12l7-7 7 7" />
</svg>
<span>Pull</span>
</>
)}
</button>
{/* Push to repo button */}
{onPushToRepo && (
<button
onClick={onPushToRepo}
disabled={isActuallySyncing || isPushing}
className={`flex items-center gap-1 px-1.5 py-0.5 rounded transition-colors ${
isPushing
? "text-[#555] cursor-wait"
: "text-[#555] hover:text-green-500 hover:bg-[rgba(34,197,94,0.1)]"
}`}
title="Push changes to repository (creates a task)"
>
{isPushing ? (
<>
<svg
width="10"
height="10"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
className="animate-spin"
>
<circle cx="12" cy="12" r="10" strokeDasharray="32" strokeDashoffset="8" />
</svg>
<span>Pushing...</span>
</>
) : (
<>
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M12 5v14" />
<path d="M19 12l-7 7-7-7" />
</svg>
<span>Push</span>
</>
)}
</button>
)}
{/* Error message */}
{error && (
<span className="text-red-500 text-[10px]" title={error}>
Failed
</span>
)}
</div>
);
}