Skip to content

Commit c8f231d

Browse files
authored
Styling connect (#406)
* Scaffold connnector card proper * Add some fixture data for connections card and start of .cursorrules * Further update fixtures for connect * Bettering story * Add grid for connection * Add DataTileView component and its associated stories for rendering connection and simple item tiles * Remove SimpleDataTable and related story files; update .cursorrules with additional guidelines for component creation and usage. * Use ConnectionCard in Connect * expand on connector when retrieving connection * Removing irrelevant stuff * Scaffold ConnectorConfigCard * use the connector config card in connect * Fix type errors * fix more tests * Fix more tests
1 parent f6df933 commit c8f231d

25 files changed

+899
-1130
lines changed

.cursorrules

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
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.
2+
3+
## Analysis Process
4+
5+
Before responding to any request, follow these steps:
6+
7+
1. Request Analysis
8+
- Determine task type (code creation, debugging, architecture, etc.)
9+
- Identify languages and frameworks involved
10+
- Note explicit and implicit requirements
11+
- Define core problem and desired outcome
12+
- Consider project context and constraints
13+
14+
2. Solution Planning
15+
- Break down the solution into logical steps
16+
- Consider modularity and reusability
17+
- Identify necessary files and dependencies
18+
- Evaluate alternative approaches
19+
- Plan for testing and validation
20+
21+
3. Implementation Strategy
22+
- Choose appropriate design patterns
23+
- Consider performance implications
24+
- Plan for error handling and edge cases
25+
- Ensure accessibility compliance
26+
- Verify best practices alignment
27+
28+
## Code Style and Structure
29+
30+
### General Principles
31+
32+
- Write concise, readable TypeScript code
33+
- Use functional and declarative programming patterns
34+
- Follow DRY (Don't Repeat Yourself) principle
35+
- Implement early returns for better readability
36+
- Structure components logically: exports, subcomponents, helpers, types
37+
38+
### Naming Conventions
39+
40+
- Use descriptive names with auxiliary verbs (isLoading, hasError)
41+
- Prefix event handlers with "handle" (handleClick, handleSubmit)
42+
- Use lowercase with dashes for directories (components/auth-wizard)
43+
- Favor named exports for components
44+
45+
### TypeScript Usage
46+
47+
- Use TypeScript for all code
48+
- Prefer interfaces over types
49+
- Avoid enums; use const maps instead
50+
- Implement proper type safety and inference
51+
- Use `satisfies` operator for type validation
52+
53+
## React 19 and Next.js 15 Best Practices
54+
55+
### Component Architecture
56+
57+
- Favor React Server Components (RSC) where possible
58+
- Minimize 'use client' directives
59+
- Implement proper error boundaries
60+
- Use Suspense for async operations
61+
- Optimize for performance and Web Vitals
62+
63+
### State Management
64+
65+
- Use `useActionState` instead of deprecated `useFormState`
66+
- Leverage enhanced `useFormStatus` with new properties (data, method, action)
67+
- Implement URL state management with 'nuqs'
68+
- Minimize client-side state
69+
70+
### Async Request APIs
71+
72+
```typescript
73+
// Always use async versions of runtime APIs
74+
const cookieStore = await cookies()
75+
const headersList = await headers()
76+
const { isEnabled } = await draftMode()
77+
78+
// Handle async params in layouts/pages
79+
const params = await props.params
80+
const searchParams = await props.searchParams
81+
82+
83+
## Additional
84+
85+
- Classname should be combined using the `cn` util from `@openint/shadcn/lib/utils` whenever we create a component
86+
- Always use with `React.` prefix for things like React.useState and React.useEffect, or whenever referencing built-in hooks.
87+
- Use Components should reference Core models whwnever possible rather than repeating types. Use consistent mocks from fixtures file

apps/web/app/(v1)/connect-v1/client.tsx

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
import dynamic from 'next/dynamic'
44
import React from 'react'
5-
import type {AppRouterOutput} from '@openint/api-v1/routers'
5+
import {AppRouterOutput} from '@openint/api-v1'
6+
import {ConnectorConfig} from '@openint/api-v1/models'
67
import type {ConnectorClient} from '@openint/cdk'
7-
import {extractId} from '@openint/cdk'
8-
import {Button} from '@openint/shadcn/ui'
9-
import {CommandPopover} from '@openint/ui-v1'
8+
import {Label} from '@openint/shadcn/ui'
9+
import {CommandPopover, DataTileView} from '@openint/ui-v1'
10+
import {ConnectionCard} from '@openint/ui-v1/domain-components/ConnectionCard'
11+
import {ConnectorConfigCard} from '@openint/ui-v1/domain-components/ConnectorConfigCard'
1012
import {useMutation, useSuspenseQuery} from '@openint/ui-v1/trpc'
1113
import {useTRPC} from '../console/(authenticated)/client'
1214
import {useCommandDefinitionMap} from '../GlobalCommandBarProvider'
@@ -53,16 +55,16 @@ const ConnectorClientComponents = Object.fromEntries(
5355
)
5456

5557
export function AddConnectionInner({
56-
connectorConfigId,
58+
connectorConfig,
5759
...props
5860
}: {
59-
connectorConfigId: string
61+
connectorConfig: ConnectorConfig<'connector'>
6062
onReady?: (ctx: {state: string}, name: string) => void
6163
initialData?: Promise<AppRouterOutput['preConnect']>
6264
}) {
6365
const initialData = React.use(props.initialData ?? Promise.resolve(undefined))
6466

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

6769
console.log('AddConnectionInner rendering', name)
6870

@@ -73,7 +75,7 @@ export function AddConnectionInner({
7375
const preConnectRes = useSuspenseQuery(
7476
trpc.preConnect.queryOptions(
7577
{
76-
id: connectorConfigId,
78+
id: connectorConfig.id,
7779
data: {
7880
connector_name: name,
7981
input: {},
@@ -90,21 +92,21 @@ export function AddConnectionInner({
9092
const handleConnect = React.useCallback(async () => {
9193
console.log('ref.current', ref.current)
9294
const connectRes = await ref.current?.(preConnectRes.data.output, {
93-
connectorConfigId: connectorConfigId as `ccfg_${string}`,
95+
connectorConfigId: connectorConfig.id as `ccfg_${string}`,
9496
connectionExternalId: undefined,
9597
integrationExternalId: undefined,
9698
})
9799
console.log('connectRes', connectRes)
98100
const postConnectRes = await postConnect.mutateAsync({
99-
id: connectorConfigId,
101+
id: connectorConfig.id,
100102
data: {
101103
connector_name: name,
102104
input: connectRes,
103105
},
104106
options: {},
105107
})
106108
console.log('postConnectRes', postConnectRes)
107-
}, [connectorConfigId, preConnectRes])
109+
}, [connectorConfig, preConnectRes])
108110

109111
const Component =
110112
ConnectorClientComponents[name as keyof typeof ConnectorClientComponents]
@@ -128,43 +130,51 @@ export function AddConnectionInner({
128130
// setFn(c)
129131
}, [])}
130132
/>
131-
<Button onClick={handleConnect}>Connect with {name}</Button>
133+
134+
<ConnectorConfigCard
135+
displayNameLocation="right"
136+
connectorConfig={connectorConfig}
137+
onPress={() => handleConnect()}>
138+
<Label className="text-muted-foreground pointer-events-none ml-auto text-sm">
139+
Connect
140+
</Label>
141+
</ConnectorConfigCard>
132142
</>
133143
)
134144
}
135145

136146
export function MyConnectionsClient(props: {
137-
// TODO: Figure out how to type this without duplication
138-
139147
connector_name?: string
140148
initialData?: Promise<AppRouterOutput['listConnections']>
141149
}) {
142150
const initialData = React.use(props.initialData ?? Promise.resolve(undefined))
143151
const trpc = useTRPC()
144152
const res = useSuspenseQuery(
145153
trpc.listConnections.queryOptions(
146-
{connector_name: props.connector_name},
154+
{connector_name: props.connector_name, expand: ['connector']},
147155
initialData ? {initialData} : undefined,
148156
),
149157
)
150158

151159
const definitions = useCommandDefinitionMap()
152160
return (
153-
<>
154-
<h1 className="text-3xl">My connections</h1>
155-
{res.data.items.map((conn) => (
156-
<div key={conn.id} className="p-4">
157-
<h2 className="text-2xl"> {conn.id}</h2>
161+
<DataTileView
162+
data={res.data.items}
163+
columns={[]}
164+
getItemId={(conn) => conn.id}
165+
renderItem={(conn) => (
166+
<ConnectionCard connection={conn} className="relative">
158167
<CommandPopover
168+
className="absolute right-2 top-2"
159169
hideGroupHeadings
160170
initialParams={{
161171
connection_id: conn.id,
162172
}}
163173
ctx={{}}
164174
definitions={definitions}
165175
/>
166-
</div>
167-
))}
168-
</>
176+
</ConnectionCard>
177+
)}
178+
/>
169179
)
170180
}

apps/web/app/(v1)/connect-v1/page.tsx

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {Suspense} from 'react'
2+
import {ConnectorConfig} from '@openint/api-v1/models'
23
import type {Viewer} from '@openint/cdk'
3-
import {extractId} from '@openint/cdk'
44
import {TabsContent, TabsList, TabsTrigger} from '@openint/shadcn/ui/tabs'
55
import {z} from '@openint/util'
66
import {parsePageProps, type PageProps} from '@/lib-common/next-utils'
@@ -54,7 +54,7 @@ export default async function Page(
5454
const api = createAPICaller(viewer)
5555

5656
return (
57-
<div>
57+
<div className="p-4">
5858
<style
5959
dangerouslySetInnerHTML={{
6060
__html: `
@@ -66,10 +66,6 @@ export default async function Page(
6666
`,
6767
}}
6868
/>
69-
70-
<pre className="bg-background">
71-
<code>{JSON.stringify(viewer, null, 2)}</code>
72-
</pre>
7369
<ClientApp token={token!}>
7470
<GlobalCommandBarProvider>
7571
{/* <TabsClient defaultValue={(await props.searchParams).tab ?? 'my-connections'}> */}
@@ -79,18 +75,19 @@ export default async function Page(
7975
<TabsTrigger value="my-connections">My connections</TabsTrigger>
8076
<TabsTrigger value="add-connection">Add connection</TabsTrigger>
8177
</TabsList>
82-
<TabsContent value="my-connections" className="p-4">
78+
<TabsContent value="my-connections" className="pt-2">
8379
<Suspense fallback={<Fallback />}>
8480
<MyConnectionsClient
8581
// TODO: How to avoid the duplicate construction of input parameters?
8682
connector_name={searchParams.connector_name}
8783
initialData={api.listConnections({
8884
connector_name: searchParams.connector_name,
85+
expand: ['connector'],
8986
})}
9087
/>
9188
</Suspense>
9289
</TabsContent>
93-
<TabsContent value="add-connection" className="p-4">
90+
<TabsContent value="add-connection" className="pt-2">
9491
<Suspense fallback={<Fallback />}>
9592
<AddConnections
9693
viewer={viewer}
@@ -117,41 +114,40 @@ async function AddConnections({
117114

118115
const res = await api.listConnectorConfigs({
119116
connector_name,
117+
expand: 'connector',
120118
})
121119

122120
return (
123-
<>
121+
<div className="flex flex-col gap-4">
124122
{res.items.map((ccfg) => (
125123
<Suspense key={ccfg.id} fallback={<Fallback />}>
126-
<div key={ccfg.id} className="p-4">
127-
<h1 className="text-3xl">Add {ccfg.id} connection</h1>
128-
<AddConnectionServer connectorConfigId={ccfg.id} viewer={viewer} />
129-
</div>
124+
<AddConnectionServer
125+
key={ccfg.id}
126+
connectorConfig={ccfg}
127+
viewer={viewer}
128+
/>
130129
</Suspense>
131130
))}
132-
</>
131+
</div>
133132
)
134133
}
135134

136135
function AddConnectionServer({
137136
viewer,
138-
connectorConfigId,
137+
connectorConfig,
139138
}: {
140139
viewer: Viewer
141-
connectorConfigId: string
140+
connectorConfig: ConnectorConfig<'connector'>
142141
}) {
143142
const api = createAPICaller(viewer)
144-
const name = extractId(connectorConfigId as `ccfg_${string}`)[1]
143+
const name = connectorConfig.connector_name
145144
const res = api.preConnect({
146-
id: connectorConfigId,
145+
id: connectorConfig.id,
147146
data: {connector_name: name, input: {}},
148147
options: {},
149148
})
150149

151150
return (
152-
<AddConnectionInner
153-
connectorConfigId={connectorConfigId}
154-
initialData={res}
155-
/>
151+
<AddConnectionInner connectorConfig={connectorConfig} initialData={res} />
156152
)
157153
}

apps/web/app/(v1)/console/(authenticated)/connector-config/client.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,7 @@ export function ConnectorConfigList(props: {
9090
accessorKey: 'display_name',
9191
cell: ({row}) => {
9292
const connector = row.original.connector
93-
return (
94-
<ConnectorTableCell
95-
connector={{...connector, stage: connector.stage || 'alpha'}}
96-
/>
97-
)
93+
return connector ? <ConnectorTableCell connector={connector} /> : null
9894
},
9995
},
10096
{
@@ -127,12 +123,12 @@ export function ConnectorConfigList(props: {
127123
) => {
128124
// Find the full connector data from the connectors list
129125
const fullConnector = connectorRes.data.find(
130-
(c) => c.name === ccfg.connector.name,
126+
(c) => c.name === ccfg.connector?.name,
131127
)
132128

133129
setSelectedCcfg(ccfg)
134130
// Use the full connector data if available, otherwise use the one from ccfg
135-
setSelectedConnector(fullConnector || ccfg.connector)
131+
setSelectedConnector(fullConnector || ccfg.connector || null)
136132
setSheetOpen(true)
137133
}
138134

0 commit comments

Comments
 (0)