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' ;
63import { 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' ;
214import { 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' ;
246import type { RepoSummary } from '@/hooks/use-repos' ;
257
268interface 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