BranchPanels.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import dayjs from 'dayjs'
  2. import { Github } from 'lucide-react'
  3. import Link from 'next/link'
  4. import { useRouter } from 'next/router'
  5. import { PropsWithChildren, ReactNode } from 'react'
  6. import { Tooltip, TooltipContent, TooltipTrigger } from 'ui'
  7. import { TimestampInfo } from 'ui-patterns'
  8. import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
  9. import { WorkflowLogs } from './WorkflowLogs'
  10. import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
  11. import type { Branch } from '@/data/branches/branches-query'
  12. interface BranchManagementSectionProps {
  13. header: string | ReactNode
  14. footer?: ReactNode
  15. }
  16. export const BranchManagementSection = ({
  17. header,
  18. footer,
  19. children,
  20. }: PropsWithChildren<BranchManagementSectionProps>) => {
  21. return (
  22. <div className="border rounded-lg overflow-hidden">
  23. <div className="bg-surface-100 shadow-xs flex justify-between items-center px-4 py-3 rounded-t-lg text-xs font-mono uppercase">
  24. {typeof header === 'string' ? <span>{header}</span> : header}
  25. </div>
  26. <div className="bg-surface border-t shadow-xs rounded-b-lg text-sm divide-y">{children}</div>
  27. {footer !== undefined && <div className="bg-surface-100 px-6 py-1 border-t">{footer}</div>}
  28. </div>
  29. )
  30. }
  31. export const BranchRowLoader = () => {
  32. return (
  33. <div className="flex items-center justify-between px-6 py-2.5">
  34. <div className="flex items-center gap-x-4">
  35. <ShimmeringLoader className="w-52" />
  36. <ShimmeringLoader className="w-52" />
  37. </div>
  38. <div className="flex items-center gap-x-4">
  39. <ShimmeringLoader className="w-52" />
  40. <ShimmeringLoader className="w-52" />
  41. </div>
  42. </div>
  43. )
  44. }
  45. export const BranchLoader = () => {
  46. return (
  47. <>
  48. <BranchRowLoader />
  49. <BranchRowLoader />
  50. <BranchRowLoader />
  51. <BranchRowLoader />
  52. <BranchRowLoader />
  53. </>
  54. )
  55. }
  56. interface BranchRowProps {
  57. repo: string
  58. label?: string | ReactNode
  59. branch: Branch
  60. isGithubConnected: boolean
  61. rowLink?: string
  62. external?: boolean
  63. rowActions?: ReactNode
  64. }
  65. export const BranchRow = ({
  66. branch,
  67. isGithubConnected,
  68. label,
  69. repo,
  70. rowLink,
  71. external = false,
  72. rowActions,
  73. }: BranchRowProps) => {
  74. const router = useRouter()
  75. const page = router.pathname.split('/').pop()
  76. const daysFromNow = dayjs().diff(dayjs(branch.updated_at), 'day')
  77. const willBeDeletedIn = branch.deletion_scheduled_at
  78. ? dayjs(branch.deletion_scheduled_at).diff(dayjs(), 'minutes')
  79. : null
  80. const isDeletionPending = willBeDeletedIn !== null && willBeDeletedIn < 0
  81. const formattedTimeFromNow = dayjs(branch.updated_at).fromNow()
  82. const navigateUrl = rowLink ?? `/project/${branch.project_ref}`
  83. return (
  84. <div className="w-full flex items-center justify-between px-4 py-2.5 hover:bg-surface-100">
  85. <div className="flex items-center gap-x-3">
  86. {branch.git_branch && isGithubConnected && (
  87. <ButtonTooltip
  88. asChild
  89. type="default"
  90. className="px-1.5"
  91. tooltip={{ content: { side: 'bottom', text: 'View branch on GitHub' } }}
  92. >
  93. <a
  94. target="_blank"
  95. rel="noreferrer noopener"
  96. href={`https://github.com/${repo}/tree/${branch.git_branch}`}
  97. >
  98. <Github size={14} className="text-foreground-light" />
  99. </a>
  100. </ButtonTooltip>
  101. )}
  102. <Tooltip>
  103. <TooltipTrigger>
  104. <Link
  105. target={external ? '_blank' : '_self'}
  106. rel={external ? 'noopener noreferrer' : undefined}
  107. href={navigateUrl}
  108. className="flex items-center"
  109. >
  110. {label || branch.name}
  111. </Link>
  112. </TooltipTrigger>
  113. {((page === 'branches' && !branch.is_default) || page === 'merge-requests') && (
  114. <TooltipContent side="bottom">
  115. {page === 'branches' && !branch.is_default && 'Switch to branch'}
  116. {page === 'merge-requests' && 'View merge request'}
  117. </TooltipContent>
  118. )}
  119. </Tooltip>
  120. </div>
  121. <div className="flex items-center gap-x-4">
  122. {branch.deletion_scheduled_at ? (
  123. <p className="text-xs text-foreground-lighter">
  124. {isDeletionPending
  125. ? 'Deletion pending...'
  126. : `Will be deleted in ${willBeDeletedIn} minutes`}
  127. </p>
  128. ) : (
  129. <p className="text-xs text-foreground-lighter">
  130. {daysFromNow > 1 ? 'Updated on' : 'Updated'}{' '}
  131. <TimestampInfo
  132. utcTimestamp={branch.updated_at}
  133. label={daysFromNow <= 1 ? formattedTimeFromNow : undefined}
  134. />
  135. </p>
  136. )}
  137. <WorkflowLogs branch={branch} />
  138. {rowActions}
  139. </div>
  140. </div>
  141. )
  142. }