Fixed errors on code and made MarketCard default

This commit is contained in:
Lucas Oliveira 2023-11-16 18:49:08 -03:00
commit f7903043fe
10 changed files with 308 additions and 368 deletions

View file

@ -1,6 +1,17 @@
import { cardComponentPropsType } from "../../types/components"; import { useContext, useState } from "react";
import { useNavigate } from "react-router-dom";
import { alertContext } from "../../contexts/alertContext";
import { FlowsContext } from "../../contexts/flowsContext";
import { StoreContext } from "../../contexts/storeContext";
import { getComponent, postLikeComponent } from "../../controllers/API";
import { storeComponent } from "../../types/store";
import cloneFLowWithParent from "../../utils/storeUtils";
import { gradients } from "../../utils/styleUtils"; import { gradients } from "../../utils/styleUtils";
import { classNames } from "../../utils/utils";
import ShadTooltip from "../ShadTooltipComponent";
import IconComponent from "../genericIconComponent"; import IconComponent from "../genericIconComponent";
import { Badge } from "../ui/badge";
import { Button } from "../ui/button";
import { import {
Card, Card,
CardDescription, CardDescription,
@ -9,47 +20,288 @@ import {
CardTitle, CardTitle,
} from "../ui/card"; } from "../ui/card";
export const CardComponent = ({ export default function CollectionCardComponent({
data, data,
onDelete, authorized = true,
disabled = false,
button, button,
}: cardComponentPropsType): JSX.Element => { onDelete,
return ( }: {
<Card className="group"> data: storeComponent;
<CardHeader> authorized?: boolean;
<CardTitle className="card-component-title-display"> disabled?: boolean;
<span button?: JSX.Element;
className={ onDelete?: () => void;
"card-component-image " + }) {
gradients[parseInt(data.id.slice(0, 12), 16) % gradients.length] const [loading, setLoading] = useState(false);
} const { addFlow } = useContext(FlowsContext);
></span> const [loadingLike, setLoadingLike] = useState(false);
<span className="card-component-title-size">{data.name}</span> const { setSuccessData, setErrorData } = useContext(alertContext);
{onDelete && ( const { setValidApiKey } = useContext(StoreContext);
<button className="card-component-delete-button" onClick={onDelete}> const [liked_by_user, setLiked_by_user] = useState(data.liked_by_user);
<IconComponent const [likes_count, setLikes_count] = useState(data.liked_by_count ?? 0);
name="Trash2"
className="card-component-delete-icon"
/>
</button>
)}
</CardTitle>
<CardDescription className="card-component-desc">
<div className="card-component-desc-text">
{data.description}
{/* {flow.description} */}
</div>
</CardDescription>
</CardHeader>
{button && ( const name = data.is_component ? "Component" : "Flow";
<CardFooter>
<div className="card-component-footer-arrangement"> const navigate = useNavigate();
<div className="card-component-footer"></div>
{button} function handleInstall() {
</div> setLoading(true);
</CardFooter> getComponent(data.id).then((res) => {
const newFlow = cloneFLowWithParent(res, res.id, data.is_component);
addFlow(true, newFlow).then((id) => {
setSuccessData({ title: `${name} Installed` });
setLoading(false);
if (!data.is_component) navigate("/flow/" + id);
});
});
}
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) => prev + 1);
} else {
setLikes_count((prev) => prev - 1);
}
console.log(data.id);
postLikeComponent(data.id)
.then((response) => {
setLoadingLike(false);
setLikes_count(response.likes_count);
setLiked_by_user(response.liked_by_user);
})
.catch((error) => {
setLoadingLike(false);
setLikes_count(tempNum);
setLiked_by_user(temp);
if (error.response.status === 403 || error.response.status === 401) {
setValidApiKey(false);
} else {
console.error(error);
setErrorData({
title: `Error liking ${name}.`,
list: [error["response"]["data"]["detail"]],
});
}
});
}
}
const totalComponentsMetadata = () => {
return data?.metadata ? data.metadata["total"] : 0;
};
return (
<Card
className={classNames(
"group relative flex flex-col justify-between overflow-hidden transition-all hover:shadow-md",
disabled ? "pointer-events-none opacity-50" : ""
)} )}
>
<div>
<CardHeader>
<div>
<CardTitle className="flex w-full items-center justify-between gap-3 text-xl">
<div
className={classNames(
"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-primary",
gradients[
parseInt(data.id.slice(0, 12), 16) % gradients.length
]
)}
>
<div
className={classNames(
data.is_component ? "h-7 w-7 rounded-full bg-muted" : "",
"flex items-center justify-center"
)}
>
{data.is_component ? (
<svg className="h-5 w-5" viewBox="0 0 24 24">
<defs>
<linearGradient
id={data.id}
x1="0%"
y1="0%"
x2="100%"
y2="100%"
className={
gradients[
parseInt(data.id.slice(0, 12), 16) %
gradients.length
]
}
>
<stop
offset="0%"
stopColor="var(--tw-gradient-from)"
/>
<stop
offset="100%"
stopColor="var(--tw-gradient-to)"
/>
</linearGradient>
</defs>
<IconComponent
className={classNames(
"h-4 w-4",
gradients[
parseInt(data.id.slice(0, 12), 16) %
gradients.length
]
)}
stroke={`url(#${data.id})`}
name="ToyBrick"
/>
</svg>
) : (
<IconComponent
className="h-4 w-4 text-background"
name="Network"
/>
)}
</div>
</div>
<ShadTooltip content={data.name}>
<div className="w-full truncate">{data.name}</div>
</ShadTooltip>
{data?.metadata && (
<div className="flex gap-3">
{!data.is_component && (
<ShadTooltip content="Components">
<span className="flex items-center gap-1.5 text-xs text-muted-foreground">
<IconComponent name="ToyBrick" className="h-4 w-4" />
{totalComponentsMetadata()}
</span>
</ShadTooltip>
)}
<ShadTooltip content="Likes">
<span className="flex items-center gap-1.5 text-xs text-muted-foreground">
<IconComponent
name="Heart"
className={classNames("h-4 w-4 ")}
/>
{likes_count ?? 0}
</span>
</ShadTooltip>
<ShadTooltip content="Downloads">
<span className="flex items-center gap-1.5 text-xs text-muted-foreground">
<IconComponent name="DownloadCloud" className="h-4 w-4" />
{data.downloads_count}
</span>
</ShadTooltip>
</div>
)}
{onDelete && (
<button onClick={onDelete}>
<IconComponent
name="Trash2"
className="h-5 w-5 text-primary opacity-0 transition-all hover:text-destructive group-hover:opacity-100"
/>
</button>
)}
</CardTitle>
</div>
{data.user_created && data.user_created.username && (
<span className="text-xs text-primary">
by <b>{data.user_created.username}</b>
</span>
)}
<CardDescription className="pb-2 pt-2">
<div className="truncate-doubleline">{data.description}</div>
</CardDescription>
</CardHeader>
</div>
<CardFooter>
<div className="flex w-full items-center justify-between gap-2">
<div className="flex w-full flex-wrap items-end justify-between gap-2">
<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>
{data.liked_by_count && (
<div className="flex gap-0.5">
<ShadTooltip
content={authorized ? "Like" : "Please review your API key."}
>
<Button
disabled={loadingLike || !authorized}
variant="ghost"
size="xs"
className={
"whitespace-nowrap" +
(!authorized ? " cursor-not-allowed" : "")
}
onClick={() => {
handleLike();
}}
>
<IconComponent
name="Heart"
className={classNames(
"h-6 w-6 p-0.5",
liked_by_user
? "fill-destructive stroke-destructive"
: "",
!authorized ? " text-ring" : ""
)}
/>
</Button>
</ShadTooltip>
<ShadTooltip
content={
authorized
? "Install Locally"
: "Please review your API key."
}
>
<Button
variant="ghost"
size="xs"
className={
"whitespace-nowrap" +
(!authorized ? " cursor-not-allowed" : "")
}
onClick={() => {
if (loading || !authorized) {
return;
}
handleInstall();
}}
>
<IconComponent
name={loading ? "Loader2" : "Plus"}
className={classNames(
loading ? "h-5 w-5 animate-spin" : "h-6 w-6 p-0.5",
!authorized ? " text-ring" : ""
)}
/>
</Button>
</ShadTooltip>
</div>
)}
{button && button}
</div>
</div>
</CardFooter>
</Card> </Card>
); );
}; }

View file

@ -29,7 +29,7 @@ import {
sourceHandleType, sourceHandleType,
targetHandleType, targetHandleType,
} from "../types/flow"; } from "../types/flow";
import { FlowsContextType, TabsState } from "../types/tabs"; import { FlowsContextType, FlowsState } from "../types/tabs";
import { import {
addVersionToDuplicates, addVersionToDuplicates,
checkOldEdgesHandles, checkOldEdgesHandles,
@ -73,7 +73,7 @@ const FlowsContextInitialValue: FlowsContextType = {
lastCopiedSelection: null, lastCopiedSelection: null,
setLastCopiedSelection: (selection: any) => {}, setLastCopiedSelection: (selection: any) => {},
tabsState: {}, tabsState: {},
setTabsState: (state: TabsState) => {}, setTabsState: (state: FlowsState) => {},
getNodeId: (nodeType: string) => "", getNodeId: (nodeType: string) => "",
setTweak: (tweak: any) => {}, setTweak: (tweak: any) => {},
getTweak: [], getTweak: [],
@ -107,7 +107,7 @@ export function FlowsProvider({ children }: { children: ReactNode }) {
nodes: any; nodes: any;
edges: any; edges: any;
} | null>(null); } | null>(null);
const [tabsState, setTabsState] = useState<TabsState>({}); const [tabsState, setTabsState] = useState<FlowsState>({});
const [getTweak, setTweak] = useState<tweakType>([]); const [getTweak, setTweak] = useState<tweakType>([]);
useEffect(() => { useEffect(() => {
@ -698,7 +698,7 @@ export function FlowsProvider({ children }: { children: ReactNode }) {
` (${increment})`; ` (${increment})`;
} }
} }
return addFlow(true, createFlowComponent(component)); return addFlow(true, createFlowComponent(component, version));
} }
function deleteComponent(id: string, key: string) { function deleteComponent(id: string, key: string) {

View file

@ -1,5 +1,5 @@
import { useContext } from "react"; import { useContext } from "react";
import { CardComponent } from "../../../../components/cardComponent"; import CollectionCardComponent from "../../../../components/cardComponent";
import CardsWrapComponent from "../../../../components/cardsWrapComponent"; import CardsWrapComponent from "../../../../components/cardsWrapComponent";
import { alertContext } from "../../../../contexts/alertContext"; import { alertContext } from "../../../../contexts/alertContext";
import { FlowsContext } from "../../../../contexts/flowsContext"; import { FlowsContext } from "../../../../contexts/flowsContext";
@ -31,7 +31,7 @@ export default function ComponentsComponent() {
{flows {flows
.filter((flow) => flow.is_component) .filter((flow) => flow.is_component)
.map((flow, idx) => ( .map((flow, idx) => (
<CardComponent <CollectionCardComponent
key={idx} key={idx}
data={flow} data={flow}
onDelete={() => { onDelete={() => {

View file

@ -1,6 +1,6 @@
import { useContext } from "react"; import { useContext } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { CardComponent } from "../../../../components/cardComponent"; import CollectionCardComponent from "../../../../components/cardComponent";
import CardsWrapComponent from "../../../../components/cardsWrapComponent"; import CardsWrapComponent from "../../../../components/cardsWrapComponent";
import IconComponent from "../../../../components/genericIconComponent"; import IconComponent from "../../../../components/genericIconComponent";
import { Button } from "../../../../components/ui/button"; import { Button } from "../../../../components/ui/button";
@ -34,7 +34,7 @@ export default function FlowsComponent() {
.filter((flow) => !flow.is_component) .filter((flow) => !flow.is_component)
.reverse() .reverse()
.map((flow, idx) => ( .map((flow, idx) => (
<CardComponent <CollectionCardComponent
key={idx} key={idx}
data={flow} data={flow}
button={ button={

View file

@ -1,286 +0,0 @@
import { useContext, useState } from "react";
import { useNavigate } from "react-router-dom";
import ShadTooltip from "../../../components/ShadTooltipComponent";
import IconComponent from "../../../components/genericIconComponent";
import { Badge } from "../../../components/ui/badge";
import { Button } from "../../../components/ui/button";
import {
Card,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "../../../components/ui/card";
import { alertContext } from "../../../contexts/alertContext";
import { FlowsContext } from "../../../contexts/flowsContext";
import { StoreContext } from "../../../contexts/storeContext";
import { getComponent, postLikeComponent } from "../../../controllers/API";
import { storeComponent } from "../../../types/store";
import cloneFLowWithParent from "../../../utils/storeUtils";
import { gradients } from "../../../utils/styleUtils";
import { classNames } from "../../../utils/utils";
export const MarketCardComponent = ({
data,
authorized = true,
disabled = false,
}: {
data: storeComponent;
authorized?: boolean;
disabled?: boolean;
}) => {
const [loading, setLoading] = useState(false);
const { addFlow } = useContext(FlowsContext);
const [loadingLike, setLoadingLike] = useState(false);
const { setSuccessData, setErrorData } = useContext(alertContext);
const { setValidApiKey } = useContext(StoreContext);
const [liked_by_user, setLiked_by_user] = useState(data.liked_by_user);
const [likes_count, setLikes_count] = useState(data.liked_by_count ?? 0);
const name = data.is_component ? "Component" : "Flow";
const navigate = useNavigate();
function handleInstall() {
setLoading(true);
getComponent(data.id).then((res) => {
const newFlow = cloneFLowWithParent(res, res.id, data.is_component);
addFlow(true, newFlow).then((id) => {
setSuccessData({ title: `${name} Installed` });
setLoading(false);
if (!data.is_component) navigate("/flow/" + id);
});
});
}
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) => prev + 1);
} else {
setLikes_count((prev) => prev - 1);
}
console.log(data.id);
postLikeComponent(data.id)
.then((response) => {
setLoadingLike(false);
setLikes_count(response.likes_count);
setLiked_by_user(response.liked_by_user);
})
.catch((error) => {
setLoadingLike(false);
setLikes_count(tempNum);
setLiked_by_user(temp);
if (error.response.status === 403 || error.response.status === 401) {
setValidApiKey(false);
} else {
console.error(error);
setErrorData({
title: `Error liking ${name}.`,
list: [error["response"]["data"]["detail"]],
});
}
});
}
}
const totalComponentsMetadata = () => {
return data?.metadata ? data.metadata["total"] : 0;
};
return (
<Card
className={classNames(
"group relative flex flex-col justify-between overflow-hidden transition-all hover:shadow-md",
disabled ? "pointer-events-none opacity-50" : ""
)}
>
<div>
<CardHeader>
<div>
<CardTitle className="flex w-full items-center justify-between gap-3 text-xl">
<div
className={classNames(
"flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-primary",
gradients[
parseInt(data.id.slice(0, 12), 16) % gradients.length
]
)}
>
<div
className={classNames(
data.is_component ? "h-7 w-7 rounded-full bg-muted" : "",
"flex items-center justify-center"
)}
>
{data.is_component ? (
<svg className="h-5 w-5" viewBox="0 0 24 24">
<defs>
<linearGradient
id={data.id}
x1="0%"
y1="0%"
x2="100%"
y2="100%"
className={
gradients[
parseInt(data.id.slice(0, 12), 16) %
gradients.length
]
}
>
<stop
offset="0%"
stopColor="var(--tw-gradient-from)"
/>
<stop
offset="100%"
stopColor="var(--tw-gradient-to)"
/>
</linearGradient>
</defs>
<IconComponent
className={classNames(
"h-4 w-4",
gradients[
parseInt(data.id.slice(0, 12), 16) %
gradients.length
]
)}
stroke={`url(#${data.id})`}
name="ToyBrick"
/>
</svg>
) : (
<IconComponent
className="h-4 w-4 text-background"
name="Network"
/>
)}
</div>
</div>
<ShadTooltip content={data.name}>
<div className="w-full truncate">{data.name}</div>
</ShadTooltip>
<div className="flex gap-3">
{!data.is_component && (
<ShadTooltip content="Components">
<span className="flex items-center gap-1.5 text-xs text-muted-foreground">
<IconComponent name="ToyBrick" className="h-4 w-4" />
{totalComponentsMetadata()}
</span>
</ShadTooltip>
)}
<ShadTooltip content="Likes">
<span className="flex items-center gap-1.5 text-xs text-muted-foreground">
<IconComponent
name="Heart"
className={classNames("h-4 w-4 ")}
/>
{likes_count ?? 0}
</span>
</ShadTooltip>
<ShadTooltip content="Downloads">
<span className="flex items-center gap-1.5 text-xs text-muted-foreground">
<IconComponent name="DownloadCloud" className="h-4 w-4" />
{data.downloads_count}
</span>
</ShadTooltip>
</div>
</CardTitle>
</div>
{data.user_created.username && (
<span className="text-xs text-primary">
by <b>{data.user_created.username}</b>
</span>
)}
<CardDescription className="pb-2 pt-2">
<div className="truncate-doubleline">{data.description}</div>
</CardDescription>
</CardHeader>
</div>
<CardFooter>
<div className="flex w-full items-center justify-between gap-2">
<div className="flex w-full flex-wrap items-end justify-between gap-2">
<div className="flex w-full flex-1 flex-wrap gap-2">
{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 gap-0.5">
<ShadTooltip
content={authorized ? "Like" : "Please review your API key."}
>
<Button
disabled={loadingLike || !authorized}
variant="ghost"
size="xs"
className={
"whitespace-nowrap" +
(!authorized ? " cursor-not-allowed" : "")
}
onClick={() => {
handleLike();
}}
>
<IconComponent
name="Heart"
className={classNames(
"h-6 w-6 p-0.5",
liked_by_user
? "fill-destructive stroke-destructive"
: "",
!authorized ? " text-ring" : ""
)}
/>
</Button>
</ShadTooltip>
<ShadTooltip
content={
authorized ? "Install Locally" : "Please review your API key."
}
>
<Button
variant="ghost"
size="xs"
className={
"whitespace-nowrap" +
(!authorized ? " cursor-not-allowed" : "")
}
onClick={() => {
if (loading || !authorized) {
return;
}
handleInstall();
}}
>
<IconComponent
name={loading ? "Loader2" : "Plus"}
className={classNames(
loading ? "h-5 w-5 animate-spin" : "h-6 w-6 p-0.5",
!authorized ? " text-ring" : ""
)}
/>
</Button>
</ShadTooltip>
</div>
</div>
</div>
</CardFooter>
</Card>
);
};

View file

@ -1,6 +1,7 @@
import { useContext, useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import PaginatorComponent from "../../components/PaginatorComponent"; import PaginatorComponent from "../../components/PaginatorComponent";
import ShadTooltip from "../../components/ShadTooltipComponent"; import ShadTooltip from "../../components/ShadTooltipComponent";
import CollectionCardComponent from "../../components/cardComponent";
import IconComponent from "../../components/genericIconComponent"; import IconComponent from "../../components/genericIconComponent";
import Header from "../../components/headerComponent"; import Header from "../../components/headerComponent";
import { SkeletonCardComponent } from "../../components/skeletonCardComponent"; import { SkeletonCardComponent } from "../../components/skeletonCardComponent";
@ -21,7 +22,6 @@ import { getStoreComponents, getStoreTags } from "../../controllers/API";
import StoreApiKeyModal from "../../modals/StoreApiKeyModal"; import StoreApiKeyModal from "../../modals/StoreApiKeyModal";
import { storeComponent } from "../../types/store"; import { storeComponent } from "../../types/store";
import { cn } from "../../utils/utils"; import { cn } from "../../utils/utils";
import { MarketCardComponent } from "./components/market-card";
export default function StorePage(): JSX.Element { export default function StorePage(): JSX.Element {
const { validApiKey, setValidApiKey, hasApiKey, loadingApiKey } = const { validApiKey, setValidApiKey, hasApiKey, loadingApiKey } =
useContext(StoreContext); useContext(StoreContext);
@ -326,7 +326,7 @@ export default function StorePage(): JSX.Element {
searchData.map((item) => { searchData.map((item) => {
return ( return (
<> <>
<MarketCardComponent <CollectionCardComponent
key={item.id} key={item.id}
data={item} data={item}
authorized={validApiKey} authorized={validApiKey}

View file

@ -353,34 +353,6 @@
@apply alert-font-size text-success-foreground word-break-break-word; @apply alert-font-size text-success-foreground word-break-break-word;
} }
.card-component-title-display {
@apply round-button-div flex-max-width;
}
.card-component-image {
@apply flex h-7 w-7 items-center justify-center rounded-full text-2xl;
}
.card-component-title-size {
@apply w-full flex-1 truncate-doubleline word-break-break-word;
}
.card-component-delete-button {
@apply flex self-start;
}
.card-component-delete-icon {
@apply h-4 w-4 text-primary opacity-0 transition-all group-hover:opacity-100;
}
.card-component-desc {
@apply pb-2 pt-2;
}
.card-component-desc-text {
@apply truncate-doubleline;
}
.card-component-footer-arrangement {
@apply flex-max-width items-end justify-between gap-2;
}
.card-component-footer {
@apply flex flex-wrap gap-2;
}
.unused-side-bar-aside { .unused-side-bar-aside {
@apply flex flex-shrink-0 flex-col overflow-hidden border-r transition-all duration-500; @apply flex flex-shrink-0 flex-col overflow-hidden border-r transition-all duration-500;
} }

View file

@ -7,7 +7,7 @@ export type FlowType = {
data: ReactFlowJsonObject | null; data: ReactFlowJsonObject | null;
description: string; description: string;
style?: FlowStyleType; style?: FlowStyleType;
is_component?: boolean; is_component: boolean;
parent?: string; parent?: string;
date_created?: string; date_created?: string;
last_tested_version?: string; last_tested_version?: string;

View file

@ -1,14 +1,14 @@
export type storeComponent = { export type storeComponent = {
id: string; id: string;
is_component: boolean; is_component: boolean;
tags: { id: string; name: string }[]; tags?: { id: string; name: string }[];
metadata?: {}; metadata?: {};
downloads_count: number; downloads_count?: number;
name: string; name: string;
description: string; description: string;
liked_by_count: number; liked_by_count?: number;
liked_by_user?: boolean; liked_by_user?: boolean;
user_created: { username: string }; user_created?: { username: string };
}; };
export type StoreComponentResponse = { export type StoreComponentResponse = {

View file

@ -8,6 +8,7 @@ import {
ReactFlowJsonObject, ReactFlowJsonObject,
XYPosition, XYPosition,
} from "reactflow"; } from "reactflow";
import ShortUniqueId from "short-unique-id";
import { specialCharsRegex } from "../constants/constants"; import { specialCharsRegex } from "../constants/constants";
import { APITemplateType, TemplateVariableType } from "../types/api"; import { APITemplateType, TemplateVariableType } from "../types/api";
import { import {
@ -519,6 +520,7 @@ export function generateFlow(
const newFlow: FlowType = { const newFlow: FlowType = {
data: newFlowData, data: newFlowData,
is_component: false,
name: name, name: name,
description: "", description: "",
//generating local id instead of using the id from the server, can change in the future //generating local id instead of using the id from the server, can change in the future