Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions connect-react-demo/app/actions/backendClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ export type FetchTokenOpts = {
externalUserId: string
}

export type ProxyRequestOpts = {
externalUserId: string
accountId: string
url: string
method: string
data?: any
}

const allowedOrigins = ([
process.env.VERCEL_URL,
process.env.VERCEL_BRANCH_URL,
Expand All @@ -32,3 +40,38 @@ const _fetchToken = async (opts: FetchTokenOpts) => {

// export const fetchToken = unstable_cache(_fetchToken, [], { revalidate: 3600 })
export const fetchToken = _fetchToken

const _proxyRequest = async (opts: ProxyRequestOpts) => {
const serverClient = backendClient()

try {
const proxyOptions = {
searchParams: {
external_user_id: opts.externalUserId,
account_id: opts.accountId
}
}

const targetRequest = {
url: opts.url,
options: {
method: opts.method as "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
...(opts.data && { body: JSON.stringify(opts.data) }),
...(opts.data && { headers: { "Content-Type": "application/json" } })
}
}

const resp = await serverClient.makeProxyRequest(proxyOptions, targetRequest);
return resp
} catch (error: any) {
// Re-throw with structured error info
throw {
message: error.message || 'Proxy request failed',
status: error.response?.status,
data: error.response?.data,
headers: error.response?.headers
}
}
}

export const proxyRequest = _proxyRequest
14 changes: 10 additions & 4 deletions connect-react-demo/app/components/ComponentTypeSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { cn } from "@/lib/utils"
import { IoCubeSharp, IoFlashOutline } from "react-icons/io5"
import { IoCubeSharp, IoFlashOutline, IoGlobe } from "react-icons/io5"
import { TOGGLE_STYLES } from "@/lib/constants/ui"

interface ComponentTypeSelectorProps {
selectedType: "action" | "trigger"
onTypeChange: (type: "action" | "trigger") => void
selectedType: "action" | "trigger" | "proxy"
onTypeChange: (type: "action" | "trigger" | "proxy") => void
}

const COMPONENT_TYPES = [
Expand All @@ -20,6 +20,12 @@ const COMPONENT_TYPES = [
icon: IoFlashOutline,
description: "React to events and webhooks"
},
{
value: "proxy",
label: "Proxy",
icon: IoGlobe,
description: "Make direct API requests through authenticated accounts"
},
] as const

export function ComponentTypeSelector({ selectedType, onTypeChange }: ComponentTypeSelectorProps) {
Expand All @@ -42,7 +48,7 @@ export function ComponentTypeSelector({ selectedType, onTypeChange }: ComponentT
<type.icon className="h-3 w-3" />
{type.label}
</button>
{index === 0 && <div className={TOGGLE_STYLES.separator} />}
{index < COMPONENT_TYPES.length - 1 && <div className={TOGGLE_STYLES.separator} />}
</div>
))}
</div>
Expand Down
168 changes: 102 additions & 66 deletions connect-react-demo/app/components/ConfigPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@ export const ConfigPanel = () => {
propNames,
webhookUrlValidationAttempted,
setWebhookUrlValidationAttempted,
editableExternalUserId,
setEditableExternalUserId,
accountId,
setAccountId,
} = useAppState()
const id1 = useId();
const id2 = useId();
Expand Down Expand Up @@ -305,7 +309,7 @@ export const ConfigPanel = () => {
<div className="pb-1.5 mb-1.5 border-b border-neutral-200 font-medium">
<span className="text-[#d73a49]">type</span>{" "}
<span className="text-[#6f42c1]">componentType</span> ={" "}
<span className="text-[#d73a49]">'action' | 'trigger'</span>
<span className="text-[#d73a49]">'action' | 'trigger' | 'proxy'</span>
</div>

<div className="font-sans text-neutral-600 py-1 text-[13px] leading-normal font-normal">
Expand Down Expand Up @@ -334,48 +338,52 @@ export const ConfigPanel = () => {
/>
</div>
</div>
<PropertyItem
name="app"
type="string"
description="App to connect to"
required={true}
>
<CustomizeProvider customization={dropdownCustomization}>
<SelectApp
value={selectedApp}
onChange={(app) => {
app
? setSelectedAppSlug(app.name_slug)
: removeSelectedAppSlug()
}}
/>
</CustomizeProvider>
</PropertyItem>
<PropertyItem
name={selectedComponentType === "action" ? "actionId" : "triggerId"}
type="string"
description={`${selectedComponentType === "action" ? "Action" : "Trigger"} to use`}
required={true}
>
<CustomizeProvider customization={dropdownCustomization}>
{selectedApp ? (
<SelectComponent
app={selectedApp}
componentType={selectedComponentType}
value={selectedComponent}
onChange={(comp) => {
comp
? setSelectedComponentKey(comp.key)
: removeSelectedComponentKey()
{(selectedComponentType === "action" || selectedComponentType === "trigger") && (
<PropertyItem
name="app"
type="string"
description="App to connect to"
required={true}
>
<CustomizeProvider customization={dropdownCustomization}>
<SelectApp
value={selectedApp}
onChange={(app) => {
app
? setSelectedAppSlug(app.name_slug)
: removeSelectedAppSlug()
}}
/>
) : (
<div className="w-full px-3 py-1.5 text-sm text-gray-500 border rounded bg-gray-50">
Loading components...
</div>
)}
</CustomizeProvider>
</PropertyItem>
</CustomizeProvider>
</PropertyItem>
)}
{selectedComponentType !== "proxy" && (
<PropertyItem
name={selectedComponentType === "action" ? "actionId" : "triggerId"}
type="string"
description={`${selectedComponentType === "action" ? "Action" : "Trigger"} to use`}
required={true}
>
<CustomizeProvider customization={dropdownCustomization}>
{selectedApp ? (
<SelectComponent
app={selectedApp}
componentType={selectedComponentType}
value={selectedComponent}
onChange={(comp) => {
comp
? setSelectedComponentKey(comp.key)
: removeSelectedComponentKey()
}}
/>
) : (
<div className="w-full px-3 py-1.5 text-sm text-gray-500 border rounded bg-gray-50">
Loading components...
</div>
)}
</CustomizeProvider>
</PropertyItem>
)}
{selectedComponentType === "trigger" && (
<PropertyItem
name="webhookUrl"
Expand Down Expand Up @@ -424,12 +432,36 @@ export const ConfigPanel = () => {
description="Authenticated user identifier"
required={true}
>
<input
value={externalUserId || ""}
className="w-full px-3 py-1.5 text-sm font-mono border rounded bg-zinc-50/50"
readOnly
/>
{selectedComponentType === "proxy" ? (
<input
value={editableExternalUserId || ""}
onChange={(e) => setEditableExternalUserId(e.target.value)}
placeholder="Enter external user ID"
className="w-full px-3 py-1.5 text-sm font-mono border rounded bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500"
/>
) : (
<input
value={externalUserId || ""}
className="w-full px-3 py-1.5 text-sm font-mono border rounded bg-zinc-50/50"
readOnly
/>
)}
</PropertyItem>
{selectedComponentType === "proxy" && (
<PropertyItem
name="accountId"
type="string"
description="Account ID for authenticated requests"
required={true}
>
<input
value={accountId || ""}
onChange={(e) => setAccountId(e.target.value)}
placeholder="Enter account ID"
className="w-full px-3 py-1.5 text-sm font-mono border rounded bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500"
/>
</PropertyItem>
)}
</div>
)

Expand Down Expand Up @@ -566,29 +598,33 @@ export const ConfigPanel = () => {
{basicFormControls}

{/* Desktop: Show with section header */}
<div className="hidden md:block mt-6">
<div className="border-t border-gray-200 pt-6">
<h3 className="text-sm font-medium text-gray-700 mb-4">Additional Config Options</h3>
{advancedFormControls}
{selectedComponentType !== "proxy" && (
<div className="hidden md:block mt-6">
<div className="border-t border-gray-200 pt-6">
<h3 className="text-sm font-medium text-gray-700 mb-4">Additional Config Options</h3>
{advancedFormControls}
</div>
</div>
</div>
)}

{/* Mobile: Collapsible */}
<div className="md:hidden mt-4">
<Collapsible open={showAdvanced} onOpenChange={setShowAdvanced}>
<CollapsibleTrigger className="flex items-center justify-between w-full py-2 px-3 text-sm font-medium text-neutral-500 hover:text-neutral-600 hover:bg-neutral-25 rounded border border-neutral-150">
<div className="flex items-center gap-2">
<IoSettingsOutline className="h-4 w-4" />
More options
</div>
<IoChevronDown className={cn("h-4 w-4 transition-transform", showAdvanced && "rotate-180")} />
</CollapsibleTrigger>

<CollapsibleContent>
{advancedFormControls}
</CollapsibleContent>
</Collapsible>
</div>
{selectedComponentType !== "proxy" && (
<div className="md:hidden mt-4">
<Collapsible open={showAdvanced} onOpenChange={setShowAdvanced}>
<CollapsibleTrigger className="flex items-center justify-between w-full py-2 px-3 text-sm font-medium text-neutral-500 hover:text-neutral-600 hover:bg-neutral-25 rounded border border-neutral-150">
<div className="flex items-center gap-2">
<IoSettingsOutline className="h-4 w-4" />
More options
</div>
<IoChevronDown className={cn("h-4 w-4 transition-transform", showAdvanced && "rotate-180")} />
</CollapsibleTrigger>

<CollapsibleContent>
{advancedFormControls}
</CollapsibleContent>
</Collapsible>
</div>
)}

{triggerInfo}
</div>
Expand Down
39 changes: 22 additions & 17 deletions connect-react-demo/app/components/DemoPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { ConfigurableProps } from "@pipedream/sdk"
import { useAppState } from "@/lib/app-state"
import { PageSkeleton } from "./PageSkeleton"
import { TerminalCollapsible } from "./TerminalCollapsible"
import { ProxyRequestBuilder } from "./ProxyRequestBuilder"

export const DemoPanel = () => {
const frontendClient = useFrontendClient()
Expand Down Expand Up @@ -176,23 +177,27 @@ export const DemoPanel = () => {
>
<PageSkeleton customizationOption={customizationOption}>
<div className="p-4 sm:p-6 space-y-4">
<CustomizeProvider {...customizationOption.customization}>
{selectedComponentKey && (
<ComponentFormContainer
externalUserId={externalUserId}
componentKey={selectedComponentKey}
configuredProps={configuredProps}
onUpdateConfiguredProps={setConfiguredProps}
hideOptionalProps={hideOptionalProps}
propNames={debouncedPropNames}
enableDebugging={enableDebugging}
onSubmit={handleSubmit}
onUpdateDynamicProps={handleDynamicProps}
sdkResponse={sdkErrors}
// oauthAppConfig={oauthAppConfig}
/>
)}
</CustomizeProvider>
{selectedComponentType === "proxy" ? (
<ProxyRequestBuilder />
) : (
<CustomizeProvider {...customizationOption.customization}>
{selectedComponentKey && (
<ComponentFormContainer
externalUserId={externalUserId}
componentKey={selectedComponentKey}
configuredProps={configuredProps}
onUpdateConfiguredProps={setConfiguredProps}
hideOptionalProps={hideOptionalProps}
propNames={debouncedPropNames}
enableDebugging={enableDebugging}
onSubmit={handleSubmit}
onUpdateDynamicProps={handleDynamicProps}
sdkResponse={sdkErrors}
// oauthAppConfig={oauthAppConfig}
/>
)}
</CustomizeProvider>
)}
</div>
</PageSkeleton>
</div>
Expand Down
Loading