Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import ReactFlow, {
useReactFlow,
} from 'reactflow'
import 'reactflow/dist/style.css'
import { Loader2 } from 'lucide-react'
import { createLogger } from '@/lib/logs/console/logger'
import { TriggerUtils } from '@/lib/workflows/triggers/triggers'
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
Expand Down Expand Up @@ -2187,7 +2188,11 @@ const WorkflowContent = React.memo(() => {
return (
<div className='flex h-screen w-full flex-col overflow-hidden'>
<div className='relative h-full w-full flex-1 transition-all duration-200'>
<div className='workflow-container h-full' />
<div className='workflow-container flex h-full items-center justify-center'>
<div className='flex flex-col items-center gap-3'>
<Loader2 className='h-[24px] w-[24px] animate-spin text-muted-foreground' />
</div>
</div>
</div>
<Panel />
<Terminal />
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
getUsage,
} from '@/lib/billing/client/utils'
import { createLogger } from '@/lib/logs/console/logger'
import { RotatingDigit } from '@/app/workspace/[workspaceId]/w/components/sidebar/components-new/usage-indicator/rotating-digit'
import { useSocket } from '@/app/workspace/providers/socket-provider'
import { subscriptionKeys, useSubscriptionData } from '@/hooks/queries/subscription'
import { MIN_SIDEBAR_WIDTH, useSidebarStore } from '@/stores/sidebar/store'
Expand Down Expand Up @@ -272,15 +271,9 @@ export function UsageIndicator({ onClick }: UsageIndicatorProps) {
</>
) : (
<>
<div className='flex items-center font-medium text-[12px] text-[var(--text-tertiary)]'>
<span className='mr-[1px]'>$</span>
<RotatingDigit
value={usage.current}
height={14}
width={7}
textClassName='font-medium text-[12px] text-[var(--text-tertiary)] tabular-nums'
/>
</div>
<span className='font-medium text-[12px] text-[var(--text-tertiary)] tabular-nums'>
${usage.current}
</span>
<span className='font-medium text-[12px] text-[var(--text-tertiary)]'>/</span>
<span className='font-medium text-[12px] text-[var(--text-tertiary)] tabular-nums'>
${usage.limit}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import { useDeleteFolder, useDuplicateFolder } from '@/app/workspace/[workspaceI
import { useUpdateFolder } from '@/hooks/queries/folders'
import { useCreateWorkflow } from '@/hooks/queries/workflows'
import type { FolderTreeNode } from '@/stores/folders/store'
import {
generateCreativeWorkflowName,
getNextWorkflowColor,
} from '@/stores/workflows/registry/utils'

interface FolderItemProps {
folder: FolderTreeNode
Expand Down Expand Up @@ -60,12 +64,23 @@ export function FolderItem({ folder, level, hoverHandlers }: FolderItemProps) {
})

/**
* Handle create workflow in folder using React Query mutation
* Handle create workflow in folder using React Query mutation.
* Generates name and color upfront for optimistic UI updates.
*/
const handleCreateWorkflowInFolder = useCallback(async () => {
if (createWorkflowMutation.isPending) {
return
}

// Generate name and color upfront for optimistic updates
const name = generateCreativeWorkflowName()
const color = getNextWorkflowColor()

const result = await createWorkflowMutation.mutateAsync({
workspaceId,
folderId: folder.id,
name,
color,
})

if (result.id) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useState } from 'react'
import { useCallback } from 'react'
import { createLogger } from '@/lib/logs/console/logger'
import { generateFolderName } from '@/lib/workspaces/naming'
import { useCreateFolder } from '@/hooks/queries/folders'
Expand All @@ -12,40 +12,39 @@ interface UseFolderOperationsProps {
/**
* Custom hook to manage folder operations including creating folders.
* Handles folder name generation and state management.
* Uses React Query mutation's isPending state for immediate loading feedback.
*
* @param props - Configuration object containing workspaceId
* @returns Folder operations state and handlers
*/
export function useFolderOperations({ workspaceId }: UseFolderOperationsProps) {
const createFolderMutation = useCreateFolder()
const [isCreatingFolder, setIsCreatingFolder] = useState(false)

/**
* Create folder handler - creates folder with auto-generated name
* Create folder handler - creates folder with auto-generated name.
* Generates name upfront to enable optimistic UI updates.
*/
const handleCreateFolder = useCallback(async (): Promise<string | null> => {
if (isCreatingFolder || !workspaceId) {
if (createFolderMutation.isPending || !workspaceId) {
logger.info('Folder creation already in progress or no workspaceId available')
return null
}

try {
setIsCreatingFolder(true)
// Generate folder name upfront for optimistic updates
const folderName = await generateFolderName(workspaceId)
const folder = await createFolderMutation.mutateAsync({ name: folderName, workspaceId })
logger.info(`Created folder: ${folderName}`)
return folder.id
} catch (error) {
logger.error('Failed to create folder:', { error })
return null
} finally {
setIsCreatingFolder(false)
}
}, [createFolderMutation, workspaceId, isCreatingFolder])
}, [createFolderMutation, workspaceId])

return {
// State
isCreatingFolder,
isCreatingFolder: createFolderMutation.isPending,

// Operations
handleCreateFolder,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { useCallback, useState } from 'react'
import { useCallback } from 'react'
import { useRouter } from 'next/navigation'
import { createLogger } from '@/lib/logs/console/logger'
import { useCreateWorkflow, useWorkflows } from '@/hooks/queries/workflows'
import { useWorkflowDiffStore } from '@/stores/workflow-diff/store'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import {
generateCreativeWorkflowName,
getNextWorkflowColor,
} from '@/stores/workflows/registry/utils'

const logger = createLogger('useWorkflowOperations')

Expand All @@ -29,7 +33,6 @@ export function useWorkflowOperations({
const { workflows } = useWorkflowRegistry()
const workflowsQuery = useWorkflows(workspaceId)
const createWorkflowMutation = useCreateWorkflow()
const [isCreatingWorkflow, setIsCreatingWorkflow] = useState(false)

/**
* Filter and sort workflows for the current workspace
Expand All @@ -42,25 +45,30 @@ export function useWorkflowOperations({
})

/**
* Create workflow handler - creates workflow and navigates to it
* Now uses React Query mutation for better performance and caching
* Create workflow handler - creates workflow and navigates to it.
* Uses React Query mutation's isPending state for immediate loading feedback.
* Generates name and color upfront to enable optimistic UI updates.
*/
const handleCreateWorkflow = useCallback(async (): Promise<string | null> => {
if (isCreatingWorkflow) {
if (createWorkflowMutation.isPending) {
logger.info('Workflow creation already in progress, ignoring request')
return null
}

try {
setIsCreatingWorkflow(true)

// Clear workflow diff store when creating a new workflow
const { clearDiff } = useWorkflowDiffStore.getState()
clearDiff()

// Use React Query mutation for creation
// Generate name and color upfront for optimistic updates
const name = generateCreativeWorkflowName()
const color = getNextWorkflowColor()

// Use React Query mutation for creation - isPending updates immediately
const result = await createWorkflowMutation.mutateAsync({
workspaceId: workspaceId,
workspaceId,
name,
color,
})

// Navigate to the newly created workflow
Expand All @@ -72,17 +80,15 @@ export function useWorkflowOperations({
} catch (error) {
logger.error('Error creating workflow:', error)
return null
} finally {
setIsCreatingWorkflow(false)
}
}, [isCreatingWorkflow, createWorkflowMutation, workspaceId, router])
}, [createWorkflowMutation, workspaceId, router])

return {
// State
workflows,
regularWorkflows,
workflowsLoading: workflowsQuery.isLoading,
isCreatingWorkflow,
isCreatingWorkflow: createWorkflowMutation.isPending,

// Operations
handleCreateWorkflow,
Expand Down
62 changes: 61 additions & 1 deletion apps/sim/hooks/queries/folders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ interface CreateFolderVariables {
color?: string
}

interface CreateFolderContext {
tempId: string
previousFolders: Record<string, WorkflowFolder>
}

interface UpdateFolderVariables {
workspaceId: string
id: string
Expand Down Expand Up @@ -103,7 +108,62 @@ export function useCreateFolder() {
const { folder } = await response.json()
return mapFolder(folder)
},
onSuccess: (_data, variables) => {
onMutate: async (variables): Promise<CreateFolderContext> => {
// Cancel any outgoing refetches to prevent race conditions
await queryClient.cancelQueries({ queryKey: folderKeys.list(variables.workspaceId) })

// Snapshot previous state for rollback
const previousFolders = { ...useFolderStore.getState().folders }

const tempId = `temp-folder-${Date.now()}`

// Optimistically add folder entry immediately
useFolderStore.setState((state) => ({
folders: {
...state.folders,
[tempId]: {
id: tempId,
name: variables.name,
userId: '',
workspaceId: variables.workspaceId,
parentId: variables.parentId || null,
color: variables.color || '#808080',
isExpanded: false,
sortOrder: 0,
createdAt: new Date(),
updatedAt: new Date(),
},
},
}))

logger.info(`Added optimistic folder entry: ${tempId}`)
return { tempId, previousFolders }
},
onSuccess: (data, _variables, context) => {
logger.info(`Folder ${data.id} created successfully, replacing temp entry ${context.tempId}`)

// Replace optimistic entry with real folder data
useFolderStore.setState((state) => {
const { [context.tempId]: _, ...remainingFolders } = state.folders
return {
folders: {
...remainingFolders,
[data.id]: data,
},
}
})
},
onError: (error: Error, _variables, context) => {
logger.error('Failed to create folder:', error)

// Rollback to previous state snapshot
if (context?.previousFolders) {
useFolderStore.setState({ folders: context.previousFolders })
logger.info(`Rolled back to previous folders state`)
}
},
onSettled: (_data, _error, variables) => {
// Always invalidate to sync with server state
queryClient.invalidateQueries({ queryKey: folderKeys.list(variables.workspaceId) })
},
})
Expand Down
Loading