Skip to content

Commit 31012fc

Browse files
committed
feat(marketplace): load workflows into marketplace
1 parent 1966c73 commit 31012fc

File tree

6 files changed

+427
-779
lines changed

6 files changed

+427
-779
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { NextRequest } from 'next/server'
2+
import { eq, sql } from 'drizzle-orm'
3+
import { createLogger } from '@/lib/logs/console-logger'
4+
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
5+
import { db } from '@/db'
6+
import * as schema from '@/db/schema'
7+
8+
const logger = createLogger('MarketplaceStateAPI')
9+
10+
// Cache for 1 hour
11+
export const revalidate = 3600
12+
13+
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
14+
const requestId = crypto.randomUUID().slice(0, 8)
15+
16+
try {
17+
const { id } = await params
18+
19+
// Fetch marketplace data to get the state
20+
const marketplaceEntry = await db
21+
.select({
22+
id: schema.marketplace.id,
23+
state: schema.marketplace.state,
24+
})
25+
.from(schema.marketplace)
26+
.where(eq(schema.marketplace.workflowId, id))
27+
.limit(1)
28+
.then((rows) => rows[0])
29+
30+
if (!marketplaceEntry) {
31+
logger.warn(`[${requestId}] No marketplace entry found for workflow: ${id}`)
32+
return createErrorResponse('Workflow not found in marketplace', 404)
33+
}
34+
35+
// Increment the view count for this workflow
36+
await db
37+
.update(schema.marketplace)
38+
.set({
39+
views: sql`${schema.marketplace.views} + 1`
40+
})
41+
.where(eq(schema.marketplace.workflowId, id))
42+
43+
logger.info(`[${requestId}] Retrieved workflow state for marketplace item: ${id}`)
44+
45+
return createSuccessResponse({
46+
id: marketplaceEntry.id,
47+
state: marketplaceEntry.state,
48+
})
49+
} catch (error) {
50+
logger.error(
51+
`[${requestId}] Error getting workflow state for marketplace item: ${(await params).id}`,
52+
error
53+
)
54+
return createErrorResponse('Failed to get workflow state', 500)
55+
}
56+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { NextRequest, NextResponse } from 'next/server'
2+
import { desc, eq, sql } from 'drizzle-orm'
3+
import { createLogger } from '@/lib/logs/console-logger'
4+
import { db } from '@/db'
5+
import { marketplace } from '@/db/schema'
6+
import { CATEGORIES } from '@/app/w/marketplace/constants/categories'
7+
8+
const logger = createLogger('MarketplaceFeaturedAPI')
9+
10+
// 1 hour cache
11+
export const revalidate = 3600
12+
13+
export async function GET(request: NextRequest) {
14+
const requestId = crypto.randomUUID().slice(0, 8)
15+
16+
try {
17+
// Parse query parameters
18+
const url = new URL(request.url)
19+
const categoryParam = url.searchParams.get('category')
20+
const limitParam = url.searchParams.get('limit') || '6'
21+
const limit = parseInt(limitParam, 10)
22+
23+
const result: {
24+
popular: any[]
25+
recent: any[]
26+
byCategory: Record<string, any[]>
27+
} = {
28+
popular: [],
29+
recent: [],
30+
byCategory: {}
31+
}
32+
33+
// Get popular items (most stars)
34+
result.popular = await db
35+
.select({
36+
id: marketplace.id,
37+
workflowId: marketplace.workflowId,
38+
name: marketplace.name,
39+
description: marketplace.description,
40+
authorName: marketplace.authorName,
41+
stars: marketplace.stars,
42+
views: marketplace.views,
43+
category: marketplace.category,
44+
createdAt: marketplace.createdAt,
45+
updatedAt: marketplace.updatedAt,
46+
})
47+
.from(marketplace)
48+
.orderBy(desc(marketplace.stars), desc(marketplace.views))
49+
.limit(limit)
50+
51+
// Get recent items (most recent first)
52+
result.recent = await db
53+
.select({
54+
id: marketplace.id,
55+
workflowId: marketplace.workflowId,
56+
name: marketplace.name,
57+
description: marketplace.description,
58+
authorName: marketplace.authorName,
59+
stars: marketplace.stars,
60+
views: marketplace.views,
61+
category: marketplace.category,
62+
createdAt: marketplace.createdAt,
63+
updatedAt: marketplace.updatedAt,
64+
})
65+
.from(marketplace)
66+
.orderBy(desc(marketplace.createdAt))
67+
.limit(limit)
68+
69+
// If a specific category is requested
70+
if (categoryParam) {
71+
result.byCategory[categoryParam] = await db
72+
.select({
73+
id: marketplace.id,
74+
workflowId: marketplace.workflowId,
75+
name: marketplace.name,
76+
description: marketplace.description,
77+
authorName: marketplace.authorName,
78+
stars: marketplace.stars,
79+
views: marketplace.views,
80+
category: marketplace.category,
81+
createdAt: marketplace.createdAt,
82+
updatedAt: marketplace.updatedAt,
83+
})
84+
.from(marketplace)
85+
.where(eq(marketplace.category, categoryParam))
86+
.orderBy(desc(marketplace.stars), desc(marketplace.views))
87+
.limit(limit)
88+
} else {
89+
// Get items for each category
90+
// Using Promise.all for parallel fetching to improve performance
91+
await Promise.all(
92+
CATEGORIES.map(async (category) => {
93+
result.byCategory[category.value] = await db
94+
.select({
95+
id: marketplace.id,
96+
workflowId: marketplace.workflowId,
97+
name: marketplace.name,
98+
description: marketplace.description,
99+
authorName: marketplace.authorName,
100+
stars: marketplace.stars,
101+
views: marketplace.views,
102+
category: marketplace.category,
103+
createdAt: marketplace.createdAt,
104+
updatedAt: marketplace.updatedAt,
105+
})
106+
.from(marketplace)
107+
.where(eq(marketplace.category, category.value))
108+
.orderBy(desc(marketplace.stars), desc(marketplace.views))
109+
.limit(limit)
110+
})
111+
)
112+
}
113+
114+
logger.info(`[${requestId}] Fetched featured marketplace items successfully`)
115+
116+
return NextResponse.json(result)
117+
} catch (error: any) {
118+
logger.error(`[${requestId}] Error fetching marketplace items`, error)
119+
return NextResponse.json({ error: error.message }, { status: 500 })
120+
}
121+
}

sim/app/w/[id]/components/control-bar/components/marketplace-modal/marketplace-modal.tsx

Lines changed: 6 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ import { useNotificationStore } from '@/stores/notifications/store'
5454
import { getWorkflowWithValues } from '@/stores/workflows'
5555
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
5656
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
57+
import {
58+
CATEGORIES,
59+
getCategoryColor,
60+
getCategoryIcon,
61+
getCategoryLabel,
62+
} from '@/app/w/marketplace/constants/categories'
5763

5864
const logger = createLogger('MarketplaceModal')
5965

@@ -114,41 +120,6 @@ const marketplaceFormSchema = z.object({
114120

115121
type MarketplaceFormValues = z.infer<typeof marketplaceFormSchema>
116122

117-
// Categories for the dropdown with icons
118-
const CATEGORIES = [
119-
// {
120-
// value: 'project_management',
121-
// label: 'Project Management',
122-
// icon: <ChartBar className="h-4 w-4 mr-2" />,
123-
// },
124-
{ value: 'data', label: 'Data Analysis', icon: <Database className="h-4 w-4 mr-2" /> },
125-
{ value: 'marketing', label: 'Marketing', icon: <MailIcon className="h-4 w-4 mr-2" /> },
126-
{ value: 'sales', label: 'Sales', icon: <Store className="h-4 w-4 mr-2" /> },
127-
// { value: 'productivity', label: 'Productivity', icon: <TimerIcon className="h-4 w-4 mr-2" /> },
128-
// { value: 'content', label: 'Content Creation', icon: <NotebookPen className="h-4 w-4 mr-2" /> },
129-
{
130-
value: 'customer_service',
131-
label: 'Customer Service',
132-
icon: <BotMessageSquare className="h-4 w-4 mr-2" />,
133-
},
134-
{ value: 'research', label: 'Research', icon: <Atom className="h-4 w-4 mr-2" /> },
135-
{ value: 'finance', label: 'Finance', icon: <LineChart className="h-4 w-4 mr-2" /> },
136-
{ value: 'programming', label: 'Programming', icon: <Code className="h-4 w-4 mr-2" /> },
137-
{ value: 'other', label: 'Other', icon: <Brain className="h-4 w-4 mr-2" /> },
138-
]
139-
140-
// Find category label by value
141-
const getCategoryLabel = (value: string) => {
142-
const category = CATEGORIES.find((cat) => cat.value === value)
143-
return category ? category.label : value
144-
}
145-
146-
// Find category icon by value
147-
const getCategoryIcon = (value: string) => {
148-
const category = CATEGORIES.find((cat) => cat.value === value)
149-
return category ? category.icon : <Store className="h-4 w-4 mr-2" />
150-
}
151-
152123
// Tooltip texts
153124
const TOOLTIPS = {
154125
category: 'Categorizing your workflow helps users find it more easily.',
@@ -601,25 +572,3 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps)
601572
</Dialog>
602573
)
603574
}
604-
605-
// Add this helper function after the getCategoryLabel function
606-
const getCategoryColor = (value: string): string => {
607-
switch (value) {
608-
case 'data':
609-
return '#0ea5e9' // sky-500
610-
case 'marketing':
611-
return '#f43f5e' // rose-500
612-
case 'sales':
613-
return '#10b981' // emerald-500
614-
case 'customer_service':
615-
return '#8b5cf6' // violet-500
616-
case 'research':
617-
return '#f59e0b' // amber-500
618-
case 'finance':
619-
return '#14b8a6' // teal-500
620-
case 'programming':
621-
return '#6366f1' // indigo-500
622-
default:
623-
return '#7F2FFF' // Brand purple
624-
}
625-
}

sim/app/w/marketplace/components/toolbar/toolbar.tsx

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useEffect, useState } from 'react'
44
import {
55
BotMessageSquare,
6+
Clock,
67
Code,
78
LineChart,
89
MailIcon,
@@ -15,7 +16,7 @@ import {
1516
import { Button } from '@/components/ui/button'
1617
import { ScrollArea } from '@/components/ui/scroll-area'
1718
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
18-
import { mockWorkflows } from '../../marketplace'
19+
import { CATEGORIES, getCategoryIcon } from '../../constants/categories'
1920

2021
export type MarketplaceCategory = 'popular' | 'programming' | 'marketing' | 'all'
2122

@@ -24,21 +25,27 @@ interface ToolbarProps {
2425
activeSection: string | null
2526
}
2627

27-
// Map of category icons
28-
const categoryIcons: Record<string, React.ReactNode> = {
28+
// Map of special section icons
29+
const specialIcons: Record<string, React.ReactNode> = {
2930
popular: <Star className="h-4 w-4 mr-2" />,
30-
programming: <Code className="h-4 w-4 mr-2" />,
31-
marketing: <MailIcon className="h-4 w-4 mr-2" />,
32-
sales: <Store className="h-4 w-4 mr-2" />,
31+
recent: <Clock className="h-4 w-4 mr-2" />,
3332
}
3433

3534
export function Toolbar({ scrollToSection, activeSection }: ToolbarProps) {
3635
const [categories, setCategories] = useState<string[]>([])
3736

38-
// Extract all available categories from mockWorkflows
37+
// Set categories including special sections
3938
useEffect(() => {
40-
const availableCategories = Object.keys(mockWorkflows)
41-
setCategories(availableCategories)
39+
// Start with special sections like 'popular' and 'recent'
40+
const specialSections = ['popular']
41+
42+
// Add categories from centralized definitions
43+
const categoryValues = CATEGORIES.map((cat) => cat.value)
44+
45+
// Add 'recent' as the last item
46+
const allCategories = [...specialSections, ...categoryValues, 'recent']
47+
48+
setCategories(allCategories)
4249
}, [])
4350

4451
return (
@@ -54,7 +61,7 @@ export function Toolbar({ scrollToSection, activeSection }: ToolbarProps) {
5461
}`}
5562
onClick={() => scrollToSection(category)}
5663
>
57-
{categoryIcons[category] || <Star className="h-4 w-4 mr-2" />}
64+
{specialIcons[category] || getCategoryIcon(category)}
5865
{category}
5966
</Button>
6067
))}

0 commit comments

Comments
 (0)