Add Types & Refactor Card Components (useHooks) (#2365)

This pull request introduces two improvements:

Type Annotations for Custom Hooks: Enhances code readability and
maintainability by adding type annotations to our custom hooks. This
will improve IDE support and help catch potential errors early in the
development process.

Card Component Refactor (useHooks): Refactors card components to
leverage the useHooks pattern. This improves code organization and
potentially simplifies component logic.

These changes improve the overall codebase quality and make it easier to
understand and maintain.
This commit is contained in:
Gabriel Luiz Freitas Almeida 2024-06-25 17:12:20 -07:00 • committed by GitHub
commit 51fd1cca39
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
42 changed files with 502 additions and 276 deletions

View file

@ -6,13 +6,14 @@ import {
} from "../../constants/constants"; } from "../../constants/constants";
import useAlertStore from "../../stores/alertStore"; import useAlertStore from "../../stores/alertStore";
import { ResponseErrorDetailAPI } from "../../types/api"; import { ResponseErrorDetailAPI } from "../../types/api";
import { NodeDataType } from "../../types/flow";
const useFetchDataOnMount = ( const useFetchDataOnMount = (
data, data: NodeDataType,
name, name: string,
handleUpdateValues, handleUpdateValues: (name: string, data: NodeDataType) => Promise<any>,
setNode, setNode: (id: string, callback: (oldNode: any) => any) => void,
setIsLoading, setIsLoading: (value: boolean) => void,
) => { ) => {
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);

View file

@ -5,15 +5,16 @@ import {
} from "../../constants/constants"; } from "../../constants/constants";
import useAlertStore from "../../stores/alertStore"; import useAlertStore from "../../stores/alertStore";
import { ResponseErrorTypeAPI } from "../../types/api"; import { ResponseErrorTypeAPI } from "../../types/api";
import { NodeDataType } from "../../types/flow";
const useHandleOnNewValue = ( const useHandleOnNewValue = (
data, data: NodeDataType,
name, name: string,
takeSnapshot, takeSnapshot: () => void,
handleUpdateValues, handleUpdateValues: (name: string, data: NodeDataType) => Promise<any>,
debouncedHandleUpdateValues, debouncedHandleUpdateValues: any,
setNode, setNode: (id: string, callback: (oldNode: any) => any) => void,
setIsLoading, setIsLoading: (value: boolean) => void,
) => { ) => {
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);

View file

@ -1,11 +1,12 @@
import { cloneDeep } from "lodash"; import { cloneDeep } from "lodash";
import { NodeDataType } from "../../types/flow";
const useHandleNodeClass = ( const useHandleNodeClass = (
data, data: NodeDataType,
name, name: string,
takeSnapshot, takeSnapshot: () => void,
setNode, setNode: (id: string, callback: (oldNode: any) => any) => void,
updateNodeInternals, updateNodeInternals: (id: string) => void,
) => { ) => {
const handleNodeClass = (newNodeClass, code, type?: string) => { const handleNodeClass = (newNodeClass, code, type?: string) => {
if (!data.node) return; if (!data.node) return;

View file

@ -7,7 +7,10 @@ import useAlertStore from "../../stores/alertStore";
import { ResponseErrorDetailAPI } from "../../types/api"; import { ResponseErrorDetailAPI } from "../../types/api";
import { handleUpdateValues } from "../../utils/parameterUtils"; import { handleUpdateValues } from "../../utils/parameterUtils";
const useHandleRefreshButtonPress = (setIsLoading, setNode) => { const useHandleRefreshButtonPress = (
setIsLoading: (value: boolean) => void,
setNode: (id: string, callback: (oldNode: any) => any) => void,
) => {
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
const handleRefreshButtonPress = async (name, data) => { const handleRefreshButtonPress = async (name, data) => {

View file

@ -1,6 +1,11 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { FlowPoolType } from "../../types/zustand/flow";
const useUpdateValidationStatus = (dataId, flowPool, setValidationStatus) => { const useUpdateValidationStatus = (
dataId: string,
flowPool: FlowPoolType,
setValidationStatus: (value: any) => void,
) => {
useEffect(() => { useEffect(() => {
const relevantData = const relevantData =
flowPool[dataId] && flowPool[dataId]?.length > 0 flowPool[dataId] && flowPool[dataId]?.length > 0

View file

@ -4,7 +4,7 @@ import { isErrorLog } from "../../types/utils/typeCheckingUtils";
const useValidationStatusString = ( const useValidationStatusString = (
validationStatus: VertexBuildTypeAPI | null, validationStatus: VertexBuildTypeAPI | null,
setValidationString, setValidationString: (value: any) => void,
) => { ) => {
useEffect(() => { useEffect(() => {
if (validationStatus && validationStatus.data?.outputs) { if (validationStatus && validationStatus.data?.outputs) {

View file

@ -0,0 +1,19 @@
import { useEffect } from "react";
import { storeComponent } from "../../../types/store";
const useDataEffect = (
data: storeComponent,
setLikedByUser: (value: any) => void,
setLikesCount: (value: any) => void,
setDownloadsCount: (value: any) => void,
) => {
useEffect(() => {
if (data) {
setLikedByUser(data?.liked_by_user ?? false);
setLikesCount(data?.liked_by_count ?? 0);
setDownloadsCount(data?.downloads_count ?? 0);
}
}, [data, data?.liked_by_count, data?.liked_by_user, data?.downloads_count]);
};
export default useDataEffect;

View file

@ -0,0 +1,55 @@
import { useState } from "react";
import { getComponent } from "../../../controllers/API";
import useFlowsManagerStore from "../../../stores/flowsManagerStore";
import { storeComponent } from "../../../types/store";
import cloneFlowWithParent from "../../../utils/storeUtils";
const useInstallComponent = (
data: storeComponent,
name: string,
isStore: boolean,
downloadsCount: number,
setDownloadsCount: (value: any) => void,
setLoading: (value: boolean) => void,
setSuccessData: (value: { title: string }) => void,
setErrorData: (value: { title: string; list: string[] }) => void,
) => {
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const handleInstall = () => {
const temp = downloadsCount;
setDownloadsCount((old) => Number(old) + 1);
setLoading(true);
getComponent(data.id)
.then((res) => {
const newFlow = cloneFlowWithParent(res, res.id, data.is_component);
addFlow(true, newFlow)
.then((id) => {
setSuccessData({
title: `${name} ${isStore ? "Downloaded" : "Installed"} Successfully.`,
});
setLoading(false);
})
.catch((error) => {
setLoading(false);
setErrorData({
title: `Error ${isStore ? "downloading" : "installing"} the ${name}`,
list: [error.response.data.detail],
});
});
})
.catch((err) => {
setLoading(false);
setErrorData({
title: `Error ${isStore ? "downloading" : "installing"} the ${name}`,
list: [err.response.data.detail],
});
setDownloadsCount(temp);
});
};
return { handleInstall };
};
export default useInstallComponent;

View file

@ -0,0 +1,51 @@
import { postLikeComponent } from "../../../controllers/API";
import { storeComponent } from "../../../types/store";
const useLikeComponent = (
data: storeComponent,
name: string,
setLoadingLike: (value: boolean) => void,
likedByUser: boolean | null | undefined,
likesCount: number,
setLikedByUser: (value: any) => void,
setLikesCount: (value: any) => void,
setValidApiKey: (value: boolean) => void,
setErrorData: (value: { title: string; list: string[] }) => void,
) => {
const handleLike = () => {
setLoadingLike(true);
if (likedByUser !== undefined || likedByUser !== null) {
const temp = likedByUser;
const tempNum = likesCount;
setLikedByUser((prev) => !prev);
setLikesCount((prev) => (temp ? prev - 1 : prev + 1));
postLikeComponent(data.id)
.then((response) => {
setLoadingLike(false);
setLikesCount(response.data.likes_count);
setLikedByUser(response.data.liked_by_user);
})
.catch((error) => {
setLoadingLike(false);
setLikesCount(tempNum);
setLikedByUser(temp);
if (error.response.status === 403) {
setValidApiKey(false);
} else {
console.error(error);
setErrorData({
title: `Error liking ${name}.`,
list: [error.response.data.detail],
});
}
});
}
};
return {
handleLike,
};
};
export default useLikeComponent;

View file

@ -0,0 +1,34 @@
import { useCallback } from "react";
import { createRoot } from "react-dom/client";
import useFlowsManagerStore from "../../../stores/flowsManagerStore";
import { storeComponent } from "../../../types/store";
import DragCardComponent from "../components/dragCardComponent";
const useDragStart = (data: storeComponent) => {
const getFlowById = useFlowsManagerStore((state) => state.getFlowById);
const onDragStart = useCallback(
(event) => {
let image = <DragCardComponent data={data} />; // Replace with whatever you want here
const ghost = document.createElement("div");
ghost.style.transform = "translate(-10000px, -10000px)";
ghost.style.position = "absolute";
document.body.appendChild(ghost);
event.dataTransfer.setDragImage(ghost, 0, 0);
const root = createRoot(ghost);
root.render(image);
const flow = getFlowById(data.id);
if (flow) {
event.dataTransfer.setData("flow", JSON.stringify(data));
}
},
[data],
);
return { onDragStart };
};
export default useDragStart;

View file

@ -0,0 +1,27 @@
import { useEffect } from "react";
import { FlowType } from "../../../types/flow";
const usePlaygroundEffect = (
currentFlowId: string,
playground: boolean,
openPlayground: boolean,
currentFlow: FlowType | undefined,
setNodes: (value: any, value2: boolean) => void,
setEdges: (value: any, value2: boolean) => void,
cleanFlowPool: () => void,
) => {
useEffect(() => {
if (currentFlowId && playground) {
if (openPlayground) {
setNodes(currentFlow?.data?.nodes ?? [], true);
setEdges(currentFlow?.data?.edges ?? [], true);
} else {
setNodes([], true);
setEdges([], true);
}
cleanFlowPool();
}
}, [openPlayground]);
};
export default usePlaygroundEffect;

View file

@ -28,6 +28,11 @@ import { Checkbox } from "../ui/checkbox";
import { FormControl, FormField } from "../ui/form"; import { FormControl, FormField } from "../ui/form";
import Loading from "../ui/loading"; import Loading from "../ui/loading";
import DragCardComponent from "./components/dragCardComponent"; import DragCardComponent from "./components/dragCardComponent";
import useDataEffect from "./hooks/use-data-effect";
import useInstallComponent from "./hooks/use-handle-install";
import useLikeComponent from "./hooks/use-handle-like";
import useDragStart from "./hooks/use-on-drag-start";
import usePlaygroundEffect from "./hooks/use-playground-effect";
import { convertTestName } from "./utils/convert-test-name"; import { convertTestName } from "./utils/convert-test-name";
export default function CollectionCardComponent({ export default function CollectionCardComponent({
@ -59,11 +64,9 @@ export default function CollectionCardComponent({
const isStore = false; const isStore = false;
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [loadingLike, setLoadingLike] = useState(false); const [loadingLike, setLoadingLike] = useState(false);
const [liked_by_user, setLiked_by_user] = useState( const [likedByUser, setLikedByUser] = useState(data?.liked_by_user ?? false);
data?.liked_by_user ?? false, const [likesCount, setLikesCount] = useState(data?.liked_by_count ?? 0);
); const [downloadsCount, setDownloadsCount] = useState(
const [likes_count, setLikes_count] = useState(data?.liked_by_count ?? 0);
const [downloads_count, setDownloads_count] = useState(
data?.downloads_count ?? 0, data?.downloads_count ?? 0,
); );
const currentFlow = useFlowsManagerStore((state) => state.currentFlow); const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
@ -99,115 +102,45 @@ export default function CollectionCardComponent({
return inputs.length > 0 || outputs.length > 0; return inputs.length > 0 || outputs.length > 0;
} }
useEffect(() => { usePlaygroundEffect(
if (currentFlowId && playground) { currentFlowId,
if (openPlayground) { playground!,
setNodes(currentFlow?.data?.nodes ?? [], true); openPlayground,
setEdges(currentFlow?.data?.edges ?? [], true); currentFlow,
} else { setNodes,
setNodes([], true); setEdges,
setEdges([], true); cleanFlowPool,
} );
cleanFlowPool();
}
}, [openPlayground]);
useEffect(() => { useDataEffect(data, setLikedByUser, setLikesCount, setDownloadsCount);
if (data) {
setLiked_by_user(data?.liked_by_user ?? false);
setLikes_count(data?.liked_by_count ?? 0);
setDownloads_count(data?.downloads_count ?? 0);
}
}, [data, data.liked_by_count, data.liked_by_user, data.downloads_count]);
function handleInstall() { const { handleInstall } = useInstallComponent(
const temp = downloads_count; data,
setDownloads_count((old) => Number(old) + 1); name,
setLoading(true); isStore,
getComponent(data.id) downloadsCount,
.then((res) => { setDownloadsCount,
const newFlow = cloneFLowWithParent(res, res.id, data.is_component); setLoading,
addFlow(true, newFlow) setSuccessData,
.then((id) => { setErrorData,
setSuccessData({ );
title: `${name} ${
isStore ? "Downloaded" : "Installed"
} Successfully.`,
});
setLoading(false);
})
.catch((error) => {
setLoading(false);
setErrorData({
title: `Error ${
isStore ? "downloading" : "installing"
} the ${name}`,
list: [error["response"]["data"]["detail"]],
});
});
})
.catch((err) => {
setLoading(false);
setErrorData({
title: `Error ${isStore ? "downloading" : "installing"} the ${name}`,
list: [err["response"]["data"]["detail"]],
});
setDownloads_count(temp);
});
}
function handleLike() { const { handleLike } = useLikeComponent(
setLoadingLike(true); data,
if (liked_by_user !== undefined || liked_by_user !== null) { name,
const temp = liked_by_user; setLoadingLike,
const tempNum = likes_count; likedByUser,
setLiked_by_user((prev) => !prev); likesCount,
if (!temp) { setLikedByUser,
setLikes_count((prev) => Number(prev) + 1); setLikesCount,
} else { setValidApiKey,
setLikes_count((prev) => Number(prev) - 1); setErrorData,
} );
postLikeComponent(data.id)
.then((response) => {
setLoadingLike(false);
setLikes_count(response.data.likes_count);
setLiked_by_user(response.data.liked_by_user);
})
.catch((error) => {
setLoadingLike(false);
setLikes_count(tempNum);
setLiked_by_user(temp);
if (error.response.status === 403) {
setValidApiKey(false);
} else {
console.error(error);
setErrorData({
title: `Error liking ${name}.`,
list: [error["response"]["data"]["detail"]],
});
}
});
}
}
const isSelectedCard = const isSelectedCard =
selectedFlowsComponentsCards?.includes(data?.id) ?? false; selectedFlowsComponentsCards?.includes(data?.id) ?? false;
function onDragStart(event: React.DragEvent<any>) { const { onDragStart } = useDragStart(data);
let image: JSX.Element = <DragCardComponent data={data} />; // <== whatever you want here
var ghost = document.createElement("div");
ghost.style.transform = "translate(-10000px, -10000px)";
ghost.style.position = "absolute";
document.body.appendChild(ghost);
event.dataTransfer.setDragImage(ghost, 0, 0);
const root = createRoot(ghost);
root.render(image);
const flow = getFlowById(data.id);
if (flow) {
event.dataTransfer.setData("flow", JSON.stringify(data));
}
}
return ( return (
<> <>
@ -264,7 +197,7 @@ export default function CollectionCardComponent({
<span className="flex items-center gap-1.5 text-xs text-muted-foreground"> <span className="flex items-center gap-1.5 text-xs text-muted-foreground">
<IconComponent name="Heart" className={cn("h-4 w-4")} /> <IconComponent name="Heart" className={cn("h-4 w-4")} />
<span data-testid={`likes-${data.name}`}> <span data-testid={`likes-${data.name}`}>
{likes_count ?? 0} {likesCount ?? 0}
</span> </span>
</span> </span>
</ShadTooltip> </ShadTooltip>
@ -275,7 +208,7 @@ export default function CollectionCardComponent({
className="h-4 w-4" className="h-4 w-4"
/> />
<span data-testid={`downloads-${data.name}`}> <span data-testid={`downloads-${data.name}`}>
{downloads_count ?? 0} {downloadsCount ?? 0}
</span> </span>
</span> </span>
</ShadTooltip> </ShadTooltip>
@ -324,20 +257,7 @@ export default function CollectionCardComponent({
)} )}
</span> </span>
)} )}
<div className="flex w-full flex-1 flex-wrap gap-2"> <div className="flex w-full flex-1 flex-wrap gap-2"></div>
{/* {data.tags &&
data.tags.length > 0 &&
data.tags.map((tag, index) => (
<Badge
key={index}
variant="outline"
size="xq"
className="text-muted-foreground"
>
{tag.name}
</Badge>
))} */}
</div>
</div> </div>
<CardDescription className="pb-2 pt-2"> <CardDescription className="pb-2 pt-2">
@ -457,7 +377,7 @@ export default function CollectionCardComponent({
name="Heart" name="Heart"
className={cn( className={cn(
"h-5 w-5", "h-5 w-5",
liked_by_user likedByUser
? "fill-destructive stroke-destructive" ? "fill-destructive stroke-destructive"
: "", : "",
!authorized ? "text-ring" : "", !authorized ? "text-ring" : "",

View file

@ -9,7 +9,10 @@ import useFlowsManagerStore from "../../../stores/flowsManagerStore";
import { useFolderStore } from "../../../stores/foldersStore"; import { useFolderStore } from "../../../stores/foldersStore";
import { addVersionToDuplicates } from "../../../utils/reactflowUtils"; import { addVersionToDuplicates } from "../../../utils/reactflowUtils";
const useFileDrop = (folderId, folderChangeCallback) => { const useFileDrop = (
folderId: string,
folderChangeCallback: (folderId: string) => void,
) => {
const setFolderDragging = useFolderStore((state) => state.setFolderDragging); const setFolderDragging = useFolderStore((state) => state.setFolderDragging);
const setFolderIdDragging = useFolderStore( const setFolderIdDragging = useFolderStore(
(state) => state.setFolderIdDragging, (state) => state.setFolderIdDragging,

View file

@ -1,6 +1,9 @@
import { useEffect } from "react"; import { useEffect } from "react";
const useAutoResizeTextArea = (value, inputRef) => { const useAutoResizeTextArea = (
value: string,
inputRef: React.RefObject<HTMLInputElement>,
) => {
useEffect(() => { useEffect(() => {
if (inputRef.current && inputRef.current.scrollHeight! !== 0) { if (inputRef.current && inputRef.current.scrollHeight! !== 0) {
inputRef.current.style!.height = "inherit"; // Reset the height inputRef.current.style!.height = "inherit"; // Reset the height

View file

@ -7,10 +7,10 @@ import {
import useFileUpload from "./use-file-upload"; import useFileUpload from "./use-file-upload";
const useDragAndDrop = ( const useDragAndDrop = (
setIsDragging, setIsDragging: (value: boolean) => void,
setFiles, setFiles: (value: any) => void,
currentFlowId, currentFlowId: string,
setErrorData, setErrorData: (value: any) => void,
) => { ) => {
const dragOver = (e) => { const dragOver = (e) => {
e.preventDefault(); e.preventDefault();

View file

@ -1,6 +1,9 @@
import { useEffect } from "react"; import { useEffect } from "react";
const useFocusOnUnlock = (lockChat, inputRef) => { const useFocusOnUnlock = (
lockChat: boolean,
inputRef: React.RefObject<HTMLInputElement>,
) => {
useEffect(() => { useEffect(() => {
if (!lockChat && inputRef.current) { if (!lockChat && inputRef.current) {
inputRef.current.focus(); inputRef.current.focus();

View file

@ -1,3 +1,4 @@
import { AxiosResponse } from "axios";
import { useEffect } from "react"; import { useEffect } from "react";
import ShortUniqueId from "short-unique-id"; import ShortUniqueId from "short-unique-id";
import { import {
@ -6,9 +7,18 @@ import {
SN_ERROR_TEXT, SN_ERROR_TEXT,
} from "../../../../../../constants/constants"; } from "../../../../../../constants/constants";
import useAlertStore from "../../../../../../stores/alertStore"; import useAlertStore from "../../../../../../stores/alertStore";
import { UploadFileTypeAPI } from "../../../../../../types/api";
import useFileUpload from "./use-file-upload"; import useFileUpload from "./use-file-upload";
const useUpload = (uploadFile, currentFlowId, setFiles, lockChat) => { const useUpload = (
uploadFile: (
file: File,
id: string,
) => Promise<AxiosResponse<UploadFileTypeAPI>>,
currentFlowId: string,
setFiles: any,
lockChat: boolean,
) => {
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
useEffect(() => { useEffect(() => {
const handlePaste = (event: ClipboardEvent): void => { const handlePaste = (event: ClipboardEvent): void => {

View file

@ -2,9 +2,10 @@ import { ColDef, ValueGetterParams } from "ag-grid-community";
import { useMemo } from "react"; import { useMemo } from "react";
import TableNodeCellRender from "../../../components/tableComponent/components/tableNodeCellRender"; import TableNodeCellRender from "../../../components/tableComponent/components/tableNodeCellRender";
import TableToggleCellRender from "../../../components/tableComponent/components/tableToggleCellRender"; import TableToggleCellRender from "../../../components/tableComponent/components/tableToggleCellRender";
import { NodeDataType } from "../../../types/flow";
const useColumnDefs = ( const useColumnDefs = (
myData: any, myData: NodeDataType,
handleOnNewValue: (newValue: any, name: string) => void, handleOnNewValue: (newValue: any, name: string) => void,
handleOnChangeDb: (value: boolean, key: string) => void, handleOnChangeDb: (value: boolean, key: string) => void,
changeAdvanced: (n: string) => void, changeAdvanced: (n: string) => void,

View file

@ -1,14 +1,12 @@
import { useMemo } from "react"; import { useMemo } from "react";
import { LANGFLOW_SUPPORTED_TYPES } from "../../../constants/constants"; import { LANGFLOW_SUPPORTED_TYPES } from "../../../constants/constants";
import { TemplateVariableType } from "../../../types/api"; import { NodeDataType } from "../../../types/flow";
const useRowData = (myData, open) => { const useRowData = (myData: NodeDataType, open: boolean) => {
const rowData = useMemo(() => { const rowData = useMemo(() => {
return Object.keys(myData.node!.template) return Object.keys(myData.node!.template)
.filter((key: string) => { .filter((key: string) => {
const templateParam = myData.node!.template[ const templateParam = myData.node!.template[key] as any;
key
] as TemplateVariableType;
return ( return (
key.charAt(0) !== "_" && key.charAt(0) !== "_" &&
templateParam.show && templateParam.show &&
@ -20,9 +18,7 @@ const useRowData = (myData, open) => {
); );
}) })
.map((key: string) => { .map((key: string) => {
const templateParam = myData.node!.template[ const templateParam = myData.node!.template[key] as any;
key
] as TemplateVariableType;
return { return {
...templateParam, ...templateParam,
key: key, key: key,

View file

@ -16,13 +16,11 @@ const EditNodeModal = forwardRef(
nodeLength, nodeLength,
open, open,
setOpen, setOpen,
// setOpenWDoubleClick,
data, data,
}: { }: {
nodeLength: number; nodeLength: number;
open: boolean; open: boolean;
setOpen: (open: boolean) => void; setOpen: (open: boolean) => void;
// setOpenWDoubleClick: (open: boolean) => void;
data: NodeDataType; data: NodeDataType;
}, },
ref, ref,

View file

@ -0,0 +1,58 @@
import React from "react";
import { Link, useNavigate } from "react-router-dom";
import CollectionCardComponent from "../../../../../../components/cardComponent";
import IconComponent from "../../../../../../components/genericIconComponent";
import { Button } from "../../../../../../components/ui/button";
const CollectionCard = ({ item, type, isLoading, control }) => {
const navigate = useNavigate();
const isComponent = item.is_component ?? false;
const editFlowLink = `/flow/${item.id}`;
const editFlowButtonTestId = `edit-flow-button-${item.id}`;
const handleClick = () => {
if (!isComponent) {
navigate(editFlowLink);
}
};
const renderButton = () => {
if (!isComponent) {
return (
<Link to={editFlowLink}>
<Button
tabIndex={-1}
variant="outline"
size="sm"
className="whitespace-nowrap"
data-testid={editFlowButtonTestId}
>
<IconComponent
name="ExternalLink"
className="main-page-nav-button select-none"
/>
Edit Flow
</Button>
</Link>
);
}
return null;
};
return (
<CollectionCardComponent
is_component={type === "component"}
data={{
is_component: isComponent,
...item,
}}
disabled={isLoading}
data-testid={editFlowButtonTestId}
button={renderButton()!}
onClick={!isComponent ? handleClick : undefined}
playground={!isComponent}
control={control}
/>
);
};
export default CollectionCard;

View file

@ -1,15 +1,15 @@
import { useCallback } from "react"; import { useCallback } from "react";
const useDeleteMultipleFlows = ( const useDeleteMultipleFlows = (
selectedFlowsComponentsCards, selectedFlowsComponentsCards: string[],
removeFlow, removeFlow: (selectedFlowsComponentsCards: string[]) => Promise<void>,
resetFilter, resetFilter: () => void,
getFoldersApi, getFoldersApi: (refetch?: boolean) => Promise<void>,
folderId, folderId: string | undefined,
myCollectionId, myCollectionId: string,
getFolderById, getFolderById: (id: string) => void,
setSuccessData, setSuccessData: (data: { title: string }) => void,
setErrorData, setErrorData: (data: { title: string; list: string[] }) => void,
) => { ) => {
const handleDeleteMultiple = useCallback(() => { const handleDeleteMultiple = useCallback(() => {
removeFlow(selectedFlowsComponentsCards) removeFlow(selectedFlowsComponentsCards)

View file

@ -1,6 +1,9 @@
import { useMemo } from "react"; import { useMemo } from "react";
const useDescriptionModal = (selectedFlowsComponentsCards, type) => { const useDescriptionModal = (
selectedFlowsComponentsCards: string[] | undefined,
type: string | undefined,
) => {
const getDescriptionModal = useMemo(() => { const getDescriptionModal = useMemo(() => {
const getTypeLabel = (type) => { const getTypeLabel = (type) => {
const labels = { const labels = {

View file

@ -1,10 +1,11 @@
import cloneDeep from "lodash/cloneDeep"; import cloneDeep from "lodash/cloneDeep";
import { useEffect } from "react"; import { useEffect } from "react";
import { FlowType } from "../../../../../types/flow";
const useFilteredFlows = ( const useFilteredFlows = (
flowsFromFolder, flowsFromFolder: FlowType[],
searchFlowsComponents, searchFlowsComponents: string,
setAllFlows, setAllFlows: (value: any[]) => void,
) => { ) => {
useEffect(() => { useEffect(() => {
const newFlows = cloneDeep(flowsFromFolder || []); const newFlows = cloneDeep(flowsFromFolder || []);

View file

@ -1,18 +1,31 @@
import { useCallback } from "react"; import { useCallback } from "react";
import { XYPosition } from "reactflow";
import { FlowType } from "../../../../../types/flow";
const useDuplicateFlows = ( const useDuplicateFlows = (
selectedFlowsComponentsCards, selectedFlowsComponentsCards: string[],
addFlow, addFlow: (
allFlows, newProject: boolean,
resetFilter, flow?: FlowType,
getFoldersApi, override?: boolean,
folderId, position?: XYPosition,
myCollectionId, fromDragAndDrop?: boolean,
getFolderById, ) => Promise<string | undefined>,
setSuccessData, allFlows: any[],
setSelectedFlowsComponentsCards, resetFilter: () => void,
handleSelectAll, getFoldersApi: (
cardTypes, refetch?: boolean,
startupApplication?: boolean,
) => Promise<void>,
folderId: string,
myCollectionId: string,
getFolderById: (id: string) => void,
setSuccessData: (data: { title: string }) => void,
setSelectedFlowsComponentsCards: (
selectedFlowsComponentsCards: string[],
) => void,
handleSelectAll: (select: boolean) => void,
cardTypes: string,
) => { ) => {
const handleDuplicate = useCallback(() => { const handleDuplicate = useCallback(() => {
Promise.all( Promise.all(

View file

@ -1,15 +1,18 @@
import { useCallback } from "react"; import { useCallback } from "react";
import { FlowType } from "../../../../../types/flow";
const useExportFlows = ( const useExportFlows = (
selectedFlowsComponentsCards, selectedFlowsComponentsCards: string[],
allFlows, allFlows: Array<FlowType>,
downloadFlow, downloadFlow: (flow: any, name: string, description: string) => void,
removeApiKeys, removeApiKeys: (flow: any) => any,
version, version: string,
setSuccessData, setSuccessData: (data: { title: string }) => void,
setSelectedFlowsComponentsCards, setSelectedFlowsComponentsCards: (
handleSelectAll, selectedFlowsComponentsCards: string[],
cardTypes, ) => void,
handleSelectAll: (select: boolean) => void,
cardTypes: string,
) => { ) => {
const handleExport = useCallback(() => { const handleExport = useCallback(() => {
selectedFlowsComponentsCards.forEach((selectedFlowId) => { selectedFlowsComponentsCards.forEach((selectedFlowId) => {

View file

@ -1,6 +1,11 @@
import { useCallback } from "react"; import { useCallback } from "react";
import { FlowType } from "../../../../../types/flow";
const useSelectAll = (flowsFromFolder, getValues, setValue) => { const useSelectAll = (
flowsFromFolder: FlowType[],
getValues: () => Record<string, boolean>,
setValue: (key: string, value: boolean) => void,
) => {
const handleSelectAll = useCallback( const handleSelectAll = useCallback(
(select) => { (select) => {
const flowsFromFolderIds = flowsFromFolder?.map((f) => f.id); const flowsFromFolderIds = flowsFromFolder?.map((f) => f.id);

View file

@ -1,15 +1,15 @@
import { useCallback } from "react"; import { useCallback } from "react";
const useSelectOptionsChange = ( const useSelectOptionsChange = (
selectedFlowsComponentsCards, selectedFlowsComponentsCards: string[] | undefined,
setErrorData, setErrorData: (data: { title: string; list: string[] }) => void,
setOpenDelete, setOpenDelete: (value: boolean) => void,
handleDuplicate, handleDuplicate: () => void,
handleExport, handleExport: () => void,
) => { ) => {
const handleSelectOptionsChange = useCallback( const handleSelectOptionsChange = useCallback(
(action) => { (action) => {
const hasSelected = selectedFlowsComponentsCards?.length > 0; const hasSelected = selectedFlowsComponentsCards?.length! > 0;
if (!hasSelected) { if (!hasSelected) {
setErrorData({ setErrorData({
title: "No items selected", title: "No items selected",

View file

@ -1,8 +1,10 @@
import { useEffect } from "react"; import { useEffect } from "react";
const useSelectedFlows = ( const useSelectedFlows = (
entireFormValues, entireFormValues: Record<string, boolean> | undefined,
setSelectedFlowsComponentsCards, setSelectedFlowsComponentsCards: (
selectedFlowsComponentsCards: string[],
) => void,
) => { ) => {
useEffect(() => { useEffect(() => {
if (!entireFormValues || Object.keys(entireFormValues).length === 0) return; if (!entireFormValues || Object.keys(entireFormValues).length === 0) return;

View file

@ -19,6 +19,7 @@ import { getNameByType } from "../../utils/get-name-by-type";
import { sortFlows } from "../../utils/sort-flows"; import { sortFlows } from "../../utils/sort-flows";
import EmptyComponent from "../emptyComponent"; import EmptyComponent from "../emptyComponent";
import HeaderComponent from "../headerComponent"; import HeaderComponent from "../headerComponent";
import CollectionCard from "./components/collectionCard";
import useDeleteMultipleFlows from "./hooks/use-delete-multiple"; import useDeleteMultipleFlows from "./hooks/use-delete-multiple";
import useDescriptionModal from "./hooks/use-description-modal"; import useDescriptionModal from "./hooks/use-description-modal";
import useFilteredFlows from "./hooks/use-filtered-flows"; import useFilteredFlows from "./hooks/use-filtered-flows";
@ -61,7 +62,6 @@ export default function ComponentsComponent({
const [handleFileDrop] = useFileDrop(uploadFlow, type)!; const [handleFileDrop] = useFileDrop(uploadFlow, type)!;
const [pageSize, setPageSize] = useState(20); const [pageSize, setPageSize] = useState(20);
const [pageIndex, setPageIndex] = useState(1); const [pageIndex, setPageIndex] = useState(1);
const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
const all: FlowType[] = sortFlows(allFlows, type); const all: FlowType[] = sortFlows(allFlows, type);
const start = (pageIndex - 1) * pageSize; const start = (pageIndex - 1) * pageSize;
@ -94,7 +94,7 @@ export default function ComponentsComponent({
getFolderById(folderId ? folderId : myCollectionId); getFolderById(folderId ? folderId : myCollectionId);
}, [location]); }, [location]);
useFilteredFlows(flowsFromFolder, searchFlowsComponents, setAllFlows); useFilteredFlows(flowsFromFolder!, searchFlowsComponents, setAllFlows);
const resetFilter = () => { const resetFilter = () => {
setPageIndex(1); setPageIndex(1);
@ -107,7 +107,7 @@ export default function ComponentsComponent({
const methods = useForm(); const methods = useForm();
const { handleSelectAll } = useSelectAll( const { handleSelectAll } = useSelectAll(
flowsFromFolder, flowsFromFolder!,
getValues, getValues,
setValue, setValue,
); );
@ -119,7 +119,7 @@ export default function ComponentsComponent({
resetFilter, resetFilter,
getFoldersApi, getFoldersApi,
folderId, folderId,
myCollectionId, myCollectionId!,
getFolderById, getFolderById,
setSuccessData, setSuccessData,
setSelectedFlowsComponentsCards, setSelectedFlowsComponentsCards,
@ -155,7 +155,7 @@ export default function ComponentsComponent({
resetFilter, resetFilter,
getFoldersApi, getFoldersApi,
folderId, folderId,
myCollectionId, myCollectionId!,
getFolderById, getFolderById,
setSuccessData, setSuccessData,
setErrorData, setErrorData,
@ -205,43 +205,10 @@ export default function ComponentsComponent({
{data?.map((item) => ( {data?.map((item) => (
<FormProvider {...methods} key={item.id}> <FormProvider {...methods} key={item.id}>
<form> <form>
<CollectionCardComponent <CollectionCard
is_component={type === "component"} item={item}
data={{ type={type}
is_component: item.is_component ?? false, isLoading={isLoading}
...item,
}}
disabled={isLoading}
data-testid={"edit-flow-button-" + item.id}
button={
!item.is_component ? (
<Link to={"/flow/" + item.id}>
<Button
tabIndex={-1}
variant="outline"
size="sm"
className="whitespace-nowrap"
data-testid={"edit-flow-button-" + item.id}
>
<IconComponent
name="ExternalLink"
className="main-page-nav-button select-none"
/>
Edit Flow
</Button>
</Link>
) : (
<></>
)
}
onClick={
!item.is_component
? () => {
navigate("/flow/" + item.id);
}
: undefined
}
playground={!item.is_component}
control={control} control={control}
/> />
</form> </form>

View file

@ -2,7 +2,7 @@ import useAlertStore from "../../../stores/alertStore";
import { useFolderStore } from "../../../stores/foldersStore"; import { useFolderStore } from "../../../stores/foldersStore";
import { deleteFolder, getFolderById } from "../services"; import { deleteFolder, getFolderById } from "../services";
const useDeleteFolder = ({ navigate }) => { const useDeleteFolder = ({ navigate }: { navigate: (url: string) => void }) => {
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);

View file

@ -1,7 +1,26 @@
import { XYPosition } from "reactflow";
import { CONSOLE_ERROR_MSG } from "../../../constants/alerts_constants"; import { CONSOLE_ERROR_MSG } from "../../../constants/alerts_constants";
import useAlertStore from "../../../stores/alertStore"; import useAlertStore from "../../../stores/alertStore";
const useDropdownOptions = ({ uploadFlow, navigate, is_component }) => { const useDropdownOptions = ({
uploadFlow,
navigate,
is_component,
}: {
uploadFlow: ({
newProject,
file,
isComponent,
position,
}: {
newProject: boolean;
file?: File;
isComponent: boolean | null;
position?: XYPosition;
}) => Promise<string | never>;
navigate: (url: string) => void;
is_component: boolean;
}) => {
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 handleImportFromJSON = () => { const handleImportFromJSON = () => {

View file

@ -1,6 +1,12 @@
import { getApiKey } from "../../../../../controllers/API"; import { getApiKey } from "../../../../../controllers/API";
import { Users } from "../../../../../types/api";
const useApiKeys = (userData, setLoadingKeys, keysList, setUserId) => { const useApiKeys = (
userData: Users | null,
setLoadingKeys: (load: boolean) => void,
keysList: React.MutableRefObject<never[]>,
setUserId: (userId: string) => void,
) => {
const fetchApiKeys = () => { const fetchApiKeys = () => {
setLoadingKeys(true); setLoadingKeys(true);
getApiKey() getApiKey()

View file

@ -7,10 +7,10 @@ import {
import { deleteApiKey } from "../../../../../controllers/API"; import { deleteApiKey } from "../../../../../controllers/API";
const useDeleteApiKeys = ( const useDeleteApiKeys = (
selectedRows, selectedRows: string[],
resetFilter, resetFilter: () => void,
setSuccessData, setSuccessData: (data: { title: string }) => void,
setErrorData, setErrorData: (data: { title: string; list: string[] }) => void,
) => { ) => {
const handleDeleteKey = () => { const handleDeleteKey = () => {
Promise.all(selectedRows.map((selectedRow) => deleteApiKey(selectedRow))) Promise.all(selectedRows.map((selectedRow) => deleteApiKey(selectedRow)))

View file

@ -4,7 +4,10 @@ import {
BASE_URL_API, BASE_URL_API,
} from "../../../../../../../../../constants/constants"; } from "../../../../../../../../../constants/constants";
const usePreloadImages = (profilePictures, setImagesLoaded) => { const usePreloadImages = (
profilePictures: { [key: string]: string[] },
setImagesLoaded: (value: boolean) => void,
) => {
const preloadImages = async (imageUrls) => { const preloadImages = async (imageUrls) => {
return Promise.all( return Promise.all(
imageUrls.map( imageUrls.map(

View file

@ -5,8 +5,13 @@ import {
SAVE_SUCCESS_ALERT, SAVE_SUCCESS_ALERT,
} from "../../../../constants/alerts_constants"; } from "../../../../constants/alerts_constants";
import { resetPassword } from "../../../../controllers/API"; import { resetPassword } from "../../../../controllers/API";
import { Users } from "../../../../types/api";
const usePatchPassword = (userData, setSuccessData, setErrorData) => { const usePatchPassword = (
userData: Users | null,
setSuccessData: (data: { title: string; list?: string[] }) => void,
setErrorData: (data: { title: string; list: string[] }) => void,
) => {
const handlePatchPassword = async (password, cnfPassword, handleInput) => { const handlePatchPassword = async (password, cnfPassword, handleInput) => {
if (password !== cnfPassword) { if (password !== cnfPassword) {
setErrorData({ setErrorData({
@ -16,7 +21,7 @@ const usePatchPassword = (userData, setSuccessData, setErrorData) => {
return; return;
} }
try { try {
if (password !== "") await resetPassword(userData.id, { password }); if (password !== "") await resetPassword(userData!.id, { password });
handleInput({ target: { name: "password", value: "" } }); handleInput({ target: { name: "password", value: "" } });
handleInput({ target: { name: "cnfPassword", value: "" } }); handleInput({ target: { name: "cnfPassword", value: "" } });
setSuccessData({ title: SAVE_SUCCESS_ALERT }); setSuccessData({ title: SAVE_SUCCESS_ALERT });

View file

@ -4,21 +4,22 @@ import {
SAVE_SUCCESS_ALERT, SAVE_SUCCESS_ALERT,
} from "../../../../constants/alerts_constants"; } from "../../../../constants/alerts_constants";
import { updateUser } from "../../../../controllers/API"; import { updateUser } from "../../../../controllers/API";
import { Users } from "../../../../types/api";
const usePatchProfilePicture = ( const usePatchProfilePicture = (
setSuccessData, setSuccessData: (data: { title: string; list?: string[] }) => void,
setErrorData, setErrorData: (data: { title: string; list: string[] }) => void,
currentUserData, currentUserData: Users | null,
setUserData, setUserData: (data: any) => void,
) => { ) => {
const handlePatchProfilePicture = async (profile_picture) => { const handlePatchProfilePicture = async (profile_picture) => {
try { try {
if (profile_picture !== "") { if (profile_picture !== "") {
await updateUser(currentUserData.id, { await updateUser(currentUserData!.id, {
profile_image: profile_picture, profile_image: profile_picture,
}); });
let newUserData = cloneDeep(currentUserData); let newUserData = cloneDeep(currentUserData);
newUserData.profile_image = profile_picture; newUserData!.profile_image = profile_picture;
setUserData(newUserData); setUserData(newUserData);
} }
setSuccessData({ title: SAVE_SUCCESS_ALERT }); setSuccessData({ title: SAVE_SUCCESS_ALERT });

View file

@ -7,11 +7,11 @@ import { AuthContext } from "../../../../contexts/authContext";
import { addApiKeyStore } from "../../../../controllers/API"; import { addApiKeyStore } from "../../../../controllers/API";
const useSaveKey = ( const useSaveKey = (
setSuccessData, setSuccessData: (data: { title: string }) => void,
setErrorData, setErrorData: (data: { title: string; list: string[] }) => void,
setHasApiKey, setHasApiKey: (hasApiKey: boolean) => void,
setValidApiKey, setValidApiKey: (validApiKey: boolean) => void,
setLoadingApiKey, setLoadingApiKey: (loadingApiKey: boolean) => void,
) => { ) => {
const { storeApiKey } = useContext(AuthContext); const { storeApiKey } = useContext(AuthContext);

View file

@ -1,6 +1,9 @@
import { useEffect } from "react"; import { useEffect } from "react";
const useScrollToElement = (scrollId, setCurrentFlowId) => { const useScrollToElement = (
scrollId: string | null | undefined,
setCurrentFlowId: (currentFlowId: string) => void,
) => {
useEffect(() => { useEffect(() => {
const element = document.getElementById(scrollId ?? "null"); const element = document.getElementById(scrollId ?? "null");
if (element) { if (element) {

View file

@ -1,8 +1,11 @@
import { ColDef, ColGroupDef } from "ag-grid-community";
import { useEffect } from "react"; import { useEffect } from "react";
import { getMessagesTable } from "../../../../../controllers/API"; import { getMessagesTable } from "../../../../../controllers/API";
import { useMessagesStore } from "../../../../../stores/messagesStore"; import { useMessagesStore } from "../../../../../stores/messagesStore";
const useMessagesTable = (setColumns) => { const useMessagesTable = (
setColumns: (data: Array<ColDef | ColGroupDef>) => void,
) => {
const setMessages = useMessagesStore((state) => state.setMessages); const setMessages = useMessagesStore((state) => state.setMessages);
useEffect(() => { useEffect(() => {
const fetchData = async () => { const fetchData = async () => {

View file

@ -2,10 +2,10 @@ import { deleteMessagesFn } from "../../../../../controllers/API";
import { useMessagesStore } from "../../../../../stores/messagesStore"; import { useMessagesStore } from "../../../../../stores/messagesStore";
const useRemoveMessages = ( const useRemoveMessages = (
setSelectedRows, setSelectedRows: (data: number[]) => void,
setSuccessData, setSuccessData: (data: { title: string }) => void,
setErrorData, setErrorData: (data: { title: string }) => void,
selectedRows, selectedRows: number[],
) => { ) => {
const deleteMessages = useMessagesStore((state) => state.removeMessages); const deleteMessages = useMessagesStore((state) => state.removeMessages);

View file

@ -2,7 +2,10 @@ import { updateMessageApi } from "../../../../../controllers/API";
import { useMessagesStore } from "../../../../../stores/messagesStore"; import { useMessagesStore } from "../../../../../stores/messagesStore";
import { Message } from "../../../../../types/messages"; import { Message } from "../../../../../types/messages";
const useUpdateMessage = (setSuccessData, setErrorData) => { const useUpdateMessage = (
setSuccessData: (data: { title: string; list?: string[] }) => void,
setErrorData: (data: { title: string; list?: string[] }) => void,
) => {
const updateMessage = useMessagesStore((state) => state.updateMessage); const updateMessage = useMessagesStore((state) => state.updateMessage);
const handleUpdate = async (data: Message) => { const handleUpdate = async (data: Message) => {