diff --git a/frontend/src/component/integrations/IntegrationList/AddonsList.tsx b/frontend/src/component/integrations/IntegrationList/AddonsList.tsx new file mode 100644 index 0000000000..4076b9ddc2 --- /dev/null +++ b/frontend/src/component/integrations/IntegrationList/AddonsList.tsx @@ -0,0 +1,21 @@ +import { ConfiguredAddons } from './ConfiguredAddons/ConfiguredAddons'; +import { AvailableAddons } from './AvailableAddons/AvailableAddons'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import useAddons from 'hooks/api/getters/useAddons/useAddons'; + +/** + * @deprecated remove with `integrationsRework` flag + */ +export const AddonsList = () => { + const { providers, addons, loading } = useAddons(); + + return ( + <> + 0} + show={} + /> + + + ); +}; diff --git a/frontend/src/component/integrations/IntegrationList/AvailableIntegrations/AvailableIntegrations.tsx b/frontend/src/component/integrations/IntegrationList/AvailableIntegrations/AvailableIntegrations.tsx index bb5afe0df1..e8a97db0d3 100644 --- a/frontend/src/component/integrations/IntegrationList/AvailableIntegrations/AvailableIntegrations.tsx +++ b/frontend/src/component/integrations/IntegrationList/AvailableIntegrations/AvailableIntegrations.tsx @@ -3,34 +3,27 @@ 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 { styled } from '@mui/material'; +import { StyledCardsGrid } from '../IntegrationList.styles'; interface IAvailableIntegrationsProps { providers: AddonTypeSchema[]; } - -const StyledGrid = styled('div')(({ theme }) => ({ - gridTemplateColumns: 'repeat(auto-fit, minmax(350px, 1fr))', - gridAutoRows: '1fr', - gap: theme.spacing(2), - display: 'grid', -})); - export const AvailableIntegrations: VFC = ({ providers, }) => { return ( }> - + {providers?.map(({ name, displayName, description }) => ( ))} - + ); }; diff --git a/frontend/src/component/integrations/IntegrationList/ConfiguredIntegrations/ConfiguredIntegrations.tsx b/frontend/src/component/integrations/IntegrationList/ConfiguredIntegrations/ConfiguredIntegrations.tsx new file mode 100644 index 0000000000..61003c1f89 --- /dev/null +++ b/frontend/src/component/integrations/IntegrationList/ConfiguredIntegrations/ConfiguredIntegrations.tsx @@ -0,0 +1,63 @@ +import { Table, TableBody, TableCell, TableRow } from 'component/common/Table'; +import { useMemo, useState, useCallback } from 'react'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { PageContent } from 'component/common/PageContent/PageContent'; +import useAddons from 'hooks/api/getters/useAddons/useAddons'; +import useToast from 'hooks/useToast'; +import useAddonsApi from 'hooks/api/actions/useAddonsApi/useAddonsApi'; +import { Dialogue } from 'component/common/Dialogue/Dialogue'; +import { formatUnknownError } from 'utils/formatUnknownError'; +import { sortTypes } from 'utils/sortTypes'; +import { useTable, useSortBy } from 'react-table'; +import { PageHeader } from 'component/common/PageHeader/PageHeader'; +import { SortableTableHeader, TablePlaceholder } from 'component/common/Table'; +import { IconCell } from 'component/common/Table/cells/IconCell/IconCell'; +import { IntegrationIcon } from '../IntegrationIcon/IntegrationIcon'; +import { IntegrationNameCell } from '../IntegrationNameCell/IntegrationNameCell'; +import { StyledCardsGrid } from '../IntegrationList.styles'; +import { IntegrationCard } from '../IntegrationCard/IntegrationCard'; + +export const ConfiguredIntegrations = () => { + const { refetchAddons, addons, loading } = useAddons(); + const { updateAddon, removeAddon } = useAddonsApi(); + const { setToastData, setToastApiError } = useToast(); + const [showDelete, setShowDelete] = useState(false); + + const data = useMemo(() => { + if (loading) { + return Array(5).fill({ + name: 'Addon name', + description: 'Addon description when loading', + }); + } + + return addons.map(addon => ({ + ...addon, + })); + }, [addons, loading]); + + const counter = addons.length ? `(${addons.length})` : ''; + + return ( + } + sx={theme => ({ marginBottom: theme.spacing(2) })} + > + + {addons?.map(({ id, enabled, provider, description }) => ( + + ))} + + + ); +}; diff --git a/frontend/src/component/integrations/IntegrationList/IntegrationCard/IntegrationCard.tsx b/frontend/src/component/integrations/IntegrationList/IntegrationCard/IntegrationCard.tsx index 637a38704d..52af2b2170 100644 --- a/frontend/src/component/integrations/IntegrationList/IntegrationCard/IntegrationCard.tsx +++ b/frontend/src/component/integrations/IntegrationList/IntegrationCard/IntegrationCard.tsx @@ -1,40 +1,93 @@ -import { VFC } from 'react'; -import { Box, styled, Typography } from '@mui/material'; +import { useState, VFC } from 'react'; +import { Link } from 'react-router-dom'; +import { styled, Typography } from '@mui/material'; import { IntegrationIcon } from '../IntegrationIcon/IntegrationIcon'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import ChevronRightIcon from '@mui/icons-material/ChevronRight'; +import { Badge } from 'component/common/Badge/Badge'; +import { IntegrationCardMenu } from './IntegrationCardMenu/IntegrationCardMenu'; interface IIntegrationCardProps { - icon: string; + id?: string | number; + icon?: string; title: string; description?: string; + isConfigured?: boolean; isEnabled?: boolean; - isDeprecated?: boolean; + configureActionText?: string; + link: string; } -const StyledBox = styled(Box)(({ theme }) => ({ +const StyledLink = styled(Link)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', padding: theme.spacing(3), 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 StyledHeader = styled(Typography)(({ theme }) => ({ +const StyledHeader = styled('div')(({ theme }) => ({ display: 'flex', alignItems: 'center', marginBottom: theme.spacing(2), })); +const StyledTitle = styled(Typography)(() => ({ + display: 'flex', + alignItems: 'center', + marginRight: 'auto', +})); + +const StyledAction = styled(Typography)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + color: theme.palette.primary.main, + fontWeight: theme.typography.fontWeightBold, + marginTop: 'auto', + paddingTop: theme.spacing(1), + gap: theme.spacing(0.5), +})); + export const IntegrationCard: VFC = ({ + id, icon, title, description, - isDeprecated, isEnabled, + configureActionText = 'Configure', + link, }) => { + const isConfigured = id !== undefined; + return ( - - - {title} + + + + {title} + + Enabled} + /> + Disabled} + /> + } + /> {description} - + + {configureActionText} + + ); }; diff --git a/frontend/src/component/integrations/IntegrationList/IntegrationCard/IntegrationCardMenu/IntegrationCardMenu.tsx b/frontend/src/component/integrations/IntegrationList/IntegrationCard/IntegrationCardMenu/IntegrationCardMenu.tsx new file mode 100644 index 0000000000..70c6deb187 --- /dev/null +++ b/frontend/src/component/integrations/IntegrationList/IntegrationCard/IntegrationCardMenu/IntegrationCardMenu.tsx @@ -0,0 +1,126 @@ +import { useState, VFC } from 'react'; +import { + IconButton, + ListItemIcon, + ListItemText, + Menu, + MenuItem, + styled, + Tooltip, +} from '@mui/material'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; + +import { Delete, PowerSettingsNew } from '@mui/icons-material'; +import { + DELETE_ADDON, + UPDATE_ADDON, +} from 'component/providers/AccessProvider/permissions'; +import { useHasRootAccess } from 'hooks/useHasAccess'; + +interface IIntegrationCardMenuProps { + id: string; +} + +const StyledMenu = styled('div')(({ theme }) => ({ + marginLeft: theme.spacing(1), + marginTop: theme.spacing(-1), + marginBottom: theme.spacing(-1), + marginRight: theme.spacing(-1), + display: 'flex', + alignItems: 'center', +})); + +export const IntegrationCardMenu: VFC = ({ id }) => { + const [open, setOpen] = useState(false); + const [anchorEl, setAnchorEl] = useState(null); + + const handleMenuClick = (event: React.SyntheticEvent) => { + event.preventDefault(); + if (open) { + setOpen(false); + setAnchorEl(null); + } else { + setAnchorEl(event.currentTarget); + setOpen(true); + } + }; + const updateAccess = useHasRootAccess(UPDATE_ADDON); + const deleteAccess = useHasRootAccess(DELETE_ADDON); + + return ( + + + + + + + { + event.preventDefault(); + }} + onClose={handleMenuClick} + > + { + // e.preventDefault(); + // navigate( + // getProjectEditPath( + // id, + // Boolean( + // uiConfig.flags + // .newProjectLayout + // ) + // ) + // ); + // }} + disabled={!updateAccess} + > + + + + Disable + {' '} + setRemoveDialogOpen(true)} + > + + + + Delete + + {/* { + e.preventDefault(); + setShowDelDialog(true); + }} + disabled={!canDeleteProject} + > + + {id === DEFAULT_PROJECT_ID && + !canDeleteProject + ? "You can't delete the default project" + : 'Delete project'} + */} + + + ); +}; diff --git a/frontend/src/component/integrations/IntegrationList/IntegrationList.styles.tsx b/frontend/src/component/integrations/IntegrationList/IntegrationList.styles.tsx new file mode 100644 index 0000000000..c97ab76c26 --- /dev/null +++ b/frontend/src/component/integrations/IntegrationList/IntegrationList.styles.tsx @@ -0,0 +1,8 @@ +import { styled } from '@mui/material'; + +export const StyledCardsGrid = styled('div')(({ theme }) => ({ + gridTemplateColumns: 'repeat(auto-fit, minmax(350px, 1fr))', + gridAutoRows: '1fr', + gap: theme.spacing(2), + display: 'grid', +})); diff --git a/frontend/src/component/integrations/IntegrationList/IntegrationList.tsx b/frontend/src/component/integrations/IntegrationList/IntegrationList.tsx index 65d84c5dfe..1217bd33da 100644 --- a/frontend/src/component/integrations/IntegrationList/IntegrationList.tsx +++ b/frontend/src/component/integrations/IntegrationList/IntegrationList.tsx @@ -1,28 +1,19 @@ -import { ConfiguredAddons } from './ConfiguredAddons/ConfiguredAddons'; -import { AvailableAddons } from './AvailableAddons/AvailableAddons'; +import { VFC } from 'react'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import useAddons from 'hooks/api/getters/useAddons/useAddons'; -import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import { AvailableIntegrations } from './AvailableIntegrations/AvailableIntegrations'; +import { ConfiguredIntegrations } from './ConfiguredIntegrations/ConfiguredIntegrations'; -export const IntegrationList = () => { +export const IntegrationList: VFC = () => { const { providers, addons, loading } = useAddons(); - const { uiConfig } = useUiConfig(); - const integrationsRework = uiConfig?.flags?.integrationsRework || false; return ( <> 0} - show={} - /> - } - elseShow={ - - } + show={} /> + ); }; diff --git a/frontend/src/component/menu/routes.ts b/frontend/src/component/menu/routes.ts index dcf40a6f37..c10ba96ebb 100644 --- a/frontend/src/component/menu/routes.ts +++ b/frontend/src/component/menu/routes.ts @@ -44,6 +44,7 @@ import { LazyAdmin } from 'component/admin/LazyAdmin'; import { LazyProject } from 'component/project/Project/LazyProject'; import { LoginHistory } from 'component/loginHistory/LoginHistory'; import { FeatureTypesList } from 'component/featureTypes/FeatureTypesList'; +import { AddonsList } from 'component/integrations/IntegrationList/AddonsList'; export const routes: IRoute[] = [ // Splash @@ -322,7 +323,7 @@ export const routes: IRoute[] = [ { path: '/addons', title: 'Addons', - component: IntegrationList, + component: AddonsList, // TODO: use AddonRedirect after removing `integrationsRework` menu flag hidden: false, type: 'protected', diff --git a/src/server-dev.ts b/src/server-dev.ts index fe4fee7c64..bd74b93c8a 100644 --- a/src/server-dev.ts +++ b/src/server-dev.ts @@ -45,6 +45,7 @@ process.nextTick(async () => { frontendNavigationUpdate: true, lastSeenByEnvironment: true, segmentChangeRequests: true, + integrationsRework: true, }, }, authentication: {