Skip to content
Merged
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
56 changes: 56 additions & 0 deletions bun.lock

Large diffs are not rendered by default.

356 changes: 356 additions & 0 deletions docs/prebuilt-workflows-design.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const config = {
},
experimental: {
webpackMemoryOptimizations: true,
optimizePackageImports: ["twilio"],
},
};

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@hookform/resolvers": "4.1.3",
"@next/bundle-analyzer": "^15.5.6",
"@prisma/client": "^5.14.0",
"@radix-ui/react-alert-dialog": "^1.1.6",
"@radix-ui/react-avatar": "^1.1.3",
Expand All @@ -47,6 +48,7 @@
"@radix-ui/react-separator": "^1.1.2",
"@radix-ui/react-slider": "^1.2.3",
"@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.3",
"@radix-ui/react-toggle": "^1.1.10",
"@radix-ui/react-tooltip": "^1.1.8",
Expand Down
63,788 changes: 53 additions & 63,735 deletions prisma/generated/zod/index.ts

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions prisma/migrations/20251018102846_/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
-- CreateTable
CREATE TABLE "PrebuiltWorkflowConfig" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"workflowKey" TEXT NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT false,
"emailSubject" TEXT NOT NULL,
"emailBody" TEXT NOT NULL,
"templateVersion" INTEGER NOT NULL DEFAULT 1,
"updatedByUserId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "PrebuiltWorkflowConfig_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE INDEX "PrebuiltWorkflowConfig_organizationId_idx" ON "PrebuiltWorkflowConfig"("organizationId");

-- CreateIndex
CREATE INDEX "PrebuiltWorkflowConfig_workflowKey_idx" ON "PrebuiltWorkflowConfig"("workflowKey");

-- CreateIndex
CREATE UNIQUE INDEX "PrebuiltWorkflowConfig_organizationId_workflowKey_key" ON "PrebuiltWorkflowConfig"("organizationId", "workflowKey");

-- AddForeignKey
ALTER TABLE "PrebuiltWorkflowConfig" ADD CONSTRAINT "PrebuiltWorkflowConfig_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "PrebuiltWorkflowConfig" ADD CONSTRAINT "PrebuiltWorkflowConfig_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;
32 changes: 32 additions & 0 deletions prisma/migrations/20251018105029_/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
-- CreateTable
CREATE TABLE "PrebuiltWorkflowRun" (
"id" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"workflowKey" TEXT NOT NULL,
"status" "WorkflowExecutionStatus" NOT NULL DEFAULT 'RUNNING',
"triggerModule" TEXT NOT NULL,
"triggerEntity" TEXT NOT NULL,
"triggerEvent" TEXT NOT NULL,
"triggeredAt" TIMESTAMP(3) NOT NULL,
"startedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"completedAt" TIMESTAMP(3),
"durationMs" INTEGER,
"emailRecipient" TEXT,
"emailSubject" TEXT,
"context" JSONB,
"result" JSONB,
"error" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "PrebuiltWorkflowRun_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE INDEX "PrebuiltWorkflowRun_organizationId_workflowKey_createdAt_idx" ON "PrebuiltWorkflowRun"("organizationId", "workflowKey", "createdAt");

-- CreateIndex
CREATE INDEX "PrebuiltWorkflowRun_organizationId_idx" ON "PrebuiltWorkflowRun"("organizationId");

-- AddForeignKey
ALTER TABLE "PrebuiltWorkflowRun" ADD CONSTRAINT "PrebuiltWorkflowRun_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
14 changes: 14 additions & 0 deletions prisma/migrations/20251018163029_/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- AlterTable
ALTER TABLE "PrebuiltWorkflowConfig" ADD COLUMN "messageTemplateId" TEXT;

-- AlterTable
ALTER TABLE "Project" ADD COLUMN "customerId" TEXT;

-- CreateIndex
CREATE INDEX "PrebuiltWorkflowConfig_messageTemplateId_idx" ON "PrebuiltWorkflowConfig"("messageTemplateId");

-- CreateIndex
CREATE INDEX "Project_customerId_idx" ON "Project"("customerId");

-- AddForeignKey
ALTER TABLE "Project" ADD CONSTRAINT "Project_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE SET NULL ON UPDATE CASCADE;
69 changes: 69 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ generator client {
generator zod {
provider = "zod-prisma-types"
output = "./generated/zod"
writeBarrelFiles = true // default is true
createInputTypes = false // default is true
createModelTypes = true // default is true
addInputTypeValidation = false // default is true
addIncludeType = false // default is true
addSelectType = false // default is true
validateWhereUniqueInput = false // default is true
createOptionalDefaultValuesTypes = false // default is false
createRelationValuesTypes = false // default is false
createPartialTypes = false // default is false
useDefaultValidators = true // default is true
coerceDate = true // default is true
writeNullishInModelTypes = false // default is false
useTypeAssertions = false // default is false
}

datasource db {
Expand Down Expand Up @@ -115,6 +129,8 @@ model Organization {
customRoles CustomRole[]
financialReports FinancialReport[]
workflows Workflow[]
prebuiltWorkflowConfigs PrebuiltWorkflowConfig[]
prebuiltWorkflowRuns PrebuiltWorkflowRun[]
actionTemplates ActionTemplate[]
variableDefinitions VariableDefinition[]
integrationConfigs IntegrationConfig[]
Expand Down Expand Up @@ -340,6 +356,7 @@ model Customer {
interactions CustomerInteraction[]
deals Deal[]
invoices Invoice[]
projects Project[]

@@index([organizationId])
@@index([email])
Expand Down Expand Up @@ -450,18 +467,21 @@ model Project {
budget Decimal? @db.Decimal(10, 2)
currency String @default("USD")
createdById String?
customerId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

// Relations
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
customer Customer? @relation(fields: [customerId], references: [id], onDelete: SetNull)
tasks Task[]
resources ProjectResource[]
timeEntries TimeEntry[]
expenses Expense[]

@@index([organizationId])
@@index([createdById])
@@index([customerId])
@@index([status])
}

Expand Down Expand Up @@ -1185,6 +1205,7 @@ model User {
accounts Account[]
createdImportSessions ImportSession[] @relation("CreatedImportSessions")
createdImportTemplates ImportTemplate[] @relation("CreatedImportTemplates")
updatedPrebuiltWorkflowConfigs PrebuiltWorkflowConfig[] @relation("PrebuiltWorkflowConfigUpdatedBy")

@@unique([email])
@@map("user")
Expand Down Expand Up @@ -1567,6 +1588,54 @@ model Workflow {
@@index([createdById])
}

model PrebuiltWorkflowConfig {
id String @id @default(cuid())
organizationId String
workflowKey String
enabled Boolean @default(false)
emailSubject String
emailBody String
templateVersion Int @default(1)
updatedByUserId String
messageTemplateId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
updatedBy User @relation("PrebuiltWorkflowConfigUpdatedBy", fields: [updatedByUserId], references: [id], onDelete: Cascade)

@@unique([organizationId, workflowKey])
@@index([organizationId])
@@index([workflowKey])
@@index([messageTemplateId])
}

model PrebuiltWorkflowRun {
id String @id @default(cuid())
organizationId String
workflowKey String
status WorkflowExecutionStatus @default(RUNNING)
triggerModule String
triggerEntity String
triggerEvent String
triggeredAt DateTime
startedAt DateTime @default(now())
completedAt DateTime?
durationMs Int?
emailRecipient String?
emailSubject String?
context Json?
result Json?
error String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)

@@index([organizationId, workflowKey, createdAt])
@@index([organizationId])
}

// ==============================
// UI Configuration Store
// ==============================
Expand Down
85 changes: 8 additions & 77 deletions src/app/(dashboard)/[orgId]/workflows/[workflowId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,70 +1,24 @@
import { redirect } from "next/navigation";
import { auth } from "~/lib/auth";
import { headers } from "next/headers";
import { auth } from "~/lib/auth";
import { db } from "~/server/db";
import { WorkflowDesigner } from "~/components/workflow/workflow-designer";
import { Skeleton } from "~/components/ui/skeleton";
import { Suspense } from "react";
import Link from "next/link";

function WorkflowDesignerSkeleton() {
return (
<div className="flex-1 space-y-4 p-4 md:p-8 pt-6">
<div className="flex items-center justify-between mb-4">
<div className="space-y-2">
<Skeleton className="h-8 w-64" />
<Skeleton className="h-4 w-96" />
</div>
<Skeleton className="h-10 w-32" />
</div>
<div className="h-[calc(100vh-12rem)] border rounded-lg">
<div className="p-4 space-y-4">
<div className="flex justify-between items-center">
<Skeleton className="h-6 w-32" />
<div className="flex gap-2">
<Skeleton className="h-8 w-20" />
<Skeleton className="h-8 w-20" />
</div>
</div>
<div className="grid grid-cols-3 gap-4 h-full">
<div className="space-y-2">
<Skeleton className="h-4 w-16" />
<Skeleton className="h-32 w-full" />
<Skeleton className="h-32 w-full" />
</div>
<div className="col-span-2 border rounded">
<div className="p-4 space-y-4">
<div className="flex gap-4">
<Skeleton className="h-16 w-32" />
<Skeleton className="h-16 w-32" />
</div>
<Skeleton className="h-48 w-full" />
</div>
</div>
</div>
</div>
</div>
</div>
);
}

export default async function WorkflowDesignerPage({
export default async function DeprecatedWorkflowDesignerPage({
params,
}: {
params: Promise<{ orgId: string; workflowId: string }>;
params: { orgId: string; workflowId: string };
}) {
const { orgId, workflowId } = await params;
const { orgId } = params;

// Get user session
const requestHeaders = await headers();
const session = await auth.api.getSession({
headers: await headers(),
headers: requestHeaders,
});

if (!session?.user?.id) {
redirect("/sign-in");
}

// Check if organization exists and user has access
const organization = await db.organization.findFirst({
where: {
id: orgId,
Expand All @@ -74,35 +28,12 @@ export default async function WorkflowDesignerPage({
},
},
},
select: { id: true },
});

if (!organization) {
redirect("/");
}

return (
<div className="flex-1 space-y-4 p-4 md:p-8 pt-6">
<div className="flex items-center justify-between mb-4">
<div>
<h2 className="text-3xl font-bold tracking-tight">
Workflow Designer
</h2>
<p className="text-muted-foreground">
Design and configure your automated workflows
</p>
</div>
<Link
href={`/${orgId}/workflows`}
className="inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2"
>
Back to Workflows
</Link>
</div>
<div className="h-[calc(100vh-12rem)]">
<Suspense fallback={<WorkflowDesignerSkeleton />}>
<WorkflowDesigner organizationId={orgId} workflowId={workflowId} />
</Suspense>
</div>
</div>
);
redirect(`/${orgId}/workflows`);
}
1 change: 1 addition & 0 deletions src/components/app-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
title: "Workflows",
url: "/[orgId]/workflows",
icon: GitBranch,
badge: "Beta",
items: [],
});

Expand Down
18 changes: 18 additions & 0 deletions src/components/nav-main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { ChevronRight, type LucideIcon } from "lucide-react";
import { usePathname } from "next/navigation";
import Link from "next/link";
import { Badge } from "~/components/ui/badge";
import {
Collapsible,
CollapsibleContent,
Expand All @@ -23,6 +24,7 @@ interface NavItem {
url: string;
icon?: LucideIcon;
isActive?: boolean;
badge?: string;
action?: {
icon: LucideIcon;
onClick: () => void;
Expand Down Expand Up @@ -73,6 +75,14 @@ export function NavMain({ items }: { items: NavItem[] }) {
<Link href={itemUrl}>
{item.icon && <item.icon />}
<span>{item.title}</span>
{item.badge && (
<Badge
variant="secondary"
className="ml-auto h-5 text-xs font-normal"
>
{item.badge}
</Badge>
)}
</Link>
</SidebarMenuButton>
{item.action && (
Expand Down Expand Up @@ -111,6 +121,14 @@ export function NavMain({ items }: { items: NavItem[] }) {
>
{item.icon && <item.icon />}
<span>{item.title}</span>
{item.badge && (
<Badge
variant="secondary"
className="ml-2 h-5 text-xs font-normal"
>
{item.badge}
</Badge>
)}
<ChevronRight className="ml-auto transition-transform duration-200 opacity-0 group-hover/item:opacity-100 group-data-[state=open]/collapsible:rotate-90 group-data-[state=open]/collapsible:opacity-100" />
</SidebarMenuButton>
</CollapsibleTrigger>
Expand Down
Loading
Loading