Skip to content

Commit 257d716

Browse files
Adding interactive Connect functionality to Connect docs (#16414)
* Fix hydration error in CodeBlock and fix ESLint issues in ConnectSDKDemo * Fixing some linting issues * Update pnpm-lock.yaml * Integrate Connect demo components into the getting started sections * Clean up Connect demo layout in quickstart guide * Split Connect demo into separate step components * Create global Connect context to properly separate demo steps * Consolidate demo components for better maintainability * Centralize code snippets in dedicated file to reduce duplication * Remove redundant Connect demo components - Consolidated all functionality into GlobalConnectProvider, TokenGenerationDemo, and AccountConnectionDemo - Removed multiple smaller components that had overlapping code - Simplified the component hierarchy - Reduced code duplication - Maintained the same functionality in the quickstart docs * Working well * connect link handling, more ui polish * Showing account info on successful connetion * Restricted allowed origins for API calls * Cleaning up code * Adding .env.example * Update pnpm-lock.yaml * Linting * Allowing Vercel preview URLs * Code cleanup * Linting * Update ConnectLinkDemo.jsx * Removing references to nextjs example app * Update GlobalConnectProvider.jsx * Dark mode styling * Update ConnectLinkDemo.jsx * Improve UUID generation Replace custom UUI generation w/ crypto.randomUUID()
1 parent 902ac05 commit 257d716

21 files changed

+1376
-69
lines changed

docs-v2/.env.example

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Connect demo API configuration
2+
PIPEDREAM_CLIENT_ID=your_client_id
3+
PIPEDREAM_CLIENT_SECRET=your_client_secret
4+
PIPEDREAM_PROJECT_ID=your_project_id
5+
PIPEDREAM_PROJECT_ENVIRONMENT=development
6+
7+
# Additional redirect URIs for the Connect demo (optional)
8+
PIPEDREAM_CONNECT_TOKEN_WEBHOOK_URI=
9+
PIPEDREAM_CONNECT_SUCCESS_REDIRECT_URI=
10+
PIPEDREAM_CONNECT_ERROR_REDIRECT_URI=
11+
12+
# Comma-separated list of additional allowed origins (optional)
13+
# ALLOWED_ORIGINS=https://your-custom-domain.com,https://another-domain.com
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
"use client";
2+
3+
import { useGlobalConnect } from "./GlobalConnectProvider";
4+
import CodeBlock from "./CodeBlock";
5+
import { styles } from "../utils/componentStyles";
6+
7+
export default function AccountConnectionDemo() {
8+
const {
9+
appSlug,
10+
setAppSlug,
11+
tokenData,
12+
getClientCodeSnippet,
13+
connectAccount,
14+
connectedAccount,
15+
error,
16+
} = useGlobalConnect();
17+
18+
return (
19+
<div className={styles.container}>
20+
<div className={styles.header}>
21+
Connect an account from your frontend
22+
</div>
23+
<div className="p-4">
24+
<div className="mb-4">
25+
<label className="flex items-center mb-4">
26+
<span className={styles.label}>App to connect:</span>
27+
<select
28+
value={appSlug}
29+
onChange={(e) => setAppSlug(e.target.value)}
30+
className={styles.select}
31+
>
32+
<option value="google_sheets">Google Sheets</option>
33+
<option value="github">GitHub</option>
34+
<option value="notion">Notion</option>
35+
<option value="gmail">Gmail</option>
36+
<option value="openai">OpenAI</option>
37+
</select>
38+
</label>
39+
40+
<div className="mb-4">
41+
<div className="border border-blue-100 rounded-lg overflow-hidden">
42+
<CodeBlock code={getClientCodeSnippet()} language="javascript" />
43+
</div>
44+
</div>
45+
</div>
46+
47+
<div className="mt-4 mb-2">
48+
<button
49+
onClick={connectAccount}
50+
disabled={!tokenData}
51+
className={styles.primaryButton}
52+
>
53+
Connect Account
54+
</button>
55+
{!tokenData && <p className={`mt-2 ${styles.text.muted}`}><a href="/docs/connect/managed-auth/quickstart/#generate-a-short-lived-token" className="font-semibold underline underline-offset-4 hover:decoration-2 decoration-brand/50">Generate a token above</a> in order to test the account connection flow</p>}
56+
</div>
57+
58+
{error && (
59+
<div className={styles.statusBox.error}>
60+
<div className="font-medium text-sm">Error</div>
61+
<div className="mt-1 text-sm">{error}</div>
62+
</div>
63+
)}
64+
65+
{connectedAccount && (
66+
<div className={styles.statusBox.success}>
67+
<div className="font-medium text-sm">Successfully connected your {appSlug} account!</div>
68+
<div className="mt-4 text-sm">
69+
{connectedAccount.loading
70+
? (
71+
<div>Loading account details...</div>
72+
)
73+
: (
74+
<>
75+
{connectedAccount.name
76+
? (
77+
<div>Account info: <span className="font-medium">{connectedAccount.name}</span></div>
78+
)
79+
: null}
80+
<div>Account ID: <span className="font-medium">{connectedAccount.id}</span></div>
81+
</>
82+
)}
83+
</div>
84+
</div>
85+
)}
86+
</div>
87+
</div>
88+
);
89+
}

docs-v2/components/CodeBlock.js

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
"use client";
2+
3+
import {
4+
useState, useEffect,
5+
} from "react";
6+
// We don't need the default Prism CSS as we're using our custom CSS
7+
// import "prismjs/themes/prism.css";
8+
9+
// We'll dynamically import Prism on the client side only
10+
let Prism;
11+
12+
export default function CodeBlock({
13+
code, language = "javascript", className = "",
14+
}) {
15+
const [
16+
copied,
17+
setCopied,
18+
] = useState(false);
19+
const [
20+
highlightedCode,
21+
setHighlightedCode,
22+
] = useState(code);
23+
const [
24+
isClient,
25+
setIsClient,
26+
] = useState(false);
27+
28+
// Load Prism and highlight code on client-side only
29+
useEffect(() => {
30+
setIsClient(true);
31+
32+
const loadPrism = async () => {
33+
Prism = (await import("prismjs")).default;
34+
35+
// Use manual mode so we can control highlighting
36+
Prism.manual = true;
37+
38+
// Import language definitions dynamically
39+
if (!Prism.languages.javascript) {
40+
await import("prismjs/components/prism-javascript");
41+
}
42+
43+
if (!Prism.languages.json && language === "json") {
44+
await import("prismjs/components/prism-json");
45+
}
46+
47+
// Apply syntax highlighting
48+
try {
49+
if (Prism.languages[language]) {
50+
const highlighted = Prism.highlight(code, Prism.languages[language], language);
51+
setHighlightedCode(highlighted);
52+
}
53+
} catch (error) {
54+
console.error("Prism highlighting error:", error);
55+
}
56+
};
57+
58+
loadPrism();
59+
}, [
60+
code,
61+
language,
62+
]);
63+
64+
const copyToClipboard = () => {
65+
navigator.clipboard.writeText(code);
66+
setCopied(true);
67+
setTimeout(() => setCopied(false), 2000);
68+
};
69+
70+
return (
71+
<div className={`relative group ${className}`}>
72+
<pre className="overflow-x-auto rounded-lg font-medium border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 p-4 text-[13px] leading-relaxed mb-0">
73+
<div className="absolute top-2 right-2 z-10">
74+
<button
75+
onClick={copyToClipboard}
76+
className="opacity-0 group-hover:opacity-100 transition-opacity h-8 w-8 rounded-md bg-gray-100 dark:bg-gray-800 flex items-center justify-center text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 hover:text-gray-700 dark:hover:text-gray-300 focus:opacity-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
77+
aria-label={copied
78+
? "Copied"
79+
: "Copy code"}
80+
>
81+
{copied
82+
? (
83+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
84+
<path d="M5 13l4 4L19 7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
85+
</svg>
86+
)
87+
: (
88+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
89+
<path d="M8 16c0 1.886 0 2.828.586 3.414C9.172 20 10.114 20 12 20h4c1.886 0 2.828 0 3.414-.586C20 18.828 20 17.886 20 16v-4c0-1.886 0-2.828-.586-3.414C18.828 8 17.886 8 16 8m-8 8h4c1.886 0 2.828 0 3.414-.586C16 14.828 16 13.886 16 12V8m-8 8c-1.886 0-2.828 0-3.414-.586C4 14.828 4 13.886 4 12V8c0-1.886 0-2.828.586-3.414C5.172 4 6.114 4 8 4h4c1.886 0 2.828 0 3.414.586C16 5.172 16 6.114 16 8" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
90+
</svg>
91+
)}
92+
</button>
93+
</div>
94+
{isClient
95+
? (
96+
<code
97+
className={`language-${language} text-gray-800 dark:text-gray-200 [text-shadow:none]`}
98+
dangerouslySetInnerHTML={{
99+
__html: highlightedCode,
100+
}}
101+
/>
102+
)
103+
: (
104+
<code className={`language-${language} text-gray-800 dark:text-gray-200 [text-shadow:none]`}>{code}</code>
105+
)}
106+
</pre>
107+
</div>
108+
);
109+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* This file contains all the code snippets used in the Connect SDK demo.
3+
* Centralizing them here helps to maintain consistency and makes updates easier.
4+
*/
5+
6+
/**
7+
* Server-side code for generating a Connect token
8+
* @param {string} externalUserId - The user's external ID
9+
* @returns {string} The server code snippet
10+
*/
11+
export function getServerCodeSnippet(externalUserId) {
12+
return `import { createBackendClient } from "@pipedream/sdk/server";
13+
14+
// This code runs on your server
15+
const pd = createBackendClient({
16+
environment: "development",
17+
credentials: {
18+
clientId: process.env.PIPEDREAM_CLIENT_ID,
19+
clientSecret: process.env.PIPEDREAM_CLIENT_SECRET,
20+
},
21+
projectId: process.env.PIPEDREAM_PROJECT_ID
22+
});
23+
24+
// Create a token for a specific user
25+
const { token, expires_at, connect_link_url } = await pd.createConnectToken({
26+
external_user_id: "${externalUserId || "YOUR_USER_ID"}",
27+
});`;
28+
}
29+
30+
/**
31+
* Client-side code for connecting an account
32+
* @param {string} appSlug - The app to connect to (slack, github, etc)
33+
* @param {object} tokenData - The token data from the server
34+
* @returns {string} The client code snippet
35+
*/
36+
export function getClientCodeSnippet(appSlug, tokenData) {
37+
return `import { createFrontendClient } from "@pipedream/sdk/browser"
38+
39+
// This code runs in the frontend using the token from your server
40+
export default function Home() {
41+
function connectAccount() {
42+
const pd = createFrontendClient()
43+
pd.connectAccount({
44+
app: "${appSlug}",
45+
token: "${tokenData?.token
46+
? tokenData.token
47+
: "{connect_token}"}",
48+
onSuccess: (account) => {
49+
// Handle successful connection
50+
console.log(\`Account successfully connected: \${account.id}\`)
51+
},
52+
onError: (err) => {
53+
// Handle connection error
54+
console.error(\`Connection error: \${err.message}\`)
55+
}
56+
})
57+
}
58+
59+
return (
60+
<main>
61+
<button onClick={connectAccount}>Connect Account</button>
62+
</main>
63+
)
64+
}`;
65+
}

0 commit comments

Comments
 (0)