fix: refactor loading of main page, use new Sidebar in main page, fix small UI bugs (#4451)

* Tighten space between main page sidebar buttons

* Fixed skeleton size

* Added playground button back

* Updated no components and flows state

* Update default icon to Workflow

* Fixed size of list cards

* Removed browse store

* Removed playground button

* Removed black background from empty folder state

* Update empty state color

* Fix color of empty state

* fix text not selectable

* updated border color

* added shadow only on hover

* Remove JSON from Download JSON

* Fixed colors and weight of tabs on home page

* Fixed padding on list and grid components

* Update icons that take long to load

* Fixed icon and bg color for home tiles

* Removed unused code

* removed placeholder data for skeleton to not appear on first load

* Make onSuccess refetch the queries so that the loading waits for it

* Removed unused divs on foldersidebarnav

* Refactor sidebar buttons to use new shadcn sidebar

* Created skeletons for folder, grid and list

* Added new sidebar size

* Use new sidebar button on header and implemented animation

* Changed icon to getIcon

* Added sidebar provider and fixed loading states of the main page

* Removed folder buttons on emptyPage

* Fixed foldername to appear immediatly, and fixed loading states for the folders

* Removed unused state from folders store

* Removed unused states from folders store type

* Added new icon

* fixed modals component to not show a trash icon

* Changed icons to load immediatly

* Added empty folder condition to not display header info

* Added conditions to show loading state until everything loads

* Created empty folder state

* Changed empty page to correct colors

* Added skeletons while flows of the folder are loading

* Removed shadow from text

* Fixed font chivo taking long time to load

* Fix adding new folder not redirecting

* [autofix.ci] apply automated fixes

* Fixed colors and paddings on list and grid components

* Re added tooltips to upload and create folders

* fix input for name editing

* Fix tests

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Lucas Oliveira 2024-11-08 14:33:52 -03:00 • committed by GitHub
commit a09652ca5f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 697 additions and 631 deletions

View file

@ -9,11 +9,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link <link
href="https://fonts.googleapis.com/css2?family=Chivo:ital,wght@0,100..900;1,100..900&display=swap" href="https://fonts.googleapis.com/css2?family=Chivo:ital,wght@0,100..900;1,100..900&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"
/>
<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" rel="stylesheet"
/> />
<script <script

View file

@ -5,6 +5,17 @@ import {
SelectItem, SelectItem,
SelectTrigger, SelectTrigger,
} from "@/components/ui/select-custom"; } from "@/components/ui/select-custom";
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarTrigger,
} from "@/components/ui/sidebar";
import { import {
usePatchFolders, usePatchFolders,
usePostFolders, usePostFolders,
@ -18,7 +29,7 @@ import { getObjectsFromFilelist } from "@/helpers/get-objects-from-filelist";
import useUploadFlow from "@/hooks/flows/use-upload-flow"; import useUploadFlow from "@/hooks/flows/use-upload-flow";
import { useIsFetching, useIsMutating } from "@tanstack/react-query"; import { useIsFetching, useIsMutating } from "@tanstack/react-query";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { useParams } from "react-router-dom"; import { useLocation, useParams } from "react-router-dom";
import { FolderType } from "../../../../pages/MainPage/entities"; import { FolderType } from "../../../../pages/MainPage/entities";
import useAlertStore from "../../../../stores/alertStore"; import useAlertStore from "../../../../stores/alertStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
@ -26,25 +37,28 @@ import { useFolderStore } from "../../../../stores/foldersStore";
import { handleKeyDown } from "../../../../utils/reactflowUtils"; import { handleKeyDown } from "../../../../utils/reactflowUtils";
import { cn } from "../../../../utils/utils"; import { cn } from "../../../../utils/utils";
import IconComponent from "../../../genericIconComponent"; import IconComponent from "../../../genericIconComponent";
import { Button, buttonVariants } from "../../../ui/button"; import { Button } from "../../../ui/button";
import { Input } from "../../../ui/input"; import { Input } from "../../../ui/input";
import useFileDrop from "../../hooks/use-on-file-drop"; import useFileDrop from "../../hooks/use-on-file-drop";
import { SidebarFolderSkeleton } from "../sidebarFolderSkeleton"; import { SidebarFolderSkeleton } from "../sidebarFolderSkeleton";
type SideBarFoldersButtonsComponentProps = { type SideBarFoldersButtonsComponentProps = {
pathname: string;
handleChangeFolder?: (id: string) => void; handleChangeFolder?: (id: string) => void;
handleDeleteFolder?: (item: FolderType) => void; handleDeleteFolder?: (item: FolderType) => void;
folders: FolderType[] | undefined;
loading?: boolean;
}; };
const SideBarFoldersButtonsComponent = ({ const SideBarFoldersButtonsComponent = ({
pathname,
handleChangeFolder, handleChangeFolder,
handleDeleteFolder, handleDeleteFolder,
folders = [],
loading,
}: SideBarFoldersButtonsComponentProps) => { }: SideBarFoldersButtonsComponentProps) => {
const location = useLocation();
const pathname = location.pathname;
const folders = useFolderStore((state) => state.folders);
const isFetchingFolders = !!useIsFetching({
queryKey: ["useGetFolders"],
exact: false,
});
const loading = !folders;
const refInput = useRef<HTMLInputElement>(null); const refInput = useRef<HTMLInputElement>(null);
const [foldersNames, setFoldersNames] = useState({}); const [foldersNames, setFoldersNames] = useState({});
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot); const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
@ -56,10 +70,6 @@ const SideBarFoldersButtonsComponent = ({
pathname.split("/").length < (ENABLE_CUSTOM_PARAM ? 5 : 4); pathname.split("/").length < (ENABLE_CUSTOM_PARAM ? 5 : 4);
const myCollectionId = useFolderStore((state) => state.myCollectionId); const myCollectionId = useFolderStore((state) => state.myCollectionId);
const folderIdDragging = useFolderStore((state) => state.folderIdDragging); const folderIdDragging = useFolderStore((state) => state.folderIdDragging);
const showFolderModal = useFolderStore((state) => state.showFolderModal);
const setShowFolderModal = useFolderStore(
(state) => state.setShowFolderModal,
);
const checkPathName = (itemId: string) => { const checkPathName = (itemId: string) => {
if (urlWithoutPath && itemId === myCollectionId) { if (urlWithoutPath && itemId === myCollectionId) {
@ -152,14 +162,21 @@ const SideBarFoldersButtonsComponent = ({
const { mutate: mutateUpdateFolder } = usePatchFolders(); const { mutate: mutateUpdateFolder } = usePatchFolders();
function addNewFolder() { function addNewFolder() {
mutateAddFolder({ mutateAddFolder(
data: { {
name: "New Folder", data: {
parent_id: null, name: "New Folder",
description: "", parent_id: null,
description: "",
},
}, },
}); {
track("Create New Folder"); onSuccess: (folder) => {
track("Create New Folder");
handleChangeFolder!(folder.id);
},
},
);
} }
function handleEditFolderName(e, name): void { function handleEditFolderName(e, name): void {
@ -232,11 +249,6 @@ const SideBarFoldersButtonsComponent = ({
} }
}; };
const isFetchingFolders = !!useIsFetching({
queryKey: ["useGetFolders"],
exact: false,
});
const isFetchingFolder = !!useIsFetching({ const isFetchingFolder = !!useIsFetching({
queryKey: ["useGetFolder"], queryKey: ["useGetFolder"],
exact: false, exact: false,
@ -254,27 +266,28 @@ const SideBarFoldersButtonsComponent = ({
isDeletingFolder; isDeletingFolder;
const HeaderButtons = () => ( const HeaderButtons = () => (
<div className="my-4 flex shrink-0 items-center justify-between gap-1"> <div className="flex shrink-0 items-center justify-between gap-2">
<Button <SidebarTrigger className="lg:hidden">
variant="ghost" <IconComponent name="PanelLeftClose" className="h-4 w-4" />
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" </SidebarTrigger>
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> <div className="flex-1 text-sm font-semibold">Folders</div>
<UploadFolderButton <div className="flex items-center gap-1">
onClick={handleUploadFlowsToFolder} <UploadFolderButton
disabled={isUpdatingFolder} onClick={handleUploadFlowsToFolder}
/> disabled={isUpdatingFolder}
<AddFolderButton onClick={addNewFolder} disabled={isUpdatingFolder} /> />
<AddFolderButton
onClick={addNewFolder}
disabled={isUpdatingFolder}
loading={isPending}
/>
</div>
</div> </div>
); );
const AddFolderButton = ({ onClick, disabled }) => ( const AddFolderButton = ({ onClick, disabled, loading }) => (
<ShadTooltip content="Create new folder" styleClasses="z-10"> <ShadTooltip content="Create new folder" styleClasses="z-50">
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
@ -282,6 +295,7 @@ const SideBarFoldersButtonsComponent = ({
onClick={onClick} onClick={onClick}
data-testid="add-folder-button" data-testid="add-folder-button"
disabled={disabled} disabled={disabled}
loading={loading}
> >
<IconComponent name="Plus" className="h-4 w-4" /> <IconComponent name="Plus" className="h-4 w-4" />
</Button> </Button>
@ -289,12 +303,11 @@ const SideBarFoldersButtonsComponent = ({
); );
const UploadFolderButton = ({ onClick, disabled }) => ( const UploadFolderButton = ({ onClick, disabled }) => (
/* Todo: change this back to being a folder upload */ <ShadTooltip content="Upload a flow" styleClasses="z-50">
<ShadTooltip content="Upload a flow" styleClasses="z-10">
<Button <Button
variant="ghost" 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" size="icon"
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} onClick={onClick}
data-testid="upload-folder-button" data-testid="upload-folder-button"
disabled={disabled} disabled={disabled}
@ -389,153 +402,158 @@ const SideBarFoldersButtonsComponent = ({
}; };
return ( return (
<> <Sidebar collapsible="offcanvas" data-testid="folder-sidebar">
<HeaderButtons /> <SidebarHeader className="p-4">
<HeaderButtons />
</SidebarHeader>
<SidebarContent>
<SidebarGroup className="p-4 py-2">
<SidebarGroupContent>
<SidebarMenu>
{!loading ? (
folders.map((item, index) => {
const editFolderName = editFolders?.filter(
(folder) => folder.name === item.name,
)[0];
return (
<SidebarMenuItem>
<SidebarMenuButton
size="md"
onDragOver={(e) => dragOver(e, item.id!)}
onDragEnter={(e) => dragEnter(e, item.id!)}
onDragLeave={dragLeave}
onDrop={(e) => onDrop(e, item.id!)}
key={item.id}
data-testid={`sidebar-nav-${item.name}`}
isActive={checkPathName(item.id!)}
onClick={() => handleChangeFolder!(item.id!)}
className="group/menu-button"
>
<div
onDoubleClick={(event) => {
handleDoubleClick(event, item);
}}
className="flex w-full items-center justify-between gap-2"
>
<div className="flex flex-1 items-center gap-2">
{editFolderName?.edit && !isUpdatingFolder ? (
<Input
className="h-6 flex-1 focus:border-0"
onChange={(e) => {
handleEditFolderName(e, item.name);
}}
ref={refInput}
onKeyDown={(e) => {
handleKeyDownFn(e, item);
handleKeyDown(e, e.key, "");
}}
autoFocus={true}
onBlur={(e) => {
// fixes autofocus problem where cursor isn't present
if (
e.relatedTarget?.id ===
`options-trigger-${item.name}`
) {
refInput.current?.focus();
return;
}
<div className="flex h-[70vh] flex-col gap-2 overflow-auto"> if (refInput.current?.value !== item.name) {
<> handleEditNameFolder(item);
{!loading ? ( } else {
folders.map((item, index) => { editFolderName.edit = false;
const editFolderName = editFolders?.filter( }
(folder) => folder.name === item.name, refInput.current?.blur();
)[0]; }}
return ( value={foldersNames[item.name]}
<div id={`input-folder-${item.name}`}
onDragOver={(e) => dragOver(e, item.id!)} data-testid={`input-folder`}
onDragEnter={(e) => dragEnter(e, item.id!)} />
onDragLeave={dragLeave} ) : (
onDrop={(e) => onDrop(e, item.id!)} <span className="block w-full grow truncate text-[13px] opacity-100">
key={item.id} {item.name}
data-testid={`sidebar-nav-${item.name}`} </span>
className={cn( )}
buttonVariants({ variant: "ghost" }), </div>
checkPathName(item.id!) <Select
? "bg-zinc-200 hover:bg-zinc-200 dark:bg-zinc-800" onValueChange={(value) =>
: "hover:bg-transparent hover:bg-zinc-200 dark:hover:bg-zinc-800 lg:border-transparent", handleSelectChange(value, item)
"group flex w-full shrink-0 cursor-pointer gap-2 opacity-100 lg:min-w-full", }
folderIdDragging === item.id! ? "bg-border" : "", value=""
)} >
onClick={() => handleChangeFolder!(item.id!)} <ShadTooltip
> content="Options"
<div side="right"
onDoubleClick={(event) => { styleClasses="z-50"
handleDoubleClick(event, item); >
}} <SelectTrigger
className="flex w-full items-center justify-between" className="w-fit"
> id={`options-trigger-${item.name}`}
<div className="flex items-center gap-2"> data-testid="more-options-button"
{editFolderName?.edit && !isUpdatingFolder ? ( >
<div> <IconComponent
<Input name={"MoreHorizontal"}
className="w-36" className={`w-4 stroke-[1.5] px-0 text-muted-foreground group-hover/menu-button:block group-hover/menu-button:text-foreground ${
onChange={(e) => { checkPathName(item.id!) ? "block" : "hidden"
handleEditFolderName(e, item.name); }`}
}} />
ref={refInput} </SelectTrigger>
onKeyDown={(e) => { </ShadTooltip>
handleKeyDownFn(e, item); <SelectContent
handleKeyDown(e, e.key, ""); align="end"
}} alignOffset={-16}
autoFocus={true} position="popper"
onBlur={(e) => { >
// fixes autofocus problem where cursor isn't present {item.name !== "My Projects" && (
if ( <SelectItem
e.relatedTarget?.id === id="rename-button"
`options-trigger-${item.name}` value="rename"
) { data-testid="btn-rename-folder"
refInput.current?.focus(); >
return; <FolderSelectItem
} name="Rename"
iconName="SquarePen"
if (refInput.current?.value !== item.name) { />
handleEditNameFolder(item); </SelectItem>
} else { )}
editFolderName.edit = false; <SelectItem
} value="download"
refInput.current?.blur(); data-testid="btn-download-folder"
}} >
value={foldersNames[item.name]} <FolderSelectItem
id={`input-folder-${item.name}`} name="Download Content"
data-testid={`input-folder`} iconName="Download"
/> />
</SelectItem>
{index > 0 && (
<SelectItem
value="delete"
data-testid="btn-delete-folder"
>
<FolderSelectItem
name="Delete"
iconName="Trash2"
/>
</SelectItem>
)}
</SelectContent>
</Select>
</div> </div>
) : ( </SidebarMenuButton>
<span className="block w-full grow truncate text-[13px] opacity-100"> </SidebarMenuItem>
{item.name} );
</span> })
)} ) : (
</div> <>
<Select <SidebarFolderSkeleton />
onValueChange={(value) => handleSelectChange(value, item)} <SidebarFolderSkeleton />
value="" </>
> )}
<ShadTooltip </SidebarMenu>
content="Options" </SidebarGroupContent>
side="right" </SidebarGroup>
styleClasses="z-10" </SidebarContent>
> </Sidebar>
<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}
position="popper"
>
{item.name !== "My Projects" && (
<SelectItem
id="rename-button"
value="rename"
data-testid="btn-rename-folder"
>
<FolderSelectItem
name="Rename"
iconName="square-pen"
/>
</SelectItem>
)}
<SelectItem
value="download"
data-testid="btn-download-folder"
>
<FolderSelectItem
name="Download Content"
iconName="download"
/>
</SelectItem>
{index > 0 && (
<SelectItem
value="delete"
data-testid="btn-delete-folder"
>
<FolderSelectItem name="Delete" iconName="trash" />
</SelectItem>
)}
</SelectContent>
</Select>
</div>
</div>
);
})
) : (
<>
<SidebarFolderSkeleton />
<SidebarFolderSkeleton />
</>
)}
</>
</div>
</>
); );
}; };
export default SideBarFoldersButtonsComponent; export default SideBarFoldersButtonsComponent;

View file

@ -2,8 +2,7 @@ import { Skeleton } from "@/components/ui/skeleton";
export function SidebarFolderSkeleton() { export function SidebarFolderSkeleton() {
return ( return (
<div className="flex h-10 w-full shrink-0 animate-pulse cursor-pointer items-center gap-4 rounded-md border bg-background px-4 opacity-100 lg:min-w-full"> <div className="flex h-9 w-full shrink-0 animate-pulse cursor-pointer items-center gap-4 rounded-md border bg-background px-2 opacity-100 lg:min-w-full">
<Skeleton className="h-3 w-4 rounded-full" />
<Skeleton className="h-3 w-[40%]" /> <Skeleton className="h-3 w-[40%]" />
</div> </div>
); );

View file

@ -1,10 +1,7 @@
import { useGetFoldersQuery } from "@/controllers/API/queries/folders/use-get-folders";
import { useFolderStore } from "@/stores/foldersStore"; import { useFolderStore } from "@/stores/foldersStore";
import { useIsFetching } from "@tanstack/react-query"; import { useIsFetching } from "@tanstack/react-query";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import { FolderType } from "../../pages/MainPage/entities"; import { FolderType } from "../../pages/MainPage/entities";
import { cn } from "../../utils/utils";
import HorizontalScrollFadeComponent from "../horizontalScrollFadeComponent";
import SideBarFoldersButtonsComponent from "./components/sideBarFolderButtons"; import SideBarFoldersButtonsComponent from "./components/sideBarFolderButtons";
type SidebarNavProps = { type SidebarNavProps = {
@ -29,16 +26,9 @@ export default function FolderSidebarNav({
}); });
return ( return (
<nav className={cn(className)} {...props}> <SideBarFoldersButtonsComponent
<HorizontalScrollFadeComponent> handleChangeFolder={handleChangeFolder}
<SideBarFoldersButtonsComponent handleDeleteFolder={handleDeleteFolder}
loading={isPending || !folders} />
pathname={pathname}
handleChangeFolder={handleChangeFolder}
handleDeleteFolder={handleDeleteFolder}
folders={folders}
/>
</HorizontalScrollFadeComponent>
</nav>
); );
} }

View file

@ -518,6 +518,7 @@ const sidebarMenuButtonVariants = cva(
}, },
size: { size: {
default: "h-8 text-sm", default: "h-8 text-sm",
md: "h-9 text-sm",
sm: "h-7 text-xs", sm: "h-7 text-xs",
lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0", lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0",
}, },

View file

@ -13,6 +13,7 @@ interface IPostAddFlow {
is_component: boolean; is_component: boolean;
folder_id: string; folder_id: string;
endpoint_name: string | undefined; endpoint_name: string | undefined;
icon: string | undefined;
} }
export const usePostAddFlow: useMutationFunctionType< export const usePostAddFlow: useMutationFunctionType<
@ -29,6 +30,7 @@ export const usePostAddFlow: useMutationFunctionType<
description: payload.description, description: payload.description,
is_component: payload.is_component, is_component: payload.is_component,
folder_id: payload.folder_id || null, folder_id: payload.folder_id || null,
icon: payload.icon || null,
endpoint_name: payload.endpoint_name || null, endpoint_name: payload.endpoint_name || null,
}); });

View file

@ -72,7 +72,6 @@ export const useGetFolderQuery: useQueryFunctionType<
() => getFolderFn(params), () => getFolderFn(params),
{ {
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
placeholderData: true,
...options, ...options,
}, },
); );

View file

@ -28,8 +28,8 @@ export const usePostFolders: useMutationFunctionType<
const mutation = mutate(["usePostFolders"], addFoldersFn, { const mutation = mutate(["usePostFolders"], addFoldersFn, {
...options, ...options,
onSettled: () => { onSuccess: () => {
queryClient.refetchQueries({ queryKey: ["useGetFolders"] }); return queryClient.refetchQueries({ queryKey: ["useGetFolders"] });
}, },
}); });

View file

@ -35,23 +35,10 @@ export default function TemplateCardComponent({
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
onClick={onClick} onClick={onClick}
> >
<div <div className="relative h-20 w-20 shrink-0 overflow-hidden rounded-md bg-muted p-4 outline-none ring-ring group-hover:bg-border group-focus-visible:bg-border">
className="relative h-20 w-20 shrink-0 overflow-hidden rounded-md p-4 outline-none ring-ring"
style={{
backgroundImage: bgGradient,
transform: "scale(1)",
transition: "transform 0.3s ease-in-out",
}}
>
<div
className="absolute inset-0 transition-transform duration-300 group-hover:scale-125 group-focus-visible:scale-125"
style={{
backgroundImage: bgGradient,
}}
/>
<IconComponent <IconComponent
name={example.icon || "FileText"} name={example.icon || "FileText"}
className="absolute left-1/2 top-1/2 h-10 w-10 -translate-x-1/2 -translate-y-1/2 text-white duration-300 group-hover:scale-105 group-focus-visible:scale-105" className="absolute left-1/2 top-1/2 h-10 w-10 -translate-x-1/2 -translate-y-1/2 text-muted-foreground duration-300 group-hover:scale-105 group-hover:text-foreground group-focus-visible:scale-105 group-focus-visible:text-foreground"
/> />
</div> </div>
<div className="flex flex-1 flex-col justify-between"> <div className="flex flex-1 flex-col justify-between">

View file

@ -78,11 +78,11 @@ const DropdownComponent = ({
data-testid="btn-download-json" data-testid="btn-download-json"
> >
<ForwardedIconComponent <ForwardedIconComponent
name="download" name="Download"
aria-hidden="true" aria-hidden="true"
className="mr-2 h-4 w-4" className="mr-2 h-4 w-4"
/> />
Download JSON Download
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
onClick={(e) => { onClick={(e) => {
@ -93,7 +93,7 @@ const DropdownComponent = ({
data-testid="btn-duplicate-flow" data-testid="btn-duplicate-flow"
> >
<ForwardedIconComponent <ForwardedIconComponent
name="copy-plus" name="CopyPlus"
aria-hidden="true" aria-hidden="true"
className="mr-2 h-4 w-4" className="mr-2 h-4 w-4"
/> />
@ -104,10 +104,10 @@ const DropdownComponent = ({
e.stopPropagation(); e.stopPropagation();
setOpenDelete(true); setOpenDelete(true);
}} }}
className="cursor-pointer text-red-500 focus:text-red-500 dark:text-red-500 dark:focus:text-red-500" className="cursor-pointer text-destructive"
> >
<ForwardedIconComponent <ForwardedIconComponent
name="trash" name="Trash2"
aria-hidden="true" aria-hidden="true"
className="mr-2 h-4 w-4" className="mr-2 h-4 w-4"
/> />

View file

@ -8,10 +8,8 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate"; import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import { track } from "@/customization/utils/analytics";
import useDeleteFlow from "@/hooks/flows/use-delete-flow"; import useDeleteFlow from "@/hooks/flows/use-delete-flow";
import DeleteConfirmationModal from "@/modals/deleteConfirmationModal"; import DeleteConfirmationModal from "@/modals/deleteConfirmationModal";
import IOModal from "@/modals/IOModal";
import useAlertStore from "@/stores/alertStore"; import useAlertStore from "@/stores/alertStore";
import useFlowsManagerStore from "@/stores/flowsManagerStore"; import useFlowsManagerStore from "@/stores/flowsManagerStore";
import { FlowType } from "@/types/flow"; import { FlowType } from "@/types/flow";
@ -19,27 +17,27 @@ import { getInputsAndOutputs } from "@/utils/storeUtils";
import { useState } from "react"; import { useState } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import useDescriptionModal from "../../oldComponents/componentsComponent/hooks/use-description-modal"; import useDescriptionModal from "../../oldComponents/componentsComponent/hooks/use-description-modal";
import { getTemplateStyle } from "../../utils/get-template-style"; import { useGetTemplateStyle } from "../../utils/get-template-style";
import { timeElapsed } from "../../utils/time-elapse"; import { timeElapsed } from "../../utils/time-elapse";
import DropdownComponent from "../dropdown"; import DropdownComponent from "../dropdown";
const GridComponent = ({ flowData }: { flowData: FlowType }) => { const GridComponent = ({ flowData }: { flowData: FlowType }) => {
const navigate = useCustomNavigate(); const navigate = useCustomNavigate();
// const [openPlayground, setOpenPlayground] = useState(false); /* const [openPlayground, setOpenPlayground] = useState(false);
const [loadingPlayground, setLoadingPlayground] = useState(false); const [loadingPlayground, setLoadingPlayground] = useState(false); */
const [openDelete, setOpenDelete] = useState(false); const [openDelete, setOpenDelete] = useState(false);
const setSuccessData = useAlertStore((state) => state.setSuccessData); const setSuccessData = useAlertStore((state) => state.setSuccessData);
const { deleteFlow } = useDeleteFlow(); const { deleteFlow } = useDeleteFlow();
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow); /* const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow); */
const { folderId } = useParams(); const { folderId } = useParams();
const isComponent = flowData.is_component ?? false; const isComponent = flowData.is_component ?? false;
const setFlowToCanvas = useFlowsManagerStore( const setFlowToCanvas = useFlowsManagerStore(
(state) => state.setFlowToCanvas, (state) => state.setFlowToCanvas,
); );
const { icon, icon_bg_color } = getTemplateStyle(flowData); const { getIcon } = useGetTemplateStyle(flowData);
const editFlowLink = `/flow/${flowData.id}${folderId ? `/folder/${folderId}` : ""}`; const editFlowLink = `/flow/${flowData.id}${folderId ? `/folder/${folderId}` : ""}`;
@ -51,7 +49,7 @@ const GridComponent = ({ flowData }: { flowData: FlowType }) => {
return inputs.length > 0 || outputs.length > 0; return inputs.length > 0 || outputs.length > 0;
} }
const handlePlaygroundClick = () => { /* const handlePlaygroundClick = () => {
track("Playground Button Clicked", { flowId: flowData.id }); track("Playground Button Clicked", { flowId: flowData.id });
setLoadingPlayground(true); setLoadingPlayground(true);
@ -73,7 +71,7 @@ const GridComponent = ({ flowData }: { flowData: FlowType }) => {
list: ["Error getting flow data."], list: ["Error getting flow data."],
}); });
} }
}; }; */
const handleClick = async () => { const handleClick = async () => {
if (!isComponent) { if (!isComponent) {
@ -108,18 +106,16 @@ const GridComponent = ({ flowData }: { flowData: FlowType }) => {
draggable draggable
onDragStart={onDragStart} onDragStart={onDragStart}
onClick={handleClick} onClick={handleClick}
className={`my-1 flex flex-col rounded-lg border border-zinc-100 bg-background p-5 shadow-sm hover:border-border dark:border-zinc-800 dark:hover:border-muted-foreground ${ className={`my-1 flex flex-col rounded-lg border border-border bg-background p-4 hover:border-placeholder-foreground hover:shadow-sm ${
isComponent ? "cursor-default" : "cursor-pointer" isComponent ? "cursor-default" : "cursor-pointer"
}`} }`}
> >
<div className="flex w-full items-center gap-2"> <div className="flex w-full items-center gap-4">
<div <div className={`flex rounded-lg bg-muted p-3`}>
className={`mr-3 flex rounded-lg border ${flowData?.icon_bg_color || icon_bg_color} p-3`}
>
<ForwardedIconComponent <ForwardedIconComponent
name={flowData?.icon || icon} name={getIcon()}
aria-hidden="true" aria-hidden="true"
className="h-5 w-5 dark:text-black" className="h-5 w-5 text-foreground"
/> />
</div> </div>
<div className="flex w-full min-w-0 items-center justify-between"> <div className="flex w-full min-w-0 items-center justify-between">
@ -127,27 +123,27 @@ const GridComponent = ({ flowData }: { flowData: FlowType }) => {
<div className="text-md truncate font-semibold"> <div className="text-md truncate font-semibold">
{flowData.name} {flowData.name}
</div> </div>
<div className="truncate text-xs text-zinc-500 dark:text-zinc-400"> <div className="truncate text-xs text-muted-foreground">
Edited {timeElapsed(flowData.updated_at)} ago Edited {timeElapsed(flowData.updated_at)} ago
</div> </div>
</div> </div>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button <Button
variant="outline" variant="ghost"
data-testid="home-dropdown-menu" data-testid="home-dropdown-menu"
size="icon" size="iconMd"
className="group ml-2 h-10 w-10 border-none dark:hover:bg-zinc-700" className="group"
> >
<ForwardedIconComponent <ForwardedIconComponent
name="ellipsis" name="Ellipsis"
aria-hidden="true" aria-hidden="true"
className="h-5 w-5 dark:text-zinc-400 dark:group-hover:text-white" className="h-5 w-5 text-muted-foreground group-hover:text-foreground"
/> />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent <DropdownMenuContent
className="mr-[30px] w-[185px] bg-white dark:bg-black" className="w-[185px]"
sideOffset={5} sideOffset={5}
side="bottom" side="bottom"
> >
@ -160,7 +156,7 @@ const GridComponent = ({ flowData }: { flowData: FlowType }) => {
</div> </div>
</div> </div>
<div className="line-clamp-2 h-full pt-5 text-sm text-zinc-800 dark:text-white"> <div className="line-clamp-2 h-full pt-5 text-sm text-primary">
{flowData.description} {flowData.description}
</div> </div>

View file

@ -0,0 +1,34 @@
import { Card } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
const GridSkeleton = () => {
return (
<Card className="my-1 flex flex-col rounded-lg border border-border bg-background p-4">
<div className="flex w-full items-center gap-4">
{/* Icon skeleton */}
<div className="flex rounded-lg">
<Skeleton className="h-[44px] w-[44px] rounded-lg" />
</div>
<div className="flex w-full min-w-0 items-center justify-between">
<div className="flex min-w-0 flex-col gap-2">
{/* Title skeleton */}
<Skeleton className="h-5 w-[120px]" />
{/* Time skeleton */}
<Skeleton className="h-4 w-[150px]" />
</div>
{/* Dropdown button skeleton */}
<Skeleton className="ml-2 h-10 w-10 rounded-md" />
</div>
</div>
{/* Description skeleton */}
<div className="pt-5">
<Skeleton className="h-4 w-full" />
<Skeleton className="mt-2 h-4 w-3/4" />
</div>
</Card>
);
};
export default GridSkeleton;

View file

@ -2,8 +2,8 @@ import ForwardedIconComponent from "@/components/genericIconComponent";
import ShadTooltip from "@/components/shadTooltipComponent"; import ShadTooltip from "@/components/shadTooltipComponent";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate"; import { SidebarTrigger, useSidebar } from "@/components/ui/sidebar";
import { useFolderStore } from "@/stores/foldersStore"; import { cn } from "@/utils/utils";
import { debounce } from "lodash"; import { debounce } from "lodash";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
@ -15,6 +15,7 @@ interface HeaderComponentProps {
setNewProjectModal: (newProjectModal: boolean) => void; setNewProjectModal: (newProjectModal: boolean) => void;
folderName?: string; folderName?: string;
setSearch: (search: string) => void; setSearch: (search: string) => void;
isEmptyFolder: boolean;
} }
const HeaderComponent = ({ const HeaderComponent = ({
@ -25,10 +26,10 @@ const HeaderComponent = ({
setView, setView,
setNewProjectModal, setNewProjectModal,
setSearch, setSearch,
isEmptyFolder,
}: HeaderComponentProps) => { }: HeaderComponentProps) => {
const navigate = useCustomNavigate();
const [debouncedSearch, setDebouncedSearch] = useState(""); const [debouncedSearch, setDebouncedSearch] = useState("");
const { showFolderModal, setShowFolderModal } = useFolderStore(); const { open } = useSidebar();
// Debounce the setSearch function from the parent // Debounce the setSearch function from the parent
const debouncedSetSearch = useCallback( const debouncedSetSearch = useCallback(
@ -56,75 +57,83 @@ const HeaderComponent = ({
className="flex items-center pb-8 text-xl font-semibold" className="flex items-center pb-8 text-xl font-semibold"
data-testid="mainpage_title" data-testid="mainpage_title"
> >
<Button <div className={cn("w-10 transition-all lg:hidden", open && "md:w-0")}>
variant="ghost" <div
className="mr-2 lg:hidden" className={cn(
size="icon" "relative left-0 opacity-100 transition-all",
onClick={() => setShowFolderModal(!showFolderModal)} open ? "md:opacity-0" : "",
> )}
<ForwardedIconComponent >
name={showFolderModal ? "panel-right-open" : "panel-right-close"} <SidebarTrigger>
aria-hidden="true" <ForwardedIconComponent
className="h-5 w-5 text-zinc-500 dark:text-zinc-400" name="PanelLeftOpen"
/> aria-hidden="true"
</Button> className="text-zinc-500 dark:text-zinc-400"
/>
</SidebarTrigger>
</div>
</div>
{folderName} {folderName}
</div> </div>
<div className="flex flex-row-reverse pb-8"> {!isEmptyFolder && (
<div className="w-full border-b dark:border-border" /> <>
{["components", "flows"].map((type) => ( <div className="flex flex-row-reverse pb-8">
<Button <div className="w-full border-b dark:border-border" />
key={type} {["components", "flows"].map((type) => (
unstyled
id={`${type}-btn`}
data-testid={`${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 <Button
key={viewType} key={type}
unstyled unstyled
size="icon" id={`${type}-btn`}
className={`group mx-[2px] my-[2px] rounded-lg p-2 ${ data-testid={`${type}-btn`}
view === viewType onClick={() => setFlowType(type as "flows" | "components")}
? "bg-white text-black shadow-md dark:bg-black dark:text-white" className={`border-b ${
: "bg-zinc-100 text-zinc-500 dark:bg-zinc-800 dark:hover:bg-zinc-800" flowType === type
}`} ? "border-b-2 border-foreground text-foreground"
onClick={() => setView(viewType as "list" | "grid")} : "border-border text-muted-foreground hover:text-foreground"
} px-3 pb-2 text-sm`}
> >
<ForwardedIconComponent <div className={flowType === type ? "-mb-px" : ""}>
name={viewType === "list" ? "menu" : "layout-grid"} {type.charAt(0).toUpperCase() + type.slice(1)}
aria-hidden="true" </div>
className="h-4 w-4 group-hover:text-black dark:group-hover:text-white"
/>
</Button> </Button>
))} ))}
</div> </div>
</div> {/* Search and filters */}
<div className="flex gap-2"> <div className="flex justify-between">
<ShadTooltip content="Store" side="bottom"> <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" : "LayoutGrid"}
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")}> <Button variant="outline" onClick={() => navigate("/store")}>
<ForwardedIconComponent <ForwardedIconComponent
name="store" name="store"
@ -135,25 +144,27 @@ const HeaderComponent = ({
Browse Store Browse Store
</span> </span>
</Button> </Button>
</ShadTooltip> </ShadTooltip> */}
<ShadTooltip content="New Flow" side="bottom"> <ShadTooltip content="New Flow" side="bottom">
<Button <Button
variant="default" variant="default"
onClick={() => setNewProjectModal(true)} onClick={() => setNewProjectModal(true)}
id="new-project-btn" id="new-project-btn"
> >
<ForwardedIconComponent <ForwardedIconComponent
name="plus" name="Plus"
aria-hidden="true" aria-hidden="true"
className="h-4 w-4" className="h-4 w-4"
/> />
<span className="hidden whitespace-nowrap font-semibold md:inline"> <span className="hidden whitespace-nowrap font-semibold md:inline">
New Flow New Flow
</span> </span>
</Button> </Button>
</ShadTooltip> </ShadTooltip>
</div> </div>
</div> </div>
</>
)}
</> </>
); );
}; };

View file

@ -5,74 +5,70 @@ import { Card } from "@/components/ui/card";
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate"; import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import { track } from "@/customization/utils/analytics";
import useDeleteFlow from "@/hooks/flows/use-delete-flow"; import useDeleteFlow from "@/hooks/flows/use-delete-flow";
import DeleteConfirmationModal from "@/modals/deleteConfirmationModal"; import DeleteConfirmationModal from "@/modals/deleteConfirmationModal";
import IOModal from "@/modals/IOModal";
import useAlertStore from "@/stores/alertStore"; import useAlertStore from "@/stores/alertStore";
import useFlowsManagerStore from "@/stores/flowsManagerStore"; import useFlowsManagerStore from "@/stores/flowsManagerStore";
import { FlowType } from "@/types/flow"; import { FlowType } from "@/types/flow";
import { getInputsAndOutputs } from "@/utils/storeUtils";
import { useState } from "react"; import { useState } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import useDescriptionModal from "../../oldComponents/componentsComponent/hooks/use-description-modal"; import useDescriptionModal from "../../oldComponents/componentsComponent/hooks/use-description-modal";
import { getTemplateStyle } from "../../utils/get-template-style"; import { useGetTemplateStyle } from "../../utils/get-template-style";
import { timeElapsed } from "../../utils/time-elapse"; import { timeElapsed } from "../../utils/time-elapse";
import DropdownComponent from "../dropdown"; import DropdownComponent from "../dropdown";
const ListComponent = ({ flowData }: { flowData: FlowType }) => { const ListComponent = ({ flowData }: { flowData: FlowType }) => {
const navigate = useCustomNavigate(); const navigate = useCustomNavigate();
// const [openPlayground, setOpenPlayground] = useState(false); /* const [openPlayground, setOpenPlayground] = useState(false);
// const [loadingPlayground, setLoadingPlayground] = useState(false); const [loadingPlayground, setLoadingPlayground] = useState(false); */
const [openDelete, setOpenDelete] = useState(false); const [openDelete, setOpenDelete] = useState(false);
const setSuccessData = useAlertStore((state) => state.setSuccessData); const setSuccessData = useAlertStore((state) => state.setSuccessData);
const { deleteFlow } = useDeleteFlow(); const { deleteFlow } = useDeleteFlow();
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow); /* const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow); */
const { folderId } = useParams(); const { folderId } = useParams();
const isComponent = flowData.is_component ?? false; const isComponent = flowData.is_component ?? false;
const setFlowToCanvas = useFlowsManagerStore( const setFlowToCanvas = useFlowsManagerStore(
(state) => state.setFlowToCanvas, (state) => state.setFlowToCanvas,
); );
const { icon, icon_bg_color } = getTemplateStyle(flowData); const { getIcon } = useGetTemplateStyle(flowData);
const editFlowLink = `/flow/${flowData.id}${folderId ? `/folder/${folderId}` : ""}`; const editFlowLink = `/flow/${flowData.id}${folderId ? `/folder/${folderId}` : ""}`;
function hasPlayground(flow?: FlowType) { /* function hasPlayground(flow?: FlowType) {
if (!flow) { if (!flow) {
return false; return false;
} }
const { inputs, outputs } = getInputsAndOutputs(flow?.data?.nodes ?? []); const { inputs, outputs } = getInputsAndOutputs(flow?.data?.nodes ?? []);
return inputs.length > 0 || outputs.length > 0; return inputs.length > 0 || outputs.length > 0;
} } */
// const handlePlaygroundClick = () => { /* const handlePlaygroundClick = () => {
// track("Playground Button Clicked", { flowId: flowData.id }); track("Playground Button Clicked", { flowId: flowData.id });
// setLoadingPlayground(true); setLoadingPlayground(true);
// if (flowData) { if (flowData) {
// if (!hasPlayground(flowData)) { if (!hasPlayground(flowData)) {
// setErrorData({ setErrorData({
// title: "Error", title: "Error",
// list: ["This flow doesn't have a playground."], list: ["This flow doesn't have a playground."],
// }); });
// setLoadingPlayground(false); setLoadingPlayground(false);
// return; return;
// } }
// setCurrentFlow(flowData); setCurrentFlow(flowData);
// setOpenPlayground(true); setOpenPlayground(true);
// setLoadingPlayground(false); setLoadingPlayground(false);
// } else { } else {
// setErrorData({ setErrorData({
// title: "Error", title: "Error",
// list: ["Error getting flow data."], list: ["Error getting flow data."],
// }); });
// } }
// }; }; */
const handleClick = async () => { const handleClick = async () => {
if (!isComponent) { if (!isComponent) {
@ -107,24 +103,24 @@ const ListComponent = ({ flowData }: { flowData: FlowType }) => {
draggable draggable
onDragStart={onDragStart} onDragStart={onDragStart}
onClick={handleClick} onClick={handleClick}
className={`my-2 flex h-[110px] flex-row bg-background ${ className={`my-2 flex flex-row bg-background ${
isComponent ? "cursor-default" : "cursor-pointer" 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`} } group justify-between rounded-lg border border-border p-4 hover:border-placeholder-foreground hover:shadow-sm`}
> >
{/* left side */} {/* left side */}
<div <div
className={`flex min-w-0 ${ className={`flex min-w-0 ${
isComponent ? "cursor-default" : "cursor-pointer" isComponent ? "cursor-default" : "cursor-pointer"
} items-center gap-2`} } items-center gap-4`}
> >
{/* Icon */} {/* Icon */}
<div <div
className={`item-center mr-3 flex justify-center rounded-lg border ${flowData?.icon_bg_color || icon_bg_color} p-3`} className={`item-center flex justify-center rounded-lg bg-muted p-3`}
> >
<ForwardedIconComponent <ForwardedIconComponent
name={flowData?.icon || icon} name={flowData?.icon || getIcon()}
aria-hidden="true" aria-hidden="true"
className="flex h-5 w-5 items-center justify-center dark:text-black" className="flex h-5 w-5 items-center justify-center text-foreground"
/> />
</div> </div>
@ -133,11 +129,11 @@ const ListComponent = ({ flowData }: { flowData: FlowType }) => {
<div className="text-md flex truncate pr-2 font-semibold max-md:w-full"> <div className="text-md flex truncate pr-2 font-semibold max-md:w-full">
<span className="truncate">{flowData.name}</span> <span className="truncate">{flowData.name}</span>
</div> </div>
<div className="item-baseline flex text-xs text-zinc-500 dark:text-zinc-400"> <div className="item-baseline flex text-xs text-muted-foreground">
Edited {timeElapsed(flowData.updated_at)} ago Edited {timeElapsed(flowData.updated_at)} ago
</div> </div>
</div> </div>
<div className="line-clamp-2 flex text-sm text-zinc-800 truncate-doubleline dark:text-white"> <div className="overflow-hidden truncate text-sm text-primary">
{flowData.description} {flowData.description}
</div> </div>
</div> </div>
@ -164,20 +160,20 @@ const ListComponent = ({ flowData }: { flowData: FlowType }) => {
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button <Button
variant="outline" variant="ghost"
size="icon" size="iconMd"
data-testid="home-dropdown-menu" data-testid="home-dropdown-menu"
className="group h-10 w-10 border-none dark:hover:bg-zinc-700" className="group"
> >
<ForwardedIconComponent <ForwardedIconComponent
name="ellipsis" name="Ellipsis"
aria-hidden="true" aria-hidden="true"
className="h-5 w-5 dark:text-zinc-400 dark:group-hover:text-white" className="h-5 w-5 text-muted-foreground group-hover:text-foreground"
/> />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent <DropdownMenuContent
className="mr-[30px] w-[185px] bg-white dark:bg-black" className="w-[185px]"
sideOffset={5} sideOffset={5}
side="bottom" side="bottom"
> >

View file

@ -0,0 +1,33 @@
import { Card } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
const ListSkeleton = () => {
return (
<Card className="my-2 flex flex-row justify-between rounded-lg border border-border bg-background p-4">
{/* left side */}
<div className="flex min-w-0 items-center gap-4">
{/* Icon skeleton */}
<div className="flex h-[52px] w-[52px] items-center justify-center rounded-lg">
<Skeleton className="h-full w-full rounded-lg" />
</div>
<div className="flex min-w-0 flex-col justify-start gap-2">
{/* Title and time skeleton */}
<div className="flex min-w-0 items-baseline max-md:flex-col">
<Skeleton className="h-5 w-[150px]" />
<Skeleton className="ml-2 h-4 w-[180px]" />
</div>
{/* Description skeleton */}
<Skeleton className="h-4 w-[250px]" />
</div>
</div>
{/* right side */}
<div className="ml-5 flex items-center gap-2">
<Skeleton className="h-10 w-10 rounded-md" />
</div>
</Card>
);
};
export default ListSkeleton;

View file

@ -0,0 +1,41 @@
// Modals.tsx
import TemplatesModal from "@/modals/templatesModal";
import DeleteConfirmationModal from "../../../../modals/deleteConfirmationModal";
interface ModalsProps {
openModal: boolean;
setOpenModal: (value: boolean) => void;
openDeleteFolderModal: boolean;
setOpenDeleteFolderModal: (value: boolean) => void;
handleDeleteFolder: () => void;
}
const ModalsComponent = ({
openModal = false,
setOpenModal = () => {},
openDeleteFolderModal = false,
setOpenDeleteFolderModal = () => {},
handleDeleteFolder = () => {},
}: ModalsProps) => (
<>
{openModal && <TemplatesModal open={openModal} setOpen={setOpenModal} />}
{openDeleteFolderModal && (
<DeleteConfirmationModal
open={openDeleteFolderModal}
setOpen={setOpenDeleteFolderModal}
onConfirm={() => {
handleDeleteFolder();
setOpenDeleteFolderModal(false);
}}
description="folder"
note={
"Deleting the selected folder will remove all associated flows and components."
}
>
<></>
</DeleteConfirmationModal>
)}
</>
);
export default ModalsComponent;

View file

@ -1,9 +1,6 @@
// Modals.tsx // Modals.tsx
import TemplatesModal from "@/modals/templatesModal"; import TemplatesModal from "@/modals/templatesModal";
import IconComponent from "../../../../components/genericIconComponent";
import { Button } from "../../../../components/ui/button";
import DeleteConfirmationModal from "../../../../modals/deleteConfirmationModal"; import DeleteConfirmationModal from "../../../../modals/deleteConfirmationModal";
import { cn } from "../../../../utils/utils";
interface ModalsProps { interface ModalsProps {
openModal: boolean; openModal: boolean;
@ -35,13 +32,7 @@ const ModalsComponent = ({
"Deleting the selected folder will remove all associated flows and components." "Deleting the selected folder will remove all associated flows and components."
} }
> >
<Button variant="ghost" size="icon" className={"whitespace-nowrap"}> <></>
<IconComponent
data-testid={`delete-folder`}
name="Trash2"
className={cn("h-5 w-5")}
/>
</Button>
</DeleteConfirmationModal> </DeleteConfirmationModal>
)} )}
</> </>

View file

@ -0,0 +1,45 @@
import ForwardedIconComponent from "@/components/genericIconComponent";
import { Button } from "@/components/ui/button";
import { useFolderStore } from "@/stores/foldersStore";
type EmptyFolderProps = {
setOpenModal: (open: boolean) => void;
};
export const EmptyFolder = ({ setOpenModal }: EmptyFolderProps) => {
const folders = useFolderStore((state) => state.folders);
return (
<div className="m-0 h-full w-full bg-secondary p-0">
<div className="text-container">
<div className="relative z-20 flex w-full flex-col items-center justify-center gap-2">
<h3
className="pt-5 font-chivo text-2xl font-semibold"
data-testid="mainpage_title"
>
{folders?.length > 1 ? "Empty folder" : "Start building"}
</h3>
<p className="pb-5 text-sm text-secondary-foreground">
Begin with a template, or start from scratch.
</p>
<Button
variant="default"
onClick={() => setOpenModal(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>
</div>
</div>
</div>
);
};
export default EmptyFolder;

File diff suppressed because one or more lines are too long

View file

@ -1,16 +1,18 @@
import CardsWrapComponent from "@/components/cardsWrapComponent"; import CardsWrapComponent from "@/components/cardsWrapComponent";
import ForwardedIconComponent from "@/components/genericIconComponent";
import PaginatorComponent from "@/components/paginatorComponent"; import PaginatorComponent from "@/components/paginatorComponent";
import { useGetFolderQuery } from "@/controllers/API/queries/folders/use-get-folder"; import { useGetFolderQuery } from "@/controllers/API/queries/folders/use-get-folder";
import { ENABLE_DATASTAX_LANGFLOW } from "@/customization/feature-flags"; import useFlowsManagerStore from "@/stores/flowsManagerStore";
import { useFolderStore } from "@/stores/foldersStore"; import { useFolderStore } from "@/stores/foldersStore";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import GridComponent from "../../components/grid"; import GridComponent from "../../components/grid";
import GridSkeleton from "../../components/gridSkeleton";
import HeaderComponent from "../../components/header"; import HeaderComponent from "../../components/header";
import ListComponent from "../../components/list"; import ListComponent from "../../components/list";
import ListSkeleton from "../../components/listSkeleton";
import useFileDrop from "../../hooks/use-on-file-drop"; import useFileDrop from "../../hooks/use-on-file-drop";
import ModalsComponent from "../../oldComponents/modalsComponent"; import ModalsComponent from "../../oldComponents/modalsComponent";
import EmptyFolder from "../emptyFolder";
const HomePage = ({ type }) => { const HomePage = ({ type }) => {
const [view, setView] = useState<"grid" | "list">(() => { const [view, setView] = useState<"grid" | "list">(() => {
@ -25,9 +27,14 @@ const HomePage = ({ type }) => {
const handleFileDrop = useFileDrop("flows"); const handleFileDrop = useFileDrop("flows");
const [flowType, setFlowType] = useState<"flows" | "components">(type); const [flowType, setFlowType] = useState<"flows" | "components">(type);
const myCollectionId = useFolderStore((state) => state.myCollectionId); const myCollectionId = useFolderStore((state) => state.myCollectionId);
const [folderName, setFolderName] = useState(""); const folders = useFolderStore((state) => state.folders);
const folderName =
folders.find((folder) => folder.id === folderId)?.name ??
folders[0]?.name ??
"";
const flows = useFlowsManagerStore((state) => state.flows);
const { data: folderData, isFetching } = useGetFolderQuery({ const { data: folderData, isLoading } = useGetFolderQuery({
id: folderId ?? myCollectionId!, id: folderId ?? myCollectionId!,
page: pageIndex, page: pageIndex,
size: pageSize, size: pageSize,
@ -50,12 +57,6 @@ const HomePage = ({ type }) => {
}, },
}; };
useEffect(() => {
if (folderData && folderData?.folder?.name) {
setFolderName(folderData.folder.name);
}
}, [folderData, folderData?.folder?.name]);
useEffect(() => { useEffect(() => {
localStorage.setItem("view", view); localStorage.setItem("view", view);
}, [view]); }, [view]);
@ -70,6 +71,10 @@ const HomePage = ({ type }) => {
setPageIndex(1); setPageIndex(1);
}, []); }, []);
const isEmptyFolder =
flows?.find((flow) => flow.folder_id === (folderId ?? myCollectionId)) ===
undefined;
return ( return (
<CardsWrapComponent <CardsWrapComponent
onFileDrop={handleFileDrop} onFileDrop={handleFileDrop}
@ -110,11 +115,25 @@ const HomePage = ({ type }) => {
setView={setView} setView={setView}
setNewProjectModal={setNewProjectModal} setNewProjectModal={setNewProjectModal}
setSearch={onSearch} setSearch={onSearch}
isEmptyFolder={isEmptyFolder}
/> />
{isEmptyFolder ? (
{flowType === "flows" ? ( <EmptyFolder setOpenModal={setNewProjectModal} />
) : (
<div className="mt-6"> <div className="mt-6">
{data && data.pagination.total > 0 ? ( {isLoading ? (
view === "grid" ? (
<div className="mt-1 grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-3">
<GridSkeleton />
<GridSkeleton />
</div>
) : (
<div className="flex flex-col">
<ListSkeleton />
<ListSkeleton />
</div>
)
) : data && data.pagination.total > 0 ? (
view === "grid" ? ( view === "grid" ? (
<div className="mt-1 grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-3"> <div className="mt-1 grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-3">
{data.flows.map((flow) => ( {data.flows.map((flow) => (
@ -128,39 +147,19 @@ const HomePage = ({ type }) => {
))} ))}
</div> </div>
) )
) : ( ) : flowType === "flows" ? (
<div className="pt-2 text-center"> <div className="pt-2 text-center text-sm text-secondary-foreground">
No saved or custom components. Learn more about{" "} No flows in this folder.{" "}
<a <a
href="https://docs.langflow.org/components-custom-components" onClick={() => setNewProjectModal(true)}
target="_blank" className="cursor-pointer underline"
rel="noreferrer"
className="underline"
> >
creating custom components Create a new flow
</a> </a>
, or browse the store. , or browse the store.
</div> </div>
)}
</div>
) : (
<div className="mt-6">
{data && data.pagination.total > 0 ? (
view === "grid" ? (
<div className="mt-1 grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-3">
{data.flows.map((flow) => (
<GridComponent key={flow.id} flowData={flow} />
))}
</div>
) : (
<div className="flex flex-col">
{data.flows.map((flow) => (
<ListComponent key={flow.id} flowData={flow} />
))}
</div>
)
) : ( ) : (
<div className="pt-2 text-center"> <div className="pt-2 text-center text-sm text-secondary-foreground">
No saved or custom components. Learn more about{" "} No saved or custom components. Learn more about{" "}
<a <a
href="https://docs.langflow.org/components-custom-components" href="https://docs.langflow.org/components-custom-components"
@ -177,7 +176,7 @@ const HomePage = ({ type }) => {
)} )}
</div> </div>
{!isFetching && data.pagination.total >= 10 && ( {!isLoading && !isEmptyFolder && data.pagination.total >= 10 && (
<div className="relative flex justify-end px-3 py-6"> <div className="relative flex justify-end px-3 py-6">
<PaginatorComponent <PaginatorComponent
storeComponent={true} storeComponent={true}
@ -191,6 +190,7 @@ const HomePage = ({ type }) => {
</div> </div>
)} )}
</div> </div>
<ModalsComponent <ModalsComponent
openModal={newProjectModal} openModal={newProjectModal}
setOpenModal={setNewProjectModal} setOpenModal={setNewProjectModal}

View file

@ -1,15 +1,15 @@
import CardsWrapComponent from "@/components/cardsWrapComponent"; import CardsWrapComponent from "@/components/cardsWrapComponent";
import FolderSidebarNav from "@/components/folderSidebarComponent"; import SideBarFoldersButtonsComponent from "@/components/folderSidebarComponent/components/sideBarFolderButtons";
import LoadingComponent from "@/components/loadingComponent";
import { SidebarProvider } from "@/components/ui/sidebar";
import { useDeleteFolders } from "@/controllers/API/queries/folders"; import { useDeleteFolders } from "@/controllers/API/queries/folders";
import { useGetFolderQuery } from "@/controllers/API/queries/folders/use-get-folder";
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate"; import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import { LoadingPage } from "@/pages/LoadingPage";
import useAlertStore from "@/stores/alertStore"; import useAlertStore from "@/stores/alertStore";
import useFlowsManagerStore from "@/stores/flowsManagerStore";
import { useFolderStore } from "@/stores/foldersStore"; import { useFolderStore } from "@/stores/foldersStore";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Outlet, useParams } from "react-router-dom"; import { Outlet } from "react-router-dom";
import { PaginatedFolderType } from "../entities";
import useFileDrop from "../hooks/use-on-file-drop"; import useFileDrop from "../hooks/use-on-file-drop";
import ModalsComponent from "../oldComponents/modalsComponent"; import ModalsComponent from "../oldComponents/modalsComponent";
import EmptyPage from "./emptyPage"; import EmptyPage from "./emptyPage";
@ -19,35 +19,19 @@ export default function CollectionPage(): JSX.Element {
const [openDeleteFolderModal, setOpenDeleteFolderModal] = useState(false); const [openDeleteFolderModal, setOpenDeleteFolderModal] = useState(false);
const setFolderToEdit = useFolderStore((state) => state.setFolderToEdit); const setFolderToEdit = useFolderStore((state) => state.setFolderToEdit);
const navigate = useCustomNavigate(); const navigate = useCustomNavigate();
const { folderId } = useParams(); const flows = useFlowsManagerStore((state) => state.flows);
const myCollectionId = useFolderStore((state) => state.myCollectionId); const examples = useFlowsManagerStore((state) => state.examples);
const handleFileDrop = useFileDrop("flow"); const handleFileDrop = useFileDrop("flow");
const setSuccessData = useAlertStore((state) => state.setSuccessData); const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
const folderToEdit = useFolderStore((state) => state.folderToEdit); const folderToEdit = useFolderStore((state) => state.folderToEdit);
const showFolderModal = useFolderStore((state) => state.showFolderModal);
const folders = useFolderStore((state) => state.folders); const folders = useFolderStore((state) => state.folders);
const setShowFolderModal = useFolderStore(
(state) => state.setShowFolderModal,
);
const queryClient = useQueryClient(); const queryClient = useQueryClient();
useEffect(() => { useEffect(() => {
return () => queryClient.removeQueries({ queryKey: ["useGetFolder"] }); return () => queryClient.removeQueries({ queryKey: ["useGetFolder"] });
}, []); }, []);
const { isFetching, data } = useGetFolderQuery({
id: folderId ?? myCollectionId!,
});
const [folderData, setFolderData] = useState<PaginatedFolderType | null>(
null,
);
useEffect(() => {
setFolderData(data ?? null);
}, [data]);
const { mutate } = useDeleteFolders(); const { mutate } = useDeleteFolders();
const handleDeleteFolder = () => { const handleDeleteFolder = () => {
@ -73,58 +57,41 @@ export default function CollectionPage(): JSX.Element {
}; };
return ( return (
<> <SidebarProvider>
{(folderData?.flows?.items?.length !== 0 || folders?.length > 1) && ( {flows &&
<aside examples &&
className={`flex w-2/6 min-w-[220px] max-w-[20rem] flex-col border-r bg-background px-4 lg:inline ${ folders &&
showFolderModal ? "" : "hidden" (flows?.length !== examples?.length || folders?.length > 1) && (
}`} <SideBarFoldersButtonsComponent
>
<FolderSidebarNav
handleChangeFolder={(id: string) => { handleChangeFolder={(id: string) => {
navigate(`all/folder/${id}`); navigate(`all/folder/${id}`);
setShowFolderModal(false);
}} }}
handleDeleteFolder={(item) => { handleDeleteFolder={(item) => {
setFolderToEdit(item); setFolderToEdit(item);
setOpenDeleteFolderModal(true); setOpenDeleteFolderModal(true);
}} }}
/> />
</aside> )}
)} <main className="flex flex-1">
{flows && examples && folders ? (
{!isFetching && folderData ? ( <div className={`relative mx-auto h-full w-full overflow-y-scroll`}>
<div <CardsWrapComponent
className={`relative mx-auto h-full w-full overflow-y-scroll ${ onFileDrop={handleFileDrop}
showFolderModal ? "opacity-80 blur-[2px]" : "" dragMessage={`Drop your file(s) here`}
}`} >
onClick={(e) => { {flows?.length !== examples?.length || folders?.length > 1 ? (
e.stopPropagation(); <Outlet />
) : (
if (showFolderModal) { <EmptyPage setOpenModal={setOpenModal} />
setShowFolderModal(false); )}
} </CardsWrapComponent>
}} </div>
> ) : (
<CardsWrapComponent <div className="flex h-full w-full items-center justify-center">
onFileDrop={handleFileDrop} <LoadingComponent remSize={30} />
dragMessage={`Drop your file(s) here`} </div>
> )}
{folderData && folderData?.flows?.items?.length !== 0 ? ( </main>
<Outlet />
) : (
<EmptyPage
setOpenModal={setOpenModal}
setShowFolderModal={setShowFolderModal}
folderData={folderData}
/>
)}
</CardsWrapComponent>
</div>
) : (
<LoadingPage />
)}
<ModalsComponent <ModalsComponent
openModal={openModal} openModal={openModal}
setOpenModal={setOpenModal} setOpenModal={setOpenModal}
@ -132,6 +99,6 @@ export default function CollectionPage(): JSX.Element {
setOpenDeleteFolderModal={setOpenDeleteFolderModal} setOpenDeleteFolderModal={setOpenDeleteFolderModal}
handleDeleteFolder={handleDeleteFolder} handleDeleteFolder={handleDeleteFolder}
/> />
</> </SidebarProvider>
); );
} }

View file

@ -1,10 +1,23 @@
import { TEMPLATES_DATA } from "../constants"; import { useTypesStore } from "@/stores/typesStore";
import { FlowType } from "@/types/flow";
import { nodeIconsLucide } from "@/utils/styleUtils";
export const getTemplateStyle = (flowData: { export const useGetTemplateStyle = (
name: string; flowData: FlowType,
}): { icon: string; icon_bg_color: string } => { ): { getIcon: () => string } => {
const { icon, icon_bg_color } = TEMPLATES_DATA.examples.find((example) => const getIcon = () => {
flowData.name.includes(example.name), if (flowData.is_component) {
) ?? { icon: "circle-help", icon_bg_color: "bg-purple-300" }; const dataType = flowData.data?.nodes[0].data.type;
return { icon, icon_bg_color }; const isGroup = !!flowData.data?.nodes[0].data.node?.flow;
const icon = flowData.data?.nodes[0].data.node?.icon;
const types = useTypesStore((state) => state.types);
const name = nodeIconsLucide[dataType] ? dataType : types[dataType];
const iconName = icon || (isGroup ? "group_components" : name);
return iconName;
} else {
return flowData.icon ?? "Workflow";
}
};
return { getIcon };
}; };

View file

@ -17,7 +17,4 @@ export const useFolderStore = create<FoldersStoreType>((set, get) => ({
setStarterProjectId: (id) => set(() => ({ starterProjectId: id })), setStarterProjectId: (id) => set(() => ({ starterProjectId: id })),
folders: [], folders: [],
setFolders: (folders) => set(() => ({ folders: folders })), setFolders: (folders) => set(() => ({ folders: folders })),
showFolderModal: false,
setShowFolderModal: (showFolderModal) =>
set(() => ({ showFolderModal: showFolderModal })),
})); }));

View file

@ -1281,8 +1281,6 @@
left: 0; left: 0;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
user-select: none;
text-shadow: 1px 1px rgba(0, 0, 0, 0.1);
} }
:root { :root {
@ -1326,7 +1324,7 @@
var(--color-bg1); var(--color-bg1);
top: 0; top: 0;
left: 0; left: 0;
opacity: 0.6; background-blend-mode: overlay;
svg { svg {
display: none; display: none;

View file

@ -8,6 +8,7 @@
:root { :root {
--font-sans: "Inter", sans-serif; --font-sans: "Inter", sans-serif;
--font-mono: "JetBrains Mono", monospace; --font-mono: "JetBrains Mono", monospace;
--font-chivo: "Chivo", sans-serif;
--foreground: 0 0% 0%; /* hsl(0, 0%, 0%) */ --foreground: 0 0% 0%; /* hsl(0, 0%, 0%) */
--background: 0 0% 100%; /* hsl(0, 0%, 100%) */ --background: 0 0% 100%; /* hsl(0, 0%, 100%) */

View file

@ -13,6 +13,4 @@ export type FoldersStoreType = {
setStarterProjectId: (id: string) => void; setStarterProjectId: (id: string) => void;
folders: FolderType[]; folders: FolderType[];
setFolders: (folders: FolderType[]) => void; setFolders: (folders: FolderType[]) => void;
showFolderModal: boolean;
setShowFolderModal: (show: boolean) => void;
}; };

View file

@ -1596,6 +1596,7 @@ export const createNewFlow = (
name: flow?.name ? flow.name : "Untitled document", name: flow?.name ? flow.name : "Untitled document",
data: flowData, data: flowData,
id: "", id: "",
icon: flow?.icon ?? undefined,
is_component: flow?.is_component ?? false, is_component: flow?.is_component ?? false,
folder_id: folderId, folder_id: folderId,
endpoint_name: flow?.endpoint_name ?? undefined, endpoint_name: flow?.endpoint_name ?? undefined,

View file

@ -48,6 +48,7 @@ import {
Command, Command,
Compass, Compass,
Copy, Copy,
CopyPlus,
CornerDownLeft, CornerDownLeft,
Cpu, Cpu,
CpuIcon, CpuIcon,
@ -96,6 +97,7 @@ import {
Keyboard, Keyboard,
Laptop2, Laptop2,
Layers, Layers,
LayoutGrid,
LayoutPanelTop, LayoutPanelTop,
Link, Link,
Link2, Link2,
@ -127,6 +129,7 @@ import {
PanelLeftClose, PanelLeftClose,
PanelLeftOpen, PanelLeftOpen,
PanelRightClose, PanelRightClose,
PanelRightOpen,
Paperclip, Paperclip,
PaperclipIcon, PaperclipIcon,
Pen, Pen,
@ -792,6 +795,7 @@ export const nodeIconsLucide: iconsType = {
ArrowBigUp, ArrowBigUp,
PanelRightClose, PanelRightClose,
Dot, Dot,
LayoutGrid,
StickyNote, StickyNote,
note: StickyNote, note: StickyNote,
RotateCcw, RotateCcw,
@ -808,7 +812,9 @@ export const nodeIconsLucide: iconsType = {
ArrowUpRight, ArrowUpRight,
Scroll, Scroll,
Image, Image,
CopyPlus,
Pen, Pen,
PanelRightOpen,
CornerDownLeft, CornerDownLeft,
ChevronsDownUp, ChevronsDownUp,
OptionIcon, OptionIcon,

View file

@ -258,6 +258,7 @@ const config = {
fontFamily: { fontFamily: {
sans: ["var(--font-sans)", ...fontFamily.sans], sans: ["var(--font-sans)", ...fontFamily.sans],
mono: ["var(--font-mono)", ...fontFamily.mono], mono: ["var(--font-mono)", ...fontFamily.mono],
chivo: ["var(--font-chivo)", ...fontFamily.sans],
}, },
boxShadow: { boxShadow: {
"frozen-ring": "0 0 10px 2px rgba(128, 190, 230, 0.5)", "frozen-ring": "0 0 10px 2px rgba(128, 190, 230, 0.5)",

View file

@ -41,9 +41,17 @@ test("CRUD folders", async ({ page }) => {
await page.getByText("Select All").first().isVisible(); await page.getByText("Select All").first().isVisible();
await page.getByTestId("add-folder-button").click(); await page.getByTestId("add-folder-button").click();
await page.getByText("New Folder").last().isVisible(); await page
.locator("[data-testid='folder-sidebar']")
.getByText("New Folder")
.last()
.isVisible();
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await page.getByText("New Folder").last().dblclick(); await page
.locator("[data-testid='folder-sidebar']")
.getByText("New Folder")
.last()
.dblclick();
const element = await page.getByTestId("input-folder"); const element = await page.getByTestId("input-folder");
await element.fill("new folder test name"); await element.fill("new folder test name");
@ -172,9 +180,17 @@ test("change flow folder", async ({ page }) => {
await page.getByText("Select All").first().isVisible(); await page.getByText("Select All").first().isVisible();
await page.getByTestId("add-folder-button").click(); await page.getByTestId("add-folder-button").click();
await page.getByText("New Folder").last().isVisible(); await page
.locator("[data-testid='folder-sidebar']")
.getByText("New Folder")
.last()
.isVisible();
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await page.getByText("New Folder").last().dblclick(); await page
.locator("[data-testid='folder-sidebar']")
.getByText("New Folder")
.last()
.dblclick();
await page.getByTestId("input-folder").fill("new folder test name"); await page.getByTestId("input-folder").fill("new folder test name");
await page.keyboard.press("Enter"); await page.keyboard.press("Enter");
await page.getByText("new folder test name").last().isVisible(); await page.getByText("new folder test name").last().isVisible();