diff --git a/apps/platform/src/features/flow-editor/components/schema-field-renderer.tsx b/apps/platform/src/features/flow-editor/components/schema-field-renderer.tsx
index a5eac32..1dde804 100644
--- a/apps/platform/src/features/flow-editor/components/schema-field-renderer.tsx
+++ b/apps/platform/src/features/flow-editor/components/schema-field-renderer.tsx
@@ -49,15 +49,25 @@ const isMultilineFormat = (format?: string) =>
format === 'multiline' || format === 'textarea' || format === 'multi-line'
const inputTypeFor = (field: SchemaField) => {
- if (field.writeOnly) return 'password'
- if (field.format === 'email') return 'email'
- if (field.format === 'uri' || field.format === 'url') return 'url'
+ if (field.writeOnly) {
+ return 'password'
+ }
+ if (field.format === 'email') {
+ return 'email'
+ }
+ if (field.format === 'uri' || field.format === 'url') {
+ return 'url'
+ }
return 'text'
}
const stringifyComplex = (val: any) => {
- if (val === undefined || val === null) return ''
- if (typeof val === 'string') return val
+ if (val === undefined || val === null) {
+ return ''
+ }
+ if (typeof val === 'string') {
+ return val
+ }
try {
return JSON.stringify(val, null, 2)
} catch {
@@ -68,15 +78,23 @@ const stringifyComplex = (val: any) => {
const PRIMITIVE_ITEM_TYPES = new Set(['string', 'number', 'integer', 'boolean'])
const isPrimitiveItemSchema = (items: any) => {
- if (!items || typeof items !== 'object') return true
+ if (!items || typeof items !== 'object') {
+ return true
+ }
const type = items.type
- if (type === undefined) return true
+ if (type === undefined) {
+ return true
+ }
return PRIMITIVE_ITEM_TYPES.has(type)
}
const defaultForItemType = (type?: string) => {
- if (type === 'boolean') return false
- if (type === 'number' || type === 'integer') return 0
+ if (type === 'boolean') {
+ return false
+ }
+ if (type === 'number' || type === 'integer') {
+ return 0
+ }
return ''
}
@@ -222,7 +240,9 @@ export function SchemaFieldRenderer({
}: SchemaFieldRendererProps) {
const { t } = useTranslation()
- if (field.hidden) return null
+ if (field.hidden) {
+ return null
+ }
const labelText = t(field.title || field.key, field.title || field.key)
const isReadOnly = !!field.readOnly
diff --git a/apps/platform/src/features/flow-editor/hooks/use-flow-drag.ts b/apps/platform/src/features/flow-editor/hooks/use-flow-drag.ts
index 481868e..34117f3 100644
--- a/apps/platform/src/features/flow-editor/hooks/use-flow-drag.ts
+++ b/apps/platform/src/features/flow-editor/hooks/use-flow-drag.ts
@@ -2,6 +2,8 @@ import { useState, useRef } from 'react'
import { useReactFlow } from '@xyflow/react'
import type {
FlowNode,
+ IconSource,
+ NodeHandle,
NodeOutputType,
SchemaField,
SetFlowNodes,
@@ -17,6 +19,10 @@ interface DragItem {
name: string
description: string
category: string
+ icon?: IconSource
+ inputs?: NodeHandle[]
+ outputs?: NodeHandle[]
+ subCategory?: string
tags?: string[]
isComingSoon?: boolean
outputTypes?: NodeOutputType[]
@@ -55,11 +61,15 @@ export function useFlowDrag({
const pane = document.querySelector(
'.react-flow__pane.draggable',
) as HTMLElement
- if (pane) pane.style.cursor = 'grab'
+ if (pane) {
+ pane.style.cursor = 'grab'
+ }
document.removeEventListener('mousemove', onMouseMove)
document.removeEventListener('mouseup', onMouseUp)
- if (!dragData.current) return
+ if (!dragData.current) {
+ return
+ }
const position = screenToFlowPosition({
x: e.clientX + 100,
y: e.clientY,
@@ -72,6 +82,9 @@ export function useFlowDrag({
tags: dragData.current.tags,
title: dragData.current.name,
category: dragData.current.category,
+ icon: dragData.current.icon,
+ inputs: dragData.current.inputs,
+ outputs: dragData.current.outputs,
},
instruction: undefined,
parameters: {},
@@ -95,12 +108,16 @@ export function useFlowDrag({
const pane = document.querySelector(
'.react-flow__pane.draggable',
) as HTMLElement
- if (pane) pane.style.cursor = 'grab'
+ if (pane) {
+ pane.style.cursor = 'grab'
+ }
document.removeEventListener('mousemove', onMouseMove)
document.removeEventListener('mouseup', onMouseUpAnnotation)
- if (!dragData.current) return
+ if (!dragData.current) {
+ return
+ }
const position = screenToFlowPosition({
x: e.clientX + 100,
@@ -139,7 +156,9 @@ export function useFlowDrag({
const pane = document.querySelector(
'.react-flow__pane.draggable',
) as HTMLElement
- if (pane) pane.style.cursor = 'grabbing'
+ if (pane) {
+ pane.style.cursor = 'grabbing'
+ }
document.addEventListener('mousemove', onMouseMove)
document.addEventListener('mouseup', onMouseUp)
}
@@ -161,7 +180,9 @@ export function useFlowDrag({
const pane = document.querySelector(
'.react-flow__pane.draggable',
) as HTMLElement
- if (pane) pane.style.cursor = 'grabbing'
+ if (pane) {
+ pane.style.cursor = 'grabbing'
+ }
document.addEventListener('mousemove', onMouseMove)
document.addEventListener('mouseup', onMouseUpAnnotation)
diff --git a/apps/platform/src/features/flow-editor/hooks/use-flow-nodes.ts b/apps/platform/src/features/flow-editor/hooks/use-flow-nodes.ts
index 69654f9..9584fa1 100644
--- a/apps/platform/src/features/flow-editor/hooks/use-flow-nodes.ts
+++ b/apps/platform/src/features/flow-editor/hooks/use-flow-nodes.ts
@@ -2,11 +2,11 @@ import { useCallback, useEffect, useMemo, useRef } from 'react'
import useSWR from 'swr'
import nodeEngineService from '@/features/flow-editor/services/node-engine'
import { useTranslation } from 'react-i18next'
-import { useThemeStore } from '@/stores/theme-store'
import type {
ContextMenuItem,
FlowEdge,
FlowNode,
+ IconSource,
NodeEngineNode,
NodeOutputType,
RendererNode,
@@ -15,18 +15,13 @@ import type {
SchemaField,
} from '@/features/flow-editor/types'
-export const resolveIconKey = (icon: any, mode: 'light' | 'dark'): string => {
- if (!icon) return ''
- if (typeof icon === 'string') return icon
- if (mode === 'dark' && icon.dark) return icon.dark
- return icon.light ?? icon.dark ?? ''
-}
-
const normalizeKey = (value?: string) =>
(value ?? '').toLowerCase().replace(/\s+/g, '')
const inputSchemaToFields = (schema: any): SchemaField[] => {
- if (!schema?.properties) return []
+ if (!schema?.properties) {
+ return []
+ }
const required = new Set
(schema.required ?? [])
return Object.entries(schema.properties).map(
([key, value]: [string, any]) => ({
@@ -53,7 +48,9 @@ const collectOutputPaths = (
prefix: string,
acc: NodeOutputType[],
) => {
- if (!node || typeof node !== 'object') return
+ if (!node || typeof node !== 'object') {
+ return
+ }
if (node.type === 'object' && node.properties) {
for (const [key, value] of Object.entries(node.properties)) {
@@ -85,7 +82,9 @@ const collectOutputPaths = (
}
const outputSchemaToTypes = (schema: any): NodeOutputType[] => {
- if (!schema || typeof schema !== 'object') return []
+ if (!schema || typeof schema !== 'object') {
+ return []
+ }
const acc: NodeOutputType[] = []
collectOutputPaths(schema, '', acc)
return acc
@@ -95,13 +94,19 @@ const buildNodeList = (nodes: ResolvedNode[]): RendererNodeMap => {
const r: RendererNodeMap = {}
for (const item of nodes) {
const subCategory = item.subCategory ?? ''
- if (!r[item.category]) r[item.category] = {}
- if (!r[item.category][subCategory]) r[item.category][subCategory] = []
+ if (!r[item.category]) {
+ r[item.category] = {}
+ }
+ if (!r[item.category][subCategory]) {
+ r[item.category][subCategory] = []
+ }
const labels: string[] = []
if (item.schema && item.schema.length > 0) {
for (const field of item.schema) {
if (Array.isArray(field.enum)) {
- for (const opt of field.enum) labels.push(String(opt))
+ for (const opt of field.enum) {
+ labels.push(String(opt))
+ }
}
}
}
@@ -110,6 +115,9 @@ const buildNodeList = (nodes: ResolvedNode[]): RendererNodeMap => {
label: item.name,
description: item.description,
type: item.category,
+ icon: item.icon,
+ inputs: item.inputs,
+ outputs: item.outputs,
subCategory: item.subCategory,
actions: labels,
tags: item.tags,
@@ -120,11 +128,45 @@ const buildNodeList = (nodes: ResolvedNode[]): RendererNodeMap => {
return r
}
+const buildProviderList = (nodes: ResolvedNode[]) => {
+ const groups = new Map<
+ string,
+ { key: string; label: string; icon: IconSource; nodes: RendererNode[] }
+ >()
+
+ for (const item of nodes) {
+ const label = item.provider || item.subCategory || ''
+ const key = item.subCategory || normalizeKey(label)
+ if (!groups.has(key)) {
+ groups.set(key, {
+ key,
+ label,
+ icon: { brand: item.icon?.brand },
+ nodes: [],
+ })
+ }
+ groups.get(key)!.nodes.push({
+ id: item.id,
+ label: item.name,
+ description: item.description,
+ type: item.category,
+ icon: item.icon,
+ inputs: item.inputs,
+ outputs: item.outputs,
+ subCategory: item.subCategory,
+ actions: [],
+ tags: item.tags,
+ isComingSoon: item.isComingSoon ?? false,
+ })
+ }
+
+ return [...groups.values()].sort((a, b) => a.label.localeCompare(b.label))
+}
+
const NODES_KEY = 'node-engine-nodes'
const TRIGGER_VARIABLES_KEY = 'node-engine-trigger-variables'
export function useNodeEngineNodes() {
- const mode = useThemeStore((s) => s.getMode())
const { data, isLoading } = useSWR(NODES_KEY, async () => {
const response = await nodeEngineService.getNodes()
return (response.data as NodeEngineNode[]) || []
@@ -136,9 +178,12 @@ export function useNodeEngineNodes() {
id: item.id ?? '',
name: item.name ?? '',
description: item.description ?? '',
- icon: resolveIconKey(item.icon, mode),
+ icon: item.icon,
+ inputs: item.inputs,
+ outputs: item.outputs,
category: normalizeKey(item.categories?.[0]),
subCategory: normalizeKey(item.subCategories?.[0]),
+ provider: item.subCategories?.[0] ?? '',
tags: item.tags ?? [],
supportedCredentials: item.supportedCredentials,
schema: inputSchemaToFields(item.inputSchema),
@@ -147,11 +192,12 @@ export function useNodeEngineNodes() {
isComingSoon: item.isComingSoon,
hasNaturalLanguage: item.hasNaturalLanguage,
})),
- [data, mode],
+ [data],
)
const nodeList = useMemo(() => buildNodeList(apiNodes), [apiNodes])
+ const providerList = useMemo(() => buildProviderList(apiNodes), [apiNodes])
- return { apiNodes, nodeList, isLoading }
+ return { apiNodes, nodeList, providerList, isLoading }
}
export function useTriggerVariables() {
@@ -159,7 +205,9 @@ export function useTriggerVariables() {
TRIGGER_VARIABLES_KEY,
async () => {
const response = await nodeEngineService.getTriggerVariables()
- if (response.isSuccess && response.data) return response.data
+ if (response.isSuccess && response.data) {
+ return response.data
+ }
return []
},
)
@@ -195,7 +243,9 @@ export function useFlowNodes(apiNodes: ResolvedNode[] = []) {
from?: SourceRef,
apiNodesArg?: ResolvedNode[],
): ContextMenuItem[] | undefined => {
- if (node.id === '0') return undefined
+ if (node.id === '0') {
+ return undefined
+ }
let contextMenu: ContextMenuItem[] = []
const ref = node.id
@@ -204,7 +254,9 @@ export function useFlowNodes(apiNodes: ResolvedNode[] = []) {
const apiNode = apiOriginRef.current.find(
(n) => n.id === sourceNode.nodeId,
)
- if (apiNode === undefined) return contextMenu
+ if (apiNode === undefined) {
+ return contextMenu
+ }
const outputs = apiNode.outputTypes ?? []
for (let j = 0; j < outputs.length; j++) {
@@ -237,15 +289,21 @@ export function useFlowNodes(apiNodes: ResolvedNode[] = []) {
const edges = source.edges.filter((e) => e.target === ref)
for (let i = 0; i < edges.length; i++) {
const edge = edges[i]
- if (edge.source === '0') continue
+ if (edge.source === '0') {
+ continue
+ }
const sourceNode = source.nodes.find((n) => n.id === edge.source)
- if (!sourceNode) continue
+ if (!sourceNode) {
+ continue
+ }
const currentApiNodes = apiNodesArg || apiOriginRef.current
const apiNode = currentApiNodes?.find(
(n) => n.id === sourceNode.nodeId,
)
- if (apiNode === undefined) continue
+ if (apiNode === undefined) {
+ continue
+ }
const sourceContextMenu = sourceNode.data?.contextMenu
if (sourceContextMenu && sourceContextMenu.length > 0) {
contextMenu = contextMenu.concat(sourceContextMenu)
diff --git a/apps/platform/src/features/flow-editor/hooks/use-navigation-guard.ts b/apps/platform/src/features/flow-editor/hooks/use-navigation-guard.ts
index ffb1fef..0f28d70 100644
--- a/apps/platform/src/features/flow-editor/hooks/use-navigation-guard.ts
+++ b/apps/platform/src/features/flow-editor/hooks/use-navigation-guard.ts
@@ -35,8 +35,12 @@ export function useNavigationGuard({
// Link click and popstate interception
useEffect(() => {
- if (previewMode) return
- if (!hasUnsavedChanges) return
+ if (previewMode) {
+ return
+ }
+ if (!hasUnsavedChanges) {
+ return
+ }
const handleClick = (e: MouseEvent) => {
const link = (e.target as HTMLElement).closest('a[href]')
@@ -74,7 +78,9 @@ export function useNavigationGuard({
// beforeunload
useEffect(() => {
- if (previewMode) return
+ if (previewMode) {
+ return
+ }
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
if (hasUnsavedChanges) {
e.preventDefault()
diff --git a/apps/platform/src/features/flow-editor/icons/brand-icons.tsx b/apps/platform/src/features/flow-editor/icons/brand-icons.tsx
deleted file mode 100644
index 9fe2bea..0000000
--- a/apps/platform/src/features/flow-editor/icons/brand-icons.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-// Brand AI & Media Icons
-
-const AnthropicIcon = (props) => (
-
-)
-
-const ChatGptIcon = (props) => (
-
-)
-
-const DeepseekIcon = (props) => (
-
-)
-
-const GeminiIcon = (props) => (
-
-)
-
-const SunoIcon = (props) => (
-
-)
-
-const ElevenlabsIcon = (props) => (
-
-)
-
-const PiApiIcon = (props) => (
-
-)
-
-const CreatoMateIcon = (props) => (
-
-)
-
-const SoundCloudIcon = (props) => (
-
-)
-
-export {
- AnthropicIcon,
- ChatGptIcon,
- DeepseekIcon,
- GeminiIcon,
- SunoIcon,
- ElevenlabsIcon,
- PiApiIcon,
- CreatoMateIcon,
- SoundCloudIcon,
-}
diff --git a/apps/platform/src/features/flow-editor/icons/index.ts b/apps/platform/src/features/flow-editor/icons/index.ts
index db63d0f..e1f37cc 100644
--- a/apps/platform/src/features/flow-editor/icons/index.ts
+++ b/apps/platform/src/features/flow-editor/icons/index.ts
@@ -1,71 +1,2 @@
-import { Send } from 'lucide-react'
-
-import {
- AnthropicIcon,
- ChatGptIcon,
- DeepseekIcon,
- GeminiIcon,
- SunoIcon,
- ElevenlabsIcon,
- PiApiIcon,
- CreatoMateIcon,
- SoundCloudIcon,
-} from './brand-icons'
-
-import {
- XIcon,
- InstagramIcon,
- TiktokIcon,
- FacebookIcon,
- LinkedinIcon,
- YouTubeIcon,
- DiscordIcon,
- SlackIcon,
- WhatsAppIcon,
- TelegramIcon,
-} from './social-icons'
-
-import {
- GmailIcon,
- OutlookIcon,
- GoogleDriveIcon,
- GoogleSheetsIcon,
- GoogleDocsIcon,
- NotionIcon,
- DeepLIcon,
- CoinGeckoIcon,
-} from './productivity-icons'
-
export { directionIcons } from './direction-icons'
-
-export const secretIcons = {
- anthropic: AnthropicIcon,
- chatgpt: ChatGptIcon,
- deepseek: DeepseekIcon,
- gemini: GeminiIcon,
- gmail: GmailIcon,
- outlook: OutlookIcon,
- telegram: TelegramIcon,
- x: XIcon,
- instagram: InstagramIcon,
- tiktok: TiktokIcon,
- facebook: FacebookIcon,
- linkedin: LinkedinIcon,
- youtube: YouTubeIcon,
- google_drive: GoogleDriveIcon,
- google_sheets: GoogleSheetsIcon,
- google_docs: GoogleDocsIcon,
- sunomusic: SunoIcon,
- elevenlabs: ElevenlabsIcon,
- smtp: Send,
- piapi: PiApiIcon,
- creatomate: CreatoMateIcon,
- whatsapp: WhatsAppIcon,
- deepl: DeepLIcon,
- coingecko: CoinGeckoIcon,
- discord: DiscordIcon,
- slack: SlackIcon,
- notion: NotionIcon,
- soundcloud: SoundCloudIcon,
- veo: GeminiIcon,
-}
+export { useIconResolver, type ProviderIconComponent } from './provider-icons'
diff --git a/apps/platform/src/features/flow-editor/icons/productivity-icons.tsx b/apps/platform/src/features/flow-editor/icons/productivity-icons.tsx
deleted file mode 100644
index 61a9074..0000000
--- a/apps/platform/src/features/flow-editor/icons/productivity-icons.tsx
+++ /dev/null
@@ -1,313 +0,0 @@
-// Productivity & Service Icons
-
-const GmailIcon = (props) => (
-
-)
-
-const OutlookIcon = (props) => (
-
-)
-
-const GoogleDriveIcon = (props) => (
-
-)
-
-const GoogleSheetsIcon = (props) => (
-
-)
-
-const GoogleDocsIcon = (props) => (
-
-)
-
-const NotionIcon = (props) => (
-
-)
-
-const DeepLIcon = (props) => (
-
-)
-
-const CoinGeckoIcon = (props) => (
-
-)
-
-export {
- GmailIcon,
- OutlookIcon,
- GoogleDriveIcon,
- GoogleSheetsIcon,
- GoogleDocsIcon,
- NotionIcon,
- DeepLIcon,
- CoinGeckoIcon,
-}
diff --git a/apps/platform/src/features/flow-editor/icons/provider-icons.tsx b/apps/platform/src/features/flow-editor/icons/provider-icons.tsx
new file mode 100644
index 0000000..05f86f8
--- /dev/null
+++ b/apps/platform/src/features/flow-editor/icons/provider-icons.tsx
@@ -0,0 +1,119 @@
+import { useCallback } from 'react'
+import type { ReactNode } from 'react'
+
+import type { IconSource } from '@/features/flow-editor/types'
+import { cn } from '@/lib/utils'
+import { useThemeStore } from '@/stores/theme-store'
+
+const brandSource = (brand: string, mode: 'light' | 'dark') =>
+ `/assets/icons/brands/${brand}/${mode}.svg`
+const glyphSource = (glyph: string) => `/assets/icons/glyphs/${glyph}.svg`
+
+type IconProps = {
+ className?: string
+}
+
+export type ProviderIconComponent = (props: IconProps) => ReactNode
+
+const builtIcons = new Map()
+
+const artwork = (source: string, className?: string) => (
+
+)
+
+const badge = (glyph: string) => (
+
+
+
+)
+
+const buildIcon = (
+ icon: { brand?: string; glyph?: string },
+ mode: 'light' | 'dark',
+): ProviderIconComponent | undefined => {
+ if (icon.brand && icon.glyph) {
+ return ({ className }: IconProps) => (
+
+ {artwork(brandSource(icon.brand!, mode), 'size-[70%]')}
+ {badge(icon.glyph!)}
+
+ )
+ }
+
+ if (icon.brand) {
+ return ({ className }: IconProps) =>
+ artwork(
+ brandSource(icon.brand!, mode),
+ cn('inline-block shrink-0', className),
+ )
+ }
+
+ if (icon.glyph) {
+ return ({ className }: IconProps) => (
+
+ )
+ }
+
+ return undefined
+}
+
+export const useIconResolver = () => {
+ const mode = useThemeStore((state) => state.getMode())
+
+ return useCallback(
+ (icon: IconSource) => {
+ if (!icon) {
+ return undefined
+ }
+
+ const key = `${mode}:${icon.brand ?? ''}:${icon.glyph ?? ''}`
+ const existing = builtIcons.get(key)
+ if (existing) {
+ return existing
+ }
+
+ const built = buildIcon(icon, mode)
+ if (built) {
+ builtIcons.set(key, built)
+ }
+
+ return built
+ },
+ [mode],
+ )
+}
diff --git a/apps/platform/src/features/flow-editor/icons/social-icons.tsx b/apps/platform/src/features/flow-editor/icons/social-icons.tsx
deleted file mode 100644
index 4d8a400..0000000
--- a/apps/platform/src/features/flow-editor/icons/social-icons.tsx
+++ /dev/null
@@ -1,322 +0,0 @@
-// Social Media Icons
-
-const XIcon = (props) => (
-
-)
-
-const InstagramIcon = (props) => (
-
-)
-
-const TiktokIcon = (props) => (
-
-)
-
-const FacebookIcon = (props) => (
-
-)
-
-const LinkedinIcon = (props) => (
-
-)
-
-const YouTubeIcon = (props) => (
-
-)
-
-const DiscordIcon = (props) => (
-
-)
-
-const SlackIcon = (props) => (
-
-)
-
-const WhatsAppIcon = (props) => (
-
-)
-
-const TelegramIcon = (props) => (
-
-)
-
-export {
- XIcon,
- InstagramIcon,
- TiktokIcon,
- FacebookIcon,
- LinkedinIcon,
- YouTubeIcon,
- DiscordIcon,
- SlackIcon,
- WhatsAppIcon,
- TelegramIcon,
-}
diff --git a/apps/platform/src/features/flow-editor/nodes/annotation.tsx b/apps/platform/src/features/flow-editor/nodes/annotation.tsx
index d5393a3..ddc4899 100644
--- a/apps/platform/src/features/flow-editor/nodes/annotation.tsx
+++ b/apps/platform/src/features/flow-editor/nodes/annotation.tsx
@@ -1,15 +1,18 @@
-import { useState, useRef, useEffect } from 'react'
+import { useState, useRef, useEffect, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import {
AnnotationNode,
AnnotationNodeContent,
} from '@/features/flow-editor/nodes/annotation-node'
+import { useFlowSetNodes } from '@/features/flow-editor/contexts/flow-nodes-context'
-const Annotation = () => {
+const Annotation = ({ id, data }) => {
const { t } = useTranslation()
- const [content, setContent] = useState(t('ui.text.annotationPlaceholder'))
+ const setNodes = useFlowSetNodes()
const [isEditing, setIsEditing] = useState(false)
- const inputRef = useRef(null)
+ const inputRef = useRef(null)
+
+ const note = data?.note ?? ''
useEffect(() => {
if (isEditing && inputRef.current) {
@@ -17,20 +20,19 @@ const Annotation = () => {
}
}, [isEditing])
- const handleContentClick = () => {
- setIsEditing(true)
- }
-
- const handleInputChange = (e: React.ChangeEvent) => {
- setContent(e.target.value)
- }
-
- const handleInputBlur = () => {
- setIsEditing(false)
- }
+ const updateNote = useCallback(
+ (value: string) => {
+ setNodes((nodes) =>
+ nodes.map((n) =>
+ n.id === id ? { ...n, data: { ...n.data, note: value } } : n,
+ ),
+ )
+ },
+ [id, setNodes],
+ )
- const handleInputKeyDown = (e: React.KeyboardEvent) => {
- if (e.key === 'Enter') {
+ const handleInputKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === 'Escape') {
setIsEditing(false)
}
}
@@ -38,29 +40,22 @@ const Annotation = () => {
return (
setIsEditing(true)}
+ className="w-full cursor-text"
>
{isEditing ? (
diff --git a/apps/platform/src/features/flow-editor/nodes/core-node.tsx b/apps/platform/src/features/flow-editor/nodes/core-node.tsx
index 9e5a30c..d82473c 100644
--- a/apps/platform/src/features/flow-editor/nodes/core-node.tsx
+++ b/apps/platform/src/features/flow-editor/nodes/core-node.tsx
@@ -1,18 +1,14 @@
import { memo, useState, useMemo, useCallback, useEffect } from 'react'
-import { Handle, Position, useUpdateNodeInternals } from '@xyflow/react'
+import { Position, useUpdateNodeInternals } from '@xyflow/react'
import {
useFlowSetNodes,
useFlowSetEdges,
} from '@/features/flow-editor/contexts/flow-nodes-context'
import { Card, CardContent } from '@/components/ui/card'
-import {
- Tooltip,
- TooltipTrigger,
- TooltipContent,
-} from '@/components/ui/tooltip'
import { useTranslation } from 'react-i18next'
import { flowCategoryPreferences } from '@/lib/flow-categories'
-import { directionIcons } from '@/features/flow-editor/icons'
+import { directionIcons, useIconResolver } from '@/features/flow-editor/icons'
+import { NodeHandles } from '@/features/flow-editor/nodes/node-handles'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Button } from '@/components/ui/button'
@@ -57,17 +53,21 @@ const CoreNode = memo(({ selected, data }) => {
const [name, setName] = useState(data?.title ?? '')
const [position, setPosition] = useState(data?.position || 'l-r')
const { t } = useTranslation()
+ const resolveIcon = useIconResolver()
const { prefs, Icon } = useMemo(() => {
+ const providerIcon = resolveIcon(data?.icon)
if (!data?.category || !flowCategoryPreferences[data.category]) {
- return { prefs: { color: '#ffffff' }, Icon: () => null }
+ return { prefs: { color: '#ffffff' }, Icon: providerIcon ?? (() => null) }
}
const p = flowCategoryPreferences[data.category]
- return { prefs: p, Icon: p.icon }
- }, [data?.category])
+ return { prefs: p, Icon: providerIcon ?? p.icon }
+ }, [data?.category, data?.icon, resolveIcon])
const updateField = useCallback(
(field, value) => {
- if (!data?.id) return
+ if (!data?.id) {
+ return
+ }
setNodes((nodes) =>
nodes.map((n) => {
if (n.id === data.id) {
@@ -87,7 +87,9 @@ const CoreNode = memo(({ selected, data }) => {
)
const deleteNode = useCallback(() => {
- if (!data?.id) return
+ if (!data?.id) {
+ return
+ }
setNodes((nodes) => nodes.filter((n) => n.id !== data.id))
setEdges((edges) =>
edges.filter((e) => e.source !== data.id && e.target !== data.id),
@@ -95,13 +97,17 @@ const CoreNode = memo(({ selected, data }) => {
}, [data?.id, setNodes, setEdges])
useEffect(() => {
- if (!data) return
+ if (!data) {
+ return
+ }
if (!data.position || (data.position && data.position !== position)) {
updateField('position', position)
}
}, [position])
useEffect(() => {
- if (!data?.id) return
+ if (!data?.id) {
+ return
+ }
updateNodeInternals(data.id)
}, [position, updateNodeInternals, data?.id])
@@ -228,35 +234,23 @@ const CoreNode = memo(({ selected, data }) => {
-
- {/* Giriş Noktası */}
- {!data?.hide_left && (
-
-
-
-
-
- {t('ui.text.dataEntry')}
-
-
- )}
+
+
-
- {Icon &&
}
+
+ {Icon && }
{
- {/* Çıkış Noktası */}
-
-
-
-
-
- {t('ui.text.dataOutput')}
-
-
+
diff --git a/apps/platform/src/features/flow-editor/nodes/node-handles.tsx b/apps/platform/src/features/flow-editor/nodes/node-handles.tsx
new file mode 100644
index 0000000..ae8693c
--- /dev/null
+++ b/apps/platform/src/features/flow-editor/nodes/node-handles.tsx
@@ -0,0 +1,73 @@
+import { Handle } from '@xyflow/react'
+import type { Position } from '@xyflow/react'
+import {
+ Tooltip,
+ TooltipTrigger,
+ TooltipContent,
+} from '@/components/ui/tooltip'
+import type { NodeHandle } from '@/features/flow-editor/types'
+import { cn } from '@/lib/utils'
+
+type NodeHandlesProps = {
+ handles: NodeHandle[]
+ type: 'source' | 'target'
+ position: Position
+ tooltip: string
+}
+
+// Labels sit outside the node so they never cover its content.
+const LABEL_SIDE: Record
= {
+ left: 'right-full mr-2 -translate-y-1/2',
+ right: 'left-full ml-2 -translate-y-1/2',
+ top: 'bottom-full mb-2 -translate-x-1/2',
+ bottom: 'top-full mt-2 -translate-x-1/2',
+}
+
+const NodeHandles = ({
+ handles,
+ type,
+ position,
+ tooltip,
+}: NodeHandlesProps) => {
+ const isVertical = position === 'left' || position === 'right'
+
+ return handles.map((handle, index) => {
+ const offset = ((index + 1) / (handles.length + 1)) * 100
+ const placement = isVertical
+ ? { top: `${offset}%` }
+ : { left: `${offset}%` }
+
+ return (
+
+
+
+
+
+
+ {handle.label || tooltip}
+
+
+ {handle.label && (
+
+ {handle.label}
+
+ )}
+
+ )
+ })
+}
+
+export { NodeHandles }
diff --git a/apps/platform/src/features/flow-editor/nodes/starter-node.tsx b/apps/platform/src/features/flow-editor/nodes/starter-node.tsx
deleted file mode 100644
index a60bf5b..0000000
--- a/apps/platform/src/features/flow-editor/nodes/starter-node.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import { memo, useState, useMemo, useEffect } from 'react'
-import { Handle, Position, useUpdateNodeInternals } from '@xyflow/react'
-import { Card, CardContent } from '@/components/ui/card'
-import {
- Tooltip,
- TooltipTrigger,
- TooltipContent,
-} from '@/components/ui/tooltip'
-import { useTranslation } from 'react-i18next'
-import { flowCategoryPreferences } from '@/lib/flow-categories'
-
-const StarterNode = memo(({ selected, data }) => {
- const updateNodeInternals = useUpdateNodeInternals()
- const [position] = useState('l-r')
- const { t } = useTranslation()
-
- const { prefs, Icon } = useMemo(() => {
- if (!data?.category || !flowCategoryPreferences[data.category]) {
- return { prefs: { color: '#ffffff' }, Icon: () => null }
- }
- const p = flowCategoryPreferences[data.category]
- return { prefs: p, Icon: p.icon }
- }, [data?.category])
-
- useEffect(() => {
- updateNodeInternals(data?.id)
- }, [position, updateNodeInternals, data?.id])
-
- return (
-
-
-
-
-
-
- {Icon && }
-
-
-
- {prefs?.labelKey ? t(prefs.labelKey) : data?.category}
-
-
- {data?.title ? t(data.title) : null}
-
-
-
-
-
-
-
-
-
-
-
- {t('ui.text.dataOutput')}
-
-
-
-
- )
-})
-
-StarterNode.displayName = 'StarterNode'
-
-export default StarterNode
diff --git a/apps/platform/src/features/flow-editor/types.ts b/apps/platform/src/features/flow-editor/types.ts
index 192f144..59a8cca 100644
--- a/apps/platform/src/features/flow-editor/types.ts
+++ b/apps/platform/src/features/flow-editor/types.ts
@@ -45,13 +45,23 @@ export interface NodeEngineNode {
hasNaturalLanguage?: boolean
}
+export interface NodeHandle {
+ key: string
+ label?: string
+}
+
+export type IconSource = { brand?: string; glyph?: string } | null | undefined
+
export interface ResolvedNode {
id: string
name: string
description: string
- icon: string
+ icon: IconSource
+ inputs: NodeHandle[]
+ outputs: NodeHandle[]
category: string
subCategory: string
+ provider: string
tags: string[]
supportedCredentials?: string[]
schema: SchemaField[]
@@ -80,6 +90,7 @@ export interface FlowNodeData extends Record {
title?: string
description?: string
category?: string
+ subCategory?: string
tags?: string[]
contextMenu?: ContextMenuItem[]
note?: string
@@ -109,6 +120,9 @@ export interface RendererNode {
label: string
description: string
type: string
+ icon?: IconSource
+ inputs?: NodeHandle[]
+ outputs?: NodeHandle[]
subCategory?: string
actions: string[]
tags?: string[]
diff --git a/apps/platform/src/features/generation/components/ai-chat-sheet.tsx b/apps/platform/src/features/generation/components/ai-chat-sheet.tsx
index e3f6341..934c5ed 100644
--- a/apps/platform/src/features/generation/components/ai-chat-sheet.tsx
+++ b/apps/platform/src/features/generation/components/ai-chat-sheet.tsx
@@ -58,7 +58,9 @@ const AIChatSheet = ({ organizationId, isOpen, onClose, onApplyWorkflow }) => {
const handleScroll = useCallback(() => {
const el = scrollContainerRef.current
- if (!el) return
+ if (!el) {
+ return
+ }
const threshold = 50
isUserScrolledUp.current =
el.scrollHeight - el.scrollTop - el.clientHeight > threshold
@@ -92,7 +94,9 @@ const AIChatSheet = ({ organizationId, isOpen, onClose, onApplyWorkflow }) => {
onClose()
}
- if (!isOpen) return null
+ if (!isOpen) {
+ return null
+ }
return (
<>
diff --git a/apps/platform/src/features/generation/components/workflow-preview.tsx b/apps/platform/src/features/generation/components/workflow-preview.tsx
index 1297135..f9423ab 100644
--- a/apps/platform/src/features/generation/components/workflow-preview.tsx
+++ b/apps/platform/src/features/generation/components/workflow-preview.tsx
@@ -19,7 +19,7 @@ const WorkflowPreview = ({ json, onApplyWorkflow }) => {
// Try parsing with sanitization if direct parse fails
try {
parsed = JSON.parse(json)
- } catch (e) {
+ } catch {
try {
parsed = JSON.parse(sanitizeJson(json))
} catch (e2) {
diff --git a/apps/platform/src/features/generation/hooks/use-chat-session.ts b/apps/platform/src/features/generation/hooks/use-chat-session.ts
index e3a1ace..c6481f1 100644
--- a/apps/platform/src/features/generation/hooks/use-chat-session.ts
+++ b/apps/platform/src/features/generation/hooks/use-chat-session.ts
@@ -85,7 +85,9 @@ export function useChatSession(organizationId: string) {
const handleSearchChange = (e: React.ChangeEvent) => {
const value = e.target.value
setSearchQuery(value)
- if (searchTimeoutRef.current) clearTimeout(searchTimeoutRef.current)
+ if (searchTimeoutRef.current) {
+ clearTimeout(searchTimeoutRef.current)
+ }
searchTimeoutRef.current = setTimeout(() => {
fetchSessions(value, 0)
}, 300)
@@ -97,7 +99,9 @@ export function useChatSession(organizationId: string) {
}
const selectSession = async (session: Session) => {
- if (editingSessionId) return // Don't select while editing
+ if (editingSessionId) {
+ return
+ } // Don't select while editing
setSessionId(session.id)
setMessages([])
setView('chat')
@@ -124,7 +128,9 @@ export function useChatSession(organizationId: string) {
}
const confirmRename = async () => {
- if (!editingTitle.trim() || !editingSessionId) return
+ if (!editingTitle.trim() || !editingSessionId) {
+ return
+ }
setActionLoading(true)
try {
await generation.updateSession(
@@ -156,7 +162,9 @@ export function useChatSession(organizationId: string) {
}
const confirmDelete = async () => {
- if (!deleteConfirmSession) return
+ if (!deleteConfirmSession) {
+ return
+ }
setActionLoading(true)
try {
await generation.deleteSession(organizationId, deleteConfirmSession.id)
@@ -183,7 +191,9 @@ export function useChatSession(organizationId: string) {
const handleSend = async () => {
const text = input.trim()
- if (!text || streaming) return
+ if (!text || streaming) {
+ return
+ }
const userMessage: ChatMessage = {
role: 'user',
@@ -232,7 +242,9 @@ export function useChatSession(organizationId: string) {
await generation.sendMessageStream(organizationId, currentSessionId, text, {
onChunk: (chunk: string) => {
- if (abortRef.current) return
+ if (abortRef.current) {
+ return
+ }
setMessages((prev) =>
prev.map((m) =>
m.id === assistantId ? { ...m, content: m.content + chunk } : m,
diff --git a/apps/platform/src/features/generation/services/generation.ts b/apps/platform/src/features/generation/services/generation.ts
index 277f490..490c8ed 100644
--- a/apps/platform/src/features/generation/services/generation.ts
+++ b/apps/platform/src/features/generation/services/generation.ts
@@ -10,7 +10,9 @@ function parseSSEEvents(raw) {
// Split by double newline to separate SSE events
const blocks = raw.split('\n\n')
for (const block of blocks) {
- if (!block.trim()) continue
+ if (!block.trim()) {
+ continue
+ }
const lines = block.split('\n')
let eventType = 'message'
@@ -40,7 +42,9 @@ export const generation = {
{ query = '', offset = 0, limit = 10 } = {},
) => {
const params = new URLSearchParams()
- if (query) params.set('query', query)
+ if (query) {
+ params.set('query', query)
+ }
params.set('offset', offset)
params.set('limit', limit)
return await platformApi.get(
@@ -120,14 +124,18 @@ export const generation = {
while (true) {
const { done, value } = await reader.read()
- if (done) break
+ if (done) {
+ break
+ }
buffer += decoder.decode(value, { stream: true })
// Only process complete SSE events (terminated by \n\n)
// Keep incomplete data in buffer for next iteration
const lastDoubleNewline = buffer.lastIndexOf('\n\n')
- if (lastDoubleNewline === -1) continue
+ if (lastDoubleNewline === -1) {
+ continue
+ }
const complete = buffer.slice(0, lastDoubleNewline + 2)
buffer = buffer.slice(lastDoubleNewline + 2)
@@ -157,7 +165,9 @@ export const generation = {
onDone?.()
break
case 'message':
- if (data) onChunk?.(data)
+ if (data) {
+ onChunk?.(data)
+ }
break
case 'error':
onError?.(data)
diff --git a/apps/platform/src/features/mcp/components/mcp-server-card.tsx b/apps/platform/src/features/mcp/components/mcp-server-card.tsx
index 8fcc995..ccdbad4 100644
--- a/apps/platform/src/features/mcp/components/mcp-server-card.tsx
+++ b/apps/platform/src/features/mcp/components/mcp-server-card.tsx
@@ -1,9 +1,11 @@
import { memo, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
-import { KeyRound, Server, Wrench } from 'lucide-react'
+import { Check, Copy, KeyRound, Server, Wrench } from 'lucide-react'
import { Badge } from '@/components/ui/badge'
+import { Button } from '@/components/ui/button'
import { Card, CardContent, CardFooter } from '@/components/ui/card'
-import { FlowIcon } from '@/components/shared/custom-icons'
+import { useIconResolver } from '@/features/flow-editor/icons'
+import { useCopy } from '@/hooks/use-copy'
import { cn } from '@/lib/utils'
import type { McpServer } from '@/features/mcp/services/mcp'
@@ -13,6 +15,9 @@ type McpServerCardProps = {
}
const McpServerCard = memo(({ server, onClick }: McpServerCardProps) => {
+ const { copied, copy } = useCopy()
+ const resolveIcon = useIconResolver()
+ const BrandIcon = resolveIcon(server.icon)
const { t } = useTranslation()
const toolCount = server.tools?.length ?? 0
@@ -20,7 +25,9 @@ const McpServerCard = memo(({ server, onClick }: McpServerCardProps) => {
const credentialCount = useMemo(() => {
const credentials = new Set()
for (const tool of server.tools ?? []) {
- for (const cred of tool.supportedCredentials ?? []) credentials.add(cred)
+ for (const cred of tool.supportedCredentials ?? []) {
+ credentials.add(cred)
+ }
}
return credentials.size
}, [server.tools])
@@ -35,15 +42,12 @@ const McpServerCard = memo(({ server, onClick }: McpServerCardProps) => {
>
-
-
+
+ {BrandIcon ? (
+
+ ) : (
+
+ )}
@@ -56,7 +60,7 @@ const McpServerCard = memo(({ server, onClick }: McpServerCardProps) => {
{server.description && (
-
+
{server.description}
)}
@@ -64,7 +68,7 @@ const McpServerCard = memo(({ server, onClick }: McpServerCardProps) => {
-
+
{t('ui.text.mcpToolCount', { count: toolCount })}
@@ -75,6 +79,24 @@ const McpServerCard = memo(({ server, onClick }: McpServerCardProps) => {
{credentialCount}
)}
+ {server.url && (
+
+ )}
)
diff --git a/apps/platform/src/features/mcp/components/mcp-server-detail-dialog.tsx b/apps/platform/src/features/mcp/components/mcp-server-detail-dialog.tsx
index 2c2b19b..b4fa359 100644
--- a/apps/platform/src/features/mcp/components/mcp-server-detail-dialog.tsx
+++ b/apps/platform/src/features/mcp/components/mcp-server-detail-dialog.tsx
@@ -1,5 +1,5 @@
import { useTranslation } from 'react-i18next'
-import { KeyRound } from 'lucide-react'
+import { KeyRound, Wrench } from 'lucide-react'
import { Badge } from '@/components/ui/badge'
import {
Dialog,
@@ -9,8 +9,9 @@ import {
DialogTitle,
} from '@/components/ui/dialog'
import { CopyField } from '@/components/shared/copy-field'
-import { FlowIcon } from '@/components/shared/custom-icons'
+import { useIconResolver } from '@/features/flow-editor/icons'
import { McpToolSchemaSection } from '@/features/mcp/components/mcp-tool-schema'
+import type { IconSource } from '@/features/flow-editor/types'
import type { McpServer } from '@/features/mcp/services/mcp'
type McpServerDetailDialogProps = {
@@ -19,6 +20,21 @@ type McpServerDetailDialogProps = {
onOpenChange: (open: boolean) => void
}
+const McpToolIcon = ({ icon }: { icon?: IconSource }) => {
+ const resolveIcon = useIconResolver()
+ const Icon = resolveIcon(icon)
+
+ return (
+
+ {Icon ? (
+
+ ) : (
+
+ )}
+
+ )
+}
+
const McpServerDetailDialog = ({
server,
open,
@@ -66,14 +82,7 @@ const McpServerDetailDialog = ({
key={tool.id}
className="flex items-start gap-2.5 rounded-lg border bg-card p-3 ring-1 ring-foreground/5 transition-colors hover:border-foreground/20"
>
-
+
diff --git a/apps/platform/src/features/mcp/components/mcp-tool-schema.tsx b/apps/platform/src/features/mcp/components/mcp-tool-schema.tsx
index 076bb3f..f31cba8 100644
--- a/apps/platform/src/features/mcp/components/mcp-tool-schema.tsx
+++ b/apps/platform/src/features/mcp/components/mcp-tool-schema.tsx
@@ -32,7 +32,9 @@ type SchemaShape = {
const resolveShape = (
node: McpToolSchema | McpToolSchemaProperty | undefined,
): SchemaShape | null => {
- if (!node || typeof node !== 'object') return null
+ if (!node || typeof node !== 'object') {
+ return null
+ }
if (node.type === 'array' && node.items && typeof node.items === 'object') {
return resolveShape(node.items as McpToolSchemaProperty)
}
@@ -101,7 +103,9 @@ const McpToolSchemaSection = ({ label, schema }: McpToolSchemaSectionProps) => {
const shape = resolveShape(schema)
const count = shape ? Object.keys(shape.properties ?? {}).length : 0
- if (count === 0) return null
+ if (count === 0) {
+ return null
+ }
return (
diff --git a/apps/platform/src/features/mcp/services/mcp.ts b/apps/platform/src/features/mcp/services/mcp.ts
index 0d435bf..a869fee 100644
--- a/apps/platform/src/features/mcp/services/mcp.ts
+++ b/apps/platform/src/features/mcp/services/mcp.ts
@@ -1,3 +1,4 @@
+import type { IconSource } from '@/features/flow-editor/types'
import mcpApi from '@/lib/mcp-api'
export type McpToolSchemaProperty = {
@@ -23,6 +24,7 @@ export type McpTool = {
version: string
name: string
description?: string
+ icon?: IconSource
inputSchema?: McpToolSchema
outputSchema?: McpToolSchema
supportedCredentials?: string[]
@@ -32,6 +34,7 @@ export type McpServer = {
id: string
name: string
description?: string
+ icon?: IconSource
version: string
url?: string
tools: McpTool[]
diff --git a/apps/platform/src/features/navigation/components/logo.tsx b/apps/platform/src/features/navigation/components/logo.tsx
index feafba3..69fac2b 100644
--- a/apps/platform/src/features/navigation/components/logo.tsx
+++ b/apps/platform/src/features/navigation/components/logo.tsx
@@ -15,9 +15,13 @@ const PlatformLogo = ({ width = 200, height = 40, className = '' }) => {
useEffect(() => {
const theme = getMode()
- if (theme === 'dark') setLogoName('logo-b.png')
- else if (theme === 'light') setLogoName('logo-w.png')
- else setLogoName(prefersDark ? 'logo-b.png' : 'logo-w.png')
+ if (theme === 'dark') {
+ setLogoName('logo-b.png')
+ } else if (theme === 'light') {
+ setLogoName('logo-w.png')
+ } else {
+ setLogoName(prefersDark ? 'logo-b.png' : 'logo-w.png')
+ }
}, [mode, getMode])
return (
diff --git a/apps/platform/src/features/navigation/components/main-nav.tsx b/apps/platform/src/features/navigation/components/main-nav.tsx
index c4962be..51e701f 100644
--- a/apps/platform/src/features/navigation/components/main-nav.tsx
+++ b/apps/platform/src/features/navigation/components/main-nav.tsx
@@ -30,7 +30,9 @@ const NavMain = ({ items, title }) => {
}
const isLinkActive = (url: string) => {
- if (!url || url === '#') return false
+ if (!url || url === '#') {
+ return false
+ }
const resolved = getUrl(url).split('?')[0]
return location.pathname === resolved
}
diff --git a/apps/platform/src/features/navigation/components/organization-switcher.tsx b/apps/platform/src/features/navigation/components/organization-switcher.tsx
index a763f80..dbca744 100644
--- a/apps/platform/src/features/navigation/components/organization-switcher.tsx
+++ b/apps/platform/src/features/navigation/components/organization-switcher.tsx
@@ -61,9 +61,13 @@ const OrganizationSwitcher = ({ teams, onCreateOrganization }) => {
const prefersDark = window.matchMedia(
'(prefers-color-scheme: dark)',
).matches
- if (mode === 'dark') setAvatarColor('2a2627')
- else if (mode === 'light') setAvatarColor('fafafa')
- else setAvatarColor(prefersDark ? '2a2627' : 'fafafa')
+ if (mode === 'dark') {
+ setAvatarColor('2a2627')
+ } else if (mode === 'light') {
+ setAvatarColor('fafafa')
+ } else {
+ setAvatarColor(prefersDark ? '2a2627' : 'fafafa')
+ }
}, [mode])
if (!activeTeam || !teams || teams.length === 0) {
diff --git a/apps/platform/src/features/navigation/components/user-nav.tsx b/apps/platform/src/features/navigation/components/user-nav.tsx
index dede523..b2d9f8a 100644
--- a/apps/platform/src/features/navigation/components/user-nav.tsx
+++ b/apps/platform/src/features/navigation/components/user-nav.tsx
@@ -47,7 +47,7 @@ const NavUser = ({ linkedAccounts, alone }) => {
const sidebar = useSidebar()
ib = sidebar.isMobile
st = sidebar.state
- } catch (error) {
+ } catch {
// If useSidebar fails (not within SidebarProvider), use defaults
ib = false
st = 'expanded'
diff --git a/apps/platform/src/features/notifications/components/notifications-bell.tsx b/apps/platform/src/features/notifications/components/notifications-bell.tsx
index f179860..4960bcb 100644
--- a/apps/platform/src/features/notifications/components/notifications-bell.tsx
+++ b/apps/platform/src/features/notifications/components/notifications-bell.tsx
@@ -109,16 +109,22 @@ export function NotificationsBell() {
handleMarkAllAsRead,
} = useOrganizationNotificationsBell(organizationId, open)
- if (!organizationId) return null
+ if (!organizationId) {
+ return null
+ }
const handleOpenChange = (next: boolean) => {
setOpen(next)
// Opening the panel clears the bell badge (marks everything seen).
- if (next && unseenCount > 0) handleMarkAllAsSeen()
+ if (next && unseenCount > 0) {
+ handleMarkAllAsSeen()
+ }
}
const handleActivate = (notification: any) => {
- if (!notification.readAt) handleMarkAsRead(notification.id)
+ if (!notification.readAt) {
+ handleMarkAsRead(notification.id)
+ }
setOpen(false)
}
diff --git a/apps/platform/src/features/notifications/components/notifications-tab.tsx b/apps/platform/src/features/notifications/components/notifications-tab.tsx
index 77383d5..c2e4634 100644
--- a/apps/platform/src/features/notifications/components/notifications-tab.tsx
+++ b/apps/platform/src/features/notifications/components/notifications-tab.tsx
@@ -121,7 +121,9 @@ const NotificationsTab = ({
const actionUrl: string | undefined = notification.actionUrl
const external = actionUrl && isExternalUrl(actionUrl)
const onActionClick = () => {
- if (!isRead) onMarkAsRead(notification.id)
+ if (!isRead) {
+ onMarkAsRead(notification.id)
+ }
}
const titleEl = (
diff --git a/apps/platform/src/features/notifications/hooks/use-notifications.ts b/apps/platform/src/features/notifications/hooks/use-notifications.ts
index dc92495..533e72d 100644
--- a/apps/platform/src/features/notifications/hooks/use-notifications.ts
+++ b/apps/platform/src/features/notifications/hooks/use-notifications.ts
@@ -85,7 +85,9 @@ export function useOrganizationNotifications(
)
const handleMarkAllAsSeen = useCallback(async () => {
- if (!organizationId) return
+ if (!organizationId) {
+ return
+ }
await notificationsService.markAllOrganizationNotificationsSeen(
organizationId,
)
@@ -94,7 +96,9 @@ export function useOrganizationNotifications(
const handleMarkAsRead = useCallback(
async (recipientId: string) => {
- if (!organizationId) return
+ if (!organizationId) {
+ return
+ }
await notificationsService.markOrganizationNotificationRead(
organizationId,
recipientId,
@@ -105,7 +109,9 @@ export function useOrganizationNotifications(
)
const handleMarkAllAsRead = useCallback(async () => {
- if (!organizationId) return
+ if (!organizationId) {
+ return
+ }
await notificationsService.markAllOrganizationNotificationsRead(
organizationId,
)
@@ -114,7 +120,9 @@ export function useOrganizationNotifications(
const handleDelete = useCallback(
async (recipientId: string) => {
- if (!organizationId) return
+ if (!organizationId) {
+ return
+ }
await notificationsService.deleteOrganizationNotification(
organizationId,
recipientId,
@@ -194,7 +202,9 @@ export function useOrganizationNotificationsBell(
)
const handleMarkAllAsSeen = useCallback(async () => {
- if (!organizationId) return
+ if (!organizationId) {
+ return
+ }
await notificationsService.markAllOrganizationNotificationsSeen(
organizationId,
)
@@ -203,7 +213,9 @@ export function useOrganizationNotificationsBell(
const handleMarkAsRead = useCallback(
async (recipientId: string) => {
- if (!organizationId) return
+ if (!organizationId) {
+ return
+ }
await notificationsService.markOrganizationNotificationRead(
organizationId,
recipientId,
@@ -214,7 +226,9 @@ export function useOrganizationNotificationsBell(
)
const handleMarkAllAsRead = useCallback(async () => {
- if (!organizationId) return
+ if (!organizationId) {
+ return
+ }
await notificationsService.markAllOrganizationNotificationsRead(
organizationId,
)
diff --git a/apps/platform/src/features/notifications/services/notifications.ts b/apps/platform/src/features/notifications/services/notifications.ts
index ebb5a2c..ec006f6 100644
--- a/apps/platform/src/features/notifications/services/notifications.ts
+++ b/apps/platform/src/features/notifications/services/notifications.ts
@@ -4,7 +4,9 @@ const buildQuery = ({ offset = 0, limit = 10, query = '' } = {}) => {
const params = new URLSearchParams()
params.set('offset', String(offset))
params.set('limit', String(limit))
- if (query) params.set('query', query)
+ if (query) {
+ params.set('query', query)
+ }
return params.toString()
}
diff --git a/apps/platform/src/features/organizations/components/create-organization-form.tsx b/apps/platform/src/features/organizations/components/create-organization-form.tsx
index d3c0be5..cf68de6 100644
--- a/apps/platform/src/features/organizations/components/create-organization-form.tsx
+++ b/apps/platform/src/features/organizations/components/create-organization-form.tsx
@@ -35,7 +35,9 @@ const CreateOrganizationForm = ({
const fieldErrors: Record = {}
for (const issue of result.error.issues) {
const field = String(issue.path[0])
- if (fieldErrors[field]) continue
+ if (fieldErrors[field]) {
+ continue
+ }
fieldErrors[field] = issue.message.startsWith('ui.')
? t(issue.message)
: issue.message
@@ -72,7 +74,9 @@ const CreateOrganizationForm = ({
value={values.title}
onChange={(e) => {
setValues({ ...values, title: e.target.value })
- if (errors.title) setErrors({ ...errors, title: '' })
+ if (errors.title) {
+ setErrors({ ...errors, title: '' })
+ }
}}
/>
{errors.title && (
@@ -91,7 +95,9 @@ const CreateOrganizationForm = ({
value={values.description}
onChange={(e) => {
setValues({ ...values, description: e.target.value })
- if (errors.description) setErrors({ ...errors, description: '' })
+ if (errors.description) {
+ setErrors({ ...errors, description: '' })
+ }
}}
/>
{errors.description && (
diff --git a/apps/platform/src/features/organizations/components/history/output-renderer.tsx b/apps/platform/src/features/organizations/components/history/output-renderer.tsx
index 6993031..35e7875 100644
--- a/apps/platform/src/features/organizations/components/history/output-renderer.tsx
+++ b/apps/platform/src/features/organizations/components/history/output-renderer.tsx
@@ -26,7 +26,9 @@ const isUrl = (value: string) => {
}
const isImageUrl = (value: string) => {
- if (!isUrl(value)) return false
+ if (!isUrl(value)) {
+ return false
+ }
const ext = value.split('?')[0].toLowerCase()
return (
ext.endsWith('.png') ||
@@ -39,7 +41,9 @@ const isImageUrl = (value: string) => {
}
const isVideoUrl = (value: string) => {
- if (!isUrl(value)) return false
+ if (!isUrl(value)) {
+ return false
+ }
const ext = value.split('?')[0].toLowerCase()
return (
ext.endsWith('.mp4') ||
@@ -50,7 +54,9 @@ const isVideoUrl = (value: string) => {
}
const isAudioUrl = (value: string) => {
- if (!isUrl(value)) return false
+ if (!isUrl(value)) {
+ return false
+ }
const ext = value.split('?')[0].toLowerCase()
return (
ext.endsWith('.mp3') ||
@@ -199,23 +205,35 @@ const ObjectOutput = ({ value }: { value: any }) => (
)
const renderValue = (key: string, value: any) => {
- if (value === null || value === undefined) return null
+ if (value === null || value === undefined) {
+ return null
+ }
const strValue = typeof value === 'string' ? value : String(value)
// Detect by key name hints
if (key === 'image' || key.includes('image') || key.includes('thumbnail')) {
- if (isUrl(strValue)) return
+ if (isUrl(strValue)) {
+ return
+ }
}
if (key === 'video' || key.includes('video')) {
- if (isVideoUrl(strValue)) return
- if (isUrl(strValue)) return
+ if (isVideoUrl(strValue)) {
+ return
+ }
+ if (isUrl(strValue)) {
+ return
+ }
}
if (key === 'audio' || key.includes('audio')) {
- if (isAudioUrl(strValue)) return
- if (isUrl(strValue)) return
+ if (isAudioUrl(strValue)) {
+ return
+ }
+ if (isUrl(strValue)) {
+ return
+ }
}
if (key === 'file') {
@@ -227,16 +245,26 @@ const renderValue = (key: string, value: any) => {
}
if (key === 'videoUrl' || key === 'videoId') {
- if (isUrl(strValue)) return
+ if (isUrl(strValue)) {
+ return
+ }
return
}
// Detect by content
if (typeof value === 'string') {
- if (isImageUrl(value)) return
- if (isVideoUrl(value)) return
- if (isAudioUrl(value)) return
- if (isUrl(value)) return
+ if (isImageUrl(value)) {
+ return
+ }
+ if (isVideoUrl(value)) {
+ return
+ }
+ if (isAudioUrl(value)) {
+ return
+ }
+ if (isUrl(value)) {
+ return
+ }
return
}
@@ -250,7 +278,9 @@ const renderValue = (key: string, value: any) => {
const OutputRenderer = ({ outputs }: { outputs: any }) => {
const { t } = useTranslation()
- if (!outputs) return null
+ if (!outputs) {
+ return null
+ }
// outputs can be an array of objects or a single object
const items = Array.isArray(outputs) ? outputs : [outputs]
@@ -263,12 +293,16 @@ const OutputRenderer = ({ outputs }: { outputs: any }) => {
{items.map((item, itemIndex) => {
- if (!item || typeof item !== 'object') return null
+ if (!item || typeof item !== 'object') {
+ return null
+ }
return (
{Object.entries(item).map(([key, value]) => {
- if (value === null || value === undefined) return null
+ if (value === null || value === undefined) {
+ return null
+ }
return (
diff --git a/apps/platform/src/features/organizations/components/history/run-step-detail.tsx b/apps/platform/src/features/organizations/components/history/run-step-detail.tsx
index 83e6e56..1a7bda8 100644
--- a/apps/platform/src/features/organizations/components/history/run-step-detail.tsx
+++ b/apps/platform/src/features/organizations/components/history/run-step-detail.tsx
@@ -28,7 +28,9 @@ const RunStepDetail = ({
const formatDuration = (startedAt?: string, completedAt?: string) => {
try {
- if (!startedAt || !completedAt) return t('ui.text.workInProgress')
+ if (!startedAt || !completedAt) {
+ return t('ui.text.workInProgress')
+ }
const start = new Date(startedAt)
const end = new Date(completedAt)
@@ -45,9 +47,13 @@ const RunStepDetail = ({
const formatTime = (dateString?: string) => {
try {
- if (!dateString) return t('ui.text.workInProgress')
+ if (!dateString) {
+ return t('ui.text.workInProgress')
+ }
const date = new Date(dateString)
- if (isNaN(date.getTime())) return t('ui.text.invalid')
+ if (isNaN(date.getTime())) {
+ return t('ui.text.invalid')
+ }
return format(date, 'HH:mm:ss.SSS')
} catch {
return t('ui.text.error')
diff --git a/apps/platform/src/features/organizations/components/settings/settings-basic-info.tsx b/apps/platform/src/features/organizations/components/settings/settings-basic-info.tsx
index 5f11ff8..ffb911c 100644
--- a/apps/platform/src/features/organizations/components/settings/settings-basic-info.tsx
+++ b/apps/platform/src/features/organizations/components/settings/settings-basic-info.tsx
@@ -39,7 +39,9 @@ const SettingsBasicInfo = ({
}
const handleSave = async () => {
- if (!validateForm()) return
+ if (!validateForm()) {
+ return
+ }
setIsSubmitting(true)
diff --git a/apps/platform/src/features/organizations/hooks/use-organization-credentials.ts b/apps/platform/src/features/organizations/hooks/use-organization-credentials.ts
index 62cbbb4..f7ca356 100644
--- a/apps/platform/src/features/organizations/hooks/use-organization-credentials.ts
+++ b/apps/platform/src/features/organizations/hooks/use-organization-credentials.ts
@@ -54,7 +54,9 @@ export function useOrganizationCredentialActions(
const create = useCallback(
async (payload: unknown) => {
- if (!organizationId) return null
+ if (!organizationId) {
+ return null
+ }
const result = await unwrap(
organizationsService.createOrganizationCredential(
organizationId,
@@ -69,7 +71,9 @@ export function useOrganizationCredentialActions(
const update = useCallback(
async (credentialId: string, payload: unknown) => {
- if (!organizationId) return null
+ if (!organizationId) {
+ return null
+ }
const result = await unwrap(
organizationsService.updateOrganizationCredential(
organizationId,
@@ -88,7 +92,9 @@ export function useOrganizationCredentialActions(
const remove = useCallback(
async (credentialId: string) => {
- if (!organizationId) return null
+ if (!organizationId) {
+ return null
+ }
const result = await unwrap(
organizationsService.deleteOrganizationCredential(
organizationId,
diff --git a/apps/platform/src/features/organizations/hooks/use-organization-members.ts b/apps/platform/src/features/organizations/hooks/use-organization-members.ts
index 5868f4b..1a845d1 100644
--- a/apps/platform/src/features/organizations/hooks/use-organization-members.ts
+++ b/apps/platform/src/features/organizations/hooks/use-organization-members.ts
@@ -59,7 +59,9 @@ export function useOrganizationMemberActions(
const invite = useCallback(
async (payload: unknown) => {
- if (!organizationId) return null
+ if (!organizationId) {
+ return null
+ }
const result = await unwrap(
organizationsService.inviteMember(organizationId, payload as never),
)
@@ -71,7 +73,9 @@ export function useOrganizationMemberActions(
const updateRole = useCallback(
async (memberId: string, payload: unknown) => {
- if (!organizationId) return null
+ if (!organizationId) {
+ return null
+ }
const result = await unwrap(
organizationsService.updateMemberRole(
organizationId,
@@ -87,7 +91,9 @@ export function useOrganizationMemberActions(
const updateInfo = useCallback(
async (memberId: string, payload: unknown) => {
- if (!organizationId) return null
+ if (!organizationId) {
+ return null
+ }
const result = await unwrap(
organizationsService.updateMemberInfo(
organizationId,
@@ -103,7 +109,9 @@ export function useOrganizationMemberActions(
const remove = useCallback(
async (memberId: string) => {
- if (!organizationId) return null
+ if (!organizationId) {
+ return null
+ }
const result = await unwrap(
organizationsService.deleteMember(organizationId, memberId),
)
diff --git a/apps/platform/src/features/organizations/services/organizations.ts b/apps/platform/src/features/organizations/services/organizations.ts
index 15a2104..9898bce 100644
--- a/apps/platform/src/features/organizations/services/organizations.ts
+++ b/apps/platform/src/features/organizations/services/organizations.ts
@@ -29,9 +29,15 @@ const getMembers = async (
params?: { offset?: number; limit?: number; query?: string },
) => {
const search = new URLSearchParams()
- if (params?.offset !== undefined) search.set('offset', String(params.offset))
- if (params?.limit !== undefined) search.set('limit', String(params.limit))
- if (params?.query) search.set('query', params.query)
+ if (params?.offset !== undefined) {
+ search.set('offset', String(params.offset))
+ }
+ if (params?.limit !== undefined) {
+ search.set('limit', String(params.limit))
+ }
+ if (params?.query) {
+ search.set('query', params.query)
+ }
const qs = search.toString()
return await platformApi.get(
`/organizations/${organizationId}/users${qs ? `?${qs}` : ''}`,
@@ -69,7 +75,9 @@ const getOrganizationCredentials = async (
const params = new URLSearchParams()
params.set('offset', String(offset))
params.set('limit', String(limit))
- if (query) params.set('query', query)
+ if (query) {
+ params.set('query', query)
+ }
return await platformApi.get(
`/organizations/${organizationId}/credentials?${params.toString()}`,
)
diff --git a/apps/platform/src/features/preferences/components/profile-section.tsx b/apps/platform/src/features/preferences/components/profile-section.tsx
index 9276621..2093fde 100644
--- a/apps/platform/src/features/preferences/components/profile-section.tsx
+++ b/apps/platform/src/features/preferences/components/profile-section.tsx
@@ -126,7 +126,9 @@ const ProfileSection = ({
} else if (!isPlaceholder) {
subtitle = identifier
}
- if (!subtitle) return null
+ if (!subtitle) {
+ return null
+ }
return (
{subtitle}
@@ -157,7 +159,9 @@ const ProfileSection = ({
const canResend =
account.authProvider === 'email' && !account.isVerified
const canRemove = !account.isPrimary
- if (!canResend && !canRemove) return null
+ if (!canResend && !canRemove) {
+ return null
+ }
const isBusy =
resendingEmail === account.identifier ||
deletingAccountId === account.id
@@ -221,7 +225,9 @@ const ProfileSection = ({
const target = linkedAccounts.find(
(a) => a.id === confirmRemoveId,
)
- if (!target) return null
+ if (!target) {
+ return null
+ }
return t('ui.text.removeLinkedAccountConfirmation', {
name: target.displayName ?? target.identifier,
provider: target.authProvider,
@@ -233,7 +239,9 @@ const ProfileSection = ({
{t('ui.text.cancel')}
{
- if (confirmRemoveId) deleteLinkedAccount(confirmRemoveId)
+ if (confirmRemoveId) {
+ deleteLinkedAccount(confirmRemoveId)
+ }
setConfirmRemoveId(null)
}}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
@@ -255,8 +263,9 @@ const ProfileSection = ({
const unlinked = getUnlinkedProviders()
const showEmailRow = !hasEmail
const showPasswordRow = passwordEnabled && hasEmail && !hasPassword
- if (unlinked.length === 0 && !showEmailRow && !showPasswordRow)
+ if (unlinked.length === 0 && !showEmailRow && !showPasswordRow) {
return null
+ }
return (
<>
diff --git a/apps/platform/src/features/sessions/components/sessions.tsx b/apps/platform/src/features/sessions/components/sessions.tsx
index 11c702c..5eb9cf5 100644
--- a/apps/platform/src/features/sessions/components/sessions.tsx
+++ b/apps/platform/src/features/sessions/components/sessions.tsx
@@ -34,22 +34,35 @@ import { ProviderIcon } from '@/features/auth/components/provider-icon'
import { getProviderName } from '@/features/auth/components/provider-utils'
function parseDevice(userAgent: string, unknownLabel: string) {
- if (!userAgent) return { label: unknownLabel, isMobile: false }
+ if (!userAgent) {
+ return { label: unknownLabel, isMobile: false }
+ }
const ua = userAgent.toLowerCase()
let browser = 'Browser'
- if (ua.includes('firefox')) browser = 'Firefox'
- else if (ua.includes('edg')) browser = 'Edge'
- else if (ua.includes('chrome') && !ua.includes('edg')) browser = 'Chrome'
- else if (ua.includes('safari') && !ua.includes('chrome')) browser = 'Safari'
+ if (ua.includes('firefox')) {
+ browser = 'Firefox'
+ } else if (ua.includes('edg')) {
+ browser = 'Edge'
+ } else if (ua.includes('chrome') && !ua.includes('edg')) {
+ browser = 'Chrome'
+ } else if (ua.includes('safari') && !ua.includes('chrome')) {
+ browser = 'Safari'
+ }
let os = ''
- if (ua.includes('windows')) os = 'Windows'
- else if (ua.includes('mac os') || ua.includes('macintosh')) os = 'macOS'
- else if (ua.includes('linux') && !ua.includes('android')) os = 'Linux'
- else if (ua.includes('android')) os = 'Android'
- else if (ua.includes('iphone') || ua.includes('ipad')) os = 'iOS'
+ if (ua.includes('windows')) {
+ os = 'Windows'
+ } else if (ua.includes('mac os') || ua.includes('macintosh')) {
+ os = 'macOS'
+ } else if (ua.includes('linux') && !ua.includes('android')) {
+ os = 'Linux'
+ } else if (ua.includes('android')) {
+ os = 'Android'
+ } else if (ua.includes('iphone') || ua.includes('ipad')) {
+ os = 'iOS'
+ }
const isMobile =
ua.includes('mobile') || ua.includes('android') || ua.includes('iphone')
diff --git a/apps/platform/src/features/theme/components/mode-toggle.tsx b/apps/platform/src/features/theme/components/mode-toggle.tsx
index 2011a07..19465c0 100644
--- a/apps/platform/src/features/theme/components/mode-toggle.tsx
+++ b/apps/platform/src/features/theme/components/mode-toggle.tsx
@@ -31,7 +31,9 @@ const ModeToggle = (props) => {
}
const handleValueChange = (val: string) => {
- if (val) setTheme(val)
+ if (val) {
+ setTheme(val)
+ }
}
return (
diff --git a/apps/platform/src/features/tour/components/tour-component.tsx b/apps/platform/src/features/tour/components/tour-component.tsx
index 80d1799..c7979ae 100644
--- a/apps/platform/src/features/tour/components/tour-component.tsx
+++ b/apps/platform/src/features/tour/components/tour-component.tsx
@@ -114,7 +114,9 @@ const TourComponent = ({
}
useEffect(() => {
- if (!autoProgressOnClick || !isOpen || currentStep >= steps.length) return
+ if (!autoProgressOnClick || !isOpen || currentStep >= steps.length) {
+ return
+ }
const currentStepData = steps[currentStep]
let targetElement = document.querySelector(currentStepData.target)
@@ -204,7 +206,9 @@ const TourComponent = ({
}
}, [isOpen, currentStep])
- if (!isOpen || steps.length === 0) return null
+ if (!isOpen || steps.length === 0) {
+ return null
+ }
return (
{
- const { t } = useTranslation('ui')
+ const { t } = useTranslation()
const [isTourOpen, setIsTourOpen] = useState(false)
const tourSteps = [
diff --git a/apps/platform/src/features/tour/components/tour-overlay.tsx b/apps/platform/src/features/tour/components/tour-overlay.tsx
index 4011060..cce29a2 100644
--- a/apps/platform/src/features/tour/components/tour-overlay.tsx
+++ b/apps/platform/src/features/tour/components/tour-overlay.tsx
@@ -1,8 +1,9 @@
import { useTranslation } from 'react-i18next'
const getTooltipPosition = (steps, currentStep, highlightPosition) => {
- if (!steps[currentStep])
+ if (!steps[currentStep]) {
return { position: 'fixed' as const, top: 0, left: 0 }
+ }
const { position = 'bottom' } = steps[currentStep]
const { top, left, width, height } = highlightPosition
@@ -47,7 +48,7 @@ const TourOverlay = ({
onPrev,
onClose,
}) => {
- const { t } = useTranslation('ui')
+ const { t } = useTranslation()
const currentStepData = steps[currentStep]
return (
diff --git a/apps/platform/src/features/workflows/hooks/use-executions.ts b/apps/platform/src/features/workflows/hooks/use-executions.ts
index 70c6428..89b5493 100644
--- a/apps/platform/src/features/workflows/hooks/use-executions.ts
+++ b/apps/platform/src/features/workflows/hooks/use-executions.ts
@@ -62,7 +62,9 @@ export function useExecutionActions(organizationId: string | null | undefined) {
const remove = useCallback(
async (executionId: string) => {
- if (!organizationId) return null
+ if (!organizationId) {
+ return null
+ }
await executionsService.delete(organizationId, executionId)
await invalidateList()
},
@@ -71,7 +73,9 @@ export function useExecutionActions(organizationId: string | null | undefined) {
const bulkRemove = useCallback(
async (payload: unknown) => {
- if (!organizationId) return null
+ if (!organizationId) {
+ return null
+ }
await executionsService.bulkDelete(organizationId, payload as never)
await invalidateList()
},
diff --git a/apps/platform/src/features/workflows/hooks/use-triggers.ts b/apps/platform/src/features/workflows/hooks/use-triggers.ts
index 34ebdfb..fb929c2 100644
--- a/apps/platform/src/features/workflows/hooks/use-triggers.ts
+++ b/apps/platform/src/features/workflows/hooks/use-triggers.ts
@@ -42,7 +42,9 @@ export function useTriggerActions(organizationId: string | null | undefined) {
const update = useCallback(
async (triggerId: string, payload: unknown) => {
- if (!organizationId) return null
+ if (!organizationId) {
+ return null
+ }
const result = await triggersService.update(
organizationId,
triggerId,
@@ -56,7 +58,9 @@ export function useTriggerActions(organizationId: string | null | undefined) {
const regenerateWebhookToken = useCallback(
async (triggerId: string) => {
- if (!organizationId) return null
+ if (!organizationId) {
+ return null
+ }
const result = await triggersService.regenerateWebhookToken(
organizationId,
triggerId,
@@ -69,7 +73,9 @@ export function useTriggerActions(organizationId: string | null | undefined) {
const remove = useCallback(
async (triggerId: string) => {
- if (!organizationId) return null
+ if (!organizationId) {
+ return null
+ }
const result = await triggersService.delete(organizationId, triggerId)
await invalidate()
return result.data
diff --git a/apps/platform/src/features/workflows/hooks/use-workflows.ts b/apps/platform/src/features/workflows/hooks/use-workflows.ts
index 470c310..d66a76d 100644
--- a/apps/platform/src/features/workflows/hooks/use-workflows.ts
+++ b/apps/platform/src/features/workflows/hooks/use-workflows.ts
@@ -87,7 +87,9 @@ export function useWorkflowActions(organizationId: string | null | undefined) {
const create = useCallback(
async (payload: unknown) => {
- if (!organizationId) return null
+ if (!organizationId) {
+ return null
+ }
const result = await unwrap(
workflowsService.create(organizationId, payload as never),
)
@@ -99,7 +101,9 @@ export function useWorkflowActions(organizationId: string | null | undefined) {
const update = useCallback(
async (workflowId: string, payload: unknown) => {
- if (!organizationId) return null
+ if (!organizationId) {
+ return null
+ }
const result = await unwrap(
workflowsService.update(organizationId, workflowId, payload as never),
)
@@ -111,7 +115,9 @@ export function useWorkflowActions(organizationId: string | null | undefined) {
const remove = useCallback(
async (workflowId: string) => {
- if (!organizationId) return null
+ if (!organizationId) {
+ return null
+ }
const result = await unwrap(
workflowsService.delete(organizationId, workflowId),
)
@@ -123,7 +129,9 @@ export function useWorkflowActions(organizationId: string | null | undefined) {
const duplicate = useCallback(
async (workflowId: string, payload: unknown) => {
- if (!organizationId) return null
+ if (!organizationId) {
+ return null
+ }
const result = await unwrap(
workflowsService.duplicate(
organizationId,
diff --git a/apps/platform/src/features/workflows/services/executions.ts b/apps/platform/src/features/workflows/services/executions.ts
index c0e577b..1d67c8a 100644
--- a/apps/platform/src/features/workflows/services/executions.ts
+++ b/apps/platform/src/features/workflows/services/executions.ts
@@ -5,7 +5,9 @@ const getAll = async (
{ query = '', offset = 0, limit = 10 } = {},
) => {
const params = new URLSearchParams()
- if (query) params.set('query', query)
+ if (query) {
+ params.set('query', query)
+ }
params.set('offset', String(offset))
params.set('limit', String(limit))
return await platformApi.get(
diff --git a/apps/platform/src/features/workflows/services/triggers.ts b/apps/platform/src/features/workflows/services/triggers.ts
index a679b5d..665e781 100644
--- a/apps/platform/src/features/workflows/services/triggers.ts
+++ b/apps/platform/src/features/workflows/services/triggers.ts
@@ -5,7 +5,9 @@ const getAll = async (
{ query = '', offset = 0, limit = 10 } = {},
) => {
const params = new URLSearchParams()
- if (query) params.set('query', query)
+ if (query) {
+ params.set('query', query)
+ }
params.set('offset', String(offset))
params.set('limit', String(limit))
return await platformApi.get(
diff --git a/apps/platform/src/features/workflows/services/workflows.ts b/apps/platform/src/features/workflows/services/workflows.ts
index 86ba48c..305f1a4 100644
--- a/apps/platform/src/features/workflows/services/workflows.ts
+++ b/apps/platform/src/features/workflows/services/workflows.ts
@@ -5,9 +5,15 @@ const getAll = async (
params?: { offset?: number; limit?: number; query?: string },
) => {
const search = new URLSearchParams()
- if (params?.offset !== undefined) search.set('offset', String(params.offset))
- if (params?.limit !== undefined) search.set('limit', String(params.limit))
- if (params?.query) search.set('query', params.query)
+ if (params?.offset !== undefined) {
+ search.set('offset', String(params.offset))
+ }
+ if (params?.limit !== undefined) {
+ search.set('limit', String(params.limit))
+ }
+ if (params?.query) {
+ search.set('query', params.query)
+ }
const qs = search.toString()
return await platformApi.get(
`/organizations/${organizationId}/workflows${qs ? `?${qs}` : ''}`,
diff --git a/apps/platform/src/hooks/use-copy.ts b/apps/platform/src/hooks/use-copy.ts
index 18ed2ed..863531f 100644
--- a/apps/platform/src/hooks/use-copy.ts
+++ b/apps/platform/src/hooks/use-copy.ts
@@ -10,16 +10,22 @@ export function useCopy(resetMs = 2000) {
useEffect(() => {
return () => {
- if (timeoutRef.current) clearTimeout(timeoutRef.current)
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current)
+ }
}
}, [])
const copy = useCallback(
(value: string) => {
- if (!value) return
+ if (!value) {
+ return
+ }
navigator.clipboard.writeText(value)
setCopied(true)
- if (timeoutRef.current) clearTimeout(timeoutRef.current)
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current)
+ }
timeoutRef.current = setTimeout(() => setCopied(false), resetMs)
},
[resetMs],
diff --git a/apps/platform/src/hooks/use-organization-events.ts b/apps/platform/src/hooks/use-organization-events.ts
index 4a6e470..9bda76c 100644
--- a/apps/platform/src/hooks/use-organization-events.ts
+++ b/apps/platform/src/hooks/use-organization-events.ts
@@ -12,8 +12,12 @@ export function useOrganizationEvents(
) {
useEffect(() => {
const unsubscribe = wsManager.subscribe((event) => {
- if (filter?.type && event.type !== filter.type) return
- if (filter?.executionId && event.id !== filter.executionId) return
+ if (filter?.type && event.type !== filter.type) {
+ return
+ }
+ if (filter?.executionId && event.id !== filter.executionId) {
+ return
+ }
callback(event)
})
diff --git a/apps/platform/src/layouts/organization-layout.tsx b/apps/platform/src/layouts/organization-layout.tsx
index 4e13867..103f4eb 100644
--- a/apps/platform/src/layouts/organization-layout.tsx
+++ b/apps/platform/src/layouts/organization-layout.tsx
@@ -29,7 +29,9 @@ const OrganizationLayout = () => {
usePlatformFeatures()
useEffect(() => {
- if (!organizations) return
+ if (!organizations) {
+ return
+ }
setOrganizations(organizations)
if (organizationId === null && organizations.length > 0) {
setOrganizationId(organizations[0].id)
diff --git a/apps/platform/src/layouts/protected-layout.tsx b/apps/platform/src/layouts/protected-layout.tsx
index 7a98dd4..6511f96 100644
--- a/apps/platform/src/layouts/protected-layout.tsx
+++ b/apps/platform/src/layouts/protected-layout.tsx
@@ -30,7 +30,9 @@ const ProtectedLayout = () => {
}, [navigate, location])
useEffect(() => {
- if (!preferences) return
+ if (!preferences) {
+ return
+ }
syncFromBackend(preferences.theme ?? {})
if (preferences.language && preferences.language !== i18n.language) {
i18n.changeLanguage(preferences.language)
diff --git a/apps/platform/src/lib/flow-categories.ts b/apps/platform/src/lib/flow-categories.ts
index d9fb9ad..3276373 100644
--- a/apps/platform/src/lib/flow-categories.ts
+++ b/apps/platform/src/lib/flow-categories.ts
@@ -19,13 +19,15 @@ import {
} from 'lucide-react'
const DEFAULT_PREFS = {
- color: 'rgba(120, 120, 120, 1)',
+ color: 'var(--muted-foreground)',
icon: Box,
labelKey: '',
}
export const getCategoryPrefs = (key?: string) => {
- if (!key) return DEFAULT_PREFS
+ if (!key) {
+ return DEFAULT_PREFS
+ }
return (
(flowCategoryPreferences as Record)[key] ??
DEFAULT_PREFS
@@ -34,87 +36,87 @@ export const getCategoryPrefs = (key?: string) => {
export const flowCategoryPreferences = {
mailing: {
- color: 'rgba(33, 221, 102, 1)',
+ color: 'var(--category-mailing)',
icon: Mail,
labelKey: 'ui.text.category.mail',
},
genai: {
- color: 'rgba(255, 187, 0, 1)',
+ color: 'var(--category-genai)',
icon: Sparkles,
labelKey: 'ui.text.category.ai',
},
ai: {
- color: 'rgba(255, 187, 0, 1)',
+ color: 'var(--category-ai)',
icon: Sparkles,
labelKey: 'ui.text.category.ai',
},
image: {
- color: 'rgba(69, 221, 222, 1)',
+ color: 'var(--category-image)',
icon: Image,
labelKey: 'ui.text.category.image',
},
googleworkspace: {
- color: 'rgba(11, 187, 255, 1)',
+ color: 'var(--category-googleworkspace)',
icon: Cloud,
labelKey: 'ui.text.category.googleWorkspace',
},
- mediapublishing: {
- color: 'rgba(182, 86, 255, 1)',
+ publishing: {
+ color: 'var(--category-publishing)',
icon: MonitorUp,
labelKey: 'ui.text.category.publishing',
},
system: {
- color: 'rgba(118, 162, 195, 1)',
+ color: 'var(--category-system)',
icon: Wrench,
labelKey: 'ui.text.category.system',
},
audio: {
- color: 'rgba(7, 119, 255, 1)',
+ color: 'var(--category-audio)',
icon: AudioLines,
labelKey: 'ui.text.category.audio',
},
video: {
- color: 'rgba(255, 86, 119, 1)',
+ color: 'var(--category-video)',
icon: Video,
labelKey: 'ui.text.category.video',
},
blockchain: {
- color: 'rgba(252, 165, 3, 1)',
+ color: 'var(--category-blockchain)',
icon: Blocks,
labelKey: 'ui.text.category.blockchain',
},
database: {
- color: 'rgba(19, 184, 166, 1)',
+ color: 'var(--category-database)',
icon: Database,
labelKey: 'ui.text.category.database',
},
marketing: {
- color: 'rgba(239, 68, 68, 1)',
+ color: 'var(--category-marketing)',
icon: TrendingUp,
labelKey: 'ui.text.category.marketing',
},
ecommerce: {
- color: 'rgba(219, 39, 119, 1)',
+ color: 'var(--category-ecommerce)',
icon: ShoppingCart,
labelKey: 'ui.text.category.ecommerce',
},
crm: {
- color: 'rgba(79, 70, 229, 1)',
+ color: 'var(--category-crm)',
icon: Users,
labelKey: 'ui.text.category.crm',
},
productivity: {
- color: 'rgba(132, 204, 22, 1)',
+ color: 'var(--category-productivity)',
icon: Calendar,
labelKey: 'ui.text.category.productivity',
},
analytics: {
- color: 'rgba(217, 119, 6, 1)',
+ color: 'var(--category-analytics)',
icon: BarChart3,
labelKey: 'ui.text.category.analytics',
},
utility: {
- color: 'rgba(100, 116, 139, 1)',
+ color: 'var(--category-utility)',
icon: Lightbulb,
labelKey: 'ui.text.category.utility',
},
diff --git a/apps/platform/src/lib/i18n.ts b/apps/platform/src/lib/i18n.ts
index 1a43562..864b53d 100644
--- a/apps/platform/src/lib/i18n.ts
+++ b/apps/platform/src/lib/i18n.ts
@@ -3,9 +3,6 @@ import { initReactI18next } from 'react-i18next'
import LanguageDetector from 'i18next-browser-languagedetector'
import HttpBackend from 'i18next-http-backend'
-// The app uses a single `ui` namespace, loaded eagerly at app boot.
-const criticalNamespaces = ['ui']
-
i18n
.use(HttpBackend)
.use(LanguageDetector)
@@ -21,7 +18,7 @@ i18n
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
- ns: criticalNamespaces,
+ ns: 'ui',
defaultNS: 'ui',
fallbackNS: 'ui',
partialBundledLanguages: true,
diff --git a/apps/platform/src/lib/token-manager.ts b/apps/platform/src/lib/token-manager.ts
index d46a332..8d16d9a 100644
--- a/apps/platform/src/lib/token-manager.ts
+++ b/apps/platform/src/lib/token-manager.ts
@@ -25,9 +25,13 @@ const clearTokens = () => {
const decodeAccessToken = >(): T | null => {
const token = getAccessToken()
- if (!token) return null
+ if (!token) {
+ return null
+ }
const parts = token.split('.')
- if (parts.length !== 3) return null
+ if (parts.length !== 3) {
+ return null
+ }
try {
const base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/')
const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4)
diff --git a/apps/platform/src/lib/ws-manager.ts b/apps/platform/src/lib/ws-manager.ts
index d3bc7b3..32595c1 100644
--- a/apps/platform/src/lib/ws-manager.ts
+++ b/apps/platform/src/lib/ws-manager.ts
@@ -97,14 +97,18 @@ class WSManager {
}
private setStatus(status: WsStatus) {
- if (this.status === status) return
+ if (this.status === status) {
+ return
+ }
this.status = status
this.statusListeners.forEach((listener) => listener(status))
}
private createConnection() {
const token = tokenManager.getAccessToken()
- if (!token || !this.organizationId) return
+ if (!token || !this.organizationId) {
+ return
+ }
const wsUrl = BASE_URL.replace(/^http/, 'ws')
@@ -128,7 +132,9 @@ class WSManager {
this.ws.onclose = () => {
this.ws = null
- if (this.shouldReconnect) this.setStatus('reconnecting')
+ if (this.shouldReconnect) {
+ this.setStatus('reconnecting')
+ }
this.scheduleReconnect()
}
@@ -138,7 +144,9 @@ class WSManager {
}
private scheduleReconnect() {
- if (!this.shouldReconnect || !this.organizationId) return
+ if (!this.shouldReconnect || !this.organizationId) {
+ return
+ }
this.reconnectTimer = setTimeout(() => {
this.createConnection()
diff --git a/apps/platform/src/pages/auth/email-change/confirm/page.tsx b/apps/platform/src/pages/auth/email-change/confirm/page.tsx
index 84de974..3c949ec 100644
--- a/apps/platform/src/pages/auth/email-change/confirm/page.tsx
+++ b/apps/platform/src/pages/auth/email-change/confirm/page.tsx
@@ -23,11 +23,15 @@ function AuthEmailChangeConfirmPage() {
authService
.confirmEmailChange({ token })
.then((response) => {
- if (cancelled) return
+ if (cancelled) {
+ return
+ }
setStatus(response.isSuccess ? 'success' : 'error')
})
.catch(() => {
- if (!cancelled) setStatus('error')
+ if (!cancelled) {
+ setStatus('error')
+ }
})
return () => {
cancelled = true
diff --git a/apps/platform/src/pages/auth/forgot-password/page.tsx b/apps/platform/src/pages/auth/forgot-password/page.tsx
index 6b22fb9..82a1cfe 100644
--- a/apps/platform/src/pages/auth/forgot-password/page.tsx
+++ b/apps/platform/src/pages/auth/forgot-password/page.tsx
@@ -15,11 +15,15 @@ function AuthForgotPasswordPage() {
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
- if (!email.trim()) return
+ if (!email.trim()) {
+ return
+ }
setSubmitting(true)
try {
const response = await authService.requestPasswordReset({ email })
- if (!response.isSuccess) return
+ if (!response.isSuccess) {
+ return
+ }
setSubmitted(true)
} catch (error) {
console.error('Password reset request error:', error)
@@ -51,9 +55,7 @@ function AuthForgotPasswordPage() {
) : (