LFEN-215 HomePage Uplift (#4242)

* refactor: Remove CustomHeader component from AppWrapperPage

* refactor: Remove unused code and fix formatting in MainPage component

- Remove commented out code and unused imports
- Fix indentation and formatting issues in the component

* colors update and component building

* refactor: Update grid and list components in MainPage

- Update grid component in MainPage to use custom navigation hook
- Add folderId parameter to editFlowLink in grid component
- Update list component in MainPage to use custom navigation hook
- Add folderId parameter to editFlowLink in list component

Refactor the grid and list components in MainPage to use the custom navigation hook instead of the react-router-dom's useNavigate hook. This allows for better control and customization of navigation within the components. Additionally, the editFlowLink now includes the folderId parameter when navigating to the flow edit page.

Closes #<issue_number>

* refactor: Update grid and list components in MainPage

Refactor the grid and list components in the MainPage to improve performance and enhance user experience. This includes updating the styling, optimizing code, and fixing formatting issues.

* Refactor header component to dynamically display folder name

* Refactor CSS styles and button component

- Update CSS styles in applies.css to adjust the position of the header notifications dot.
- Refactor the button component in button.tsx to fix a typo in the class names.

Closes #4259

* Refactor folder store and sidebar components

* Refactor header component to dynamically toggle folder sidebar visibility

* Refactor CSS styles and button component

* Refactor CSS styles and button component

* Refactor CSS styles to adjust the width of the text container

* Refactor dropdown and grid components to handle playground click

* Refactor folder sidebar and list components

* Refactor CSS styles and components for HeaderMenu, GridComponent, and ListComponent

* Refactor CSS styles and components for HeaderMenu, GridComponent, ListComponent, dropdown, and button

* Refactor CSS styles to adjust the width of the text container in ListComponent

* refactor: Update folder name display in EmptyPage component

* refactor: Update folder name display in FlowMenu component

* refactor: Update folder name display in AccountMenu and EmptyPage components

* refactor: Update folder name display in AccountMenu, EmptyPage, and FlowMenu components

* refactor: Update folder name display in AppHeader, DashboardWrapperPage, and HomePage components

* refactor: Update folder name display in MainPage components

- Update folder name display in AppHeader, DashboardWrapperPage, and HomePage components
- Update folder name display in AccountMenu, EmptyPage, and FlowMenu components
- Update folder name display in AccountMenu and EmptyPage components
- Update folder name display in FlowMenu component
- Update folder name display in EmptyPage component

Update the folder name display in various components of the MainPage. This ensures that the folder names are correctly displayed in the UI. The affected components include AppHeader, DashboardWrapperPage, HomePage, AccountMenu, EmptyPage, and FlowMenu. This refactor improves the consistency and user experience of the application.

* refactor: Update folder name display in EmptyPage and HomePage components

* refactor: Update folder name display in AppHeader, DashboardWrapperPage, and HomePage components

* refactor: Update folder name display in MainPage components

* file organization

* Refactor folder name display in MainPage, ListComponent, and HomePage components

* Refactor folder name display in MainPage components

* test fixes

* test fixes part 2
This commit is contained in:
Deon Sanchez 2024-11-01 14:23:10 -06:00 • committed by GitHub
commit 85cffda753
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
133 changed files with 2002 additions and 419 deletions

View file

@ -28,11 +28,6 @@ def build_vertex(self, vertex: Vertex) -> Vertex:
@celery_app.task(acks_late=True)
def process_graph_cached_task(
data_graph: dict[str, Any],
inputs: dict | list[dict] | None = None,
clear_cache=False, # noqa: FBT002
session_id=None,
) -> dict[str, Any]:
def process_graph_cached_task() -> dict[str, Any]:
msg = "This task is not implemented yet"
raise NotImplementedError(msg)

View file

@ -7,6 +7,10 @@
<link rel="icon" href="/favicon.ico" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Chivo:ital,wght@0,100..900;1,100..900&display=swap"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap"
rel="stylesheet"

View file

@ -1,5 +1,5 @@
import { Cross2Icon } from "@radix-ui/react-icons";
import { useState } from "react";
import { useEffect, useState } from "react";
import IconComponent from "../../components/genericIconComponent";
import {
Popover,
@ -13,6 +13,8 @@ import SingleAlert from "./components/singleAlertComponent";
export default function AlertDropdown({
children,
notificationRef,
onClose,
}: AlertDropdownType): JSX.Element {
const notificationList = useAlertStore((state) => state.notificationList);
const clearNotificationList = useAlertStore(
@ -27,16 +29,29 @@ export default function AlertDropdown({
const [open, setOpen] = useState(false);
useEffect(() => {
if (!open) {
onClose?.();
}
}, [open]);
return (
<Popover
data-testid="notification-dropdown"
open={open}
onOpenChange={(target) => {
setOpen(target);
if (target) setNotificationCenter(false);
if (target) {
setNotificationCenter(false);
}
}}
>
<PopoverTrigger>{children}</PopoverTrigger>
<PopoverContent className="noflow nowheel nopan nodelete nodrag flex h-[500px] w-[500px] flex-col">
<PopoverContent
ref={notificationRef}
data-testid="notification-dropdown-content"
className="noflow nowheel nopan nodelete nodrag z-10 flex h-[500px] w-[500px] flex-col"
>
<div className="text-md flex flex-row justify-between pl-3 font-medium text-foreground">
Notifications
<div className="flex gap-3 pr-3">

View file

@ -1,3 +1,4 @@
import ForwardedIconComponent from "@/components/genericIconComponent";
import { useLogout } from "@/controllers/API/queries/auth";
import { CustomFeedbackDialog } from "@/customization/components/custom-feedback-dialog";
import { CustomHeaderMenuItemsTitle } from "@/customization/components/custom-header-menu-items-title";
@ -50,8 +51,10 @@ export const AccountMenu = () => {
<HeaderMenuItems position="right">
{ENABLE_DATASTAX_LANGFLOW && <CustomHeaderMenuItemsTitle />}
<HeaderMenuItemsSection>
<div className="flex h-[46px] w-full items-center justify-between pl-2">
<div className="text-sm text-zinc-500">Version {version}</div>
<div className="flex h-[46px] w-full items-center justify-between px-3">
<div className="text-xs font-medium text-zinc-500">
Version {version}
</div>
<ThemeButtons />
</div>
{ENABLE_DATASTAX_LANGFLOW ? (
@ -60,6 +63,7 @@ export const AccountMenu = () => {
</HeaderMenuItemLink>
) : (
<HeaderMenuItemButton
icon="arrow-right"
onClick={() => {
navigate("/settings");
}}
@ -106,19 +110,19 @@ export const AccountMenu = () => {
</HeaderMenuItemLink>
)}
<HeaderMenuItemLink newPage href="https://twitter.com/langflow_ai">
Follow {ENABLE_DATASTAX_LANGFLOW ? "Langflow" : "us"} on X
Follow Langflow on X
</HeaderMenuItemLink>
<HeaderMenuItemLink newPage href="https://discord.gg/EqksyE2EX9">
Join our Discord
Join the Langflow Discord
</HeaderMenuItemLink>
</HeaderMenuItemsSection>
<HeaderMenuItemsSection>
{ENABLE_DATASTAX_LANGFLOW ? (
<HeaderMenuItemLink href="/session/logout">
<HeaderMenuItemLink href="/session/logout" icon="log-out">
Logout
</HeaderMenuItemLink>
) : (
<HeaderMenuItemButton onClick={handleLogout}>
<HeaderMenuItemButton onClick={handleLogout} icon="log-out">
Logout
</HeaderMenuItemButton>
)}

View file

@ -64,13 +64,15 @@ export const MenuBar = ({}: {}): JSX.Element => {
customStringify(currentFlow) !== customStringify(currentSavedFlow);
const savedText =
updatedAt && changesNotSaved
? SAVED_HOVER +
new Date(updatedAt).toLocaleString("en-US", {
hour: "numeric",
minute: "numeric",
})
: "Saved";
updatedAt && changesNotSaved ? (
SAVED_HOVER +
new Date(updatedAt).toLocaleString("en-US", {
hour: "numeric",
minute: "numeric",
})
) : (
<div className="text-[#059669]">Saved</div>
);
function handleAddFlow() {
try {
@ -91,11 +93,12 @@ export const MenuBar = ({}: {}): JSX.Element => {
function printByBuildStatus() {
if (isBuilding) {
return "Building...";
return <div className="truncate">Building...</div>;
} else if (saveLoading) {
return "Saving...";
return <div className="truncate">Saving...</div>;
}
return savedText;
// return savedText;
return <div className="truncate text-[#059669]">Saved</div>;
}
const handleSave = () => {
@ -108,36 +111,44 @@ export const MenuBar = ({}: {}): JSX.Element => {
useHotkeys(changes, handleSave, { preventDefault: true });
return currentFlow && onFlowPage ? (
<div className="flex items-center">
<div className="header-menu-bar">
<div className="flex items-baseline gap-2 truncate">
<div className="header-menu-bar w-full justify-end truncate">
{currentFolder?.name && (
<>
<div className="flex hidden truncate md:flex">
<div
className="cursor-pointer whitespace-nowrap font-normal text-zinc-500 dark:text-zinc-400"
className="cursor-pointer truncate text-muted-foreground hover:text-primary"
onClick={() => {
navigate("/");
}}
>
{currentFolder?.name}
</div>
<div className="px-2 font-normal text-zinc-500">/</div>
</>
</div>
)}
</div>
<div className="hidden w-fit font-normal text-muted-foreground md:flex">
/
</div>
<div className="w-fit truncate text-sm sm:overflow-visible sm:whitespace-normal">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div className="header-menu-bar-display-2">
<div className="header-menu-bar-display-2 group truncate">
<div
className="header-menu-flow-name-2 flex"
className="header-menu-flow-name-2 truncate"
data-testid="flow-configuration-button"
>
<div
className="whitespace-nowrap font-semibold text-black dark:text-[white]"
className="truncate font-semibold group-hover:text-primary dark:text-[white]"
data-testid="flow_name"
>
{currentFlow.name}
</div>
</div>
<IconComponent name="ChevronDown" className="w-4 text-zinc-500" />
<IconComponent
name="ChevronDown"
className="flex h-5 w-5 text-muted-foreground group-hover:text-primary"
/>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-44 bg-white dark:bg-black">
@ -268,7 +279,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
></FlowSettingsModal>
<FlowLogsModal open={openLogs} setOpen={setOpenLogs}></FlowLogsModal>
</div>
<div className="flex items-center">
<div className="hidden w-full items-center truncate sm:flex">
{!autoSaving && (
<Button
variant="primary"
@ -309,14 +320,16 @@ export const MenuBar = ({}: {}): JSX.Element => {
)
}
side="bottom"
styleClasses="cursor-default"
styleClasses="cursor-default z-10"
>
<div className="ml-2 flex cursor-default items-center gap-2 text-sm text-muted-foreground transition-all">
<div className="flex cursor-default items-center gap-2 text-sm text-zinc-500 transition-all">
<div>{printByBuildStatus()}</div>
<div className="mr-3 flex cursor-default items-center gap-2 truncate text-sm text-muted-foreground">
<div className="flex cursor-default items-center gap-2 truncate text-sm text-zinc-500">
<div className="w-full truncate text-xs">
{printByBuildStatus()}
</div>
</div>
<button
data-testid="stop_building_button"
data-testid="stop_building_button "
disabled={!isBuilding}
onClick={(_) => {
if (isBuilding) {
@ -325,7 +338,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
}}
className={
isBuilding
? "flex items-center gap-1.5 text-status-red transition-all"
? "flex hidden items-center gap-1.5 text-xs text-status-red sm:flex"
: "hidden"
}
>

View file

@ -1,3 +1,4 @@
import ShadTooltip from "@/components/shadTooltipComponent";
import { useDarkStore } from "@/stores/darkStore";
import { FaGithub } from "react-icons/fa";
@ -5,13 +6,17 @@ export const GithubStarComponent = () => {
const stars = useDarkStore((state) => state.stars);
return (
<div className="header-github-link gap-1 bg-zinc-100 dark:bg-zinc-900 dark:hover:bg-zinc-900">
<FaGithub className="h-4 w-4 text-black dark:text-[white]" />
<div className="hidden text-black dark:text-[white] lg:block">Star</div>
<div className="header-github-display text-black dark:text-[white]">
{stars?.toLocaleString() ?? 0}
<ShadTooltip content="Go to Github repo" side="bottom" styleClasses="z-10">
<div className="header-github-link-box gap-1 bg-muted hover:bg-zinc-200 dark:bg-zinc-900 dark:hover:bg-zinc-800">
<FaGithub className="h-4 w-4 text-black dark:text-[white]" />
<div className="hidden text-xs font-semibold text-black dark:text-[white] lg:block">
Star
</div>
<div className="header-github-display text-xs font-semibold text-black dark:text-[white]">
{stars.toLocaleString() ?? 0}
</div>
</div>
</div>
</ShadTooltip>
);
};

View file

@ -1,3 +1,4 @@
import ForwardedIconComponent from "@/components/genericIconComponent";
import { Skeleton } from "@/components/ui/skeleton";
import { Menu, Transition } from "@headlessui/react";
import { ChevronsUpDown } from "lucide-react";
@ -54,28 +55,36 @@ export const HeaderMenuSelector = ({
);
const BASE_ITEM_STYLES =
"group flex w-full items-center justify-between h-[46px] rounded-md pl-2 py-2 text-sm text-gray-900 dark:text-[white] dark:hover:bg-zinc-800 hover:bg-gray-100";
"group flex w-full items-center justify-between h-[40px] my-1 rounded-md px-3 text-sm text-gray-900 dark:text-[white] dark:hover:bg-zinc-800 hover:bg-gray-100";
export const HeaderMenuItemLink = ({
href = "#",
selected = false,
children,
newPage = false,
icon = "external-link",
}) => (
<Menu.Item>
{({ active }) => (
<a
className={`${selected ? "bg-gray-50" : ""} ${BASE_ITEM_STYLES}`}
className={`group ${selected ? "bg-gray-50" : ""} ${BASE_ITEM_STYLES}`}
href={href}
{...(newPage ? { rel: "noreferrer", target: "_blank" } : {})}
>
{children}
{icon && (
<ForwardedIconComponent
name={icon}
className="side-bar-button-size hidden h-[18px] w-[18px] group-hover:block" // Use group-hover:block to show on hover
/>
)}
</a>
)}
</Menu.Item>
);
export const HeaderMenuItemButton = ({
icon = "",
onClick,
selected = false,
children,
@ -87,6 +96,12 @@ export const HeaderMenuItemButton = ({
onClick={onClick}
>
{children}
{icon && (
<ForwardedIconComponent
name={icon}
className="side-bar-button-size hidden h-[18px] w-[18px] group-hover:block" // Use group-hover:block to show on hover
/>
)}
</button>
)}
</Menu.Item>
@ -108,7 +123,7 @@ export const HeaderMenuItems = ({
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items
className={`absolute dark:bg-black ${positionClass} z-[999] mt-2 w-[20rem] origin-top-right rounded-md bg-[white] shadow-lg ring-1 ring-black/5 focus:outline-none`}
className={`absolute dark:bg-black ${positionClass} z-[999] mt-2 w-[20rem] origin-top-right rounded-md border bg-[white] shadow-lg ring-1 ring-black/5 focus:outline-none dark:border-zinc-800`}
>
{children}
</Menu.Items>
@ -118,7 +133,7 @@ export const HeaderMenuItems = ({
export const HeaderMenuItemsSection = ({ children }) => (
<>
<div className="m-1 p-1">{children}</div>
<div className="px-1">{children}</div>
<hr className="border-gray-200 last:hidden dark:border-zinc-700" />
</>
);

View file

@ -9,6 +9,7 @@ import {
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import useTheme from "@/customization/hooks/use-custom-theme";
import useAlertStore from "@/stores/alertStore";
import { useEffect, useRef, useState } from "react";
import ForwardedIconComponent from "../genericIconComponent";
import { Button } from "../ui/button";
import { Separator } from "../ui/separator";
@ -21,12 +22,34 @@ import GithubStarComponent from "./components/GithubStarButton";
export default function AppHeader(): JSX.Element {
const notificationCenter = useAlertStore((state) => state.notificationCenter);
const navigate = useCustomNavigate();
const [activeState, setActiveState] = useState<"notifications" | null>(null);
const lastPath = window.location.pathname.split("/").filter(Boolean).pop();
const notificationRef = useRef<HTMLButtonElement | null>(null);
const notificationContentRef = useRef<HTMLDivElement | null>(null);
useTheme();
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
const target = event.target as Node;
const isNotificationButton = notificationRef.current?.contains(target);
const isNotificationContent =
notificationContentRef.current?.contains(target);
if (!isNotificationButton && !isNotificationContent) {
setActiveState(null);
}
}
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);
return (
<div className="relative flex items-center border-b px-4 py-1.5 dark:bg-black">
<div className="fixed left-0 top-0 z-[1] flex h-[62px] w-full items-center border-b px-5 py-2.5 dark:bg-background">
{/* Left Section */}
<div className="flex w-full items-center gap-2 lg:max-w-[475px]">
<div className={`absolute left-[20px] flex gap-2`}>
<Button
unstyled
onClick={() => navigate("/")}
@ -36,12 +59,12 @@ export default function AppHeader(): JSX.Element {
{ENABLE_DATASTAX_LANGFLOW ? (
<ShortDataStaxLogo className="fill-black dark:fill-[white]" />
) : ENABLE_NEW_LOGO ? (
<ShortLangFlowIcon className="fill-black dark:fill-[white]" />
<ShortLangFlowIcon className="h-5 w-5 fill-black dark:fill-[white]" />
) : (
<span className="fill-black text-2xl dark:fill-white">⛓️</span>
)}
</Button>
{ENABLE_DATASTAX_LANGFLOW && (
{!ENABLE_DATASTAX_LANGFLOW && (
<>
<CustomOrgSelector />
<CustomProductSelector />
@ -50,69 +73,93 @@ export default function AppHeader(): JSX.Element {
</div>
{/* Middle Section */}
<div className="mx-auto flex items-center px-5">
<div className="absolute left-[225px] right-[225px] truncate md:left-[230px] md:right-[230px] lg:left-[350px] lg:right-[350px] xl:left-[350px] xl:right-[350px] 2xl:left-[500px] 2xl:right-[500px]">
<FlowMenu />
</div>
{/* Right Section */}
<div className="flex items-center gap-2">
<div className={`absolute right-[20px] flex gap-2`}>
{!ENABLE_DATASTAX_LANGFLOW && (
<>
<Button
unstyled
className="flex items-center"
className="flex hidden items-center whitespace-nowrap 2xl:inline"
onClick={() =>
window.open("https://github.com/langflow-ai/langflow", "_blank")
}
>
<GithubStarComponent />
</Button>
<Separator
orientation="vertical"
className="h-7 dark:border-zinc-700"
/>
</>
)}
<AlertDropdown>
<ShadTooltip content="Notifications" side="bottom">
<Button variant="ghost" className="flex text-sm font-medium">
{notificationCenter && (
<div className="header-notifications-dot"></div>
)}
<AlertDropdown
notificationRef={notificationContentRef}
onClose={() => setActiveState(null)}
>
<ShadTooltip
content="Notifications and errors"
side="bottom"
styleClasses="z-10"
>
<Button
ref={notificationRef}
variant="ghost"
className={`relative ${activeState === "notifications" ? "bg-accent text-accent-foreground" : ""}`}
onClick={() =>
setActiveState((prev) =>
prev === "notifications" ? null : "notifications",
)
}
>
<span
className={
notificationCenter ? `header-notifications-dot` : "hidden"
}
/>
<ForwardedIconComponent
name="bell"
className="side-bar-button-size"
className="side-bar-button-size h-[18px] w-[18px]"
aria-hidden="true"
/>
Notifications
<span className="hidden whitespace-nowrap lg:inline">
Notifications
</span>
</Button>
</ShadTooltip>
</AlertDropdown>
{!ENABLE_DATASTAX_LANGFLOW && (
<>
<ShadTooltip content="Store" side="bottom">
<ShadTooltip
content="Go to LangflowStore"
side="bottom"
styleClasses="z-10"
>
<Button
variant="ghost"
className="flex items-center text-sm font-medium"
onClick={() => navigate("/store")}
className={`flex items-center text-sm font-medium ${lastPath === "store" ? "bg-accent text-accent-foreground" : ""}`}
onClick={() => {
navigate("/store");
}}
data-testid="button-store"
>
<ForwardedIconComponent
name="Store"
className="side-bar-button-size"
className="side-bar-button-size h-[18px] w-[18px]"
/>
Store
<span className="hidden whitespace-nowrap lg:inline">
Store
</span>
</Button>
</ShadTooltip>
<Separator
orientation="vertical"
className="h-7 dark:border-zinc-700"
className="my-auto h-7 dark:border-zinc-700"
/>
</>
)}
{ENABLE_DATASTAX_LANGFLOW && (
<>
<ShadTooltip content="Docs" side="bottom">
<ShadTooltip content="Docs" side="bottom" styleClasses="z-10">
<Button
variant="ghost"
className="flex text-sm font-medium"
@ -125,13 +172,13 @@ export default function AppHeader(): JSX.Element {
>
<ForwardedIconComponent
name="book-open-text"
className="side-bar-button-size"
className="side-bar-button-size h-[18px] w-[18px]"
aria-hidden="true"
/>
Docs
</Button>
</ShadTooltip>
<ShadTooltip content="Settings" side="bottom">
<ShadTooltip content="Settings" side="bottom" styleClasses="z-10">
<Button
data-testid="user-profile-settings"
variant="ghost"
@ -140,18 +187,20 @@ export default function AppHeader(): JSX.Element {
>
<ForwardedIconComponent
name="Settings"
className="side-bar-button-size"
className="side-bar-button-size h-[18px] w-[18px]"
/>
Settings
</Button>
</ShadTooltip>
<Separator
orientation="vertical"
className="h-7 dark:border-zinc-700"
className="my-auto h-7 dark:border-zinc-700"
/>
</>
)}
<AccountMenu />
<div className="ml-3 flex">
<AccountMenu />
</div>
</div>
</div>
);

View file

@ -56,6 +56,10 @@ const SideBarFoldersButtonsComponent = ({
pathname.split("/").length < (ENABLE_CUSTOM_PARAM ? 5 : 4);
const myCollectionId = useFolderStore((state) => state.myCollectionId);
const folderIdDragging = useFolderStore((state) => state.folderIdDragging);
const showFolderModal = useFolderStore((state) => state.showFolderModal);
const setShowFolderModal = useFolderStore(
(state) => state.setShowFolderModal,
);
const checkPathName = (itemId: string) => {
if (urlWithoutPath && itemId === myCollectionId) {
@ -252,8 +256,17 @@ const SideBarFoldersButtonsComponent = ({
isDeletingFolder;
const HeaderButtons = () => (
<div className="mt-4 flex shrink-0 items-center justify-between gap-2">
<div className="text-md flex-1 font-semibold">Folders</div>
<div className="my-4 flex shrink-0 items-center justify-between gap-1">
<Button
variant="ghost"
className="h-7 w-7 border-0 text-zinc-500 hover:bg-zinc-200 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-white lg:hidden"
size="icon"
onClick={() => setShowFolderModal(!showFolderModal)}
data-testid="upload-folder-button"
>
<IconComponent name="panel-right-open" className="h-4 w-4" />
</Button>
<div className="flex-1 text-sm font-semibold">Folders</div>
<UploadFolderButton
onClick={handleUploadFlowsToFolder}
disabled={isUpdatingFolder}
@ -263,31 +276,31 @@ const SideBarFoldersButtonsComponent = ({
);
const AddFolderButton = ({ onClick, disabled }) => (
<ShadTooltip content="Add a new folder">
<ShadTooltip content="Create new folder" styleClasses="z-10">
<Button
variant="primary"
variant="ghost"
size="icon"
className="border-0"
className="h-7 w-7 border-0 text-zinc-500 hover:bg-zinc-200 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-white"
onClick={onClick}
data-testid="add-folder-button"
disabled={disabled}
>
<IconComponent name="Plus" className="w-5" />
<IconComponent name="Plus" className="h-4 w-4" />
</Button>
</ShadTooltip>
);
const UploadFolderButton = ({ onClick, disabled }) => (
<ShadTooltip content="Upload a folder">
<ShadTooltip content="Upload a folder" styleClasses="z-10">
<Button
variant="primary"
variant="ghost"
className="h-7 w-7 border-0 text-zinc-500 hover:bg-zinc-200 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-white"
size="icon"
className="border-0"
onClick={onClick}
data-testid="upload-folder-button"
disabled={disabled}
>
<IconComponent name="Upload" className="w-4" />
<IconComponent name="Upload" className="h-4 w-4" />
</Button>
</ShadTooltip>
);
@ -295,7 +308,7 @@ const SideBarFoldersButtonsComponent = ({
const FolderSelectItem = ({ name, iconName }) => (
<div
className={cn(
name === "Delete" ? "text-error" : "",
name === "Delete" ? "text-destructive" : "",
"flex items-center font-medium",
)}
>
@ -380,7 +393,7 @@ const SideBarFoldersButtonsComponent = ({
<>
<HeaderButtons />
<div className="flex gap-2 overflow-auto lg:h-[70vh] lg:flex-col">
<div className="flex h-[70vh] flex-col gap-2 overflow-auto">
<>
{!loading ? (
folders.map((item, index) => {
@ -398,8 +411,8 @@ const SideBarFoldersButtonsComponent = ({
className={cn(
buttonVariants({ variant: "ghost" }),
checkPathName(item.id!)
? "bg-muted hover:bg-muted"
: "border hover:bg-transparent lg:border-transparent lg:hover:border-border",
? "bg-zinc-200 hover:bg-zinc-200 dark:bg-zinc-800"
: "hover:bg-transparent hover:bg-zinc-200 dark:hover:bg-zinc-800 lg:border-transparent",
"group flex w-full shrink-0 cursor-pointer gap-2 opacity-100 lg:min-w-full",
folderIdDragging === item.id! ? "bg-border" : "",
)}
@ -412,10 +425,6 @@ const SideBarFoldersButtonsComponent = ({
className="flex w-full items-center justify-between"
>
<div className="flex items-center gap-2">
<IconComponent
name={"folder"}
className="mr-2 w-4 flex-shrink-0 justify-start stroke-[1.5] opacity-100"
/>
{editFolderName?.edit && !isUpdatingFolder ? (
<div>
<Input
@ -452,7 +461,7 @@ const SideBarFoldersButtonsComponent = ({
/>
</div>
) : (
<span className="block w-full grow truncate opacity-100">
<span className="block w-full grow truncate text-[13px] opacity-100">
{item.name}
</span>
)}
@ -461,16 +470,24 @@ const SideBarFoldersButtonsComponent = ({
onValueChange={(value) => handleSelectChange(value, item)}
value=""
>
<SelectTrigger
className="w-fit"
id={`options-trigger-${item.name}`}
data-testid="more-options-button"
<ShadTooltip
content="Options"
side="right"
styleClasses="z-10"
>
<IconComponent
name={"MoreHorizontal"}
className="hidden w-4 stroke-[1.5] px-0 text-primary group-hover:block"
/>
</SelectTrigger>
<SelectTrigger
className="w-fit"
id={`options-trigger-${item.name}`}
data-testid="more-options-button"
>
<IconComponent
name={"MoreHorizontal"}
className={`w-4 stroke-[1.5] px-0 text-zinc-500 group-hover:block group-hover:text-black dark:text-zinc-400 dark:group-hover:text-white ${
checkPathName(item.id!) ? "block" : "hidden"
}`}
/>
</SelectTrigger>
</ShadTooltip>
<SelectContent
align="end"
alignOffset={-16}

View file

@ -1,20 +1,48 @@
import * as React from "react";
import { cn } from "../../utils/utils";
import ForwardedIconComponent from "../genericIconComponent";
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
extends React.InputHTMLAttributes<HTMLInputElement> {
icon?: string;
inputClassName?: string;
}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
data-testid=""
type={type}
className={cn("nopan nodelete nodrag noflow primary-input", className)}
ref={ref}
{...props}
/>
);
({ className, inputClassName, icon = "", type, ...props }, ref) => {
if (icon) {
return (
<label className={cn("relative block w-full", className)}>
<ForwardedIconComponent
name={icon}
className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 transform text-muted-foreground"
/>
<input
data-testid=""
type={type}
className={cn(
"nopan nodelete nodrag noflow form-input block w-full appearance-none truncate rounded-md border-border bg-background px-3 pl-9 text-left text-sm placeholder:text-muted-foreground focus:border-black focus:placeholder-transparent focus:ring-zinc-300 disabled:cursor-not-allowed disabled:opacity-50 dark:focus:border-white dark:focus:ring-zinc-800",
inputClassName,
)}
ref={ref}
{...props}
/>
</label>
);
} else {
return (
<input
data-testid=""
type={type}
className={cn(
"nopan nodelete nodrag noflow primary-input",
className,
)}
ref={ref}
{...props}
/>
);
}
},
);
Input.displayName = "Input";

View file

@ -10,3 +10,4 @@ export const ENABLE_INTEGRATIONS = false;
export const ENABLE_NEW_IO_MODAL = false;
export const ENABLE_NEW_LOGO = false;
export const ENABLE_DATASTAX_LANGFLOW = false;
export const ENABLE_HOMEPAGE = true;

View file

@ -1,6 +1,5 @@
import AlertDisplayArea from "@/alerts/displayArea";
import CrashErrorComponent from "@/components/crashErrorComponent";
import { CustomHeader } from "@/customization/components/custom-header";
import { ErrorBoundary } from "react-error-boundary";
import { Outlet } from "react-router-dom";
import { GenericErrorComponent } from "./components/GenericErrorComponent";
@ -10,8 +9,7 @@ export function AppWrapperPage() {
const { healthCheckTimeout, fetchingHealth, refetch } = useHealthCheck();
return (
<div className="flex h-full flex-col">
<CustomHeader />
<div className="flex flex-col">
<ErrorBoundary
onReset={() => {
// any reset function

View file

@ -8,7 +8,9 @@ export function DashboardWrapperPage() {
return (
<div className="flex h-screen w-full flex-col">
<AppHeader />
<Outlet />
<div className="mt-[62px] flex h-[calc(100vh-62px)] w-full flex-row">
<Outlet />
</div>
</div>
);
}

View file

@ -0,0 +1,120 @@
import ForwardedIconComponent from "@/components/genericIconComponent";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
import useAlertStore from "@/stores/alertStore";
import { FlowType } from "@/types/flow";
import { downloadFlow } from "@/utils/reactflowUtils";
import useDuplicateFlows from "../../oldComponents/componentsComponent/hooks/use-handle-duplicate";
import useSelectOptionsChange from "../../oldComponents/componentsComponent/hooks/use-select-options-change";
type DropdownComponentProps = {
flowData: FlowType;
setOpenDelete: (open: boolean) => void;
handlePlaygroundClick?: () => void;
};
const DropdownComponent = ({
flowData,
setOpenDelete,
handlePlaygroundClick,
}: DropdownComponentProps) => {
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const { handleDuplicate } = useDuplicateFlows(
[flowData.id],
[flowData],
() => {},
setSuccessData,
() => {},
() => {},
"flow",
);
const handleExport = () => {
downloadFlow(flowData, flowData.name, flowData.description);
setSuccessData({ title: `${flowData.name} exported successfully` });
};
const { handleSelectOptionsChange } = useSelectOptionsChange(
[flowData.id],
setErrorData,
setOpenDelete,
handleDuplicate,
handleExport,
);
return (
<>
{/* <DropdownMenuItem onClick={() => {}} className="cursor-pointer">
<ForwardedIconComponent
name="square-pen"
aria-hidden="true"
className="mr-2 h-4 w-4"
/>
Edit details
</DropdownMenuItem> */}
{handlePlaygroundClick && (
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
handlePlaygroundClick();
}}
className="cursor-pointer sm:hidden"
>
<ForwardedIconComponent
name="play"
aria-hidden="true"
className="mr-2 h-4 w-4"
/>
Playground
</DropdownMenuItem>
)}
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
handleSelectOptionsChange("export");
}}
className="cursor-pointer"
data-testid="btn-download-json"
>
<ForwardedIconComponent
name="download"
aria-hidden="true"
className="mr-2 h-4 w-4"
/>
Download JSON
</DropdownMenuItem>
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
handleSelectOptionsChange("duplicate");
}}
className="cursor-pointer"
data-testid="btn-duplicate-flow"
>
<ForwardedIconComponent
name="copy-plus"
aria-hidden="true"
className="mr-2 h-4 w-4"
/>
Duplicate
</DropdownMenuItem>
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
setOpenDelete(true);
}}
className="cursor-pointer text-red-500 focus:text-red-500 dark:text-red-500 dark:focus:text-red-500"
>
<ForwardedIconComponent
name="trash"
aria-hidden="true"
className="mr-2 h-4 w-4"
/>
Delete
</DropdownMenuItem>
</>
);
};
export default DropdownComponent;

View file

@ -0,0 +1,203 @@
import ForwardedIconComponent from "@/components/genericIconComponent";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import { track } from "@/customization/utils/analytics";
import useDeleteFlow from "@/hooks/flows/use-delete-flow";
import DeleteConfirmationModal from "@/modals/deleteConfirmationModal";
import IOModal from "@/modals/IOModal";
import useAlertStore from "@/stores/alertStore";
import useFlowsManagerStore from "@/stores/flowsManagerStore";
import { FlowType } from "@/types/flow";
import { getInputsAndOutputs } from "@/utils/storeUtils";
import { useState } from "react";
import { useParams } from "react-router-dom";
import useDescriptionModal from "../../oldComponents/componentsComponent/hooks/use-description-modal";
import { getTemplateStyle } from "../../utils/get-template-style";
import { timeElapsed } from "../../utils/time-elapse";
import DropdownComponent from "../dropdown";
const GridComponent = ({ flowData }: { flowData: FlowType }) => {
const navigate = useCustomNavigate();
const [openPlayground, setOpenPlayground] = useState(false);
const [loadingPlayground, setLoadingPlayground] = useState(false);
const [openDelete, setOpenDelete] = useState(false);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const { deleteFlow } = useDeleteFlow();
const setErrorData = useAlertStore((state) => state.setErrorData);
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
const { folderId } = useParams();
const isComponent = flowData.is_component ?? false;
const setFlowToCanvas = useFlowsManagerStore(
(state) => state.setFlowToCanvas,
);
const { icon, icon_bg_color } = getTemplateStyle(flowData);
const editFlowLink = `/flow/${flowData.id}${folderId ? `/folder/${folderId}` : ""}`;
function hasPlayground(flow?: FlowType) {
if (!flow) {
return false;
}
const { inputs, outputs } = getInputsAndOutputs(flow?.data?.nodes ?? []);
return inputs.length > 0 || outputs.length > 0;
}
const handlePlaygroundClick = () => {
track("Playground Button Clicked", { flowId: flowData.id });
setLoadingPlayground(true);
if (flowData) {
if (!hasPlayground(flowData)) {
setErrorData({
title: "Error",
list: ["This flow doesn't have a playground."],
});
setLoadingPlayground(false);
return;
}
setCurrentFlow(flowData);
setOpenPlayground(true);
setLoadingPlayground(false);
} else {
setErrorData({
title: "Error",
list: ["Error getting flow data."],
});
}
};
const handleClick = async () => {
if (!isComponent) {
await setFlowToCanvas(flowData);
navigate(editFlowLink);
}
};
const handleDelete = () => {
deleteFlow({ id: [flowData.id] })
.then(() => {
setSuccessData({
title: "Selected items deleted successfully",
});
})
.catch(() => {
setErrorData({
title: "Error deleting items",
list: ["Please try again"],
});
});
};
const descriptionModal = useDescriptionModal([flowData?.id], "flow");
return (
<>
<div
key={flowData.id}
onClick={handleClick}
className={`my-1 flex flex-col rounded-lg border border-zinc-100 p-5 shadow-sm hover:border-border dark:border-zinc-800 dark:hover:border-muted-foreground ${
isComponent ? "cursor-default" : "cursor-pointer"
}`}
>
<div className="flex w-full items-center gap-2">
<div
className={`mr-3 flex rounded-lg border ${flowData?.icon_bg_color || icon_bg_color} p-3`}
>
<ForwardedIconComponent
name={flowData?.icon || icon}
aria-hidden="true"
className="h-5 w-5 dark:text-black"
/>
</div>
<div className="flex w-full min-w-0 items-center justify-between">
<div className="flex min-w-0 flex-col">
<div className="text-md truncate font-semibold">
{flowData.name}
</div>
<div className="truncate text-xs text-zinc-500 dark:text-zinc-400">
Edited {timeElapsed(flowData.updated_at)} ago
</div>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
data-testid="home-dropdown-menu"
size="icon"
className="group ml-2 h-10 w-10 border-none dark:hover:bg-zinc-700"
>
<ForwardedIconComponent
name="ellipsis"
aria-hidden="true"
className="h-5 w-5 dark:text-zinc-400 dark:group-hover:text-white"
/>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
className="mr-[30px] w-[185px] bg-white dark:bg-black"
sideOffset={5}
side="bottom"
>
<DropdownComponent
flowData={flowData}
setOpenDelete={setOpenDelete}
/>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
<div className="line-clamp-2 h-full pt-5 text-sm text-zinc-800 dark:text-white">
{flowData.description}
</div>
<div className="flex justify-end pt-[24px]">
{flowData.is_component ? (
<></>
) : (
<Button
disabled={loadingPlayground || !hasPlayground(flowData)}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handlePlaygroundClick();
}}
variant="outline"
>
Playground
</Button>
)}
</div>
</div>
{openPlayground && (
<IOModal
key={flowData.id}
cleanOnClose={true}
open={openPlayground}
setOpen={setOpenPlayground}
>
<></>
</IOModal>
)}
{openDelete && (
<DeleteConfirmationModal
open={openDelete}
setOpen={setOpenDelete}
onConfirm={handleDelete}
description={descriptionModal}
>
<></>
</DeleteConfirmationModal>
)}
</>
);
};
export default GridComponent;

View file

@ -0,0 +1,160 @@
import ForwardedIconComponent from "@/components/genericIconComponent";
import ShadTooltip from "@/components/shadTooltipComponent";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import { useFolderStore } from "@/stores/foldersStore";
import { debounce } from "lodash";
import { useCallback, useEffect, useState } from "react";
interface HeaderComponentProps {
flowType: "flows" | "components";
setFlowType: (flowType: "flows" | "components") => void;
view: "list" | "grid";
setView: (view: "list" | "grid") => void;
setNewProjectModal: (newProjectModal: boolean) => void;
folderName?: string;
setSearch: (search: string) => void;
}
const HeaderComponent = ({
folderName = "",
flowType,
setFlowType,
view,
setView,
setNewProjectModal,
setSearch,
}: HeaderComponentProps) => {
const navigate = useCustomNavigate();
const [debouncedSearch, setDebouncedSearch] = useState("");
const { showFolderModal, setShowFolderModal } = useFolderStore();
// Debounce the setSearch function from the parent
const debouncedSetSearch = useCallback(
debounce((value: string) => {
setSearch(value);
}, 1000),
[setSearch],
);
useEffect(() => {
debouncedSetSearch(debouncedSearch);
return () => {
debouncedSetSearch.cancel(); // Cleanup on unmount
};
}, [debouncedSearch, debouncedSetSearch]);
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setDebouncedSearch(e.target.value);
};
return (
<>
<div
className="flex items-center pb-8 text-xl font-semibold"
data-testid="mainpage_title"
>
<Button
variant="ghost"
className="mr-2 lg:hidden"
size="icon"
onClick={() => setShowFolderModal(!showFolderModal)}
>
<ForwardedIconComponent
name={showFolderModal ? "panel-right-open" : "panel-right-close"}
aria-hidden="true"
className="h-5 w-5 text-zinc-500 dark:text-zinc-400"
/>
</Button>
{folderName}
</div>
<div className="flex flex-row-reverse pb-8">
<div className="w-full border-b dark:border-border" />
{["components", "flows"].map((type) => (
<Button
key={type}
unstyled
id={`${type}-btn`}
onClick={() => setFlowType(type as "flows" | "components")}
className={`border-b ${
flowType === type
? "border-b-2 border-black font-semibold dark:border-white dark:text-white"
: "border-border text-zinc-400 hover:text-black dark:hover:text-white"
} px-3 pb-2`}
>
{type.charAt(0).toUpperCase() + type.slice(1)}
</Button>
))}
</div>
{/* Search and filters */}
<div className="flex justify-between">
<div className="flex w-full xl:w-5/12">
<Input
icon="search"
data-testid="search-store-input"
type="text"
placeholder={`Search ${flowType}...`}
className="mr-2"
value={debouncedSearch}
onChange={handleSearch}
/>
<div className="px-py mr-2 flex rounded-lg border border-zinc-100 bg-zinc-100 dark:border-zinc-800 dark:bg-zinc-800">
{["list", "grid"].map((viewType) => (
<Button
key={viewType}
unstyled
size="icon"
className={`group mx-[2px] my-[2px] rounded-lg p-2 ${
view === viewType
? "bg-white text-black shadow-md dark:bg-black dark:text-white"
: "bg-zinc-100 text-zinc-500 dark:bg-zinc-800 dark:hover:bg-zinc-800"
}`}
onClick={() => setView(viewType as "list" | "grid")}
>
<ForwardedIconComponent
name={viewType === "list" ? "menu" : "layout-grid"}
aria-hidden="true"
className="h-4 w-4 group-hover:text-black dark:group-hover:text-white"
/>
</Button>
))}
</div>
</div>
<div className="flex gap-2">
<ShadTooltip content="Store" side="bottom">
<Button variant="outline" onClick={() => navigate("/store")}>
<ForwardedIconComponent
name="store"
aria-hidden="true"
className="h-4 w-4"
/>
<span className="hidden whitespace-nowrap font-semibold md:inline">
Browse Store
</span>
</Button>
</ShadTooltip>
<ShadTooltip content="New Flow" side="bottom">
<Button
variant="default"
onClick={() => setNewProjectModal(true)}
id="new-project-btn"
>
<ForwardedIconComponent
name="plus"
aria-hidden="true"
className="h-4 w-4"
/>
<span className="hidden whitespace-nowrap font-semibold md:inline">
New Flow
</span>
</Button>
</ShadTooltip>
</div>
</div>
</>
);
};
export default HeaderComponent;

View file

@ -0,0 +1,213 @@
import ForwardedIconComponent from "@/components/genericIconComponent";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import { track } from "@/customization/utils/analytics";
import useDeleteFlow from "@/hooks/flows/use-delete-flow";
import DeleteConfirmationModal from "@/modals/deleteConfirmationModal";
import IOModal from "@/modals/IOModal";
import useAlertStore from "@/stores/alertStore";
import useFlowsManagerStore from "@/stores/flowsManagerStore";
import { FlowType } from "@/types/flow";
import { getInputsAndOutputs } from "@/utils/storeUtils";
import { useState } from "react";
import { useParams } from "react-router-dom";
import useDescriptionModal from "../../oldComponents/componentsComponent/hooks/use-description-modal";
import { getTemplateStyle } from "../../utils/get-template-style";
import { timeElapsed } from "../../utils/time-elapse";
import DropdownComponent from "../dropdown";
const ListComponent = ({ flowData }: { flowData: FlowType }) => {
const navigate = useCustomNavigate();
const [openPlayground, setOpenPlayground] = useState(false);
const [loadingPlayground, setLoadingPlayground] = useState(false);
const [openDelete, setOpenDelete] = useState(false);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const { deleteFlow } = useDeleteFlow();
const setErrorData = useAlertStore((state) => state.setErrorData);
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
const { folderId } = useParams();
const isComponent = flowData.is_component ?? false;
const setFlowToCanvas = useFlowsManagerStore(
(state) => state.setFlowToCanvas,
);
const { icon, icon_bg_color } = getTemplateStyle(flowData);
const editFlowLink = `/flow/${flowData.id}${folderId ? `/folder/${folderId}` : ""}`;
function hasPlayground(flow?: FlowType) {
if (!flow) {
return false;
}
const { inputs, outputs } = getInputsAndOutputs(flow?.data?.nodes ?? []);
return inputs.length > 0 || outputs.length > 0;
}
const handlePlaygroundClick = () => {
track("Playground Button Clicked", { flowId: flowData.id });
setLoadingPlayground(true);
if (flowData) {
if (!hasPlayground(flowData)) {
setErrorData({
title: "Error",
list: ["This flow doesn't have a playground."],
});
setLoadingPlayground(false);
return;
}
setCurrentFlow(flowData);
setOpenPlayground(true);
setLoadingPlayground(false);
} else {
setErrorData({
title: "Error",
list: ["Error getting flow data."],
});
}
};
const handleClick = async () => {
if (!isComponent) {
await setFlowToCanvas(flowData);
navigate(editFlowLink);
}
};
const handleDelete = () => {
deleteFlow({ id: [flowData.id] })
.then(() => {
setSuccessData({
title: "Selected items deleted successfully",
});
})
.catch(() => {
setErrorData({
title: "Error deleting items",
list: ["Please try again"],
});
});
};
const descriptionModal = useDescriptionModal([flowData?.id], "flow");
return (
<>
<div
key={flowData.id}
onClick={handleClick}
className={`my-2 flex h-[110px] ${
isComponent ? "cursor-default" : "cursor-pointer"
} justify-between rounded-lg border border-zinc-100 p-5 shadow-sm hover:border-border dark:border-zinc-800 dark:hover:border-muted-foreground`}
>
{/* left side */}
<div
className={`flex min-w-0 ${
isComponent ? "cursor-default" : "cursor-pointer"
} items-center gap-2`}
>
{/* Icon */}
<div
className={`item-center mr-3 flex justify-center rounded-lg border ${flowData?.icon_bg_color || icon_bg_color} p-3`}
>
<ForwardedIconComponent
name={flowData?.icon || icon}
aria-hidden="true"
className="flex h-5 w-5 items-center justify-center dark:text-black"
/>
</div>
<div className="flex min-w-0 flex-col justify-start">
<div className="line-clamp-1 flex min-w-0 items-baseline truncate max-md:flex-col">
<div className="text-md flex truncate pr-2 font-semibold max-md:w-full">
<span className="truncate">{flowData.name}</span>
</div>
<div className="item-baseline flex text-xs text-zinc-500 dark:text-zinc-400">
Edited {timeElapsed(flowData.updated_at)} ago
</div>
</div>
<div className="line-clamp-2 flex text-sm text-zinc-800 truncate-doubleline dark:text-white">
{flowData.description}
</div>
</div>
</div>
{/* right side */}
<div className="ml-5 flex items-center gap-2">
{flowData.is_component ? (
<></>
) : (
<Button
variant="outline"
disabled={loadingPlayground || !hasPlayground(flowData)}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handlePlaygroundClick();
}}
className="hidden sm:block"
>
Playground
</Button>
)}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="icon"
data-testid="home-dropdown-menu"
className="group h-10 w-10 border-none dark:hover:bg-zinc-700"
>
<ForwardedIconComponent
name="ellipsis"
aria-hidden="true"
className="h-5 w-5 dark:text-zinc-400 dark:group-hover:text-white"
/>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
className="mr-[30px] w-[185px] bg-white dark:bg-black"
sideOffset={5}
side="bottom"
>
<DropdownComponent
flowData={flowData}
setOpenDelete={setOpenDelete}
handlePlaygroundClick={() => {
handlePlaygroundClick();
}}
/>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
{openPlayground && (
<IOModal
key={flowData.id}
cleanOnClose={true}
open={openPlayground}
setOpen={setOpenPlayground}
>
<></>
</IOModal>
)}
{openDelete && (
<DeleteConfirmationModal
open={openDelete}
setOpen={setOpenDelete}
onConfirm={handleDelete}
description={descriptionModal}
>
<></>
</DeleteConfirmationModal>
)}
</>
);
};
export default ListComponent;

View file

@ -0,0 +1,54 @@
export const TEMPLATES_DATA = {
examples: [
{
name: "Basic Prompting (Hello, World)",
icon: "BotMessageSquare",
icon_bg_color: "bg-blue-500",
},
{
name: "Memory Chatbot",
icon: "MessagesSquare",
icon_bg_color: "bg-purple-500",
},
{
name: "Vector Store RAG",
icon: "Database",
icon_bg_color: "bg-green-500",
},
{
name: "Travel Planning Agents",
icon: "Plane",
icon_bg_color: "bg-yellow-500",
},
{
name: "Dynamic Agent",
icon: "Users",
icon_bg_color: "bg-red-500",
},
{
name: "Blog Writer",
icon: "FileText",
icon_bg_color: "bg-indigo-500",
},
{
name: "Sequential Tasks Agent",
icon: "ListOrdered",
icon_bg_color: "bg-pink-500",
},
{
name: "Hierarchical Tasks Agent",
icon: "GitFork",
icon_bg_color: "bg-orange-500",
},
{
name: "Simple Agent",
icon: "Users",
icon_bg_color: "bg-teal-500",
},
{
name: "Document QA",
icon: "FileText",
icon_bg_color: "bg-cyan-500",
},
],
};

View file

@ -0,0 +1,39 @@
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import useFlowsManagerStore from "@/stores/flowsManagerStore";
import { useParams } from "react-router-dom";
import CollectionCardComponent from "../../../../../../components/cardComponent";
const CollectionCard = ({ item, type, isLoading, control }) => {
const navigate = useCustomNavigate();
const isComponent = item.is_component ?? false;
const editFlowButtonTestId = `edit-flow-button-${item.id}`;
const { folderId } = useParams();
const editFlowLink = `/flow/${item.id}${folderId ? `/folder/${folderId}` : ""}`;
const setFlowToCanvas = useFlowsManagerStore(
(state) => state.setFlowToCanvas,
);
const handleClick = async () => {
if (!isComponent) {
await setFlowToCanvas(item);
navigate(editFlowLink);
}
};
return (
<CollectionCardComponent
data={{
is_component: isComponent,
...item,
}}
disabled={isLoading}
data-testid={editFlowButtonTestId}
onClick={!isComponent ? handleClick : undefined}
control={control}
/>
);
};
export default CollectionCard;

View file

@ -0,0 +1,35 @@
import { useMemo } from "react";
const useDescriptionModal = (
selectedFlowsComponentsCards: string[] | undefined,
type: string | undefined,
) => {
const getDescriptionModal = useMemo(() => {
const getTypeLabel = (type) => {
const labels = {
all: "item",
component: "component",
flow: "flow",
};
return labels[type] || "";
};
const getPluralizedLabel = (type) => {
const labels = {
all: "items",
component: "components",
flow: "flows",
};
return labels[type] || "";
};
if (selectedFlowsComponentsCards?.length === 1) {
return getTypeLabel(type);
}
return getPluralizedLabel(type);
}, [selectedFlowsComponentsCards, type]);
return getDescriptionModal;
};
export default useDescriptionModal;

Some files were not shown because too many files have changed in this diff Show more