improvement(subblocks): fixed trigger save, schedule save, time inp, text subblocks and schedule/workflow badges, can now deploy from the badge itself (#1868)

This commit is contained in:
Waleed
2025-11-10 01:31:37 -08:00
committed by GitHub
parent d0720b85bc
commit 28b416078c
7 changed files with 259 additions and 253 deletions

View File

@@ -1,7 +1,7 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { AlertCircle, Check, Save, Trash2 } from 'lucide-react'
import { useParams } from 'next/navigation'
import { Tooltip } from '@/components/emcn/components/tooltip/tooltip'
import { Button } from '@/components/emcn/components'
import { Trash } from '@/components/emcn/icons/trash'
import { Alert, AlertDescription } from '@/components/ui/alert'
import {
AlertDialog,
@@ -13,8 +13,6 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { createLogger } from '@/lib/logs/console/logger'
import { parseCronToHumanReadable } from '@/lib/schedules/utils'
import { cn } from '@/lib/utils'
@@ -377,6 +375,7 @@ export function ScheduleSave({ blockId, isPreview = false, disabled = false }: S
<div className='mt-2'>
<div className='flex gap-2'>
<Button
variant='default'
onClick={handleSave}
disabled={disabled || isPreview || isSaving || saveStatus === 'saving' || isLoadingStatus}
className={cn(
@@ -391,37 +390,22 @@ export function ScheduleSave({ blockId, isPreview = false, disabled = false }: S
Saving...
</>
)}
{saveStatus === 'saved' && (
<>
<Check className='mr-2 h-4 w-4' />
Saved
</>
)}
{saveStatus === 'idle' && (
<>
<Save className='mr-2 h-4 w-4' />
{scheduleId ? 'Update Schedule' : 'Save Schedule'}
</>
)}
{saveStatus === 'error' && (
<>
<AlertCircle className='mr-2 h-4 w-4' />
Error
</>
)}
{saveStatus === 'saved' && 'Saved'}
{saveStatus === 'idle' && (scheduleId ? 'Update Schedule' : 'Save Schedule')}
{saveStatus === 'error' && 'Error'}
</Button>
{scheduleId && (
<Button
variant='default'
onClick={() => setShowDeleteDialog(true)}
disabled={disabled || isPreview || deleteStatus === 'deleting' || isSaving}
variant='outline'
className='h-9 rounded-[8px] px-3 text-destructive hover:bg-destructive/10'
className='h-9 rounded-[8px] px-3'
>
{deleteStatus === 'deleting' ? (
<div className='h-4 w-4 animate-spin rounded-full border-[1.5px] border-current border-t-transparent' />
) : (
<Trash2 className='h-4 w-4' />
<Trash className='h-[14px] w-[14px]' />
)}
</Button>
)}
@@ -442,54 +426,21 @@ export function ScheduleSave({ blockId, isPreview = false, disabled = false }: S
</div>
) : (
<>
<div className='flex items-center gap-2'>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Badge
variant='outline'
className={cn(
'flex cursor-pointer items-center gap-1 font-normal text-xs',
scheduleStatus === 'disabled'
? 'border-amber-200 bg-amber-50 text-amber-600 hover:bg-amber-100 dark:bg-amber-900/20 dark:text-amber-400'
: 'border-green-200 bg-green-50 text-green-600 hover:bg-green-100 dark:bg-green-900/20 dark:text-green-400'
)}
onClick={handleToggleStatus}
>
<div className='relative mr-0.5 flex items-center justify-center'>
<div
className={cn(
'absolute h-3 w-3 rounded-full',
scheduleStatus === 'disabled' ? 'bg-amber-500/20' : 'bg-green-500/20'
)}
/>
<div
className={cn(
'relative h-2 w-2 rounded-full',
scheduleStatus === 'disabled' ? 'bg-amber-500' : 'bg-green-500'
)}
/>
</div>
{scheduleStatus === 'active' ? 'Active' : 'Disabled'}
</Badge>
</Tooltip.Trigger>
<Tooltip.Content side='top' className='max-w-[300px]'>
{scheduleStatus === 'disabled' ? (
<p className='text-sm'>Click to reactivate this schedule</p>
) : (
<p className='text-sm'>Click to disable this schedule</p>
)}
</Tooltip.Content>
</Tooltip.Root>
{failedCount > 0 && (
{failedCount > 0 && (
<div className='flex items-center gap-2'>
<span className='text-destructive text-sm'>
{failedCount} failed run{failedCount !== 1 ? 's' : ''}
</span>
)}
</div>
</div>
)}
{savedCronExpression && (
<p className='text-muted-foreground text-sm'>
{parseCronToHumanReadable(savedCronExpression, scheduleTimezone || 'UTC')}
Runs{' '}
{parseCronToHumanReadable(
savedCronExpression,
scheduleTimezone || 'UTC'
).toLowerCase()}
</p>
)}

View File

@@ -27,10 +27,10 @@ export function Text({ blockId, subBlockId, content, className }: TextProps) {
return (
<div
id={`${blockId}-${subBlockId}`}
className={`rounded-md border bg-card p-4 shadow-sm ${className || ''}`}
className={`rounded-md border bg-[#232323] p-4 shadow-sm ${className || ''}`}
>
<div
className='prose prose-sm dark:prose-invert max-w-none text-sm [&_a]:text-blue-600 [&_a]:underline [&_a]:hover:text-blue-700 [&_a]:dark:text-blue-400 [&_a]:dark:hover:text-blue-300 [&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_code]:text-xs [&_strong]:font-semibold [&_ul]:ml-5 [&_ul]:list-disc'
className='prose prose-sm dark:prose-invert max-w-none break-words text-sm [&_a]:text-blue-600 [&_a]:underline [&_a]:hover:text-blue-700 [&_a]:dark:text-blue-400 [&_a]:dark:hover:text-blue-300 [&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_code]:text-xs [&_strong]:font-semibold [&_ul]:ml-5 [&_ul]:list-disc'
dangerouslySetInnerHTML={{ __html: content }}
/>
</div>
@@ -40,7 +40,7 @@ export function Text({ blockId, subBlockId, content, className }: TextProps) {
return (
<div
id={`${blockId}-${subBlockId}`}
className={`whitespace-pre-wrap rounded-md border bg-card p-4 text-muted-foreground text-sm shadow-sm ${className || ''}`}
className={`whitespace-pre-wrap break-words rounded-md border bg-[#232323] p-4 text-muted-foreground text-sm shadow-sm ${className || ''}`}
>
{content}
</div>

View File

@@ -1,10 +1,7 @@
'use client'
import * as React from 'react'
import { Clock } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { Button, Input, Popover, PopoverContent, PopoverTrigger } from '@/components/emcn'
import { cn } from '@/lib/utils'
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/hooks/use-sub-block-value'
@@ -91,18 +88,15 @@ export function TimeInput({
}}
>
<PopoverTrigger asChild>
<Button
variant='outline'
disabled={isPreview || disabled}
className={cn(
'w-full justify-start text-left font-normal',
!value && 'text-muted-foreground',
className
)}
>
<Clock className='mr-1 h-4 w-4' />
{value ? formatDisplayTime(value) : <span>{placeholder || 'Select time'}</span>}
</Button>
<div className='relative w-full cursor-pointer'>
<Input
readOnly
disabled={isPreview || disabled}
value={value ? formatDisplayTime(value) : ''}
placeholder={placeholder || 'Select time'}
className={cn('cursor-pointer', !value && 'text-muted-foreground', className)}
/>
</div>
</PopoverTrigger>
<PopoverContent className='w-auto p-4'>
<div className='flex items-center space-x-2'>
@@ -129,7 +123,7 @@ export function TimeInput({
}}
type='text'
/>
<span>:</span>
<span className='text-[#E6E6E6]'>:</span>
<Input
className='w-[4rem]'
value={minute}

View File

@@ -1,5 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { AlertCircle, Check, Copy, Save, Trash2 } from 'lucide-react'
import { Button } from '@/components/emcn/components'
import { Trash } from '@/components/emcn/icons/trash'
import { Alert, AlertDescription } from '@/components/ui/alert'
import {
AlertDialog,
@@ -11,8 +12,6 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { createLogger } from '@/lib/logs/console/logger'
import { cn } from '@/lib/utils'
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
@@ -21,6 +20,7 @@ import { useWebhookManagement } from '@/hooks/use-webhook-management'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import { getTrigger, isTriggerValid } from '@/triggers'
import { SYSTEM_SUBBLOCK_IDS } from '@/triggers/consts'
import { ShortInput } from '../short-input/short-input'
const logger = createLogger('TriggerSave')
@@ -45,10 +45,20 @@ export function TriggerSave({
const [errorMessage, setErrorMessage] = useState<string | null>(null)
const [deleteStatus, setDeleteStatus] = useState<'idle' | 'deleting'>('idle')
const [showDeleteDialog, setShowDeleteDialog] = useState(false)
const [testUrl, setTestUrl] = useState<string | null>(null)
const [testUrlExpiresAt, setTestUrlExpiresAt] = useState<string | null>(null)
const [isGeneratingTestUrl, setIsGeneratingTestUrl] = useState(false)
const [copied, setCopied] = useState<string | null>(null)
const storedTestUrl = useSubBlockStore((state) => state.getValue(blockId, 'testUrl'))
const storedTestUrlExpiresAt = useSubBlockStore((state) =>
state.getValue(blockId, 'testUrlExpiresAt')
)
const isTestUrlExpired = useMemo(() => {
if (!storedTestUrlExpiresAt) return true
return new Date(storedTestUrlExpiresAt) < new Date()
}, [storedTestUrlExpiresAt])
const testUrl = isTestUrlExpired ? null : (storedTestUrl as string | null)
const testUrlExpiresAt = isTestUrlExpired ? null : (storedTestUrlExpiresAt as string | null)
const effectiveTriggerId = useMemo(() => {
if (triggerId && isTriggerValid(triggerId)) {
@@ -203,6 +213,13 @@ export function TriggerSave({
validateRequiredFields,
])
useEffect(() => {
if (isTestUrlExpired && storedTestUrl) {
useSubBlockStore.getState().setValue(blockId, 'testUrl', null)
useSubBlockStore.getState().setValue(blockId, 'testUrlExpiresAt', null)
}
}, [blockId, isTestUrlExpired, storedTestUrl])
const handleSave = async () => {
if (isPreview || disabled) return
@@ -276,8 +293,10 @@ export function TriggerSave({
throw new Error(err?.error || 'Failed to generate test URL')
}
const json = await res.json()
setTestUrl(json.url)
setTestUrlExpiresAt(json.expiresAt)
useSubBlockStore.getState().setValue(blockId, 'testUrl', json.url)
useSubBlockStore.getState().setValue(blockId, 'testUrlExpiresAt', json.expiresAt)
collaborativeSetSubblockValue(blockId, 'testUrl', json.url)
collaborativeSetSubblockValue(blockId, 'testUrlExpiresAt', json.expiresAt)
} catch (e) {
logger.error('Failed to generate test webhook URL', { error: e })
setErrorMessage(
@@ -288,12 +307,6 @@ export function TriggerSave({
}
}
const copyToClipboard = (text: string, type: string): void => {
navigator.clipboard.writeText(text)
setCopied(type)
setTimeout(() => setCopied(null), 2000)
}
const handleDeleteClick = () => {
if (isPreview || disabled || !webhookId) return
setShowDeleteDialog(true)
@@ -311,12 +324,15 @@ export function TriggerSave({
setDeleteStatus('idle')
setSaveStatus('idle')
setErrorMessage(null)
setTestUrl(null)
setTestUrlExpiresAt(null)
useSubBlockStore.getState().setValue(blockId, 'testUrl', null)
useSubBlockStore.getState().setValue(blockId, 'testUrlExpiresAt', null)
collaborativeSetSubblockValue(blockId, 'triggerPath', '')
collaborativeSetSubblockValue(blockId, 'webhookId', null)
collaborativeSetSubblockValue(blockId, 'triggerConfig', null)
collaborativeSetSubblockValue(blockId, 'testUrl', null)
collaborativeSetSubblockValue(blockId, 'testUrlExpiresAt', null)
logger.info('Trigger configuration deleted successfully', {
blockId,
@@ -344,6 +360,7 @@ export function TriggerSave({
<div id={`${blockId}-${subBlockId}`}>
<div className='flex gap-2'>
<Button
variant='default'
onClick={handleSave}
disabled={disabled || isProcessing}
className={cn(
@@ -358,37 +375,22 @@ export function TriggerSave({
Saving...
</>
)}
{saveStatus === 'saved' && (
<>
<Check className='mr-2 h-4 w-4' />
Saved
</>
)}
{saveStatus === 'error' && (
<>
<AlertCircle className='mr-2 h-4 w-4' />
Error
</>
)}
{saveStatus === 'idle' && (
<>
<Save className='mr-2 h-4 w-4' />
{webhookId ? 'Update Configuration' : 'Save Configuration'}
</>
)}
{saveStatus === 'saved' && 'Saved'}
{saveStatus === 'error' && 'Error'}
{saveStatus === 'idle' && (webhookId ? 'Update Configuration' : 'Save Configuration')}
</Button>
{webhookId && (
<Button
variant='default'
onClick={handleDeleteClick}
disabled={disabled || isProcessing}
variant='outline'
className='h-9 rounded-[8px] px-3 text-destructive hover:bg-destructive/10'
className='h-9 rounded-[8px] px-3'
>
{deleteStatus === 'deleting' ? (
<div className='h-4 w-4 animate-spin rounded-full border-[1.5px] border-current border-t-transparent' />
) : (
<Trash2 className='h-4 w-4' />
<Trash className='h-[14px] w-[14px]' />
)}
</Button>
)}
@@ -406,7 +408,6 @@ export function TriggerSave({
<span className='font-medium text-sm'>Test Webhook URL</span>
<Button
variant='outline'
size='sm'
onClick={generateTestUrl}
disabled={isGeneratingTestUrl || isProcessing}
className='h-8 rounded-[8px]'
@@ -424,29 +425,21 @@ export function TriggerSave({
</Button>
</div>
{testUrl ? (
<div className='flex items-center gap-2'>
<Input
readOnly
value={testUrl}
className='h-9 flex-1 rounded-[8px] font-mono text-xs'
onClick={(e: React.MouseEvent<HTMLInputElement>) =>
(e.target as HTMLInputElement).select()
}
/>
<Button
type='button'
size='icon'
variant='outline'
className='h-9 w-9 rounded-[8px]'
onClick={() => copyToClipboard(testUrl, 'testUrl')}
>
{copied === 'testUrl' ? (
<Check className='h-4 w-4 text-green-500' />
) : (
<Copy className='h-4 w-4' />
)}
</Button>
</div>
<ShortInput
blockId={blockId}
subBlockId={`${subBlockId}-test-url`}
config={{
id: `${subBlockId}-test-url`,
type: 'short-input',
readOnly: true,
showCopyButton: true,
}}
value={testUrl}
readOnly={true}
showCopyButton={true}
disabled={isPreview || disabled}
isPreview={isPreview}
/>
) : (
<p className='text-muted-foreground text-xs'>
Generate a temporary URL that executes this webhook against the live (un-deployed)

View File

@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
/**
* Return type for the useChildDeployment hook
@@ -7,9 +7,11 @@ export interface UseChildDeploymentReturn {
/** The active version number of the child workflow */
activeVersion: number | null
/** Whether the child workflow has an active deployment */
isDeployed: boolean
isDeployed: boolean | null
/** Whether the deployment information is currently being fetched */
isLoading: boolean
/** Function to manually refetch deployment status */
refetch: () => void
}
/**
@@ -20,67 +22,73 @@ export interface UseChildDeploymentReturn {
*/
export function useChildDeployment(childWorkflowId: string | undefined): UseChildDeploymentReturn {
const [activeVersion, setActiveVersion] = useState<number | null>(null)
const [isDeployed, setIsDeployed] = useState<boolean>(false)
const [isDeployed, setIsDeployed] = useState<boolean | null>(null)
const [isLoading, setIsLoading] = useState(false)
const [refetchTrigger, setRefetchTrigger] = useState(0)
useEffect(() => {
const fetchActiveVersion = useCallback(async (wfId: string) => {
let cancelled = false
const fetchActiveVersion = async (wfId: string) => {
try {
setIsLoading(true)
const res = await fetch(`/api/workflows/${wfId}/deployments`, {
cache: 'no-store',
headers: { 'Cache-Control': 'no-cache' },
})
try {
setIsLoading(true)
const res = await fetch(`/api/workflows/${wfId}/deployments`, {
cache: 'no-store',
headers: { 'Cache-Control': 'no-cache' },
})
if (!res.ok) {
if (!cancelled) {
setActiveVersion(null)
setIsDeployed(false)
}
return
}
const json = await res.json()
const versions = Array.isArray(json?.data?.versions)
? json.data.versions
: Array.isArray(json?.versions)
? json.versions
: []
const active = versions.find((v: any) => v.isActive)
if (!cancelled) {
const v = active ? Number(active.version) : null
setActiveVersion(v)
setIsDeployed(v != null)
}
} catch {
if (!res.ok) {
if (!cancelled) {
setActiveVersion(null)
setIsDeployed(false)
setIsDeployed(null)
}
} finally {
if (!cancelled) setIsLoading(false)
return
}
}
if (childWorkflowId) {
void fetchActiveVersion(childWorkflowId)
} else {
setActiveVersion(null)
setIsDeployed(false)
const json = await res.json()
const versions = Array.isArray(json?.data?.versions)
? json.data.versions
: Array.isArray(json?.versions)
? json.versions
: []
const active = versions.find((v: any) => v.isActive)
if (!cancelled) {
const v = active ? Number(active.version) : null
setActiveVersion(v)
setIsDeployed(v != null) // true if deployed, false if undeployed
}
} catch {
if (!cancelled) {
setActiveVersion(null)
setIsDeployed(null)
}
} finally {
if (!cancelled) setIsLoading(false)
}
return () => {
cancelled = true
}
}, [childWorkflowId])
}, [])
useEffect(() => {
if (childWorkflowId) {
void fetchActiveVersion(childWorkflowId)
} else {
setActiveVersion(null)
setIsDeployed(null)
}
}, [childWorkflowId, refetchTrigger, fetchActiveVersion])
const refetch = useCallback(() => {
setRefetchTrigger((prev) => prev + 1)
}, [])
return {
activeVersion,
isDeployed,
isLoading,
refetch,
}
}

View File

@@ -11,9 +11,11 @@ export interface UseChildWorkflowReturn {
/** The active version of the child workflow */
childActiveVersion: number | null
/** Whether the child workflow is deployed */
childIsDeployed: boolean
childIsDeployed: boolean | null
/** Whether the child version information is loading */
isLoadingChildVersion: boolean
/** Function to manually refetch deployment status */
refetchDeployment: () => void
}
/**
@@ -53,6 +55,7 @@ export function useChildWorkflow(
activeVersion: childActiveVersion,
isDeployed: childIsDeployed,
isLoading: isLoadingChildVersion,
refetch: refetchDeployment,
} = useChildDeployment(isWorkflowSelector ? childWorkflowId : undefined)
return {
@@ -60,5 +63,6 @@ export function useChildWorkflow(
childActiveVersion,
childIsDeployed,
isLoadingChildVersion,
refetchDeployment,
}
}

View File

@@ -1,4 +1,4 @@
import { memo, useCallback, useEffect, useMemo, useRef } from 'react'
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useParams } from 'next/navigation'
import { Handle, type NodeProps, Position, useUpdateNodeInternals } from 'reactflow'
import { Badge } from '@/components/emcn/components/badge/badge'
@@ -212,13 +212,57 @@ export const WorkflowBlock = memo(function WorkflowBlock({
disableSchedule,
} = useScheduleInfo(id, type, currentWorkflowId)
const { childWorkflowId, childIsDeployed } = useChildWorkflow(
const { childWorkflowId, childIsDeployed, refetchDeployment } = useChildWorkflow(
id,
type,
data.isPreview ?? false,
data.subBlockValues
)
const [isDeploying, setIsDeploying] = useState(false)
const setDeploymentStatus = useWorkflowRegistry((state) => state.setDeploymentStatus)
const deployWorkflow = useCallback(
async (workflowId: string) => {
if (isDeploying) return
try {
setIsDeploying(true)
const response = await fetch(`/api/workflows/${workflowId}/deploy`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
deployChatEnabled: false,
}),
})
if (response.ok) {
const responseData = await response.json()
const isDeployedStatus = responseData.isDeployed ?? false
const deployedAtTime = responseData.deployedAt
? new Date(responseData.deployedAt)
: undefined
setDeploymentStatus(
workflowId,
isDeployedStatus,
deployedAtTime,
responseData.apiKey || ''
)
refetchDeployment()
} else {
logger.error('Failed to deploy workflow')
}
} catch (error) {
logger.error('Error deploying workflow:', error)
} finally {
setIsDeploying(false)
}
},
[isDeploying, setDeploymentStatus, refetchDeployment]
)
const { collaborativeSetSubblockValue } = useCollaborativeWorkflow()
/**
@@ -604,68 +648,80 @@ export const WorkflowBlock = memo(function WorkflowBlock({
</div>
<div className='flex flex-shrink-0 items-center gap-2'>
{isWorkflowSelector && childWorkflowId && (
<Badge
variant='outline'
style={{
borderColor: childIsDeployed ? '#22C55E' : '#EF4444',
color: childIsDeployed ? '#22C55E' : '#EF4444',
}}
>
{childIsDeployed ? 'deployed' : 'undeployed'}
</Badge>
)}
{!isEnabled && <Badge>Disabled</Badge>}
{shouldShowScheduleBadge && (
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Badge
variant='outline'
className='cursor-pointer'
style={{
borderColor: scheduleInfo?.isDisabled ? '#FF6600' : '#22C55E',
color: scheduleInfo?.isDisabled ? '#FF6600' : '#22C55E',
}}
onClick={(e) => {
e.stopPropagation()
if (scheduleInfo?.id) {
if (scheduleInfo.isDisabled) {
reactivateSchedule(scheduleInfo.id)
} else {
disableSchedule(scheduleInfo.id)
}
}
}}
>
<div className='relative flex items-center justify-center'>
<div
className='absolute h-3 w-3 rounded-full'
<>
{typeof childIsDeployed === 'boolean' ? (
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Badge
variant='outline'
className={!childIsDeployed ? 'cursor-pointer' : ''}
style={{
backgroundColor: scheduleInfo?.isDisabled
? 'rgba(255, 102, 0, 0.2)'
: 'rgba(34, 197, 94, 0.2)',
borderColor: childIsDeployed ? '#22C55E' : '#EF4444',
color: childIsDeployed ? '#22C55E' : '#EF4444',
}}
/>
<div
className='relative h-2 w-2 rounded-full'
style={{
backgroundColor: scheduleInfo?.isDisabled ? '#FF6600' : '#22C55E',
onClick={(e) => {
e.stopPropagation()
if (!childIsDeployed && childWorkflowId && !isDeploying) {
deployWorkflow(childWorkflowId)
}
}}
/>
</div>
{scheduleInfo?.isDisabled ? 'Disabled' : 'Scheduled'}
>
{isDeploying ? 'Deploying...' : childIsDeployed ? 'deployed' : 'undeployed'}
</Badge>
</Tooltip.Trigger>
{!childIsDeployed && (
<Tooltip.Content>
<span className='text-sm'>Click to deploy</span>
</Tooltip.Content>
)}
</Tooltip.Root>
) : (
<Badge variant='outline' style={{ visibility: 'hidden' }}>
deployed
</Badge>
</Tooltip.Trigger>
<Tooltip.Content side='top' className='max-w-[300px] p-4'>
{scheduleInfo?.isDisabled ? (
<p className='text-sm'>
This schedule is currently disabled. Click the badge to reactivate it.
</p>
) : (
<p className='text-sm'>Click the badge to disable this schedule.</p>
)}
</Tooltip.Content>
</Tooltip.Root>
)}
</>
)}
{!isEnabled && <Badge>disabled</Badge>}
{type === 'schedule' && (
<>
{shouldShowScheduleBadge ? (
<Tooltip.Root>
<Tooltip.Trigger asChild>
<Badge
variant='outline'
className={scheduleInfo?.isDisabled ? 'cursor-pointer' : ''}
style={{
borderColor: scheduleInfo?.isDisabled ? '#FF6600' : '#22C55E',
color: scheduleInfo?.isDisabled ? '#FF6600' : '#22C55E',
}}
onClick={(e) => {
e.stopPropagation()
if (scheduleInfo?.id) {
if (scheduleInfo.isDisabled) {
reactivateSchedule(scheduleInfo.id)
} else {
disableSchedule(scheduleInfo.id)
}
}
}}
>
{scheduleInfo?.isDisabled ? 'disabled' : 'scheduled'}
</Badge>
</Tooltip.Trigger>
{scheduleInfo?.isDisabled && (
<Tooltip.Content>
<span className='text-sm'>Click to reactivate</span>
</Tooltip.Content>
)}
</Tooltip.Root>
) : (
<Badge variant='outline' style={{ visibility: 'hidden' }}>
scheduled
</Badge>
)}
</>
)}
{showWebhookIndicator && (