(hooks): add custom hooks for card component

- Add `useDataEffect` to handle data-related side effects
- Add `useInstallComponent` to manage component installation logic
- Add `useLikeComponent` to handle like functionality
- Add `useDragStart` to manage drag start events
- Add `usePlaygroundEffect` to handle playground-related side effects

 (cardComponent): add custom hooks for data, install, like, drag start, and playground effects
♻️ (cardComponent): rename state variables for consistency and readability
 (MainPage): add CollectionCard component to handle card rendering and interactions

 (index.tsx): add CollectionCard component to ComponentsComponent
♻️ (index.tsx): refactor to use CollectionCard instead of inline code
This commit is contained in:
cristhianzl 2024-06-21 17:25:39 -03:00 committed by Gabriel Luiz Freitas Almeida
commit 1d7dd4550e
8 changed files with 288 additions and 162 deletions

View file

@ -0,0 +1,18 @@
import { useEffect } from "react";
const useDataEffect = (
data,
setLikedByUser,
setLikesCount,
setDownloadsCount,
) => {
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,54 @@
import { useState } from "react";
import { getComponent } from "../../../controllers/API";
import useFlowsManagerStore from "../../../stores/flowsManagerStore";
import cloneFlowWithParent from "../../../utils/storeUtils";
const useInstallComponent = (
data,
name,
isStore,
downloadsCount,
setDownloadsCount,
setLoading,
setSuccessData,
setErrorData,
) => {
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,50 @@
import { postLikeComponent } from "../../../controllers/API";
const useLikeComponent = (
data,
name,
setLoadingLike,
likedByUser,
likesCount,
setLikedByUser,
setLikesCount,
setValidApiKey,
setErrorData,
) => {
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,33 @@
import { useCallback } from "react";
import { createRoot } from "react-dom/client";
import useFlowsManagerStore from "../../../stores/flowsManagerStore";
import DragCardComponent from "../components/dragCardComponent";
const useDragStart = (data) => {
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,26 @@
import { useEffect } from "react";
const usePlaygroundEffect = (
currentFlowId,
playground,
openPlayground,
currentFlow,
setNodes,
setEdges,
cleanFlowPool,
) => {
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 Loading from "../ui/loading";
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";
export default function CollectionCardComponent({
@ -59,11 +64,9 @@ export default function CollectionCardComponent({
const isStore = false;
const [loading, setLoading] = useState(false);
const [loadingLike, setLoadingLike] = useState(false);
const [liked_by_user, setLiked_by_user] = useState(
data?.liked_by_user ?? false,
);
const [likes_count, setLikes_count] = useState(data?.liked_by_count ?? 0);
const [downloads_count, setDownloads_count] = useState(
const [likedByUser, setLikedByUser] = useState(data?.liked_by_user ?? false);
const [likesCount, setLikesCount] = useState(data?.liked_by_count ?? 0);
const [downloadsCount, setDownloadsCount] = useState(
data?.downloads_count ?? 0,
);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
@ -99,115 +102,45 @@ export default function CollectionCardComponent({
return inputs.length > 0 || outputs.length > 0;
}
useEffect(() => {
if (currentFlowId && playground) {
if (openPlayground) {
setNodes(currentFlow?.data?.nodes ?? [], true);
setEdges(currentFlow?.data?.edges ?? [], true);
} else {
setNodes([], true);
setEdges([], true);
}
cleanFlowPool();
}
}, [openPlayground]);
usePlaygroundEffect(
currentFlowId,
playground,
openPlayground,
currentFlow,
setNodes,
setEdges,
cleanFlowPool,
);
useEffect(() => {
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]);
useDataEffect(data, setLikedByUser, setLikesCount, setDownloadsCount);
function handleInstall() {
const temp = downloads_count;
setDownloads_count((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"]],
});
setDownloads_count(temp);
});
}
const { handleInstall } = useInstallComponent(
data,
name,
isStore,
downloadsCount,
setDownloadsCount,
setLoading,
setSuccessData,
setErrorData,
);
function handleLike() {
setLoadingLike(true);
if (liked_by_user !== undefined || liked_by_user !== null) {
const temp = liked_by_user;
const tempNum = likes_count;
setLiked_by_user((prev) => !prev);
if (!temp) {
setLikes_count((prev) => Number(prev) + 1);
} else {
setLikes_count((prev) => Number(prev) - 1);
}
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 { handleLike } = useLikeComponent(
data,
name,
setLoadingLike,
likedByUser,
likesCount,
setLikedByUser,
setLikesCount,
setValidApiKey,
setErrorData,
);
const isSelectedCard =
selectedFlowsComponentsCards?.includes(data?.id) ?? false;
function onDragStart(event: React.DragEvent<any>) {
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));
}
}
const { onDragStart } = useDragStart(data);
return (
<>
@ -264,7 +197,7 @@ export default function CollectionCardComponent({
<span className="flex items-center gap-1.5 text-xs text-muted-foreground">
<IconComponent name="Heart" className={cn("h-4 w-4")} />
<span data-testid={`likes-${data.name}`}>
{likes_count ?? 0}
{likesCount ?? 0}
</span>
</span>
</ShadTooltip>
@ -275,7 +208,7 @@ export default function CollectionCardComponent({
className="h-4 w-4"
/>
<span data-testid={`downloads-${data.name}`}>
{downloads_count ?? 0}
{downloadsCount ?? 0}
</span>
</span>
</ShadTooltip>
@ -324,20 +257,7 @@ export default function CollectionCardComponent({
)}
</span>
)}
<div className="flex w-full flex-1 flex-wrap gap-2">
{/* {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 className="flex w-full flex-1 flex-wrap gap-2"></div>
</div>
<CardDescription className="pb-2 pt-2">
@ -457,7 +377,7 @@ export default function CollectionCardComponent({
name="Heart"
className={cn(
"h-5 w-5",
liked_by_user
likedByUser
? "fill-destructive stroke-destructive"
: "",
!authorized ? "text-ring" : "",

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

@ -19,6 +19,7 @@ import { getNameByType } from "../../utils/get-name-by-type";
import { sortFlows } from "../../utils/sort-flows";
import EmptyComponent from "../emptyComponent";
import HeaderComponent from "../headerComponent";
import CollectionCard from "./components/collectionCard";
import useDeleteMultipleFlows from "./hooks/use-delete-multiple";
import useDescriptionModal from "./hooks/use-description-modal";
import useFilteredFlows from "./hooks/use-filtered-flows";
@ -61,7 +62,6 @@ export default function ComponentsComponent({
const [handleFileDrop] = useFileDrop(uploadFlow, type)!;
const [pageSize, setPageSize] = useState(20);
const [pageIndex, setPageIndex] = useState(1);
const navigate = useNavigate();
const location = useLocation();
const all: FlowType[] = sortFlows(allFlows, type);
const start = (pageIndex - 1) * pageSize;
@ -205,43 +205,10 @@ export default function ComponentsComponent({
{data?.map((item) => (
<FormProvider {...methods} key={item.id}>
<form>
<CollectionCardComponent
is_component={type === "component"}
data={{
is_component: item.is_component ?? false,
...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}
<CollectionCard
item={item}
type={type}
isLoading={isLoading}
control={control}
/>
</form>