diff --git a/apps/react-playground/src/App.styles.tsx b/apps/react-playground/src/App.styles.tsx index 6530380a5..0d2dc6cf6 100644 --- a/apps/react-playground/src/App.styles.tsx +++ b/apps/react-playground/src/App.styles.tsx @@ -8,5 +8,5 @@ export const AppContainer = styled.div` export const MainContent = styled.main` flex: 1; - margin-left: 280px; /* Width of the sidebar */ + margin-left: 220px; /* Width of the sidebar */ `; diff --git a/apps/react-playground/src/App.tsx b/apps/react-playground/src/App.tsx index 8229210f6..c91962416 100644 --- a/apps/react-playground/src/App.tsx +++ b/apps/react-playground/src/App.tsx @@ -65,6 +65,8 @@ import { MaterialTextField } from "./pages/material/MaterialTextField"; import { MaterialTooltip } from "./pages/material/MaterialTooltip"; import { MaterialTypography } from "./pages/material/MaterialTypography"; import { ApSankeyShowcase } from "./pages/components/ApSankeyShowcase"; +import { ExecutionTargetPrototype } from "./pages/prototypes/ExecutionTargetPrototype"; +import { UserEventTriggersPrototype } from "./pages/prototypes/UserEventTriggersPrototype"; function App() { return ( @@ -202,6 +204,14 @@ function App() { path="/components/sankey-diagram" element={} /> + } + /> + } + /> diff --git a/apps/react-playground/src/components/Sidebar.styles.tsx b/apps/react-playground/src/components/Sidebar.styles.tsx index 6a038116c..ba7c1f15a 100644 --- a/apps/react-playground/src/components/Sidebar.styles.tsx +++ b/apps/react-playground/src/components/Sidebar.styles.tsx @@ -1,126 +1,271 @@ import { Link } from "react-router-dom"; import styled from "styled-components"; +// ─── Shell ──────────────────────────────────────────────────────────────────── + export const SidebarContainer = styled.aside` position: fixed; top: 0; left: 0; bottom: 0; - width: 280px; - background: var(--color-background); - border-right: 1px solid var(--color-border); + width: 220px; + background: var(--color-background-raised); + border-right: 1px solid var(--color-border-de-emp); z-index: 1000; - overflow-y: auto; + display: flex; + flex-direction: column; + overflow: hidden; `; export const SidebarNav = styled.nav` - padding: 0; + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; `; -export const NavSection = styled.div``; +// ─── UiPath header ──────────────────────────────────────────────────────────── -export const NavLink = styled(Link)<{ $isActive: boolean }>` +export const OrchestratorHeader = styled.div` display: flex; align-items: center; - gap: 12px; - padding: 10px 20px; - text-decoration: none; - color: ${(props) => - props.$isActive ? "var(--color-primary)" : "var(--color-foreground-emp)"}; - background: ${(props) => - props.$isActive ? "var(--color-background-selected)" : "transparent"}; - border-left: ${(props) => - props.$isActive - ? "4px solid var(--color-primary)" - : "4px solid transparent"}; - font-weight: ${(props) => (props.$isActive ? "600" : "500")}; + gap: 8px; + padding: 13px 14px 11px; + border-bottom: 1px solid var(--color-border-de-emp); + flex-shrink: 0; +`; + +export const OrchestratorLogo = styled.div` + width: 26px; + height: 26px; + border-radius: 5px; + background: var(--color-foreground-highlight); + display: flex; + align-items: center; + justify-content: center; font-size: 14px; - transition: all 0.2s ease; + color: #fff; + font-weight: 800; + flex-shrink: 0; +`; + +export const OrchestratorTitle = styled.span` + font-size: 13px; + font-weight: 700; + color: var(--color-foreground-emp); +`; + +// ─── Top fixed nav items ────────────────────────────────────────────────────── + +export const TopNav = styled.div` + padding: 6px 0 0; + flex-shrink: 0; +`; +export const TopNavItem = styled.button<{ $isActive?: boolean }>` + display: flex; + align-items: center; + gap: 9px; + width: 100%; + padding: 7px 14px; + border: none; + background: ${({ $isActive }) => + $isActive ? "var(--color-background-selected)" : "transparent"}; + color: ${({ $isActive }) => + $isActive ? "var(--color-primary)" : "var(--color-foreground-emp)"}; + font-size: 13px; + font-weight: ${({ $isActive }) => ($isActive ? "600" : "500")}; + cursor: pointer; + text-align: left; + border-left: 3px solid ${({ $isActive }) => + $isActive ? "var(--color-primary)" : "transparent"}; + transition: background 0.12s, color 0.12s; &:hover { - background: ${(props) => - props.$isActive - ? "var(--color-background-selected)" - : "var(--color-background-hover)"}; + background: ${({ $isActive }) => + $isActive ? "var(--color-background-selected)" : "var(--color-background-hover)"}; } `; -export const NavIcon = styled.span` - font-size: 16px; +export const TopNavIcon = styled.span` + font-size: 14px; + width: 16px; + text-align: center; + flex-shrink: 0; +`; + +// ─── Folder search ──────────────────────────────────────────────────────────── + +export const FolderSearchWrap = styled.div` + display: flex; + align-items: center; + gap: 7px; + margin: 6px 10px 4px; + border: 1px solid var(--color-border); + border-radius: 5px; + padding: 5px 9px; + background: var(--color-background-edit); + flex-shrink: 0; +`; + +export const FolderSearchIcon = styled.span` + font-size: 12px; + color: var(--color-foreground-de-emp); + flex-shrink: 0; +`; + +export const FolderSearchInput = styled.input` + border: none; + background: transparent; + font-size: 12px; + color: var(--color-foreground); + outline: none; + width: 100%; + &::placeholder { color: var(--color-foreground-de-emp); } `; -export const NavLabel = styled.span` +// ─── Folder tree ───────────────────────────────────────────────────────────── + +export const FolderTree = styled.div` flex: 1; + overflow-y: auto; + padding: 2px 0 6px; + &::-webkit-scrollbar { width: 4px; } + &::-webkit-scrollbar-thumb { + background: var(--color-border); + border-radius: 4px; + } `; -export const ParentNavButton = styled.button<{ $isActive: boolean }>` +export const FolderItem = styled.button<{ $isActive: boolean; $depth?: number }>` display: flex; align-items: center; - gap: 12px; - padding: 10px 20px; + gap: 5px; width: 100%; - text-align: left; + padding: 5px 10px 5px ${({ $depth }) => 10 + ($depth ?? 0) * 14}px; border: none; + background: ${({ $isActive }) => + $isActive ? "var(--color-background-selected)" : "transparent"}; + color: ${({ $isActive }) => + $isActive ? "var(--color-primary)" : "var(--color-foreground)"}; + font-size: 12px; + font-weight: ${({ $isActive }) => ($isActive ? "600" : "400")}; cursor: pointer; - color: ${(props) => - props.$isActive ? "var(--color-primary)" : "var(--color-foreground-emp)"}; - background: ${(props) => - props.$isActive ? "var(--color-background-selected)" : "transparent"}; - border-left: ${(props) => - props.$isActive - ? "4px solid var(--color-primary)" - : "4px solid transparent"}; - font-weight: ${(props) => (props.$isActive ? "600" : "500")}; - font-size: 14px; - transition: all 0.2s ease; - + text-align: left; + border-left: 3px solid ${({ $isActive }) => + $isActive ? "var(--color-primary)" : "transparent"}; + transition: background 0.1s, color 0.1s; + white-space: nowrap; &:hover { - background: ${(props) => - props.$isActive - ? "var(--color-background-selected)" - : "var(--color-background-hover)"}; + background: ${({ $isActive }) => + $isActive ? "var(--color-background-selected)" : "var(--color-background-hover)"}; + color: ${({ $isActive }) => + $isActive ? "var(--color-primary)" : "var(--color-foreground-emp)"}; } `; -export const ChevronIcon = styled.span<{ $isExpanded: boolean }>` - font-size: 10px; - transition: transform 0.2s ease; - transform: ${(props) => - props.$isExpanded ? "rotate(0deg)" : "rotate(-90deg)"}; +export const FolderChevron = styled.span<{ $expanded: boolean; $visible: boolean }>` + font-size: 8px; color: var(--color-foreground-de-emp); + width: 10px; + flex-shrink: 0; + visibility: ${({ $visible }) => ($visible ? "visible" : "hidden")}; + transform: rotate(${({ $expanded }) => ($expanded ? "90deg" : "0deg")}); + transition: transform 0.15s ease; +`; + +export const FolderIcon = styled.span` + font-size: 13px; + flex-shrink: 0; `; -export const SubNav = styled.div<{ $isExpanded: boolean }>` - padding-left: 20px; - max-height: ${(props) => (props.$isExpanded ? "2000px" : "0")}; +export const FolderLabel = styled.span` + flex: 1; overflow: hidden; - transition: max-height 0.3s ease; + text-overflow: ellipsis; + white-space: nowrap; `; -export const SubNavLink = styled(Link)<{ $isActive: boolean }>` - display: block; - padding: 8px 20px; - text-decoration: none; - color: ${(props) => - props.$isActive - ? "var(--color-primary)" - : "var(--color-foreground-de-emp)"}; - background: ${(props) => - props.$isActive ? "var(--color-background-selected)" : "transparent"}; - border-left: ${(props) => - props.$isActive - ? "3px solid var(--color-primary)" - : "3px solid transparent"}; - font-weight: ${(props) => (props.$isActive ? "600" : "400")}; - font-size: 13px; - transition: all 0.2s ease; +// ─── Divider ───────────────────────────────────────────────────────────────── + +export const SidebarDivider = styled.div` + height: 1px; + background: var(--color-border-de-emp); + margin: 4px 0; + flex-shrink: 0; +`; + +// ─── Prototypes section ─────────────────────────────────────────────────────── +export const PrototypesSection = styled.div` + flex-shrink: 0; + border-top: 1px solid var(--color-border-de-emp); + padding: 4px 0 6px; +`; + +export const PrototypesHeader = styled.button<{ $isActive: boolean }>` + display: flex; + align-items: center; + gap: 9px; + width: 100%; + padding: 7px 14px; + border: none; + background: transparent; + color: ${({ $isActive }) => + $isActive ? "var(--color-primary)" : "var(--color-foreground-emp)"}; + font-size: 12px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + cursor: pointer; + text-align: left; + transition: color 0.12s; + &:hover { color: var(--color-foreground-emp); } +`; + +export const PrototypesChevron = styled.span<{ $expanded: boolean }>` + font-size: 9px; + margin-left: auto; + color: var(--color-foreground-de-emp); + transform: rotate(${({ $expanded }) => ($expanded ? "0deg" : "-90deg")}); + transition: transform 0.15s ease; +`; + +export const PrototypesLinks = styled.div<{ $expanded: boolean }>` + max-height: ${({ $expanded }) => ($expanded ? "400px" : "0")}; + overflow: hidden; + transition: max-height 0.2s ease; +`; + +export const ProtoLink = styled(Link)<{ $isActive: boolean }>` + display: flex; + align-items: center; + gap: 8px; + padding: 6px 14px 6px 30px; + font-size: 12px; + text-decoration: none; + color: ${({ $isActive }) => + $isActive ? "var(--color-primary)" : "var(--color-foreground-de-emp)"}; + font-weight: ${({ $isActive }) => ($isActive ? "600" : "400")}; + border-left: 3px solid ${({ $isActive }) => + $isActive ? "var(--color-primary)" : "transparent"}; + background: ${({ $isActive }) => + $isActive ? "var(--color-background-selected)" : "transparent"}; + transition: background 0.1s, color 0.1s; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; &:hover { - background: ${(props) => - props.$isActive - ? "var(--color-background-selected)" - : "var(--color-background-hover)"}; - color: ${(props) => - props.$isActive ? "var(--color-primary)" : "var(--color-foreground-emp)"}; + background: var(--color-background-hover); + color: var(--color-foreground-emp); } `; + +// Keep these exports so nothing that imports them breaks +export const NavSection = styled.div``; +export const NavLink = styled(Link)<{ $isActive: boolean }>``; +export const NavIcon = styled.span``; +export const NavLabel = styled.span``; +export const ParentNavButton = styled.button<{ $isActive: boolean }>``; +export const ChevronIcon = styled.span<{ $isExpanded: boolean }>``; +export const SubNav = styled.div<{ $isExpanded: boolean }>``; +export const SubNavLink = styled(Link)<{ $isActive: boolean }>``; diff --git a/apps/react-playground/src/components/Sidebar.tsx b/apps/react-playground/src/components/Sidebar.tsx index c730e741f..6254ae2f4 100644 --- a/apps/react-playground/src/components/Sidebar.tsx +++ b/apps/react-playground/src/components/Sidebar.tsx @@ -1,163 +1,221 @@ import { useState } from "react"; import { useLocation } from "react-router-dom"; import { - ChevronIcon, - NavIcon, - NavLabel, - NavLink, - NavSection, - ParentNavButton, + FolderChevron, + FolderIcon, + FolderItem, + FolderLabel, + FolderSearchIcon, + FolderSearchInput, + FolderSearchWrap, + FolderTree, + OrchestratorHeader, + OrchestratorLogo, + OrchestratorTitle, + ProtoLink, + PrototypesChevron, + PrototypesHeader, + PrototypesLinks, + PrototypesSection, SidebarContainer, + SidebarDivider, SidebarNav, - SubNav, - SubNavLink, + TopNav, + TopNavIcon, + TopNavItem, } from "./Sidebar.styles"; +// ───────────────────────────────────────────────────────────────────────────── +// Folder tree mock data (matches the Orchestrator screenshot) +// ───────────────────────────────────────────────────────────────────────────── + +interface FolderNode { + id: string; + label: string; + children?: FolderNode[]; +} + +const FOLDER_TREE: FolderNode[] = [ + { id: "demo-gs", label: "Demo GS Invoice Proce…" }, + { id: "lead-scoring", label: "Lead scoring agent 2 1…" }, + { + id: "maestro-1", + label: "Maestro w multiple ent…", + children: [ + { id: "maestro-1a", label: "Sub-folder A" }, + { id: "maestro-1b", label: "Sub-folder B" }, + ], + }, + { id: "maestro-2", label: "Maestro w multiple ent…" }, + { id: "maestro-3", label: "Maestro w multiple ent…" }, + { id: "maestro-4", label: "Maestro w multiple ent…" }, + { id: "maestro-5", label: "Maestro w multiple ent…" }, + { id: "solution-api", label: "Solution w API" }, + { id: "solution-28", label: "Solution 28" }, + { id: "solution-cs", label: "Solution with CS" }, + { id: "solution-cs2", label: "Solution with CS 2" }, + { id: "agent-3api", label: "agent with 3 api wfs" }, + { id: "api-4", label: "api 4" }, + { + id: "demo-trigger", + label: "demo trigger", + children: [ + { id: "demo-trigger-a", label: "Child trigger A" }, + ], + }, + { id: "destination-high", label: "destination high" }, + { id: "destination-low", label: "destination low" }, + { id: "dev-work", label: "dev work" }, + { id: "fara-numere", label: "fara numere" }, + { id: "folder-w-feed", label: "folder w feed" }, + { id: "hw-solution", label: "hw solution" }, + { id: "interm", label: "interm" }, +]; + +const PROTOTYPES = [ + { label: "Execution Target (OR-92995)", path: "/prototypes/execution-target" }, + { label: "User Event Triggers", path: "/prototypes/user-event-triggers" }, +]; + +// ───────────────────────────────────────────────────────────────────────────── + export function Sidebar() { const location = useLocation(); - const [expandedMenus, setExpandedMenus] = useState([ - "/core", - "/material", - "/components", - ]); - - const navigation = [ - { - label: "Home", - path: "/", - icon: "🏠", - }, - { - label: "Core", - path: "/core", - icon: "🎨", - children: [ - { label: "Borders & Strokes", path: "/core/borders" }, - { label: "Breakpoints", path: "/core/screens" }, - { label: "Colors", path: "/core/colors" }, - { label: "CSS Variables", path: "/core/css-variables" }, - { label: "Icons", path: "/core/icons" }, - { label: "Shadows", path: "/core/shadows" }, - { label: "Spacing & Padding", path: "/core/spacing" }, - { label: "Typography", path: "/core/fonts" }, - ], - }, - { - label: "Components", - path: "/components", - icon: "🧩", - children: [ - { label: "Accordion", path: "/components/accordion" }, - { label: "Alert Bar", path: "/components/alert-bar" }, - { label: "Badge", path: "/components/badge" }, - { label: "Button", path: "/components/button" }, - { label: "Chat", path: "/components/chat" }, - { label: "Chip", path: "/components/chip" }, - { label: "Circular Progress", path: "/components/circular-progress" }, - { label: "Icon", path: "/components/icon" }, - { label: "Icon Button", path: "/components/icon-button" }, - { label: "Link", path: "/components/link" }, - { label: "Menu", path: "/components/menu" }, - { label: "Modal", path: "/components/modal" }, - { label: "Popover", path: "/components/popover" }, - { label: "Progress Spinner", path: "/components/progress-spinner" }, - { label: "Sankey Diagram", path: "/components/sankey-diagram" }, - { label: "Skeleton", path: "/components/skeleton" }, - { label: "Text Area", path: "/components/text-area" }, - { label: "Text Field", path: "/components/text-field" }, - { label: "Tool Call", path: "/components/tool-call" }, - { label: "Tooltip", path: "/components/tooltip" }, - { label: "Tree View", path: "/components/tree-view" }, - { label: "Typography", path: "/components/typography" }, - ], - }, - { - label: "Material Overrides", - path: "/material", - icon: "🎭", - children: [ - { label: "Alert", path: "/material/alert" }, - { label: "Autocomplete", path: "/material/autocomplete" }, - { label: "Button Base", path: "/material/button-base" }, - { label: "Buttons", path: "/material/buttons" }, - { label: "Checkbox", path: "/material/checkbox" }, - { label: "Chip", path: "/material/chip" }, - { label: "Circular Progress", path: "/material/circular-progress" }, - { label: "Datepicker", path: "/material/datepicker" }, - { label: "Dialog", path: "/material/dialog" }, - { label: "Divider", path: "/material/divider" }, - { label: "Form Controls", path: "/material/form-controls" }, - { label: "Input Base", path: "/material/input-base" }, - { label: "Inputs", path: "/material/inputs" }, - { label: "Linear Progress", path: "/material/linear-progress" }, - { label: "Link", path: "/material/link" }, - { label: "List", path: "/material/list" }, - { label: "Menu Item", path: "/material/menu-item" }, - { label: "Radio", path: "/material/radio" }, - { label: "Select", path: "/material/select" }, - { label: "Slider", path: "/material/slider" }, - { label: "Snackbar", path: "/material/snackbar" }, - { label: "Stepper", path: "/material/stepper" }, - { label: "Switch", path: "/material/switch" }, - { label: "Tabs", path: "/material/tabs" }, - { label: "Text Field", path: "/material/text-field" }, - { label: "Tooltip", path: "/material/tooltip" }, - { label: "Typography", path: "/material/typography" }, - ], - }, - ]; - - const isActivePath = (path: string) => { - if (path === "/") { - return location.pathname === "/"; - } - return location.pathname.startsWith(path); - }; - - const toggleMenu = (path: string) => { - setExpandedMenus((prev) => - prev.includes(path) ? prev.filter((p) => p !== path) : [...prev, path], - ); - }; - const isExpanded = (path: string) => expandedMenus.includes(path); + const [selectedFolder, setSelectedFolder] = useState("maestro-5"); + const [expandedFolders, setExpandedFolders] = useState>( + new Set(["maestro-1"]), + ); + const [folderSearch, setFolderSearch] = useState(""); + const [activeTopNav, setActiveTopNav] = useState(null); + const [prototypesExpanded, setPrototypesExpanded] = useState(true); + + const onPrototypePath = location.pathname.startsWith("/prototypes"); + + function toggleFolder(id: string) { + setExpandedFolders((prev) => { + const next = new Set(prev); + if (next.has(id)) next.delete(id); + else next.add(id); + return next; + }); + } + + function matchesSearch(label: string) { + if (!folderSearch) return true; + return label.toLowerCase().includes(folderSearch.toLowerCase()); + } + + function renderFolder(node: FolderNode, depth = 0) { + const hasChildren = !!node.children?.length; + const expanded = expandedFolders.has(node.id); + const isActive = selectedFolder === node.id; + + // Filter: show node if it or any descendant matches search + const selfMatches = matchesSearch(node.label); + const childMatches = node.children?.some((c) => matchesSearch(c.label)); + if (!selfMatches && !childMatches) return null; + + return ( +
+ { + setSelectedFolder(node.id); + setActiveTopNav(null); + if (hasChildren) toggleFolder(node.id); + }} + title={node.label} + > + + ▶ + + {hasChildren ? (expanded ? "📂" : "📁") : "📁"} + {node.label} + + + {hasChildren && expanded && ( +
+ {node.children!.map((child) => renderFolder(child, depth + 1))} +
+ )} +
+ ); + } return ( + {/* ── UiPath Orchestrator branding ────────────── */} + + U + Orchestrator + + - {navigation.map((item) => ( - - {item.children ? ( - toggleMenu(item.path)} - $isActive={isActivePath(item.path)} + {/* ── Top nav items ─────────────────────────── */} + + {[ + { id: "tenant", icon: "🏢", label: "Tenant" }, + { id: "registry", icon: "📦", label: "Registry" }, + { id: "my-folders", icon: "📂", label: "My Folders" }, + ].map(({ id, icon, label }) => ( + { + setActiveTopNav((prev) => (prev === id ? null : id)); + setSelectedFolder(""); + }} + > + {icon} + {label} + + ))} + + + + + {/* ── Folder search ──────────────────────────── */} + + 🔍 + setFolderSearch(e.target.value)} + /> + + + {/* ── Folder tree ────────────────────────────── */} + + {FOLDER_TREE.map((node) => renderFolder(node))} + + + {/* ── Prototypes nav ─────────────────────────── */} + + setPrototypesExpanded((p) => !p)} + > + 🧪 Prototypes + + + + {PROTOTYPES.map(({ label, path }) => ( + - {item.icon} - {item.label} - - - ) : ( - - {item.icon} - {item.label} - - )} - - {item.children && ( - - {item.children.map((child) => ( - - {child.label} - - ))} - - )} - - ))} + {label} + + ))} + + ); diff --git a/apps/react-playground/src/pages/prototypes/ExecutionTargetPrototype.tsx b/apps/react-playground/src/pages/prototypes/ExecutionTargetPrototype.tsx new file mode 100644 index 000000000..ed623a251 --- /dev/null +++ b/apps/react-playground/src/pages/prototypes/ExecutionTargetPrototype.tsx @@ -0,0 +1,548 @@ +/** + * Prototype for OR-92995 + * "Enable Run as Myself in context of Windows automations and rename in Execution Target" + * + * Changes prototyped: + * 1. Rename "Run As Myself" → "Inherit parent job identity" + * 2. Allow the option for Windows processes with a warning callout + */ + +import { useState } from "react"; +import styled from "styled-components"; + +// ─── Layout ────────────────────────────────────────────────────────────────── + +const Page = styled.div` + padding: 32px; + display: flex; + flex-direction: column; + gap: 32px; +`; + +const PageHeader = styled.div` + display: flex; + flex-direction: column; + gap: 6px; +`; + +const PageTitle = styled.h1` + font-size: 22px; + font-weight: 700; + color: var(--color-foreground-emp); + margin: 0; +`; + +const PageSubtitle = styled.p` + font-size: 13px; + color: var(--color-foreground-de-emp); + margin: 0; +`; + +const JiraChip = styled.a` + display: inline-flex; + align-items: center; + gap: 5px; + padding: 3px 10px; + background: var(--color-info-background); + color: var(--color-info-text); + border-radius: 4px; + font-size: 12px; + font-weight: 600; + text-decoration: none; + width: fit-content; + &:hover { text-decoration: underline; } +`; + +// ─── Process type toggle ────────────────────────────────────────────────────── + +const ToggleRow = styled.div` + display: flex; + gap: 0; + border-radius: 6px; + border: 1px solid var(--color-border); + overflow: hidden; + width: fit-content; +`; + +const ToggleButton = styled.button<{ $active: boolean }>` + padding: 7px 18px; + font-size: 13px; + font-weight: 600; + border: none; + cursor: pointer; + transition: background 0.15s, color 0.15s; + background: ${({ $active }) => + $active ? "var(--color-primary)" : "var(--color-background)"}; + color: ${({ $active }) => + $active ? "#fff" : "var(--color-foreground-de-emp)"}; + &:hover { + background: ${({ $active }) => + $active ? "var(--color-primary-hover)" : "var(--color-background-secondary)"}; + } +`; + +// ─── Panel shell ───────────────────────────────────────────────────────────── + +const PanelWrap = styled.div` + display: flex; + flex-direction: column; + gap: 0; + border: 1px solid var(--color-border); + border-radius: 8px; + background: var(--color-background-raised); + overflow: hidden; + max-width: 580px; + box-shadow: 0 2px 8px rgba(0,0,0,0.06); +`; + +const PanelHeader = styled.div` + padding: 18px 24px 14px; + border-bottom: 1px solid var(--color-border-de-emp); + background: var(--color-background-secondary); +`; + +const PanelTitle = styled.h2` + font-size: 15px; + font-weight: 700; + color: var(--color-foreground-emp); + margin: 0 0 2px; +`; + +const PanelDesc = styled.p` + font-size: 12px; + color: var(--color-foreground-de-emp); + margin: 0; +`; + +const PanelBody = styled.div` + padding: 20px 24px; + display: flex; + flex-direction: column; + gap: 10px; +`; + +const PanelFooter = styled.div` + padding: 14px 24px; + border-top: 1px solid var(--color-border-de-emp); + display: flex; + justify-content: flex-end; + gap: 8px; +`; + +// ─── Section label ──────────────────────────────────────────────────────────── + +const SectionLabel = styled.div` + font-size: 11px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--color-foreground-de-emp); + margin-bottom: 4px; +`; + +// ─── Option card ───────────────────────────────────────────────────────────── + +const OptionCard = styled.label<{ $selected: boolean; $disabled?: boolean }>` + display: flex; + align-items: flex-start; + gap: 12px; + padding: 14px 16px; + border-radius: 6px; + border: 1.5px solid ${({ $selected }) => + $selected ? "var(--color-primary)" : "var(--color-border-de-emp)"}; + background: ${({ $selected }) => + $selected ? "var(--color-background-selected)" : "var(--color-background)"}; + cursor: ${({ $disabled }) => ($disabled ? "not-allowed" : "pointer")}; + opacity: ${({ $disabled }) => ($disabled ? 0.5 : 1)}; + transition: border-color 0.15s, background 0.15s; + &:hover { + border-color: ${({ $selected, $disabled }) => + $disabled ? "var(--color-border-de-emp)" : $selected ? "var(--color-primary)" : "var(--color-border)"}; + background: ${({ $selected, $disabled }) => + $disabled + ? "" + : $selected + ? "var(--color-background-selected)" + : "var(--color-background-secondary)"}; + } +`; + +const RadioDot = styled.div<{ $selected: boolean }>` + width: 16px; + height: 16px; + min-width: 16px; + border-radius: 50%; + border: 2px solid ${({ $selected }) => + $selected ? "var(--color-primary)" : "var(--color-border)"}; + background: ${({ $selected }) => + $selected ? "var(--color-primary)" : "transparent"}; + display: flex; + align-items: center; + justify-content: center; + margin-top: 1px; + transition: border-color 0.15s, background 0.15s; + &::after { + content: ""; + display: ${({ $selected }) => ($selected ? "block" : "none")}; + width: 6px; + height: 6px; + border-radius: 50%; + background: #fff; + } +`; + +const OptionContent = styled.div` + display: flex; + flex-direction: column; + gap: 3px; + flex: 1; +`; + +const OptionTitle = styled.span` + font-size: 13px; + font-weight: 600; + color: var(--color-foreground-emp); +`; + +const OptionDesc = styled.span` + font-size: 12px; + color: var(--color-foreground-de-emp); + line-height: 1.5; +`; + +const NewBadge = styled.span` + display: inline-flex; + align-items: center; + padding: 1px 7px; + background: var(--color-success-background); + color: var(--color-success-text); + border-radius: 10px; + font-size: 10px; + font-weight: 700; + margin-left: 7px; + text-transform: uppercase; + letter-spacing: 0.04em; +`; + +const ChangedBadge = styled.span` + display: inline-flex; + align-items: center; + padding: 1px 7px; + background: var(--color-info-background); + color: var(--color-info-text); + border-radius: 10px; + font-size: 10px; + font-weight: 700; + margin-left: 7px; + text-transform: uppercase; + letter-spacing: 0.04em; +`; + +// ─── Warning callout ────────────────────────────────────────────────────────── + +const Callout = styled.div<{ $variant: "warning" | "info" }>` + display: flex; + align-items: flex-start; + gap: 10px; + padding: 10px 13px; + border-radius: 6px; + background: ${({ $variant }) => + $variant === "warning" + ? "var(--color-warning-background)" + : "var(--color-info-background)"}; + border: 1px solid ${({ $variant }) => + $variant === "warning" ? "#ffe099" : "var(--color-primary-lighter)"}; + margin-top: 8px; +`; + +const CalloutIcon = styled.span<{ $variant: "warning" | "info" }>` + font-size: 15px; + line-height: 1.4; + color: ${({ $variant }) => + $variant === "warning" + ? "var(--color-warning-icon)" + : "var(--color-info-icon)"}; +`; + +const CalloutText = styled.p` + font-size: 12px; + color: var(--color-foreground); + margin: 0; + line-height: 1.6; +`; + +const CalloutStrong = styled.strong` + font-weight: 700; + color: var(--color-foreground-emp); +`; + +// ─── Buttons ───────────────────────────────────────────────────────────────── + +const BtnSecondary = styled.button` + padding: 7px 16px; + font-size: 13px; + font-weight: 600; + border-radius: 5px; + border: 1px solid var(--color-border); + background: var(--color-background); + color: var(--color-foreground); + cursor: pointer; + &:hover { background: var(--color-background-secondary); } +`; + +const BtnPrimary = styled.button` + padding: 7px 16px; + font-size: 13px; + font-weight: 600; + border-radius: 5px; + border: none; + background: var(--color-primary); + color: #fff; + cursor: pointer; + &:hover { background: var(--color-primary-hover); } +`; + +// ─── Annotation box ─────────────────────────────────────────────────────────── + +const DiffAnnotation = styled.div` + max-width: 580px; + padding: 16px 20px; + border-radius: 8px; + background: var(--color-background-secondary); + border: 1px solid var(--color-border-de-emp); +`; + +const DiffTitle = styled.h3` + font-size: 13px; + font-weight: 700; + color: var(--color-foreground-emp); + margin: 0 0 10px; +`; + +const DiffList = styled.ul` + margin: 0; + padding-left: 18px; + display: flex; + flex-direction: column; + gap: 6px; +`; + +const DiffItem = styled.li` + font-size: 13px; + color: var(--color-foreground-de-emp); + line-height: 1.6; +`; + +const DiffHighlight = styled.code` + background: var(--color-background-raised); + border: 1px solid var(--color-border-de-emp); + border-radius: 3px; + padding: 1px 5px; + font-size: 12px; + color: var(--color-foreground-emp); +`; + +// ─── Input (for "Specific user" field mockup) ───────────────────────────────── + +const SelectMock = styled.div` + margin-top: 8px; + display: flex; + align-items: center; + border: 1px solid var(--color-border); + border-radius: 5px; + padding: 7px 11px; + background: var(--color-background-edit); + gap: 8px; + color: var(--color-foreground-de-emp); + font-size: 13px; +`; + +const SelectMockChevron = styled.span` + margin-left: auto; + font-size: 11px; +`; + +// ───────────────────────────────────────────────────────────────────────────── +// Component +// ───────────────────────────────────────────────────────────────────────────── + +type Identity = "dynamic" | "specific-user" | "inherit-parent"; +type ProcessType = "cross-platform" | "windows"; + +export function ExecutionTargetPrototype() { + const [processType, setProcessType] = useState("cross-platform"); + const [identity, setIdentity] = useState("dynamic"); + + const isWindows = processType === "windows"; + + return ( + + {/* ── Page header ─────────────────────────────────── */} + + + 🎫 OR-92995 + + Execution Target — Prototype + + Enable "Inherit parent job identity" for Windows automations & + rename the option in the Execution Target panel. + + + + {/* ── Process type switcher ────────────────────────── */} +
+ + Simulate process type + + + setProcessType("cross-platform")} + > + Cross-platform + + setProcessType("windows")} + > + Windows + + +
+ + {/* ── Panel ───────────────────────────────────────── */} + + + Execution Target + + Choose how this job's execution identity is resolved when invoked + by a parent job. + + + + + Execution identity + + {/* Option 1: Dynamically allocated */} + setIdentity("dynamic")} + > + + + Dynamically allocated + + Automatically assigns an available Unattended Robot from the + license pool at runtime. + + + + + {/* Option 2: Specific user */} + setIdentity("specific-user")} + > + + + Specific user + + Run as a fixed user account with credentials configured in + Orchestrator. + + {identity === "specific-user" && ( + + Search for a user account… + + + )} + + + + {/* Option 3: Inherit parent job identity (renamed from Run As Myself) */} + setIdentity("inherit-parent")} + > + + + + Inherit parent job identity + + renamed + + {isWindows && now available} + + + The job runs with the same identity as the job that triggered + it, inheriting its robot credentials and license. + + + {/* Warning only for Windows processes */} + {isWindows && ( + + + + + Windows automations require Unattended Robot credentials. + {" "} + The job invocation may fail if the triggering job's identity + does not have an Unattended Robot license with credentials + configured in Orchestrator. + + + )} + + {/* Info note for cross-platform — no change in behaviour */} + {!isWindows && identity === "inherit-parent" && ( + + + + The identity resolution works the same as before — this + option was previously labelled{" "} + Run As Myself. + + + )} + + + + + + Cancel + Save changes + + + + {/* ── Change summary ───────────────────────────────── */} + + 📋 Changes in this prototype (OR-92995) + + + Renamed:{" "} + Run As Myself →{" "} + Inherit parent job identity in the + Execution Target panel for all process types. + + + New for Windows: The{" "} + Inherit parent job identity option + is now selectable for Windows process automations (previously + hidden/disabled). + + + Warning callout: When a Windows process is + selected and Inherit parent job identity{" "} + is chosen, a contextual warning is shown explaining that the job + invocation may fail if the parent job's identity lacks an + Unattended Robot license with credentials. + + + +
+ ); +} diff --git a/apps/react-playground/src/pages/prototypes/UserEventTriggersPrototype.tsx b/apps/react-playground/src/pages/prototypes/UserEventTriggersPrototype.tsx new file mode 100644 index 000000000..a4744d939 --- /dev/null +++ b/apps/react-playground/src/pages/prototypes/UserEventTriggersPrototype.tsx @@ -0,0 +1,1730 @@ +/** + * Prototype: User Event Triggers + * + * Extends the Orchestrator Event Triggers grid so that triggers marked + * "Configurable by users" can be expanded to reveal the per-user trigger + * instances that each end-user has created using their own personal Connection. + */ + +import { useState, useEffect, useRef, useCallback } from "react"; +import styled, { keyframes } from "styled-components"; + +// ───────────────────────────────────────────────────────────────────────────── +// Types +// ───────────────────────────────────────────────────────────────────────────── + +interface UserTrigger { + id: string; + user: string; + email: string; + initials: string; + connection: string; + state: "enabled" | "disabled"; + lastRun: string; +} + +interface EventTrigger { + id: string; + name: string; + process: string; + description: string; + connectorIcon: string | null; + connectorName: string | null; + connection: string; + configurableByUsers: boolean; + jobPriority: string; + type: "Connected" | "Disconnected"; + state: "active" | "inactive"; + userTriggers: UserTrigger[]; +} + +// ───────────────────────────────────────────────────────────────────────────── +// Mock data +// ───────────────────────────────────────────────────────────────────────────── + +const MOCK_TRIGGERS: EventTrigger[] = [ + { + id: "t1", + name: "Agentic Process w multiple connectors", + process: "Agentic Process w…", + description: "", + connectorIcon: null, + connectorName: null, + connection: "Configurable by users", + configurableByUsers: true, + jobPriority: "Inherited", + type: "Connected", + state: "active", + userTriggers: [ + { + id: "u1a", + user: "Bogdan Doaga", + email: "bogdan.doaga@uipath.com", + initials: "BD", + connection: "bogdan - Gdrive Personal", + state: "enabled", + lastRun: "2 min ago", + }, + { + id: "u1b", + user: "Sorin Buse", + email: "sorin.buse@uipath.com", + initials: "SB", + connection: "sorin - Gdrive Work", + state: "disabled", + lastRun: "Never", + }, + { + id: "u1c", + user: "Paul Motiu", + email: "paul.motiu@uipath.com", + initials: "PM", + connection: "paul - Gdrive Corp", + state: "enabled", + lastRun: "1 hour ago", + }, + ], + }, + { + id: "t2", + name: "Agentic Process w multiple connectors", + process: "Agentic Process w…", + description: "", + connectorIcon: "🔵", + connectorName: "Google Drive", + connection: "bogdan - Gdrive P…", + configurableByUsers: false, + jobPriority: "Inherited", + type: "Connected", + state: "active", + userTriggers: [], + }, + { + id: "t3", + name: "Agentic Process w multiple connectors", + process: "Agentic Process w…", + description: "", + connectorIcon: null, + connectorName: null, + connection: "Configurable by users", + configurableByUsers: true, + jobPriority: "Inherited", + type: "Connected", + state: "active", + userTriggers: [ + { + id: "u3a", + user: "Bogdan Doaga", + email: "bogdan.doaga@uipath.com", + initials: "BD", + connection: "bogdan - Slack Workspace", + state: "enabled", + lastRun: "5 hours ago", + }, + ], + }, + { + id: "t4", + name: "Invoice Automation", + process: "Invoice Process", + description: "Watches for new invoices", + connectorIcon: "💬", + connectorName: "Slack", + connection: "configurable-slack", + configurableByUsers: false, + jobPriority: "Inherited", + type: "Connected", + state: "inactive", + userTriggers: [], + }, + { + id: "t5", + name: "Lead Scoring Agent", + process: "Lead Scoring w…", + description: "", + connectorIcon: null, + connectorName: null, + connection: "Configurable by users", + configurableByUsers: true, + jobPriority: "High", + type: "Connected", + state: "active", + userTriggers: [], + }, +]; + +const MENU_ITEMS = [ + "Edit in package requirements", + "View jobs", + "View history", + "View traces", + null, // divider + "Enable", + "Disable", + null, // divider + "Remove", +]; + +// ───────────────────────────────────────────────────────────────────────────── +// Animations +// ───────────────────────────────────────────────────────────────────────────── + +const fadeIn = keyframes` + from { opacity: 0; transform: translateY(-4px); } + to { opacity: 1; transform: translateY(0); } +`; + +const expandIn = keyframes` + from { opacity: 0; } + to { opacity: 1; } +`; + +// ───────────────────────────────────────────────────────────────────────────── +// Page chrome +// ───────────────────────────────────────────────────────────────────────────── + +const Page = styled.div` + display: flex; + flex-direction: column; + height: 100vh; + background: var(--color-background); + overflow: hidden; +`; + +// Wraps the scrollable grid + annotation area +const ContentRow = styled.div` + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; +`; + +const MainArea = styled.div` + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; +`; + +const TopBar = styled.div` + padding: 16px 28px 0; + display: flex; + flex-direction: column; + gap: 0; + border-bottom: 1px solid var(--color-border-de-emp); + background: var(--color-background-raised); +`; + +const BreadcrumbRow = styled.div` + display: flex; + align-items: center; + gap: 6px; + font-size: 12px; + color: var(--color-foreground-de-emp); + margin-bottom: 12px; +`; + +const BreadcrumbLink = styled.span` + color: var(--color-foreground-link); + cursor: pointer; + &:hover { text-decoration: underline; } +`; + +const BreadcrumbSep = styled.span` + font-size: 10px; +`; + +const BreadcrumbCurrent = styled.span` + color: var(--color-foreground); + font-weight: 600; +`; + +const PageNav = styled.div` + display: flex; + gap: 0; + margin-bottom: -1px; +`; + +const NavTab = styled.button<{ $active: boolean }>` + padding: 8px 16px; + font-size: 13px; + font-weight: ${({ $active }) => ($active ? "700" : "500")}; + border: none; + background: transparent; + color: ${({ $active }) => + $active ? "var(--color-primary)" : "var(--color-foreground-de-emp)"}; + border-bottom: 2px solid ${({ $active }) => + $active ? "var(--color-primary)" : "transparent"}; + cursor: pointer; + white-space: nowrap; + transition: color 0.15s, border-color 0.15s; + &:hover { + color: ${({ $active }) => + $active ? "var(--color-primary)" : "var(--color-foreground)"}; + } +`; + +// ───────────────────────────────────────────────────────────────────────────── +// Toolbar +// ───────────────────────────────────────────────────────────────────────────── + +const Toolbar = styled.div` + display: flex; + align-items: center; + gap: 8px; + padding: 12px 28px; + background: var(--color-background); + border-bottom: 1px solid var(--color-border-de-emp); + flex-wrap: wrap; +`; + +const SearchWrap = styled.div` + display: flex; + align-items: center; + gap: 8px; + border: 1px solid var(--color-border); + border-radius: 5px; + padding: 6px 11px; + background: var(--color-background-edit); + width: 220px; +`; + +const SearchInput = styled.input` + border: none; + background: transparent; + font-size: 13px; + color: var(--color-foreground); + outline: none; + width: 100%; + &::placeholder { color: var(--color-foreground-de-emp); } +`; + +const FilterChip = styled.button` + display: flex; + align-items: center; + gap: 5px; + padding: 6px 11px; + border: 1px solid var(--color-border); + border-radius: 5px; + background: var(--color-background); + font-size: 12px; + color: var(--color-foreground); + cursor: pointer; + white-space: nowrap; + &:hover { background: var(--color-background-secondary); } +`; + +const Spacer = styled.div` + flex: 1; +`; + +const AddBtn = styled.button` + display: flex; + align-items: center; + gap: 6px; + padding: 7px 16px; + border: none; + border-radius: 5px; + background: var(--color-primary); + color: #fff; + font-size: 13px; + font-weight: 600; + cursor: pointer; + white-space: nowrap; + &:hover { background: var(--color-primary-hover); } +`; + +// ───────────────────────────────────────────────────────────────────────────── +// Grid +// ───────────────────────────────────────────────────────────────────────────── + +const GridWrap = styled.div` + flex: 1; + overflow: auto; + padding: 0 16px 16px; +`; + +const Grid = styled.table` + width: 100%; + border-collapse: collapse; + table-layout: fixed; +`; + +const GridHead = styled.thead` + position: sticky; + top: 0; + z-index: 2; +`; + +const GridHeadRow = styled.tr` + background: var(--color-background-secondary); + border-bottom: 1px solid var(--color-border-de-emp); +`; + +const Th = styled.th<{ $width?: string; $align?: string }>` + padding: 9px 12px; + font-size: 12px; + font-weight: 700; + color: var(--color-foreground-de-emp); + text-align: ${({ $align }) => $align ?? "left"}; + width: ${({ $width }) => $width ?? "auto"}; + white-space: nowrap; + user-select: none; +`; + +const SortIcon = styled.span` + font-size: 9px; + margin-left: 3px; + opacity: 0.6; +`; + +const GridBody = styled.tbody``; + +const GridRow = styled.tr<{ $expanded: boolean; $configurable: boolean }>` + border-bottom: 1px solid var(--color-border-de-emp); + background: ${({ $expanded }) => + $expanded ? "var(--color-background-selected)" : "var(--color-background-raised)"}; + border-left: 3px solid ${({ $expanded }) => + $expanded ? "var(--color-primary)" : "transparent"}; + transition: background 0.12s, border-left-color 0.12s; + &:hover { + background: ${({ $expanded }) => + $expanded + ? "var(--color-background-selected)" + : "var(--color-background-hover)"}; + } +`; + +const Td = styled.td<{ $width?: string }>` + padding: 10px 12px; + font-size: 13px; + color: var(--color-foreground); + vertical-align: middle; + width: ${({ $width }) => $width ?? "auto"}; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +`; + +const CheckboxCell = styled(Td)` + width: 36px; + padding-left: 16px; +`; + +const ExpandCell = styled(Td)` + width: 32px; + padding: 0; +`; + +const ExpandBtn = styled.button<{ $expanded: boolean }>` + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + border: none; + background: transparent; + cursor: pointer; + color: var(--color-primary); + font-size: 11px; + border-radius: 4px; + transform: rotate(${({ $expanded }) => ($expanded ? "90deg" : "0deg")}); + transition: transform 0.18s ease; + &:hover { background: var(--color-background-hover); } +`; + +const TriggerName = styled.span` + font-weight: 600; + color: var(--color-foreground-emp); + cursor: pointer; + &:hover { color: var(--color-primary); text-decoration: underline; } +`; + +const StatusDot = styled.span<{ $active: boolean }>` + display: inline-block; + width: 8px; + height: 8px; + border-radius: 50%; + background: ${({ $active }) => + $active ? "var(--color-success-icon)" : "var(--color-border)"}; + margin-right: 6px; +`; + +const ConnectorCell = styled.div` + display: flex; + align-items: center; + gap: 6px; + font-size: 13px; +`; + +const ConnectorIcon = styled.span` + font-size: 14px; +`; + +const ConfigurableTag = styled.span` + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 12px; + color: var(--color-foreground-de-emp); + font-style: italic; +`; + +const UserCountBadge = styled.span` + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 18px; + height: 18px; + padding: 0 5px; + border-radius: 9px; + background: var(--color-primary); + color: #fff; + font-size: 10px; + font-weight: 700; + margin-left: 6px; +`; + +const PriorityCell = styled.div` + display: flex; + align-items: center; + gap: 6px; + font-size: 13px; + color: var(--color-foreground-de-emp); +`; + +// ───────────────────────────────────────────────────────────────────────────── +// Row action menu +// ───────────────────────────────────────────────────────────────────────────── + +const ActionCell = styled(Td)` + width: 48px; + text-align: right; + overflow: visible; +`; + +const MenuBtn = styled.button` + width: 28px; + height: 28px; + border: none; + background: transparent; + cursor: pointer; + color: var(--color-foreground-de-emp); + font-size: 16px; + border-radius: 4px; + display: inline-flex; + align-items: center; + justify-content: center; + &:hover { + background: var(--color-background-hover); + color: var(--color-foreground); + } +`; + +// Rendered via a fixed portal overlay — escapes table overflow clipping +const DropMenu = styled.div<{ $top: number; $right: number }>` + position: fixed; + top: ${({ $top }) => $top}px; + right: ${({ $right }) => $right}px; + background: var(--color-background-raised); + border: 1px solid var(--color-border); + border-radius: 6px; + box-shadow: 0 4px 16px rgba(0,0,0,0.14); + z-index: 9999; + min-width: 210px; + animation: ${fadeIn} 0.12s ease; + padding: 4px 0; +`; + +const MenuItem = styled.button<{ $danger?: boolean; $disabled?: boolean }>` + display: block; + width: 100%; + padding: 8px 16px; + text-align: left; + font-size: 13px; + border: none; + background: transparent; + cursor: ${({ $disabled }) => ($disabled ? "default" : "pointer")}; + color: ${({ $danger, $disabled }) => + $disabled + ? "var(--color-foreground-disable)" + : $danger + ? "var(--color-error-text)" + : "var(--color-foreground)"}; + &:hover { + background: ${({ $disabled }) => + $disabled ? "transparent" : "var(--color-background-secondary)"}; + } +`; + +const MenuDivider = styled.div` + height: 1px; + background: var(--color-border-de-emp); + margin: 4px 0; +`; + +// ───────────────────────────────────────────────────────────────────────────── +// User sub-table +// ───────────────────────────────────────────────────────────────────────────── + +const SubTableRow = styled.tr` + animation: ${expandIn} 0.18s ease; +`; + +const SubTableCell = styled.td` + padding: 0; + border-bottom: 1px solid var(--color-border-de-emp); + background: var(--color-background-secondary); +`; + +const SubTableWrap = styled.div` + padding: 12px 16px 16px 52px; + border-left: 3px solid var(--color-primary); + background: var(--color-background-secondary); +`; + +const SubTableHeader = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 10px; +`; + +const SubTableTitle = styled.span` + font-size: 12px; + font-weight: 700; + color: var(--color-foreground-de-emp); + text-transform: uppercase; + letter-spacing: 0.06em; +`; + +const AddUserTriggerBtn = styled.button` + display: flex; + align-items: center; + gap: 5px; + padding: 5px 12px; + border: 1px solid var(--color-primary); + border-radius: 5px; + background: transparent; + color: var(--color-primary); + font-size: 12px; + font-weight: 600; + cursor: pointer; + &:hover { + background: var(--color-background-selected); + } +`; + +const SubTable = styled.table` + width: 100%; + border-collapse: collapse; +`; + +const SubTh = styled.th<{ $width?: string }>` + padding: 7px 10px; + font-size: 11px; + font-weight: 700; + color: var(--color-foreground-de-emp); + text-align: left; + border-bottom: 1px solid var(--color-border-de-emp); + width: ${({ $width }) => $width ?? "auto"}; + white-space: nowrap; +`; + +const SubTd = styled.td` + padding: 9px 10px; + font-size: 12px; + color: var(--color-foreground); + border-bottom: 1px solid var(--color-border-de-emp); + vertical-align: middle; +`; + +const SubTr = styled.tr` + background: var(--color-background-raised); + &:last-child td { border-bottom: none; } + &:hover { background: var(--color-background-hover); } +`; + +const UserCell = styled.div` + display: flex; + align-items: center; + gap: 8px; +`; + +const Avatar = styled.div` + width: 26px; + height: 26px; + border-radius: 50%; + background: var(--color-primary); + color: #fff; + display: flex; + align-items: center; + justify-content: center; + font-size: 10px; + font-weight: 700; + flex-shrink: 0; +`; + +const UserInfo = styled.div` + display: flex; + flex-direction: column; + gap: 1px; +`; + +const UserName = styled.span` + font-size: 12px; + font-weight: 600; + color: var(--color-foreground-emp); +`; + +const UserEmail = styled.span` + font-size: 11px; + color: var(--color-foreground-de-emp); +`; + +const StateChip = styled.span<{ $state: "enabled" | "disabled" }>` + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + border-radius: 10px; + font-size: 11px; + font-weight: 600; + background: ${({ $state }) => + $state === "enabled" + ? "var(--color-success-background)" + : "var(--color-chip-default-background)"}; + color: ${({ $state }) => + $state === "enabled" + ? "var(--color-success-text)" + : "var(--color-foreground-de-emp)"}; +`; + +const EmptyState = styled.div` + padding: 24px; + text-align: center; + color: var(--color-foreground-de-emp); + font-size: 13px; + background: var(--color-background-raised); + border-radius: 6px; + border: 1px dashed var(--color-border-de-emp); +`; + +const EmptyStateIcon = styled.div` + font-size: 28px; + margin-bottom: 8px; +`; + +const EmptyStateText = styled.div` + font-weight: 600; + color: var(--color-foreground); + margin-bottom: 4px; +`; + +const EmptyStateSub = styled.div` + font-size: 12px; +`; + +// ───────────────────────────────────────────────────────────────────────────── +// Pagination +// ───────────────────────────────────────────────────────────────────────────── + +const PaginationRow = styled.div` + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + padding: 12px 28px; + border-top: 1px solid var(--color-border-de-emp); + background: var(--color-background-raised); + font-size: 12px; + color: var(--color-foreground-de-emp); +`; + +const PageBtn = styled.button` + width: 26px; + height: 26px; + border: 1px solid var(--color-border-de-emp); + border-radius: 4px; + background: var(--color-background); + font-size: 12px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + color: var(--color-foreground-de-emp); + &:disabled { opacity: 0.4; cursor: default; } + &:not(:disabled):hover { background: var(--color-background-secondary); } +`; + + +// ───────────────────────────────────────────────────────────────────────────── +// Side panel +// ───────────────────────────────────────────────────────────────────────────── + +const slideIn = keyframes` + from { transform: translateX(100%); opacity: 0; } + to { transform: translateX(0); opacity: 1; } +`; + +// Fixed overlay drawer — slides in from the right, never pushes the grid +const SidePanel = styled.div` + position: fixed; + top: 0; + right: 0; + bottom: 0; + width: 480px; + display: flex; + flex-direction: column; + border-left: 1px solid var(--color-border-de-emp); + background: var(--color-background-raised); + box-shadow: -4px 0 24px rgba(0, 0, 0, 0.10); + animation: ${slideIn} 0.18s ease; + overflow: hidden; + z-index: 200; +`; + +const SPHeader = styled.div` + padding: 14px 16px 0; + border-bottom: 1px solid var(--color-border-de-emp); + flex-shrink: 0; +`; + +const SPTitleRow = styled.div` + display: flex; + align-items: flex-start; + gap: 8px; + margin-bottom: 10px; +`; + +const SPTitle = styled.h2` + flex: 1; + font-size: 13px; + font-weight: 700; + color: var(--color-foreground-emp); + margin: 0; + line-height: 1.4; + word-break: break-word; +`; + +const SPIconBtn = styled.button` + width: 26px; + height: 26px; + border: none; + background: transparent; + cursor: pointer; + color: var(--color-foreground-de-emp); + font-size: 14px; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + &:hover { + background: var(--color-background-hover); + color: var(--color-foreground); + } +`; + +const SPMeta = styled.div` + display: flex; + flex-direction: column; + gap: 3px; + margin-bottom: 10px; +`; + +const SPMetaRow = styled.div` + font-size: 12px; + color: var(--color-foreground-de-emp); + display: flex; + gap: 4px; +`; + +const SPMetaLabel = styled.span` + font-weight: 600; + color: var(--color-foreground); + min-width: 75px; +`; + +const SPAuditLink = styled.a` + font-size: 12px; + color: var(--color-foreground-link); + text-decoration: none; + display: flex; + align-items: center; + gap: 4px; + margin-left: auto; + white-space: nowrap; + align-self: flex-start; + margin-top: 2px; + &:hover { text-decoration: underline; } +`; + +const SPTabRow = styled.div` + display: flex; + gap: 0; +`; + +const SPTab = styled.button<{ $active: boolean }>` + padding: 7px 14px; + font-size: 13px; + font-weight: ${({ $active }) => ($active ? "700" : "500")}; + border: none; + background: transparent; + color: ${({ $active }) => + $active ? "var(--color-primary)" : "var(--color-foreground-de-emp)"}; + border-bottom: 2px solid ${({ $active }) => + $active ? "var(--color-primary)" : "transparent"}; + cursor: pointer; + margin-bottom: -1px; + transition: color 0.12s, border-color 0.12s; + &:hover { + color: ${({ $active }) => + $active ? "var(--color-primary)" : "var(--color-foreground)"}; + } +`; + +const SPBody = styled.div` + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; +`; + +const SPToolbar = styled.div` + display: flex; + align-items: center; + gap: 6px; + padding: 10px 12px; + border-bottom: 1px solid var(--color-border-de-emp); + flex-shrink: 0; +`; + +const SPSearchWrap = styled.div` + display: flex; + align-items: center; + gap: 6px; + border: 1px solid var(--color-border); + border-radius: 5px; + padding: 5px 9px; + background: var(--color-background-edit); + flex: 1; +`; + +const SPSearchInput = styled.input` + border: none; + background: transparent; + font-size: 12px; + color: var(--color-foreground); + outline: none; + width: 100%; + &::placeholder { color: var(--color-foreground-de-emp); } +`; + +const SPFilterBtn = styled.button` + display: flex; + align-items: center; + gap: 4px; + padding: 5px 10px; + border: 1px solid var(--color-border); + border-radius: 5px; + background: var(--color-background); + font-size: 12px; + color: var(--color-foreground); + cursor: pointer; + &:hover { background: var(--color-background-secondary); } +`; + +const SPGrid = styled.div` + flex: 1; + overflow-y: auto; +`; + +const SPGridHead = styled.div` + display: grid; + grid-template-columns: 40px 1fr 1fr 1fr 1fr; + padding: 8px 12px; + border-bottom: 1px solid var(--color-border-de-emp); + background: var(--color-background-secondary); + position: sticky; + top: 0; + z-index: 1; +`; + +const SPGridHeadCell = styled.span` + font-size: 11px; + font-weight: 700; + color: var(--color-foreground-de-emp); + display: flex; + align-items: center; + gap: 3px; +`; + +const SPGridRow = styled.div` + display: grid; + grid-template-columns: 40px 1fr 1fr 1fr 1fr; + padding: 9px 12px; + border-bottom: 1px solid var(--color-border-de-emp); + font-size: 12px; + color: var(--color-foreground); + &:hover { background: var(--color-background-hover); } +`; + +const SPStateChip = styled.span<{ $state: string }>` + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + border-radius: 10px; + font-size: 11px; + font-weight: 600; + background: ${({ $state }) => + $state === "Successful" + ? "var(--color-success-background)" + : $state === "Faulted" + ? "var(--color-error-background)" + : $state === "Running" + ? "var(--color-info-background)" + : "var(--color-chip-default-background)"}; + color: ${({ $state }) => + $state === "Successful" + ? "var(--color-success-text)" + : $state === "Faulted" + ? "var(--color-error-text)" + : $state === "Running" + ? "var(--color-info-text)" + : "var(--color-foreground-de-emp)"}; +`; + +const SPEmptyState = styled.div` + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 10px; + color: var(--color-foreground-de-emp); + padding: 40px 20px; +`; + +const SPEmptyIcon = styled.div` + font-size: 36px; + opacity: 0.35; +`; + +const SPEmptyText = styled.span` + font-size: 13px; +`; + +const SPPagination = styled.div` + display: flex; + align-items: center; + gap: 6px; + padding: 8px 12px; + border-top: 1px solid var(--color-border-de-emp); + font-size: 12px; + color: var(--color-foreground-de-emp); + flex-shrink: 0; +`; + +const SPPageBtn = styled.button` + width: 22px; + height: 22px; + border: 1px solid var(--color-border-de-emp); + border-radius: 3px; + background: var(--color-background); + font-size: 11px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + color: var(--color-foreground-de-emp); + &:disabled { opacity: 0.4; cursor: default; } +`; + +const SPItemsSelect = styled.span` + margin-left: auto; + font-size: 12px; + color: var(--color-foreground-de-emp); +`; + +// ───────────────────────────────────────────────────────────────────────────── +// Component +// ───────────────────────────────────────────────────────────────────────────── + +// ─── Mock jobs / history data ───────────────────────────────────────────────── + +const MOCK_JOBS: Record> = { + // Parent triggers + t1: [ + { state: "Successful", started: "2026-06-03 09:12", ended: "2026-06-03 09:14", duration: "1m 48s" }, + { state: "Faulted", started: "2026-06-03 08:55", ended: "2026-06-03 08:56", duration: "0m 42s" }, + { state: "Successful", started: "2026-06-03 08:30", ended: "2026-06-03 08:33", duration: "2m 11s" }, + ], + t2: [ + { state: "Running", started: "2026-06-03 09:20", ended: "—", duration: "—" }, + { state: "Successful", started: "2026-06-03 09:01", ended: "2026-06-03 09:04", duration: "3m 02s" }, + ], + // User triggers + u1a: [ + { state: "Successful", started: "2026-06-04 09:05", ended: "2026-06-04 09:07", duration: "1m 52s" }, + { state: "Successful", started: "2026-06-04 07:30", ended: "2026-06-04 07:32", duration: "2m 04s" }, + { state: "Faulted", started: "2026-06-03 18:12", ended: "2026-06-03 18:12", duration: "0m 18s" }, + ], + u1b: [], + u1c: [ + { state: "Successful", started: "2026-06-04 08:02", ended: "2026-06-04 08:04", duration: "1m 37s" }, + ], + u3a: [ + { state: "Running", started: "2026-06-04 09:18", ended: "—", duration: "—" }, + { state: "Successful", started: "2026-06-03 14:55", ended: "2026-06-03 14:58", duration: "3m 10s" }, + ], +}; + +const MOCK_HISTORY: Record> = { + // Parent triggers + t1: [ + { event: "Trigger enabled", timestamp: "2026-06-01 14:20", user: "bogdan.doaga@uipath.com" }, + { event: "Connection updated", timestamp: "2026-05-30 10:05", user: "sorin.buse@uipath.com" }, + { event: "Trigger created", timestamp: "2026-05-19 17:14", user: "bogdan.doaga@uipath.com" }, + ], + t2: [ + { event: "Trigger enabled", timestamp: "2026-06-02 09:00", user: "bogdan.doaga@uipath.com" }, + ], + // User triggers + u1a: [ + { event: "User trigger enabled", timestamp: "2026-06-01 15:00", user: "bogdan.doaga@uipath.com" }, + { event: "Connection set", timestamp: "2026-06-01 14:55", user: "bogdan.doaga@uipath.com" }, + { event: "User trigger created", timestamp: "2026-06-01 14:53", user: "bogdan.doaga@uipath.com" }, + ], + u1b: [ + { event: "User trigger disabled", timestamp: "2026-06-02 11:30", user: "sorin.buse@uipath.com" }, + { event: "User trigger created", timestamp: "2026-06-01 16:00", user: "sorin.buse@uipath.com" }, + ], + u1c: [ + { event: "User trigger enabled", timestamp: "2026-06-03 09:00", user: "paul.motiu@uipath.com" }, + { event: "User trigger created", timestamp: "2026-06-03 08:55", user: "paul.motiu@uipath.com" }, + ], + u3a: [ + { event: "User trigger enabled", timestamp: "2026-05-28 10:12", user: "bogdan.doaga@uipath.com" }, + { event: "User trigger created", timestamp: "2026-05-28 10:08", user: "bogdan.doaga@uipath.com" }, + ], +}; + +// ───────────────────────────────────────────────────────────────────────────── + +export function UserEventTriggersPrototype() { + const [expandedRows, setExpandedRows] = useState>(new Set()); + const [openMenu, setOpenMenu] = useState(null); + const [menuPos, setMenuPos] = useState<{ top: number; right: number }>({ top: 0, right: 0 }); + const [searchValue, setSearchValue] = useState(""); + const menuRef = useRef(null); + + // Side panel state + const [panelTriggerId, setPanelTriggerId] = useState(null); + const [panelUserTriggerId, setPanelUserTriggerId] = useState(null); + const [panelTab, setPanelTab] = useState<"Jobs" | "History" | "Traces">("Jobs"); + const panelTrigger = MOCK_TRIGGERS.find((t) => t.id === panelTriggerId) ?? null; + const panelUserTrigger = panelTrigger?.userTriggers.find((ut) => ut.id === panelUserTriggerId) ?? null; + // Data key: user trigger ID when viewing a user trigger, otherwise parent trigger ID + const panelDataKey = panelUserTriggerId ?? panelTriggerId ?? ""; + + function openPanel(id: string, tab: "Jobs" | "History" | "Traces" = "Jobs") { + setPanelTriggerId(id); + setPanelUserTriggerId(null); + setPanelTab(tab); + } + + function openUserPanel(triggerId: string, userTriggerId: string, tab: "Jobs" | "History" | "Traces" = "Jobs") { + setPanelTriggerId(triggerId); + setPanelUserTriggerId(userTriggerId); + setPanelTab(tab); + } + + function closePanel() { + setPanelTriggerId(null); + setPanelUserTriggerId(null); + } + + // Close menu on outside click (including Escape key) + useEffect(() => { + function handleMouseDown(e: MouseEvent) { + if (menuRef.current && !menuRef.current.contains(e.target as Node)) { + setOpenMenu(null); + } + } + function handleKeyDown(e: KeyboardEvent) { + if (e.key === "Escape") setOpenMenu(null); + } + document.addEventListener("mousedown", handleMouseDown); + document.addEventListener("keydown", handleKeyDown); + return () => { + document.removeEventListener("mousedown", handleMouseDown); + document.removeEventListener("keydown", handleKeyDown); + }; + }, []); + + const openMenuForRow = useCallback((id: string, btn: HTMLButtonElement) => { + if (openMenu === id) { + setOpenMenu(null); + return; + } + const rect = btn.getBoundingClientRect(); + setMenuPos({ + top: rect.bottom + 4, + right: window.innerWidth - rect.right, + }); + setOpenMenu(id); + }, [openMenu]); + + function toggleRow(id: string) { + setExpandedRows((prev) => { + const next = new Set(prev); + if (next.has(id)) next.delete(id); + else next.add(id); + return next; + }); + } + + const filteredTriggers = MOCK_TRIGGERS.filter((t) => + t.name.toLowerCase().includes(searchValue.toLowerCase()), + ); + + return ( + + {/* ── Page chrome ─────────────────────────────────── */} + + + Automations + + Triggers + + Event Triggers + + + {["Time Triggers", "Queue Triggers", "Event Triggers", "API Triggers"].map( + (tab) => ( + + {tab} + + ), + )} + + + + {/* ── Toolbar ─────────────────────────────────────── */} + + + + 🔍 + + setSearchValue(e.target.value)} + /> + + State: All ▾ + Connector: All ▾ + Connection: All ▾ + Job priority: All ▾ + + + Add a new trigger + + + {/* ── Main content row (grid + side panel) ───────── */} + + + + {/* ── Grid ────────────────────────────────────────── */} + + + + + + + + + + Name + + + Process + + + Description + + Connector + Connection + + Job priority + + Type + + + + + + {filteredTriggers.map((trigger) => { + const expanded = expandedRows.has(trigger.id); + + return ( + <> + openPanel(trigger.id, "Jobs")} + style={{ cursor: "pointer" }} + > + {/* Expand toggle (only for configurable rows) */} + e.stopPropagation()}> + {trigger.configurableByUsers && ( + toggleRow(trigger.id)} + title={expanded ? "Collapse user triggers" : "Show user triggers"} + > + ▶ + + )} + + + {/* Checkbox */} + e.stopPropagation()}> + + + + {/* Name */} + +
+ + openPanel(trigger.id, "Jobs")} + > + {trigger.name.length > 30 + ? trigger.name.slice(0, 30) + "…" + : trigger.name} + +
+ + + {/* Process */} + {trigger.process} + + {/* Description */} + + {trigger.description || "—"} + + + {/* Connector */} + + {trigger.connectorName ? ( + + {trigger.connectorIcon} + {trigger.connectorName} + + ) : ( + + — + + )} + + + {/* Connection */} + + {trigger.configurableByUsers ? ( +
+ + 👥 Configurable by users + + {trigger.userTriggers.length > 0 && ( + + {trigger.userTriggers.length} + + )} +
+ ) : ( + + {trigger.connection.length > 20 + ? trigger.connection.slice(0, 20) + "…" + : trigger.connection} + + )} + + + {/* Job priority */} + + + + {trigger.jobPriority} + + + + {/* Type */} + + {trigger.type} + + + {/* Actions menu button (dropdown rendered outside table via fixed positioning) */} + e.stopPropagation()}> + openMenuForRow(trigger.id, e.currentTarget)} + aria-label="Row actions" + > + ⋮ + + +
+ + {/* ── Expanded: user sub-table ─────────── */} + {trigger.configurableByUsers && expanded && ( + + + + + + User triggers + {trigger.userTriggers.length > 0 && + ` · ${trigger.userTriggers.length} configured`} + + + + Add user trigger + + + + {trigger.userTriggers.length === 0 ? ( + + 🔌 + No user triggers yet + + Users can create their own trigger instance using their + personal Connection. + + + ) : ( + + + + User + Connection + State + Last run + + + + + {trigger.userTriggers.map((ut) => ( + openUserPanel(trigger.id, ut.id)} + style={{ cursor: "pointer" }} + > + + + {ut.initials} + + {ut.user} + {ut.email} + + + + + + 🔵 {ut.connection} + + + + + {ut.state === "enabled" ? "✓ " : ""} + {ut.state.charAt(0).toUpperCase() + + ut.state.slice(1)} + + + + {ut.lastRun} + + e.stopPropagation()} + > + { + e.stopPropagation(); + openMenuForRow(ut.id, e.currentTarget as HTMLButtonElement); + }} + > + ⋮ + + + + ))} + + + )} + + + + )} + + ); + })} +
+
+
+ + {/* ── Pagination ──────────────────────────────────── */} + + ⟨⟨ + + 1 – {filteredTriggers.length} / {filteredTriggers.length} + + ⟩⟩ + + +
+ + {/* ── Side panel ──────────────────────────────────── */} + {panelTrigger && ( + + + + {panelUserTrigger ? ( +
+ {panelUserTrigger.initials} +
+ {panelUserTrigger.user} +
+ {panelTrigger.name} +
+
+
+ ) : ( + {panelTrigger.name} + )} + {}}>⤢ + +
+ +
+
+ {panelUserTrigger ? ( + <> + + Connection: + {panelUserTrigger.connection} + + + State: + + {panelUserTrigger.state === "enabled" ? "✓ " : ""} + {panelUserTrigger.state.charAt(0).toUpperCase() + panelUserTrigger.state.slice(1)} + + + + Last run: + {panelUserTrigger.lastRun} + + + Process: + {panelTrigger.process} + + + ) : ( + <> + + Process: + {panelTrigger.process} + + + Details: + Agentic process + + + Tags: + Epic, sap + + + Event Type: + N/A + + + )} +
+ e.preventDefault()}> + ↗ Go to Audit + +
+
+ + {(["Jobs", "History", "Traces"] as const).map((tab) => ( + setPanelTab(tab)} + > + {tab} + + ))} + +
+ + + + + 🔍 + + + ⊟ ▾ + ⊜ ▾ + + + {panelTab === "Jobs" && (() => { + const jobs = MOCK_JOBS[panelDataKey] ?? []; + if (jobs.length === 0) { + return ( + + + No data to display yet. + + ); + } + return ( + + + + State ↕ + Started ↕ + Ended ↕ + Duration + + {jobs.map((job, i) => ( + + + {job.state} + {job.started} + {job.ended} + {job.duration} + + ))} + + ); + })()} + + {panelTab === "History" && (() => { + const history = MOCK_HISTORY[panelDataKey] ?? []; + if (history.length === 0) { + return ( + + + No history to display yet. + + ); + } + return ( + + + + Event + Timestamp + User + + {history.map((h, i) => ( + + + {h.event} + {h.timestamp} + {h.user} + + ))} + + ); + })()} + + {panelTab === "Traces" && ( + + + No data to display yet. + + )} + + + + ⟨⟨ + + + 1 – {(panelTab === "Jobs" ? MOCK_JOBS[panelDataKey] : MOCK_HISTORY[panelDataKey])?.length ?? 0} / {(panelTab === "Jobs" ? MOCK_JOBS[panelDataKey] : MOCK_HISTORY[panelDataKey])?.length ?? 0} + + + ⟩⟩ + Items 25 ▾ + +
+ )} + +
+ + {/* ── Floating row-action menu (fixed, escapes table overflow) ── */} + {openMenu && ( + + {(() => { + const trigger = MOCK_TRIGGERS.find((t) => t.id === openMenu); + return MENU_ITEMS.map((item, idx) => + item === null ? ( + + ) : ( + { + if (item === "View jobs" || item === "View history" || item === "View traces") { + const tab = item === "View jobs" ? "Jobs" : item === "View history" ? "History" : "Traces"; + openPanel(openMenu, tab as "Jobs" | "History" | "Traces"); + } + setOpenMenu(null); + }} + > + {item} + + ), + ); + })()} + + )} +
+ ); +}