Skip to content

oint-1334 ConnectionCard and ConnectionStatus components #603

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions packages/ui-v1/domain-components/ConnectionCard.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const Disconnected: Story = {
...FIXTURES.connections['google-drive-basic'],
status: 'disconnected',
},
onReconnect: () => alert('Reconnect clicked!'),
},
}

Expand All @@ -66,6 +67,61 @@ export const NoIntegration: Story = {
},
}

export const WithReconnectButton: Story = {
args: {
connection: FIXTURES.connections['notion-basic'],
onReconnect: () => alert('Reconnecting...'),
},
}

export const LinearInspiredShowcase: Story = {
render: () => (
<div className="flex flex-wrap gap-6 p-6">
<div className="space-y-2">
<h3 className="text-sm font-medium text-gray-600">
Healthy Connection
</h3>
<ConnectionCard
connection={FIXTURES.connections['google-drive-basic']}
onPress={() => {}}
/>
Comment on lines +85 to +87
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Empty onPress handler could be confusing. Consider removing it if not needed or adding a meaningful action.

</div>

<div className="space-y-2">
<h3 className="text-sm font-medium text-gray-600">
Disconnected (with Reconnect)
</h3>
<ConnectionCard
connection={FIXTURES.connections['notion-basic']}
onReconnect={() => alert('Reconnecting...')}
/>
</div>

<div className="space-y-2">
<h3 className="text-sm font-medium text-gray-600">Error State</h3>
<ConnectionCard
connection={FIXTURES.connections['hubspot-without-logo']}
/>
</div>

<div className="space-y-2">
<h3 className="text-sm font-medium text-gray-600">Manual Status</h3>
<ConnectionCard
connection={FIXTURES.connections['notion-with-integration']}
/>
</div>
</div>
),
parameters: {
docs: {
description: {
story:
'Showcase of the Linear-inspired ConnectionCard design with status indicators in the top-left corner and improved Reconnect button styling.',
},
},
},
}

export const Grid: Story = {
render: () => (
<div className="flex flex-wrap gap-4">
Expand Down
62 changes: 55 additions & 7 deletions packages/ui-v1/domain-components/ConnectionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ import type {ConnectionExpanded, ConnectorName} from '@openint/api-v1/models'
import {Settings} from 'lucide-react'
import React, {useRef, useState} from 'react'
import {cn} from '@openint/shadcn/lib/utils'
import {Card, CardContent} from '@openint/shadcn/ui'
import {
Card,
CardContent,
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@openint/shadcn/ui'
import {titleCase} from '@openint/util/string-utils'
import {
ConnectionStatusPill,
Expand Down Expand Up @@ -36,7 +43,12 @@ export function ConnectionCard({
connection.connector?.display_name ||
titleCase(connection.connector_name)

const {borderColor} = getConnectionStatusStyles(connection.status)
const {
borderColor,
pillColor,
icon: StatusIcon,
message,
} = getConnectionStatusStyles(connection.status)

const handleMouseEnter = () => {
if (onPress) {
Expand All @@ -63,6 +75,38 @@ export function ConnectionCard({
)}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}>
{/* Status indicator in top-left corner with tooltip */}
{connection.status && connection.status !== 'healthy' && (
<TooltipProvider>
<Tooltip delayDuration={200}>
<TooltipTrigger asChild>
<div className="absolute left-2 top-2 z-10 cursor-help">
<div className={cn('h-2 w-2 rounded-full', pillColor)} />
</div>
</TooltipTrigger>
<TooltipContent
className="z-50 flex max-w-[260px] items-start gap-2.5"
side="bottom"
align="start"
sideOffset={5}>
<StatusIcon
className={cn(
'mt-0.5 h-4 w-4 flex-shrink-0',
connection.status === 'error'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inline status icon color logic duplicates styling. Consider extracting status-to-color mapping to a helper for consistency.

? 'text-rose-600'
: connection.status === 'disconnected'
? 'text-amber-600'
: 'text-slate-600',
)}
/>
<span className="text-background text-xs leading-relaxed">
{message}
</span>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}

<CardContent
className="flex h-full flex-col items-center justify-center p-4 py-2"
onClick={onPress}>
Comment on lines 110 to 112
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: onClick handler on CardContent could trigger both onPress and onReconnect when clicking the reconnect button

Expand Down Expand Up @@ -92,11 +136,15 @@ export function ConnectionCard({
{connection.id}
</pre>
)}
{connection.status && (
<ConnectionStatusPill
status={connection.status}
onClick={onReconnect}
/>

{/* Reconnect button for disconnected status */}
{connection.status === 'disconnected' && (
<div className="mt-2">
<ConnectionStatusPill
status={connection.status}
onClick={onReconnect}
/>
</div>
)}
Comment on lines +141 to 148
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: onReconnect prop might be undefined when status is disconnected. Add a check or make the prop required

Suggested change
{connection.status === 'disconnected' && (
<div className="mt-2">
<ConnectionStatusPill
status={connection.status}
onClick={onReconnect}
/>
</div>
)}
{connection.status === 'disconnected' && onReconnect && (
<div className="mt-2">
<ConnectionStatusPill
status={connection.status}
onClick={onReconnect}
/>
</div>
)}

</>
)}
Expand Down
31 changes: 21 additions & 10 deletions packages/ui-v1/domain-components/ConnectionStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@

import type {ConnectionExpanded} from '@openint/api-v1/models'

import {AlertCircle, CheckCircle2, HelpCircle, XCircle} from 'lucide-react'
import {
AlertCircle,
CheckCircle2,
HelpCircle,
RotateCcw,
XCircle,
} from 'lucide-react'
import React from 'react'
import {cn} from '@openint/shadcn/lib/utils'
import {
Expand Down Expand Up @@ -122,16 +128,21 @@ export function ConnectionStatusPill({
<Tooltip delayDuration={200}>
<TooltipTrigger asChild>
<div className={cn('inline-flex', className)}>
<div className={cn('flex items-center gap-1')}>
<div className={cn('h-2 w-2 rounded-full', pillColor)} />
{status !== 'disconnected' ? (
{status !== 'disconnected' ? (
<div className={cn('flex items-center gap-1')}>
<div className={cn('h-2 w-2 rounded-full', pillColor)} />
<div className="text-xs text-gray-500">{label}</div>
) : (
<Button variant="ghost" onClick={onClick}>
Reconnect
</Button>
)}
</div>
</div>
) : (
<Button
variant="outline"
size="sm"
onClick={onClick}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: onClick handler is optional but no disabled state handling

className="group h-7 gap-1.5 px-3 text-xs font-medium shadow-none transition-colors">
<RotateCcw className="h-3 w-3 transition-transform duration-500 group-hover:-rotate-[360deg]" />
Reconnect
</Button>
)}
</div>
</TooltipTrigger>
<TooltipContent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,29 @@ export const Error: Story = {

// Disconnected status
export const Disconnected: Story = {
args: {status: 'disconnected'},
args: {
status: 'disconnected',
onClick: () => alert('Reconnect clicked!'),
},
}

// Manual status
export const Manual: Story = {
args: {status: 'manual'},
}

// Reconnect button showcase
export const ReconnectButton: Story = {
args: {
status: 'disconnected',
onClick: () => console.log('Reconnecting...'),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Using console.log for demo purposes. Consider using alert() for consistency with other stories or add a comment explaining the logging choice.

},
parameters: {
docs: {
description: {
story:
'Shows the improved Reconnect button with icon and outline variant when connection is disconnected.',
},
},
},
}
9 changes: 9 additions & 0 deletions packages/ui-v1/domain-components/__stories__/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ const connections = {
settings: {},
disabled: false,
display_name: 'HubSpot Connection',
status: 'healthy' as const,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding 'status' field without proper type definition. The code is adding a 'status' property to objects that satisfy the ConnectionExpanded type, but there's no indication that this field is defined in the type. This will likely cause TypeScript type errors and could lead to runtime issues if the field is expected by other parts of the application. The status values ('healthy', 'error', 'disconnected', etc.) are being forcibly typed using 'as const' without proper validation against an expected enum or type definition.


React with 👍 to tell me that this comment was useful, or 👎 if not (and I'll stop posting more comments like this in the future)

},
'hubspot-with-integration': {
id: 'conn_hubspot_01HN4QZXG7YPBR8MXQT4KBWQ5N',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Duplicate connection ID with 'hubspot-basic' (line 116)

Suggested change
id: 'conn_hubspot_01HN4QZXG7YPBR8MXQT4KBWQ5N',
id: 'conn_hubspot_01HN4QZXG7YPBR8MXQT4KBWQ5P',

Expand All @@ -139,6 +140,7 @@ const connections = {
settings: {},
disabled: false,
display_name: 'HubSpot Connection with Integration',
status: 'healthy' as const,
},
'hubspot-without-logo': {
id: 'conn_hubspot_123',
Expand All @@ -156,6 +158,7 @@ const connections = {
settings: {},
disabled: false,
display_name: 'HubSpot No Logo',
status: 'error' as const,
},
'notion-basic': {
id: 'conn_notion_01HN4QZXG7YPBR8MXQT4KBWQ5N',
Expand All @@ -170,6 +173,7 @@ const connections = {
settings: {},
disabled: false,
display_name: 'Notion Connection',
status: 'disconnected' as const,
},
'notion-with-integration': {
id: 'conn_notion_01HN4QZXG7YPBR8MXQT4KBWQ5N',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Duplicate connection ID with 'notion-basic' (line 164)

Suggested change
id: 'conn_notion_01HN4QZXG7YPBR8MXQT4KBWQ5N',
id: 'conn_notion_01HN4QZXG7YPBR8MXQT4KBWQ5Q',

Expand All @@ -184,6 +188,7 @@ const connections = {
settings: {},
disabled: false,
display_name: 'Notion Connection with Integration',
status: 'manual' as const,
},
'notion-without-logo': {
id: 'conn_notion_123',
Expand All @@ -201,6 +206,7 @@ const connections = {
settings: {},
disabled: false,
display_name: 'Notion No Logo',
status: 'unknown' as const,
},
'google-drive-basic': {
id: 'conn_google-drive_01HN4QZXG7YPBR8MXQT4KBWQ5N',
Expand All @@ -215,6 +221,7 @@ const connections = {
settings: {},
disabled: false,
display_name: 'Google Drive Connection',
status: 'healthy' as const,
},
'google-drive-with-integration': {
id: 'conn_google-drive_01HN4QZXG7YPBR8MXQT4KBWQ5N',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Duplicate connection ID with 'google-drive-basic' (line 212)

Suggested change
id: 'conn_google-drive_01HN4QZXG7YPBR8MXQT4KBWQ5N',
id: 'conn_google-drive_01HN4QZXG7YPBR8MXQT4KBWQ5R',

Expand All @@ -229,6 +236,7 @@ const connections = {
settings: {},
disabled: false,
display_name: 'Google Drive Connection with Integration',
status: 'healthy' as const,
},
'google-drive-without-logo': {
id: 'conn_gdrive_123',
Expand All @@ -246,6 +254,7 @@ const connections = {
settings: {},
disabled: false,
display_name: 'Google Drive No Logo',
status: 'disconnected' as const,
},
} satisfies Record<string, ConnectionExpanded>

Expand Down