Marketplace and API key modal inserted
This commit is contained in:
parent
c5ef201755
commit
439c331939
8 changed files with 382 additions and 0 deletions
|
|
@ -64,6 +64,16 @@ export default function Header(): JSX.Element {
|
||||||
<div className="flex-1">Community Examples</div>
|
<div className="flex-1">Community Examples</div>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</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>
|
||||||
<div className="header-end-division">
|
<div className="header-end-division">
|
||||||
<div className="header-end-display">
|
<div className="header-end-display">
|
||||||
|
|
|
||||||
92
src/frontend/src/modals/StoreApiKeyModal/index.tsx
Normal file
92
src/frontend/src/modals/StoreApiKeyModal/index.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
94
src/frontend/src/pages/StorePage/components/market-card.tsx
Normal file
94
src/frontend/src/pages/StorePage/components/market-card.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
157
src/frontend/src/pages/StorePage/index.tsx
Normal file
157
src/frontend/src/pages/StorePage/index.tsx
Normal 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ import CommunityPage from "./pages/CommunityPage";
|
||||||
import FlowPage from "./pages/FlowPage";
|
import FlowPage from "./pages/FlowPage";
|
||||||
import HomePage from "./pages/MainPage";
|
import HomePage from "./pages/MainPage";
|
||||||
import ProfileSettingsPage from "./pages/ProfileSettingsPage";
|
import ProfileSettingsPage from "./pages/ProfileSettingsPage";
|
||||||
|
import StorePage from "./pages/StorePage";
|
||||||
import ViewPage from "./pages/ViewPage";
|
import ViewPage from "./pages/ViewPage";
|
||||||
import DeleteAccountPage from "./pages/deleteAccountPage";
|
import DeleteAccountPage from "./pages/deleteAccountPage";
|
||||||
import LoginPage from "./pages/loginPage";
|
import LoginPage from "./pages/loginPage";
|
||||||
|
|
@ -34,6 +35,14 @@ const Router = () => {
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path="/store"
|
||||||
|
element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<StorePage />
|
||||||
|
</ProtectedRoute>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Route path="/flow/:id/">
|
<Route path="/flow/:id/">
|
||||||
<Route
|
<Route
|
||||||
path=""
|
path=""
|
||||||
|
|
|
||||||
|
|
@ -326,6 +326,11 @@ export type ApiKeyType = {
|
||||||
onCloseModal: () => void;
|
onCloseModal: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type StoreApiKeyType = {
|
||||||
|
children: ReactElement;
|
||||||
|
onCloseModal: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
export type ApiKeyInputType = {
|
export type ApiKeyInputType = {
|
||||||
apikeyname: string;
|
apikeyname: string;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
13
src/frontend/src/types/store/index.ts
Normal file
13
src/frontend/src/types/store/index.ts
Normal 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;
|
||||||
|
};
|
||||||
|
|
@ -69,6 +69,7 @@ import {
|
||||||
Shield,
|
Shield,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
Square,
|
Square,
|
||||||
|
Store,
|
||||||
SunIcon,
|
SunIcon,
|
||||||
TerminalSquare,
|
TerminalSquare,
|
||||||
Trash2,
|
Trash2,
|
||||||
|
|
@ -285,6 +286,7 @@ export const nodeIconsLucide: iconsType = {
|
||||||
Clipboard,
|
Clipboard,
|
||||||
Code2,
|
Code2,
|
||||||
Variable,
|
Variable,
|
||||||
|
Store,
|
||||||
Download,
|
Download,
|
||||||
Eraser,
|
Eraser,
|
||||||
Lock,
|
Lock,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue