mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	chore: adapt integrations layout for incoming webhooks (#5828)
https://linear.app/unleash/issue/2-1823/adapt-integrations-page-to-incoming-webhooks-tab-layout Adapts the current integrations page to the incoming webhooks feature, which includes things like: - Displaying both configured and available integrations in a single "page block" - Implement tabs - Add "Incoming Webhooks" integration card - Adapt the existing `IntegrationCard` component to support `onClick` This also includes a small girl scouting fix: Some tabs (like on the roles page) did not correctly reflect the active tab. ### `incomingWebhooks` disabled  ### `incomingWebhooks` enabled Notice the new "Incoming webhooks" tab and integration card. 
This commit is contained in:
		
							parent
							
								
									336eab9c5a
								
							
						
					
					
						commit
						10c3acd27d
					
				| @ -15,6 +15,7 @@ import { PageHeader } from 'component/common/PageHeader/PageHeader'; | ||||
| import { Add } from '@mui/icons-material'; | ||||
| import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton'; | ||||
| import { IRole } from 'interfaces/role'; | ||||
| import { TabLink } from 'component/common/TabNav/TabLink'; | ||||
| 
 | ||||
| const StyledHeader = styled('div')(() => ({ | ||||
|     display: 'flex', | ||||
| @ -91,11 +92,9 @@ export const RolesPage = () => { | ||||
|                                         key={label} | ||||
|                                         value={path} | ||||
|                                         label={ | ||||
|                                             <CenteredNavLink to={path}> | ||||
|                                                 <span> | ||||
|                                                     {label} ({total}) | ||||
|                                                 </span> | ||||
|                                             </CenteredNavLink> | ||||
|                                             <TabLink to={path}> | ||||
|                                                 {label} ({total}) | ||||
|                                             </TabLink> | ||||
|                                         } | ||||
|                                         sx={{ padding: 0 }} | ||||
|                                     /> | ||||
|  | ||||
							
								
								
									
										25
									
								
								frontend/src/component/common/TabNav/TabLink.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								frontend/src/component/common/TabNav/TabLink.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| import { styled } from '@mui/material'; | ||||
| import { FC } from 'react'; | ||||
| import { Link } from 'react-router-dom'; | ||||
| 
 | ||||
| const StyledTabLink = styled(Link)(({ theme }) => ({ | ||||
|     display: 'flex', | ||||
|     justifyContent: 'center', | ||||
|     alignItems: 'center', | ||||
|     width: '100%', | ||||
|     height: '100%', | ||||
|     textDecoration: 'none', | ||||
|     color: 'inherit', | ||||
|     padding: theme.spacing(0, 5), | ||||
|     '&.active': { | ||||
|         fontWeight: 'bold', | ||||
|     }, | ||||
| })); | ||||
| 
 | ||||
| interface ICenteredTabLinkProps { | ||||
|     to: string; | ||||
| } | ||||
| 
 | ||||
| export const TabLink: FC<ICenteredTabLinkProps> = ({ to, children }) => ( | ||||
|     <StyledTabLink to={to}>{children}</StyledTabLink> | ||||
| ); | ||||
| @ -1,17 +1,17 @@ | ||||
| import { type VFC } from 'react'; | ||||
| import { Box, Typography, styled } from '@mui/material'; | ||||
| import type { AddonTypeSchema } from 'openapi'; | ||||
| import { PageContent } from 'component/common/PageContent/PageContent'; | ||||
| import { PageHeader } from 'component/common/PageHeader/PageHeader'; | ||||
| import { IntegrationCard } from '../IntegrationCard/IntegrationCard'; | ||||
| import { JIRA_INFO } from '../../ViewIntegration/JiraIntegration/JiraIntegration'; | ||||
| import { StyledCardsGrid } from '../IntegrationList.styles'; | ||||
| import { RequestIntegrationCard } from '../RequestIntegrationCard/RequestIntegrationCard'; | ||||
| import { OFFICIAL_SDKS } from './SDKs'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { useUiFlag } from 'hooks/useUiFlag'; | ||||
| 
 | ||||
| interface IAvailableIntegrationsProps { | ||||
|     providers: AddonTypeSchema[]; | ||||
|     loading?: boolean; | ||||
|     onNewIncomingWebhook: () => void; | ||||
| } | ||||
| 
 | ||||
| const StyledContainer = styled('div')(({ theme }) => ({ | ||||
| @ -53,219 +53,228 @@ const StyledGrayContainer = styled('div')(({ theme }) => ({ | ||||
| 
 | ||||
| export const AvailableIntegrations: VFC<IAvailableIntegrationsProps> = ({ | ||||
|     providers, | ||||
|     loading, | ||||
|     onNewIncomingWebhook, | ||||
| }) => { | ||||
|     const incomingWebhooksEnabled = useUiFlag('incomingWebhooks'); | ||||
| 
 | ||||
|     const customProviders = [JIRA_INFO]; | ||||
|     const serverSdks = OFFICIAL_SDKS.filter((sdk) => sdk.type === 'server'); | ||||
|     const clientSdks = OFFICIAL_SDKS.filter((sdk) => sdk.type === 'client'); | ||||
| 
 | ||||
|     return ( | ||||
|         <PageContent | ||||
|             header={<PageHeader title='Integrations' secondary />} | ||||
|             isLoading={loading} | ||||
|         > | ||||
|             <StyledContainer> | ||||
|                 <StyledSection> | ||||
|                     <StyledCardsGrid> | ||||
|                         {providers | ||||
|                             ?.sort( | ||||
|                                 (a, b) => | ||||
|                                     a.displayName?.localeCompare( | ||||
|                                         b.displayName, | ||||
|                                     ) || 0, | ||||
|                             ) | ||||
|                             .map( | ||||
|         <StyledContainer> | ||||
|             <StyledSection> | ||||
|                 <div> | ||||
|                     <Typography component='h3' variant='h2'> | ||||
|                         Unleash crafted | ||||
|                     </Typography> | ||||
|                     <Typography variant='body2' color='text.secondary'> | ||||
|                         Unleash is built to be extended. We have crafted | ||||
|                         integrations to make it easier for you to get started. | ||||
|                     </Typography> | ||||
|                 </div> | ||||
|                 <StyledCardsGrid> | ||||
|                     {providers | ||||
|                         ?.sort( | ||||
|                             (a, b) => | ||||
|                                 a.displayName?.localeCompare(b.displayName) || | ||||
|                                 0, | ||||
|                         ) | ||||
|                         .map( | ||||
|                             ({ | ||||
|                                 name, | ||||
|                                 displayName, | ||||
|                                 description, | ||||
|                                 deprecated, | ||||
|                             }) => ( | ||||
|                                 <IntegrationCard | ||||
|                                     key={name} | ||||
|                                     icon={name} | ||||
|                                     title={displayName || name} | ||||
|                                     description={description} | ||||
|                                     link={`/integrations/create/${name}`} | ||||
|                                     deprecated={deprecated} | ||||
|                                 /> | ||||
|                             ), | ||||
|                         )} | ||||
|                     <ConditionallyRender | ||||
|                         condition={incomingWebhooksEnabled} | ||||
|                         show={ | ||||
|                             <IntegrationCard | ||||
|                                 icon='webhook' | ||||
|                                 title='Incoming Webhooks' | ||||
|                                 description='Incoming Webhooks allow third party services to send observable events to Unleash.' | ||||
|                                 onClick={onNewIncomingWebhook} | ||||
|                             /> | ||||
|                         } | ||||
|                     /> | ||||
|                     {/* TODO: sort providers from backend with custom providers */} | ||||
|                     {customProviders?.map( | ||||
|                         ({ name, displayName, description }) => ( | ||||
|                             <IntegrationCard | ||||
|                                 key={name} | ||||
|                                 icon={name} | ||||
|                                 title={displayName || name} | ||||
|                                 description={description} | ||||
|                                 link={`/integrations/view/${name}`} | ||||
|                                 configureActionText='Learn more' | ||||
|                             /> | ||||
|                         ), | ||||
|                     )} | ||||
|                     <RequestIntegrationCard /> | ||||
|                 </StyledCardsGrid> | ||||
|             </StyledSection> | ||||
|             <StyledSection> | ||||
|                 <div> | ||||
|                     <Typography component='h3' variant='h2'> | ||||
|                         Performance and security | ||||
|                     </Typography> | ||||
|                     <Typography variant='body2' color='text.secondary'> | ||||
|                         Connect Unleash to private, scalable, and distributed | ||||
|                         relays. | ||||
|                     </Typography> | ||||
|                 </div> | ||||
|                 <StyledCardsGrid> | ||||
|                     <IntegrationCard | ||||
|                         icon='unleash' | ||||
|                         title='Unleash Edge' | ||||
|                         description="Unleash Edge is built to help you scale Unleash. As a successor of Unleash Proxy it's even faster and more versatile." | ||||
|                         link='/integrations/view/edge' | ||||
|                         configureActionText='Learn more' | ||||
|                     /> | ||||
|                     <IntegrationCard | ||||
|                         icon='unleash' | ||||
|                         title='Unleash Proxy' | ||||
|                         description='The Unleash Proxy is a lightweight, stateless proxy that sits between your Unleash client SDKs and the Unleash API.' | ||||
|                         link='https://docs.getunleash.io/reference/unleash-proxy' | ||||
|                         configureActionText='View documentation' | ||||
|                         deprecated='Try Unleash Edge instead. It has all the features of Unleash Proxy and more.' | ||||
|                         isExternal | ||||
|                     /> | ||||
|                 </StyledCardsGrid> | ||||
|             </StyledSection> | ||||
|             <StyledSection> | ||||
|                 <div> | ||||
|                     <Typography component='h3' variant='h2'> | ||||
|                         Official SDKs | ||||
|                     </Typography> | ||||
|                     <Typography variant='body2' color='text.secondary'> | ||||
|                         In order to connect your application to Unleash you will | ||||
|                         need a client SDK (software developer kit) for your | ||||
|                         programming language and an{' '} | ||||
|                         <a | ||||
|                             href='https://docs.getunleash.io/how-to/how-to-create-api-tokens' | ||||
|                             target='_blank' | ||||
|                             rel='noopener noreferrer' | ||||
|                         > | ||||
|                             API token | ||||
|                         </a> | ||||
|                     </Typography> | ||||
|                 </div> | ||||
|                 <StyledSdksSection> | ||||
|                     <StyledSdksGroup> | ||||
|                         <Box> | ||||
|                             <Typography component='h4' variant='h4'> | ||||
|                                 Server-side SDKs | ||||
|                             </Typography> | ||||
|                             <Typography variant='body2' color='text.secondary'> | ||||
|                                 Server-side clients run on your server and | ||||
|                                 communicate directly with your Unleash instance. | ||||
|                             </Typography> | ||||
|                         </Box> | ||||
|                         <StyledCardsGrid small> | ||||
|                             {serverSdks?.map( | ||||
|                                 ({ | ||||
|                                     name, | ||||
|                                     displayName, | ||||
|                                     description, | ||||
|                                     deprecated, | ||||
|                                     documentationUrl, | ||||
|                                 }) => ( | ||||
|                                     <IntegrationCard | ||||
|                                         key={name} | ||||
|                                         icon={name} | ||||
|                                         title={displayName || name} | ||||
|                                         description={description} | ||||
|                                         link={`/integrations/create/${name}`} | ||||
|                                         deprecated={deprecated} | ||||
|                                         link={documentationUrl} | ||||
|                                         configureActionText={ | ||||
|                                             'View documentation' | ||||
|                                         } | ||||
|                                         isExternal | ||||
|                                     /> | ||||
|                                 ), | ||||
|                             )} | ||||
|                         {/* TODO: sort providers from backend with custom providers */} | ||||
|                         {customProviders?.map( | ||||
|                             ({ name, displayName, description }) => ( | ||||
|                                 <IntegrationCard | ||||
|                                     key={name} | ||||
|                                     icon={name} | ||||
|                                     title={displayName || name} | ||||
|                                     description={description} | ||||
|                                     link={`/integrations/view/${name}`} | ||||
|                                     configureActionText='Learn more' | ||||
|                                 /> | ||||
|                             ), | ||||
|                         )} | ||||
|                         <RequestIntegrationCard /> | ||||
|                     </StyledCardsGrid> | ||||
|                 </StyledSection> | ||||
|                 <StyledSection> | ||||
|                     <div> | ||||
|                         <Typography component='h3' variant='h2'> | ||||
|                             Performance and security | ||||
|                         </Typography> | ||||
|                         <Typography variant='body2' color='text.secondary'> | ||||
|                             Connect Unleash to private, scalable, and | ||||
|                             distributed relays. | ||||
|                         </Typography> | ||||
|                     </div> | ||||
|                     <StyledCardsGrid> | ||||
|                         <IntegrationCard | ||||
|                             icon='unleash' | ||||
|                             title='Unleash Edge' | ||||
|                             description="Unleash Edge is built to help you scale Unleash. As a successor of Unleash Proxy it's even faster and more versatile." | ||||
|                             link='/integrations/view/edge' | ||||
|                             configureActionText='Learn more' | ||||
|                         /> | ||||
|                         <IntegrationCard | ||||
|                             icon='unleash' | ||||
|                             title='Unleash Proxy' | ||||
|                             description='The Unleash Proxy is a lightweight, stateless proxy that sits between your Unleash client SDKs and the Unleash API.' | ||||
|                             link='https://docs.getunleash.io/reference/unleash-proxy' | ||||
|                             configureActionText='View documentation' | ||||
|                             deprecated='Try Unleash Edge instead. It has all the features of Unleash Proxy and more.' | ||||
|                             isExternal | ||||
|                         /> | ||||
|                     </StyledCardsGrid> | ||||
|                 </StyledSection> | ||||
|                 <StyledSection> | ||||
|                     <div> | ||||
|                         <Typography component='h3' variant='h2'> | ||||
|                             Official SDKs | ||||
|                         </Typography> | ||||
|                         <Typography variant='body2' color='text.secondary'> | ||||
|                             In order to connect your application to Unleash you | ||||
|                             will need a client SDK (software developer kit) for | ||||
|                             your programming language and an{' '} | ||||
|                             <a | ||||
|                                 href='https://docs.getunleash.io/how-to/how-to-create-api-tokens' | ||||
|                                 target='_blank' | ||||
|                                 rel='noopener noreferrer' | ||||
|                             > | ||||
|                                 API token | ||||
|                             </a> | ||||
|                         </Typography> | ||||
|                     </div> | ||||
|                     <StyledSdksSection> | ||||
|                         <StyledSdksGroup> | ||||
|                             <Box> | ||||
|                                 <Typography component='h4' variant='h4'> | ||||
|                                     Server-side SDKs | ||||
|                                 </Typography> | ||||
|                                 <Typography | ||||
|                                     variant='body2' | ||||
|                                     color='text.secondary' | ||||
|                         </StyledCardsGrid> | ||||
|                     </StyledSdksGroup> | ||||
|                     <StyledSdksGroup> | ||||
|                         <Box> | ||||
|                             <Typography component='h4' variant='h4'> | ||||
|                                 Client-side SDKs | ||||
|                             </Typography> | ||||
|                             <Typography variant='body2' color='text.secondary'> | ||||
|                                 Client-side SDKs can connect to the{' '} | ||||
|                                 <a | ||||
|                                     href='https://docs.getunleash.io/reference/unleash-edge' | ||||
|                                     target='_blank' | ||||
|                                     rel='noopener noreferrer' | ||||
|                                 > | ||||
|                                     Server-side clients run on your server and | ||||
|                                     communicate directly with your Unleash | ||||
|                                     instance. | ||||
|                                 </Typography> | ||||
|                             </Box> | ||||
|                             <StyledCardsGrid small> | ||||
|                                 {serverSdks?.map( | ||||
|                                     ({ | ||||
|                                         name, | ||||
|                                         displayName, | ||||
|                                         description, | ||||
|                                         documentationUrl, | ||||
|                                     }) => ( | ||||
|                                         <IntegrationCard | ||||
|                                             key={name} | ||||
|                                             icon={name} | ||||
|                                             title={displayName || name} | ||||
|                                             description={description} | ||||
|                                             link={documentationUrl} | ||||
|                                             configureActionText={ | ||||
|                                                 'View documentation' | ||||
|                                             } | ||||
|                                             isExternal | ||||
|                                         /> | ||||
|                                     ), | ||||
|                                 )} | ||||
|                             </StyledCardsGrid> | ||||
|                         </StyledSdksGroup> | ||||
|                         <StyledSdksGroup> | ||||
|                             <Box> | ||||
|                                 <Typography component='h4' variant='h4'> | ||||
|                                     Client-side SDKs | ||||
|                                 </Typography> | ||||
|                                 <Typography | ||||
|                                     variant='body2' | ||||
|                                     color='text.secondary' | ||||
|                                     Unleash Edge | ||||
|                                 </a>{' '} | ||||
|                                 or to the{' '} | ||||
|                                 <a | ||||
|                                     href='https://docs.getunleash.io/reference/front-end-api' | ||||
|                                     target='_blank' | ||||
|                                     rel='noopener noreferrer' | ||||
|                                 > | ||||
|                                     Client-side SDKs can connect to the{' '} | ||||
|                                     Unleash front-end API | ||||
|                                 </a> | ||||
|                                 , but not to the regular Unleash client API. | ||||
|                             </Typography> | ||||
|                         </Box> | ||||
|                         <StyledCardsGrid small> | ||||
|                             {clientSdks?.map( | ||||
|                                 ({ | ||||
|                                     name, | ||||
|                                     displayName, | ||||
|                                     description, | ||||
|                                     documentationUrl, | ||||
|                                 }) => ( | ||||
|                                     <IntegrationCard | ||||
|                                         key={name} | ||||
|                                         icon={name} | ||||
|                                         title={displayName || name} | ||||
|                                         description={description} | ||||
|                                         link={documentationUrl} | ||||
|                                         configureActionText={ | ||||
|                                             'View documentation' | ||||
|                                         } | ||||
|                                         isExternal | ||||
|                                     /> | ||||
|                                 ), | ||||
|                             )} | ||||
|                         </StyledCardsGrid> | ||||
|                     </StyledSdksGroup> | ||||
|                     <StyledSdksGroup> | ||||
|                         <StyledGrayContainer> | ||||
|                             <div> | ||||
|                                 <Typography component='h4' variant='h3'> | ||||
|                                     Community SDKs | ||||
|                                 </Typography> | ||||
|                                 <Typography> | ||||
|                                     <a | ||||
|                                         href='https://docs.getunleash.io/reference/unleash-edge' | ||||
|                                         href='https://docs.getunleash.io/reference/sdks#community-sdks' | ||||
|                                         target='_blank' | ||||
|                                         rel='noopener noreferrer' | ||||
|                                     > | ||||
|                                         Unleash Edge | ||||
|                                         Here's some of the fantastic work | ||||
|                                     </a>{' '} | ||||
|                                     or to the{' '} | ||||
|                                     <a | ||||
|                                         href='https://docs.getunleash.io/reference/front-end-api' | ||||
|                                         target='_blank' | ||||
|                                         rel='noopener noreferrer' | ||||
|                                     > | ||||
|                                         Unleash front-end API | ||||
|                                     </a> | ||||
|                                     , but not to the regular Unleash client API. | ||||
|                                     our community has built to make Unleash work | ||||
|                                     in even more contexts. | ||||
|                                 </Typography> | ||||
|                             </Box> | ||||
|                             <StyledCardsGrid small> | ||||
|                                 {clientSdks?.map( | ||||
|                                     ({ | ||||
|                                         name, | ||||
|                                         displayName, | ||||
|                                         description, | ||||
|                                         documentationUrl, | ||||
|                                     }) => ( | ||||
|                                         <IntegrationCard | ||||
|                                             key={name} | ||||
|                                             icon={name} | ||||
|                                             title={displayName || name} | ||||
|                                             description={description} | ||||
|                                             link={documentationUrl} | ||||
|                                             configureActionText={ | ||||
|                                                 'View documentation' | ||||
|                                             } | ||||
|                                             isExternal | ||||
|                                         /> | ||||
|                                     ), | ||||
|                                 )} | ||||
|                             </StyledCardsGrid> | ||||
|                         </StyledSdksGroup> | ||||
|                         <StyledSdksGroup> | ||||
|                             <StyledGrayContainer> | ||||
|                                 <div> | ||||
|                                     <Typography component='h4' variant='h3'> | ||||
|                                         Community SDKs | ||||
|                                     </Typography> | ||||
|                                     <Typography> | ||||
|                                         <a | ||||
|                                             href='https://docs.getunleash.io/reference/sdks#community-sdks' | ||||
|                                             target='_blank' | ||||
|                                             rel='noopener noreferrer' | ||||
|                                         > | ||||
|                                             Here's some of the fantastic work | ||||
|                                         </a>{' '} | ||||
|                                         our community has built to make Unleash | ||||
|                                         work in even more contexts. | ||||
|                                     </Typography> | ||||
|                                 </div> | ||||
|                             </StyledGrayContainer> | ||||
|                         </StyledSdksGroup> | ||||
|                     </StyledSdksSection> | ||||
|                 </StyledSection> | ||||
|             </StyledContainer> | ||||
|         </PageContent> | ||||
|                             </div> | ||||
|                         </StyledGrayContainer> | ||||
|                     </StyledSdksGroup> | ||||
|                 </StyledSdksSection> | ||||
|             </StyledSection> | ||||
|         </StyledContainer> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -1,10 +1,16 @@ | ||||
| import { AddonSchema, AddonTypeSchema } from 'openapi'; | ||||
| import useLoading from 'hooks/useLoading'; | ||||
| import { PageContent } from 'component/common/PageContent/PageContent'; | ||||
| import { PageHeader } from 'component/common/PageHeader/PageHeader'; | ||||
| import { StyledCardsGrid } from '../IntegrationList.styles'; | ||||
| import { IntegrationCard } from '../IntegrationCard/IntegrationCard'; | ||||
| import { VFC } from 'react'; | ||||
| import { Typography, styled } from '@mui/material'; | ||||
| 
 | ||||
| const StyledConfiguredSection = styled('section')(({ theme }) => ({ | ||||
|     display: 'flex', | ||||
|     flexDirection: 'column', | ||||
|     gap: theme.spacing(2), | ||||
|     marginBottom: theme.spacing(8), | ||||
| })); | ||||
| 
 | ||||
| type ConfiguredIntegrationsProps = { | ||||
|     loading: boolean; | ||||
| @ -17,15 +23,19 @@ export const ConfiguredIntegrations: VFC<ConfiguredIntegrationsProps> = ({ | ||||
|     addons, | ||||
|     providers, | ||||
| }) => { | ||||
|     const counter = addons.length ? `(${addons.length})` : ''; | ||||
|     const ref = useLoading(loading || false); | ||||
| 
 | ||||
|     return ( | ||||
|         <PageContent | ||||
|             header={<PageHeader title={`Configured integrations ${counter}`} />} | ||||
|             sx={(theme) => ({ marginBottom: theme.spacing(2) })} | ||||
|             isLoading={loading} | ||||
|         > | ||||
|         <StyledConfiguredSection> | ||||
|             <div> | ||||
|                 <Typography component='h3' variant='h2'> | ||||
|                     Configured integrations | ||||
|                 </Typography> | ||||
|                 <Typography variant='body2' color='text.secondary'> | ||||
|                     These are the integrations that are currently configured for | ||||
|                     your Unleash instance. | ||||
|                 </Typography> | ||||
|             </div> | ||||
|             <StyledCardsGrid ref={ref}> | ||||
|                 {addons | ||||
|                     ?.sort(({ id: a }, { id: b }) => a - b) | ||||
| @ -56,6 +66,6 @@ export const ConfiguredIntegrations: VFC<ConfiguredIntegrationsProps> = ({ | ||||
|                         ); | ||||
|                     })} | ||||
|             </StyledCardsGrid> | ||||
|         </PageContent> | ||||
|         </StyledConfiguredSection> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import { VFC } from 'react'; | ||||
| import { Link } from 'react-router-dom'; | ||||
| import { styled, Tooltip, Typography } from '@mui/material'; | ||||
| import { Link as RouterLink } from 'react-router-dom'; | ||||
| import { Link, styled, Tooltip, Typography } from '@mui/material'; | ||||
| import { IntegrationIcon } from '../IntegrationIcon/IntegrationIcon'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import ChevronRightIcon from '@mui/icons-material/ChevronRight'; | ||||
| @ -10,46 +10,56 @@ import type { AddonSchema } from 'openapi'; | ||||
| import OpenInNewIcon from '@mui/icons-material/OpenInNew'; | ||||
| import { usePlausibleTracker } from 'hooks/usePlausibleTracker'; | ||||
| 
 | ||||
| interface IIntegrationCardProps { | ||||
| interface IIntegrationCardBaseProps { | ||||
|     id?: string | number; | ||||
|     icon?: string; | ||||
|     title: string; | ||||
|     description?: string; | ||||
|     isEnabled?: boolean; | ||||
|     configureActionText?: string; | ||||
|     link: string; | ||||
|     isExternal?: boolean; | ||||
|     addon?: AddonSchema; | ||||
|     deprecated?: string; | ||||
| } | ||||
| 
 | ||||
| const StyledLink = styled(Link)(({ theme }) => ({ | ||||
| interface IIntegrationCardWithLinkProps extends IIntegrationCardBaseProps { | ||||
|     link: string; | ||||
|     isExternal?: boolean; | ||||
|     onClick?: never; | ||||
| } | ||||
| 
 | ||||
| interface IIntegrationCardWithOnClickProps extends IIntegrationCardBaseProps { | ||||
|     link?: never; | ||||
|     isExternal?: never; | ||||
|     onClick: () => void; | ||||
| } | ||||
| 
 | ||||
| type IIntegrationCardProps = | ||||
|     | IIntegrationCardWithLinkProps | ||||
|     | IIntegrationCardWithOnClickProps; | ||||
| 
 | ||||
| const StyledCard = styled('div')(({ theme }) => ({ | ||||
|     display: 'flex', | ||||
|     flexDirection: 'column', | ||||
|     padding: theme.spacing(3), | ||||
|     height: '100%', | ||||
|     borderRadius: `${theme.shape.borderRadiusMedium}px`, | ||||
|     border: `1px solid ${theme.palette.divider}`, | ||||
|     textDecoration: 'none', | ||||
|     color: 'inherit', | ||||
|     boxShadow: theme.boxShadows.card, | ||||
|     ':hover': { | ||||
|         backgroundColor: theme.palette.action.hover, | ||||
|     }, | ||||
| })); | ||||
| 
 | ||||
| const StyledAnchor = styled('a')(({ theme }) => ({ | ||||
|     display: 'flex', | ||||
|     flexDirection: 'column', | ||||
|     padding: theme.spacing(3), | ||||
|     borderRadius: `${theme.shape.borderRadiusMedium}px`, | ||||
|     border: `1px solid ${theme.palette.divider}`, | ||||
| const StyledLink = styled(Link)({ | ||||
|     textDecoration: 'none', | ||||
|     color: 'inherit', | ||||
|     boxShadow: theme.boxShadows.card, | ||||
|     ':hover': { | ||||
|         backgroundColor: theme.palette.action.hover, | ||||
|     }, | ||||
| })); | ||||
|     textAlign: 'left', | ||||
| }) as typeof Link; | ||||
| 
 | ||||
| const StyledRouterLink = styled(RouterLink)({ | ||||
|     textDecoration: 'none', | ||||
|     color: 'inherit', | ||||
| }); | ||||
| 
 | ||||
| const StyledHeader = styled('div')(({ theme }) => ({ | ||||
|     display: 'flex', | ||||
| @ -85,6 +95,7 @@ export const IntegrationCard: VFC<IIntegrationCardProps> = ({ | ||||
|     isEnabled, | ||||
|     configureActionText = 'Configure', | ||||
|     link, | ||||
|     onClick, | ||||
|     addon, | ||||
|     deprecated, | ||||
|     isExternal = false, | ||||
| @ -102,7 +113,7 @@ export const IntegrationCard: VFC<IIntegrationCardProps> = ({ | ||||
|     }; | ||||
| 
 | ||||
|     const content = ( | ||||
|         <> | ||||
|         <StyledCard> | ||||
|             <StyledHeader> | ||||
|                 <StyledTitle variant='h3' data-loading> | ||||
|                     <IntegrationIcon name={icon as string} /> {title} | ||||
| @ -143,25 +154,37 @@ export const IntegrationCard: VFC<IIntegrationCardProps> = ({ | ||||
|                     elseShow={<ChevronRightIcon />} | ||||
|                 /> | ||||
|             </StyledAction> | ||||
|         </> | ||||
|         </StyledCard> | ||||
|     ); | ||||
| 
 | ||||
|     if (isExternal) { | ||||
|     if (onClick) { | ||||
|         return ( | ||||
|             <StyledAnchor | ||||
|             <StyledLink | ||||
|                 component='button' | ||||
|                 onClick={() => { | ||||
|                     handleClick(); | ||||
|                     onClick(); | ||||
|                 }} | ||||
|             > | ||||
|                 {content} | ||||
|             </StyledLink> | ||||
|         ); | ||||
|     } else if (isExternal) { | ||||
|         return ( | ||||
|             <StyledLink | ||||
|                 href={link} | ||||
|                 target='_blank' | ||||
|                 rel='noreferrer' | ||||
|                 onClick={handleClick} | ||||
|             > | ||||
|                 {content} | ||||
|             </StyledAnchor> | ||||
|             </StyledLink> | ||||
|         ); | ||||
|     } else { | ||||
|         return ( | ||||
|             <StyledLink to={link} onClick={handleClick}> | ||||
|             <StyledRouterLink to={link} onClick={handleClick}> | ||||
|                 {content} | ||||
|             </StyledLink> | ||||
|             </StyledRouterLink> | ||||
|         ); | ||||
|     } | ||||
| }; | ||||
|  | ||||
| @ -1,38 +1,143 @@ | ||||
| import { VFC } from 'react'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import useAddons from 'hooks/api/getters/useAddons/useAddons'; | ||||
| import { AvailableIntegrations } from './AvailableIntegrations/AvailableIntegrations'; | ||||
| import { ConfiguredIntegrations } from './ConfiguredIntegrations/ConfiguredIntegrations'; | ||||
| import { AddonSchema } from 'openapi'; | ||||
| import { Tab, Tabs, styled, useTheme } from '@mui/material'; | ||||
| import { Add } from '@mui/icons-material'; | ||||
| import { Route, Routes, useLocation, useNavigate } from 'react-router-dom'; | ||||
| import { useUiFlag } from 'hooks/useUiFlag'; | ||||
| import { useIncomingWebhooks } from 'hooks/api/getters/useIncomingWebhooks/useIncomingWebhooks'; | ||||
| import { PageContent } from 'component/common/PageContent/PageContent'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { PageHeader } from 'component/common/PageHeader/PageHeader'; | ||||
| import { TabLink } from 'component/common/TabNav/TabLink'; | ||||
| import ResponsiveButton from 'component/common/ResponsiveButton/ResponsiveButton'; | ||||
| import { ADMIN } from 'component/providers/AccessProvider/permissions'; | ||||
| 
 | ||||
| const StyledHeader = styled('div')(() => ({ | ||||
|     display: 'flex', | ||||
|     justifyContent: 'space-between', | ||||
|     alignItems: 'center', | ||||
| })); | ||||
| 
 | ||||
| const StyledTabsContainer = styled('div')({ | ||||
|     flex: 1, | ||||
| }); | ||||
| 
 | ||||
| const StyledActions = styled('div')({ | ||||
|     display: 'flex', | ||||
|     alignItems: 'center', | ||||
| }); | ||||
| 
 | ||||
| export const IntegrationList: VFC = () => { | ||||
|     const { pathname } = useLocation(); | ||||
|     const navigate = useNavigate(); | ||||
|     const theme = useTheme(); | ||||
|     const incomingWebhooksEnabled = useUiFlag('incomingWebhooks'); | ||||
|     const { providers, addons, loading } = useAddons(); | ||||
|     const { incomingWebhooks } = useIncomingWebhooks(); | ||||
| 
 | ||||
|     const loadingPlaceholderAddons: AddonSchema[] = Array.from({ length: 4 }) | ||||
|         .fill({}) | ||||
|         .map((_, id) => ({ | ||||
|             id, | ||||
|             provider: 'mock', | ||||
|             description: 'mock integratino', | ||||
|             events: [], | ||||
|             projects: [], | ||||
|             parameters: {}, | ||||
|             enabled: false, | ||||
|         })); | ||||
|     const onNewIncomingWebhook = () => { | ||||
|         navigate('/integrations/incoming-webhooks'); | ||||
|         // TODO: Implement:
 | ||||
|         // setSelectedIncomingWebhook(undefined);
 | ||||
|         // setIncomingWebhookModalOpen(true);
 | ||||
|     }; | ||||
| 
 | ||||
|     const tabs = [ | ||||
|         { | ||||
|             label: 'Integrations', | ||||
|             path: '/integrations', | ||||
|         }, | ||||
|         { | ||||
|             label: `Incoming webhooks (${incomingWebhooks.length})`, | ||||
|             path: '/integrations/incoming-webhooks', | ||||
|         }, | ||||
|     ]; | ||||
| 
 | ||||
|     return ( | ||||
|         <> | ||||
|             <ConditionallyRender | ||||
|                 condition={addons.length > 0} | ||||
|                 show={ | ||||
|                     <ConfiguredIntegrations | ||||
|                         addons={loading ? loadingPlaceholderAddons : addons} | ||||
|                         providers={providers} | ||||
|                         loading={loading} | ||||
|                     /> | ||||
|                 } | ||||
|             /> | ||||
|             <AvailableIntegrations providers={providers} loading={loading} /> | ||||
|         </> | ||||
|         <PageContent | ||||
|             header={ | ||||
|                 <ConditionallyRender | ||||
|                     condition={incomingWebhooksEnabled} | ||||
|                     show={ | ||||
|                         <StyledHeader> | ||||
|                             <StyledTabsContainer> | ||||
|                                 <Tabs | ||||
|                                     value={pathname} | ||||
|                                     indicatorColor='primary' | ||||
|                                     textColor='primary' | ||||
|                                     variant='scrollable' | ||||
|                                     allowScrollButtonsMobile | ||||
|                                 > | ||||
|                                     {tabs.map(({ label, path }) => ( | ||||
|                                         <Tab | ||||
|                                             key={label} | ||||
|                                             value={path} | ||||
|                                             label={ | ||||
|                                                 <TabLink to={path}> | ||||
|                                                     {label} | ||||
|                                                 </TabLink> | ||||
|                                             } | ||||
|                                             sx={{ | ||||
|                                                 padding: 0, | ||||
|                                             }} | ||||
|                                         /> | ||||
|                                     ))} | ||||
|                                 </Tabs> | ||||
|                             </StyledTabsContainer> | ||||
|                             <StyledActions> | ||||
|                                 <ConditionallyRender | ||||
|                                     condition={pathname.includes( | ||||
|                                         'incoming-webhooks', | ||||
|                                     )} | ||||
|                                     show={ | ||||
|                                         <ResponsiveButton | ||||
|                                             onClick={onNewIncomingWebhook} | ||||
|                                             maxWidth={`${theme.breakpoints.values.sm}px`} | ||||
|                                             Icon={Add} | ||||
|                                             permission={ADMIN} | ||||
|                                         > | ||||
|                                             New incoming webhook | ||||
|                                         </ResponsiveButton> | ||||
|                                     } | ||||
|                                 /> | ||||
|                             </StyledActions> | ||||
|                         </StyledHeader> | ||||
|                     } | ||||
|                     elseShow={<PageHeader title='Integrations' />} | ||||
|                 /> | ||||
|             } | ||||
|             isLoading={loading} | ||||
|             withTabs={incomingWebhooksEnabled} | ||||
|         > | ||||
|             <Routes> | ||||
|                 <Route | ||||
|                     path='incoming-webhooks' | ||||
|                     element={<span>TODO: Implement</span>} | ||||
|                 /> | ||||
|                 <Route | ||||
|                     path='*' | ||||
|                     element={ | ||||
|                         <> | ||||
|                             <ConditionallyRender | ||||
|                                 condition={addons.length > 0} | ||||
|                                 show={ | ||||
|                                     <ConfiguredIntegrations | ||||
|                                         addons={addons} | ||||
|                                         providers={providers} | ||||
|                                         loading={loading} | ||||
|                                     /> | ||||
|                                 } | ||||
|                             /> | ||||
|                             <AvailableIntegrations | ||||
|                                 providers={providers} | ||||
|                                 onNewIncomingWebhook={onNewIncomingWebhook} | ||||
|                             /> | ||||
|                         </> | ||||
|                     } | ||||
|                 /> | ||||
|             </Routes> | ||||
|         </PageContent> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -346,7 +346,7 @@ exports[`returns all baseRoutes 1`] = ` | ||||
|       "advanced": true, | ||||
|       "mobile": true, | ||||
|     }, | ||||
|     "path": "/integrations", | ||||
|     "path": "/integrations/*", | ||||
|     "title": "Integrations", | ||||
|     "type": "protected", | ||||
|   }, | ||||
|  | ||||
| @ -350,7 +350,7 @@ export const routes: IRoute[] = [ | ||||
|         menu: {}, | ||||
|     }, | ||||
|     { | ||||
|         path: '/integrations', | ||||
|         path: '/integrations/*', | ||||
|         title: 'Integrations', | ||||
|         component: IntegrationList, | ||||
|         hidden: false, | ||||
|  | ||||
| @ -4,14 +4,16 @@ import handleErrorResponses from '../httpErrorResponseHandler'; | ||||
| import { useConditionalSWR } from '../useConditionalSWR/useConditionalSWR'; | ||||
| import useUiConfig from '../useUiConfig/useUiConfig'; | ||||
| import { IIncomingWebhook } from 'interfaces/incomingWebhook'; | ||||
| import { useUiFlag } from 'hooks/useUiFlag'; | ||||
| 
 | ||||
| const ENDPOINT = 'api/admin/incoming-webhooks'; | ||||
| 
 | ||||
| export const useIncomingWebhooks = () => { | ||||
|     const { isEnterprise } = useUiConfig(); | ||||
|     const incomingWebhooksEnabled = useUiFlag('incomingWebhooks'); | ||||
| 
 | ||||
|     const { data, error, mutate } = useConditionalSWR( | ||||
|         isEnterprise(), | ||||
|         isEnterprise() && incomingWebhooksEnabled, | ||||
|         { incomingWebhooks: [] }, | ||||
|         formatApiPath(ENDPOINT), | ||||
|         fetcher, | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user