diff --git a/src/frontend/src/components/headerComponent/index.tsx b/src/frontend/src/components/headerComponent/index.tsx index 5827588e1..c66716069 100644 --- a/src/frontend/src/components/headerComponent/index.tsx +++ b/src/frontend/src/components/headerComponent/index.tsx @@ -64,6 +64,16 @@ export default function Header(): JSX.Element {
Community Examples
+ + +
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,