Skip to content

Commit a29003f

Browse files
committed
Merge only changes that are required for sendgrid
1 parent 862c278 commit a29003f

File tree

23 files changed

+1111
-216
lines changed

23 files changed

+1111
-216
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,12 @@ Visit [http://localhost:3000](http://localhost:3000) to get started.
7979
### Action Nodes
8080

8181
<!-- PLUGINS:START - Do not remove. Auto-generated by discover-plugins -->
82-
- **Clerk**: Get User, Create User, Update User, Delete User
82+
- **Discord**: Send Discord Message
8383
- **Resend**: Send Email
84+
- **SendGrid**: Send Email
8485
- **Slack**: Send Slack Message
8586
- **v0**: Create Chat, Send Message
86-
- **Web3**: Transfer Funds, Read Contract, Write Contract
87-
- **Webflow**: List Sites, Get Site, Publish Site
87+
- **Web3**: Check Balance, Transfer Funds, Read Contract, Write Contract
8888
<!-- PLUGINS:END -->
8989

9090
## Code Generation

app/api/integrations/[integrationId]/test/route.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,11 @@ export async function POST(
4747
}
4848

4949
if (integration.type === "database") {
50-
const result = await testDatabaseConnection(integration.config.url);
50+
const url =
51+
typeof integration.config.url === "string"
52+
? integration.config.url
53+
: undefined;
54+
const result = await testDatabaseConnection(url);
5155
return NextResponse.json(result);
5256
}
5357

app/globals.css

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -211,20 +211,14 @@
211211
z-index: 20 !important;
212212
}
213213

214-
/* Invisible hit area for desktop */
215-
.react-flow__handle::after {
216-
content: "" !important;
217-
position: absolute !important;
218-
top: 50% !important;
219-
left: 50% !important;
220-
transform: translate(-50%, -50%) !important;
221-
width: 24px !important;
222-
height: 24px !important;
223-
}
224-
225-
/* Invisible hit area for mobile with WCAG minimum target size */
214+
/* Increase hit area on mobile without changing visual size */
226215
@media (max-width: 768px) {
227216
.react-flow__handle::after {
217+
content: "" !important;
218+
position: absolute !important;
219+
top: 50% !important;
220+
left: 50% !important;
221+
transform: translate(-50%, -50%) !important;
228222
width: 44px !important;
229223
height: 44px !important;
230224
}

components/settings/integration-form-dialog.tsx

Lines changed: 168 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
"use client";
22

3-
import { useEffect, useState } from "react";
3+
import { useCallback, useEffect, useState } from "react";
44
import { toast } from "sonner";
55
import { Button } from "@/components/ui/button";
6+
import { Checkbox } from "@/components/ui/checkbox";
67
import {
78
Dialog,
89
DialogContent,
@@ -22,13 +23,21 @@ import {
2223
SelectValue,
2324
} from "@/components/ui/select";
2425
import { Spinner } from "@/components/ui/spinner";
25-
import { api, type Integration } from "@/lib/api-client";
26-
import type { IntegrationType } from "@/lib/types/integration";
26+
import {
27+
api,
28+
type Integration,
29+
type IntegrationWithConfig,
30+
} from "@/lib/api-client";
31+
import type {
32+
IntegrationConfig,
33+
IntegrationType,
34+
} from "@/lib/types/integration";
2735
import {
2836
getIntegration,
2937
getIntegrationLabels,
3038
getSortedIntegrationTypes,
3139
} from "@/plugins";
40+
import { SendGridIntegrationSection } from "./sendgrid-integration-section";
3241
import { Web3WalletSection } from "./web3-wallet-section";
3342

3443
type IntegrationFormDialogProps = {
@@ -43,7 +52,7 @@ type IntegrationFormDialogProps = {
4352
type IntegrationFormData = {
4453
name: string;
4554
type: IntegrationType;
46-
config: Record<string, string>;
55+
config: Record<string, string | boolean>;
4756
};
4857

4958
// System integrations that don't have plugins
@@ -77,21 +86,70 @@ export function IntegrationFormDialog({
7786
config: {},
7887
});
7988

89+
const initializeConfigFromPlugin = useCallback(
90+
(pluginType: IntegrationType): Record<string, string | boolean> => {
91+
const plugin = getIntegration(pluginType);
92+
const config: Record<string, string | boolean> = {};
93+
if (plugin?.formFields) {
94+
for (const field of plugin.formFields) {
95+
if (field.defaultValue !== undefined) {
96+
config[field.configKey] = field.defaultValue as string | boolean;
97+
}
98+
}
99+
}
100+
return config;
101+
},
102+
[]
103+
);
104+
105+
const initializeConfigFromIntegration = useCallback(
106+
(
107+
integrationData: Integration | IntegrationWithConfig
108+
): Record<string, string | boolean> => {
109+
const plugin = getIntegration(integrationData.type);
110+
const config: Record<string, string | boolean> = {};
111+
112+
if (plugin?.formFields && "config" in integrationData) {
113+
const integrationConfig = integrationData.config as IntegrationConfig;
114+
for (const field of plugin.formFields) {
115+
if (integrationConfig[field.configKey] !== undefined) {
116+
config[field.configKey] = integrationConfig[field.configKey] as
117+
| string
118+
| boolean;
119+
} else if (field.defaultValue !== undefined) {
120+
config[field.configKey] = field.defaultValue as string | boolean;
121+
}
122+
}
123+
}
124+
125+
return config;
126+
},
127+
[]
128+
);
129+
80130
useEffect(() => {
81131
if (integration) {
132+
const initialConfig = initializeConfigFromIntegration(integration);
82133
setFormData({
83134
name: integration.name,
84135
type: integration.type,
85-
config: {},
136+
config: initialConfig,
86137
});
87138
} else {
139+
const pluginType = preselectedType || "resend";
140+
const initialConfig = initializeConfigFromPlugin(pluginType);
88141
setFormData({
89142
name: "",
90-
type: preselectedType || "resend",
91-
config: {},
143+
type: pluginType,
144+
config: initialConfig,
92145
});
93146
}
94-
}, [integration, preselectedType]);
147+
}, [
148+
integration,
149+
preselectedType,
150+
initializeConfigFromIntegration,
151+
initializeConfigFromPlugin,
152+
]);
95153

96154
const handleSave = async () => {
97155
try {
@@ -126,13 +184,96 @@ export function IntegrationFormDialog({
126184
}
127185
};
128186

129-
const updateConfig = (key: string, value: string) => {
187+
const updateConfig = (key: string, value: string | boolean) => {
130188
setFormData({
131189
...formData,
132190
config: { ...formData.config, [key]: value },
133191
});
134192
};
135193

194+
const renderHelpText = (
195+
helpText?: string,
196+
helpLink?: { text: string; url: string }
197+
) => {
198+
if (!(helpText || helpLink)) {
199+
return null;
200+
}
201+
return (
202+
<p className="text-muted-foreground text-xs">
203+
{helpText}
204+
{helpLink && (
205+
<a
206+
className="underline hover:text-foreground"
207+
href={helpLink.url}
208+
rel="noopener noreferrer"
209+
target="_blank"
210+
>
211+
{helpLink.text}
212+
</a>
213+
)}
214+
</p>
215+
);
216+
};
217+
218+
const renderCheckboxField = (field: {
219+
id: string;
220+
type: string;
221+
label: string;
222+
configKey: string;
223+
defaultValue?: string | boolean;
224+
helpText?: string;
225+
helpLink?: { text: string; url: string };
226+
}) => {
227+
let checkboxValue: string | boolean | undefined =
228+
formData.config[field.configKey];
229+
if (checkboxValue === undefined) {
230+
checkboxValue =
231+
field.defaultValue !== undefined ? field.defaultValue : true;
232+
}
233+
const isChecked =
234+
typeof checkboxValue === "boolean"
235+
? checkboxValue
236+
: checkboxValue === "true";
237+
238+
return (
239+
<div className="flex items-center space-x-2" key={field.id}>
240+
<Checkbox
241+
checked={isChecked}
242+
id={field.id}
243+
onCheckedChange={(checked) =>
244+
updateConfig(field.configKey, checked === true)
245+
}
246+
/>
247+
<Label className="cursor-pointer font-normal" htmlFor={field.id}>
248+
{field.label}
249+
</Label>
250+
{renderHelpText(field.helpText, field.helpLink)}
251+
</div>
252+
);
253+
};
254+
255+
const renderInputField = (field: {
256+
id: string;
257+
type: string;
258+
label: string;
259+
configKey: string;
260+
placeholder?: string;
261+
helpText?: string;
262+
helpLink?: { text: string; url: string };
263+
}) => (
264+
<div className="space-y-2" key={field.id}>
265+
<Label htmlFor={field.id}>{field.label}</Label>
266+
<Input
267+
id={field.id}
268+
onChange={(e) => updateConfig(field.configKey, e.target.value)}
269+
placeholder={field.placeholder}
270+
type={field.type}
271+
value={(formData.config[field.configKey] as string) || ""}
272+
/>
273+
{renderHelpText(field.helpText, field.helpLink)}
274+
</div>
275+
);
276+
136277
const renderConfigFields = () => {
137278
// Handle system integrations with hardcoded fields
138279
if (formData.type === "database") {
@@ -144,7 +285,7 @@ export function IntegrationFormDialog({
144285
onChange={(e) => updateConfig("url", e.target.value)}
145286
placeholder="postgresql://..."
146287
type="password"
147-
value={formData.config.url || ""}
288+
value={(formData.config.url as string) || ""}
148289
/>
149290
<p className="text-muted-foreground text-xs">
150291
Connection string in the format:
@@ -165,33 +306,24 @@ export function IntegrationFormDialog({
165306
return null;
166307
}
167308

168-
return plugin.formFields.map((field) => (
169-
<div className="space-y-2" key={field.id}>
170-
<Label htmlFor={field.id}>{field.label}</Label>
171-
<Input
172-
id={field.id}
173-
onChange={(e) => updateConfig(field.configKey, e.target.value)}
174-
placeholder={field.placeholder}
175-
type={field.type}
176-
value={formData.config[field.configKey] || ""}
309+
// Handle SendGrid integration with special checkbox logic
310+
if (formData.type === "sendgrid") {
311+
return (
312+
<SendGridIntegrationSection
313+
config={formData.config}
314+
formFields={plugin.formFields}
315+
updateConfig={updateConfig}
177316
/>
178-
{(field.helpText || field.helpLink) && (
179-
<p className="text-muted-foreground text-xs">
180-
{field.helpText}
181-
{field.helpLink && (
182-
<a
183-
className="underline hover:text-foreground"
184-
href={field.helpLink.url}
185-
rel="noopener noreferrer"
186-
target="_blank"
187-
>
188-
{field.helpLink.text}
189-
</a>
190-
)}
191-
</p>
192-
)}
193-
</div>
194-
));
317+
);
318+
}
319+
320+
// Default rendering for other integrations
321+
return plugin.formFields.map((field) => {
322+
if (field.type === "checkbox") {
323+
return renderCheckboxField(field);
324+
}
325+
return renderInputField(field);
326+
});
195327
};
196328

197329
return (

0 commit comments

Comments
 (0)