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
10 changes: 5 additions & 5 deletions services/platform/app/components/theme/theme-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
createContext,
useContext,
useEffect,
useMemo,
useState,
useCallback,
type ReactNode,
Expand Down Expand Up @@ -90,11 +91,10 @@ export function ThemeProvider({
[updateResolvedTheme],
);

const value: ThemeContextValue = {
theme,
resolvedTheme,
setTheme,
};
const value = useMemo<ThemeContextValue>(
() => ({ theme, resolvedTheme, setTheme }),
[theme, resolvedTheme, setTheme],
);

return (
<ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,10 @@ export function ApprovalsClient({
return (
<Stack gap={1}>
{list.map((p, index) => {
const id =
safeGetString(p, 'productId', '') ||
safeGetString(p, 'id', '') ||
String(index);
const name =
safeGetString(p, 'name', '') ||
safeGetString(p, 'productName', '');
Expand All @@ -431,7 +435,7 @@ export function ApprovalsClient({
'/assets/placeholder-image.png';

return (
<HStack key={index} gap={2}>
<HStack key={id} gap={2}>
<div className="size-5 bg-muted rounded flex-shrink-0 overflow-hidden">
<Image
src={image}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client';

import { useMemo } from 'react';
import {
BaseEdge,
EdgeLabelRenderer,
Expand Down Expand Up @@ -42,8 +43,7 @@ export function AutomationEdge({
data,
type = 'smoothstep',
}: AutomationEdgeProps) {
// Use appropriate path function based on edge type
const [edgePath, defaultLabelX, defaultLabelY] = (() => {
const [edgePath, defaultLabelX, defaultLabelY] = useMemo(() => {
if (type === 'smoothstep' || type === 'default') {
return getSmoothStepPath({
sourceX,
Expand All @@ -62,7 +62,6 @@ export function AutomationEdge({
targetY,
});
}
// bezier (or fallback)
return getBezierPath({
sourceX,
sourceY,
Expand All @@ -71,42 +70,34 @@ export function AutomationEdge({
targetY,
targetPosition,
});
})();
}, [type, sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition]);

// Calculate smart label position
const calculateLabelPosition = () => {
const { labelX, labelY } = useMemo(() => {
const labelPosition = data?.labelPosition || 'center';
let labelX = defaultLabelX;
let labelY = defaultLabelY;
let lx = defaultLabelX;
let ly = defaultLabelY;

// Adjust position based on labelPosition setting
if (labelPosition === 'source') {
// Position label 25% from source
labelX = sourceX + (targetX - sourceX) * 0.25;
labelY = sourceY + (targetY - sourceY) * 0.25;
lx = sourceX + (targetX - sourceX) * 0.25;
ly = sourceY + (targetY - sourceY) * 0.25;
} else if (labelPosition === 'target') {
// Position label 75% toward target
labelX = sourceX + (targetX - sourceX) * 0.75;
labelY = sourceY + (targetY - sourceY) * 0.75;
lx = sourceX + (targetX - sourceX) * 0.75;
ly = sourceY + (targetY - sourceY) * 0.75;
}

// Special handling for backward connections
if (data?.isBackwardConnection) {
// Offset label to the side for backward connections to avoid overlap
const offsetX = 30; // Offset to the right
labelX += offsetX;
lx += 30;
}

// Apply manual offset if provided
if (data?.labelOffset) {
labelX += data.labelOffset.x;
labelY += data.labelOffset.y;
if (data?.labelOffset?.x) {
lx += data.labelOffset.x;
}
if (data?.labelOffset?.y) {
ly += data.labelOffset.y;
}

return { labelX, labelY };
};

const { labelX, labelY } = calculateLabelPosition();
return { labelX: lx, labelY: ly };
}, [defaultLabelX, defaultLabelY, sourceX, sourceY, targetX, targetY, data?.labelPosition, data?.isBackwardConnection, data?.labelOffset?.x, data?.labelOffset?.y]);

return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -382,8 +382,8 @@ export function AutomationSidePanel({
{t('sidePanel.validationErrors')}
</div>
<ul className="text-xs text-destructive space-y-1">
{errors.map((error, i) => (
<li key={i}>• {error}</li>
{errors.map((error, index) => (
<li key={`${error}-${index}`}>• {error}</li>
))}
</ul>
</div>
Expand All @@ -396,8 +396,8 @@ export function AutomationSidePanel({
{t('sidePanel.validationWarnings')}
</div>
<ul className="text-xs text-amber-600 dark:text-amber-400 space-y-1">
{warnings.map((warning, i) => (
<li key={i}>• {warning}</li>
{warnings.map((warning, index) => (
<li key={`${warning}-${index}`}>• {warning}</li>
))}
</ul>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,8 @@ export function AutomationTester({
{t('tester.dryRun.errors')}:
</p>
<ul className="text-xs text-destructive space-y-0.5">
{dryRunResult.errors.map((err, i) => (
<li key={i}>• {err}</li>
{dryRunResult.errors.map((err, index) => (
<li key={`${err}-${index}`}>• {err}</li>
))}
</ul>
</div>
Expand All @@ -218,8 +218,8 @@ export function AutomationTester({
{t('tester.dryRun.warnings')}:
</p>
<ul className="text-xs text-amber-600 dark:text-amber-400 space-y-0.5">
{dryRunResult.warnings.map((warn, i) => (
<li key={i}>• {warn}</li>
{dryRunResult.warnings.map((warn, index) => (
<li key={`${warn}-${index}`}>• {warn}</li>
))}
</ul>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useNavigate } from '@tanstack/react-router';
import { useQuery } from 'convex/react';
import { X } from 'lucide-react';
Expand Down Expand Up @@ -30,19 +30,28 @@ export function ChatSearchDialog({
const navigate = useNavigate();
const [query, setQuery] = useState('');
const [selectedIndex, setSelectedIndex] = useState(-1);
const selectedIndexRef = useRef(selectedIndex);
selectedIndexRef.current = selectedIndex;
const inputRef = useRef<HTMLInputElement>(null);

const debouncedQuery = useDebounce(query, 300);

const threadsData = useQuery(api.threads.queries.listThreads, {
search: debouncedQuery || undefined,
});
const chats =
threadsData?.map((thread) => ({
_id: thread._id,
title: thread.title ?? t('searchChat.untitledChat'),
createdAt: thread._creationTime,
})) || [];

const chats = useMemo(
() =>
threadsData?.map((thread) => ({
_id: thread._id,
title: thread.title ?? t('searchChat.untitledChat'),
createdAt: thread._creationTime,
formattedDate: thread._creationTime
? formatDateSmart(new Date(thread._creationTime))
: '',
})) ?? [],
[threadsData, t, formatDateSmart],
);

useEffect(() => {
if (isOpen) {
Expand All @@ -68,30 +77,34 @@ export function ChatSearchDialog({
return () => window.removeEventListener('keydown', onKeyDown, true);
}, [isOpen, onOpenChange]);

// Reset selection when results change (debounced query), not on every keystroke
useEffect(() => {
if (isOpen) setSelectedIndex(-1);
}, [isOpen, query]);

const close = () => onOpenChange(false);

const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'ArrowDown') {
e.preventDefault();
setSelectedIndex((i) => Math.min(i + 1, chats.length - 1));
} else if (e.key === 'ArrowUp') {
e.preventDefault();
setSelectedIndex((i) => Math.max(i - 1, 0));
} else if (e.key === 'Enter' && chats[selectedIndex]) {
navigate({
to: '/dashboard/$id/chat/$threadId',
params: { id: organizationId, threadId: chats[selectedIndex]._id },
});
onOpenChange(false);
} else if (e.key === 'Escape') {
e.preventDefault();
onOpenChange(false);
}
};
}, [isOpen, debouncedQuery]);

const close = useCallback(() => onOpenChange(false), [onOpenChange]);

const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'ArrowDown') {
e.preventDefault();
setSelectedIndex((i) => Math.min(i + 1, chats.length - 1));
} else if (e.key === 'ArrowUp') {
e.preventDefault();
setSelectedIndex((i) => Math.max(i - 1, 0));
} else if (e.key === 'Enter' && chats[selectedIndexRef.current]) {
navigate({
to: '/dashboard/$id/chat/$threadId',
params: { id: organizationId, threadId: chats[selectedIndexRef.current]._id },
});
onOpenChange(false);
} else if (e.key === 'Escape') {
e.preventDefault();
onOpenChange(false);
}
},
[chats, navigate, organizationId, onOpenChange],
);
Comment thread
yannickmonney marked this conversation as resolved.

return (
<Dialog
Expand Down Expand Up @@ -149,8 +162,7 @@ export function ChatSearchDialog({
<div className="text-sm text-foreground flex items-center gap-2 w-full min-w-0">
<span className="truncate">{chat.title}</span>
<span className="text-[0.625rem] text-muted-foreground shrink-0 ml-auto">
{chat.createdAt &&
formatDateSmart(new Date(chat.createdAt) || '')}
{chat.formattedDate}
</span>
</div>
</button>
Expand Down
Loading