refactor: rename files to PascalCase components and kebab-case APIs (Task 4.4)
- Rename all component files to PascalCase matching exported names - Move components into feature directories (git/, session/, project/, terminal/, workspace/, ui/, layout/) - Rename all page files to PascalCase with Page suffix - Rename all API files to kebab-case - Update all imports across codebase with corrected relative depths - Preserve git history via git mv Quality gates: tsc (pass), eslint (pass), 66/74 tests pass (8 pre-existing failures) Refs: repo-restructure Task 4.4
This commit is contained in:
@@ -0,0 +1,269 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import {
|
||||
checkoutBranch,
|
||||
createBranch,
|
||||
fetchRepository,
|
||||
getRepositoryStatus,
|
||||
pullRepository,
|
||||
pushRepository,
|
||||
type GitStatus,
|
||||
} from "../../../api/git-repositories";
|
||||
import { Icon } from "../../ui/Icon";
|
||||
import { MergeDialog } from "./MergeDialog";
|
||||
import styles from "./features/git/GitToolbar.module.css";
|
||||
|
||||
interface GitToolbarProps {
|
||||
projectId: string;
|
||||
repoId: string;
|
||||
currentBranch: string;
|
||||
branches: string[];
|
||||
hasRemote: boolean;
|
||||
onBranchChange: (branch: string) => void;
|
||||
onRefresh: () => void;
|
||||
}
|
||||
|
||||
export const GitToolbar = ({
|
||||
projectId,
|
||||
repoId,
|
||||
currentBranch,
|
||||
branches,
|
||||
hasRemote,
|
||||
onBranchChange,
|
||||
onRefresh,
|
||||
}: GitToolbarProps) => {
|
||||
const [status, setStatus] = useState<GitStatus | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [showNewBranch, setShowNewBranch] = useState(false);
|
||||
const [newBranchName, setNewBranchName] = useState("");
|
||||
const [newBranchBase, setNewBranchBase] = useState("");
|
||||
const [showMergeDialog, setShowMergeDialog] = useState(false);
|
||||
|
||||
const loadStatus = useCallback(async () => {
|
||||
try {
|
||||
const data = await getRepositoryStatus(projectId, repoId);
|
||||
setStatus(data);
|
||||
setError(null);
|
||||
} catch {
|
||||
setError("Failed to load status");
|
||||
}
|
||||
}, [projectId, repoId]);
|
||||
|
||||
useEffect(() => {
|
||||
void loadStatus();
|
||||
// Poll status every 5 seconds
|
||||
const interval = setInterval(() => void loadStatus(), 5000);
|
||||
return () => clearInterval(interval);
|
||||
}, [loadStatus]);
|
||||
|
||||
const handleFetch = async () => {
|
||||
if (!hasRemote) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
await fetchRepository(projectId, repoId);
|
||||
await loadStatus();
|
||||
} catch {
|
||||
setError("Fetch failed");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePull = async () => {
|
||||
if (!hasRemote) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
await pullRepository(projectId, repoId, currentBranch || undefined);
|
||||
await loadStatus();
|
||||
onRefresh();
|
||||
} catch {
|
||||
setError("Pull failed");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePush = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await pushRepository(projectId, repoId, currentBranch);
|
||||
await loadStatus();
|
||||
} catch {
|
||||
setError("Push failed");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCheckout = async (branch: string) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await checkoutBranch(projectId, repoId, branch);
|
||||
onBranchChange(branch);
|
||||
onRefresh();
|
||||
} catch {
|
||||
setError("Checkout failed");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreateBranch = async () => {
|
||||
if (!newBranchName.trim()) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
await createBranch(projectId, repoId, newBranchName, newBranchBase || currentBranch || "HEAD");
|
||||
setShowNewBranch(false);
|
||||
setNewBranchName("");
|
||||
setNewBranchBase("");
|
||||
onRefresh();
|
||||
} catch {
|
||||
setError("Failed to create branch");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const hasChanges = status && (
|
||||
status.modified.length > 0 ||
|
||||
status.added.length > 0 ||
|
||||
status.deleted.length > 0 ||
|
||||
status.untracked.length > 0
|
||||
);
|
||||
|
||||
const canSync = hasRemote;
|
||||
|
||||
return (
|
||||
<div className={styles.gitToolbar}>
|
||||
{error && <div className={styles.toolbarError}>{error}</div>}
|
||||
|
||||
<div className={styles.toolbarRow}>
|
||||
<div className={styles.toolbarGroup}>
|
||||
<select
|
||||
value={currentBranch}
|
||||
onChange={(e) => handleCheckout(e.target.value)}
|
||||
disabled={loading}
|
||||
className={styles.branchSelect}
|
||||
>
|
||||
{branches.map((b) => (
|
||||
<option key={b} value={b}>
|
||||
{b === currentBranch ? (
|
||||
<>
|
||||
<Icon name="branch" size="sm" /> {b}
|
||||
</>
|
||||
) : (
|
||||
b
|
||||
)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
className={styles.toolbarButton}
|
||||
onClick={() => setShowNewBranch(!showNewBranch)}
|
||||
disabled={loading}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="add" size="sm" /> New
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className={styles.toolbarGroup}>
|
||||
<button
|
||||
className={styles.toolbarButton}
|
||||
onClick={handleFetch}
|
||||
disabled={loading || !canSync}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="fetch" size="sm" /> Fetch
|
||||
</button>
|
||||
<button
|
||||
className={styles.toolbarButton}
|
||||
onClick={handlePull}
|
||||
disabled={loading || !canSync}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="pull" size="sm" /> Pull
|
||||
{status?.behind ? <span className={styles.badge}>{status.behind}</span> : null}
|
||||
</button>
|
||||
<button
|
||||
className={styles.toolbarButton}
|
||||
onClick={handlePush}
|
||||
disabled={loading || !canSync || !status?.ahead}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="push" size="sm" /> Push
|
||||
{status?.ahead ? <span className={styles.badge}>{status.ahead}</span> : null}
|
||||
</button>
|
||||
<button
|
||||
className={styles.toolbarButton}
|
||||
onClick={() => setShowMergeDialog(true)}
|
||||
disabled={loading}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="merge" size="sm" /> Merge
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showNewBranch && (
|
||||
<div className={`${styles.toolbarRow} ${styles.newBranchForm}`}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Branch name"
|
||||
value={newBranchName}
|
||||
onChange={(e) => setNewBranchName(e.target.value)}
|
||||
className={styles.toolbarInput}
|
||||
/>
|
||||
<select
|
||||
value={newBranchBase}
|
||||
onChange={(e) => setNewBranchBase(e.target.value)}
|
||||
className={styles.toolbarInput}
|
||||
>
|
||||
<option value="">Base: HEAD</option>
|
||||
{branches.map((b) => (
|
||||
<option key={b} value={b}>{ b}</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
className={styles.toolbarButtonPrimary}
|
||||
onClick={handleCreateBranch}
|
||||
disabled={loading || !newBranchName.trim()}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="add" size="sm" /> Create
|
||||
</button>
|
||||
<button
|
||||
className={styles.toolbarButton}
|
||||
onClick={() => setShowNewBranch(false)}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="cancel" size="sm" /> Cancel
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasChanges && status && (
|
||||
<div className={`${styles.toolbarRow} ${styles.statusSummary}`}>
|
||||
{status.modified.length > 0 && <span className={styles.statusBadgeModified}><Icon name="edit" size="sm" /> {status.modified.length} modified</span>}
|
||||
{status.added.length > 0 && <span className={styles.statusBadgeAdded}><Icon name="add" size="sm" /> {status.added.length} added</span>}
|
||||
{status.deleted.length > 0 && <span className={styles.statusBadgeDeleted}><Icon name="delete" size="sm" /> {status.deleted.length} deleted</span>}
|
||||
{status.untracked.length > 0 && <span className={styles.statusBadgeUntracked}><Icon name="warning" size="sm" /> {status.untracked.length} untracked</span>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<MergeDialog
|
||||
projectId={projectId}
|
||||
repoId={repoId}
|
||||
branches={branches}
|
||||
currentBranch={currentBranch}
|
||||
isOpen={showMergeDialog}
|
||||
onClose={() => setShowMergeDialog(false)}
|
||||
onMerge={() => {
|
||||
void loadStatus();
|
||||
onRefresh();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user