Refactored API Key handling

This commit is contained in:
Lucas Oliveira 2023-11-16 17:16:50 -03:00
commit dcde9b7ae3
8 changed files with 69 additions and 231 deletions

View file

@ -561,10 +561,6 @@ export const CONTROL_NEW_USER = {
is_superuser: false, is_superuser: false,
}; };
export const CONTROL_NEW_API_KEY = {
apikeyname: "",
};
export const tabsCode = []; export const tabsCode = [];
export function tabsArray(codes: string[], method: number) { export function tabsArray(codes: string[], method: number) {

View file

@ -1,34 +1,24 @@
import { createContext, useEffect, useState } from "react"; import { createContext, useEffect, useState } from "react";
import { import { checkHasApiKey, checkHasStore } from "../controllers/API";
checkHasApiKey,
checkHasStore,
getStoreComponents,
} from "../controllers/API";
import { storeContextType } from "../types/contexts/store"; import { storeContextType } from "../types/contexts/store";
//store context to share user components and update them //store context to share user components and update them
const initialValue = { const initialValue = {
savedFlows: new Set<string>(),
setSavedFlows: () => {},
hasStore: true, hasStore: true,
setHasStore: () => {}, setHasStore: () => {},
validApiKey: false,
setValidApiKey: () => {},
hasApiKey: false, hasApiKey: false,
setHasApiKey: () => {}, setHasApiKey: () => {},
getSavedComponents: () => {},
errorApiKey: false,
loadingSaved: false,
}; };
export const StoreContext = createContext<storeContextType>(initialValue); export const StoreContext = createContext<storeContextType>(initialValue);
export function StoreProvider({ children }) { export function StoreProvider({ children }) {
const [savedFlows, setSavedFlows] = useState<Set<string>>(new Set());
const [hasStore, setHasStore] = useState(true); const [hasStore, setHasStore] = useState(true);
const [hasApiKey, setHasApiKey] = useState(false); const [hasApiKey, setHasApiKey] = useState(false);
const [validApiKey, setValidApiKey] = useState(false);
const [storeChecked, setStoreChecked] = useState(false); const [storeChecked, setStoreChecked] = useState(false);
const [loadingSaved, setLoadingSaved] = useState(false);
const [errorApiKey, setErrorApiKey] = useState(false);
useEffect(() => { useEffect(() => {
const fetchStoreData = async () => { const fetchStoreData = async () => {
@ -43,36 +33,8 @@ export function StoreProvider({ children }) {
}; };
fetchStoreData(); fetchStoreData();
getSavedComponents();
}, []); }, []);
function getSavedComponents() {
setLoadingSaved(true);
getStoreComponents({
sort: "-count(liked_by)",
filterByUser: true,
})
.then((data) => {
if (data?.authorized === false) {
setErrorApiKey(true);
setSavedFlows(new Set<string>());
} else {
let savedIds = new Set<string>();
let results = data?.results ?? [];
results.forEach((flow) => {
savedIds.add(flow.id);
});
setSavedFlows(savedIds);
setErrorApiKey(false);
setLoadingSaved(false);
}
})
.catch((err) => {
setSavedFlows(new Set<string>());
setErrorApiKey(true);
});
}
useEffect(() => { useEffect(() => {
const fetchStoreData = async () => { const fetchStoreData = async () => {
try { try {
@ -80,26 +42,26 @@ export function StoreProvider({ children }) {
const res = await checkHasApiKey(); const res = await checkHasApiKey();
console.log(res); console.log(res);
setHasApiKey(res?.has_api_key ?? false); setHasApiKey(res?.has_api_key ?? false);
if (!res?.has_api_key) {
setValidApiKey(false);
}
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }
}; };
fetchStoreData(); fetchStoreData();
}, [storeChecked]); }, [storeChecked, validApiKey]);
return ( return (
<StoreContext.Provider <StoreContext.Provider
value={{ value={{
savedFlows,
setSavedFlows,
hasStore, hasStore,
setHasStore, setHasStore,
hasApiKey, hasApiKey,
setHasApiKey, setHasApiKey,
getSavedComponents, validApiKey,
errorApiKey, setValidApiKey,
loadingSaved,
}} }}
> >
{children} {children}

View file

@ -3,14 +3,9 @@ import { useContext, useEffect, useRef, useState } from "react";
import IconComponent from "../../components/genericIconComponent"; import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button"; import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input"; import { Input } from "../../components/ui/input";
import { CONTROL_NEW_API_KEY } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext"; import { alertContext } from "../../contexts/alertContext";
import { createApiKey } from "../../controllers/API"; import { createApiKey } from "../../controllers/API";
import { import { ApiKeyType } from "../../types/components";
ApiKeyInputType,
ApiKeyType,
inputHandlerEventType,
} from "../../types/components";
import { nodeIconsLucide } from "../../utils/styleUtils"; import { nodeIconsLucide } from "../../utils/styleUtils";
import BaseModal from "../baseModal"; import BaseModal from "../baseModal";
@ -27,19 +22,11 @@ export default function SecretKeyModal({
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [apiKeyName, setApiKeyName] = useState(data?.apikeyname ?? ""); const [apiKeyName, setApiKeyName] = useState(data?.apikeyname ?? "");
const [apiKeyValue, setApiKeyValue] = useState(""); const [apiKeyValue, setApiKeyValue] = useState("");
const [inputState, setInputState] =
useState<ApiKeyInputType>(CONTROL_NEW_API_KEY);
const [renderKey, setRenderKey] = useState(false); const [renderKey, setRenderKey] = useState(false);
const [textCopied, setTextCopied] = useState(true); const [textCopied, setTextCopied] = useState(true);
const { setSuccessData } = useContext(alertContext); const { setSuccessData } = useContext(alertContext);
const inputRef = useRef<HTMLInputElement | null>(null); const inputRef = useRef<HTMLInputElement | null>(null);
function handleInput({
target: { name, value },
}: inputHandlerEventType): void {
setInputState((prev) => ({ ...prev, [name]: value }));
}
useEffect(() => { useEffect(() => {
if (open) { if (open) {
setRenderKey(false); setRenderKey(false);
@ -101,14 +88,7 @@ export default function SecretKeyModal({
</span> </span>
<div className="flex pt-3"> <div className="flex pt-3">
<div className="w-full"> <div className="w-full">
<Input <Input ref={inputRef} readOnly={true} value={apiKeyValue} />
ref={inputRef}
onChange={(event) => {
setApiKeyValue(event.target.value);
}}
readOnly={true}
value={apiKeyValue}
/>
</div> </div>
<div> <div>
@ -153,7 +133,6 @@ export default function SecretKeyModal({
<Form.Control asChild> <Form.Control asChild>
<input <input
onChange={({ target: { value } }) => { onChange={({ target: { value } }) => {
handleInput({ target: { name: "apikeyname", value } });
setApiKeyName(value); setApiKeyName(value);
}} }}
value={apiKeyName} value={apiKeyName}

View file

@ -1,57 +1,31 @@
import * as Form from "@radix-ui/react-form"; import * as Form from "@radix-ui/react-form";
import { useContext, useEffect, useRef, useState } from "react"; import { useContext, useState } from "react";
import IconComponent from "../../components/genericIconComponent"; import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button"; import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input"; import { Input } from "../../components/ui/input";
import { CONTROL_NEW_API_KEY } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext"; import { alertContext } from "../../contexts/alertContext";
import { AuthContext } from "../../contexts/authContext"; import { AuthContext } from "../../contexts/authContext";
import { StoreContext } from "../../contexts/storeContext"; import { StoreContext } from "../../contexts/storeContext";
import { addApiKeyStore } from "../../controllers/API"; import { addApiKeyStore } from "../../controllers/API";
import { import { StoreApiKeyType } from "../../types/components";
ApiKeyInputType,
StoreApiKeyType,
inputHandlerEventType,
} from "../../types/components";
import BaseModal from "../baseModal"; import BaseModal from "../baseModal";
export default function StoreApiKeyModal({ export default function StoreApiKeyModal({ children }: StoreApiKeyType) {
children,
onCloseModal,
}: StoreApiKeyType) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [inputState, setInputState] =
useState<ApiKeyInputType>(CONTROL_NEW_API_KEY);
const { setSuccessData, setErrorData } = useContext(alertContext); const { setSuccessData, setErrorData } = useContext(alertContext);
const inputRef = useRef<HTMLInputElement | null>(null);
const { storeApiKey } = useContext(AuthContext); const { storeApiKey } = useContext(AuthContext);
const { hasApiKey } = useContext(StoreContext); const { hasApiKey, setHasApiKey, validApiKey } = useContext(StoreContext);
const [apiKeyValue, setApiKeyValue] = useState( const [apiKeyValue, setApiKeyValue] = useState("");
hasApiKey ? "This is not a real api key." : ""
);
function handleInput({
target: { name, value },
}: inputHandlerEventType): void {
setInputState((prev) => ({ ...prev, [name]: value }));
}
useEffect(() => {
if (open) {
// resetForm();
} else {
onCloseModal();
}
}, [open]);
const handleSaveKey = () => { const handleSaveKey = () => {
if (inputState && inputState["apikey"]) { if (apiKeyValue) {
addApiKeyStore(inputState["apikey"]).then( addApiKeyStore(apiKeyValue).then(
() => { () => {
setSuccessData({ setSuccessData({
title: "Success! Your API Key has been saved.", title: "Success! Your API Key has been saved.",
}); });
storeApiKey(inputState["apikey"]); storeApiKey(apiKeyValue);
setHasApiKey(true);
setOpen(false); setOpen(false);
}, },
(error) => { (error) => {
@ -67,7 +41,15 @@ export default function StoreApiKeyModal({
return ( return (
<BaseModal size="small-h-full" open={open} setOpen={setOpen}> <BaseModal size="small-h-full" open={open} setOpen={setOpen}>
<BaseModal.Trigger>{children}</BaseModal.Trigger> <BaseModal.Trigger>{children}</BaseModal.Trigger>
<BaseModal.Header description={"Insert your Langflow API key."}> <BaseModal.Header
description={
(hasApiKey && !validApiKey
? "Your API key is not valid. "
: !hasApiKey
? "You don't have an API key. "
: "") + "Insert your Langflow API key."
}
>
<span className="pr-2">API Key</span> <span className="pr-2">API Key</span>
<IconComponent <IconComponent
name="Key" name="Key"
@ -82,7 +64,7 @@ export default function StoreApiKeyModal({
}} }}
> >
<div className="grid gap-5"> <div className="grid gap-5">
<Form.Field name="username"> <Form.Field name="apikey">
<div className="flex items-center justify-between gap-2"> <div className="flex items-center justify-between gap-2">
<Form.Control asChild> <Form.Control asChild>
<Input <Input
@ -90,7 +72,6 @@ export default function StoreApiKeyModal({
value={apiKeyValue} value={apiKeyValue}
type="password" type="password"
onChange={({ target: { value } }) => { onChange={({ target: { value } }) => {
handleInput({ target: { name: "apikey", value } });
setApiKeyValue(value); setApiKeyValue(value);
}} }}
placeholder="Insert your API Key" placeholder="Insert your API Key"

View file

@ -1,4 +1,4 @@
import { useContext, useEffect, useState } from "react"; import { useContext, useState } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import ShadTooltip from "../../../components/ShadTooltipComponent"; import ShadTooltip from "../../../components/ShadTooltipComponent";
import IconComponent from "../../../components/genericIconComponent"; import IconComponent from "../../../components/genericIconComponent";
@ -13,12 +13,7 @@ import {
} from "../../../components/ui/card"; } from "../../../components/ui/card";
import { alertContext } from "../../../contexts/alertContext"; import { alertContext } from "../../../contexts/alertContext";
import { FlowsContext } from "../../../contexts/flowsContext"; import { FlowsContext } from "../../../contexts/flowsContext";
import { StoreContext } from "../../../contexts/storeContext"; import { getComponent, postLikeComponent } from "../../../controllers/API";
import {
getComponent,
postLikeComponent,
saveFlowStore,
} from "../../../controllers/API";
import { storeComponent } from "../../../types/store"; import { storeComponent } from "../../../types/store";
import cloneFLowWithParent from "../../../utils/storeUtils"; import cloneFLowWithParent from "../../../utils/storeUtils";
import { gradients } from "../../../utils/styleUtils"; import { gradients } from "../../../utils/styleUtils";
@ -28,16 +23,11 @@ export const MarketCardComponent = ({
data, data,
authorized = true, authorized = true,
disabled = false, disabled = false,
installable = false,
}: { }: {
data: storeComponent; data: storeComponent;
authorized?: boolean; authorized?: boolean;
disabled?: boolean; disabled?: boolean;
installable?: boolean;
}) => { }) => {
const { savedFlows } = useContext(StoreContext);
const [added, setAdded] = useState(savedFlows.has(data.id) ? true : false);
const [installed, setInstalled] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { addFlow } = useContext(FlowsContext); const { addFlow } = useContext(FlowsContext);
const [loadingLike, setLoadingLike] = useState(false); const [loadingLike, setLoadingLike] = useState(false);
@ -47,40 +37,6 @@ export const MarketCardComponent = ({
const name = data.is_component ? "Component" : "Flow"; const name = data.is_component ? "Component" : "Flow";
useEffect(() => {
setAdded(savedFlows.has(data.id) ? true : false);
}, [savedFlows]);
function handleAdd() {
setLoading(true);
getComponent(data.id).then(
(res) => {
console.log(res);
const newFLow = cloneFLowWithParent(res, res.id, data.is_component);
console.log(newFLow);
saveFlowStore(
newFLow,
data.tags.map((tag) => tag.id)
)
.then(() => {
setAdded(true);
setLoading(false);
setSuccessData({ title: `${name} added to account.` });
})
.catch((error) => {
console.error(error);
setErrorData({
title: `Error on adding ${name}`,
list: [error["response"]["data"]["detail"]],
});
});
},
(error) => {
console.log(error);
}
);
}
const navigate = useNavigate(); const navigate = useNavigate();
function handleInstall() { function handleInstall() {
@ -90,7 +46,6 @@ export const MarketCardComponent = ({
addFlow(true, newFlow).then((id) => { addFlow(true, newFlow).then((id) => {
setSuccessData({ title: `${name} Installed` }); setSuccessData({ title: `${name} Installed` });
setLoading(false); setLoading(false);
setInstalled(true);
if (!data.is_component) navigate("/flow/" + id); if (!data.is_component) navigate("/flow/" + id);
}); });
}); });
@ -290,13 +245,7 @@ export const MarketCardComponent = ({
</ShadTooltip> </ShadTooltip>
<ShadTooltip <ShadTooltip
content={ content={
authorized authorized ? "Install Locally" : "Please review your API key."
? added
? installable
? "Install Locally"
: "Added to Account"
: "Add to Account"
: "Please review your API key."
} }
> >
<Button <Button
@ -310,34 +259,13 @@ export const MarketCardComponent = ({
if (loading || !authorized) { if (loading || !authorized) {
return; return;
} }
if (!added) { handleInstall();
handleAdd();
} else if (installable) {
handleInstall();
}
}} }}
> >
<IconComponent <IconComponent
name={ name={loading ? "Loader2" : "Plus"}
loading
? "Loader2"
: added && installable && !installed
? "ExternalLink"
: added
? "Check"
: "Plus"
}
className={classNames( className={classNames(
!added && !installable && !loading loading ? "h-5 w-5 animate-spin" : "h-6 w-6 p-0.5",
? "h-6 w-6"
: "h-5 w-5",
((added && !installable) || installed) && !loading
? "text-chat-send"
: "",
added && installable && !installed && !loading
? "text-high-indigo"
: "",
loading ? " animate-spin" : "",
!authorized ? " text-ring" : "" !authorized ? " text-ring" : ""
)} )}
/> />

View file

@ -23,7 +23,7 @@ import { storeComponent } from "../../types/store";
import { cn } from "../../utils/utils"; import { cn } from "../../utils/utils";
import { MarketCardComponent } from "./components/market-card"; import { MarketCardComponent } from "./components/market-card";
export default function StorePage(): JSX.Element { export default function StorePage(): JSX.Element {
const { errorApiKey, hasApiKey, setHasApiKey } = useContext(StoreContext); const { validApiKey, setValidApiKey, hasApiKey } = useContext(StoreContext);
const { setErrorData } = useContext(alertContext); const { setErrorData } = useContext(alertContext);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [loadingTags, setLoadingTags] = useState(true); const [loadingTags, setLoadingTags] = useState(true);
@ -52,7 +52,7 @@ export default function StorePage(): JSX.Element {
pageSize, pageSize,
filteredCategories, filteredCategories,
selectFilter, selectFilter,
hasApiKey, validApiKey,
]); ]);
function handleGetTags() { function handleGetTags() {
@ -72,29 +72,36 @@ export default function StorePage(): JSX.Element {
tabActive === "All" ? null : tabActive === "Flows" ? false : true, tabActive === "All" ? null : tabActive === "Flows" ? false : true,
sort: pageOrder === "Popular" ? "-count(downloads)" : "name", sort: pageOrder === "Popular" ? "-count(downloads)" : "name",
tags: filteredCategories, tags: filteredCategories,
liked: selectFilter === "likedbyme" && hasApiKey ? true : null, liked: selectFilter === "likedbyme" && validApiKey ? true : null,
status: null, status: null,
search: searchText === "" ? null : searchText, search: searchText === "" ? null : searchText,
filterByUser: selectFilter === "createdbyme" && hasApiKey ? true : null, filterByUser: selectFilter === "createdbyme" && validApiKey ? true : null,
}) })
.then((res) => { .then((res) => {
setHasApiKey(res?.authorized ?? false); if (!res?.authorized && validApiKey === true) {
setLoading(false); setValidApiKey(false);
setSearchData(res?.results ?? []); } else {
setTotalRowsCount( setLoading(false);
filteredCategories?.length === 0 setSearchData(res?.results ?? []);
? Number(res?.count ?? 0) setTotalRowsCount(
: res?.results?.length ?? 0 filteredCategories?.length === 0
); ? Number(res?.count ?? 0)
: res?.results?.length ?? 0
);
}
}) })
.catch((err) => { .catch((err) => {
setSearchData([]); if (err.response.status === 403) {
setTotalRowsCount(0); setValidApiKey(false);
setLoading(false); } else {
setErrorData({ setSearchData([]);
title: "Error to get components.", setTotalRowsCount(0);
list: [err["response"]["data"]["detail"]], setLoading(false);
}); setErrorData({
title: "Error to get components.",
list: [err["response"]["data"]["detail"]],
});
}
}); });
} }
@ -123,17 +130,10 @@ export default function StorePage(): JSX.Element {
Langflow Store Langflow Store
</span> </span>
<div className="community-page-nav-button"> <div className="community-page-nav-button">
<StoreApiKeyModal <StoreApiKeyModal>
onCloseModal={() => {
handleGetTags();
handleGetComponents();
}}
>
<Button <Button
className={`${ className={`${
errorApiKey && !hasApiKey !validApiKey ? "animate-pulse border-error" : ""
? "animate-pulse border-error"
: ""
}`} }`}
variant="primary" variant="primary"
> >
@ -303,7 +303,7 @@ export default function StorePage(): JSX.Element {
<MarketCardComponent <MarketCardComponent
key={item.id} key={item.id}
data={item} data={item}
authorized={hasApiKey} authorized={validApiKey}
disabled={loading} disabled={loading}
/> />
</> </>

View file

@ -269,7 +269,7 @@ export type signUpInputStateType = {
export type inputHandlerEventType = { export type inputHandlerEventType = {
target: { target: {
value: string | boolean; value: string;
name: string; name: string;
}; };
}; };
@ -356,11 +356,6 @@ export type ApiKeyType = {
export type StoreApiKeyType = { export type StoreApiKeyType = {
children: ReactElement; children: ReactElement;
onCloseModal: () => void;
};
export type ApiKeyInputType = {
apikeyname: string;
}; };
export type groupedObjType = { export type groupedObjType = {
family: string; family: string;

View file

@ -1,11 +1,8 @@
export type storeContextType = { export type storeContextType = {
savedFlows: Set<string>;
setSavedFlows: (newState: Set<string>) => void;
setHasStore: (store: boolean) => void; setHasStore: (store: boolean) => void;
hasStore: boolean; hasStore: boolean;
setHasApiKey: (key: boolean) => void; setHasApiKey: (key: boolean) => void;
hasApiKey: boolean; hasApiKey: boolean;
getSavedComponents: () => void; setValidApiKey: (key: boolean) => void;
errorApiKey: boolean; validApiKey: boolean;
loadingSaved: boolean;
}; };