Skip to content

Styling connect #406

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

Merged
merged 16 commits into from
Mar 30, 2025
Merged
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
87 changes: 87 additions & 0 deletions .cursorrules
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
You are an expert senior software engineer specializing in modern web development, with deep expertise in TypeScript, React 19, Next.js 15 (App Router), Vercel AI SDK, Shadcn UI, Radix UI, and Tailwind CSS. You are thoughtful, precise, and focus on delivering high-quality, maintainable solutions.

## Analysis Process

Before responding to any request, follow these steps:

1. Request Analysis
- Determine task type (code creation, debugging, architecture, etc.)
- Identify languages and frameworks involved
- Note explicit and implicit requirements
- Define core problem and desired outcome
- Consider project context and constraints

2. Solution Planning
- Break down the solution into logical steps
- Consider modularity and reusability
- Identify necessary files and dependencies
- Evaluate alternative approaches
- Plan for testing and validation

3. Implementation Strategy
- Choose appropriate design patterns
- Consider performance implications
- Plan for error handling and edge cases
- Ensure accessibility compliance
- Verify best practices alignment

## Code Style and Structure

### General Principles

- Write concise, readable TypeScript code
- Use functional and declarative programming patterns
- Follow DRY (Don't Repeat Yourself) principle
- Implement early returns for better readability
- Structure components logically: exports, subcomponents, helpers, types

### Naming Conventions

- Use descriptive names with auxiliary verbs (isLoading, hasError)
- Prefix event handlers with "handle" (handleClick, handleSubmit)
- Use lowercase with dashes for directories (components/auth-wizard)
- Favor named exports for components

### TypeScript Usage

- Use TypeScript for all code
- Prefer interfaces over types
- Avoid enums; use const maps instead
- Implement proper type safety and inference
- Use `satisfies` operator for type validation

## React 19 and Next.js 15 Best Practices

### Component Architecture

- Favor React Server Components (RSC) where possible
- Minimize 'use client' directives
- Implement proper error boundaries
- Use Suspense for async operations
- Optimize for performance and Web Vitals

### State Management

- Use `useActionState` instead of deprecated `useFormState`
- Leverage enhanced `useFormStatus` with new properties (data, method, action)
- Implement URL state management with 'nuqs'
- Minimize client-side state

### Async Request APIs

```typescript
// Always use async versions of runtime APIs
const cookieStore = await cookies()
const headersList = await headers()
const { isEnabled } = await draftMode()

// Handle async params in layouts/pages
const params = await props.params
const searchParams = await props.searchParams


## Additional

- Classname should be combined using the `cn` util from `@openint/shadcn/lib/utils` whenever we create a component
- Always use with `React.` prefix for things like React.useState and React.useEffect, or whenever referencing built-in hooks.
- Use Components should reference Core models whwnever possible rather than repeating types. Use consistent mocks from fixtures file
56 changes: 33 additions & 23 deletions apps/web/app/(v1)/connect-v1/client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import dynamic from 'next/dynamic'
import React from 'react'
import type {AppRouterOutput} from '@openint/api-v1/routers'
import {AppRouterOutput} from '@openint/api-v1'
import {ConnectorConfig} from '@openint/api-v1/models'
import type {ConnectorClient} from '@openint/cdk'
import {extractId} from '@openint/cdk'
import {Button} from '@openint/shadcn/ui'
import {CommandPopover} from '@openint/ui-v1'
import {Label} from '@openint/shadcn/ui'
import {CommandPopover, DataTileView} from '@openint/ui-v1'
import {ConnectionCard} from '@openint/ui-v1/domain-components/ConnectionCard'
import {ConnectorConfigCard} from '@openint/ui-v1/domain-components/ConnectorConfigCard'
import {useMutation, useSuspenseQuery} from '@openint/ui-v1/trpc'
import {useTRPC} from '../console/(authenticated)/client'
import {useCommandDefinitionMap} from '../GlobalCommandBarProvider'
Expand Down Expand Up @@ -53,16 +55,16 @@ const ConnectorClientComponents = Object.fromEntries(
)

export function AddConnectionInner({
connectorConfigId,
connectorConfig,
...props
}: {
connectorConfigId: string
connectorConfig: ConnectorConfig<'connector'>
onReady?: (ctx: {state: string}, name: string) => void
initialData?: Promise<AppRouterOutput['preConnect']>
}) {
const initialData = React.use(props.initialData ?? Promise.resolve(undefined))

const name = extractId(connectorConfigId as `ccfg_${string}`)[1]
const name = connectorConfig.connector_name

console.log('AddConnectionInner rendering', name)

Expand All @@ -73,7 +75,7 @@ export function AddConnectionInner({
const preConnectRes = useSuspenseQuery(
trpc.preConnect.queryOptions(
{
id: connectorConfigId,
id: connectorConfig.id,
data: {
connector_name: name,
input: {},
Expand All @@ -90,21 +92,21 @@ export function AddConnectionInner({
const handleConnect = React.useCallback(async () => {
console.log('ref.current', ref.current)
const connectRes = await ref.current?.(preConnectRes.data.output, {
connectorConfigId: connectorConfigId as `ccfg_${string}`,
connectorConfigId: connectorConfig.id as `ccfg_${string}`,
connectionExternalId: undefined,
integrationExternalId: undefined,
})
console.log('connectRes', connectRes)
const postConnectRes = await postConnect.mutateAsync({
id: connectorConfigId,
id: connectorConfig.id,
data: {
connector_name: name,
input: connectRes,
},
options: {},
})
console.log('postConnectRes', postConnectRes)
}, [connectorConfigId, preConnectRes])
}, [connectorConfig, preConnectRes])

const Component =
ConnectorClientComponents[name as keyof typeof ConnectorClientComponents]
Expand All @@ -128,43 +130,51 @@ export function AddConnectionInner({
// setFn(c)
}, [])}
/>
<Button onClick={handleConnect}>Connect with {name}</Button>

<ConnectorConfigCard
displayNameLocation="right"
connectorConfig={connectorConfig}
onPress={() => handleConnect()}>
<Label className="text-muted-foreground pointer-events-none ml-auto text-sm">
Connect
</Label>
</ConnectorConfigCard>
</>
)
}

export function MyConnectionsClient(props: {
// TODO: Figure out how to type this without duplication

connector_name?: string
initialData?: Promise<AppRouterOutput['listConnections']>
}) {
const initialData = React.use(props.initialData ?? Promise.resolve(undefined))
const trpc = useTRPC()
const res = useSuspenseQuery(
trpc.listConnections.queryOptions(
{connector_name: props.connector_name},
{connector_name: props.connector_name, expand: ['connector']},
initialData ? {initialData} : undefined,
),
)

const definitions = useCommandDefinitionMap()
return (
<>
<h1 className="text-3xl">My connections</h1>
{res.data.items.map((conn) => (
<div key={conn.id} className="p-4">
<h2 className="text-2xl"> {conn.id}</h2>
<DataTileView
data={res.data.items}
columns={[]}
getItemId={(conn) => conn.id}
renderItem={(conn) => (
<ConnectionCard connection={conn} className="relative">
<CommandPopover
className="absolute right-2 top-2"
hideGroupHeadings
initialParams={{
connection_id: conn.id,
}}
ctx={{}}
definitions={definitions}
/>
</div>
))}
</>
</ConnectionCard>
)}
/>
)
}
40 changes: 18 additions & 22 deletions apps/web/app/(v1)/connect-v1/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Suspense} from 'react'
import {ConnectorConfig} from '@openint/api-v1/models'
import type {Viewer} from '@openint/cdk'
import {extractId} from '@openint/cdk'
import {TabsContent, TabsList, TabsTrigger} from '@openint/shadcn/ui/tabs'
import {z} from '@openint/util'
import {parsePageProps, type PageProps} from '@/lib-common/next-utils'
Expand Down Expand Up @@ -54,7 +54,7 @@ export default async function Page(
const api = createAPICaller(viewer)

return (
<div>
<div className="p-4">
<style
dangerouslySetInnerHTML={{
__html: `
Expand All @@ -66,10 +66,6 @@ export default async function Page(
`,
}}
/>

<pre className="bg-background">
<code>{JSON.stringify(viewer, null, 2)}</code>
</pre>
<ClientApp token={token!}>
<GlobalCommandBarProvider>
{/* <TabsClient defaultValue={(await props.searchParams).tab ?? 'my-connections'}> */}
Expand All @@ -79,18 +75,19 @@ export default async function Page(
<TabsTrigger value="my-connections">My connections</TabsTrigger>
<TabsTrigger value="add-connection">Add connection</TabsTrigger>
</TabsList>
<TabsContent value="my-connections" className="p-4">
<TabsContent value="my-connections" className="pt-2">
<Suspense fallback={<Fallback />}>
<MyConnectionsClient
// TODO: How to avoid the duplicate construction of input parameters?
connector_name={searchParams.connector_name}
initialData={api.listConnections({
connector_name: searchParams.connector_name,
expand: ['connector'],
})}
/>
</Suspense>
</TabsContent>
<TabsContent value="add-connection" className="p-4">
<TabsContent value="add-connection" className="pt-2">
<Suspense fallback={<Fallback />}>
<AddConnections
viewer={viewer}
Expand All @@ -117,41 +114,40 @@ async function AddConnections({

const res = await api.listConnectorConfigs({
connector_name,
expand: 'connector',
})

return (
<>
<div className="flex flex-col gap-4">
{res.items.map((ccfg) => (
<Suspense key={ccfg.id} fallback={<Fallback />}>
<div key={ccfg.id} className="p-4">
<h1 className="text-3xl">Add {ccfg.id} connection</h1>
<AddConnectionServer connectorConfigId={ccfg.id} viewer={viewer} />
</div>
<AddConnectionServer
key={ccfg.id}
connectorConfig={ccfg}
viewer={viewer}
/>
</Suspense>
))}
</>
</div>
)
}

function AddConnectionServer({
viewer,
connectorConfigId,
connectorConfig,
}: {
viewer: Viewer
connectorConfigId: string
connectorConfig: ConnectorConfig<'connector'>
}) {
const api = createAPICaller(viewer)
const name = extractId(connectorConfigId as `ccfg_${string}`)[1]
const name = connectorConfig.connector_name
const res = api.preConnect({
id: connectorConfigId,
id: connectorConfig.id,
data: {connector_name: name, input: {}},
options: {},
})

return (
<AddConnectionInner
connectorConfigId={connectorConfigId}
initialData={res}
/>
<AddConnectionInner connectorConfig={connectorConfig} initialData={res} />
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,7 @@ export function ConnectorConfigList(props: {
accessorKey: 'display_name',
cell: ({row}) => {
const connector = row.original.connector
return (
<ConnectorTableCell
connector={{...connector, stage: connector.stage || 'alpha'}}
/>
)
return connector ? <ConnectorTableCell connector={connector} /> : null
},
},
{
Expand Down Expand Up @@ -127,12 +123,12 @@ export function ConnectorConfigList(props: {
) => {
// Find the full connector data from the connectors list
const fullConnector = connectorRes.data.find(
(c) => c.name === ccfg.connector.name,
(c) => c.name === ccfg.connector?.name,
)

setSelectedCcfg(ccfg)
// Use the full connector data if available, otherwise use the one from ccfg
setSelectedConnector(fullConnector || ccfg.connector)
setSelectedConnector(fullConnector || ccfg.connector || null)
setSheetOpen(true)
}

Expand Down
Loading