Skip to content
Merged
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
85 changes: 79 additions & 6 deletions components/workflow/workflow-toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Check,
ChevronDown,
Download,
FlaskConical,
Loader2,
Play,
Plus,
Expand All @@ -20,6 +21,7 @@ import { nanoid } from "nanoid";
import { useRouter } from "next/navigation";
import { useEffect, useRef, useState } from "react";
import { toast } from "sonner";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import {
AlertDialog,
AlertDialogAction,
Expand Down Expand Up @@ -450,6 +452,7 @@ function useWorkflowState() {

const [isDownloading, setIsDownloading] = useState(false);
const [showCodeDialog, setShowCodeDialog] = useState(false);
const [showExportDialog, setShowExportDialog] = useState(false);
const [generatedCode, _setGeneratedCode] = useState<string>("");
const [allWorkflows, setAllWorkflows] = useState<
Array<{
Expand Down Expand Up @@ -509,6 +512,8 @@ function useWorkflowState() {
setIsDownloading,
showCodeDialog,
setShowCodeDialog,
showExportDialog,
setShowExportDialog,
generatedCode,
allWorkflows,
setAllWorkflows,
Expand Down Expand Up @@ -967,13 +972,13 @@ function ToolbarActions({
{/* Save/Download - Mobile Vertical */}
<ButtonGroup className="flex lg:hidden" orientation="vertical">
<SaveButton handleSave={actions.handleSave} state={state} />
<DownloadButton handleDownload={actions.handleDownload} state={state} />
<DownloadButton state={state} />
</ButtonGroup>

{/* Save/Download - Desktop Horizontal */}
<ButtonGroup className="hidden lg:flex" orientation="horizontal">
<SaveButton handleSave={actions.handleSave} state={state} />
<DownloadButton handleDownload={actions.handleDownload} state={state} />
<DownloadButton state={state} />
</ButtonGroup>

<RunButtonGroup actions={actions} state={state} />
Expand Down Expand Up @@ -1015,10 +1020,8 @@ function SaveButton({
// Download Button Component
function DownloadButton({
state,
handleDownload,
}: {
state: ReturnType<typeof useWorkflowState>;
handleDownload: () => Promise<void>;
}) {
return (
<Button
Expand All @@ -1029,12 +1032,12 @@ function DownloadButton({
state.isGenerating ||
!state.currentWorkflowId
}
onClick={handleDownload}
onClick={() => state.setShowExportDialog(true)}
size="icon"
title={
state.isDownloading
? "Preparing download..."
: "Download workflow files"
: "Export workflow as code"
}
variant="secondary"
>
Expand Down Expand Up @@ -1375,6 +1378,76 @@ function WorkflowDialogsComponent({
</AlertDialogContent>
</AlertDialog>

<Dialog
onOpenChange={state.setShowExportDialog}
open={state.showExportDialog}
>
<DialogContent className="sm:max-w-lg">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Download className="size-5" />
Export Workflow as Code
</DialogTitle>
<DialogDescription>
Export your workflow as a standalone Next.js project that you can
run independently.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-2">
<p className="text-muted-foreground text-sm">
This will generate a complete Next.js project containing your
workflow code. Once exported, you can run your workflow outside of
the Workflow Builder, deploy it to Vercel, or integrate it into
your existing applications.
</p>
<Alert>
<FlaskConical className="size-4" />
<AlertTitle>Experimental Feature</AlertTitle>
<AlertDescription className="block">
This feature is experimental and may have limitations. If you
encounter any issues, please{" "}
<a
className="font-medium text-foreground underline underline-offset-4 hover:text-primary"
href="https://github.com/vercel-labs/workflow-builder-template/issues"
rel="noopener noreferrer"
target="_blank"
>
report them on GitHub
</a>
.
</AlertDescription>
</Alert>
</div>
<DialogFooter>
<Button
onClick={() => state.setShowExportDialog(false)}
variant="outline"
>
Cancel
</Button>
<Button
disabled={state.isDownloading}
onClick={() => {
state.setShowExportDialog(false);
actions.handleDownload();
}}
>
{state.isDownloading ? (
<>
<Loader2 className="mr-2 size-4 animate-spin" />
Exporting...
</>
) : (
<>
<Download className="mr-2 size-4" />
Export Project
</>
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>

<MissingIntegrationsDialog actions={actions} />
</>
);
Expand Down