Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | 69x 69x | import { useState, useRef } from 'react'
import { useParams, useSearchParams, useNavigate } from 'react-router-dom'
import { Page } from '@/shared/components/Page'
import { CrudReadCard } from '@/shared/components/CrudReadCard'
import { ConfirmDialog } from '@/shared/components/ConfirmDialog'
import { CrudModal } from '@/shared/components/CrudModal'
import { formatDateTime, formatRelativeTime } from '@/shared/utils/format'
import { useRepository, useDeleteRepository } from './repositoriesApi'
import { RepositoryForm, type CrudFormRef } from './components/RepositoryForm'
import { toast } from 'sonner'
export function RepositoryDetailPage() {
const { id } = useParams<{ id: string }>()
const [searchParams, setSearchParams] = useSearchParams()
const nav = useNavigate()
const { data: repo, isLoading, error, refetch } = useRepository(id ?? '')
const deleteRepo = useDeleteRepository()
const [showDelete, setShowDelete] = useState(false)
const [formDirty, setFormDirty] = useState(false)
const [formPending, setFormPending] = useState(false)
const formRef = useRef<CrudFormRef>(null)
const is404 = (error as any)?.response?.status === 404
const showEdit = searchParams.get('edit') === 'true'
function removeEditParam() {
setSearchParams((prev) => {
const n = new URLSearchParams(prev)
n.delete('edit')
return n
})
}
return (
<Page>
<CrudReadCard
title={repo?.name}
breadcrumbs={[
{ label: 'Dashboard', href: '/' },
{ label: 'Repositories', href: '/repositories' },
{ label: repo?.name ?? '...' },
]}
fields={[
{ label: 'Name', value: repo?.name, spanFull: true },
{ label: 'Description', value: repo?.description || '\u2014', spanFull: true },
{
label: 'Project',
render: () => repo?.project_id
? <a href={`#/projects/${repo.project_id}`} className="text-primary underline-offset-4 hover:underline">{repo.project_name ?? repo.project_id}</a>
: '\u2014',
},
{ label: 'URL', render: () => <span className="font-mono text-sm">{repo?.url}</span> },
{
label: 'Status',
value: repo?.status === 'connected'
? '\uD83D\uDFE2 Connected'
: repo?.status === 'disconnected'
? '\uD83D\uDD34 Disconnected'
: '\u26AA Unknown',
},
{ label: 'Last Checked', value: repo?.last_checked_at ? formatRelativeTime(repo.last_checked_at) : 'Never' },
{ label: 'Created', value: formatDateTime(repo?.created_at) },
{ label: 'Updated', value: formatDateTime(repo?.updated_at) },
]}
actions={{
edit: { onClick: () => setSearchParams({ edit: 'true' }) },
delete: {
onClick: () => setShowDelete(true),
entityName: repo?.name ?? 'this repository',
},
}}
isLoading={isLoading}
error={is404 ? null : error}
is404={is404}
onRetry={refetch}
backPath="/repositories"
/>
<ConfirmDialog
open={showDelete}
title="Delete repository"
message={`Are you sure you want to delete '${repo?.name}'?`}
confirmLabel="Delete"
onConfirm={() => deleteRepo.mutate(id ?? '', {
onSuccess: () => nav('/repositories', { replace: true }),
onError: (err) => {
if ((err as any)?.response?.status === 409) {
toast.error('Cannot delete: repository is in use')
}
},
})}
onCancel={() => setShowDelete(false)}
/>
<CrudModal
open={showEdit}
title="Edit Repository"
isDirty={formDirty}
isPending={formPending}
onClose={removeEditParam}
onSave={async () => {
setFormPending(true)
try {
await formRef.current?.submit()
} finally {
setFormPending(false)
}
}}
>
<RepositoryForm
ref={formRef}
id={id}
defaults={{ name: repo?.name, url: repo?.url, description: repo?.description }}
onSuccess={() => { removeEditParam(); refetch() }}
onClose={removeEditParam}
onDirtyChange={setFormDirty}
onPendingChange={setFormPending}
/>
</CrudModal>
</Page>
)
}
|