diff --git a/apps/web/app/connect/portal/page.tsx b/apps/web/app/connect/portal/page.tsx index 38df2faf..8fac6e6c 100644 --- a/apps/web/app/connect/portal/page.tsx +++ b/apps/web/app/connect/portal/page.tsx @@ -55,7 +55,7 @@ export default async function PortalPage({ } const isAgMode = - viewer.orgId = 'org_2nJZrA4Dk8i3wszhm6PsP3M2Vwy' || + viewer.orgId === 'org_2nJZrA4Dk8i3wszhm6PsP3M2Vwy' || viewer.orgId === 'org_2lcCCimyICKI8cpPNQt195h5zrP' || viewer.orgId === 'org_2ms9FdeczlbrDIHJLcwGdpv3dTx' diff --git a/packages/engine-frontend/components/AddConnectionTabContent.tsx b/packages/engine-frontend/components/AddConnectionTabContent.tsx new file mode 100644 index 00000000..7267fec8 --- /dev/null +++ b/packages/engine-frontend/components/AddConnectionTabContent.tsx @@ -0,0 +1,29 @@ +import type {ConnectorConfigFilters} from '../hocs/WithConnectConfig' +import {ConnectIntegrations} from './ConnectIntegrations' + +interface AddConnectionTabContentProps { + connectorConfigFilters: ConnectorConfigFilters + refetch: () => void +} + +export function AddConnectionTabContent({ + connectorConfigFilters, + refetch, +}: AddConnectionTabContentProps) { + return ( +
+
+

Setup a new Connection

+

Choose a connector config to start

+
+ { + if (event.type === 'close') { + refetch() + } + }} + /> +
+ ) +} diff --git a/packages/engine-frontend/components/ConnectIntegrations.tsx b/packages/engine-frontend/components/ConnectIntegrations.tsx new file mode 100644 index 00000000..9419ce5a --- /dev/null +++ b/packages/engine-frontend/components/ConnectIntegrations.tsx @@ -0,0 +1,34 @@ +import { + WithConnectConfig, + type ConnectorConfigFilters, +} from '../hocs/WithConnectConfig' +import {IntegrationSearch} from './IntegrationSearch' + +export function ConnectIntegrations({ + connectorConfigFilters, + connectorNames = [], + onEvent, +}: { + connectorConfigFilters: ConnectorConfigFilters + connectorNames?: string[] + onEvent: (event: any) => void +}) { + return ( + + {({ccfgs}) => { + const filteredCcfgs = ccfgs.filter( + (c) => !connectorNames.includes(c.connectorName), + ) + + return ( + { + if (onEvent) onEvent(e) + }} + /> + ) + }} + + ) +} diff --git a/packages/engine-frontend/components/ConnectionPortal.tsx b/packages/engine-frontend/components/ConnectionPortal.tsx index 8e2e8821..f558029d 100644 --- a/packages/engine-frontend/components/ConnectionPortal.tsx +++ b/packages/engine-frontend/components/ConnectionPortal.tsx @@ -1,24 +1,15 @@ 'use client' -import {Loader, Settings} from 'lucide-react' import type {Id} from '@openint/cdk' import type {UIPropsNoChildren} from '@openint/ui' -import { - Badge, - ConnectorLogo, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, - Separator, - useToast, -} from '@openint/ui' -import {Tabs, TabsContent, TabsList, TabsTrigger} from '@openint/ui/shadcn/Tabs' +import {useToast} from '@openint/ui' +import {Tabs} from '@openint/ui/components/Tabs' import {cn} from '@openint/ui/utils' import {R} from '@openint/util' import {WithConnectConfig} from '../hocs/WithConnectConfig' import {_trpcReact} from '../providers/TRPCProvider' -import {ConnectDialog} from './ConnectDialog' +import {AddConnectionTabContent} from './AddConnectionTabContent' +import {ConnectionsTabContent} from './ConnectionsTabContent' type ConnectEventType = 'open' | 'close' | 'error' @@ -77,139 +68,35 @@ export function ConnectionPortal({className}: ConnectionPortalProps) { const connectionCount = connections.length console.log({connectionCount}) + const tabConfig = [ + { + key: 'connections', + title: `My Connections (${connectionCount})`, + content: ( + + ), + }, + { + key: 'add-connection', + title: 'Add a Connection', + content: ( + + ), + }, + ] + return ( -
- {/* Listing by categories */} - - - - My Connections ({connectionCount}) - - - Add a Connection - - - - {connectionCount === 0 ? ( -
-
-

- No connections yet -

-

- Add a connection to get started -

-
- { - if (event.type === 'close') { - listConnectionsRes.refetch(); // Trigger refetch - } - }} - > -
- ) : ( -
- {listConnectionsRes.isLoading ? ( -
- -
- ) : ( - categoriesWithConnections.map((category) => ( -
- {category.connections.map((conn) => ( - <> -
-
- -
-
-

- {conn.connectorName - .charAt(0) - .toUpperCase() + - conn.connectorName.slice(1)} -

- - {category.name} - -
- {conn.pipelineIds.length > 0 && ( -
- {conn.syncInProgress ? ( -
- -

- Syncing... -

-
- ) : ( -

Successfully synced

- )} -
- )} -
-
- - - - - - - deleteResource.mutate({id: conn.id}) - }> - - Delete - - - - -
- {/* TODO: Improve the condition to hide the separator for the last item, right now it iterates - over all categories and all connections, would be good to have a single array of connections with the category - information included already */} - - - ))} -
- )) - )} -
- )} -
- -
-
-

- Setup a new Connection -

-

- Choose a connector config to start -

-
- { - if (event.type === 'close') { - listConnectionsRes.refetch(); // Trigger refetch - } - }} - > -
-
-
+
+
) }} diff --git a/packages/engine-frontend/components/ConnectionsTabContent.tsx b/packages/engine-frontend/components/ConnectionsTabContent.tsx new file mode 100644 index 00000000..fa2e274e --- /dev/null +++ b/packages/engine-frontend/components/ConnectionsTabContent.tsx @@ -0,0 +1,120 @@ +import {Loader, Settings} from 'lucide-react' +import { + Badge, + ConnectorLogo, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, + Separator, +} from '@openint/ui' +import type {ConnectorConfig} from '../hocs/WithConnectConfig' +import {ConnectDialog} from './ConnectDialog' + +interface ConnectionsTabContentProps { + connectionCount: number + categoriesWithConnections: Array<{ + name: string + connections: Array<{ + id: string + connectorConfig: ConnectorConfig + connectorName: string + pipelineIds: string[] + syncInProgress: boolean + }> + }> + refetch: () => void + isLoading: boolean + deleteResource: ({id}: {id: string}) => void +} + +export function ConnectionsTabContent({ + connectionCount, + refetch, + isLoading, + deleteResource, + categoriesWithConnections, +}: ConnectionsTabContentProps) { + return connectionCount === 0 ? ( +
+
+

No connections yet

+

Add a connection to get started

+
+ { + if (event.type === 'close') { + refetch() // Trigger refetch + } + }}> +
+ ) : ( +
+ {isLoading ? ( +
+ +
+ ) : ( + categoriesWithConnections.map((category) => ( +
+ {category.connections.map((conn) => ( + <> +
+
+ +
+
+

+ {conn.connectorName.charAt(0).toUpperCase() + + conn.connectorName.slice(1)} +

+ {category.name} +
+ {conn.pipelineIds.length > 0 && ( +
+ {conn.syncInProgress ? ( +
+ +

Syncing...

+
+ ) : ( +

Successfully synced

+ )} +
+ )} +
+
+ + + + + + deleteResource({id: conn.id})}> + + Delete + + + + +
+ {/* TODO: Improve the condition to hide the separator for the last item, right now it iterates + over all categories and all connections, would be good to have a single array of connections with the category + information included already */} + + + ))} +
+ )) + )} +
+ ) +} diff --git a/packages/engine-frontend/components/IntegrationSearch.tsx b/packages/engine-frontend/components/IntegrationSearch.tsx index aaa64c43..bfe42808 100644 --- a/packages/engine-frontend/components/IntegrationSearch.tsx +++ b/packages/engine-frontend/components/IntegrationSearch.tsx @@ -2,7 +2,8 @@ import {Loader, Search} from 'lucide-react' import React from 'react' -import {Card, cn, ConnectorLogo, Input} from '@openint/ui' +import {Input} from '@openint/ui' +import {ConnectionCard} from '@openint/ui/domain-components/ConnectionCard' import type {ConnectorConfig} from '../hocs/WithConnectConfig' import type {ConnectEventType} from '../hocs/WithConnectorConnect' import {WithConnectorConnect} from '../hocs/WithConnectorConnect' @@ -35,10 +36,20 @@ export function IntegrationSearch({ ccfg: connectorConfigs.find((ccfg) => ccfg.id === int.connector_config_id)!, })) + const intsByCategory = ints?.reduce( + (acc, int) => { + int.ccfg.verticals.forEach((vertical) => { + acc[vertical] = (acc[vertical] || []).concat(int) + }) + return acc + }, + {} as Record, + ) + return (
{/* Search integrations */} -
+
{/* top-2.5 is not working for some reason due to tailwind setup */} @@ -58,42 +69,51 @@ export function IntegrationSearch({
) : ( -
- {ints?.map((int) => ( - { - onEvent?.({ - type: e.type, - integration: { - connectorConfigId: int.connector_config_id, - id: int.id, - }, - }) - }}> - {({openConnect}) => ( - openConnect()}> - - - {int.name} - - - )} - - ))} +
+ {Object.entries(intsByCategory ?? {}).map( + ([category, categoryInts]) => ( +
+

+ {category.length < 5 + ? category.toUpperCase() + : category + .split('-') + .map( + (word) => + word.charAt(0).toUpperCase() + word.slice(1), + ) + .join(' ')} +

+
+ {categoryInts.map((int) => ( + { + onEvent?.({ + type: e.type, + integration: { + connectorConfigId: int.connector_config_id, + id: int.id, + }, + }) + }}> + {({openConnect}) => ( + + )} + + ))} +
+
+ ), + )}
)}
diff --git a/packages/ui/components/Tabs.tsx b/packages/ui/components/Tabs.tsx new file mode 100644 index 00000000..849908c8 --- /dev/null +++ b/packages/ui/components/Tabs.tsx @@ -0,0 +1,38 @@ +import {type ReactElement} from 'react' +import { + Tabs as ShadcnTabs, + TabsContent, + TabsList, + TabsTrigger, +} from '../shadcn/Tabs' + +interface TabsProps { + tabConfig: Array<{ + key: string + title: string + content: ReactElement + }> + defaultValue: string +} + +export function Tabs({tabConfig, defaultValue}: TabsProps) { + return ( + + + {tabConfig.map((config) => ( + + {config.title} + + ))} + + {tabConfig.map((config) => ( + + {config.content} + + ))} + + ) +} diff --git a/packages/ui/domain-components/ConnectionCard.tsx b/packages/ui/domain-components/ConnectionCard.tsx index a3ad6490..5b9b60ce 100644 --- a/packages/ui/domain-components/ConnectionCard.tsx +++ b/packages/ui/domain-components/ConnectionCard.tsx @@ -1,35 +1,45 @@ -import { useState } from 'react' -import { - Card, - CardContent, -} from '../shadcn' -import { Plus } from 'lucide-react' +import {Plus} from 'lucide-react' +import {useState} from 'react' +import {Card, CardContent} from '../shadcn' export function ConnectionCard({ logo, name, + onClick, }: { logo: string name: string + onClick: () => void }) { const [isHovered, setIsHovered] = useState(false) return ( setIsHovered(true)} - onMouseLeave={() => setIsHovered(false)} - > - + onMouseLeave={() => setIsHovered(false)}> + {isHovered ? ( -
+
- Add {/* Set to 14px and semibold */} + + Add + {' '} + {/* Set to 14px and semibold */}
) : ( -
- {`${name} {/* 32x32 logo with 10px padding */} -

{name}

{/* Changed to font-semibold */} +
+ {`${name}{' '} +

+ {name} +

{' '}
)} diff --git a/packages/ui/shadcn/Dialog.tsx b/packages/ui/shadcn/Dialog.tsx index 411c0b82..3f71f3dc 100644 --- a/packages/ui/shadcn/Dialog.tsx +++ b/packages/ui/shadcn/Dialog.tsx @@ -3,7 +3,6 @@ import * as DialogPrimitive from '@radix-ui/react-dialog' import {X} from 'lucide-react' import React from 'react' - import {cn} from '../utils' const Dialog = DialogPrimitive.Root @@ -48,6 +47,7 @@ const DialogContent = React.forwardRef< ref={ref} className={cn( 'fixed z-50 grid w-full gap-4 rounded-b-lg border bg-background p-6 shadow-lg animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:max-w-lg sm:rounded-lg sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0', + 'left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%]', className, )} {...props}> diff --git a/packages/ui/shadcn/Tabs.tsx b/packages/ui/shadcn/Tabs.tsx index 3da9d009..1193681e 100644 --- a/packages/ui/shadcn/Tabs.tsx +++ b/packages/ui/shadcn/Tabs.tsx @@ -11,7 +11,8 @@ const TabsList = React.forwardRef<