Skip to content

Commit c7ad9d5

Browse files
committed
improvement: marketplace and sidebar
1 parent e14c705 commit c7ad9d5

File tree

14 files changed

+1524
-132
lines changed

14 files changed

+1524
-132
lines changed

sim/app/api/workflows/sync/route.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ import { workflow } from '@/db/schema'
88

99
const logger = createLogger('WorkflowAPI')
1010

11+
// Define marketplace data schema
12+
const MarketplaceDataSchema = z.object({
13+
id: z.string(),
14+
status: z.enum(['owner', 'temp', 'star'])
15+
}).nullable().optional()
16+
1117
// Schema for workflow data
1218
const WorkflowStateSchema = z.object({
1319
blocks: z.record(z.any()),
@@ -20,6 +26,7 @@ const WorkflowStateSchema = z.object({
2026
.optional()
2127
.transform((val) => (typeof val === 'string' ? new Date(val) : val)),
2228
isPublished: z.boolean().optional(),
29+
marketplaceData: MarketplaceDataSchema
2330
})
2431

2532
const WorkflowSchema = z.object({
@@ -28,6 +35,7 @@ const WorkflowSchema = z.object({
2835
description: z.string().optional(),
2936
color: z.string().optional(),
3037
state: WorkflowStateSchema,
38+
marketplaceData: MarketplaceDataSchema
3139
})
3240

3341
const SyncPayloadSchema = z.object({
@@ -114,6 +122,12 @@ export async function POST(req: NextRequest) {
114122
processedIds.add(id)
115123
const dbWorkflow = dbWorkflowMap.get(id)
116124

125+
// Handle legacy published workflows migration
126+
// If client workflow has isPublished but no marketplaceData, create marketplaceData with owner status
127+
if (clientWorkflow.state.isPublished && !clientWorkflow.marketplaceData) {
128+
clientWorkflow.marketplaceData = { id: clientWorkflow.id, status: 'owner' }
129+
}
130+
117131
if (!dbWorkflow) {
118132
// New workflow - create
119133
operations.push(
@@ -124,6 +138,7 @@ export async function POST(req: NextRequest) {
124138
description: clientWorkflow.description,
125139
color: clientWorkflow.color,
126140
state: clientWorkflow.state,
141+
marketplaceData: clientWorkflow.marketplaceData || null,
127142
lastSynced: now,
128143
createdAt: now,
129144
updatedAt: now,
@@ -135,7 +150,8 @@ export async function POST(req: NextRequest) {
135150
JSON.stringify(dbWorkflow.state) !== JSON.stringify(clientWorkflow.state) ||
136151
dbWorkflow.name !== clientWorkflow.name ||
137152
dbWorkflow.description !== clientWorkflow.description ||
138-
dbWorkflow.color !== clientWorkflow.color
153+
dbWorkflow.color !== clientWorkflow.color ||
154+
JSON.stringify(dbWorkflow.marketplaceData) !== JSON.stringify(clientWorkflow.marketplaceData)
139155

140156
if (needsUpdate) {
141157
operations.push(
@@ -146,6 +162,7 @@ export async function POST(req: NextRequest) {
146162
description: clientWorkflow.description,
147163
color: clientWorkflow.color,
148164
state: clientWorkflow.state,
165+
marketplaceData: clientWorkflow.marketplaceData || null,
149166
lastSynced: now,
150167
updatedAt: now,
151168
})

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

Lines changed: 46 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ import { cn } from '@/lib/utils'
5353
import { useNotificationStore } from '@/stores/notifications/store'
5454
import { getWorkflowWithValues } from '@/stores/workflows'
5555
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
56-
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
5756
import {
5857
CATEGORIES,
5958
getCategoryColor,
@@ -144,8 +143,24 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps)
144143
const [marketplaceInfo, setMarketplaceInfo] = useState<MarketplaceInfo | null>(null)
145144
const [isLoading, setIsLoading] = useState(false)
146145
const { addNotification } = useNotificationStore()
147-
const { activeWorkflowId, workflows } = useWorkflowRegistry()
148-
const { isPublished, setPublishStatus } = useWorkflowStore()
146+
const { activeWorkflowId, workflows, updateWorkflow } = useWorkflowRegistry()
147+
148+
// Get marketplace data from the registry
149+
const getMarketplaceData = () => {
150+
if (!activeWorkflowId || !workflows[activeWorkflowId]) return null
151+
return workflows[activeWorkflowId].marketplaceData
152+
}
153+
154+
// Check if workflow is published to marketplace
155+
const isPublished = () => {
156+
return !!getMarketplaceData()
157+
}
158+
159+
// Check if the current user is the owner of the published workflow
160+
const isOwner = () => {
161+
const marketplaceData = getMarketplaceData()
162+
return marketplaceData?.status === 'owner'
163+
}
149164

150165
// Initialize form with react-hook-form
151166
const form = useForm<MarketplaceFormValues>({
@@ -161,7 +176,7 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps)
161176
// Fetch marketplace information when the modal opens and the workflow is published
162177
useEffect(() => {
163178
async function fetchMarketplaceInfo() {
164-
if (!open || !activeWorkflowId || !isPublished) {
179+
if (!open || !activeWorkflowId || !isPublished()) {
165180
setMarketplaceInfo(null)
166181
return
167182
}
@@ -185,16 +200,16 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps)
185200
}
186201

187202
fetchMarketplaceInfo()
188-
}, [open, activeWorkflowId, isPublished, addNotification])
203+
}, [open, activeWorkflowId, addNotification])
189204

190205
// Update form values when the active workflow changes or modal opens
191206
useEffect(() => {
192-
if (open && activeWorkflowId && workflows[activeWorkflowId] && !isPublished) {
207+
if (open && activeWorkflowId && workflows[activeWorkflowId] && !isPublished()) {
193208
const workflow = workflows[activeWorkflowId]
194209
form.setValue('name', workflow.name)
195210
form.setValue('description', workflow.description || '')
196211
}
197-
}, [open, activeWorkflowId, workflows, form, isPublished])
212+
}, [open, activeWorkflowId, workflows, form])
198213

199214
// Listen for the custom event to open the marketplace modal
200215
useEffect(() => {
@@ -255,8 +270,10 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps)
255270
throw new Error(errorData.error || 'Failed to publish workflow')
256271
}
257272

258-
// Update the publish status with the current date
259-
setPublishStatus(true)
273+
// Update the marketplace data in the workflow registry
274+
updateWorkflow(activeWorkflowId, {
275+
marketplaceData: { id: activeWorkflowId, status: 'owner' },
276+
})
260277

261278
// Add a marketplace notification with detailed information
262279
addNotification(
@@ -293,8 +310,10 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps)
293310
throw new Error(errorData.error || 'Failed to unpublish workflow')
294311
}
295312

296-
// Update the publish status
297-
setPublishStatus(false)
313+
// Remove the marketplace data from the workflow registry
314+
updateWorkflow(activeWorkflowId, {
315+
marketplaceData: null,
316+
})
298317

299318
// Add a notification
300319
addNotification(
@@ -402,26 +421,20 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps)
402421
</div>
403422
</div>
404423

405-
{/* Action buttons */}
406-
<div className="flex justify-end gap-2 pt-2">
407-
<Button
408-
type="button"
409-
variant="destructive"
410-
onClick={handleUnpublish}
411-
disabled={isUnpublishing}
412-
className="gap-2"
413-
>
414-
<Trash className="h-4 w-4" />
415-
{isUnpublishing ? (
416-
<span className="flex items-center gap-1.5">
417-
<span className="h-2 w-2 animate-pulse rounded-full bg-background"></span>
418-
Unpublishing...
419-
</span>
420-
) : (
421-
'Unpublish'
422-
)}
423-
</Button>
424-
</div>
424+
{/* Action buttons - Only show unpublish if owner */}
425+
{isOwner() && (
426+
<div className="flex justify-end gap-2 pt-2">
427+
<Button
428+
type="button"
429+
variant="destructive"
430+
onClick={handleUnpublish}
431+
disabled={isUnpublishing}
432+
className="gap-2"
433+
>
434+
{isUnpublishing ? 'Unpublishing...' : 'Unpublish'}
435+
</Button>
436+
</div>
437+
)}
425438
</div>
426439
)
427440
}
@@ -551,7 +564,7 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps)
551564
<DialogHeader className="px-6 py-4 border-b">
552565
<div className="flex items-center justify-between">
553566
<DialogTitle className="text-lg font-medium">
554-
{isPublished ? 'Marketplace Information' : 'Publish to Marketplace'}
567+
{isPublished() ? 'Marketplace Information' : 'Publish to Marketplace'}
555568
</DialogTitle>
556569
<Button
557570
variant="ghost"
@@ -566,7 +579,7 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps)
566579
</DialogHeader>
567580

568581
<div className="pt-4 px-6 pb-6 overflow-y-auto">
569-
{isPublished ? renderMarketplaceInfo() : renderPublishForm()}
582+
{isPublished() ? renderMarketplaceInfo() : renderPublishForm()}
570583
</div>
571584
</DialogContent>
572585
</Dialog>

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

Lines changed: 57 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,8 @@ export function ControlBar() {
6565
// Store hooks
6666
const { notifications, getWorkflowNotifications, addNotification, showNotification } =
6767
useNotificationStore()
68-
const {
69-
history,
70-
revertToHistoryState,
71-
lastSaved,
72-
isDeployed,
73-
isPublished,
74-
setDeploymentStatus,
75-
setPublishStatus,
76-
} = useWorkflowStore()
68+
const { history, revertToHistoryState, lastSaved, isDeployed, setDeploymentStatus } =
69+
useWorkflowStore()
7770
const { workflows, updateWorkflow, activeWorkflowId, removeWorkflow } = useWorkflowRegistry()
7871
const { isExecuting, handleRunWorkflow } = useWorkflowExecution()
7972

@@ -112,6 +105,24 @@ export function ControlBar() {
112105
? getWorkflowNotifications(activeWorkflowId)
113106
: notifications // Show all if no workflow is active
114107

108+
// Get the marketplace data from the workflow registry if available
109+
const getMarketplaceData = () => {
110+
if (!activeWorkflowId || !workflows[activeWorkflowId]) return null
111+
return workflows[activeWorkflowId].marketplaceData
112+
}
113+
114+
// Check if the current workflow is published to marketplace
115+
const isPublishedToMarketplace = () => {
116+
const marketplaceData = getMarketplaceData()
117+
return !!marketplaceData
118+
}
119+
120+
// Check if the current user is the owner of the published workflow
121+
const isWorkflowOwner = () => {
122+
const marketplaceData = getMarketplaceData()
123+
return marketplaceData?.status === 'owner'
124+
}
125+
115126
// Client-side only rendering for the timestamp
116127
useEffect(() => {
117128
setMounted(true)
@@ -149,14 +160,13 @@ export function ControlBar() {
149160
data.isDeployed,
150161
data.deployedAt ? new Date(data.deployedAt) : undefined
151162
)
152-
setPublishStatus(data.isPublished)
153163
}
154164
} catch (error) {
155165
logger.error('Failed to check workflow status:', { error })
156166
}
157167
}
158168
checkStatus()
159-
}, [activeWorkflowId, setDeploymentStatus, setPublishStatus])
169+
}, [activeWorkflowId, setDeploymentStatus])
160170

161171
/**
162172
* Workflow name handlers
@@ -364,6 +374,7 @@ export function ControlBar() {
364374
if (!activeWorkflowId) return
365375

366376
// If already published, show marketplace modal with info instead of notifications
377+
const isPublished = isPublishedToMarketplace()
367378
if (isPublished) {
368379
setIsMarketplaceModalOpen(true)
369380
return
@@ -488,7 +499,10 @@ export function ControlBar() {
488499
</AlertDialogHeader>
489500
<AlertDialogFooter>
490501
<AlertDialogCancel>Cancel</AlertDialogCancel>
491-
<AlertDialogAction onClick={handleDeleteWorkflow} className="bg-red-600 hover:bg-red-700">
502+
<AlertDialogAction
503+
onClick={handleDeleteWorkflow}
504+
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
505+
>
492506
Delete
493507
</AlertDialogAction>
494508
</AlertDialogFooter>
@@ -636,33 +650,37 @@ export function ControlBar() {
636650
/**
637651
* Render publish button
638652
*/
639-
const renderPublishButton = () => (
640-
<Tooltip>
641-
<TooltipTrigger asChild>
642-
<Button
643-
variant="ghost"
644-
size="icon"
645-
onClick={handlePublishWorkflow}
646-
disabled={isPublishing}
647-
className={cn('hover:text-[#802FFF]', isPublished && 'text-[#802FFF]')}
648-
>
649-
{isPublishing ? (
650-
<Loader2 className="h-5 w-5 animate-spin" />
651-
) : (
652-
<Store className="h-5 w-5" />
653-
)}
654-
<span className="sr-only">Publish to Marketplace</span>
655-
</Button>
656-
</TooltipTrigger>
657-
<TooltipContent>
658-
{isPublishing
659-
? 'Publishing...'
660-
: isPublished
661-
? 'Published to Marketplace'
662-
: 'Publish to Marketplace'}
663-
</TooltipContent>
664-
</Tooltip>
665-
)
653+
const renderPublishButton = () => {
654+
const isPublished = isPublishedToMarketplace()
655+
656+
return (
657+
<Tooltip>
658+
<TooltipTrigger asChild>
659+
<Button
660+
variant="ghost"
661+
size="icon"
662+
onClick={handlePublishWorkflow}
663+
disabled={isPublishing}
664+
className={cn('hover:text-[#802FFF]', isPublished && 'text-[#802FFF]')}
665+
>
666+
{isPublishing ? (
667+
<Loader2 className="h-5 w-5 animate-spin" />
668+
) : (
669+
<Store className="h-5 w-5" />
670+
)}
671+
<span className="sr-only">Publish to Marketplace</span>
672+
</Button>
673+
</TooltipTrigger>
674+
<TooltipContent>
675+
{isPublishing
676+
? 'Publishing...'
677+
: isPublished
678+
? 'Published to Marketplace'
679+
: 'Publish to Marketplace'}
680+
</TooltipContent>
681+
</Tooltip>
682+
)
683+
}
666684

667685
/**
668686
* Render debug mode controls

0 commit comments

Comments
 (0)