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> | ||||
|                                             <TabLink to={path}> | ||||
|                                                 {label} ({total}) | ||||
|                                                 </span> | ||||
|                                             </CenteredNavLink> | ||||
|                                             </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,26 +53,32 @@ 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> | ||||
|                 <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, | ||||
|                                 a.displayName?.localeCompare(b.displayName) || | ||||
|                                 0, | ||||
|                         ) | ||||
|                         .map( | ||||
|                             ({ | ||||
| @ -91,6 +97,17 @@ export const AvailableIntegrations: VFC<IAvailableIntegrationsProps> = ({ | ||||
|                                 /> | ||||
|                             ), | ||||
|                         )} | ||||
|                     <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 }) => ( | ||||
| @ -113,8 +130,8 @@ export const AvailableIntegrations: VFC<IAvailableIntegrationsProps> = ({ | ||||
|                         Performance and security | ||||
|                     </Typography> | ||||
|                     <Typography variant='body2' color='text.secondary'> | ||||
|                             Connect Unleash to private, scalable, and | ||||
|                             distributed relays. | ||||
|                         Connect Unleash to private, scalable, and distributed | ||||
|                         relays. | ||||
|                     </Typography> | ||||
|                 </div> | ||||
|                 <StyledCardsGrid> | ||||
| @ -142,9 +159,9 @@ export const AvailableIntegrations: VFC<IAvailableIntegrationsProps> = ({ | ||||
|                         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{' '} | ||||
|                         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' | ||||
| @ -160,13 +177,9 @@ export const AvailableIntegrations: VFC<IAvailableIntegrationsProps> = ({ | ||||
|                             <Typography component='h4' variant='h4'> | ||||
|                                 Server-side SDKs | ||||
|                             </Typography> | ||||
|                                 <Typography | ||||
|                                     variant='body2' | ||||
|                                     color='text.secondary' | ||||
|                                 > | ||||
|                             <Typography variant='body2' color='text.secondary'> | ||||
|                                 Server-side clients run on your server and | ||||
|                                     communicate directly with your Unleash | ||||
|                                     instance. | ||||
|                                 communicate directly with your Unleash instance. | ||||
|                             </Typography> | ||||
|                         </Box> | ||||
|                         <StyledCardsGrid small> | ||||
| @ -197,10 +210,7 @@ export const AvailableIntegrations: VFC<IAvailableIntegrationsProps> = ({ | ||||
|                             <Typography component='h4' variant='h4'> | ||||
|                                 Client-side SDKs | ||||
|                             </Typography> | ||||
|                                 <Typography | ||||
|                                     variant='body2' | ||||
|                                     color='text.secondary' | ||||
|                                 > | ||||
|                             <Typography variant='body2' color='text.secondary'> | ||||
|                                 Client-side SDKs can connect to the{' '} | ||||
|                                 <a | ||||
|                                     href='https://docs.getunleash.io/reference/unleash-edge' | ||||
| @ -257,8 +267,8 @@ export const AvailableIntegrations: VFC<IAvailableIntegrationsProps> = ({ | ||||
|                                     > | ||||
|                                         Here's some of the fantastic work | ||||
|                                     </a>{' '} | ||||
|                                         our community has built to make Unleash | ||||
|                                         work in even more contexts. | ||||
|                                     our community has built to make Unleash work | ||||
|                                     in even more contexts. | ||||
|                                 </Typography> | ||||
|                             </div> | ||||
|                         </StyledGrayContainer> | ||||
| @ -266,6 +276,5 @@ export const AvailableIntegrations: VFC<IAvailableIntegrationsProps> = ({ | ||||
|                 </StyledSdksSection> | ||||
|             </StyledSection> | ||||
|         </StyledContainer> | ||||
|         </PageContent> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -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 ( | ||||
|         <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={loading ? loadingPlaceholderAddons : addons} | ||||
|                                         addons={addons} | ||||
|                                         providers={providers} | ||||
|                                         loading={loading} | ||||
|                                     /> | ||||
|                                 } | ||||
|                             /> | ||||
|             <AvailableIntegrations 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