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