Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function AddStepNode({ data }: AddStepNodeProps) {
<Handle
type="target"
position={Position.Left}
className="!size-2.5 !bg-muted-foreground"
className="size-2.5! bg-muted-foreground!"
/>

<button
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
'use client';

import React from 'react';
import { Handle, Position } from '@xyflow/react';
import { Position } from '@xyflow/react';
import { Repeat } from 'lucide-react';
import { Badge } from '@/components/ui/feedback/badge';
import { useT } from '@/lib/i18n/client';
import { InvisibleHandle } from './invisible-handle';

interface AutomationLoopContainerProps {
data: {
Expand Down Expand Up @@ -32,49 +33,53 @@ export function AutomationLoopContainer({
return (
<div className="relative w-full h-full">
{/* Top Target Handle - incoming from higher-ranked nodes */}
<Handle
<InvisibleHandle
type="target"
position={Position.Top}
id="top-target"
className="!w-2 !h-2 !border-0 !bg-transparent !z-10"
className="size-2! border-0! bg-transparent! z-10!"
isConnectable={true}
style={{ top: 2, left: topTargetLeft, opacity: 0 }}
/>
Comment thread
Israeltheminer marked this conversation as resolved.

{/* Top Source Handle - outgoing to higher-ranked nodes */}
<Handle
<InvisibleHandle
type="source"
position={Position.Top}
id="top-source"
className="!w-2 !h-2 !border-0 !bg-transparent !z-10"
className="size-2! border-0! bg-transparent! z-10!"
isConnectable={true}
style={{ top: 2, left: topSourceLeft, opacity: 0 }}
/>

{/* Left Target Handle - for backward connections coming from the side */}
<Handle
<InvisibleHandle
type="target"
position={Position.Left}
id="left-target"
className="!w-2 !h-2 !border-0 !bg-transparent !z-10"
className="size-2! border-0! bg-transparent! z-10!"
isConnectable={true}
style={{ left: 0, top: '50%', opacity: 0 }}
/>

{/* Right Source Handle - for backward connections going to the side */}
<Handle
<InvisibleHandle
type="source"
position={Position.Right}
id="right-source"
className="!w-2 !h-2 !border-0 !bg-transparent !z-10"
className="size-2! border-0! bg-transparent! z-10!"
isConnectable={true}
style={{ right: 0, top: '50%', opacity: 0 }}
/>

{/* Main Card - matches regular automation step styling */}
<button
type="button"
aria-label={data.label ? t('aria.openNamed', { name: data.label }) : t('aria.openLoop')}
aria-label={
data.label
? t('aria.openNamed', { name: data.label })
: t('aria.openLoop')
}
className="w-full h-full rounded-lg border-2 border-border border-dashed bg-background shadow-sm hover:shadow-md transition-shadow cursor-pointer text-left focus:outline-none"
onClick={() => data.onNodeClick?.(data.stepSlug)}
style={{ overflow: 'visible', position: 'relative' }}
Expand Down Expand Up @@ -109,21 +114,21 @@ export function AutomationLoopContainer({
</button>

{/* Bottom Target Handle - incoming from lower-ranked nodes */}
<Handle
<InvisibleHandle
type="target"
position={Position.Bottom}
id="bottom-target"
className="!w-2 !h-2 !border-0 !bg-transparent !z-10"
className="size-2! border-0! bg-transparent! z-10!"
isConnectable={true}
style={{ bottom: 0, left: bottomTargetLeft, opacity: 0 }}
/>

{/* Bottom Source Handle - outgoing to lower-ranked nodes */}
<Handle
<InvisibleHandle
type="source"
position={Position.Bottom}
id="bottom-source"
className="!w-2 !h-2 !border-0 !bg-transparent !z-10"
className="size-2! border-0! bg-transparent! z-10!"
isConnectable={true}
style={{ bottom: 0, left: bottomSourceLeft, opacity: 0 }}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,6 @@ import { api } from '@/convex/_generated/api';
import { usePublishAutomationDraft } from '../hooks/use-publish-automation-draft';
import { useCreateDraftFromActive } from '../hooks/use-create-draft-from-active';
import { Button } from '@/components/ui/primitives/button';
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
NavigationMenuViewport,
} from '@/components/ui/navigation/navigation-menu';
import {
TabNavigation,
type TabNavigationItem,
Expand All @@ -23,7 +14,19 @@ import { useToast } from '@/hooks/use-toast';
import { useState } from 'react';
import { useAuth } from '@/hooks/use-convex-auth';
import type { Doc, Id } from '@/convex/_generated/dataModel';
import { ChevronDown } from 'lucide-react';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/overlays/dropdown-menu';
import {
ChevronDown,
MoreVertical,
Upload,
Pencil,
RotateCcw,
} from 'lucide-react';
import { useT } from '@/lib/i18n/client';

interface AutomationNavigationProps {
Expand Down Expand Up @@ -179,79 +182,122 @@ export function AutomationNavigation({
ariaLabel={tCommon('aria.automationsNavigation')}
>
<div className="flex items-center gap-4 ml-auto">
{/* Version select - hidden on mobile (shown in first header row instead) */}
{automation && versions && versions.length > 0 && (
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger className="text-sm h-8 border border-input bg-background hover:bg-accent hover:text-accent-foreground">
{automation.version}
<ChevronDown
className="relative top-px ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuTrigger>
<NavigationMenuContent className="md:w-40">
<ul className="p-1 space-y-1">
{versions.map((version) => (
<li key={version._id}>
<NavigationMenuLink asChild>
<Button
variant="ghost"
size="sm"
className="w-full justify-start"
onClick={() => handleVersionChange(version._id)}
>
<span>{version.version}</span>
<span className="text-xs text-muted-foreground ml-1">
{version.status === 'draft' &&
tCommon('status.draft')}
{version.status === 'active' &&
tCommon('status.active')}
{version.status === 'archived' &&
t('navigation.archived')}
</span>
</Button>
</NavigationMenuLink>
</li>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
</NavigationMenuList>
<NavigationMenuViewport />
</NavigationMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="sm"
className="hidden md:flex text-sm h-8"
>
{automation.version}
<ChevronDown className="ml-1 size-3" aria-hidden="true" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-40">
{versions.map((version) => (
<DropdownMenuItem
key={version._id}
onClick={() => handleVersionChange(version._id)}
>
<span>{version.version}</span>
<span className="text-xs text-muted-foreground ml-1">
{version.status === 'draft' && tCommon('status.draft')}
{version.status === 'active' && tCommon('status.active')}
{version.status === 'archived' &&
tCommon('status.archived')}
</span>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
)}

{automation?.status === 'draft' && (
<Button onClick={handlePublish} disabled={isPublishing} size="sm">
{isPublishing
? t('navigation.publishing')
: t('navigation.publish')}
</Button>
)}
{/* Desktop: Show buttons directly */}
<div className="hidden md:flex items-center gap-4">
{automation?.status === 'draft' && (
<Button onClick={handlePublish} disabled={isPublishing} size="sm">
{isPublishing
? t('navigation.publishing')
: t('navigation.publish')}
</Button>
)}

{automation?.status === 'active' && (
<Button
onClick={handleCreateDraft}
disabled={isCreatingDraft}
size="sm"
variant="outline"
>
{tCommon('actions.edit')}
</Button>
)}
{automation?.status === 'active' && (
<Button
onClick={handleCreateDraft}
disabled={isCreatingDraft}
size="sm"
variant="outline"
>
{tCommon('actions.edit')}
</Button>
)}

{automation?.status === 'archived' && (
<Button
onClick={handlePublish}
disabled={isPublishing}
size="sm"
variant="secondary"
>
{isPublishing
? t('navigation.rollingBack')
: t('navigation.rollback')}
</Button>
)}
</div>

{automation?.status === 'archived' && (
<Button
onClick={handlePublish}
disabled={isPublishing}
size="sm"
variant="secondary"
>
{isPublishing
? t('navigation.rollingBack')
: t('navigation.rollback')}
</Button>
{/* Mobile: Show options dropdown */}
{(automation?.status === 'draft' ||
automation?.status === 'active' ||
automation?.status === 'archived') && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="md:hidden"
aria-label={tCommon('aria.actionsMenu')}
>
<MoreVertical className="size-5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-40">
{automation?.status === 'draft' && (
<DropdownMenuItem
onClick={handlePublish}
disabled={isPublishing}
>
<Upload className="mr-2 size-4" />
{isPublishing
? t('navigation.publishing')
: t('navigation.publish')}
</DropdownMenuItem>
)}
{automation?.status === 'active' && (
<DropdownMenuItem
onClick={handleCreateDraft}
disabled={isCreatingDraft}
>
<Pencil className="mr-2 size-4" />
{tCommon('actions.edit')}
</DropdownMenuItem>
)}
{automation?.status === 'archived' && (
<DropdownMenuItem
onClick={handlePublish}
disabled={isPublishing}
>
<RotateCcw className="mr-2 size-4" />
{isPublishing
? t('navigation.rollingBack')
: t('navigation.rollback')}
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
)}
Comment thread
Israeltheminer marked this conversation as resolved.
</div>
</TabNavigation>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ export function AutomationSidePanel({
return (
<div
ref={panelRef}
style={{ width: `${width}px` }}
className="bg-background border-l border-border flex flex-col flex-[0_0_auto] min-h-0 relative overflow-hidden max-md:!w-full max-md:absolute max-md:inset-0 max-md:z-10"
style={{ '--panel-width': `${width}px` } as React.CSSProperties}
className="bg-background border-l border-border flex flex-col flex-[0_0_auto] min-h-0 relative overflow-hidden w-(--panel-width) max-md:w-full max-md:absolute max-md:inset-0 max-md:z-10"
>
{/* Resize handle - hidden on mobile */}
<div
Expand All @@ -145,11 +145,11 @@ export function AutomationSidePanel({
</div>

{/* Panel header */}
<div className="bg-background/70 backdrop-blur-sm p-3 border-b border-border flex-shrink-0 sticky top-0">
<div className="bg-background/70 backdrop-blur-sm p-3 border-b border-border shrink-0 sticky top-0">
<div className="flex items-center gap-3">
{showAIChat ? (
<>
<div className="p-2 rounded-lg border bg-purple-100 text-purple-700 dark:bg-purple-950 dark:text-purple-300">
<div className="p-2 rounded-lg bg-purple-600 text-white dark:bg-purple-700">
<Sparkles className="size-4" />
</div>
<div className="flex-1">
Expand Down
Loading