diff --git a/src/frontend/src/modals/StoreApiKeyModal/index.tsx b/src/frontend/src/modals/StoreApiKeyModal/index.tsx
new file mode 100644
index 000000000..11d532ee2
--- /dev/null
+++ b/src/frontend/src/modals/StoreApiKeyModal/index.tsx
@@ -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
(CONTROL_NEW_API_KEY);
+ const { setSuccessData } = useContext(alertContext);
+ const inputRef = useRef(null);
+
+ function handleInput({
+ target: { name, value },
+ }: inputHandlerEventType): void {
+ setInputState((prev) => ({ ...prev, [name]: value }));
+ }
+
+ useEffect(() => {
+ if (open) {
+ resetForm();
+ } else {
+ onCloseModal();
+ }
+ }, [open]);
+
+ function resetForm() {
+ setApiKeyValue("");
+ }
+
+ return (
+
+ {children}
+
+ API Key
+
+
+
+ {
+ event.preventDefault();
+ }}
+ >
+
+
+
+ {
+ handleInput({ target: { name: "apikey", value } });
+ }}
+ placeholder="Insert your API Key"
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/frontend/src/pages/StorePage/components/market-card.tsx b/src/frontend/src/pages/StorePage/components/market-card.tsx
new file mode 100644
index 000000000..b11389f84
--- /dev/null
+++ b/src/frontend/src/pages/StorePage/components/market-card.tsx
@@ -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 (
+
+
+
+ {/*
+
+ {data.tags.map((tag) => (
+
+ {tag}
+
+ ))}
+
*/}
+
+
+
+ {data.name}
+
+
+ Free
+
+
+ {/*
+
+ {data.creator.name}
+
+
+
+ {nFormatter(data.downloads, 2)}
+ */}
+
+
+ {data.description}
+
+
+
+
+
+
+
+
+
+ chain
+
+
+
+
+ 123
+
+
+ {/* {data.isChat ? (
+
+ ) : (
+
+ )} */}
+
+
+
+
+ );
+};
diff --git a/src/frontend/src/pages/StorePage/index.tsx b/src/frontend/src/pages/StorePage/index.tsx
new file mode 100644
index 000000000..460258ada
--- /dev/null
+++ b/src/frontend/src/pages/StorePage/index.tsx
@@ -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([]);
+ const [dataSelect, setDataSelect] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [filteredCategories, setFilteredCategories] = useState(new Set());
+ const [inputText, setInputText] = useState("");
+ const [searchData, setSearchData] = useState(data);
+
+ useEffect(() => {
+ setLoading(false);
+ /* getComponents()
+ .then((res) => {
+ setData(res);
+ setSearchData(res);
+ setDataSelect(res);
+ setLoading(false);
+ })
+ .catch((err) => {
+ setLoading(false);
+ }); */
+ }, []);
+
+ return (
+ <>
+
+
+
+
+
+
+ Langflow Store
+
+
+ {}}>
+
+
+
+
+
+ Search flows and components from the community.
+
+ {!loading && (
+
+
+
+ Installed Only
+
+
+ {}}
+ value={inputText}
+ />
+
+
+
+
+
+
+
+ {Array.from(new Set(searchData.map((i) => i.is_component))).map(
+ (i, idx) => (
+ {
+ 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"
+ : ""
+ )}
+ >
+
+ {i}
+
+ )
+ )}
+
+
+ {searchData
+ .filter(
+ (f) =>
+ Array.from(filteredCategories).length === 0 ||
+ filteredCategories.has(f.is_component)
+ )
+ .map((item, idx) => (
+ {}} />
+ ))}
+
+
+ )}
+
+ >
+ );
+}
diff --git a/src/frontend/src/routes.tsx b/src/frontend/src/routes.tsx
index e9e6f1858..75aa2d436 100644
--- a/src/frontend/src/routes.tsx
+++ b/src/frontend/src/routes.tsx
@@ -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 = () => {
}
/>
+
+
+
+ }
+ />
void;
};
+export type StoreApiKeyType = {
+ children: ReactElement;
+ onCloseModal: () => void;
+};
+
export type ApiKeyInputType = {
apikeyname: string;
};
diff --git a/src/frontend/src/types/store/index.ts b/src/frontend/src/types/store/index.ts
new file mode 100644
index 000000000..63f29b4e6
--- /dev/null
+++ b/src/frontend/src/types/store/index.ts
@@ -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;
+};
diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts
index 8704515f8..d5f830285 100644
--- a/src/frontend/src/utils/styleUtils.ts
+++ b/src/frontend/src/utils/styleUtils.ts
@@ -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,