Skip to content

Commit 44e64c5

Browse files
committed
chore: simplify UI components
1 parent e9864d6 commit 44e64c5

File tree

5 files changed

+314
-405
lines changed

5 files changed

+314
-405
lines changed

packages/ui/src/components/layout.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,10 @@ export function Layout({ children, dataDir }: { children: React.ReactNode; dataD
160160
{/* Footer */}
161161
{dataDir && (
162162
<footer className="border-t border-border/80 py-2 text-xs text-muted-foreground w-full overflow-hidden">
163-
<div className="mx-auto w-full max-w-7xl px-4 flex items-center gap-2">
163+
<div className="mx-auto w-full max-w-7xl px-2 flex items-center gap-2">
164164
{machineName && (
165-
<div className="flex items-center gap-1 shrink-0">
166-
<Monitor className="h-3.5 w-3.5 shrink-0" />
165+
<div className="flex items-center gap-1.5 shrink-0 rounded-full border border-border/80 bg-muted/60 px-2 py-0.5">
166+
<Monitor className="h-3 w-3 shrink-0" />
167167
<span className="font-medium text-xs">{machineName}</span>
168168
</div>
169169
)}
Lines changed: 42 additions & 202 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,8 @@
1-
import { useState } from 'react';
2-
import { Link } from 'react-router-dom';
3-
import { Card, CardHeader, CardTitle, CardContent, CardFooter } from '@/components/ui/card';
4-
import { Button } from '@/components/ui/button';
5-
import { ConfirmDialog } from './confirm-dialog';
1+
import { FolderGit2, Star } from 'lucide-react';
2+
import { SyncItemCard } from './sync-item-card';
63
import { RepoSettingsDialog } from './repo-settings-dialog';
7-
import { SyncStatusBadge } from './sync-status-badge';
8-
import { formatDate } from '@/lib/utils';
9-
import {
10-
FolderGit2,
11-
RefreshCw,
12-
FileCode2,
13-
Trash2,
14-
Pause,
15-
Play,
16-
Star,
17-
Settings2,
18-
ShieldAlert,
19-
} from 'lucide-react';
20-
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
214
import { api } from '@/lib/api';
22-
import { getSizeLevel, type SizeThresholds, DEFAULT_SIZE_THRESHOLDS } from '@/lib/utils';
23-
import { SizeLabel } from './size-label';
5+
import { type SizeThresholds, DEFAULT_SIZE_THRESHOLDS } from '@/lib/utils';
246
import type { RepoSummary } from '@/hooks/use-repos';
257

268
interface RepoCardProps {
@@ -34,193 +16,51 @@ export function RepoCard({
3416
onSync,
3517
sizeThresholds = DEFAULT_SIZE_THRESHOLDS,
3618
}: RepoCardProps) {
37-
const [syncing, setSyncing] = useState(false);
38-
const [toggling, setToggling] = useState(false);
39-
const [deleteOpen, setDeleteOpen] = useState(false);
40-
const [settingsOpen, setSettingsOpen] = useState(false);
41-
42-
const isPaused = repo.status === 'paused';
43-
const sizeLevel = getSizeLevel(repo.syncSummary.totalStoreSize, sizeThresholds);
44-
const isBlocked = sizeLevel === 'blocked';
45-
46-
const handleSync = async (e: React.MouseEvent) => {
47-
e.preventDefault();
48-
setSyncing(true);
49-
try {
50-
await api.repos.sync(repo.id);
51-
onSync();
52-
} finally {
53-
setSyncing(false);
54-
}
55-
};
56-
57-
const handleTogglePause = async (e: React.MouseEvent) => {
58-
e.preventDefault();
59-
setToggling(true);
60-
try {
61-
const action = isPaused ? api.repos.resume : api.repos.pause;
62-
await action(repo.id);
63-
onSync();
64-
} finally {
65-
setToggling(false);
66-
}
67-
};
68-
6919
const handleToggleFavorite = async (e: React.MouseEvent) => {
7020
e.preventDefault();
7121
await api.repos.update(repo.id, { isFavorite: !repo.isFavorite });
7222
onSync();
7323
};
7424

75-
const handleDelete = async () => {
76-
await api.repos.delete(repo.id);
77-
onSync();
78-
};
79-
8025
return (
81-
<>
82-
<Link to={`/repos/${repo.id}`}>
83-
<Card className="transition-colors hover:bg-accent/50 cursor-pointer gap-2">
84-
<TooltipProvider delayDuration={300}>
85-
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
86-
<div className="flex items-center gap-2 min-w-0">
87-
<FolderGit2 className="h-4 w-4 text-muted-foreground shrink-0" />
88-
<CardTitle className="text-sm font-medium truncate">{repo.name}</CardTitle>
89-
</div>
90-
<div className="flex items-center gap-1.5">
91-
<button
92-
onClick={handleToggleFavorite}
93-
className="text-muted-foreground hover:text-yellow-500 transition-colors"
94-
>
95-
<Star
96-
className={`h-3.5 w-3.5 ${repo.isFavorite ? 'fill-yellow-500 text-yellow-500' : ''}`}
97-
/>
98-
</button>
99-
<SyncStatusBadge status={repo.status} />
100-
</div>
101-
</CardHeader>
102-
<CardContent className="pb-3">
103-
<p className="text-xs text-muted-foreground truncate font-mono">{repo.localPath}</p>
104-
<div className="mt-3 flex items-center gap-3 text-xs text-muted-foreground">
105-
<span className="flex items-center gap-1">
106-
<FileCode2 className="h-3 w-3" />
107-
{repo.syncSummary.total} files
108-
<span className="inline-flex items-center gap-0.5">
109-
· <SizeLabel bytes={repo.syncSummary.totalStoreSize} />
110-
</span>
111-
</span>
112-
{isBlocked && (
113-
<Tooltip>
114-
<TooltipTrigger asChild>
115-
<ShieldAlert className="h-3 w-3 text-destructive shrink-0" />
116-
</TooltipTrigger>
117-
<TooltipContent>
118-
Sync blocked: store exceeds {sizeThresholds.blockedMB} MB
119-
</TooltipContent>
120-
</Tooltip>
121-
)}
122-
{repo.syncSummary.conflicts > 0 && (
123-
<span className="text-destructive font-medium">
124-
{repo.syncSummary.conflicts} conflicts
125-
</span>
126-
)}
127-
{repo.syncSummary.pending > 0 && (
128-
<span className="text-yellow-600 font-medium">
129-
{repo.syncSummary.pending} pending
130-
</span>
131-
)}
132-
</div>
133-
</CardContent>
134-
<CardFooter className="flex items-center justify-between pt-0">
135-
<Tooltip>
136-
<TooltipTrigger asChild>
137-
<span className="text-[11px] text-muted-foreground cursor-default">
138-
{formatDate(repo.lastSyncedAt)}
139-
</span>
140-
</TooltipTrigger>
141-
<TooltipContent side="bottom">Last synced at</TooltipContent>
142-
</Tooltip>
143-
<div className="flex items-center gap-0.5">
144-
<Tooltip>
145-
<TooltipTrigger asChild>
146-
<Button
147-
size="icon-sm"
148-
variant="ghost"
149-
onClick={(e) => {
150-
e.preventDefault();
151-
setSettingsOpen(true);
152-
}}
153-
>
154-
<Settings2 className="h-3 w-3" />
155-
</Button>
156-
</TooltipTrigger>
157-
<TooltipContent>Settings</TooltipContent>
158-
</Tooltip>
159-
<Tooltip>
160-
<TooltipTrigger asChild>
161-
<Button
162-
size="icon-sm"
163-
variant="ghost"
164-
onClick={handleTogglePause}
165-
disabled={toggling}
166-
>
167-
{isPaused ? <Play className="h-3 w-3" /> : <Pause className="h-3 w-3" />}
168-
</Button>
169-
</TooltipTrigger>
170-
<TooltipContent>{isPaused ? 'Resume sync' : 'Pause sync'}</TooltipContent>
171-
</Tooltip>
172-
<Tooltip>
173-
<TooltipTrigger asChild>
174-
<Button
175-
size="icon-sm"
176-
variant="ghost"
177-
onClick={handleSync}
178-
disabled={syncing || isPaused || isBlocked}
179-
>
180-
<RefreshCw className={`h-3 w-3 ${syncing ? 'animate-spin' : ''}`} />
181-
</Button>
182-
</TooltipTrigger>
183-
<TooltipContent>
184-
{isBlocked
185-
? `Sync blocked: store exceeds ${sizeThresholds.blockedMB} MB`
186-
: 'Sync now'}
187-
</TooltipContent>
188-
</Tooltip>
189-
<Tooltip>
190-
<TooltipTrigger asChild>
191-
<Button
192-
size="icon-sm"
193-
variant="ghost"
194-
onClick={(e) => {
195-
e.preventDefault();
196-
setDeleteOpen(true);
197-
}}
198-
>
199-
<Trash2 className="h-3 w-3" />
200-
</Button>
201-
</TooltipTrigger>
202-
<TooltipContent>Remove</TooltipContent>
203-
</Tooltip>
204-
</div>
205-
</CardFooter>
206-
</TooltipProvider>
207-
</Card>
208-
</Link>
209-
<ConfirmDialog
210-
open={deleteOpen}
211-
onOpenChange={setDeleteOpen}
212-
onConfirm={handleDelete}
213-
title="Remove repository"
214-
description="Remove this repository from tracking? Store files will be kept."
215-
confirmLabel="Remove"
216-
variant="destructive"
217-
/>
218-
<RepoSettingsDialog
219-
open={settingsOpen}
220-
onOpenChange={setSettingsOpen}
221-
repoId={repo.id}
222-
repoName={repo.name}
223-
/>
224-
</>
26+
<SyncItemCard
27+
itemId={repo.id}
28+
itemName={repo.name}
29+
localPath={repo.localPath}
30+
status={repo.status}
31+
syncSummary={repo.syncSummary}
32+
lastSyncedAt={repo.lastSyncedAt}
33+
detailPath={`/repos/${repo.id}`}
34+
onSync={onSync}
35+
sizeThresholds={sizeThresholds}
36+
apiSync={api.repos.sync}
37+
apiPause={api.repos.pause}
38+
apiResume={api.repos.resume}
39+
apiDelete={api.repos.delete}
40+
deleteTitle="Remove repository"
41+
deleteDescription="Remove this repository from tracking? Store files will be kept."
42+
renderIcon={() => <FolderGit2 className="h-4 w-4 text-muted-foreground shrink-0" />}
43+
renderHeaderRight={(statusBadge) => (
44+
<div className="flex items-center gap-1.5">
45+
<button
46+
onClick={handleToggleFavorite}
47+
className="text-muted-foreground hover:text-yellow-500 transition-colors"
48+
>
49+
<Star
50+
className={`h-3.5 w-3.5 ${repo.isFavorite ? 'fill-yellow-500 text-yellow-500' : ''}`}
51+
/>
52+
</button>
53+
{statusBadge}
54+
</div>
55+
)}
56+
renderSettingsDialog={({ open, onOpenChange }) => (
57+
<RepoSettingsDialog
58+
open={open}
59+
onOpenChange={onOpenChange}
60+
repoId={repo.id}
61+
repoName={repo.name}
62+
/>
63+
)}
64+
/>
22565
);
22666
}

0 commit comments

Comments
 (0)