Skip to content

Commit 2f2c2b0

Browse files
committed
feat(templates): landing page templates workflow states
1 parent 15db692 commit 2f2c2b0

18 files changed

+10186
-79
lines changed

apps/sim/app/(home)/components/landing-preview/components/landing-preview-workflow/workflow-data.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ export interface PreviewWorkflow {
3535
color: string
3636
blocks: PreviewBlock[]
3737
edges: Array<{ id: string; source: string; target: string }>
38+
/** Public JSON export used to seed the landing-page import flow */
39+
seedPath?: string
3840
}
3941

4042
/**

apps/sim/app/(home)/components/templates/template-workflows.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const OCR_INVOICE_WORKFLOW: PreviewWorkflow = {
88
id: 'tpl-ocr-invoice',
99
name: 'OCR Invoice to DB',
1010
color: '#2ABBF8',
11+
seedPath: '/landing-page-templates/ocr-invoice-db-d502887e-8750-40a3-a98b-fb2a4e725a4d.json',
1112
blocks: [
1213
{
1314
id: 'starter-1',
@@ -57,6 +58,7 @@ const GITHUB_RELEASE_WORKFLOW: PreviewWorkflow = {
5758
id: 'tpl-github-release',
5859
name: 'GitHub Release Agent',
5960
color: '#00F701',
61+
seedPath: '/landing-page-templates/gh-release-agent-d3bed10e-fc87-4fdb-b458-80d8e43757d3.json',
6062
blocks: [
6163
{
6264
id: 'github-1',
@@ -108,6 +110,8 @@ const MEETING_FOLLOWUP_WORKFLOW: PreviewWorkflow = {
108110
id: 'tpl-meeting-followup',
109111
name: 'Meeting Follow-up Agent',
110112
color: '#FFCC02',
113+
seedPath:
114+
'/landing-page-templates/meeting-followup-agent-a2357a2f-67f7-40c1-8e64-301bcd604239.json',
111115
blocks: [
112116
{
113117
id: 'gcal-1',
@@ -159,6 +163,7 @@ const CV_SCANNER_WORKFLOW: PreviewWorkflow = {
159163
id: 'tpl-cv-scanner',
160164
name: 'CV/Resume Scanner',
161165
color: '#FA4EDF',
166+
seedPath: '/landing-page-templates/resume-parser-d083c931-8788-4c6e-814c-0788830e164d.json',
162167
blocks: [
163168
{
164169
id: 'starter-2',
@@ -208,6 +213,7 @@ const EMAIL_TRIAGE_WORKFLOW: PreviewWorkflow = {
208213
id: 'tpl-email-triage',
209214
name: 'Email Triage Agent',
210215
color: '#FF6B2C',
216+
seedPath: '/landing-page-templates/email-triage-57e84f83-c583-4e74-b73a-080d25f2074d.json',
211217
blocks: [
212218
{
213219
id: 'gmail-2',
@@ -273,6 +279,7 @@ const COMPETITOR_MONITOR_WORKFLOW: PreviewWorkflow = {
273279
id: 'tpl-competitor-monitor',
274280
name: 'Competitor Monitor',
275281
color: '#6366F1',
282+
seedPath: '/landing-page-templates/competitor-monitor-52454688-49ae-4279-894a-aa6494f10e3a.json',
276283
blocks: [
277284
{
278285
id: 'schedule-1',
@@ -325,6 +332,7 @@ const SOCIAL_LISTENING_WORKFLOW: PreviewWorkflow = {
325332
id: 'tpl-social-listening',
326333
name: 'Social Listening Agent',
327334
color: '#F43F5E',
335+
seedPath: '/landing-page-templates/brand-mention-d2578496-d153-4db1-8ef1-c738cfa94a96.json',
328336
blocks: [
329337
{
330338
id: 'schedule-2',
@@ -377,6 +385,7 @@ const DATA_ENRICHMENT_WORKFLOW: PreviewWorkflow = {
377385
id: 'tpl-data-enrichment',
378386
name: 'Data Enrichment Pipeline',
379387
color: '#14B8A6',
388+
seedPath: '/landing-page-templates/lead-enricher-6ed8dede-1df6-4962-95f4-887abf524b38.json',
380389
blocks: [
381390
{
382391
id: 'starter-3',
@@ -426,6 +435,8 @@ const FEEDBACK_DIGEST_WORKFLOW: PreviewWorkflow = {
426435
id: 'tpl-feedback-digest',
427436
name: 'Customer Feedback Digest',
428437
color: '#F59E0B',
438+
seedPath:
439+
'/landing-page-templates/customer-feedback-digest-2a1c59de-fdcc-4ae0-b448-69a58b36c29a.json',
429440
blocks: [
430441
{
431442
id: 'schedule-3',
@@ -478,6 +489,7 @@ const PR_REVIEW_WORKFLOW: PreviewWorkflow = {
478489
id: 'tpl-pr-review',
479490
name: 'PR Review Agent',
480491
color: '#06B6D4',
492+
seedPath: '/landing-page-templates/pr-review-cb5f2d92-a324-4958-8303-4710c572b71d.json',
481493
blocks: [
482494
{
483495
id: 'github-2',
@@ -529,6 +541,7 @@ const KNOWLEDGE_QA_WORKFLOW: PreviewWorkflow = {
529541
id: 'tpl-knowledge-qa',
530542
name: 'Knowledge Base QA',
531543
color: '#84CC16',
544+
seedPath: '/landing-page-templates/knowledge-base-qa-e9dcd9f1-18bd-4163-b5d8-3e239a297526.json',
532545
blocks: [
533546
{
534547
id: 'starter-4',

apps/sim/app/(home)/components/templates/templates.tsx

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
'use client'
22

3-
import { useRef, useState } from 'react'
3+
import { useCallback, useRef, useState } from 'react'
4+
import { createLogger } from '@sim/logger'
45
import { type MotionValue, motion, useScroll, useTransform } from 'framer-motion'
56
import dynamic from 'next/dynamic'
6-
import Link from 'next/link'
7+
import { useRouter } from 'next/navigation'
78
import { Badge, ChevronDown } from '@/components/emcn'
9+
import { LandingWorkflowSeedStorage } from '@/lib/core/utils/browser-storage'
810
import { cn } from '@/lib/core/utils/cn'
911
import { TEMPLATE_WORKFLOWS } from '@/app/(home)/components/templates/template-workflows'
1012

13+
const logger = createLogger('LandingTemplates')
14+
1115
const LandingPreviewWorkflow = dynamic(
1216
() =>
1317
import(
@@ -344,6 +348,8 @@ const TEMPLATES_PANEL_ID = 'templates-panel'
344348
export default function Templates() {
345349
const sectionRef = useRef<HTMLDivElement>(null)
346350
const [activeIndex, setActiveIndex] = useState(0)
351+
const [isPreparingTemplate, setIsPreparingTemplate] = useState(false)
352+
const router = useRouter()
347353

348354
const { scrollYProgress } = useScroll({
349355
target: sectionRef,
@@ -353,6 +359,45 @@ export default function Templates() {
353359
const activeWorkflow = TEMPLATE_WORKFLOWS[activeIndex]
354360
const activeDepth = DEPTH_CONFIGS[activeWorkflow.id]
355361

362+
const handleUseTemplate = useCallback(async () => {
363+
if (isPreparingTemplate) return
364+
365+
setIsPreparingTemplate(true)
366+
367+
try {
368+
if (activeWorkflow.seedPath) {
369+
const response = await fetch(activeWorkflow.seedPath)
370+
371+
if (!response.ok) {
372+
throw new Error(`Failed to fetch template seed: ${response.status}`)
373+
}
374+
375+
const workflowJson = await response.text()
376+
LandingWorkflowSeedStorage.store({
377+
templateId: activeWorkflow.id,
378+
workflowName: activeWorkflow.name,
379+
color: activeWorkflow.color,
380+
workflowJson,
381+
})
382+
}
383+
} catch (error) {
384+
logger.error('Failed to prepare landing template workflow seed', {
385+
templateId: activeWorkflow.id,
386+
error,
387+
})
388+
} finally {
389+
setIsPreparingTemplate(false)
390+
router.push('/signup')
391+
}
392+
}, [
393+
activeWorkflow.color,
394+
activeWorkflow.id,
395+
activeWorkflow.name,
396+
activeWorkflow.seedPath,
397+
isPreparingTemplate,
398+
router,
399+
])
400+
356401
return (
357402
<section
358403
ref={sectionRef}
@@ -508,11 +553,13 @@ export default function Templates() {
508553
fitViewOptions={{ padding: 0.15, maxZoom: 1.3 }}
509554
/>
510555
</div>
511-
<Link
512-
href='/signup'
513-
className='group/cta absolute top-[16px] right-[16px] z-10 inline-flex h-[32px] items-center gap-[6px] rounded-[5px] border border-[#33C482] bg-[#33C482] px-[10px] font-[430] font-season text-[14px] text-black transition-[filter] hover:brightness-110'
556+
<button
557+
type='button'
558+
onClick={handleUseTemplate}
559+
disabled={isPreparingTemplate}
560+
className='group/cta absolute top-[16px] right-[16px] z-10 inline-flex h-[32px] cursor-pointer items-center gap-[6px] rounded-[5px] border border-[#33C482] bg-[#33C482] px-[10px] font-[430] font-season text-[14px] text-black transition-[filter] hover:brightness-110'
514561
>
515-
Use template
562+
{isPreparingTemplate ? 'Preparing...' : 'Use template'}
516563
<span className='relative h-[10px] w-[10px] shrink-0'>
517564
<ChevronDown className='-rotate-90 absolute inset-0 h-[10px] w-[10px] transition-opacity duration-150 group-hover/cta:opacity-0' />
518565
<svg
@@ -531,7 +578,7 @@ export default function Templates() {
531578
/>
532579
</svg>
533580
</span>
534-
</Link>
581+
</button>
535582
</div>
536583
</div>
537584

apps/sim/app/workspace/[workspaceId]/home/home.tsx

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@
22

33
import { useCallback, useEffect, useRef, useState } from 'react'
44
import { createLogger } from '@sim/logger'
5-
import { useParams } from 'next/navigation'
6-
import { LandingPromptStorage } from '@/lib/core/utils/browser-storage'
5+
import { useParams, useRouter } from 'next/navigation'
6+
import {
7+
LandingPromptStorage,
8+
LandingTemplateStorage,
9+
type LandingWorkflowSeed,
10+
LandingWorkflowSeedStorage,
11+
} from '@/lib/core/utils/browser-storage'
12+
import { persistImportedWorkflow } from '@/lib/workflows/operations/import-export'
713
import { MessageContent, MothershipView, UserInput } from './components'
814
import { useChat } from './hooks'
915

@@ -15,19 +21,80 @@ interface HomeProps {
1521

1622
export function Home({ chatId }: HomeProps = {}) {
1723
const { workspaceId } = useParams<{ workspaceId: string }>()
24+
const router = useRouter()
1825
const [inputValue, setInputValue] = useState('')
19-
const hasCheckedLandingPromptRef = useRef(false)
26+
const hasCheckedLandingStorageRef = useRef(false)
27+
28+
const createWorkflowFromLandingSeed = useCallback(
29+
async (seed: LandingWorkflowSeed) => {
30+
try {
31+
const result = await persistImportedWorkflow({
32+
content: seed.workflowJson,
33+
filename: `${seed.workflowName}.json`,
34+
workspaceId,
35+
nameOverride: seed.workflowName,
36+
descriptionOverride: seed.workflowDescription || 'Imported from landing template',
37+
colorOverride: seed.color,
38+
createWorkflow: async ({ name, description, color, workspaceId }) => {
39+
const response = await fetch('/api/workflows', {
40+
method: 'POST',
41+
headers: { 'Content-Type': 'application/json' },
42+
body: JSON.stringify({
43+
name,
44+
description,
45+
color,
46+
workspaceId,
47+
}),
48+
})
49+
50+
if (!response.ok) {
51+
const errorData = await response.json().catch(() => ({}))
52+
throw new Error(errorData.error || 'Failed to create workflow')
53+
}
54+
55+
return response.json()
56+
},
57+
})
58+
59+
if (result?.workflowId) {
60+
window.location.href = `/workspace/${workspaceId}/w/${result.workflowId}`
61+
return
62+
}
63+
64+
logger.warn('Landing workflow seed did not produce a workflow', {
65+
templateId: seed.templateId,
66+
})
67+
} catch (error) {
68+
logger.error('Error creating workflow from landing workflow seed:', error)
69+
}
70+
},
71+
[workspaceId]
72+
)
2073

2174
useEffect(() => {
22-
if (hasCheckedLandingPromptRef.current) return
23-
hasCheckedLandingPromptRef.current = true
75+
if (hasCheckedLandingStorageRef.current) return
76+
hasCheckedLandingStorageRef.current = true
77+
78+
const workflowSeed = LandingWorkflowSeedStorage.consume()
79+
if (workflowSeed) {
80+
logger.info('Retrieved landing page workflow seed, creating workflow in workspace')
81+
void createWorkflowFromLandingSeed(workflowSeed)
82+
return
83+
}
84+
85+
const templateId = LandingTemplateStorage.consume()
86+
if (templateId) {
87+
logger.info('Retrieved landing page template, redirecting to template detail')
88+
router.replace(`/workspace/${workspaceId}/templates/${templateId}?use=true`)
89+
return
90+
}
2491

2592
const prompt = LandingPromptStorage.consume()
2693
if (prompt) {
2794
logger.info('Retrieved landing page prompt, populating home input')
2895
setInputValue(prompt)
2996
}
30-
}, [])
97+
}, [createWorkflowFromLandingSeed, workspaceId, router])
3198

3299
const {
33100
messages,

0 commit comments

Comments
 (0)