Skip to content

Commit 200f166

Browse files
committed
Add nextjs example to manual node creation
1 parent 95aad32 commit 200f166

2 files changed

Lines changed: 130 additions & 8 deletions

File tree

src/components/shop/create-node/StepServerConfig.tsx

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client"
22

3-
import { useState } from "react"
3+
import {useEffect, useState} from "react"
44
import { Button } from "@/components/ui/button"
55
import { Input } from "@/components/ui/input"
66
import { Label } from "@/components/ui/label"
@@ -10,7 +10,12 @@ import { Download, Server, Globe } from "lucide-react"
1010
import type { NodeDefinition } from "@/model/NodeDefinition"
1111
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
1212
import {useTranslations} from "next-intl";
13-
import {generateNodeJSFiles, generatePythonFiles, ProjectFile} from "@/utils/shop/project-generators";
13+
import {
14+
generateNextJsFiles,
15+
generateNodeJSFiles,
16+
generatePythonFiles,
17+
ProjectFile
18+
} from "@/utils/shop/project-generators";
1419
import {generateAndDownloadZip} from "@/utils/shop/zipUtils";
1520
import {FileTree} from "@/components/shop/create-node/FileTree";
1621
import {CodeEditor} from "@/components/shop/create-node/CodeEditor";
@@ -32,11 +37,12 @@ interface FileTreeItem {
3237
export function StepServerConfig({ nodeDefinition, updateNodeDefinition, onPrevious, onSave }: StepServerConfigProps) {
3338
const t = useTranslations("shop.step-server-config")
3439
const [copied, setCopied] = useState<string | null>(null)
35-
const [selectedLanguage, setSelectedLanguage] = useState("nodejs")
40+
const [selectedLanguage, setSelectedLanguage] = useState(nodeDefinition.executionMode === "Automatic" ? "nodejs" : "nextjs")
3641
const [expandedFolders, setExpandedFolders] = useState<Record<string, boolean>>({
3742
src: true,
43+
app: true
3844
})
39-
const [selectedFile, setSelectedFile] = useState<string>("src/server.js")
45+
const [selectedFile, setSelectedFile] = useState<string>(nodeDefinition.executionMode === "Automatic" ? "src/server.js" : "app/ExampleActivity.tsx")
4046
const [isDownloading, setIsDownloading] = useState(false)
4147

4248
const updateField = (field: keyof NodeDefinition, value: string) => {
@@ -64,8 +70,10 @@ export function StepServerConfig({ nodeDefinition, updateNodeDefinition, onPrevi
6470
case "python":
6571
return generatePythonFiles(nodeDefinition)
6672
case "nodejs":
67-
default:
6873
return generateNodeJSFiles(nodeDefinition)
74+
case "nextjs":
75+
default:
76+
return generateNextJsFiles(nodeDefinition)
6977
}
7078
}
7179

@@ -76,8 +84,10 @@ export function StepServerConfig({ nodeDefinition, updateNodeDefinition, onPrevi
7684
// Set default selected file based on language
7785
if (language === "python") {
7886
setSelectedFile("app.py")
79-
} else {
87+
} else if (language === "nodejs") {
8088
setSelectedFile("src/server.js")
89+
} else if (language === "nextjs") {
90+
setSelectedFile("app/ExampleActivity.tsx")
8191
}
8292
}
8393

@@ -170,6 +180,22 @@ export function StepServerConfig({ nodeDefinition, updateNodeDefinition, onPrevi
170180
}
171181

172182
const fileTree = buildFileTree(projectFiles)
183+
184+
useEffect(() => {
185+
const path = nodeDefinition.executionUrl ? nodeDefinition.executionUrl?.split(".")[1]?.split("/").slice(1).join("/") + "/" : ""
186+
187+
if (path && path !== "/") {
188+
setExpandedFolders(prev => ({
189+
...prev,
190+
[`app/${path}`.slice(0, -1)]: true,
191+
}))
192+
if (selectedLanguage === "nextjs" && selectedFile.endsWith("ExampleActivity.tsx")) {
193+
setSelectedFile(`app/${path}ExampleActivity.tsx`)
194+
}
195+
} else if (selectedLanguage === "nextjs" && selectedFile.endsWith("ExampleActivity.tsx")) {
196+
setSelectedFile("app/ExampleActivity.tsx")
197+
}
198+
}, [nodeDefinition.executionUrl, selectedFile, selectedLanguage])
173199

174200
return (
175201
<div className="space-y-8">
@@ -230,8 +256,9 @@ export function StepServerConfig({ nodeDefinition, updateNodeDefinition, onPrevi
230256
<Tabs defaultValue="nodejs" value={selectedLanguage} onValueChange={handleLanguageChange} className="p-4">
231257
<div className="flex flex-row justify-between">
232258
<TabsList className="mb-4 grid grid-cols-2 w-[200px]">
233-
<TabsTrigger value="nodejs">Node.js</TabsTrigger>
234-
<TabsTrigger value="python">Python</TabsTrigger>
259+
{ nodeDefinition.executionMode === "Automatic" && <TabsTrigger value="nodejs">Node.js</TabsTrigger> }
260+
{ nodeDefinition.executionMode === "Automatic" && <TabsTrigger value="python">Python</TabsTrigger> }
261+
{ nodeDefinition.executionMode === "Manual" && <TabsTrigger value="nextjs">Next.js</TabsTrigger> }
235262
</TabsList>
236263
<Button variant="outline" size="sm" onClick={downloadProject} disabled={isDownloading}>
237264
<Download className="h-4 w-4 mr-2" />
@@ -306,6 +333,40 @@ export function StepServerConfig({ nodeDefinition, updateNodeDefinition, onPrevi
306333
</div>
307334
</div>
308335
</TabsContent>
336+
337+
<TabsContent value="nextjs" className="mt-0 space-y-4">
338+
<div className="border rounded-md overflow-hidden">
339+
<div className="grid grid-cols-12 h-[550px]">
340+
{/* File tree */}
341+
<div className="col-span-3 border-r bg-muted/30">
342+
<div className="p-3 border-b bg-muted/50 font-medium text-sm">{t("templates.project-files")}</div>
343+
<FileTree
344+
items={fileTree}
345+
expandedFolders={expandedFolders}
346+
selectedFile={selectedFile}
347+
onToggleFolder={toggleFolder}
348+
onSelectFile={setSelectedFile}
349+
/>
350+
</div>
351+
352+
{/* Code editor */}
353+
<div className="col-span-9">
354+
<div className="p-3 border-b bg-muted/50 font-medium text-sm flex justify-between items-center">
355+
<span>{selectedFile}</span>
356+
</div>
357+
<div className="h-[500px] overflow-y-auto">
358+
<CodeEditor
359+
code={getSelectedFileContent()}
360+
language={getSelectedFileLanguage()}
361+
fileName={selectedFile}
362+
onCopy={copyToClipboard}
363+
copied={copied}
364+
/>
365+
</div>
366+
</div>
367+
</div>
368+
</div>
369+
</TabsContent>
309370
</Tabs>
310371
</CardContent>
311372
</Card>

src/utils/shop/project-generators.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,3 +387,64 @@ docker run -p 5000:5000 ${nodeDefinition.name.toLowerCase() || "flask-service"}
387387
},
388388
]
389389
}
390+
391+
export function generateNextJsFiles(nodeDefinition: NodeDefinition): ProjectFile[] {
392+
const variables = getVariableNames(nodeDefinition)
393+
const nodeName = nodeDefinition.name || "ExampleActivity"
394+
395+
const varHtmlExamples = variables
396+
.filter(v => !v.isOutput)
397+
.map(v => `<p>Received value for ${v.name}: {searchParams.${v.name}}</p>`)
398+
.join("\n")
399+
400+
// Only include output variables in the response
401+
const outputVars = variables.filter((v) => v.isOutput)
402+
const varResponses = outputVars.map((v) => ` [${v.name}]: "Example value for ${v.name}",`).join("\n")
403+
404+
const path = nodeDefinition.executionUrl ? nodeDefinition.executionUrl?.split(".")[1]?.split("/").slice(1).join("/") : ""
405+
406+
return [
407+
{
408+
path: `app/${path ? path + "/": ""}ExampleActivity.tsx`,
409+
language: "typescript",
410+
content: `export interface ActivitySearchParams {
411+
${variables.map((v) => `${v.name}: string`).join("\n ")}
412+
responsePath: string
413+
flowElementInstanceId: string
414+
userId: string
415+
}
416+
417+
export default function ${nodeName}({ searchParams }: { searchParams: ActivitySearchParams }) {
418+
function onSubmit(data: any) {
419+
fetch(searchParams.responsePath, {
420+
method: "POST",
421+
headers: {
422+
"Content-Type": "application/json"
423+
},
424+
body: JSON.stringify({
425+
flowElementInstanceId: searchParams.flowElementInstanceId,
426+
data: {
427+
// TODO Replace this with actual data that should be the output of this activity
428+
${varResponses}
429+
},
430+
completedBy: searchParams.userId
431+
})
432+
}).then(() => {
433+
console.log("submitted")
434+
}).catch((error) => {
435+
console.error("error while submitting")
436+
})
437+
}
438+
439+
// TODO This is just an example component which displays all received values
440+
return <div className="w-full h-full">
441+
<h1>Activity Title</h1>
442+
${varHtmlExamples}
443+
<form onSubmit={onSubmit} className="space-y-6">
444+
<button type="submit">Submit</button>
445+
</form>
446+
</div>
447+
}`
448+
}
449+
]
450+
}

0 commit comments

Comments
 (0)