├── README.md ├── connect-react-demo ├── .env.example ├── .gitignore ├── .tool-versions ├── Makefile ├── README.md ├── app │ ├── actions │ │ └── backendClient.ts │ ├── components │ │ ├── ClientWrapper.tsx │ │ ├── ConfigPanel.tsx │ │ ├── Cursor.tsx │ │ ├── DatadogScript.tsx │ │ ├── Demo.tsx │ │ ├── DemoHeader.tsx │ │ ├── DemoPanel.tsx │ │ ├── PageSkeleton.tsx │ │ ├── PipedreamLogo.tsx │ │ ├── SectionHeader.tsx │ │ ├── Terminal.tsx │ │ ├── TerminalCollapsible.tsx │ │ ├── config │ │ │ ├── AuthSection.tsx │ │ │ ├── CodeSection.tsx │ │ │ ├── ConfigSection.tsx │ │ │ ├── CustomizationSection.tsx │ │ │ ├── FormSettingsSection.tsx │ │ │ └── TabsHeader.tsx │ │ └── customization-select │ │ │ ├── CustomIndicators.tsx │ │ │ ├── CustomLabel.tsx │ │ │ ├── blue-theme.ts │ │ │ ├── dark-theme.ts │ │ │ └── default-unstyled.ts │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ ├── opengraph-image.png │ └── page.tsx ├── components.json ├── components │ └── ui │ │ ├── badge.tsx │ │ ├── button.tsx │ │ ├── collapsible.tsx │ │ ├── command.tsx │ │ ├── dialog.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── navigation-menu.tsx │ │ ├── popover.tsx │ │ ├── resizable.tsx │ │ ├── scroll-area.tsx │ │ ├── select.tsx │ │ ├── switch.tsx │ │ ├── tabs.tsx │ │ └── tooltip.tsx ├── lib │ ├── app-state.tsx │ ├── backend-client.ts │ ├── env.ts │ ├── query-params.tsx │ ├── stable-uuid.tsx │ ├── use-query-params.tsx │ └── utils.ts ├── next-env.d.ts ├── next.config.mjs ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── postcss.config.mjs ├── public │ └── pd_theme_basic.css ├── tailwind.config.ts └── tsconfig.json └── managed-auth-basic-next-app ├── .env.example ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .tool-versions ├── .vscode └── launch.json ├── README.md ├── app ├── CodePanel.tsx ├── accounts │ └── accounts.tsx ├── favicon.ico ├── globals.css ├── layout.tsx ├── page.tsx └── server.ts ├── next-env.d.ts ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── next.svg └── vercel.svg ├── tailwind.config.ts └── tsconfig.json /README.md: -------------------------------------------------------------------------------- 1 | # Pipedream Connect examples 2 | 3 | Learn more about Pipedream Connect [here](https://pipedream.com/connect) and in our official docs [here](https://pipedream.com/docs/connect). 4 | 5 | This repo provides a collection of demo apps to highlight example implementations: 6 | 7 | - **[managed-auth-basic-next-app](/managed-auth-basic-next-app/)** shows the foundational managed auth capability, which enables your end users to connect their account for any of the 2,400 apps available on Pipedream. This is an example Next.js app, but the SDK works in any JavaScript environment, and there are corresponding REST API as well. [Refer to our docs](https://pipedream.com/docs/connect) for more info. 8 | - **[connect-react-demo](/connect-react-demo/)** demonstrates how you can embed any of the 10k+ Pipedream actions and triggers directly in your app or AI agent. Check out the [connect-react](https://github.com/PipedreamHQ/pipedream/tree/master/packages/connect-react) for more info on the SDK. 9 | -------------------------------------------------------------------------------- /connect-react-demo/.env.example: -------------------------------------------------------------------------------- 1 | PIPEDREAM_CLIENT_ID= 2 | PIPEDREAM_CLIENT_SECRET= 3 | PIPEDREAM_PROJECT_ID= 4 | PIPEDREAM_PROJECT_ENVIRONMENT=development 5 | PIPEDREAM_ALLOWED_ORIGINS='["https://example.com", "http://localhost:3000"]' -------------------------------------------------------------------------------- /connect-react-demo/.gitignore: -------------------------------------------------------------------------------- 1 | .next 2 | node_modules 3 | .env* 4 | !.env.example 5 | -------------------------------------------------------------------------------- /connect-react-demo/.tool-versions: -------------------------------------------------------------------------------- 1 | pnpm 8.6.11 2 | nodejs 20.10.0 3 | -------------------------------------------------------------------------------- /connect-react-demo/Makefile: -------------------------------------------------------------------------------- 1 | default: 2 | pnpm install 3 | pnpm dev 4 | 5 | connect-react-dev: 6 | LOCAL_CONNECT_REACT=1 pnpm install 7 | pnpm dev 8 | 9 | 10 | clean: 11 | rm -rf node_modules 12 | -------------------------------------------------------------------------------- /connect-react-demo/README.md: -------------------------------------------------------------------------------- 1 | # Pipedream Components Demo (React) 2 | 3 | ## Clone this Next.js demo app 4 | 5 | ```sh 6 | git clone https://github.com/PipedreamHQ/pipedream-connect-examples.git 7 | cd connect-react-demo 8 | ``` 9 | 10 | ## Install dependencies 11 | 12 | ```sh 13 | pnpm install 14 | ``` 15 | 16 | ## Create necessary Pipedream resources 17 | 18 | 1. A [Pipedream account](https://pipedream.com) and [workspace](https://pipedream.com/docs/workspaces) 19 | 2. A [Pipedream API OAuth client](https://pipedream.com/docs/rest-api/auth#creating-an-oauth-client) 20 | 3. Your [project's ID](https://pipedream.com/docs/projects#finding-your-projects-id) 21 | 22 | ## Set your environment variables 23 | 24 | Copy the `.env.example` file to `.env.local` and fill in the values. 25 | 26 | ``` 27 | cp .env.example .env.local 28 | ``` 29 | 30 | ## Run the app 31 | 32 | ```sh 33 | pnpm dev 34 | ``` 35 | 36 | 37 | ## How to test @pipedream/connect-react changes with this app 38 | 39 | clone this repo as well as the the repo that contains connect-react (pipedream). 40 | Make sure the two repos are cloned in the same parent directory 41 | 42 | ```sh 43 | git clone https://github.com/PipedreamHQ/pipedream-connect-examples.git 44 | git clone https://github.com/PipedreamHQ/pipedream.git 45 | ``` 46 | 47 | Install dependencies and build connect-react. Using watch will rebuild the package when changes are detected. 48 | 49 | ```sh 50 | cd pipedream/packages/connect-react 51 | pnpm install 52 | pnpm watch 53 | ``` 54 | 55 | In a separate tab install dependencies and run the demo app. Be sure to set the correct values in .env.local 56 | 57 | ```sh 58 | cd pipedream-connect-examples/connect-react-demo 59 | cp .env.example .env.local 60 | make connect-react-dev 61 | ``` 62 | 63 | Changes made in connect-react will not be automatically loaded by the app. To pick them up you'll need to restart `make connect-react-dev` 64 | 65 | A change like the following is an easy way to check that your connect-react changes have been picked up (all app names will be prefixed with 'hello world!' 66 | ```sh 67 | diff --git a/packages/connect-react/src/components/SelectApp.tsx b/packages/connect-react/src/components/SelectApp.tsx 68 | index 61fe7bd27..a9378297d 100644 69 | --- a/packages/connect-react/src/components/SelectApp.tsx 70 | +++ b/packages/connect-react/src/components/SelectApp.tsx 71 | @@ -48,7 +48,7 @@ export function SelectApp({ 72 | /> 73 | {optionProps.data.name} 76 | + }}>hello world!{optionProps.data.name} 77 | 78 | 79 | ), 80 | ``` 81 | 82 | ## TODO 83 | 84 | It would be nice for changes to connect-react while watched to be picked up without having to restart the demo app. 85 | -------------------------------------------------------------------------------- /connect-react-demo/app/actions/backendClient.ts: -------------------------------------------------------------------------------- 1 | "use server" 2 | 3 | import { env } from "@/lib/env"; 4 | import { backendClient } from "@/lib/backend-client"; 5 | 6 | export type FetchTokenOpts = { 7 | externalUserId: string 8 | } 9 | 10 | const allowedOrigins = ([ 11 | process.env.VERCEL_URL, 12 | process.env.VERCEL_BRANCH_URL, 13 | process.env.VERCEL_PROJECT_PRODUCTION_URL, 14 | ...env.PIPEDREAM_ALLOWED_ORIGINS, 15 | ].filter(Boolean) as string[]).map((origin) => { 16 | if (origin.startsWith("http")) { 17 | return origin 18 | } 19 | return `https://${origin}` 20 | }); 21 | 22 | const _fetchToken = async (opts: FetchTokenOpts) => { 23 | const serverClient = backendClient() 24 | 25 | const resp = await serverClient.createConnectToken({ 26 | external_user_id: opts.externalUserId, 27 | allowed_origins: allowedOrigins, // TODO set this to the correct origin 28 | webhook_uri: process.env.PIPEDREAM_CONNECT_WEBHOOK_URI, 29 | }); 30 | return resp 31 | } 32 | 33 | // export const fetchToken = unstable_cache(_fetchToken, [], { revalidate: 3600 }) 34 | export const fetchToken = _fetchToken 35 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/ClientWrapper.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { AppStateProvider } from "@/lib/app-state" 4 | import { useStableUuid } from "@/lib/stable-uuid" 5 | import { FrontendClientProvider } from "@pipedream/connect-react" 6 | import { createFrontendClient } from "@pipedream/sdk/browser" 7 | import { fetchToken } from "../actions/backendClient" 8 | import Demo from "./Demo" 9 | 10 | export const ClientWrapper = () => { 11 | const [externalUserId] = useStableUuid() 12 | 13 | const client = createFrontendClient({ 14 | environment: process.env.PIPEDREAM_PROJECT_ENVIRONMENT, 15 | tokenCallback: fetchToken, 16 | externalUserId, 17 | }); 18 | 19 | return ( 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/ConfigPanel.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useId } from "react" 4 | import { SelectApp, SelectComponent } from "@pipedream/connect-react" 5 | import { 6 | Tooltip, 7 | TooltipContent, 8 | TooltipProvider, 9 | TooltipTrigger, 10 | } from "@/components/ui/tooltip" 11 | import { useAppState } from "@/lib/app-state" 12 | import { cn } from "@/lib/utils" 13 | import Select from "react-select" 14 | import { CodeSection } from "./config/CodeSection" 15 | import { ScrollArea } from "@/components/ui/scroll-area" 16 | import {enableDebugging} from "@/lib/query-params"; 17 | 18 | function getTypeDescription(prop: { 19 | name: string 20 | type: string 21 | description: string 22 | optional?: boolean 23 | default?: any 24 | min?: number 25 | max?: number 26 | secret?: boolean 27 | }) { 28 | let syntax = "" 29 | 30 | switch (prop.type) { 31 | case "string": 32 | syntax = `string` 33 | break 34 | case "integer": 35 | syntax = `number` 36 | break 37 | case "boolean": 38 | syntax = `boolean` 39 | break 40 | case "app": 41 | syntax = `AppConnection` 42 | break 43 | case "any": 44 | syntax = `any` 45 | break 46 | case "sql": 47 | syntax = `SQL` 48 | break 49 | default: 50 | if (prop.type.endsWith("[]")) { 51 | const baseType = prop.type.slice(0, -2) 52 | syntax = `Array<${baseType}>` 53 | } else { 54 | syntax = `${prop.type}` 55 | } 56 | } 57 | 58 | if (prop.optional) { 59 | syntax = `${syntax} | undefined` 60 | } 61 | 62 | return { 63 | syntax, 64 | isArray: prop.type.endsWith("[]"), 65 | isOptional: prop.optional, 66 | } 67 | } 68 | 69 | const typeBadgeStyles = { 70 | string: 71 | "text-[13px] font-mono bg-blue-50 text-blue-700 border-blue-200 shadow-[inset_0_1px_0_rgba(255,255,255,0.5)]", 72 | boolean: 73 | "text-[13px] font-mono bg-purple-50 text-purple-700 border-purple-200 shadow-[inset_0_1px_0_rgba(255,255,255,0.5)]", 74 | array: 75 | "text-[13px] font-mono bg-indigo-50 text-indigo-700 border-indigo-200 shadow-[inset_0_1px_0_rgba(255,255,255,0.5)]", 76 | object: 77 | "text-[13px] font-mono bg-cyan-50 text-cyan-700 border-cyan-200 shadow-[inset_0_1px_0_rgba(255,255,255,0.5)]", 78 | } 79 | 80 | interface PropertyItemProps { 81 | name: string 82 | type: string 83 | description: string 84 | required?: boolean 85 | children: React.ReactNode 86 | action?: React.ReactNode 87 | defaultValue?: any 88 | min?: number 89 | max?: number 90 | secret?: boolean 91 | } 92 | 93 | const PropertyItem = ({ 94 | name, 95 | type, 96 | description, 97 | required, 98 | children, 99 | action, 100 | defaultValue, 101 | min, 102 | max, 103 | secret, 104 | }: PropertyItemProps) => { 105 | const typeInfo = getTypeDescription({ 106 | name, 107 | type, 108 | description, 109 | optional: required === false, 110 | default: defaultValue, 111 | min, 112 | max, 113 | secret, 114 | }) 115 | 116 | return ( 117 |
118 |
119 | 120 | 121 | 122 | 125 | 126 | 130 |
131 | type{" "} 132 | {name} ={" "} 133 | 140 |
141 | 142 |
143 | {description} 144 |
145 | 146 |
147 | {required !== undefined && ( 148 |
149 | 150 | required: 151 | {" "} 152 | 153 | {required.toString()} 154 | 155 |
156 | )} 157 | 158 | {defaultValue !== undefined && ( 159 |
160 | default:{" "} 161 | 162 | {JSON.stringify(defaultValue)} 163 | 164 |
165 | )} 166 | 167 | {type === "integer" && ( 168 | <> 169 | {min !== undefined && ( 170 |
171 | min:{" "} 172 | {min} 173 |
174 | )} 175 | {max !== undefined && ( 176 |
177 | max:{" "} 178 | {max} 179 |
180 | )} 181 | 182 | )} 183 | 184 | {secret && ( 185 |
186 | secret:{" "} 187 | true 188 |
189 | )} 190 |
191 |
192 |
193 |
194 |
195 | 196 |
197 |
{children}
198 | {action} 199 |
200 |
201 | ) 202 | } 203 | 204 | export const ConfigPanel = () => { 205 | const { 206 | fileCode, 207 | setFileCode, 208 | customizationOption, 209 | code, 210 | userId, 211 | selectedApp, 212 | setSelectedAppSlug, 213 | removeSelectedAppSlug, 214 | selectedComponentType, 215 | selectedComponent, 216 | webhookUrl, 217 | setWebhookUrl, 218 | setSelectedComponentKey, 219 | removeSelectedComponentKey, 220 | setPropNames, 221 | setConfiguredProps, 222 | setActionRunOutput, 223 | customizationOptions, 224 | setCustomizationOption, 225 | hideOptionalProps, 226 | setHideOptionalProps, 227 | enableDebugging, 228 | setEnableDebugging, 229 | propNames, 230 | component, 231 | } = useAppState() 232 | const id1 = useId(); 233 | const id2 = useId(); 234 | 235 | const isValidWebhookUrl = () => { 236 | if (!webhookUrl) { 237 | return true 238 | } 239 | 240 | try { 241 | new URL(webhookUrl); 242 | } catch { 243 | return false 244 | } 245 | return true 246 | } 247 | 248 | 249 | const formControls = ( 250 |
251 | 257 | 262 | 263 | {selectedComponentType === "trigger" && ( 264 | 270 | { 273 | setWebhookUrl(e.target.value) 274 | }} 275 | placeholder="Enter a webhook URL to receive emitted events" 276 | className={`w-full px-3 py-1.5 text-sm font-mono border-2 ${isValidWebhookUrl() ? "" : "border-red-500"} rounded bg-zinc-50/50`} 277 | /> 278 | 279 | )} 280 | 286 |
287 | { 290 | app 291 | ? setSelectedAppSlug(app.name_slug) 292 | : removeSelectedAppSlug() 293 | }} 294 | /> 295 | { 300 | comp 301 | ? setSelectedComponentKey(comp.key) 302 | : removeSelectedComponentKey() 303 | 304 | }} 305 | /> 306 |
307 |
308 | 315 |
316 | 327 |
328 | 339 |
340 | 341 | 348 |
349 | 360 |
361 | 372 |
373 | 374 | 375 | 381 | { 413 | if (v) { 414 | setCustomizationOption(v) 415 | setFileCode(undefined) 416 | } 417 | }} 418 | getOptionValue={(o) => o.name} 419 | className="react-select-container text-sm" 420 | classNamePrefix="react-select" 421 | placeholder="Choose a theme..." 422 | components={{ 423 | IndicatorSeparator: () => null, 424 | }} 425 | /> 426 | 427 | {selectedComponentType === "trigger" && ( 428 |
429 |
430 |

431 | {/* */} 432 | 433 | When you deploy a trigger via the Pipedream components API, we'll emit events to a{' '} 434 | webhookUrl that you define. To test your trigger: 435 | 436 |

437 |
    438 |
  1. 439 | Configure the trigger and define a webhookUrl to receive events (use a{' '} 440 | 446 | RequestBin 447 | 448 | {' '}for example) 449 |
  2. 450 |
  3. 451 | Click Submit on the right (may take up to a minute, but can happen asynchronously in your app) 452 |
  4. 453 |
  5. 454 | Generate some actual events in the relevant app and check your webhook for emitted events. 455 |
  6. 456 |
457 |
458 |
459 | )} 460 |
461 | ) 462 | 463 | return ( 464 |
465 | 466 |
467 |
468 | 475 |
476 |
477 |
478 |
479 | ) 480 | } 481 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/Cursor.tsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion" 2 | 3 | export const Cursor = () => ( 4 | 14 | ) 15 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/DatadogScript.tsx: -------------------------------------------------------------------------------- 1 | import Script from "next/script" 2 | import { env as e } from "@/lib/env" 3 | 4 | const env = process.env.NODE_ENV === "production" ? "production" : "development" 5 | const version = e.NEXT_PUBLIC_GIT_COMMIT_SHA 6 | 7 | const rumInit = { 8 | clientToken: e.DD_CLIENT_TOKEN, 9 | site: 'datadoghq.com', 10 | env, 11 | service: e.DD_SERVICE, 12 | version, 13 | sessionSampleRate: 100, 14 | applicationId: e.DD_APPLICATION_ID, 15 | sessionReplaySampleRate: 100, 16 | trackUserInteractions: true, 17 | trackResources: true, 18 | trackLongTasks: true, 19 | } 20 | 21 | const logsInit = { 22 | clientToken: e.DD_CLIENT_TOKEN, 23 | site: 'datadoghq.com', 24 | env, 25 | service: e.DD_SERVICE, 26 | version, 27 | sessionSampleRate: 100, 28 | forwardErrorsToLogs: true, 29 | forwardConsoleLogs: 'all' 30 | } 31 | 32 | const script = `(function(h,o,u,n,d) { 33 | h=h[d]=h[d]||{q:[],onReady:function(c){h.q.push(c)}} 34 | d=o.createElement(u);d.async=1;d.src=n 35 | n=o.getElementsByTagName(u)[0];n.parentNode.insertBefore(d,n) 36 | })(window,document,'script','https://www.datadoghq-browser-agent.com/us1/v5/datadog-rum.js','DD_RUM') 37 | window.DD_RUM.onReady(function() { 38 | window.DD_RUM.init(${JSON.stringify(rumInit)}); 39 | }); 40 | (function(h,o,u,n,d) { 41 | h=h[d]=h[d]||{q:[],onReady:function(c){h.q.push(c)}} 42 | d=o.createElement(u);d.async=1;d.src=n 43 | n=o.getElementsByTagName(u)[0];n.parentNode.insertBefore(d,n) 44 | })(window,document,'script','https://www.datadoghq-browser-agent.com/us1/v5/datadog-logs.js','DD_LOGS') 45 | window.DD_LOGS.onReady(function() { 46 | window.DD_LOGS.init(${JSON.stringify(logsInit)}) 47 | })` 48 | 49 | export function DatadogScript() { 50 | if (!e.DD_CLIENT_TOKEN) { 51 | return null 52 | } 53 | 54 | return 55 | } 56 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/Demo.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { 4 | ResizableHandle, 5 | ResizablePanel, 6 | ResizablePanelGroup, 7 | } from "@/components/ui/resizable" 8 | import { ConfigPanel } from "./ConfigPanel" 9 | import { DemoHeader } from "./DemoHeader" 10 | import { DemoPanel } from "./DemoPanel" 11 | 12 | export default function Demo() { 13 | return ( 14 |
15 | 16 |
17 | 18 | 24 |
25 | 26 |
27 |
28 | 29 | 30 | 31 | 32 |
33 | 34 |
35 |
36 |
37 |
38 |
39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/DemoHeader.tsx: -------------------------------------------------------------------------------- 1 | import { Badge } from "@/components/ui/badge" 2 | import { Button } from "@/components/ui/button" 3 | import { 4 | NavigationMenu, 5 | NavigationMenuContent, 6 | NavigationMenuItem, 7 | NavigationMenuList, 8 | NavigationMenuTrigger, 9 | } from "@/components/ui/navigation-menu" 10 | import { 11 | Tooltip, 12 | TooltipContent, 13 | TooltipProvider, 14 | TooltipTrigger, 15 | } from "@/components/ui/tooltip" 16 | import { cn } from "@/lib/utils" 17 | import { 18 | IoCubeSharp, 19 | IoFlashOutline, 20 | IoHelpCircleOutline, 21 | } from "react-icons/io5" 22 | import { PipedreamLogo } from "./PipedreamLogo" 23 | import { SiGithub } from "react-icons/si" 24 | import { useAppState } from "@/lib/app-state" 25 | 26 | const typeOptions = [ 27 | { 28 | label: "Actions", 29 | value: "action", 30 | icon: , 31 | description: "Connect to APIs and perform operations", 32 | }, 33 | { 34 | label: "Triggers", 35 | value: "trigger", 36 | icon: , 37 | description: "React to events and webhooks", 38 | }, 39 | ] 40 | 41 | export const DemoHeader = () => { 42 | const { 43 | selectedComponentType, 44 | setSelectedComponentType, 45 | removeSelectedComponentType, 46 | } = useAppState() 47 | return ( 48 |
49 |
50 | 51 | 55 | connect-demo 56 | 57 | 58 | 59 | 62 | 63 | 68 |
69 |

70 | One SDK, thousands of API integrations for your app or AI agent 71 |

72 |

73 | Pipedream Connect provides a TypeScript SDK and REST API to 74 | let your users securely connect their accounts and integrate 75 | with their favorite tools. Common use cases include: 76 |

77 |
    78 |
  • 79 | 80 | 81 | 82 | Managed authentication: 83 | {" "} 84 | Securely manage user credentials and API tokens with 85 | built-in OAuth token refresh 86 | 87 |
  • 88 |
  • 89 | 90 | 91 | 92 | Actions: 93 | {" "} 94 | Connect to APIs and perform operations like sending 95 | messages or syncing data 96 | 97 |
  • 98 |
  • 99 | 100 | 101 | 102 | Workflow Invocation: 103 | {" "} 104 | Write one workflow and run it for all your users 105 | 106 |
  • 107 |
  • 108 | 109 | 110 | 111 | AI agents: 112 | {" "} 113 | Connect your AI agent to all the tools your customers 114 | use and enable it to take actions on their behalf 115 | 116 |
  • 117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 | 125 | 126 | 127 | 128 | {selectedComponentType === "trigger" ? 129 | : 130 | 131 | } 132 | {selectedComponentType} 133 | 134 | 135 | {typeOptions.map((option) => ( 136 |
{ 146 | type ? setSelectedComponentType(option.value) : removeSelectedComponentType() 147 | }} 148 | > 149 | {option.icon} 150 |
151 |
152 | 153 | {option.label} 154 | 155 | {option.disabled && ( 156 | 160 | Soon 161 | 162 | )} 163 |
164 |

165 | {option.description} 166 |

167 |
168 |
169 | ))} 170 |
171 |
172 |
173 |
174 |
175 | 176 |
177 | 184 | 191 | 201 |
202 |
203 | ) 204 | } 205 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/DemoPanel.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentForm, CustomizeProvider, useFrontendClient } from "@pipedream/connect-react" 2 | import { ScrollArea } from "@/components/ui/scroll-area" 3 | import { useAppState } from "@/lib/app-state" 4 | import { PageSkeleton } from "./PageSkeleton" 5 | import { TerminalCollapsible } from "./TerminalCollapsible" 6 | import { useState } from "react"; 7 | 8 | export const DemoPanel = () => { 9 | const frontendClient = useFrontendClient() 10 | const { 11 | customizationOption, 12 | userId, 13 | component, 14 | propNames, 15 | hideOptionalProps, 16 | configuredProps, 17 | setConfiguredProps, 18 | setActionRunOutput, 19 | actionRunOutput, 20 | selectedComponentType, 21 | webhookUrl, 22 | enableDebugging, 23 | } = useAppState() 24 | 25 | const [ 26 | dynamicPropsId, 27 | setDynamicPropsId, 28 | ] = useState(); 29 | 30 | const [ 31 | sdkErrors, 32 | setSdkErrors, 33 | ] = useState(undefined); 34 | 35 | const handleDynamicProps = (dynamicProps: { id: string | undefined }) => { 36 | setDynamicPropsId(dynamicProps.id) 37 | } 38 | 39 | return ( 40 |
41 | 42 |
43 |
44 |
45 |
46 | 47 |
48 | 49 | 59 | 60 | 61 | 62 | 72 | 73 | 74 | 75 | 85 | 86 |
97 |
98 |
99 | 100 |
101 |
102 | 103 | {[20, 40, 60, 80].map((top) => ( 104 | 109 | 119 | 120 | ))} 121 |
122 | 123 |
133 |
134 | 135 |
136 |
137 |
138 | 139 |
143 | 144 |
145 | 148 | {component && ( 149 | { 160 | setActionRunOutput(undefined) 161 | if (selectedComponentType === "action") { 162 | try { 163 | const data = await frontendClient.actionRun({ 164 | userId, 165 | actionId: component.key, 166 | configuredProps, 167 | dynamicPropsId 168 | }) 169 | setActionRunOutput(data) 170 | setSdkErrors(data) 171 | } catch (e) { 172 | setSdkErrors(e) 173 | } 174 | } else if (selectedComponentType === "trigger") { 175 | if (!webhookUrl) { 176 | throw new Error("webhookUrl is required") 177 | } 178 | try { 179 | const data = await frontendClient.deployTrigger({ 180 | userId, 181 | triggerId: component.key, 182 | configuredProps, 183 | webhookUrl, 184 | dynamicPropsId, 185 | }) 186 | setActionRunOutput(data) 187 | setSdkErrors(data) 188 | } catch (e) { 189 | setSdkErrors(e) 190 | } 191 | } 192 | }} 193 | /> 194 | )} 195 | 196 |
197 |
198 |
199 |
200 | 201 | {}} 204 | hasOutput={!!actionRunOutput} 205 | output={actionRunOutput} 206 | /> 207 |
208 |
209 | 210 |
211 | ) 212 | } 213 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/PageSkeleton.tsx: -------------------------------------------------------------------------------- 1 | export function PageSkeleton({ 2 | children, 3 | customizationOption, 4 | }: { 5 | children: React.ReactNode 6 | customizationOption: any 7 | }) { 8 | const isDark = customizationOption.name === "dark" 9 | 10 | return ( 11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | 23 |
24 |
25 |
26 | preview.myapp.com 27 |
28 |
29 |
30 | 31 | {children} 32 |
33 |
34 |
35 |
36 |
37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/PipedreamLogo.tsx: -------------------------------------------------------------------------------- 1 | interface PipedreamLogoProps { 2 | className?: string 3 | } 4 | 5 | export function PipedreamLogo({ className }: PipedreamLogoProps) { 6 | return ( 7 | 12 | 17 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/SectionHeader.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { cn } from "@/lib/utils" 4 | import { FC, ReactNode } from "react" 5 | 6 | interface SectionHeaderProps { 7 | title: string 8 | icon?: ReactNode 9 | children?: ReactNode 10 | variant?: "default" | "terminal" 11 | } 12 | 13 | export const SectionHeader: FC = ({ 14 | title, 15 | icon, 16 | children, 17 | variant = "default", 18 | }) => { 19 | return ( 20 |
28 |
29 | {icon} 30 | {title} 31 |
32 | {children} 33 |
34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/Terminal.tsx: -------------------------------------------------------------------------------- 1 | import { Cursor } from "./Cursor" 2 | 3 | interface TerminalProps { 4 | shouldAnimate?: boolean 5 | output?: any 6 | } 7 | 8 | export const Terminal = ({ shouldAnimate, output }: TerminalProps) => { 9 | return ( 10 |
11 | {shouldAnimate ? ( 12 |
13 |
14 |
15 | ) : output ? ( 16 |
17 |           {JSON.stringify(output, null, 2)}
18 |         
19 | ) : null} 20 |
21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/TerminalCollapsible.tsx: -------------------------------------------------------------------------------- 1 | import { IoTerminalOutline } from "react-icons/io5" 2 | import { 3 | Collapsible, 4 | CollapsibleContent, 5 | CollapsibleTrigger, 6 | } from "@/components/ui/collapsible" 7 | import { Terminal } from "./Terminal" 8 | import { SectionHeader } from "./SectionHeader" 9 | import { cn } from "@/lib/utils" 10 | 11 | interface TerminalCollapsibleProps { 12 | isOpen: boolean 13 | onOpenChange: (open: boolean) => void 14 | hasOutput: boolean 15 | output?: any 16 | } 17 | 18 | export const TerminalCollapsible = ({ 19 | isOpen, 20 | onOpenChange, 21 | hasOutput, 22 | output, 23 | }: TerminalCollapsibleProps) => { 24 | const getStatusMessage = () => { 25 | if (!hasOutput) return "Waiting for submission..." 26 | return "Response received" 27 | } 28 | 29 | return ( 30 | 31 | 32 | } 35 | variant="terminal" 36 | > 37 |
38 | 42 | 52 | {getStatusMessage()} 53 | 54 |
55 |
56 |
57 | 58 |
59 |
60 |
61 | 62 |
63 | 64 | 65 | ) 66 | } 67 | 68 | const StatusIndicator = ({ 69 | active, 70 | success = false, 71 | }: { 72 | active: boolean 73 | success?: boolean 74 | }) => ( 75 |
76 |
86 | {active && ( 87 |
94 | )} 95 |
96 | ) 97 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/config/AuthSection.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Tooltip, 3 | TooltipContent, 4 | TooltipProvider, 5 | TooltipTrigger, 6 | } from "@/components/ui/tooltip" 7 | 8 | interface AuthSectionProps { 9 | userId: string 10 | } 11 | 12 | export const AuthSection = ({ userId }: AuthSectionProps) => ( 13 |
14 |
15 |
User ID
16 | 17 | 18 | 19 |
20 | {userId} 21 |
22 |
23 | 24 | {userId} 25 | 26 |
27 |
28 |
29 |
30 | ) 31 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/config/CodeSection.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button" 2 | import { cn } from "@/lib/utils" 3 | import { IoCopyOutline, IoCheckmarkOutline, IoLogoReact } from "react-icons/io5" 4 | import { SiTypescript } from "react-icons/si" 5 | import SyntaxHighlighter from "react-syntax-highlighter" 6 | import { githubGist } from "react-syntax-highlighter/dist/esm/styles/hljs" 7 | import { useState } from "react" 8 | 9 | const syntaxHighlighterTheme = { 10 | ...githubGist, 11 | 'pre[class*="language-"]': { 12 | ...githubGist['pre[class*="language-"]'], 13 | fontSize: "13px", 14 | margin: 0, 15 | padding: "16px", 16 | background: "#FAFAFA", 17 | border: "none", 18 | }, 19 | 'code[class*="language-"]': { 20 | ...githubGist['code[class*="language-"]'], 21 | fontSize: "13px", 22 | fontFamily: 23 | 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace', 24 | }, 25 | } 26 | 27 | interface CodeSectionProps { 28 | fileCode: string 29 | setFileCode: (code: string | undefined) => void 30 | code: string 31 | customizationOption: any 32 | formControls: React.ReactNode 33 | } 34 | 35 | export const CodeSection = ({ 36 | fileCode, 37 | setFileCode, 38 | code, 39 | customizationOption, 40 | formControls, 41 | }: CodeSectionProps) => { 42 | const [copied, setCopied] = useState(false) 43 | 44 | const handleCopy = async () => { 45 | await navigator.clipboard.writeText(fileCode || code) 46 | setCopied(true) 47 | setTimeout(() => setCopied(false), 1000) 48 | } 49 | 50 | return ( 51 |
52 |
53 | 66 | {customizationOption.file && ( 67 | 80 | )} 81 |
82 | 103 |
104 | 105 |
106 | 123 | {fileCode || code} 124 | 125 |
126 | 127 |
{formControls}
128 |
129 | ) 130 | } 131 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/config/ConfigSection.tsx: -------------------------------------------------------------------------------- 1 | interface ConfigSectionProps { 2 | icon: React.ReactNode 3 | title: string 4 | children: React.ReactNode 5 | } 6 | 7 | export const ConfigSection = ({ 8 | icon, 9 | title, 10 | children, 11 | }: ConfigSectionProps) => ( 12 |
13 |
14 |
{icon}
15 |
{title}
16 |
17 |
{children}
18 |
19 | ) 20 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/config/CustomizationSection.tsx: -------------------------------------------------------------------------------- 1 | import { selectStyles } from "@pipedream/connect-react" 2 | import Select from "react-select" 3 | 4 | interface CustomizationSectionProps { 5 | customizationOptions: any[] 6 | customizationOption: any 7 | setCustomizationOption: (option: any) => void 8 | setFileCode: (code: string | undefined) => void 9 | } 10 | 11 | export const CustomizationSection = ({ 12 | customizationOptions, 13 | customizationOption, 14 | setCustomizationOption, 15 | setFileCode, 16 | }: CustomizationSectionProps) => ( 17 |
18 |
19 |
Theme
20 | ({ 65 | label: prop.name, 66 | value: prop.name, 67 | }))} 68 | isMulti={true} 69 | value={propNames.map((name) => ({ 70 | label: name, 71 | value: name, 72 | }))} 73 | onChange={(vs) => setPropNames(vs.map((v) => v.value))} 74 | className="w-full" 75 | classNamePrefix="react-select" 76 | components={{ 77 | IndicatorSeparator: () => null, 78 | }} 79 | styles={selectStyles} 80 | /> 81 |
82 | )} 83 |
84 |
85 | ) 86 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/config/TabsHeader.tsx: -------------------------------------------------------------------------------- 1 | import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs" 2 | import { 3 | Tooltip, 4 | TooltipContent, 5 | TooltipProvider, 6 | TooltipTrigger, 7 | } from "@/components/ui/tooltip" 8 | 9 | export const TabsHeader = () => ( 10 | 11 | 12 | Actions 13 | 14 | 15 | 16 | 17 | 18 | Triggers 19 | 20 | 21 | 22 | 23 |

Coming soon

24 |
25 |
26 |
27 |
28 |
29 | ) 30 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/customization-select/CustomIndicators.tsx: -------------------------------------------------------------------------------- 1 | import { components as ReactSelectComponents } from "react-select" 2 | import type { DropdownIndicatorProps } from "react-select" 3 | 4 | export function CustomDropdownIndicator(props: DropdownIndicatorProps) { 5 | return 6 | } 7 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/customization-select/CustomLabel.tsx: -------------------------------------------------------------------------------- 1 | import type { CSSProperties } from "react" 2 | import type { ConfigurableProp, LabelProps } from "@pipedream/connect-react" 3 | import { useCustomize } from "@pipedream/connect-react" 4 | 5 | export function CustomLabel(props: LabelProps) { 6 | const { text, field } = props 7 | const { id } = field 8 | 9 | const { getProps, theme } = useCustomize() 10 | 11 | const baseStyles: CSSProperties = { 12 | color: theme.colors.neutral90, 13 | fontWeight: 450, 14 | gridArea: "label", 15 | textTransform: "capitalize", 16 | lineHeight: "1.5", 17 | } 18 | 19 | const required = (!field.prop.optional && !["alert","app"].includes(field.prop.type)) 20 | ? field.prop.type == "boolean" ? typeof field.value != "undefined" : !!field.value 21 | ? 22 | : 23 | : "" 24 | return ( 25 | 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/customization-select/blue-theme.ts: -------------------------------------------------------------------------------- 1 | import { CustomizationConfig } from "@pipedream/connect-react/src/hooks/customization-context" 2 | import { CustomDropdownIndicator } from "./CustomIndicators" 3 | import { CustomLabel } from "./CustomLabel" 4 | 5 | const customization: CustomizationConfig = { 6 | components: { 7 | Label: CustomLabel, 8 | controlSelect: { 9 | DropdownIndicator: CustomDropdownIndicator, 10 | }, 11 | }, 12 | styles: { 13 | label: { fontSize: "80%" }, 14 | controlInput: (base, { theme }) => ({ 15 | ...base, 16 | borderTop: 0, 17 | borderLeft: 0, 18 | borderRight: 0, 19 | border: "solid", 20 | borderColor: theme.colors.primary, 21 | backgroundColor: theme.colors.neutral0, 22 | }), 23 | description: { fontSize: "60%" }, 24 | field: { padding: 2 }, 25 | heading: { fontSize: "80%" }, 26 | controlSelect: { 27 | control: (base, { theme }) => ({ 28 | ...base, 29 | borderRadius: 0, 30 | borderColor: theme.colors.primary25, 31 | fontSize: "small", 32 | maxHeight: "36px", 33 | }), 34 | container: (base) => ({ 35 | ...base, 36 | fontSize: "small", 37 | }), 38 | menu: (base) => ({ 39 | ...base, 40 | fontSize: "small", 41 | }), 42 | }, 43 | controlSubmit: (base, { form, theme }) => { 44 | const spinner = `` 45 | return { 46 | ...base, 47 | backgroundImage: form.submitting ? `url('data:image/svg+xml;base64,${btoa(spinner)}')` : "unset", 48 | backgroundPosition: "right 15% top 50%", 49 | backgroundRepeat: "no-repeat", 50 | } 51 | }, 52 | }, 53 | theme: { 54 | borderRadius: 0, 55 | colors: { 56 | primary: "hsl(200, 100%, 60%)", 57 | primary75: "hsl(200, 100%, 55%)", 58 | primary50: "hsl(200, 100%, 40%)", 59 | primary25: "hsl(200, 100%, 35%)", 60 | 61 | danger: "#DE350B", 62 | dangerLight: "#FFBDAD", 63 | 64 | neutral0: "hsl(200, 50%, 97%)", 65 | neutral5: "hsl(200, 50%, 95%)", 66 | neutral10: "hsl(200, 50%, 90%)", 67 | neutral20: "hsl(200, 50%, 80%)", 68 | neutral30: "hsl(200, 50%, 70%)", 69 | neutral40: "hsl(200, 50%, 60%)", 70 | neutral50: "hsl(200, 50%, 50%)", 71 | neutral60: "hsl(200, 50%, 40%)", 72 | neutral70: "hsl(200, 50%, 30%)", 73 | neutral80: "hsl(200, 50%, 20%)", 74 | neutral90: "hsl(200, 50%, 10%)", 75 | }, 76 | spacing: { 77 | baseUnit: 4, 78 | controlHeight: 10, 79 | menuGutter: 6, 80 | }, 81 | }, 82 | } 83 | 84 | export default customization 85 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/customization-select/dark-theme.ts: -------------------------------------------------------------------------------- 1 | import { CustomizationConfig } from "@pipedream/connect-react/src/hooks/customization-context" 2 | 3 | const customization: CustomizationConfig = { 4 | styles: { 5 | controlInput: (base, { theme }) => ({ 6 | ...base, 7 | backgroundColor: theme.colors.neutral20, 8 | }), 9 | controlSubmit: (base, { form, theme }) => { 10 | const spinner = `` 11 | return { 12 | ...base, 13 | color: theme.colors.neutral90, 14 | backgroundColor: theme.colors.primary, 15 | backgroundImage: form.submitting ? `url('data:image/svg+xml;base64,${btoa(spinner)}')` : "unset", 16 | backgroundPosition: "right 15% top 50%", 17 | backgroundRepeat: "no-repeat", 18 | } 19 | }, 20 | controlSelect: { 21 | control: (base, { theme }) => ({ 22 | ...base, 23 | backgroundColor: theme.colors.neutral80, 24 | borderColor: "transparent", 25 | }), 26 | option: (base, { theme }) => ({ 27 | ...base, 28 | color: theme.colors.neutral10, 29 | backgroundColor: theme.colors.neutral80, 30 | }), 31 | menuList: (base, { theme }) => ({ 32 | ...base, 33 | color: theme.colors.neutral10, 34 | backgroundColor: theme.colors.neutral80, 35 | }), 36 | singleValue: (base, { theme }) => ({ 37 | ...base, 38 | color: theme.colors.neutral10, 39 | }), 40 | valueContainer: (base, { theme }) => ({ 41 | ...base, 42 | backgroundColor: theme.colors.neutral80, 43 | }), 44 | }, 45 | }, 46 | theme: { 47 | borderRadius: 4, 48 | colors: { 49 | primary: "#2684FF", 50 | primary75: "#4C9AFF", 51 | primary50: "#B2D4FF", 52 | primary25: "#DEEBFF", 53 | 54 | danger: "#DE350B", 55 | dangerLight: "#FFBDAD", 56 | 57 | neutral0: "hsl(0, 0%, 10%)", 58 | neutral5: "hsl(0, 0%, 5%)", 59 | neutral10: "hsl(0, 0%, 10%)", 60 | neutral20: "hsl(0, 0%, 20%)", 61 | neutral30: "hsl(0, 0%, 30%)", 62 | neutral40: "hsl(0, 0%, 40%)", 63 | neutral50: "hsl(0, 0%, 50%)", 64 | neutral60: "hsl(0, 0%, 60%)", 65 | neutral70: "hsl(0, 0%, 70%)", 66 | neutral80: "hsl(0, 0%, 80%)", 67 | neutral90: "hsl(0, 0%, 90%)", 68 | }, 69 | boxShadow: { 70 | button: 71 | "rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.1) 0px 1px 2px -1px", 72 | card: "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)", 73 | dropdown: 74 | "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)", 75 | input: "0 1px 2px 0 rgb(0 0 0 / 0.05)", 76 | }, 77 | spacing: { 78 | baseUnit: 4, 79 | controlHeight: 32, 80 | menuGutter: 8, 81 | }, 82 | }, 83 | } 84 | 85 | export default customization 86 | -------------------------------------------------------------------------------- /connect-react-demo/app/components/customization-select/default-unstyled.ts: -------------------------------------------------------------------------------- 1 | import { CustomizationConfig } from "@pipedream/connect-react/src/hooks/customization-context" 2 | 3 | const customization: CustomizationConfig = { 4 | styles: { 5 | controlSubmit: (base, { form }) => { 6 | const spinner = `` 7 | return { 8 | ...base, 9 | backgroundImage: form.submitting ? `url('data:image/svg+xml;base64,${btoa(spinner)}')` : "unset", 10 | backgroundPosition: "right 15% top 50%", 11 | backgroundRepeat: "no-repeat", 12 | } 13 | }, 14 | }, 15 | 16 | } 17 | 18 | export default customization 19 | -------------------------------------------------------------------------------- /connect-react-demo/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PipedreamHQ/pipedream-connect-examples/b867674600f78662e3af5f0daf4478905dac4893/connect-react-demo/app/favicon.ico -------------------------------------------------------------------------------- /connect-react-demo/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* force to light mode for now (remove this if we support dark mode everywhere in demo) 6 | @media (prefers-color-scheme: dark) { 7 | :root { 8 | --background: #0a0a0a; 9 | --foreground: #ededed; 10 | } 11 | } 12 | */ 13 | 14 | html, 15 | body { 16 | overscroll-behavior: none; 17 | overflow: hidden; 18 | } 19 | 20 | body { 21 | font-family: system-ui, Arial, Helvetica, sans-serif; 22 | } 23 | 24 | .xstr { 25 | color: #032F62; 26 | } 27 | .xcomment { 28 | color: #888; 29 | } 30 | .xkey { 31 | color: #D73A49; 32 | } 33 | .xtag { 34 | /* color: #22863A; - html element */ 35 | color: #005CC5; 36 | } 37 | .xattr { 38 | color: #6F42C1; 39 | } 40 | .xeq { 41 | color: #D73A49; 42 | } 43 | @layer base { 44 | :root { 45 | --background: 0 0% 100%; 46 | --foreground: 240 10% 3.9%; 47 | --card: 0 0% 100%; 48 | --card-foreground: 240 10% 3.9%; 49 | --popover: 0 0% 100%; 50 | --popover-foreground: 240 10% 3.9%; 51 | --primary: 240 5.9% 10%; 52 | --primary-foreground: 0 0% 98%; 53 | --secondary: 240 4.8% 95.9%; 54 | --secondary-foreground: 240 5.9% 10%; 55 | --muted: 240 4.8% 95.9%; 56 | --muted-foreground: 240 3.8% 46.1%; 57 | --accent: 240 4.8% 95.9%; 58 | --accent-foreground: 240 5.9% 10%; 59 | --destructive: 0 84.2% 60.2%; 60 | --destructive-foreground: 0 0% 98%; 61 | --border: 240 5.9% 90%; 62 | --input: 240 5.9% 90%; 63 | --ring: 240 10% 3.9%; 64 | --chart-1: 12 76% 61%; 65 | --chart-2: 173 58% 39%; 66 | --chart-3: 197 37% 24%; 67 | --chart-4: 43 74% 66%; 68 | --chart-5: 27 87% 67%; 69 | --radius: 0.375rem; 70 | --sidebar-background: 0 0% 100%; 71 | --sidebar-foreground: 240 5.3% 26.1%; 72 | --sidebar-primary: 240 5.9% 10%; 73 | --sidebar-primary-foreground: 0 0% 98%; 74 | --sidebar-accent: 240 4.8% 95.9%; 75 | --sidebar-accent-foreground: 240 5.9% 10%; 76 | --sidebar-border: 220 13% 91%; 77 | --sidebar-ring: 217.2 91.2% 59.8%; 78 | } 79 | .dark { 80 | --background: 240 10% 3.9%; 81 | --foreground: 0 0% 98%; 82 | --card: 240 10% 3.9%; 83 | --card-foreground: 0 0% 98%; 84 | --popover: 240 10% 3.9%; 85 | --popover-foreground: 0 0% 98%; 86 | --primary: 0 0% 98%; 87 | --primary-foreground: 240 5.9% 10%; 88 | --secondary: 240 3.7% 15.9%; 89 | --secondary-foreground: 0 0% 98%; 90 | --muted: 240 3.7% 15.9%; 91 | --muted-foreground: 240 5% 64.9%; 92 | --accent: 240 3.7% 15.9%; 93 | --accent-foreground: 0 0% 98%; 94 | --destructive: 0 62.8% 30.6%; 95 | --destructive-foreground: 0 0% 98%; 96 | --border: 240 3.7% 15.9%; 97 | --input: 240 3.7% 15.9%; 98 | --ring: 240 4.9% 83.9%; 99 | --chart-1: 220 70% 50%; 100 | --chart-2: 160 60% 45%; 101 | --chart-3: 30 80% 55%; 102 | --chart-4: 280 65% 60%; 103 | --chart-5: 340 75% 55%; 104 | --sidebar-background: 240 5.9% 10%; 105 | --sidebar-foreground: 240 4.8% 95.9%; 106 | --sidebar-primary: 224.3 76.3% 48%; 107 | --sidebar-primary-foreground: 0 0% 100%; 108 | --sidebar-accent: 240 3.7% 15.9%; 109 | --sidebar-accent-foreground: 240 4.8% 95.9%; 110 | --sidebar-border: 240 3.7% 15.9%; 111 | --sidebar-ring: 217.2 91.2% 59.8%; 112 | } 113 | } 114 | @layer base { 115 | * { 116 | @apply border-border; 117 | } 118 | body { 119 | @apply bg-background text-foreground; 120 | } 121 | } 122 | 123 | .ide-tab { 124 | position: relative; 125 | border-top-left-radius: 6px; 126 | border-top-right-radius: 6px; 127 | border-bottom-left-radius: 0; 128 | border-bottom-right-radius: 0; 129 | margin-right: 2px; 130 | padding: 0 12px; 131 | height: 32px; 132 | background: #fff; 133 | border: 1px solid #e5e7eb; 134 | border-bottom: none; 135 | } 136 | 137 | .ide-tab::after { 138 | content: ''; 139 | position: absolute; 140 | bottom: -1px; 141 | left: 0; 142 | right: 0; 143 | height: 1px; 144 | background: #fff; 145 | } 146 | 147 | .ide-tab:not(.inactive):hover { 148 | background: #fff !important; 149 | } 150 | 151 | .ide-tab.inactive { 152 | background: #f9fafb; 153 | border-color: transparent; 154 | } 155 | 156 | .ide-tab.inactive::after { 157 | display: none; 158 | } 159 | -------------------------------------------------------------------------------- /connect-react-demo/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next" 2 | import "./globals.css" 3 | import { DatadogScript } from "./components/DatadogScript" 4 | 5 | export const metadata: Metadata = { 6 | title: "Demo - Pipedream Connect", 7 | description: "One SDK, thousands of API integrations in your app or AI agent. Pipedream Connect provides managed authentication, approved client IDs, durable components and infratructure for running serverless functions. Delight users, grow product usage and solve complex use cases in minutes.", 8 | } 9 | 10 | export default async function RootLayout({ 11 | children, 12 | }: Readonly<{ 13 | children: React.ReactNode 14 | }>) { 15 | 16 | return ( 17 | 18 | 19 | 20 | {children} 21 | 22 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /connect-react-demo/app/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PipedreamHQ/pipedream-connect-examples/b867674600f78662e3af5f0daf4478905dac4893/connect-react-demo/app/opengraph-image.png -------------------------------------------------------------------------------- /connect-react-demo/app/page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata, ResolvingMetadata } from "next" 2 | import { Suspense } from "react" 3 | import { ClientWrapper } from "./components/ClientWrapper"; 4 | 5 | export async function generateMetadata( 6 | { searchParams: _searchParams }: {searchParams: unknown}, 7 | parent: ResolvingMetadata 8 | ): Promise { 9 | 10 | const title = (await parent).title?.absolute || "" 11 | 12 | return { 13 | title, 14 | }; 15 | } 16 | 17 | export default async function Home({searchParams: _searchParams}: {searchParams: unknown}) { 18 | return ( 19 | 20 | 21 | 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /connect-react-demo/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | } 20 | } -------------------------------------------------------------------------------- /connect-react-demo/components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const badgeVariants = cva( 7 | "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", 13 | secondary: 14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", 15 | destructive: 16 | "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", 17 | outline: "text-foreground", 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: "default", 22 | }, 23 | } 24 | ) 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps {} 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return ( 32 |
33 | ) 34 | } 35 | 36 | export { Badge, badgeVariants } 37 | -------------------------------------------------------------------------------- /connect-react-demo/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", 16 | outline: 17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", 18 | secondary: 19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", 20 | ghost: "hover:bg-accent hover:text-accent-foreground", 21 | link: "text-primary underline-offset-4 hover:underline", 22 | }, 23 | size: { 24 | default: "h-9 px-3 py-2", 25 | sm: "h-8 rounded-md px-3 text-xs", 26 | lg: "h-10 rounded-md px-8", 27 | icon: "h-9 w-9", 28 | }, 29 | }, 30 | defaultVariants: { 31 | variant: "default", 32 | size: "default", 33 | }, 34 | } 35 | ) 36 | 37 | export interface ButtonProps 38 | extends React.ButtonHTMLAttributes, 39 | VariantProps { 40 | asChild?: boolean 41 | } 42 | 43 | const Button = React.forwardRef( 44 | ({ className, variant, size, asChild = false, ...props }, ref) => { 45 | const Comp = asChild ? Slot : "button" 46 | return ( 47 | 52 | ) 53 | } 54 | ) 55 | Button.displayName = "Button" 56 | 57 | export { Button, buttonVariants } 58 | -------------------------------------------------------------------------------- /connect-react-demo/components/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" 4 | 5 | const Collapsible = CollapsiblePrimitive.Root 6 | 7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger 8 | 9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent 10 | 11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent } 12 | -------------------------------------------------------------------------------- /connect-react-demo/components/ui/command.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { DialogProps } from "@radix-ui/react-dialog"; 5 | import { Command as CommandPrimitive } from "cmdk"; 6 | import { IoSearch } from "react-icons/io5"; 7 | 8 | import { cn } from "@/lib/utils"; 9 | import { Dialog, DialogContent } from "@/components/ui/dialog"; 10 | 11 | const Command = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef 14 | >(({ className, ...props }, ref) => ( 15 | 23 | )); 24 | Command.displayName = CommandPrimitive.displayName; 25 | 26 | interface CommandDialogProps extends DialogProps {} 27 | 28 | const CommandDialog = ({ children, ...props }: CommandDialogProps) => { 29 | return ( 30 | 31 | 32 | 33 | {children} 34 | 35 | 36 | 37 | ); 38 | }; 39 | 40 | const CommandInput = React.forwardRef< 41 | React.ElementRef, 42 | React.ComponentPropsWithoutRef 43 | >(({ className, ...props }, ref) => ( 44 |
45 | 46 | 54 |
55 | )); 56 | 57 | CommandInput.displayName = CommandPrimitive.Input.displayName; 58 | 59 | const CommandList = React.forwardRef< 60 | React.ElementRef, 61 | React.ComponentPropsWithoutRef 62 | >(({ className, ...props }, ref) => ( 63 | 68 | )); 69 | 70 | CommandList.displayName = CommandPrimitive.List.displayName; 71 | 72 | const CommandEmpty = React.forwardRef< 73 | React.ElementRef, 74 | React.ComponentPropsWithoutRef 75 | >((props, ref) => ( 76 | 81 | )); 82 | 83 | CommandEmpty.displayName = CommandPrimitive.Empty.displayName; 84 | 85 | const CommandGroup = React.forwardRef< 86 | React.ElementRef, 87 | React.ComponentPropsWithoutRef 88 | >(({ className, ...props }, ref) => ( 89 | 97 | )); 98 | 99 | CommandGroup.displayName = CommandPrimitive.Group.displayName; 100 | 101 | const CommandSeparator = React.forwardRef< 102 | React.ElementRef, 103 | React.ComponentPropsWithoutRef 104 | >(({ className, ...props }, ref) => ( 105 | 110 | )); 111 | CommandSeparator.displayName = CommandPrimitive.Separator.displayName; 112 | 113 | const CommandItem = React.forwardRef< 114 | React.ElementRef, 115 | React.ComponentPropsWithoutRef 116 | >(({ className, ...props }, ref) => ( 117 | 125 | )); 126 | 127 | CommandItem.displayName = CommandPrimitive.Item.displayName; 128 | 129 | const CommandShortcut = ({ 130 | className, 131 | ...props 132 | }: React.HTMLAttributes) => { 133 | return ( 134 | 141 | ); 142 | }; 143 | CommandShortcut.displayName = "CommandShortcut"; 144 | 145 | export { 146 | Command, 147 | CommandDialog, 148 | CommandInput, 149 | CommandList, 150 | CommandEmpty, 151 | CommandGroup, 152 | CommandItem, 153 | CommandShortcut, 154 | CommandSeparator, 155 | }; 156 | -------------------------------------------------------------------------------- /connect-react-demo/components/ui/dialog.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as DialogPrimitive from "@radix-ui/react-dialog"; 5 | import { IoClose } from "react-icons/io5"; 6 | 7 | import { cn } from "@/lib/utils"; 8 | 9 | const Dialog = DialogPrimitive.Root; 10 | 11 | const DialogTrigger = DialogPrimitive.Trigger; 12 | 13 | const DialogPortal = DialogPrimitive.Portal; 14 | 15 | const DialogClose = DialogPrimitive.Close; 16 | 17 | const DialogOverlay = React.forwardRef< 18 | React.ElementRef, 19 | React.ComponentPropsWithoutRef 20 | >(({ className, ...props }, ref) => ( 21 | 29 | )); 30 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; 31 | 32 | const DialogContent = React.forwardRef< 33 | React.ElementRef, 34 | React.ComponentPropsWithoutRef 35 | >(({ className, children, ...props }, ref) => ( 36 | 37 | 38 | 46 | {children} 47 | 48 | 49 | Close 50 | 51 | 52 | 53 | )); 54 | DialogContent.displayName = DialogPrimitive.Content.displayName; 55 | 56 | const DialogHeader = ({ 57 | className, 58 | ...props 59 | }: React.HTMLAttributes) => ( 60 |
67 | ); 68 | DialogHeader.displayName = "DialogHeader"; 69 | 70 | const DialogFooter = ({ 71 | className, 72 | ...props 73 | }: React.HTMLAttributes) => ( 74 |
81 | ); 82 | DialogFooter.displayName = "DialogFooter"; 83 | 84 | const DialogTitle = React.forwardRef< 85 | React.ElementRef, 86 | React.ComponentPropsWithoutRef 87 | >(({ className, ...props }, ref) => ( 88 | 96 | )); 97 | DialogTitle.displayName = DialogPrimitive.Title.displayName; 98 | 99 | const DialogDescription = React.forwardRef< 100 | React.ElementRef, 101 | React.ComponentPropsWithoutRef 102 | >(({ className, ...props }, ref) => ( 103 | 108 | )); 109 | DialogDescription.displayName = DialogPrimitive.Description.displayName; 110 | 111 | export { 112 | Dialog, 113 | DialogPortal, 114 | DialogOverlay, 115 | DialogClose, 116 | DialogTrigger, 117 | DialogContent, 118 | DialogHeader, 119 | DialogFooter, 120 | DialogTitle, 121 | DialogDescription, 122 | }; 123 | -------------------------------------------------------------------------------- /connect-react-demo/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Input = React.forwardRef>( 6 | ({ className, type, ...props }, ref) => { 7 | return ( 8 | 17 | ) 18 | } 19 | ) 20 | Input.displayName = "Input" 21 | 22 | export { Input } 23 | -------------------------------------------------------------------------------- /connect-react-demo/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /connect-react-demo/components/ui/navigation-menu.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu" 3 | import { cva } from "class-variance-authority" 4 | import { cn } from "@/lib/utils" 5 | import { IoChevronDownOutline } from "react-icons/io5" 6 | 7 | const NavigationMenu = React.forwardRef< 8 | React.ElementRef, 9 | React.ComponentPropsWithoutRef 10 | >(({ className, children, ...props }, ref) => ( 11 | 19 | {children} 20 | 21 | 22 | )) 23 | NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName 24 | 25 | const NavigationMenuList = React.forwardRef< 26 | React.ElementRef, 27 | React.ComponentPropsWithoutRef 28 | >(({ className, ...props }, ref) => ( 29 | 37 | )) 38 | NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName 39 | 40 | const NavigationMenuItem = NavigationMenuPrimitive.Item 41 | 42 | const navigationMenuTriggerStyle = cva( 43 | "group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50" 44 | ) 45 | 46 | const NavigationMenuTrigger = React.forwardRef< 47 | React.ElementRef, 48 | React.ComponentPropsWithoutRef 49 | >(({ className, children, ...props }, ref) => ( 50 | 55 | {children}{" "} 56 | 61 | )) 62 | NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName 63 | 64 | const NavigationMenuContent = React.forwardRef< 65 | React.ElementRef, 66 | React.ComponentPropsWithoutRef 67 | >(({ className, ...props }, ref) => ( 68 | 76 | )) 77 | NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName 78 | 79 | const NavigationMenuLink = NavigationMenuPrimitive.Link 80 | 81 | const NavigationMenuViewport = React.forwardRef< 82 | React.ElementRef, 83 | React.ComponentPropsWithoutRef 84 | >(({ className, ...props }, ref) => ( 85 |
86 | 94 |
95 | )) 96 | NavigationMenuViewport.displayName = 97 | NavigationMenuPrimitive.Viewport.displayName 98 | 99 | const NavigationMenuIndicator = React.forwardRef< 100 | React.ElementRef, 101 | React.ComponentPropsWithoutRef 102 | >(({ className, ...props }, ref) => ( 103 | 111 |
112 | 113 | )) 114 | NavigationMenuIndicator.displayName = 115 | NavigationMenuPrimitive.Indicator.displayName 116 | 117 | export { 118 | navigationMenuTriggerStyle, 119 | NavigationMenu, 120 | NavigationMenuList, 121 | NavigationMenuItem, 122 | NavigationMenuContent, 123 | NavigationMenuTrigger, 124 | NavigationMenuLink, 125 | NavigationMenuIndicator, 126 | NavigationMenuViewport, 127 | } 128 | -------------------------------------------------------------------------------- /connect-react-demo/components/ui/popover.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as PopoverPrimitive from "@radix-ui/react-popover" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Popover = PopoverPrimitive.Root 9 | 10 | const PopoverTrigger = PopoverPrimitive.Trigger 11 | 12 | const PopoverAnchor = PopoverPrimitive.Anchor 13 | 14 | const PopoverContent = React.forwardRef< 15 | React.ElementRef, 16 | React.ComponentPropsWithoutRef 17 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( 18 | 19 | 29 | 30 | )) 31 | PopoverContent.displayName = PopoverPrimitive.Content.displayName 32 | 33 | export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } 34 | -------------------------------------------------------------------------------- /connect-react-demo/components/ui/resizable.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | import * as ResizablePrimitive from "react-resizable-panels" 3 | import { cn } from "@/lib/utils" 4 | import { IoReorderThreeOutline } from "react-icons/io5" 5 | 6 | const ResizablePanelGroup = ({ 7 | className, 8 | ...props 9 | }: React.ComponentProps) => ( 10 | 17 | ) 18 | 19 | const ResizablePanel = ResizablePrimitive.Panel 20 | 21 | const ResizableHandle = ({ 22 | withHandle, 23 | className, 24 | ...props 25 | }: React.ComponentProps & { 26 | withHandle?: boolean 27 | }) => ( 28 | div]:rotate-90", 31 | className 32 | )} 33 | {...props} 34 | > 35 | {withHandle && ( 36 |
37 | 38 |
39 | )} 40 |
41 | ) 42 | 43 | export { ResizablePanelGroup, ResizablePanel, ResizableHandle } 44 | -------------------------------------------------------------------------------- /connect-react-demo/components/ui/scroll-area.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const ScrollArea = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, children, ...props }, ref) => ( 12 | 17 | 18 | {children} 19 | 20 | 21 | 22 | 23 | )) 24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName 25 | 26 | const ScrollBar = React.forwardRef< 27 | React.ElementRef, 28 | React.ComponentPropsWithoutRef 29 | >(({ className, orientation = "vertical", ...props }, ref) => ( 30 | 43 | 44 | 45 | )) 46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName 47 | 48 | export { ScrollArea, ScrollBar } 49 | -------------------------------------------------------------------------------- /connect-react-demo/components/ui/select.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SelectPrimitive from "@radix-ui/react-select" 5 | import { cn } from "@/lib/utils" 6 | import { IoCheckmark, IoChevronDown, IoChevronUp } from "react-icons/io5" 7 | 8 | const Select = SelectPrimitive.Root 9 | 10 | const SelectGroup = SelectPrimitive.Group 11 | 12 | const SelectValue = SelectPrimitive.Value 13 | 14 | const SelectTrigger = React.forwardRef< 15 | React.ElementRef, 16 | React.ComponentPropsWithoutRef 17 | >(({ className, children, ...props }, ref) => ( 18 | span]:line-clamp-1", 22 | className 23 | )} 24 | {...props} 25 | > 26 | {children} 27 | 28 | 29 | 30 | 31 | )) 32 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName 33 | 34 | const SelectScrollUpButton = React.forwardRef< 35 | React.ElementRef, 36 | React.ComponentPropsWithoutRef 37 | >(({ className, ...props }, ref) => ( 38 | 46 | 47 | 48 | )) 49 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName 50 | 51 | const SelectScrollDownButton = React.forwardRef< 52 | React.ElementRef, 53 | React.ComponentPropsWithoutRef 54 | >(({ className, ...props }, ref) => ( 55 | 63 | 64 | 65 | )) 66 | SelectScrollDownButton.displayName = 67 | SelectPrimitive.ScrollDownButton.displayName 68 | 69 | const SelectContent = React.forwardRef< 70 | React.ElementRef, 71 | React.ComponentPropsWithoutRef 72 | >(({ className, children, position = "popper", ...props }, ref) => ( 73 | 74 | 85 | 86 | 93 | {children} 94 | 95 | 96 | 97 | 98 | )) 99 | SelectContent.displayName = SelectPrimitive.Content.displayName 100 | 101 | const SelectLabel = React.forwardRef< 102 | React.ElementRef, 103 | React.ComponentPropsWithoutRef 104 | >(({ className, ...props }, ref) => ( 105 | 110 | )) 111 | SelectLabel.displayName = SelectPrimitive.Label.displayName 112 | 113 | const SelectItem = React.forwardRef< 114 | React.ElementRef, 115 | React.ComponentPropsWithoutRef 116 | >(({ className, children, ...props }, ref) => ( 117 | 125 | 126 | 127 | 128 | 129 | 130 | {children} 131 | 132 | )) 133 | SelectItem.displayName = SelectPrimitive.Item.displayName 134 | 135 | const SelectSeparator = React.forwardRef< 136 | React.ElementRef, 137 | React.ComponentPropsWithoutRef 138 | >(({ className, ...props }, ref) => ( 139 | 144 | )) 145 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName 146 | 147 | export { 148 | Select, 149 | SelectGroup, 150 | SelectValue, 151 | SelectTrigger, 152 | SelectContent, 153 | SelectLabel, 154 | SelectItem, 155 | SelectSeparator, 156 | SelectScrollUpButton, 157 | SelectScrollDownButton, 158 | } 159 | -------------------------------------------------------------------------------- /connect-react-demo/components/ui/switch.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SwitchPrimitives from "@radix-ui/react-switch" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Switch = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | 25 | 26 | )) 27 | Switch.displayName = SwitchPrimitives.Root.displayName 28 | 29 | export { Switch } 30 | -------------------------------------------------------------------------------- /connect-react-demo/components/ui/tabs.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as TabsPrimitive from "@radix-ui/react-tabs" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Tabs = TabsPrimitive.Root 9 | 10 | const TabsList = React.forwardRef< 11 | React.ElementRef, 12 | React.ComponentPropsWithoutRef 13 | >(({ className, ...props }, ref) => ( 14 | 22 | )) 23 | TabsList.displayName = TabsPrimitive.List.displayName 24 | 25 | const TabsTrigger = React.forwardRef< 26 | React.ElementRef, 27 | React.ComponentPropsWithoutRef 28 | >(({ className, ...props }, ref) => ( 29 | 37 | )) 38 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName 39 | 40 | const TabsContent = React.forwardRef< 41 | React.ElementRef, 42 | React.ComponentPropsWithoutRef 43 | >(({ className, ...props }, ref) => ( 44 | 52 | )) 53 | TabsContent.displayName = TabsPrimitive.Content.displayName 54 | 55 | export { Tabs, TabsList, TabsTrigger, TabsContent } 56 | -------------------------------------------------------------------------------- /connect-react-demo/components/ui/tooltip.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const TooltipProvider = TooltipPrimitive.Provider 9 | 10 | const Tooltip = TooltipPrimitive.Root 11 | 12 | const TooltipTrigger = TooltipPrimitive.Trigger 13 | 14 | const TooltipContent = React.forwardRef< 15 | React.ElementRef, 16 | React.ComponentPropsWithoutRef 17 | >(({ className, sideOffset = 4, ...props }, ref) => ( 18 | 19 | 28 | 29 | )) 30 | TooltipContent.displayName = TooltipPrimitive.Content.displayName 31 | 32 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } 33 | -------------------------------------------------------------------------------- /connect-react-demo/lib/app-state.tsx: -------------------------------------------------------------------------------- 1 | import blueTheme from "@/app/components/customization-select/blue-theme" 2 | import darkTheme from "@/app/components/customization-select/dark-theme" 3 | import defaultTheme from "@/app/components/customization-select/default-unstyled" 4 | import { createContext, useContext, useState } from "react" 5 | // @ts-ignore 6 | import blueThemeCode from "raw-loader!@/app/components/customization-select/blue-theme.ts" 7 | import { useComponent, useFrontendClient } from "@pipedream/connect-react" 8 | import { useSearchParams } from "next/navigation"; 9 | // @ts-ignore 10 | import darkThemeCode from "raw-loader!@/app/components/customization-select/dark-theme.ts" 11 | import { useQueryParams } from "./use-query-params" 12 | 13 | const customizationOptions = [ 14 | { 15 | name: "default", 16 | label: "Default Styles", 17 | customization: defaultTheme, 18 | file: undefined, 19 | }, 20 | { 21 | name: "blue", 22 | label: "Blue Theme", 23 | customization: blueTheme, 24 | file: blueThemeCode.split("\n\n\n;")[0], 25 | }, 26 | { 27 | name: "dark", 28 | label: "Dark Theme", 29 | containerStyle: { backgroundColor: "#202020" }, 30 | customization: darkTheme, 31 | file: darkThemeCode.split("\n\n\n;")[0], 32 | }, 33 | ] 34 | 35 | const useAppStateProviderValue = () => { 36 | const client = useFrontendClient() 37 | const userId = client.externalUserId || "" 38 | 39 | const [customizationOption, setCustomizationOption] = useState( 40 | customizationOptions[0] 41 | ) 42 | 43 | const [activeTypingIndex, setActiveTypingIndex] = useState(0) 44 | 45 | const refreshUserId = () => {} // no op since cannot serialize with "use client" 46 | 47 | const {queryParams, setQueryParam, setQueryParams} = useQueryParams() 48 | 49 | const propNames = queryParams.propNames ? queryParams.propNames.split(",") : [] 50 | const setPropNames = (value: string[]) => setQueryParam("propNames", value?.length ? value.join(",") : undefined) 51 | 52 | // XXX Selected* -> Select* (to differentiate from actual selected component (on the left)) ? 53 | const selectedAppSlug = queryParams.app || "google_sheets" 54 | const setSelectedAppSlug = (value: string) => { 55 | setQueryParams([ 56 | { key: "component", value: undefined }, 57 | { key: "app", value }, 58 | ]); 59 | } 60 | const removeSelectedAppSlug = () => { 61 | setQueryParams([ 62 | { key: "component", value: undefined }, 63 | { key: "app", value: undefined }, 64 | ]); 65 | } 66 | 67 | const selectedApp = { name_slug: selectedAppSlug } 68 | 69 | const selectedComponentType = queryParams.type || "action" 70 | const setSelectedComponentType = (value: string) => setQueryParam("type", value) 71 | const removeSelectedComponentType = () => setQueryParam("type", undefined) 72 | 73 | const [webhookUrl, setWebhookUrl] = useState("") 74 | 75 | const selectedComponentKey = queryParams.component //|| "google_sheets-add-single-row" 76 | const setSelectedComponentKey = (value: string) => { 77 | setQueryParams([{key: "component", value}, {key: "propNames", value: undefined}]) 78 | setConfiguredProps({}) 79 | setActionRunOutput(undefined) 80 | } 81 | const removeSelectedComponentKey = () => { 82 | setQueryParams([ 83 | { key: "component", value: undefined }, 84 | { key: "propNames", value: undefined }, 85 | ]); 86 | setConfiguredProps({}) 87 | setActionRunOutput(undefined) 88 | } 89 | 90 | const selectedComponent = { key: selectedComponentKey } 91 | 92 | const { 93 | component, 94 | }: { 95 | component?: any 96 | } = useComponent({ 97 | key: selectedComponent?.key, 98 | }) 99 | 100 | const searchParams = useSearchParams() 101 | 102 | const showStressTest = searchParams.get("stress") != null 103 | const [stressTestConfiguredProps, setStressTestConfiguredProps] = useState< 104 | Record 105 | >({}) 106 | 107 | const [configuredProps, setConfiguredProps] = useState>( 108 | {} 109 | ) 110 | const [actionRunOutput, setActionRunOutput] = useState() 111 | 112 | const hideOptionalProps = queryParams.hideOptionalProps === "true" 113 | const setHideOptionalProps = (value: boolean) => setQueryParam("hideOptionalProps", value ? "true" : undefined) 114 | 115 | const enableDebugging = queryParams.enableDebugging === "true" 116 | const setEnableDebugging = (value: boolean) => setQueryParam("enableDebugging", value ? "true" : undefined) 117 | 118 | const code = `import { createFrontendClient } from "@pipedream/sdk" 119 | import { FrontendClientProvider, ComponentFormContainer } from "@pipedream/connect-react"${ 120 | customizationOption.file 121 | ? ` 122 | import customization from "./customizations/${customizationOption.name}"` 123 | : "" 124 | } 125 | 126 | const client = createFrontendClient() 127 | 128 | export function MyPage() { 129 | return ( 130 | 131 | 155 | 156 | ) 157 | }` 158 | 159 | const [fileCode, setFileCode] = useState() 160 | 161 | return { 162 | userId, 163 | refreshUserId, 164 | 165 | customizationOptions, 166 | customizationOption, 167 | setCustomizationOption, 168 | 169 | activeTypingIndex, 170 | setActiveTypingIndex, 171 | 172 | propNames, 173 | setPropNames, 174 | 175 | selectedAppSlug, 176 | setSelectedAppSlug, 177 | removeSelectedAppSlug, 178 | 179 | selectedComponentType, 180 | setSelectedComponentType, 181 | removeSelectedComponentType, 182 | 183 | webhookUrl, 184 | setWebhookUrl, 185 | 186 | selectedComponentKey, 187 | setSelectedComponentKey, 188 | removeSelectedComponentKey, 189 | 190 | selectedApp, 191 | selectedComponent, 192 | component, 193 | 194 | showStressTest, 195 | 196 | stressTestConfiguredProps, 197 | setStressTestConfiguredProps, 198 | 199 | configuredProps, 200 | setConfiguredProps, 201 | 202 | actionRunOutput, 203 | setActionRunOutput, 204 | 205 | hideOptionalProps, 206 | setHideOptionalProps, 207 | 208 | enableDebugging, 209 | setEnableDebugging, 210 | 211 | fileCode, 212 | setFileCode, 213 | 214 | code, 215 | } 216 | } 217 | 218 | const AppStateContext = createContext | null>(null); 219 | 220 | export const AppStateProvider = ({children}: React.PropsWithChildren) => { 221 | const providerValue = useAppStateProviderValue(); 222 | 223 | return {children} 224 | } 225 | 226 | export const useAppState = () => { 227 | const context = useContext(AppStateContext) 228 | if (!context) { 229 | throw new Error("useAppState must be used within an AppStateProvider") 230 | } 231 | return context 232 | } 233 | -------------------------------------------------------------------------------- /connect-react-demo/lib/backend-client.ts: -------------------------------------------------------------------------------- 1 | 2 | import { createBackendClient } from "@pipedream/sdk/server" 3 | import { env } from "@/lib/env"; 4 | 5 | 6 | export const backendClient = () => { 7 | return createBackendClient({ 8 | apiHost: env.PIPEDREAM_API_HOST, 9 | environment: env.PIPEDREAM_PROJECT_ENVIRONMENT, 10 | projectId: env.PIPEDREAM_PROJECT_ID, 11 | credentials: { 12 | clientId: env.PIPEDREAM_CLIENT_ID, 13 | clientSecret: env.PIPEDREAM_CLIENT_SECRET, 14 | }, 15 | workflowDomain: env.PIPEDREAM_WORKFLOW_DOMAIN, 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /connect-react-demo/lib/env.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | const envSchema = z.object({ 4 | PIPEDREAM_CLIENT_ID: z.string().min(1, "CLIENT_ID is required"), 5 | PIPEDREAM_CLIENT_SECRET: z.string().min(1, "CLIENT_SECRET is required"), 6 | PIPEDREAM_PROJECT_ID: z.string().min(1, "PROJECT_ID is required"), 7 | PIPEDREAM_PROJECT_ENVIRONMENT: z.enum(["development", "production"]), 8 | PIPEDREAM_ALLOWED_ORIGINS: z.preprocess( 9 | (val) => { 10 | if (typeof val !== "string") { 11 | return val; // Pass through for validation as non-string inputs will fail later 12 | } 13 | try { 14 | return JSON.parse(val); 15 | } catch { 16 | return val // Return raw value, which will fail later 17 | } 18 | }, 19 | z 20 | .array(z.string()) 21 | .nonempty("ALLOWED_ORIGINS must be a non-empty array of strings") 22 | .refine( 23 | (origins) => origins.every((origin) => typeof origin === "string"), 24 | "ALLOWED_ORIGINS must contain only strings" 25 | ) 26 | ), 27 | 28 | // Optional environment variables, useful for different environments (e.g. 29 | // local development, production, etc.). 30 | PIPEDREAM_API_HOST: z.optional(z.string().default("api.pipedream.com")), 31 | PIPEDREAM_WORKFLOW_DOMAIN: z.optional(z.string().default("m.pipedream.net")), 32 | 33 | // Datadog 34 | DD_APPLICATION_ID: z.optional(z.string()), 35 | DD_CLIENT_TOKEN: z.optional(z.string()), 36 | DD_SERVICE: z.optional(z.string()), 37 | NEXT_PUBLIC_GIT_COMMIT_SHA: z.optional(z.string()), 38 | }); 39 | 40 | const parsedEnv = envSchema.safeParse(process.env); 41 | 42 | if (!parsedEnv.success) { 43 | console.error("Environment variable validation failed:", parsedEnv.error.format()); 44 | process.exit(1); // Exit the process if validation fails 45 | } 46 | 47 | export const env = parsedEnv.data; 48 | -------------------------------------------------------------------------------- /connect-react-demo/lib/query-params.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { z } from "zod"; 3 | 4 | export const appSlug = z.string().optional(); 5 | export const componentType = z.string().optional(); 6 | export const componentKeySlug = z.string().optional(); 7 | export const propNames = z.string().optional(); 8 | export const hideOptionalProps = z.string().optional(); 9 | export const enableDebugging = z.string().optional(); 10 | 11 | export const queryParamSchema = z.object({ 12 | app: appSlug, 13 | component: componentKeySlug, 14 | propNames, 15 | hideOptionalProps, 16 | enableDebugging, 17 | type: componentType, 18 | }); 19 | -------------------------------------------------------------------------------- /connect-react-demo/lib/stable-uuid.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react" 2 | import { v4 as uuid } from "uuid" 3 | 4 | 5 | export const useStableUuid = (): [string, () => void] => { 6 | const [id, setId] = useState("") 7 | 8 | const refresh = () => { 9 | setId(`demo-${uuid()}`) 10 | } 11 | 12 | useEffect(() => { 13 | refresh() 14 | }, []) 15 | 16 | return [id, refresh] 17 | } 18 | -------------------------------------------------------------------------------- /connect-react-demo/lib/use-query-params.tsx: -------------------------------------------------------------------------------- 1 | import { usePathname, useRouter, useSearchParams } from "next/navigation"; 2 | import { z } from "zod"; 3 | import { queryParamSchema } from "./query-params"; 4 | 5 | 6 | type KeyValPair = {key: keyof z.infer, value: string | undefined} 7 | 8 | 9 | export const useQueryParams = () => { 10 | const router = useRouter(); 11 | const pathname = usePathname(); 12 | const searchParams = useSearchParams(); 13 | 14 | const setQueryParams = (keyVals: KeyValPair[]) => { 15 | const current = new URLSearchParams(Array.from(searchParams.entries())); 16 | 17 | for (const {key, value} of keyVals) { 18 | if (value && !(Array.isArray(value) && !value.length)) { 19 | current.set(key, value); 20 | } else if (current.has(key)) { 21 | current.delete(key); 22 | } 23 | } 24 | 25 | const search = current.toString(); 26 | const query = search ? `?${search}` : ""; 27 | 28 | router.replace(`${pathname}${query}`); 29 | } 30 | 31 | const setQueryParam = (key: KeyValPair["key"], value: KeyValPair["value"]) => { 32 | setQueryParams([{key, value}]); 33 | } 34 | 35 | const queryParams = queryParamSchema.parse(Object.fromEntries(searchParams.entries())); 36 | 37 | return { 38 | setQueryParam, 39 | setQueryParams, 40 | queryParams, 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /connect-react-demo/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /connect-react-demo/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. 6 | -------------------------------------------------------------------------------- /connect-react-demo/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | eslint: { 4 | ignoreDuringBuilds: true, 5 | }, 6 | typescript: { 7 | ignoreBuildErrors: true, 8 | }, 9 | } 10 | 11 | export default nextConfig 12 | -------------------------------------------------------------------------------- /connect-react-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "connect-react-demo", 3 | "version": "0.2.0", 4 | "private": true, 5 | "packageManager": "pnpm@10.11.0", 6 | "scripts": { 7 | "dev": "next dev", 8 | "build": "next build", 9 | "start": "next start", 10 | "lint": "next lint" 11 | }, 12 | "dependencies": { 13 | "@pipedream/connect-react": "^1.2.0", 14 | "@pipedream/sdk": "^1.6.6", 15 | "@radix-ui/react-collapsible": "^1.1.1", 16 | "@radix-ui/react-label": "^2.1.0", 17 | "@radix-ui/react-navigation-menu": "^1.2.1", 18 | "@radix-ui/react-popover": "^1.1.2", 19 | "@radix-ui/react-scroll-area": "^1.2.0", 20 | "@radix-ui/react-select": "^2.1.2", 21 | "@radix-ui/react-slot": "^1.1.0", 22 | "@radix-ui/react-switch": "^1.1.1", 23 | "@radix-ui/react-tabs": "^1.1.1", 24 | "@radix-ui/react-tooltip": "^1.1.3", 25 | "class-variance-authority": "^0.7.0", 26 | "clsx": "^2.1.1", 27 | "framer-motion": "^11.11.11", 28 | "next": "14.2.14", 29 | "react": "^18", 30 | "react-dom": "^18", 31 | "react-icons": "^5.3.0", 32 | "react-resizable-panels": "^2.1.6", 33 | "react-select": "^5.8.2", 34 | "react-syntax-highlighter": "^15.6.1", 35 | "tailwind-merge": "^2.5.4", 36 | "tailwindcss-animate": "^1.0.7", 37 | "usehooks-ts": "^3.1.0", 38 | "uuid": "^11.0.2", 39 | "zod": "^3.23.8" 40 | }, 41 | "devDependencies": { 42 | "@types/node": "^20", 43 | "@types/react": "^18", 44 | "@types/react-dom": "^18", 45 | "@types/react-syntax-highlighter": "^15.5.13", 46 | "postcss": "^8", 47 | "raw-loader": "^4.0.2", 48 | "tailwindcss": "^3.4.1", 49 | "typescript": "^5" 50 | }, 51 | "pnpm": { 52 | "overrides": { 53 | "@pipedream/sdk": "^1.6.6" 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /connect-react-demo/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /connect-react-demo/public/pd_theme_basic.css: -------------------------------------------------------------------------------- 1 | /* TODO this needs to be part of the browser sdk web-components */ 2 | .pd-form { 3 | gap: 10px; 4 | } 5 | .pd-field { 6 | } 7 | .pd-field input { 8 | padding: 8px; 9 | font-size: 16px; 10 | border: 1px solid #ccc; 11 | border-radius: 3px; 12 | } 13 | .pd-label { 14 | color: rgb(78, 85, 94); 15 | font-weight: bold; 16 | } 17 | [data-required] .pd-label::after { 18 | content: " *"; 19 | color: red; 20 | font-size: 12px; 21 | vertical-align: top; 22 | } 23 | .pd-description { 24 | color: #888; 25 | font-size: 0.9em; 26 | margin: 0; 27 | } 28 | .pd-field input[type=checkbox] { 29 | width: 16px; 30 | height: 16px; 31 | } 32 | .pd-optionals button { 33 | border: 1px solid #bbb; 34 | border-radius: 4px; 35 | background: white; 36 | color: #888; 37 | font-weight: bold; 38 | } 39 | .pd-optionals button:hover { 40 | color: #444; 41 | border: 1px solid #444; 42 | box-shadow: 2px 2px 2px #ddd; 43 | } 44 | .pd-optional-hide { 45 | background: transparent; 46 | border: 0; 47 | font-weight: bold; 48 | color: #aaa; 49 | } 50 | .pd-optional-hide:hover { 51 | background: transparent; 52 | border: 0; 53 | font-weight: bold; 54 | color: #444; 55 | } 56 | -------------------------------------------------------------------------------- /connect-react-demo/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss" 2 | 3 | const config: Config = { 4 | darkMode: ["class"], 5 | content: [ 6 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 8 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 9 | ], 10 | theme: { 11 | extend: { 12 | colors: { 13 | background: "hsl(var(--background))", 14 | foreground: "hsl(var(--foreground))", 15 | primary: { 16 | DEFAULT: "hsl(var(--primary))", 17 | foreground: "hsl(var(--primary-foreground))", 18 | }, 19 | secondary: { 20 | DEFAULT: "hsl(var(--secondary))", 21 | foreground: "hsl(var(--secondary-foreground))", 22 | }, 23 | muted: { 24 | DEFAULT: "hsl(var(--muted))", 25 | foreground: "hsl(var(--muted-foreground))", 26 | }, 27 | accent: { 28 | DEFAULT: "hsl(var(--accent))", 29 | foreground: "hsl(var(--accent-foreground))", 30 | }, 31 | destructive: { 32 | DEFAULT: "hsl(var(--destructive))", 33 | foreground: "hsl(var(--destructive-foreground))", 34 | }, 35 | border: "hsl(var(--border))", 36 | input: "hsl(var(--input))", 37 | ring: "hsl(var(--ring))", 38 | card: { 39 | DEFAULT: "hsl(var(--card))", 40 | foreground: "hsl(var(--card-foreground))", 41 | }, 42 | popover: { 43 | DEFAULT: "hsl(var(--popover))", 44 | foreground: "hsl(var(--popover-foreground))", 45 | }, 46 | chart: { 47 | "1": "hsl(var(--chart-1))", 48 | "2": "hsl(var(--chart-2))", 49 | "3": "hsl(var(--chart-3))", 50 | "4": "hsl(var(--chart-4))", 51 | "5": "hsl(var(--chart-5))", 52 | }, 53 | }, 54 | borderRadius: { 55 | lg: "var(--radius)", 56 | md: "calc(var(--radius) - 2px)", 57 | sm: "calc(var(--radius) - 4px)", 58 | }, 59 | }, 60 | }, 61 | plugins: [], 62 | } 63 | export default config 64 | -------------------------------------------------------------------------------- /connect-react-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "next.config.mjs"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /managed-auth-basic-next-app/.env.example: -------------------------------------------------------------------------------- 1 | PIPEDREAM_CLIENT_ID= 2 | PIPEDREAM_CLIENT_SECRET= 3 | PIPEDREAM_PROJECT_ID= 4 | PIPEDREAM_PROJECT_ENVIRONMENT=development 5 | -------------------------------------------------------------------------------- /managed-auth-basic-next-app/.eslintignore: -------------------------------------------------------------------------------- 1 | .eslintrc.js 2 | -------------------------------------------------------------------------------- /managed-auth-basic-next-app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: [ 5 | 'next/core-web-vitals', 6 | 'plugin:@typescript-eslint/recommended', 7 | ], 8 | plugins: ['@typescript-eslint'], 9 | rules: { 10 | // Add your custom rules here 11 | }, 12 | ignorePatterns: ['node_modules/', '.eslintrc.js'], 13 | }; 14 | 15 | -------------------------------------------------------------------------------- /managed-auth-basic-next-app/.gitignore: -------------------------------------------------------------------------------- 1 | .env.* 2 | !.env.example 3 | node_modules 4 | .next/ 5 | -------------------------------------------------------------------------------- /managed-auth-basic-next-app/.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 22.10.0 2 | -------------------------------------------------------------------------------- /managed-auth-basic-next-app/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Next.js: Full Stack", 8 | "runtimeExecutable": "node", 9 | "runtimeArgs": [ 10 | "--inspect", 11 | "./node_modules/.bin/next", 12 | "dev" 13 | ], 14 | "attachSimplePort": 9230, 15 | "skipFiles": ["/**"], 16 | "sourceMaps": true, 17 | "console": "integratedTerminal", 18 | "internalConsoleOptions": "neverOpen" 19 | }, 20 | { 21 | "name": "Next.js: debug server-side", 22 | "type": "node-terminal", 23 | "request": "launch", 24 | "command": "npm run dev" 25 | }, 26 | { 27 | "name": "Next.js: debug client-side", 28 | "type": "chrome", 29 | "request": "launch", 30 | "url": "http://localhost:3000" 31 | }, 32 | { 33 | "name": "Next.js: debug full stack", 34 | "type": "node", 35 | "request": "launch", 36 | "program": "${workspaceFolder}/node_modules/.bin/next", 37 | "runtimeArgs": ["--inspect"], 38 | "skipFiles": ["/**"], 39 | "serverReadyAction": { 40 | "action": "debugWithEdge", 41 | "killOnServerStop": true, 42 | "pattern": "- Local:.+(https?://.+)", 43 | "uriFormat": "%s", 44 | "webRoot": "${workspaceFolder}" 45 | } 46 | } 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /managed-auth-basic-next-app/README.md: -------------------------------------------------------------------------------- 1 | # Example Pipedream Connect app 2 | 3 | This app provides an example implementation of Pipedream Connect. [See the full quickstart](https://pipedream.com/docs/connect/quickstart) for instructions on how to configure your Pipedream project with this example app. 4 | 5 | ## Getting Started 6 | 7 | First, 8 | 9 | ```bash 10 | cp .env.example .env.local 11 | ``` 12 | 13 | Then fill in the values of all environment variables. 14 | 15 | Then install dependencies and run the development server: 16 | 17 | ```bash 18 | npm i # installs dependencies 19 | npm run dev # runs app locally 20 | ``` 21 | 22 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the example app running. Start editing by modifying `app/page.tsx` or `app/server.ts`. The app auto-updates as you edit files. 23 | 24 | ## Learn more about Next.js 25 | 26 | This example app is built with Next.js, a framework for quickly developing web apps with React. To learn more about Next, take a look at the following resources: 27 | 28 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 29 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 30 | 31 | ## Deploy 32 | 33 | The easiest way to deploy this app is to use [Vercel](https://vercel.com/new). Check out the [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 34 | -------------------------------------------------------------------------------- /managed-auth-basic-next-app/app/CodePanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import Prism from "prismjs"; 3 | import "prismjs/themes/prism-okaidia.css"; 4 | import "prismjs/components/prism-typescript"; 5 | import "prismjs/components/prism-bash"; 6 | 7 | interface CodePanelProps { 8 | code: string; 9 | language: string; 10 | } 11 | 12 | const CodePanel = ({ code, language }: CodePanelProps) => { 13 | const [highlightedCode, setHighlightedCode] = useState(""); 14 | 15 | useEffect(() => { 16 | const highlighted = Prism.highlight( 17 | code, 18 | Prism.languages.typescript, 19 | language, 20 | ); 21 | const lines = highlighted.split("\n").map( 22 | (line, index) => 23 | `
24 | ${index + 1} 25 | ${line} 26 |
`, 27 | ); 28 | setHighlightedCode(lines.join("")); 29 | }, [code, language]); 30 | 31 | return ( 32 |
33 |
34 |
35 | 36 | 37 | 38 |
39 |
40 |
41 |
42 |           
43 |           
44 |
45 |
46 |
47 | ); 48 | }; 49 | 50 | export default CodePanel; 51 | -------------------------------------------------------------------------------- /managed-auth-basic-next-app/app/accounts/accounts.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import CodePanel from "../CodePanel"; 4 | import { useEffect, useState } from "react"; 5 | import { useSearchParams } from "next/navigation"; 6 | import { getUserAccounts, serverConnectTokenCreate, makeAppRequest, getTestRequest } from "../server" 7 | 8 | export default function Home() { 9 | const [externalUserId, setExternalUserId] = useState(null); 10 | const [apn, setAuthProvisionId] = useState(null) 11 | const [nameSlug, setNameSlug] = useState(null) 12 | const [externalAccounts, setExternalAccounts] = useState(null) 13 | const [testRequestConfig, setTestRequestConfig] = useState(null) 14 | const searchParams = useSearchParams() 15 | 16 | 17 | // request stuff 18 | const [method, setMethod] = useState("GET"); 19 | const [url, setUrl] = useState(""); 20 | const [body, setBody] = useState(""); 21 | const [responseBody, setResponseBody] = useState(""); 22 | const [headersArray, setHeadersArray] = useState([]) 23 | 24 | 25 | 26 | useEffect(() => { 27 | if (externalUserId) { 28 | (async () => { 29 | try { 30 | const accounts = await getUserAccounts(externalUserId) 31 | setExternalAccounts(accounts) 32 | } catch (error) { 33 | console.error("Error fetching data:", error) 34 | // Handle error appropriately 35 | } 36 | })() 37 | 38 | 39 | } else { 40 | setExternalAccounts(null); 41 | } 42 | }, [externalUserId]); 43 | 44 | useEffect(() => { 45 | const uuid = searchParams.get("uuid") ? searchParams.get("uuid") : crypto.randomUUID() 46 | setExternalUserId(uuid); 47 | }, []); 48 | 49 | useEffect(() => { 50 | if (nameSlug) { 51 | (async () => { 52 | try { 53 | const testRequest = await getTestRequest(nameSlug) 54 | console.log("setting test request") 55 | console.log("testRequest", testRequest) 56 | setTestRequestConfig(testRequest) 57 | 58 | // set some other stuff now? 59 | setUrl(testRequest.url || "") 60 | setMethod(testRequest.method || "GET") 61 | const headers = Object.entries(testRequest.config.headers).reduce((acc, [key, value]) => { 62 | if (key.toLowerCase() != "authorization") { 63 | acc.push({ name: key, value:value }) 64 | } 65 | return acc 66 | }, []) 67 | setHeadersArray([...headers]) 68 | } catch (error) { 69 | console.error("Error fetching data:", error) 70 | // Handle error appropriately 71 | } 72 | })() 73 | 74 | } 75 | 76 | }, [nameSlug]) 77 | 78 | const handleRadioChange = (account) => { 79 | setAuthProvisionId(account.id) 80 | setNameSlug(account.app.name_slug) 81 | } 82 | 83 | const makeRequest = async() => { 84 | const headers = headersArray.reduce((acc, header) => { 85 | if (header.name && header.value) { 86 | acc[header.name] = header.value 87 | } 88 | return acc 89 | }, {}) 90 | const resp = await makeAppRequest(apn, url, nameSlug, { 91 | method, 92 | headers, 93 | body, 94 | }) 95 | setResponseBody(JSON.stringify(resp, null, 2)) 96 | } 97 | 98 | const addHeader = () => { 99 | setHeadersArray([...headersArray, {}]) 100 | } 101 | 102 | const inputStyle = { 103 | border: "1px solid #ccc", 104 | padding: "8px", 105 | borderRadius: "4px", 106 | } 107 | 108 | const onChangeHeaderName = (e) => { 109 | headersArray[e.target.id].name = e.target.value 110 | setHeadersArray([...headersArray]) 111 | } 112 | 113 | const onChangeHeaderValue = (e) => { 114 | headersArray[e.target.id].value = e.target.value 115 | setHeadersArray([...headersArray]) 116 | } 117 | 118 | 119 | return ( 120 |
121 | { 122 | externalUserId && 123 |
124 | {externalAccounts ? 125 |
126 |

Accounts List

127 | 128 | 129 | 130 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | {externalAccounts.map((account) => ( 139 | 140 | 149 | 150 | 151 | 152 | 153 | 154 | ))} 155 | 156 |
131 | pd idappapp slugauth type
141 | handleRadioChange(account)} 147 | /> 148 | {account.id}{account.app.name}{account.app.name_slug}{account.app.auth_type}
157 |

158 | selected account: 159 | {apn} 160 |

161 | {testRequestConfig && false && 162 | } 174 | 175 | 176 | 177 |

Fetch Request Builder

178 |
179 | 180 | 192 | 193 | setUrl(e.target.value)} 198 | style={inputStyle} 199 | /> 200 |
201 | 202 |
203 | 204 | 205 | {headersArray.map((header, index) => ( 206 |
207 | Name 208 | 209 | Value 210 | 211 |
212 | ))} 213 |
214 | 215 | 216 | 217 | {method !== "GET" && ( 218 |
219 | 220 |