JacobJA's picture
Clean fresh deployment
34b6cef
Raw
History Blame Contribute Delete
7.04 kB
'use client';
import React from 'react';
import { motion } from 'framer-motion';
import type {
WorkflowStep,
WorkflowNodeType,
} from './WorkflowTypes';
interface Props {
step: WorkflowStep;
index: number;
updateStep: (
id: string,
field: 'label' | 'type',
value: string
) => void;
removeStep: (id: string) => void;
}
const NODE_OPTIONS: {
value: WorkflowNodeType;
label: string;
icon: string;
}[] = [
{ value: 'task', label: 'Task', icon: '□' },
{ value: 'decision', label: 'Decision', icon: '◇' },
{ value: 'approval', label: 'Approval', icon: '✓' },
{ value: 'api', label: 'API', icon: '⚡' },
{ value: 'queue', label: 'Queue', icon: '⇄' },
{ value: 'llm', label: 'LLM', icon: '🤖' },
{
value: 'human_review',
label: 'Human Review',
icon: '🧑',
},
{
value: 'notification',
label: 'Notification',
icon: '📩',
},
];
function getAccent(type: WorkflowNodeType) {
switch (type) {
case 'decision':
return '#F59E0B';
case 'approval':
return '#22C55E';
case 'api':
return '#3B82F6';
case 'queue':
return '#8B5CF6';
case 'llm':
return '#EC4899';
case 'human_review':
return '#F97316';
case 'notification':
return '#06B6D4';
default:
return '#00D4FF';
}
}
export default function WorkflowNode({
step,
index,
updateStep,
removeStep,
}: Props) {
const accent = getAccent(step.type);
const isDecision =
step.type === 'decision';
return (
<motion.div
initial={{
opacity: 0,
y: 20,
}}
animate={{
opacity: 1,
y: 0,
}}
style={{
marginBottom: '20px',
}}
>
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '16px',
}}
>
{/* Node Shape */}
<div
style={{
width: 58,
height: 58,
minWidth: 58,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
border: `1px solid ${accent}30`,
background: `${accent}08`,
color: accent,
fontSize: '18px',
fontWeight: 700,
transform: isDecision
? 'rotate(45deg)'
: 'none',
borderRadius: isDecision
? '8px'
: '14px',
}}
>
<span
style={{
transform: isDecision
? 'rotate(-45deg)'
: 'none',
}}
>
{isDecision
? '?'
: String(index + 1).padStart(
2,
'0'
)}
</span>
</div>
{/* Main Card */}
<div
style={{
flex: 1,
display: 'flex',
alignItems: 'center',
gap: '14px',
border:
'1px solid rgba(255,255,255,0.08)',
background:
'rgba(255,255,255,0.02)',
borderRadius: '18px',
padding: '18px 20px',
backdropFilter: 'blur(8px)',
}}
>
{/* Type */}
<select
value={step.type}
onChange={(e) =>
updateStep(
step.id,
'type',
e.target.value
)
}
style={{
border: 'none',
background: 'transparent',
color: accent,
fontWeight: 600,
fontSize: '14px',
outline: 'none',
minWidth: 120,
}}
>
{NODE_OPTIONS.map((item) => (
<option
key={item.value}
value={item.value}
style={{
background: '#111827',
color: '#ffffff',
}}
>
{item.icon} {item.label}
</option>
))}
</select>
{/* Input */}
<input
value={step.label}
onChange={(e) =>
updateStep(
step.id,
'label',
e.target.value
)
}
placeholder="Describe workflow step..."
style={{
flex: 1,
border: 'none',
background: 'transparent',
color: '#F0F0FF',
fontSize: '16px',
outline: 'none',
}}
/>
{/* Remove */}
<button
onClick={() =>
removeStep(step.id)
}
style={{
border: 'none',
background: 'transparent',
color: '#EF4444',
fontWeight: 600,
cursor: 'pointer',
fontSize: '14px',
}}
>
Remove
</button>
</div>
</div>
</motion.div>
);
}