Marketplace and API key modal inserted

This commit is contained in:
Lucas Oliveira 2023-10-18 17:34:33 -03:00
commit 439c331939
8 changed files with 382 additions and 0 deletions

View file

@ -64,6 +64,16 @@ export default function Header(): JSX.Element {
<div className="flex-1">Community Examples</div>
</Button>
</Link>
<Link to="/store">
<Button
className="gap-2"
variant={location.pathname === "/store" ? "primary" : "secondary"}
size="sm"
>
<IconComponent name="Store" className="h-4 w-4" />
<div className="flex-1">Store</div>
</Button>
</Link>
</div>
<div className="header-end-division">
<div className="header-end-display">

View file

@ -0,0 +1,92 @@
import * as Form from "@radix-ui/react-form";
import { useContext, useEffect, useRef, useState } from "react";
import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input";
import { CONTROL_NEW_API_KEY } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import {
ApiKeyInputType,
StoreApiKeyType,
inputHandlerEventType,
} from "../../types/components";
import BaseModal from "../baseModal";
export default function StoreApiKeyModal({
children,
onCloseModal,
}: StoreApiKeyType) {
const [open, setOpen] = useState(false);
const [apiKeyValue, setApiKeyValue] = useState("");
const [inputState, setInputState] =
useState<ApiKeyInputType>(CONTROL_NEW_API_KEY);
const { setSuccessData } = useContext(alertContext);
const inputRef = useRef<HTMLInputElement | null>(null);
function handleInput({
target: { name, value },
}: inputHandlerEventType): void {
setInputState((prev) => ({ ...prev, [name]: value }));
}
useEffect(() => {
if (open) {
resetForm();
} else {
onCloseModal();
}
}, [open]);
function resetForm() {
setApiKeyValue("");
}
return (
<BaseModal size="small-h-full" open={open} setOpen={setOpen}>
<BaseModal.Trigger>{children}</BaseModal.Trigger>
<BaseModal.Header description={"Insert your Langflow API key."}>
<span className="pr-2">API Key</span>
<IconComponent
name="Key"
className="h-6 w-6 pl-1 text-foreground"
aria-hidden="true"
/>
</BaseModal.Header>
<BaseModal.Content>
<Form.Root
onSubmit={(event) => {
event.preventDefault();
}}
>
<div className="grid gap-5">
<Form.Field name="username">
<Form.Control asChild>
<Input
onChange={({ target: { value } }) => {
handleInput({ target: { name: "apikey", value } });
}}
placeholder="Insert your API Key"
/>
</Form.Control>
</Form.Field>
</div>
<div className="float-right">
<Button
className="mr-3"
variant="outline"
onClick={() => {
setOpen(false);
}}
>
Cancel
</Button>
<Form.Submit asChild>
<Button className="mt-8">Save</Button>
</Form.Submit>
</div>
</Form.Root>
</BaseModal.Content>
</BaseModal>
);
}

View file

@ -0,0 +1,94 @@
import { Link, ToyBrick } from "lucide-react";
import { Badge } from "../../../components/ui/badge";
import {
Card,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "../../../components/ui/card";
import { FlowComponent } from "../../../types/store";
export const MarketCardComponent = ({
data,
onAdd,
}: {
data: FlowComponent;
onAdd: () => void;
}) => {
return (
<Card className="group relative flex cursor-pointer flex-col justify-between overflow-hidden transition-all hover:shadow-md">
<div>
<CardHeader>
{/*
<div className="mb-2 flex flex-wrap gap-1">
{data.tags.map((tag) => (
<Badge
size="sm"
className={
tagGradients[parseInt(tag, 35) % tagGradients.length] +
" " +
tagText[parseInt(tag, 35) % tagText.length]
}
>
{tag}
</Badge>
))}
</div> */}
<div>
<CardTitle className="flex w-full items-center justify-between gap-3 text-xl">
<span className="flex w-full items-center gap-2 word-break-break-word">
{data.name}
</span>
<Badge size="sm" variant="gray">
Free
</Badge>
</CardTitle>
{/* <span className="inline-flex items-center text-sm">
<img
className="mr-2 h-4 w-4 rounded-full"
src={data.image}
/>
{data.creator.name}
</span>
<span className="flex text-xs items-center gap-2 text-ring">
<Download className="h-3 w-3" />
{nFormatter(data.downloads, 2)}
</span> */}
</div>
<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 items-center gap-3">
<Badge size="md" variant="outline">
chain
<Link className="ml-1.5 w-3 text-green-700" />
</Badge>
<span className="flex items-center gap-1.5 text-xs text-foreground">
<ToyBrick className="h-4 w-4" />
123
</span>
</div>
{/* {data.isChat ? (
<Button size="sm" variant="outline">
<Plus className="h-4 mr-2" />
Add
</Button>
) : (
<Button size="sm" variant="success">
<Check className="h-4 mr-2" />
Added
</Button>
)} */}
</div>
</div>
</CardFooter>
</Card>
);
};

View file

@ -0,0 +1,157 @@
import { cloneDeep } from "lodash";
import { Link, Search } from "lucide-react";
import { useContext, useEffect, useState } from "react";
import IconComponent from "../../components/genericIconComponent";
import Header from "../../components/headerComponent";
import { Badge } from "../../components/ui/badge";
import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "../../components/ui/select";
import { Switch } from "../../components/ui/switch";
import { TabsContext } from "../../contexts/tabsContext";
import StoreApiKeyModal from "../../modals/StoreApiKeyModal";
import { FlowComponent } from "../../types/store";
import { cn } from "../../utils/utils";
import { MarketCardComponent } from "./components/market-card";
export default function StorePage(): JSX.Element {
const { flows, setTabId, downloadFlows, uploadFlows, addFlow } =
useContext(TabsContext);
// set null id
useEffect(() => {
setTabId("");
}, []);
const [data, setData] = useState<FlowComponent[]>([]);
const [dataSelect, setDataSelect] = useState<FlowComponent[]>([]);
const [loading, setLoading] = useState(false);
const [filteredCategories, setFilteredCategories] = useState(new Set());
const [inputText, setInputText] = useState<string>("");
const [searchData, setSearchData] = useState(data);
useEffect(() => {
setLoading(false);
/* getComponents()
.then((res) => {
setData(res);
setSearchData(res);
setDataSelect(res);
setLoading(false);
})
.catch((err) => {
setLoading(false);
}); */
}, []);
return (
<>
<Header />
<div className="community-page-arrangement">
<div className="community-page-nav-arrangement">
<span className="community-page-nav-title">
<IconComponent name="Users2" className="w-6" />
Langflow Store
</span>
<div className="community-page-nav-button">
<StoreApiKeyModal onCloseModal={() => {}}>
<Button variant="primary">
<IconComponent name="Key" className="main-page-nav-button" />
API Key
</Button>
</StoreApiKeyModal>
</div>
</div>
<span className="community-page-description-text">
Search flows and components from the community.
</span>
{!loading && (
<div className="flex w-full flex-col gap-4 p-4">
<div className="flex items-center justify-center gap-4">
<div className="flex w-[13%] items-center justify-center gap-3 text-sm">
Installed Only <Switch />
</div>
<div className="relative h-12 w-[35%]">
<Input
placeholder="Search Flows and Components"
className="absolute h-12 px-5"
onChange={(e) => {}}
value={inputText}
/>
<Search className="absolute bottom-0 right-4 top-0 my-auto h-6 stroke-1 text-muted-foreground " />
</div>
<div className="flex w-[13%] items-center justify-center gap-3 text-sm">
<Select
onValueChange={(value) => {
const filter = value === "Flow" ? false : true;
const search = data.filter(
(f) => f.is_component === filter
);
value === "" ? setSearchData(data) : setSearchData(search);
}}
>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Flows" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Flow">Flows</SelectItem>
<SelectItem value="Component">Components</SelectItem>
<SelectItem value="">Both</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="flex items-center justify-center gap-4">
{Array.from(new Set(searchData.map((i) => i.is_component))).map(
(i, idx) => (
<Badge
onClick={() => {
filteredCategories.has(i)
? setFilteredCategories((old) => {
let newFilteredCategories = cloneDeep(old);
newFilteredCategories.delete(i);
return newFilteredCategories;
})
: setFilteredCategories((old) => {
let newFilteredCategories = cloneDeep(old);
newFilteredCategories.add(i);
return newFilteredCategories;
});
}}
variant="gray"
size="md"
className={cn(
"cursor-pointer border-none",
filteredCategories.has(i)
? "bg-beta-foreground text-background hover:bg-beta-foreground"
: ""
)}
>
<Link className="mr-1.5 w-4" />
{i}
</Badge>
)
)}
</div>
<div className="mt-6 grid w-full gap-4 md:grid-cols-2 lg:grid-cols-3">
{searchData
.filter(
(f) =>
Array.from(filteredCategories).length === 0 ||
filteredCategories.has(f.is_component)
)
.map((item, idx) => (
<MarketCardComponent key={idx} data={item} onAdd={() => {}} />
))}
</div>
</div>
)}
</div>
</>
);
}

View file

@ -10,6 +10,7 @@ import CommunityPage from "./pages/CommunityPage";
import FlowPage from "./pages/FlowPage";
import HomePage from "./pages/MainPage";
import ProfileSettingsPage from "./pages/ProfileSettingsPage";
import StorePage from "./pages/StorePage";
import ViewPage from "./pages/ViewPage";
import DeleteAccountPage from "./pages/deleteAccountPage";
import LoginPage from "./pages/loginPage";
@ -34,6 +35,14 @@ const Router = () => {
</ProtectedRoute>
}
/>
<Route
path="/store"
element={
<ProtectedRoute>
<StorePage />
</ProtectedRoute>
}
/>
<Route path="/flow/:id/">
<Route
path=""

View file

@ -326,6 +326,11 @@ export type ApiKeyType = {
onCloseModal: () => void;
};
export type StoreApiKeyType = {
children: ReactElement;
onCloseModal: () => void;
};
export type ApiKeyInputType = {
apikeyname: string;
};

View file

@ -0,0 +1,13 @@
export type FlowComponent = {
id: string;
status: string;
sort: null | any;
user_created: string;
date_created: string;
user_updated: string;
date_updated: string;
is_component: boolean;
name: string;
description: string;
data: Object;
};

View file

@ -69,6 +69,7 @@ import {
Shield,
Sparkles,
Square,
Store,
SunIcon,
TerminalSquare,
Trash2,
@ -285,6 +286,7 @@ export const nodeIconsLucide: iconsType = {
Clipboard,
Code2,
Variable,
Store,
Download,
Eraser,
Lock,