Skip to content

Commit 951c8fd

Browse files
authored
feat(integrations): add integrationType and tags classification to all blocks (#3702)
* feat(integrations): add integrationType and tags classification to all blocks * improvement(integrations): replace generic api/oauth tags with use-case-oriented tags * lint * upgrade turbo
1 parent 4a34ac3 commit 951c8fd

File tree

182 files changed

+1324
-362
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

182 files changed

+1324
-362
lines changed

.claude/commands/add-block.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ When the user asks you to create a block:
1919
```typescript
2020
import { {ServiceName}Icon } from '@/components/icons'
2121
import type { BlockConfig } from '@/blocks/types'
22-
import { AuthMode } from '@/blocks/types'
22+
import { AuthMode, IntegrationType } from '@/blocks/types'
2323
import { getScopesForService } from '@/lib/oauth/utils'
2424

2525
export const {ServiceName}Block: BlockConfig = {
@@ -29,6 +29,8 @@ export const {ServiceName}Block: BlockConfig = {
2929
longDescription: 'Detailed description for docs',
3030
docsLink: 'https://docs.sim.ai/tools/{service}',
3131
category: 'tools', // 'tools' | 'blocks' | 'triggers'
32+
integrationType: IntegrationType.X, // Primary category (see IntegrationType enum)
33+
tags: ['oauth', 'api'], // Cross-cutting tags (see IntegrationTag type)
3234
bgColor: '#HEXCOLOR', // Brand color
3335
icon: {ServiceName}Icon,
3436

@@ -629,7 +631,7 @@ export const registry: Record<string, BlockConfig> = {
629631
```typescript
630632
import { ServiceIcon } from '@/components/icons'
631633
import type { BlockConfig } from '@/blocks/types'
632-
import { AuthMode } from '@/blocks/types'
634+
import { AuthMode, IntegrationType } from '@/blocks/types'
633635
import { getScopesForService } from '@/lib/oauth/utils'
634636

635637
export const ServiceBlock: BlockConfig = {
@@ -639,6 +641,8 @@ export const ServiceBlock: BlockConfig = {
639641
longDescription: 'Full description for documentation...',
640642
docsLink: 'https://docs.sim.ai/tools/service',
641643
category: 'tools',
644+
integrationType: IntegrationType.DeveloperTools,
645+
tags: ['oauth', 'api'],
642646
bgColor: '#FF6B6B',
643647
icon: ServiceIcon,
644648
authMode: AuthMode.OAuth,
@@ -796,6 +800,8 @@ All tool IDs referenced in `tools.access` and returned by `tools.config.tool` MU
796800

797801
## Checklist Before Finishing
798802

803+
- [ ] `integrationType` is set to the correct `IntegrationType` enum value
804+
- [ ] `tags` array includes all applicable `IntegrationTag` values
799805
- [ ] All subBlocks have `id`, `title` (except switch), and `type`
800806
- [ ] Conditions use correct syntax (field, value, not, and)
801807
- [ ] DependsOn set for fields that need other values

.claude/commands/add-integration.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export const {service}{Action}Tool: ToolConfig<Params, Response> = {
113113
```typescript
114114
import { {Service}Icon } from '@/components/icons'
115115
import type { BlockConfig } from '@/blocks/types'
116-
import { AuthMode } from '@/blocks/types'
116+
import { AuthMode, IntegrationType } from '@/blocks/types'
117117
import { getScopesForService } from '@/lib/oauth/utils'
118118

119119
export const {Service}Block: BlockConfig = {
@@ -123,6 +123,8 @@ export const {Service}Block: BlockConfig = {
123123
longDescription: '...',
124124
docsLink: 'https://docs.sim.ai/tools/{service}',
125125
category: 'tools',
126+
integrationType: IntegrationType.X, // Primary category (see IntegrationType enum)
127+
tags: ['oauth', 'api'], // Cross-cutting tags (see IntegrationTag type)
126128
bgColor: '#HEXCOLOR',
127129
icon: {Service}Icon,
128130
authMode: AuthMode.OAuth, // or AuthMode.ApiKey
@@ -410,6 +412,8 @@ If creating V2 versions (API-aligned outputs):
410412

411413
### Block
412414
- [ ] Created `blocks/blocks/{service}.ts`
415+
- [ ] Set `integrationType` to the correct `IntegrationType` enum value
416+
- [ ] Set `tags` array with all applicable `IntegrationTag` values
413417
- [ ] Defined operation dropdown with all operations
414418
- [ ] Added credential field with `requiredScopes: getScopesForService('{service}')`
415419
- [ ] Added conditional fields per operation

apps/sim/app/(landing)/integrations/components/integration-grid.tsx

Lines changed: 109 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,54 +6,132 @@ import { blockTypeToIconMap } from '@/app/(landing)/integrations/data/icon-mappi
66
import type { Integration } from '@/app/(landing)/integrations/data/types'
77
import { IntegrationCard } from './integration-card'
88

9+
const CATEGORY_LABELS: Record<string, string> = {
10+
ai: 'AI',
11+
analytics: 'Analytics',
12+
automation: 'Automation',
13+
communication: 'Communication',
14+
crm: 'CRM',
15+
'customer-support': 'Customer Support',
16+
databases: 'Databases',
17+
design: 'Design',
18+
'developer-tools': 'Developer Tools',
19+
documents: 'Documents',
20+
ecommerce: 'E-commerce',
21+
email: 'Email',
22+
'file-storage': 'File Storage',
23+
hr: 'HR',
24+
media: 'Media',
25+
productivity: 'Productivity',
26+
'sales-intelligence': 'Sales Intelligence',
27+
search: 'Search',
28+
security: 'Security',
29+
social: 'Social',
30+
other: 'Other',
31+
} as const
32+
933
interface IntegrationGridProps {
1034
integrations: Integration[]
1135
}
1236

1337
export function IntegrationGrid({ integrations }: IntegrationGridProps) {
1438
const [query, setQuery] = useState('')
39+
const [activeCategory, setActiveCategory] = useState<string | null>(null)
40+
41+
const availableCategories = useMemo(() => {
42+
const counts = new Map<string, number>()
43+
for (const i of integrations) {
44+
if (i.integrationType) {
45+
counts.set(i.integrationType, (counts.get(i.integrationType) || 0) + 1)
46+
}
47+
}
48+
return Array.from(counts.entries())
49+
.sort((a, b) => b[1] - a[1])
50+
.map(([key]) => key)
51+
}, [integrations])
1552

1653
const filtered = useMemo(() => {
54+
let results = integrations
55+
56+
if (activeCategory) {
57+
results = results.filter((i) => i.integrationType === activeCategory)
58+
}
59+
1760
const q = query.trim().toLowerCase()
18-
if (!q) return integrations
19-
return integrations.filter(
20-
(i) =>
21-
i.name.toLowerCase().includes(q) ||
22-
i.description.toLowerCase().includes(q) ||
23-
i.operations.some(
24-
(op) => op.name.toLowerCase().includes(q) || op.description.toLowerCase().includes(q)
25-
) ||
26-
i.triggers.some((t) => t.name.toLowerCase().includes(q))
27-
)
28-
}, [integrations, query])
61+
if (q) {
62+
results = results.filter(
63+
(i) =>
64+
i.name.toLowerCase().includes(q) ||
65+
i.description.toLowerCase().includes(q) ||
66+
i.operations.some(
67+
(op) => op.name.toLowerCase().includes(q) || op.description.toLowerCase().includes(q)
68+
) ||
69+
i.triggers.some((t) => t.name.toLowerCase().includes(q))
70+
)
71+
}
72+
73+
return results
74+
}, [integrations, query, activeCategory])
2975

3076
return (
3177
<div>
32-
<div className='relative mb-8 max-w-[480px]'>
33-
<svg
34-
aria-hidden='true'
35-
className='-translate-y-1/2 pointer-events-none absolute top-1/2 left-3 h-4 w-4 text-[#555]'
36-
fill='none'
37-
stroke='currentColor'
38-
strokeWidth={2}
39-
viewBox='0 0 24 24'
78+
<div className='mb-6 flex flex-col gap-4 sm:flex-row sm:items-center'>
79+
<div className='relative max-w-[480px] flex-1'>
80+
<svg
81+
aria-hidden='true'
82+
className='-translate-y-1/2 pointer-events-none absolute top-1/2 left-3 h-4 w-4 text-[#555]'
83+
fill='none'
84+
stroke='currentColor'
85+
strokeWidth={2}
86+
viewBox='0 0 24 24'
87+
>
88+
<circle cx={11} cy={11} r={8} />
89+
<path d='m21 21-4.35-4.35' />
90+
</svg>
91+
<Input
92+
type='search'
93+
placeholder='Search integrations, tools, or triggers…'
94+
value={query}
95+
onChange={(e) => setQuery(e.target.value)}
96+
className='pl-9'
97+
aria-label='Search integrations'
98+
/>
99+
</div>
100+
</div>
101+
102+
<div className='mb-8 flex flex-wrap gap-2'>
103+
<button
104+
type='button'
105+
onClick={() => setActiveCategory(null)}
106+
className={`rounded-md border px-3 py-1 text-[12px] transition-colors ${
107+
activeCategory === null
108+
? 'border-[#555] bg-[#333] text-[#ECECEC]'
109+
: 'border-[#2A2A2A] bg-transparent text-[#999] hover:border-[#3d3d3d] hover:text-[#ECECEC]'
110+
}`}
40111
>
41-
<circle cx={11} cy={11} r={8} />
42-
<path d='m21 21-4.35-4.35' />
43-
</svg>
44-
<Input
45-
type='search'
46-
placeholder='Search integrations, tools, or triggers…'
47-
value={query}
48-
onChange={(e) => setQuery(e.target.value)}
49-
className='pl-9'
50-
aria-label='Search integrations'
51-
/>
112+
All
113+
</button>
114+
{availableCategories.map((cat) => (
115+
<button
116+
key={cat}
117+
type='button'
118+
onClick={() => setActiveCategory(activeCategory === cat ? null : cat)}
119+
className={`rounded-md border px-3 py-1 text-[12px] transition-colors ${
120+
activeCategory === cat
121+
? 'border-[#555] bg-[#333] text-[#ECECEC]'
122+
: 'border-[#2A2A2A] bg-transparent text-[#999] hover:border-[#3d3d3d] hover:text-[#ECECEC]'
123+
}`}
124+
>
125+
{CATEGORY_LABELS[cat] || cat}
126+
</button>
127+
))}
52128
</div>
53129

54130
{filtered.length === 0 ? (
55131
<p className='py-12 text-center text-[#555] text-[15px]'>
56-
No integrations found for &ldquo;{query}&rdquo;
132+
No integrations found
133+
{query ? <> for &ldquo;{query}&rdquo;</> : null}
134+
{activeCategory ? <> in {CATEGORY_LABELS[activeCategory] || activeCategory}</> : null}
57135
</p>
58136
) : (
59137
<div className='grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4'>

0 commit comments

Comments
 (0)