mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
feat(slack): add install + privacy section to integration landing page (#4799)
* feat(slack): add install + privacy section to integration landing page Adds a hand-authored, slug-keyed landing-content module (separate from the generated integrations.json so it survives regeneration) and renders an install walkthrough + privacy-policy link on integration pages when present. Also refreshes generated docs (data-enrichment entry, icon mappings, tool mdx). * fix(landing): render privacy section independently, align CTA analytics label * docs(landing): clarify the Slack install button is behind sign-in * refactor(landing): bake integration landing content into generated json via docs-gen Moves landing content (install walkthrough + privacy) out of a render-time augment and into the generation pipeline: generate-docs reads the pure-data content map and writes landingContent into integrations.json, so the page reads a single source (integration.landingContent). Canonical types live in integrations/data/types.ts.
This commit is contained in:
@@ -1,6 +1,22 @@
|
||||
import type { SVGProps } from 'react'
|
||||
import { useId } from 'react'
|
||||
|
||||
export function EnrichmentIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox='0 0 24 24'
|
||||
fill='currentColor'
|
||||
role='img'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<path d='M12 2.5l1.9 4.6 4.6 1.9-4.6 1.9L12 15.5l-1.9-4.6L5.5 9l4.6-1.9L12 2.5z' />
|
||||
<path d='M18.5 14l.95 2.3 2.3.95-2.3.95L18.5 20.5l-.95-2.3-2.3-.95 2.3-.95.95-2.3z' />
|
||||
<path d='M5.5 14.5l.7 1.7 1.7.7-1.7.7-.7 1.7-.7-1.7-1.7-.7 1.7-.7.7-1.7z' />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function AgentMailIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} viewBox='0 0 350 363' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||
@@ -4731,7 +4747,7 @@ export function ZoomInfoIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox='0 0 65 65'
|
||||
viewBox='0.272461 0.272461 64 64'
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
aria-hidden='true'
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
AshbyIcon,
|
||||
AthenaIcon,
|
||||
AttioIcon,
|
||||
AzureDevOpsIcon,
|
||||
AzureIcon,
|
||||
BoxCompanyIcon,
|
||||
BrainIcon,
|
||||
@@ -53,6 +52,7 @@ import {
|
||||
ElasticsearchIcon,
|
||||
ElevenLabsIcon,
|
||||
EmailBisonIcon,
|
||||
EnrichmentIcon,
|
||||
EnrichSoIcon,
|
||||
EvernoteIcon,
|
||||
ExaAIIcon,
|
||||
@@ -232,7 +232,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
|
||||
ashby: AshbyIcon,
|
||||
athena: AthenaIcon,
|
||||
attio: AttioIcon,
|
||||
azure_devops: AzureDevOpsIcon,
|
||||
azure_devops: AzureIcon,
|
||||
box: BoxCompanyIcon,
|
||||
brandfetch: BrandfetchIcon,
|
||||
brightdata: BrightDataIcon,
|
||||
@@ -265,6 +265,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
|
||||
elevenlabs: ElevenLabsIcon,
|
||||
emailbison: EmailBisonIcon,
|
||||
enrich: EnrichSoIcon,
|
||||
enrichment: EnrichmentIcon,
|
||||
evernote: EvernoteIcon,
|
||||
exa: ExaAIIcon,
|
||||
extend: ExtendIcon,
|
||||
|
||||
@@ -252,7 +252,7 @@ Update an existing contact in your Apollo database
|
||||
| `mobile_phone` | string | No | Mobile phone number |
|
||||
| `home_phone` | string | No | Home phone number |
|
||||
| `other_phone` | string | No | Alternative phone number |
|
||||
| `typed_custom_fields` | json | No | Custom field values keyed by custom field ID \(accepted by Apollo but not officially documented for PATCH /contacts/\{id\}\) |
|
||||
| `typed_custom_fields` | json | No | Custom field values keyed by custom field ID |
|
||||
|
||||
#### Output
|
||||
|
||||
@@ -440,6 +440,7 @@ Update up to 1000 existing accounts at once in your Apollo database (higher limi
|
||||
| `account_ids` | array | No | Array of account IDs to update with the same values \(max 1000\). Use with name/owner_id for uniform updates. Use either this OR account_attributes. |
|
||||
| `name` | string | No | When using account_ids, apply this name to all accounts |
|
||||
| `owner_id` | string | No | When using account_ids, apply this owner to all accounts |
|
||||
| `account_stage_id` | string | No | When using account_ids, apply this account stage to all accounts |
|
||||
| `account_attributes` | json | No | Array of account objects with individual updates \(each must include id\). Example: \[\{"id": "acc1", "name": "Acme", "owner_id": "u1", "account_stage_id": "s1", "typed_custom_fields": \{"field_id": "value"\}\}\] |
|
||||
| `async` | boolean | No | When true, processes the update asynchronously. Only supported when using account_ids; returns 422 if used with account_attributes. |
|
||||
|
||||
@@ -447,9 +448,9 @@ Update up to 1000 existing accounts at once in your Apollo database (higher limi
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `accounts` | json | Updated accounts \(synchronous response, ≤100 accounts\): \[\{id, account_stage_id, ...\}\] |
|
||||
| `accounts` | json | Updated accounts \(synchronous response\): \[\{id, account_stage_id, ...\}\] |
|
||||
| `account_ids` | json | IDs of accounts that were updated |
|
||||
| `entity_progress_job` | json | Async job descriptor \(>100 accounts or async=true\) |
|
||||
| `entity_progress_job` | json | Async job descriptor \(when async=true is passed with account_ids\) |
|
||||
| `job_id` | string | Async job ID extracted from entity_progress_job |
|
||||
| `message` | string | Optional confirmation message from Apollo |
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Enrichment
|
||||
title: Data Enrichment
|
||||
description: Enrich data with a Sim enrichment
|
||||
---
|
||||
|
||||
@@ -31,18 +31,9 @@ Run a Sim enrichment (e.g. Work Email, Phone Number) and return its outputs
|
||||
|
||||
#### Output
|
||||
|
||||
The exact fields depend on which enrichment ran. `matched` and `provider` are always present.
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `matched` | boolean | Whether the enrichment found a result |
|
||||
| `provider` | string | Provider whose result was returned (e.g. "Hunter", "People Data Labs"); `null` on no match |
|
||||
| `email` | string | Work email address (Work Email enrichment) |
|
||||
| `phone` | string | Phone number (Phone Number enrichment) |
|
||||
| `domain` | string | Website domain (Company Domain enrichment) |
|
||||
| `industry` | string | Industry (Company Info enrichment) |
|
||||
| `employeeCount` | number | Employee count (Company Info enrichment) |
|
||||
| `foundedYear` | number | Founded year (Company Info enrichment) |
|
||||
| `description` | string | Company description (Company Info enrichment) |
|
||||
| `provider` | string | Provider whose result was returned \(e.g. "Hunter", "People Data Labs"\) |
|
||||
|
||||
|
||||
|
||||
@@ -80,17 +80,19 @@ Retrieve details of a previously sent email by its ID
|
||||
| --------- | ---- | ----------- |
|
||||
| `id` | string | Email ID |
|
||||
| `from` | string | Sender email address |
|
||||
| `to` | json | Recipient email addresses |
|
||||
| `to` | array | Recipient email addresses |
|
||||
| `subject` | string | Email subject |
|
||||
| `html` | string | HTML email content |
|
||||
| `text` | string | Plain text email content |
|
||||
| `cc` | json | CC email addresses |
|
||||
| `bcc` | json | BCC email addresses |
|
||||
| `replyTo` | json | Reply-to email addresses |
|
||||
| `cc` | array | CC email addresses |
|
||||
| `bcc` | array | BCC email addresses |
|
||||
| `replyTo` | array | Reply-to email addresses |
|
||||
| `lastEvent` | string | Last event status \(e.g., delivered, bounced\) |
|
||||
| `createdAt` | string | Email creation timestamp |
|
||||
| `scheduledAt` | string | Scheduled send timestamp |
|
||||
| `tags` | json | Email tags as name-value pairs |
|
||||
| `tags` | array | Email tags as name-value pairs |
|
||||
| ↳ `name` | string | Tag name |
|
||||
| ↳ `value` | string | Tag value |
|
||||
|
||||
### `resend_create_contact`
|
||||
|
||||
@@ -126,7 +128,13 @@ List all contacts in Resend
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `contacts` | json | Array of contacts with id, email, first_name, last_name, created_at, unsubscribed |
|
||||
| `contacts` | array | Array of contacts |
|
||||
| ↳ `id` | string | Contact ID |
|
||||
| ↳ `email` | string | Contact email address |
|
||||
| ↳ `first_name` | string | Contact first name |
|
||||
| ↳ `last_name` | string | Contact last name |
|
||||
| ↳ `created_at` | string | Contact creation timestamp |
|
||||
| ↳ `unsubscribed` | boolean | Whether the contact is unsubscribed |
|
||||
| `hasMore` | boolean | Whether there are more contacts to retrieve |
|
||||
|
||||
### `resend_get_contact`
|
||||
@@ -203,7 +211,12 @@ List all verified domains in your Resend account
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `domains` | json | Array of domains with id, name, status, region, and createdAt |
|
||||
| `domains` | array | Array of domains |
|
||||
| ↳ `id` | string | Domain ID |
|
||||
| ↳ `name` | string | Domain name |
|
||||
| ↳ `status` | string | Domain verification status |
|
||||
| ↳ `region` | string | Region the domain is configured in |
|
||||
| ↳ `createdAt` | string | Domain creation timestamp |
|
||||
| `hasMore` | boolean | Whether there are more domains to retrieve |
|
||||
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ Enrich a company by name, domain, LinkedIn ID, or LinkedIn slug with detailed fi
|
||||
| `company_region` | string | State/region |
|
||||
| `company_postal_code` | string | Postal code |
|
||||
| `company_country` | string | Country |
|
||||
| `credits` | json | Credits deducted for this enrichment \(api_credits: \{ total, email_credits, phone_credits, scrape_credits \}\) |
|
||||
| `credits` | json | Credits deducted for this enrichment \(api_credits: \{ total, company_credits \}\) |
|
||||
|
||||
### `wiza_start_individual_reveal`
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ Enrich up to 25 companies in one request with detailed firmographics, industry,
|
||||
| `clientId` | string | Yes | ZoomInfo OAuth client ID |
|
||||
| `clientSecret` | string | Yes | ZoomInfo OAuth client secret |
|
||||
| `matchCompanyInput` | string | Yes | JSON array \(1-25 items\) of company matching criteria, e.g. \[\{"companyName":"Acme","companyWebsite":"acme.com"\}\] |
|
||||
| `outputFields` | string | No | JSON array or comma-separated list of fields to return \(e.g. \["id","name","website","revenue","employeeCount"\]\) |
|
||||
| `outputFields` | string | No | JSON array or comma-separated list of fields to return \(e.g. \["id","name","website","revenue","employeeCount"\]\). Defaults to a standard firmographic set if omitted. |
|
||||
|
||||
#### Output
|
||||
|
||||
@@ -138,7 +138,7 @@ Enrich up to 25 contacts in one request with verified emails, phone numbers, job
|
||||
| `clientId` | string | Yes | ZoomInfo OAuth client ID |
|
||||
| `clientSecret` | string | Yes | ZoomInfo OAuth client secret |
|
||||
| `matchPersonInput` | string | Yes | JSON array \(1-25 items\) of contact matching criteria, e.g. \[\{"firstName":"Jane","lastName":"Doe","companyName":"Acme"\}\] |
|
||||
| `outputFields` | string | No | JSON array or comma-separated list of fields to return \(e.g. \["id","firstName","email","phone","jobTitle"\]\) |
|
||||
| `outputFields` | string | No | JSON array or comma-separated list of fields to return \(e.g. \["id","firstName","email","phone","jobTitle"\]\). Defaults to a standard contact set if omitted. |
|
||||
| `requiredFields` | string | No | JSON array or comma-separated list of fields that must exist in results \(e.g. \["email"\]\) |
|
||||
|
||||
#### Output
|
||||
|
||||
@@ -217,6 +217,8 @@ export default async function IntegrationPage({ params }: { params: Promise<{ sl
|
||||
const { name, description, longDescription, bgColor, docsUrl, operations, triggers, authType } =
|
||||
integration
|
||||
|
||||
const landingContent = integration.landingContent
|
||||
|
||||
const IconComponent = blockTypeToIconMap[integration.type]
|
||||
const faqs = buildFAQs(integration)
|
||||
const relatedSlugs = getRelatedSlugs(slug, operations, authType)
|
||||
@@ -444,6 +446,77 @@ export default async function IntegrationPage({ params }: { params: Promise<{ sl
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Install / Add to workspace (integration-specific) */}
|
||||
{landingContent?.install && (
|
||||
<>
|
||||
<section aria-labelledby='install-heading' className='px-6 py-10'>
|
||||
<h2
|
||||
id='install-heading'
|
||||
className='mb-4 text-[20px] text-white leading-[100%] tracking-[-0.02em]'
|
||||
>
|
||||
{landingContent.install.heading}
|
||||
</h2>
|
||||
<p className='mb-6 max-w-[700px] text-[15px] text-[var(--landing-text-body)] leading-[150%] tracking-[0.02em]'>
|
||||
{landingContent.install.intro}
|
||||
</p>
|
||||
<ol className='space-y-4' aria-label={`Steps to add ${name}`}>
|
||||
{landingContent.install.steps.map((item, index) => (
|
||||
<li key={item.title} className='flex gap-4'>
|
||||
<span
|
||||
className='mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full border border-[var(--landing-border-strong)] font-martian-mono text-[11px] text-[var(--landing-text-subtle)]'
|
||||
aria-hidden='true'
|
||||
>
|
||||
{String(index + 1).padStart(2, '0')}
|
||||
</span>
|
||||
<div>
|
||||
<h3 className='mb-1 text-[15px] text-white tracking-[-0.02em]'>
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className='text-[14px] text-[var(--landing-text-body)] leading-[150%] tracking-[0.02em]'>
|
||||
{item.body}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
<div className='mt-8 flex flex-wrap gap-2'>
|
||||
<IntegrationCtaButton
|
||||
label={`Add to ${name}`}
|
||||
className='inline-flex h-[32px] items-center gap-2 rounded-[5px] border border-white bg-white px-2.5 font-season text-black text-sm transition-colors hover:border-[#E0E0E0] hover:bg-[#E0E0E0]'
|
||||
>
|
||||
Add to {name}
|
||||
</IntegrationCtaButton>
|
||||
</div>
|
||||
</section>
|
||||
<div className='h-px w-full bg-[var(--landing-bg-elevated)]' />
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Privacy & data (integration-specific) */}
|
||||
{landingContent?.privacy && (
|
||||
<>
|
||||
<section aria-labelledby='privacy-heading' className='px-6 py-10'>
|
||||
<h2
|
||||
id='privacy-heading'
|
||||
className='mb-4 text-[20px] text-white leading-[100%] tracking-[-0.02em]'
|
||||
>
|
||||
Privacy & data
|
||||
</h2>
|
||||
<p className='max-w-[700px] text-[15px] text-[var(--landing-text-body)] leading-[150%] tracking-[0.02em]'>
|
||||
{landingContent.privacy.body}{' '}
|
||||
<Link
|
||||
href={landingContent.privacy.href}
|
||||
className='text-[var(--landing-text)] underline underline-offset-2 hover:text-white'
|
||||
>
|
||||
Privacy Policy
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</section>
|
||||
<div className='h-px w-full bg-[var(--landing-bg-elevated)]' />
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* How to automate */}
|
||||
<section aria-labelledby='how-it-works-heading' className='px-6 py-10'>
|
||||
<h2
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
AshbyIcon,
|
||||
AthenaIcon,
|
||||
AttioIcon,
|
||||
AzureDevOpsIcon,
|
||||
AzureIcon,
|
||||
BoxCompanyIcon,
|
||||
BrainIcon,
|
||||
@@ -53,6 +52,7 @@ import {
|
||||
ElasticsearchIcon,
|
||||
ElevenLabsIcon,
|
||||
EmailBisonIcon,
|
||||
EnrichmentIcon,
|
||||
EnrichSoIcon,
|
||||
EvernoteIcon,
|
||||
ExaAIIcon,
|
||||
@@ -231,7 +231,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
|
||||
ashby: AshbyIcon,
|
||||
athena: AthenaIcon,
|
||||
attio: AttioIcon,
|
||||
azure_devops: AzureDevOpsIcon,
|
||||
azure_devops: AzureIcon,
|
||||
box: BoxCompanyIcon,
|
||||
brandfetch: BrandfetchIcon,
|
||||
brightdata: BrightDataIcon,
|
||||
@@ -262,6 +262,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
|
||||
elevenlabs: ElevenLabsIcon,
|
||||
emailbison: EmailBisonIcon,
|
||||
enrich: EnrichSoIcon,
|
||||
enrichment: EnrichmentIcon,
|
||||
evernote: EvernoteIcon,
|
||||
exa: ExaAIIcon,
|
||||
extend_v2: ExtendIcon,
|
||||
|
||||
@@ -1889,7 +1889,7 @@
|
||||
"description": "Interact with Azure DevOps pipelines, builds, and work items",
|
||||
"longDescription": "Integrate Azure DevOps into your workflow. List and inspect pipelines and builds, query and manage work items, and add or read comments.",
|
||||
"bgColor": "#0078D4",
|
||||
"iconName": "AzureDevOpsIcon",
|
||||
"iconName": "AzureIcon",
|
||||
"docsUrl": "https://docs.sim.ai/tools/azure_devops",
|
||||
"operations": [
|
||||
{
|
||||
@@ -3137,6 +3137,24 @@
|
||||
"integrationTypes": ["analytics", "developer-tools"],
|
||||
"tags": ["data-analytics", "automation"]
|
||||
},
|
||||
{
|
||||
"type": "enrichment",
|
||||
"slug": "data-enrichment",
|
||||
"name": "Data Enrichment",
|
||||
"description": "Enrich data with a Sim enrichment",
|
||||
"longDescription": "Run a Sim enrichment to look up data — work email, phone number, company domain, company info, and more — from the fields you map in. Uses the same provider cascade as table enrichments.",
|
||||
"bgColor": "#9333EA",
|
||||
"iconName": "EnrichmentIcon",
|
||||
"docsUrl": "https://docs.sim.ai/tools/enrichment",
|
||||
"operations": [],
|
||||
"operationCount": 0,
|
||||
"triggers": [],
|
||||
"triggerCount": 0,
|
||||
"authType": "none",
|
||||
"category": "tools",
|
||||
"integrationTypes": ["sales"],
|
||||
"tags": ["enrichment"]
|
||||
},
|
||||
{
|
||||
"type": "databricks",
|
||||
"slug": "databricks",
|
||||
@@ -13288,7 +13306,35 @@
|
||||
"authType": "oauth",
|
||||
"category": "tools",
|
||||
"integrationTypes": ["communication", "developer-tools"],
|
||||
"tags": ["messaging", "webhooks", "automation"]
|
||||
"tags": ["messaging", "webhooks", "automation"],
|
||||
"landingContent": {
|
||||
"install": {
|
||||
"heading": "Add Sim to your Slack workspace",
|
||||
"intro": "Sim connects to Slack through Slack’s official OAuth flow. The “Add to Slack” button lives inside your Sim account (after sign-in) — connect from there and the Sim bot is installed in your Slack workspace. The steps below show exactly how to reach it.",
|
||||
"steps": [
|
||||
{
|
||||
"title": "Create your free Sim account",
|
||||
"body": "Sign up at sim.ai — no credit card required."
|
||||
},
|
||||
{
|
||||
"title": "Add a Slack block",
|
||||
"body": "Open a workflow, drag in a Slack block, and open its credential dropdown."
|
||||
},
|
||||
{
|
||||
"title": "Connect Slack",
|
||||
"body": "Click Connect Slack, choose your workspace, and approve the requested permissions. This installs the Sim bot in your Slack workspace."
|
||||
},
|
||||
{
|
||||
"title": "Invite the bot and build",
|
||||
"body": "Invite the Sim bot to the channels it should act in, pick a Slack action, wire it into your agent, and run."
|
||||
}
|
||||
]
|
||||
},
|
||||
"privacy": {
|
||||
"body": "Sim requests only the Slack permissions its actions and triggers need, and never shows private channel names or messages to people who are not members of those channels in Slack.",
|
||||
"href": "/privacy"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "smtp",
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Hand-authored, integration-specific landing content, keyed by integration
|
||||
* slug. This is a pure-data generation input: `scripts/generate-docs.ts` reads
|
||||
* it and bakes the matching entry into `integrations.json`, so the landing page
|
||||
* consumes a single source (`integration.landingContent`) with no render-time
|
||||
* augmentation. Has no app imports so the build script can import it safely.
|
||||
*/
|
||||
|
||||
import type { IntegrationLandingContent } from '@/app/(landing)/integrations/data/types'
|
||||
|
||||
export const INTEGRATION_LANDING_CONTENT: Record<string, IntegrationLandingContent> = {
|
||||
slack: {
|
||||
install: {
|
||||
heading: 'Add Sim to your Slack workspace',
|
||||
intro:
|
||||
'Sim connects to Slack through Slack’s official OAuth flow. The “Add to Slack” button lives inside your Sim account (after sign-in) — connect from there and the Sim bot is installed in your Slack workspace. The steps below show exactly how to reach it.',
|
||||
steps: [
|
||||
{
|
||||
title: 'Create your free Sim account',
|
||||
body: 'Sign up at sim.ai — no credit card required.',
|
||||
},
|
||||
{
|
||||
title: 'Add a Slack block',
|
||||
body: 'Open a workflow, drag in a Slack block, and open its credential dropdown.',
|
||||
},
|
||||
{
|
||||
title: 'Connect Slack',
|
||||
body: 'Click Connect Slack, choose your workspace, and approve the requested permissions. This installs the Sim bot in your Slack workspace.',
|
||||
},
|
||||
{
|
||||
title: 'Invite the bot and build',
|
||||
body: 'Invite the Sim bot to the channels it should act in, pick a Slack action, wire it into your agent, and run.',
|
||||
},
|
||||
],
|
||||
},
|
||||
privacy: {
|
||||
body: 'Sim requests only the Slack permissions its actions and triggers need, and never shows private channel names or messages to people who are not members of those channels in Slack.',
|
||||
href: '/privacy',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -19,6 +19,29 @@ export interface FAQItem {
|
||||
answer: string
|
||||
}
|
||||
|
||||
export interface IntegrationInstallStep {
|
||||
title: string
|
||||
body: string
|
||||
}
|
||||
|
||||
export interface IntegrationLandingContent {
|
||||
/**
|
||||
* Install walkthrough for OAuth apps whose connection lives behind sign-in.
|
||||
* Provides the "Add to {app}" instructions that app marketplaces require
|
||||
* when the install button sits behind a login.
|
||||
*/
|
||||
install?: {
|
||||
heading: string
|
||||
intro: string
|
||||
steps: IntegrationInstallStep[]
|
||||
}
|
||||
/** Short data-handling summary shown next to a privacy-policy link. */
|
||||
privacy?: {
|
||||
body: string
|
||||
href: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface Integration {
|
||||
type: string
|
||||
slug: string
|
||||
@@ -36,4 +59,5 @@ export interface Integration {
|
||||
category: string
|
||||
integrationTypes?: string[]
|
||||
tags?: string[]
|
||||
landingContent?: IntegrationLandingContent
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env ts-node
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { fileURLToPath, pathToFileURL } from 'url'
|
||||
import { glob } from 'glob'
|
||||
|
||||
console.log('Starting documentation generator...')
|
||||
@@ -183,6 +183,7 @@ interface IntegrationEntry {
|
||||
category: string
|
||||
integrationTypes?: string[]
|
||||
tags?: string[]
|
||||
landingContent?: Record<string, unknown>
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -665,6 +666,19 @@ async function writeIntegrationsJson(iconMapping: Record<string, string>): Promi
|
||||
|
||||
const triggerRegistry = await buildTriggerRegistry()
|
||||
const { desc: toolDescMap, name: toolNameMap } = await buildToolDescriptionMap()
|
||||
|
||||
// Hand-authored, integration-specific landing content (install walkthrough,
|
||||
// privacy blurb), keyed by slug. Imported as pure data — its only import is
|
||||
// type-only and erased at runtime — and baked into the entries below so the
|
||||
// landing page reads a single source instead of augmenting at render time.
|
||||
const landingContentModule = await import(
|
||||
pathToFileURL(path.join(LANDING_INTEGRATIONS_DATA_PATH, 'landing-content.ts')).href
|
||||
)
|
||||
const landingContentMap = (landingContentModule.INTEGRATION_LANDING_CONTENT ?? {}) as Record<
|
||||
string,
|
||||
Record<string, unknown>
|
||||
>
|
||||
|
||||
const integrations: IntegrationEntry[] = []
|
||||
const seenBaseTypes = new Set<string>()
|
||||
const blockFiles = (await glob(`${BLOCKS_PATH}/*.ts`)).sort()
|
||||
@@ -778,6 +792,7 @@ async function writeIntegrationsJson(iconMapping: Record<string, string>): Promi
|
||||
}
|
||||
: {}),
|
||||
...(config.tags ? { tags: config.tags } : {}),
|
||||
...(landingContentMap[slug] ? { landingContent: landingContentMap[slug] } : {}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user