All files / features / repositories RepositoryDetailPage.tsx

26.41% Statements 14/53
13.88% Branches 5/36
16.66% Functions 3/18
27.45% Lines 14/51

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>
  )
}