1+ import {
2+ ArrowForwardIosRounded ,
3+ ChevronRightRounded ,
4+ SettingsRounded
5+ } from '@mui/icons-material' ;
6+ import { Button , useTheme } from '@mui/material' ;
7+ import CircularProgress from '@mui/material/CircularProgress' ;
8+ import { useSnackbar } from 'notistack' ;
9+ import { FC , ReactNode , useEffect } from 'react' ;
10+
11+ import { FlexBox } from '@/components/FlexBox' ;
12+ import { Line } from '@/components/Text' ;
13+ import { track } from '@/constants/events' ;
14+ import { FetchState } from '@/constants/ui-states' ;
15+ import { bitBucketIntegrationDisplay } from '@/content/Dashboards/githubIntegration' ;
16+ import { useIntegrationHandlers } from '@/content/Dashboards/useIntegrationHandlers' ;
17+ import { useAuth } from '@/hooks/useAuth' ;
18+ import { useBoolState } from '@/hooks/useEasyState' ;
19+ import { fetchCurrentOrg } from '@/slices/auth' ;
20+ import { useDispatch , useSelector } from '@/store' ;
21+
22+ const cardRadius = 10.5 ;
23+ const cardBorder = 1.5 ;
24+ const getRadiusWithPadding = ( radius : number , padding : number ) =>
25+ `${ radius + padding } px` ;
26+
27+ export const BitbucketIntegrationCard = ( ) => {
28+ const theme = useTheme ( ) ;
29+ const { integrations } = useAuth ( ) ;
30+ const isBitbucketIntegrated = integrations . bitbucket ;
31+ const sliceLoading = useSelector (
32+ ( s : { auth : { requests : { org : FetchState ; } ; } ; } ) => s . auth . requests . org === FetchState . REQUEST
33+ ) ;
34+ const { link, unlink } = useIntegrationHandlers ( ) ;
35+
36+ const localLoading = useBoolState ( false ) ;
37+
38+ const isLoading = sliceLoading || localLoading . value ;
39+
40+ const dispatch = useDispatch ( ) ;
41+
42+ const { enqueueSnackbar } = useSnackbar ( ) ;
43+
44+ return (
45+ < FlexBox relative >
46+ { isBitbucketIntegrated && (
47+ < FlexBox
48+ title = "Linked"
49+ sx = { {
50+ position : 'absolute' ,
51+ right : '-6px' ,
52+ top : '-6px' ,
53+ zIndex : 2
54+ } }
55+ >
56+ < LinkedIcon />
57+ </ FlexBox >
58+ ) }
59+ < FlexBox
60+ p = { `${ cardBorder } px` }
61+ corner = { getRadiusWithPadding ( cardRadius , cardBorder ) }
62+ sx = { { background : bitBucketIntegrationDisplay . bg } }
63+ relative
64+ overflow = { 'unset' }
65+ >
66+ < FlexBox
67+ height = "120px"
68+ width = "280px"
69+ corner = { `${ cardRadius } px` }
70+ col
71+ p = { 1.5 }
72+ relative
73+ bgcolor = { theme . palette . background . default }
74+ >
75+ < FlexBox
76+ position = "absolute"
77+ fill
78+ top = { 0 }
79+ left = { 0 }
80+ sx = { { opacity : 0.2 , background : bitBucketIntegrationDisplay . bg } }
81+ />
82+ < FlexBox alignCenter gap1 fit >
83+ < FlexBox fit color = { bitBucketIntegrationDisplay . color } >
84+ { bitBucketIntegrationDisplay . icon }
85+ </ FlexBox >
86+ < Line big medium white >
87+ { bitBucketIntegrationDisplay . name }
88+ </ Line >
89+ </ FlexBox >
90+ < FlexBox alignCenter gap1 mt = "auto" >
91+ < IntegrationActionsButton
92+ onClick = { async ( ) => {
93+ track (
94+ isBitbucketIntegrated
95+ ? 'INTEGRATION_UNLINK_TRIGGERED'
96+ : 'INTEGRATION_LINK_TRIGGERED' ,
97+ { integration_name : bitBucketIntegrationDisplay . name }
98+ ) ;
99+ if ( ! isBitbucketIntegrated ) {
100+ link . bitbucket ( ) ;
101+ return ;
102+ }
103+ const shouldExecute = window . confirm (
104+ 'Are you sure you want to unlink?'
105+ ) ;
106+ if ( shouldExecute ) {
107+ localLoading . true ( ) ;
108+ await unlink
109+ . bitbucket ( )
110+ . then ( ( ) => {
111+ enqueueSnackbar ( 'Bitbucket unlinked successfully' , {
112+ variant : 'success'
113+ } ) ;
114+ } )
115+ . then ( async ( ) => dispatch ( fetchCurrentOrg ( ) ) )
116+ . catch ( ( e : any ) => {
117+ console . error ( 'Failed to unlink Bitbucket' , e ) ;
118+ enqueueSnackbar ( 'Failed to unlink Bitbucket' , {
119+ variant : 'error'
120+ } ) ;
121+ } )
122+ . finally ( localLoading . false ) ;
123+ }
124+ } }
125+ label = { ! isBitbucketIntegrated ? 'Link' : 'Unlink' }
126+ bgOpacity = { ! isBitbucketIntegrated ? 0.45 : 0.25 }
127+ endIcon = {
128+ isLoading ? (
129+ < CircularProgress
130+ size = { theme . spacing ( 1 ) }
131+ sx = { { ml : 1 / 2 } }
132+ />
133+ ) : (
134+ < ChevronRightRounded
135+ fontSize = "small"
136+ sx = { { ml : 1 / 2 , mr : - 2 / 3 } }
137+ />
138+ )
139+ }
140+ minWidth = "72px"
141+ />
142+ </ FlexBox >
143+ </ FlexBox >
144+ </ FlexBox >
145+ </ FlexBox >
146+ ) ;
147+ } ;
148+
149+ const IntegrationActionsButton : FC < {
150+ onClick : AnyFunction ;
151+ label : ReactNode ;
152+ bgOpacity ?: number ;
153+ startIcon ?: ReactNode ;
154+ endIcon ?: ReactNode ;
155+ minWidth ?: string ;
156+ } > = ( {
157+ label,
158+ onClick,
159+ bgOpacity = 0.45 ,
160+ endIcon = (
161+ < ArrowForwardIosRounded sx = { { fontSize : '0.9em' } } htmlColor = "white" />
162+ ) ,
163+ startIcon = < SettingsRounded sx = { { fontSize : '1em' } } htmlColor = "white" /> ,
164+ minWidth = '80px'
165+ } ) => {
166+ const theme = useTheme ( ) ;
167+
168+ return (
169+ < Button
170+ variant = "text"
171+ sx = { {
172+ p : '1px' ,
173+ minWidth : 0 ,
174+ background : bitBucketIntegrationDisplay . bg ,
175+ position : 'relative' ,
176+ borderRadius : getRadiusWithPadding ( 6 , 1 ) ,
177+ fontSize : '0.9em'
178+ } }
179+ onClick = { onClick }
180+ >
181+ < FlexBox
182+ position = "absolute"
183+ fill
184+ top = { 0 }
185+ left = { 0 }
186+ sx = { {
187+ opacity : bgOpacity ,
188+ background : bitBucketIntegrationDisplay . bg ,
189+ transition : 'all 0.2s' ,
190+ ':hover' : {
191+ opacity : bgOpacity * 0.6
192+ }
193+ } }
194+ corner = "6px"
195+ />
196+ < FlexBox
197+ bgcolor = { theme . palette . background . default }
198+ px = { 1 }
199+ py = { 1 / 4 }
200+ corner = "6px"
201+ color = "white"
202+ alignCenter
203+ gap = { 1 / 4 }
204+ minWidth = { minWidth }
205+ >
206+ { startIcon }
207+ < Line mr = "auto" > { label } </ Line >
208+ { endIcon }
209+ </ FlexBox >
210+ </ Button >
211+ ) ;
212+ } ;
213+
214+ const LinkedIcon = ( ) => {
215+ const isVisible = useBoolState ( false ) ;
216+ useEffect ( ( ) => {
217+ setTimeout ( isVisible . true , 200 ) ;
218+ } , [ isVisible . true ] ) ;
219+ return (
220+ < svg
221+ style = { {
222+ opacity : isVisible . value ? 1 : 0 ,
223+ transform : isVisible . value ? 'scale(1)' : 'scale(0)' ,
224+ transition : 'all 0.2s ease'
225+ } }
226+ width = "26"
227+ height = "26"
228+ viewBox = "0 0 26 26"
229+ fill = "none"
230+ xmlns = "http://www.w3.org/2000/svg"
231+ >
232+ < g clipPath = "url(#clip0_211_974)" >
233+ < path
234+ fillRule = "evenodd"
235+ clipRule = "evenodd"
236+ d = "M0 13C0 9.55219 1.36964 6.24558 3.80761 3.80761C6.24558 1.36964 9.55219 0 13 0C16.4478 0 19.7544 1.36964 22.1924 3.80761C24.6304 6.24558 26 9.55219 26 13C26 16.4478 24.6304 19.7544 22.1924 22.1924C19.7544 24.6304 16.4478 26 13 26C9.55219 26 6.24558 24.6304 3.80761 22.1924C1.36964 19.7544 0 16.4478 0 13ZM12.2581 18.564L19.7427 9.20747L18.3907 8.12587L12.0085 16.1009L7.488 12.3344L6.37867 13.6656L12.2581 18.564Z"
237+ fill = "#14AE5C"
238+ />
239+ </ g >
240+ < defs >
241+ < clipPath id = "clip0_211_974" >
242+ < rect width = "26" height = "26" fill = "white" />
243+ </ clipPath >
244+ </ defs >
245+ </ svg >
246+ ) ;
247+ } ;
248+
0 commit comments