diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 369a6f764..a58342799 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -1,90 +1,12 @@ -import { Suspense, useContext, useEffect } from "react"; -import { Cookies } from "react-cookie"; +import { Suspense } from "react"; import { RouterProvider } from "react-router-dom"; import "reactflow/dist/style.css"; -import LoadingComponent from "./components/loadingComponent"; -import { AuthContext } from "./contexts/authContext"; -import { - useAutoLogin, - useRefreshAccessToken, -} from "./controllers/API/queries/auth"; -import { useGetVersionQuery } from "./controllers/API/queries/version"; -import useSaveConfig from "./hooks/use-save-config"; +import { LoadingPage } from "./pages/LoadingPage"; import router from "./routes"; -import useAlertStore from "./stores/alertStore"; -import useAuthStore from "./stores/authStore"; -import { useDarkStore } from "./stores/darkStore"; -import useFlowsManagerStore from "./stores/flowsManagerStore"; export default function App() { - const { login, setUserData, getUser } = useContext(AuthContext); - const setAutoLogin = useAuthStore((state) => state.setAutoLogin); - const setLoading = useAlertStore((state) => state.setLoading); - const refreshStars = useDarkStore((state) => state.refreshStars); - const dark = useDarkStore((state) => state.dark); - const isAuthenticated = useAuthStore((state) => state.isAuthenticated); - const cookies = new Cookies(); - const logout = useAuthStore((state) => state.logout); - - const refreshToken = cookies.get("refresh_token"); - - const { mutate: mutateAutoLogin } = useAutoLogin(); - - const { mutate: mutateRefresh } = useRefreshAccessToken(); - - const isLoginPage = location.pathname.includes("login"); - - useEffect(() => { - if (!dark) { - document.getElementById("body")!.classList.remove("dark"); - } else { - document.getElementById("body")!.classList.add("dark"); - } - }, [dark]); - - useEffect(() => { - mutateAutoLogin(undefined, { - onSuccess: async (user) => { - if (user && user["access_token"]) { - user["refresh_token"] = "auto"; - login(user["access_token"], "auto"); - setUserData(user); - setAutoLogin(true); - refreshStars(); - // mutateRefresh({ refresh_token: refreshToken }); - } - }, - onError: (error) => { - if (error.name !== "CanceledError") { - setAutoLogin(false); - if (!isLoginPage) { - if (!isAuthenticated) { - setLoading(false); - useFlowsManagerStore.setState({ isLoading: false }); - logout(); - } else { - mutateRefresh({ refresh_token: refreshToken }); - refreshStars(); - getUser(); - } - } - } - }, - }); - }, []); - - useGetVersionQuery(); - useSaveConfig(); - return ( - //need parent component with width and height - - - - } - > + }> ); diff --git a/src/frontend/src/components/authAdminGuard/index.tsx b/src/frontend/src/components/authAdminGuard/index.tsx index 715b9bba2..f4a60dbaf 100644 --- a/src/frontend/src/components/authAdminGuard/index.tsx +++ b/src/frontend/src/components/authAdminGuard/index.tsx @@ -1,8 +1,8 @@ +import { LoadingPage } from "@/pages/LoadingPage"; import useAuthStore from "@/stores/authStore"; import { useContext } from "react"; import { Navigate } from "react-router-dom"; import { AuthContext } from "../../contexts/authContext"; -import LoadingComponent from "../loadingComponent"; export const ProtectedAdminRoute = ({ children }) => { const { userData } = useContext(AuthContext); @@ -11,11 +11,7 @@ export const ProtectedAdminRoute = ({ children }) => { const isAdmin = useAuthStore((state) => state.isAdmin); if (!isAuthenticated) { - return ( -
- -
- ); + return ; } else if ((userData && !isAdmin) || autoLogin) { return ; } else { diff --git a/src/frontend/src/components/authGuard/index.tsx b/src/frontend/src/components/authGuard/index.tsx index 550ec88e4..a64ba8c6d 100644 --- a/src/frontend/src/components/authGuard/index.tsx +++ b/src/frontend/src/components/authGuard/index.tsx @@ -25,14 +25,17 @@ export const ProtectedRoute = ({ children }) => { ? automaticRefreshTime : envRefreshTime; - const intervalId = setInterval(() => { + const intervalFunction = () => { if (isAuthenticated) { mutateRefresh({ refresh_token: refreshToken }); } - }, accessTokenTimer * 1000); + }; + + const intervalId = setInterval(intervalFunction, accessTokenTimer * 1000); + intervalFunction(); return () => clearInterval(intervalId); - }, []); + }, [isAuthenticated]); if (!isAuthenticated && hasToken) { logout(); diff --git a/src/frontend/src/components/folderSidebarComponent/hooks/use-on-file-drop.tsx b/src/frontend/src/components/folderSidebarComponent/hooks/use-on-file-drop.tsx index 40b0d8e76..7e2393444 100644 --- a/src/frontend/src/components/folderSidebarComponent/hooks/use-on-file-drop.tsx +++ b/src/frontend/src/components/folderSidebarComponent/hooks/use-on-file-drop.tsx @@ -19,12 +19,12 @@ const useFileDrop = (folderId: string) => { const flows = useFlowsManagerStore((state) => state.flows); const saveFlow = useSaveFlow(); const { mutate: uploadFlowToFolder } = usePostUploadFlowToFolder(); - const handleFileDrop = async (e) => { + const handleFileDrop = async (e, folderId) => { if (e.dataTransfer.types.some((type) => type === "Files")) { if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { const firstFile = e.dataTransfer.files[0]; if (firstFile.type === "application/json") { - uploadFormData(firstFile); + uploadFormData(firstFile, folderId); } else { setErrorData({ title: WRONG_FILE_ERROR_ALERT, @@ -94,7 +94,7 @@ const useFileDrop = (folderId: string) => { } e.preventDefault(); - handleFileDrop(e); + handleFileDrop(e, folderId); }; const uploadFromDragCard = (flowId, folderId) => { @@ -115,7 +115,7 @@ const useFileDrop = (folderId: string) => { saveFlow(updatedFlow); }; - const uploadFormData = (data) => { + const uploadFormData = (data, folderId) => { const formData = new FormData(); formData.append("file", data); setFolderDragging(false); diff --git a/src/frontend/src/contexts/authContext.tsx b/src/frontend/src/contexts/authContext.tsx index 59e4a9d32..5a8026629 100644 --- a/src/frontend/src/contexts/authContext.tsx +++ b/src/frontend/src/contexts/authContext.tsx @@ -8,7 +8,6 @@ import { useGetUserData } from "@/controllers/API/queries/auth"; import useAuthStore from "@/stores/authStore"; import { createContext, useEffect, useState } from "react"; import Cookies from "universal-cookie"; -import useAlertStore from "../stores/alertStore"; import { useStoreStore } from "../stores/storeStore"; import { Users } from "../types/api"; import { AuthContextType } from "../types/contexts/auth"; @@ -33,7 +32,6 @@ export function AuthProvider({ children }): React.ReactElement { cookies.get(LANGFLOW_ACCESS_TOKEN) ?? null, ); const [userData, setUserData] = useState(null); - const setLoading = useAlertStore((state) => state.setLoading); const [apiKey, setApiKey] = useState( cookies.get(LANGFLOW_API_TOKEN), ); @@ -71,7 +69,6 @@ export function AuthProvider({ children }): React.ReactElement { }, onError: () => { setUserData(null); - setLoading(false); }, }, ); diff --git a/src/frontend/src/controllers/API/queries/api-keys/use-get-api-keys.ts b/src/frontend/src/controllers/API/queries/api-keys/use-get-api-keys.ts index 850fa7b91..89f4bc502 100644 --- a/src/frontend/src/controllers/API/queries/api-keys/use-get-api-keys.ts +++ b/src/frontend/src/controllers/API/queries/api-keys/use-get-api-keys.ts @@ -1,4 +1,3 @@ -import { useDarkStore } from "@/stores/darkStore"; import { useQueryFunctionType } from "@/types/api"; import { api } from "../../api"; import { getURL } from "../../helpers/constants"; @@ -24,7 +23,7 @@ interface IApiQueryResponse { export const useGetApiKeysQuery: useQueryFunctionType< undefined, IApiQueryResponse -> = (_, options) => { +> = (options) => { const { query } = UseRequestProcessor(); const getApiKeysFn = async () => { diff --git a/src/frontend/src/controllers/API/queries/auth/use-get-autologin.ts b/src/frontend/src/controllers/API/queries/auth/use-get-autologin.ts index a05311268..d4bb7c710 100644 --- a/src/frontend/src/controllers/API/queries/auth/use-get-autologin.ts +++ b/src/frontend/src/controllers/API/queries/auth/use-get-autologin.ts @@ -1,24 +1,57 @@ -import { UseMutationResult } from "@tanstack/react-query"; -import { useMutationFunctionType } from "../../../../types/api"; +import { AuthContext } from "@/contexts/authContext"; +import useAuthStore from "@/stores/authStore"; +import { AxiosError } from "axios"; +import { useContext } from "react"; +import { useQueryFunctionType, Users } from "../../../../types/api"; import { api } from "../../api"; import { getURL } from "../../helpers/constants"; import { UseRequestProcessor } from "../../services/request-processor"; -export const useAutoLogin: useMutationFunctionType = ( - options?, +export interface AutoLoginResponse { + frontend_timeout: number; + auto_saving: boolean; + auto_saving_interval: number; + health_check_max_retries: number; +} + +export const useGetAutoLogin: useQueryFunctionType = ( + options, ) => { - const { mutate } = UseRequestProcessor(); + const { query } = UseRequestProcessor(); + const { login, setUserData, getUser } = useContext(AuthContext); + const setAutoLogin = useAuthStore((state) => state.setAutoLogin); + const isAuthenticated = useAuthStore((state) => state.isAuthenticated); + const logout = useAuthStore((state) => state.logout); + const isLoginPage = location.pathname.includes("login"); - const autoLoginFn = async (): Promise => { - const res = await api.get(`${getURL("AUTOLOGIN")}`); - return res.data; - }; + async function getAutoLoginFn(): Promise { + try { + const response = await api.get(`${getURL("AUTOLOGIN")}`); + const user = response.data; + if (user && user["access_token"]) { + user["refresh_token"] = "auto"; + login(user["access_token"], "auto"); + setUserData(user); + setAutoLogin(true); + } + } catch (e) { + const error = e as AxiosError; + if (error.name !== "CanceledError") { + setAutoLogin(false); + if (!isLoginPage) { + if (!isAuthenticated) { + await logout(); + throw new Error("Unauthorized"); + } else { + getUser(); + } + } + } + } + return null; + } - const mutation: UseMutationResult = mutate( - ["useAutoLogin"], - autoLoginFn, - options, - ); + const queryResult = query(["useGetAutoLogin"], getAutoLoginFn, options); - return mutation; + return queryResult; }; diff --git a/src/frontend/src/controllers/API/queries/config/use-get-config.ts b/src/frontend/src/controllers/API/queries/config/use-get-config.ts index 72a7e4318..975b21b3e 100644 --- a/src/frontend/src/controllers/API/queries/config/use-get-config.ts +++ b/src/frontend/src/controllers/API/queries/config/use-get-config.ts @@ -1,3 +1,5 @@ +import useFlowsManagerStore from "@/stores/flowsManagerStore"; +import axios from "axios"; import { useQueryFunctionType } from "../../../../types/api"; import { api } from "../../api"; import { getURL } from "../../helpers/constants"; @@ -10,18 +12,35 @@ export interface ConfigResponse { health_check_max_retries: number; } -export const useGetConfigQuery: useQueryFunctionType< - undefined, - ConfigResponse -> = (options) => { +export const useGetConfig: useQueryFunctionType = ( + options, +) => { + const setAutoSaving = useFlowsManagerStore((state) => state.setAutoSaving); + const setAutoSavingInterval = useFlowsManagerStore( + (state) => state.setAutoSavingInterval, + ); + const setHealthCheckMaxRetries = useFlowsManagerStore( + (state) => state.setHealthCheckMaxRetries, + ); + const { query } = UseRequestProcessor(); const getConfigFn = async () => { const response = await api.get(`${getURL("CONFIG")}`); - return response["data"]; + const data = response["data"]; + if (data) { + const timeoutInMilliseconds = data.frontend_timeout + ? data.frontend_timeout * 1000 + : 30000; + axios.defaults.baseURL = ""; + axios.defaults.timeout = timeoutInMilliseconds; + setAutoSaving(data.auto_saving); + setAutoSavingInterval(data.auto_saving_interval); + setHealthCheckMaxRetries(data.health_check_max_retries); + } }; - const queryResult = query(["useGetConfigQuery"], getConfigFn, options); + const queryResult = query(["useGetConfig"], getConfigFn, options); return queryResult; }; diff --git a/src/frontend/src/controllers/API/queries/health/use-get-health.ts b/src/frontend/src/controllers/API/queries/health/use-get-health.ts index 959d2e202..c43ab488c 100644 --- a/src/frontend/src/controllers/API/queries/health/use-get-health.ts +++ b/src/frontend/src/controllers/API/queries/health/use-get-health.ts @@ -5,7 +5,7 @@ import { import { useUtilityStore } from "@/stores/utilityStore"; import { createNewError503 } from "@/types/factory/axios-error-503"; import { keepPreviousData } from "@tanstack/react-query"; -import { AxiosError, AxiosHeaders } from "axios"; +import { AxiosError } from "axios"; import { useQueryFunctionType } from "../../../../types/api"; import { api } from "../../api"; import { UseRequestProcessor } from "../../services/request-processor"; @@ -21,7 +21,7 @@ interface getHealthResponse { export const useGetHealthQuery: useQueryFunctionType< undefined, getHealthResponse -> = (_, options) => { +> = (options) => { const { query } = UseRequestProcessor(); const setHealthCheckTimeout = useUtilityStore( (state) => state.setHealthCheckTimeout, diff --git a/src/frontend/src/controllers/API/queries/store/use-get-tags.ts b/src/frontend/src/controllers/API/queries/store/use-get-tags.ts index 03cf88675..bf43a628d 100644 --- a/src/frontend/src/controllers/API/queries/store/use-get-tags.ts +++ b/src/frontend/src/controllers/API/queries/store/use-get-tags.ts @@ -13,7 +13,7 @@ type tagsQueryResponse = Array; export const useGetTagsQuery: useQueryFunctionType< undefined, tagsQueryResponse -> = (_, options) => { +> = (options) => { const { query } = UseRequestProcessor(); const getTagsFn = async () => { diff --git a/src/frontend/src/controllers/API/queries/version/use-get-version.ts b/src/frontend/src/controllers/API/queries/version/use-get-version.ts index 1947a8aa1..2aca9d8f2 100644 --- a/src/frontend/src/controllers/API/queries/version/use-get-version.ts +++ b/src/frontend/src/controllers/API/queries/version/use-get-version.ts @@ -12,7 +12,7 @@ interface versionQueryResponse { export const useGetVersionQuery: useQueryFunctionType< undefined, versionQueryResponse -> = (_, options) => { +> = (options) => { const { query } = UseRequestProcessor(); const getVersionFn = async () => { diff --git a/src/frontend/src/hooks/use-save-config.ts b/src/frontend/src/hooks/use-save-config.ts deleted file mode 100644 index 5df3674d4..000000000 --- a/src/frontend/src/hooks/use-save-config.ts +++ /dev/null @@ -1,30 +0,0 @@ -import useFlowsManagerStore from "@/stores/flowsManagerStore"; -import axios from "axios"; -import { useEffect } from "react"; -import { useGetConfigQuery } from "../controllers/API/queries/config/use-get-config"; - -function useSaveConfig() { - const { data } = useGetConfigQuery(); - const setAutoSaving = useFlowsManagerStore((state) => state.setAutoSaving); - const setAutoSavingInterval = useFlowsManagerStore( - (state) => state.setAutoSavingInterval, - ); - const setHealthCheckMaxRetries = useFlowsManagerStore( - (state) => state.setHealthCheckMaxRetries, - ); - - useEffect(() => { - if (data) { - const timeoutInMilliseconds = data.frontend_timeout - ? data.frontend_timeout * 1000 - : 30000; - axios.defaults.baseURL = ""; - axios.defaults.timeout = timeoutInMilliseconds; - setAutoSaving(data.auto_saving); - setAutoSavingInterval(data.auto_saving_interval); - setHealthCheckMaxRetries(data.health_check_max_retries); - } - }, [data]); -} - -export default useSaveConfig; diff --git a/src/frontend/src/index.tsx b/src/frontend/src/index.tsx index 95878233b..ca3d58b86 100644 --- a/src/frontend/src/index.tsx +++ b/src/frontend/src/index.tsx @@ -1,5 +1,4 @@ import ReactDOM from "react-dom/client"; -import ContextWrapper from "./contexts"; import reportWebVitals from "./reportWebVitals"; import "./style/classes.css"; @@ -16,9 +15,5 @@ const root = ReactDOM.createRoot( document.getElementById("root") as HTMLElement, ); -root.render( - - - , -); +root.render(); reportWebVitals(); diff --git a/src/frontend/src/pages/AdminPage/LoginPage/index.tsx b/src/frontend/src/pages/AdminPage/LoginPage/index.tsx index 68bd53755..e401f7ec0 100644 --- a/src/frontend/src/pages/AdminPage/LoginPage/index.tsx +++ b/src/frontend/src/pages/AdminPage/LoginPage/index.tsx @@ -1,5 +1,4 @@ import { useLoginUser } from "@/controllers/API/queries/auth"; -import { useFolderStore } from "@/stores/foldersStore"; import { useContext, useState } from "react"; import { Button } from "../../../components/ui/button"; import { Input } from "../../../components/ui/input"; @@ -17,8 +16,6 @@ export default function LoginAdminPage() { const [inputState, setInputState] = useState(CONTROL_LOGIN_STATE); const { login } = useContext(AuthContext); - const setLoading = useAlertStore((state) => state.setLoading); - const setSelectedFolder = useFolderStore((state) => state.setSelectedFolder); const { password, username } = inputState; const setErrorData = useAlertStore((state) => state.setErrorData); @@ -38,9 +35,6 @@ export default function LoginAdminPage() { mutate(user, { onSuccess: (res) => { - setSelectedFolder(null); - - setLoading(true); login(res.access_token, "login", res.refresh_token); }, onError: (error) => { diff --git a/src/frontend/src/pages/AppInitPage/index.tsx b/src/frontend/src/pages/AppInitPage/index.tsx new file mode 100644 index 000000000..a724a21b9 --- /dev/null +++ b/src/frontend/src/pages/AppInitPage/index.tsx @@ -0,0 +1,40 @@ +import { useGetAutoLogin } from "@/controllers/API/queries/auth"; +import { useGetConfig } from "@/controllers/API/queries/config/use-get-config"; +import { useGetVersionQuery } from "@/controllers/API/queries/version"; +import { useDarkStore } from "@/stores/darkStore"; +import useFlowsManagerStore from "@/stores/flowsManagerStore"; +import { useEffect } from "react"; +import { Outlet } from "react-router-dom"; +import { LoadingPage } from "../LoadingPage"; + +export function AppInitPage() { + const dark = useDarkStore((state) => state.dark); + const refreshStars = useDarkStore((state) => state.refreshStars); + const isLoading = useFlowsManagerStore((state) => state.isLoading); + + const { isFetched } = useGetAutoLogin(); + useGetVersionQuery({ enabled: isFetched }); + useGetConfig({ enabled: isFetched }); + + useEffect(() => { + if (isFetched) { + refreshStars(); + } + }, [isFetched]); + + useEffect(() => { + if (!dark) { + document.getElementById("body")!.classList.remove("dark"); + } else { + document.getElementById("body")!.classList.add("dark"); + } + }, [dark]); + + return ( + //need parent component with width and height + <> + {(isLoading || !isFetched) && } + {isFetched && } + + ); +} diff --git a/src/frontend/src/pages/AppWrapperPage/index.tsx b/src/frontend/src/pages/AppWrapperPage/index.tsx index 65c16df8d..e1a0c7a93 100644 --- a/src/frontend/src/pages/AppWrapperPage/index.tsx +++ b/src/frontend/src/pages/AppWrapperPage/index.tsx @@ -1,7 +1,6 @@ import AlertDisplayArea from "@/alerts/displayArea"; import CrashErrorComponent from "@/components/crashErrorComponent"; import FetchErrorComponent from "@/components/fetchErrorComponent"; -import LoadingComponent from "@/components/loadingComponent"; import TimeoutErrorComponent from "@/components/timeoutErrorComponent"; import { FETCH_ERROR_DESCRIPION, @@ -12,15 +11,12 @@ import { import { useGetHealthQuery } from "@/controllers/API/queries/health"; import useFlowsManagerStore from "@/stores/flowsManagerStore"; import { useUtilityStore } from "@/stores/utilityStore"; -import { cn } from "@/utils/utils"; import { AxiosError } from "axios"; import { useEffect, useMemo, useState } from "react"; import { ErrorBoundary } from "react-error-boundary"; import { Outlet } from "react-router-dom"; export function AppWrapperPage() { - const isLoading = useFlowsManagerStore((state) => state.isLoading); - const healthCheckMaxRetries = useFlowsManagerStore( (state) => state.healthCheckMaxRetries, ); @@ -108,15 +104,6 @@ export function AppWrapperPage() { > <> {modalErrorComponent} - -
- -
diff --git a/src/frontend/src/pages/LoadingPage/index.tsx b/src/frontend/src/pages/LoadingPage/index.tsx new file mode 100644 index 000000000..8f2e9e858 --- /dev/null +++ b/src/frontend/src/pages/LoadingPage/index.tsx @@ -0,0 +1,15 @@ +import LoadingComponent from "@/components/loadingComponent"; +import { cn } from "@/utils/utils"; + +export function LoadingPage({ overlay = false }: { overlay?: boolean }) { + return ( +
+ +
+ ); +} diff --git a/src/frontend/src/pages/LoginPage/index.tsx b/src/frontend/src/pages/LoginPage/index.tsx index 52f2b5747..8059a2bf0 100644 --- a/src/frontend/src/pages/LoginPage/index.tsx +++ b/src/frontend/src/pages/LoginPage/index.tsx @@ -1,5 +1,4 @@ import { useLoginUser } from "@/controllers/API/queries/auth"; -import { useFolderStore } from "@/stores/foldersStore"; import * as Form from "@radix-ui/react-form"; import { useContext, useState } from "react"; import { Link } from "react-router-dom"; @@ -23,7 +22,6 @@ export default function LoginPage(): JSX.Element { const { password, username } = inputState; const { login } = useContext(AuthContext); const setErrorData = useAlertStore((state) => state.setErrorData); - const setSelectedFolder = useFolderStore((state) => state.setSelectedFolder); function handleInput({ target: { name, value }, @@ -41,8 +39,6 @@ export default function LoginPage(): JSX.Element { mutate(user, { onSuccess: (data) => { - setSelectedFolder(null); - login(data.access_token, "login", data.refresh_token); }, onError: (error) => { diff --git a/src/frontend/src/pages/MainPage/components/componentsComponent/index.tsx b/src/frontend/src/pages/MainPage/components/componentsComponent/index.tsx index d2718465f..ab6b72bdb 100644 --- a/src/frontend/src/pages/MainPage/components/componentsComponent/index.tsx +++ b/src/frontend/src/pages/MainPage/components/componentsComponent/index.tsx @@ -1,6 +1,4 @@ import { usePostDownloadMultipleFlows } from "@/controllers/API/queries/flows"; -import { useGetFolderQuery } from "@/controllers/API/queries/folders/use-get-folder"; -import { useGetFoldersQuery } from "@/controllers/API/queries/folders/use-get-folders"; import useDeleteFlow from "@/hooks/flows/use-delete-flow"; import { useEffect, useMemo, useState } from "react"; import { FormProvider, useForm, useWatch } from "react-hook-form"; @@ -13,6 +11,7 @@ import useAlertStore from "../../../../stores/alertStore"; import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; import { useFolderStore } from "../../../../stores/foldersStore"; import { FlowType } from "../../../../types/flow"; +import { FolderType } from "../../entities"; import useFileDrop from "../../hooks/use-on-file-drop"; import { getNameByType } from "../../utils/get-name-by-type"; import { sortFlows } from "../../utils/sort-flows"; @@ -28,11 +27,13 @@ import useSelectedFlows from "./hooks/use-selected-flows"; export default function ComponentsComponent({ type = "all", + currentFolder, + isLoading, }: { type?: string; + currentFolder?: FolderType; + isLoading: boolean; }) { - const isLoading = useFlowsManagerStore((state) => state.isLoading); - const { folderId } = useParams(); const setSuccessData = useAlertStore((state) => state.setSuccessData); @@ -51,10 +52,6 @@ export default function ComponentsComponent({ ); const myCollectionId = useFolderStore((state) => state.myCollectionId); - const { data: currentFolder, isLoading: isLoadingCurrentFolder } = - useGetFolderQuery({ - id: folderId ?? myCollectionId ?? "", - }); const flowsFromFolder = currentFolder?.flows ?? []; const [filteredFlows, setFilteredFlows] = @@ -71,10 +68,6 @@ export default function ComponentsComponent({ const name = getNameByType(type); - const setSelectedFolder = useFolderStore((state) => state.setSelectedFolder); - - const { isLoading: isLoadingFolders } = useGetFoldersQuery(); - const [shouldSelectAll, setShouldSelectAll] = useState(true); const cardTypes = useMemo(() => { @@ -188,7 +181,6 @@ export default function ComponentsComponent({ const handleDeleteMultiple = () => { deleteFlow({ id: selectedFlowsComponentsCards }) .then(() => { - setSelectedFolder(null); resetFilter(); setSelectedFlowsComponentsCards([]); handleSelectAll(false); @@ -218,12 +210,7 @@ export default function ComponentsComponent({ <>
handleSelectOptionsChange("delete")} @@ -243,16 +230,11 @@ export default function ComponentsComponent({ data-testid="cards-wrapper" >
- {!isLoading && - !isLoadingFolders && - !isLoadingCurrentFolder && - data?.length === 0 ? ( + {!isLoading && data?.length === 0 ? ( ) : (
- {data?.length > 0 && - isLoadingFolders === false && - isLoadingCurrentFolder === false ? ( + {data?.length > 0 && isLoading === false ? ( <> {data?.map((item) => ( diff --git a/src/frontend/src/pages/MainPage/components/myCollectionComponent/components/headerTabsSearchComponent/index.tsx b/src/frontend/src/pages/MainPage/components/myCollectionComponent/components/headerTabsSearchComponent/index.tsx index c6ceb44c7..fba02db16 100644 --- a/src/frontend/src/pages/MainPage/components/myCollectionComponent/components/headerTabsSearchComponent/index.tsx +++ b/src/frontend/src/pages/MainPage/components/myCollectionComponent/components/headerTabsSearchComponent/index.tsx @@ -3,10 +3,13 @@ import useFlowsManagerStore from "../../../../../../stores/flowsManagerStore"; import InputSearchComponent from "../inputSearchComponent"; import TabsSearchComponent from "../tabsComponent"; -type HeaderTabsSearchComponentProps = {}; +type HeaderTabsSearchComponentProps = { + loading: boolean; +}; -const HeaderTabsSearchComponent = ({}: HeaderTabsSearchComponentProps) => { - const isLoading = useFlowsManagerStore((state) => state.isLoading); +const HeaderTabsSearchComponent = ({ + loading, +}: HeaderTabsSearchComponentProps) => { const [tabActive, setTabActive] = useState("Flows"); const [inputValue, setInputValue] = useState(""); @@ -18,7 +21,7 @@ const HeaderTabsSearchComponent = ({}: HeaderTabsSearchComponentProps) => { <>
{ setSearchFlowsComponents(e.target.value); @@ -33,7 +36,7 @@ const HeaderTabsSearchComponent = ({}: HeaderTabsSearchComponentProps) => {
diff --git a/src/frontend/src/pages/MainPage/components/myCollectionComponent/index.tsx b/src/frontend/src/pages/MainPage/components/myCollectionComponent/index.tsx index c58e1cd12..eddc2481f 100644 --- a/src/frontend/src/pages/MainPage/components/myCollectionComponent/index.tsx +++ b/src/frontend/src/pages/MainPage/components/myCollectionComponent/index.tsx @@ -1,3 +1,7 @@ +import { useGetFolderQuery } from "@/controllers/API/queries/folders/use-get-folder"; +import { useGetFoldersQuery } from "@/controllers/API/queries/folders/use-get-folders"; +import { useFolderStore } from "@/stores/foldersStore"; +import { useParams } from "react-router-dom"; import ComponentsComponent from "../componentsComponent"; import HeaderTabsSearchComponent from "./components/headerTabsSearchComponent"; @@ -6,11 +10,24 @@ type MyCollectionComponentProps = { }; const MyCollectionComponent = ({ type }: MyCollectionComponentProps) => { + const { folderId } = useParams(); + const myCollectionId = useFolderStore((state) => state.myCollectionId); + + const { data, isLoading } = useGetFolderQuery({ + id: folderId ?? myCollectionId ?? "", + }); + const { isLoading: isLoadingFolders } = useGetFoldersQuery(); + return ( <> - +
- +
); diff --git a/src/frontend/src/pages/Playground/index.tsx b/src/frontend/src/pages/Playground/index.tsx index 09b4ae8c3..81155b7b3 100644 --- a/src/frontend/src/pages/Playground/index.tsx +++ b/src/frontend/src/pages/Playground/index.tsx @@ -4,7 +4,6 @@ import { useStoreStore } from "@/stores/storeStore"; import { useTypesStore } from "@/stores/typesStore"; import { useEffect } from "react"; import { useNavigate, useParams } from "react-router-dom"; -import LoadingComponent from "../../components/loadingComponent"; import { getComponent } from "../../controllers/API"; import IOModal from "../../modals/IOModal"; import useFlowsManagerStore from "../../stores/flowsManagerStore"; @@ -59,11 +58,7 @@ export default function PlaygroundPage() { return (
- {!currentSavedFlow ? ( -
- -
- ) : ( + {currentSavedFlow && ( {}} isPlayground> <> diff --git a/src/frontend/src/routes.tsx b/src/frontend/src/routes.tsx index 8626998c9..ded7e48bd 100644 --- a/src/frontend/src/routes.tsx +++ b/src/frontend/src/routes.tsx @@ -11,6 +11,8 @@ import { ProtectedRoute } from "./components/authGuard"; import { ProtectedLoginRoute } from "./components/authLoginGuard"; import { AuthSettingsGuard } from "./components/authSettingsGuard"; import { StoreGuard } from "./components/storeGuard"; +import ContextWrapper from "./contexts"; +import { AppInitPage } from "./pages/AppInitPage"; import { AppWrapperPage } from "./pages/AppWrapperPage"; import { DashboardWrapperPage } from "./pages/DashboardWrapperPage"; import FlowPage from "./pages/FlowPage"; @@ -35,129 +37,146 @@ const PlaygroundPage = lazy(() => import("./pages/Playground")); const SignUp = lazy(() => import("./pages/SignUpPage")); const router = createBrowserRouter( createRoutesFromElements([ - }> - - - - } - > - }> - }> - } /> - } - > + + + + } + > + }> + }> + + + + } + > + }> + }> + } /> + } + > + } + /> + + + } + > + + } + /> + + } + > + } + /> + + + }> + } /> + } + /> + } /> + + + + } + /> + } /> + } /> + } - /> - - - } - > - + + + + } + /> + + + + } + /> + + }> + + + + } /> - } - > - } - /> + + }> + } /> + } /> + + } /> + + + } /> - }> - } /> - } /> - } /> - - - - } - /> - } /> - } /> - - - + + + } /> - - + + + } /> - - }> - - - + + + } /> - - - }> - } /> - } /> - - } /> - - - } /> + } /> - - - - } - /> - - - - } - /> - - - - } - /> - } /> , ]), ); diff --git a/src/frontend/src/stores/alertStore.ts b/src/frontend/src/stores/alertStore.ts index 03a12050c..027b706a1 100644 --- a/src/frontend/src/stores/alertStore.ts +++ b/src/frontend/src/stores/alertStore.ts @@ -19,7 +19,6 @@ const useAlertStore = create((set, get) => ({ notificationCenter: false, notificationList: [], tempNotificationList: [], - loading: true, setErrorData: (newState: { title: string; list?: Array }) => { if (newState.title && newState.title !== "") { set({ @@ -151,9 +150,6 @@ const useAlertStore = create((set, get) => ({ ), }); }, - setLoading: (newState: boolean) => { - set({ loading: newState }); - }, clearTempNotificationList: () => { set({ tempNotificationList: [] }); }, diff --git a/src/frontend/src/stores/authStore.ts b/src/frontend/src/stores/authStore.ts index 85fb717bc..c821baf85 100644 --- a/src/frontend/src/stores/authStore.ts +++ b/src/frontend/src/stores/authStore.ts @@ -10,7 +10,7 @@ const useAuthStore = create((set, get) => ({ isAuthenticated: !!cookies.get(LANGFLOW_ACCESS_TOKEN), accessToken: cookies.get(LANGFLOW_ACCESS_TOKEN) ?? null, userData: null, - autoLogin: false, + autoLogin: null, apiKey: cookies.get("apikey_tkn_lflw"), authenticationErrorCount: 0, diff --git a/src/frontend/src/stores/foldersStore.tsx b/src/frontend/src/stores/foldersStore.tsx index 9c8b2a73c..05b281597 100644 --- a/src/frontend/src/stores/foldersStore.tsx +++ b/src/frontend/src/stores/foldersStore.tsx @@ -2,8 +2,6 @@ import { create } from "zustand"; import { FoldersStoreType } from "../types/zustand/folders"; export const useFolderStore = create((set, get) => ({ - selectedFolder: null, - setSelectedFolder: (folder) => set(() => ({ selectedFolder: folder })), loadingById: false, setMyCollectionId: (myCollectionId) => { set({ myCollectionId }); diff --git a/src/frontend/src/types/api/index.ts b/src/frontend/src/types/api/index.ts index 34969c6ad..63cde21cb 100644 --- a/src/frontend/src/types/api/index.ts +++ b/src/frontend/src/types/api/index.ts @@ -248,7 +248,6 @@ export type ResponseErrorDetailAPI = { }; export type useQueryFunctionType = T extends undefined ? ( - params?: T, options?: Omit, ) => UseQueryResult : ( diff --git a/src/frontend/src/types/zustand/alert/index.ts b/src/frontend/src/types/zustand/alert/index.ts index 7b2837517..40ce29631 100644 --- a/src/frontend/src/types/zustand/alert/index.ts +++ b/src/frontend/src/types/zustand/alert/index.ts @@ -15,6 +15,4 @@ export type AlertStoreType = { removeFromTempNotificationList: (index: string) => void; clearNotificationList: () => void; removeFromNotificationList: (index: string) => void; - loading: boolean; - setLoading: (newState: boolean) => void; }; diff --git a/src/frontend/src/types/zustand/auth/index.ts b/src/frontend/src/types/zustand/auth/index.ts index 70bbb131c..0257563b7 100644 --- a/src/frontend/src/types/zustand/auth/index.ts +++ b/src/frontend/src/types/zustand/auth/index.ts @@ -5,7 +5,7 @@ export interface AuthStoreType { isAuthenticated: boolean; accessToken: string | null; userData: Users | null; - autoLogin: boolean; + autoLogin: boolean | null; apiKey: string | null; authenticationErrorCount: number; diff --git a/src/frontend/src/types/zustand/folders/index.ts b/src/frontend/src/types/zustand/folders/index.ts index e0e809e9d..39755f0fb 100644 --- a/src/frontend/src/types/zustand/folders/index.ts +++ b/src/frontend/src/types/zustand/folders/index.ts @@ -1,8 +1,6 @@ import { FolderType } from "../../../pages/MainPage/entities"; export type FoldersStoreType = { - selectedFolder: FolderType | null; - setSelectedFolder: (folder: FolderType | null) => void; myCollectionId: string | null; setMyCollectionId: (value: string) => void; folderToEdit: FolderType | null; diff --git a/src/frontend/tests/end-to-end/folders.spec.ts b/src/frontend/tests/end-to-end/folders.spec.ts index 18ce4d8e4..dc3ea3622 100644 --- a/src/frontend/tests/end-to-end/folders.spec.ts +++ b/src/frontend/tests/end-to-end/folders.spec.ts @@ -1,4 +1,4 @@ -import { test } from "@playwright/test"; +import { expect, test } from "@playwright/test"; import { readFileSync } from "fs"; test("CRUD folders", async ({ page }) => { @@ -71,7 +71,7 @@ test("CRUD folders", async ({ page }) => { await page.getByText("Folder deleted successfully").isVisible(); }); -test("add folder by drag and drop", async ({ page }) => { +test("add a flow into a folder by drag and drop", async ({ page }) => { await page.goto("/"); await page.waitForSelector("text=my collection", { @@ -84,10 +84,10 @@ test("add folder by drag and drop", async ({ page }) => { ); // Wait for the target element to be available before evaluation - await page.waitForSelector( - '//*[@id="root"]/div/div[2]/div[2]/div[3]/aside/nav/div/div[2]', - ); + await page.waitForSelector('[data-testid="sidebar-nav-My Projects"]', { + timeout: 100000, + }); // Create the DataTransfer and File const dataTransfer = await page.evaluateHandle((data) => { const dt = new DataTransfer(); @@ -100,15 +100,34 @@ test("add folder by drag and drop", async ({ page }) => { }, jsonContent); // Now dispatch - await page.dispatchEvent( - '//*[@id="root"]/div/div[2]/div[2]/div[3]/aside/nav/div/div[2]', - "drop", - { - dataTransfer, - }, - ); + await page.getByTestId("sidebar-nav-My Projects").dispatchEvent("drop", { + dataTransfer, + }); - await page.getByText("Getting Started").first().isVisible(); + await page.waitForTimeout(3000); + + const genericNode = page.getByTestId("div-generic-node"); + const elementCount = await genericNode?.count(); + if (elementCount > 0) { + expect(true).toBeTruthy(); + } + + await page.getByTestId("sidebar-nav-My Projects").click(); + + await page.waitForTimeout(1000); + + expect( + await page.locator("text=Getting Started:").last().isVisible(), + ).toBeTruthy(); + expect( + await page.locator("text=Inquisitive Pike").last().isVisible(), + ).toBeTruthy(); + expect( + await page.locator("text=Dreamy Bassi").last().isVisible(), + ).toBeTruthy(); + expect( + await page.locator("text=Furious Faraday").last().isVisible(), + ).toBeTruthy(); }); test("change flow folder", async ({ page }) => { diff --git a/src/frontend/tests/scheduled-end-to-end/actionsMainPage-shard-0.spec.ts b/src/frontend/tests/scheduled-end-to-end/actionsMainPage-shard-0.spec.ts index e93be566b..bec55a35b 100644 --- a/src/frontend/tests/scheduled-end-to-end/actionsMainPage-shard-0.spec.ts +++ b/src/frontend/tests/scheduled-end-to-end/actionsMainPage-shard-0.spec.ts @@ -162,6 +162,12 @@ test("user should be able to duplicate a flow or a component", async ({ await page.getByText("Exit", { exact: true }).click(); } + const replaceButton = await page.getByTestId("replace-button").isVisible(); + + if (replaceButton) { + await page.getByTestId("replace-button").click(); + } + await page.getByTestId("icon-ChevronLeft").last().click(); await page.getByRole("checkbox").nth(1).click(); diff --git a/src/frontend/tests/scheduled-end-to-end/auto-save-off.spec.ts b/src/frontend/tests/scheduled-end-to-end/auto-save-off.spec.ts index 51030dba4..8101c0a3e 100644 --- a/src/frontend/tests/scheduled-end-to-end/auto-save-off.spec.ts +++ b/src/frontend/tests/scheduled-end-to-end/auto-save-off.spec.ts @@ -22,11 +22,11 @@ test("user should be able to manually save a flow when the auto_save is off", as await page.locator("span").filter({ hasText: "My Collection" }).isVisible(); await page.waitForSelector('[data-testid="mainpage_title"]', { - timeout: 30000, + timeout: 5000, }); await page.waitForSelector('[id="new-project-btn"]', { - timeout: 30000, + timeout: 5000, }); let modalCount = 0; @@ -46,12 +46,12 @@ test("user should be able to manually save a flow when the auto_save is off", as } await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, + timeout: 5000, }); await page.getByTestId("blank-flow").click(); await page.waitForSelector('[data-testid="extended-disclosure"]', { - timeout: 30000, + timeout: 5000, }); await page.getByPlaceholder("Search").click(); @@ -66,7 +66,7 @@ test("user should be able to manually save a flow when the auto_save is off", as await page.mouse.down(); await page.waitForSelector('[title="fit view"]', { - timeout: 100000, + timeout: 5000, }); await page.getByTitle("fit view").click(); @@ -77,7 +77,7 @@ test("user should be able to manually save a flow when the auto_save is off", as await page.waitForSelector("text=loading", { state: "hidden", - timeout: 100000, + timeout: 5000, }); await page.getByTestId("icon-ChevronLeft").last().click(); @@ -93,7 +93,7 @@ test("user should be able to manually save a flow when the auto_save is off", as await page.getByText("Untitled document").first().click(); await page.waitForSelector('[data-testid="icon-ChevronLeft"]', { - timeout: 100000, + timeout: 5000, }); expect(await page.getByText("NVIDIA").isVisible()).toBeFalsy(); @@ -110,7 +110,7 @@ test("user should be able to manually save a flow when the auto_save is off", as await page.mouse.down(); await page.waitForSelector('[title="fit view"]', { - timeout: 100000, + timeout: 5000, }); await page.getByTitle("fit view").click(); @@ -123,7 +123,7 @@ test("user should be able to manually save a flow when the auto_save is off", as await page.waitForSelector("text=loading", { state: "hidden", - timeout: 100000, + timeout: 5000, }); await page.waitForTimeout(5000); @@ -142,7 +142,7 @@ test("user should be able to manually save a flow when the auto_save is off", as await page.mouse.down(); await page.waitForSelector('[title="fit view"]', { - timeout: 100000, + timeout: 5000, }); await page.getByTitle("fit view").click(); @@ -150,10 +150,25 @@ test("user should be able to manually save a flow when the auto_save is off", as await page.getByTestId("save-flow-button").click(); await page.getByTestId("icon-ChevronLeft").last().click(); + const replaceButton = await page.getByTestId("replace-button").isVisible(); + + if (replaceButton) { + await page.getByTestId("replace-button").click(); + } + + const saveExitButton = await page + .getByText("Save And Exit", { exact: true }) + .last() + .isVisible(); + + if (saveExitButton) { + await page.getByText("Save And Exit", { exact: true }).last().click(); + } + await page.getByText("Untitled document").first().click(); await page.waitForSelector('[data-testid="icon-ChevronLeft"]', { - timeout: 100000, + timeout: 5000, }); await page.waitForTimeout(5000); diff --git a/src/frontend/tests/scheduled-end-to-end/dragAndDrop.spec.ts b/src/frontend/tests/scheduled-end-to-end/dragAndDrop.spec.ts index d308f313c..2c539b25a 100644 --- a/src/frontend/tests/scheduled-end-to-end/dragAndDrop.spec.ts +++ b/src/frontend/tests/scheduled-end-to-end/dragAndDrop.spec.ts @@ -42,18 +42,29 @@ test.describe("drag and drop test", () => { }, jsonContent); // Now dispatch - await page.dispatchEvent( - '//*[@id="root"]/div/div[2]/div[2]/div[3]/div', - "drop", - { - dataTransfer, - }, - ); + await page.getByTestId("cards-wrapper").dispatchEvent("drop", { + dataTransfer, + }); + + await page.waitForTimeout(3000); const genericNode = page.getByTestId("div-generic-node"); const elementCount = await genericNode?.count(); if (elementCount > 0) { expect(true).toBeTruthy(); } + + expect( + await page.locator("text=Getting Started:").last().isVisible(), + ).toBeTruthy(); + expect( + await page.locator("text=Inquisitive Pike").last().isVisible(), + ).toBeTruthy(); + expect( + await page.locator("text=Dreamy Bassi").last().isVisible(), + ).toBeTruthy(); + expect( + await page.locator("text=Furious Faraday").last().isVisible(), + ).toBeTruthy(); }); }); diff --git a/src/frontend/tests/scheduled-end-to-end/store-shard-3.spec.ts b/src/frontend/tests/scheduled-end-to-end/store-shard-3.spec.ts index c1cf62667..17225759e 100644 --- a/src/frontend/tests/scheduled-end-to-end/store-shard-3.spec.ts +++ b/src/frontend/tests/scheduled-end-to-end/store-shard-3.spec.ts @@ -96,12 +96,12 @@ test("should filter by type", async ({ page }) => { expect(toyBrick).not.toBe(0); await page.getByTestId("all-button-store").click(); - await page.waitForTimeout(8000); + await page.waitForTimeout(10000); let iconGroupAllCount = await page.getByTestId("icon-Group")?.count(); - await page.waitForTimeout(1000); + await page.waitForTimeout(5000); let toyBrickAllCount = await page.getByTestId("icon-ToyBrick")?.count(); - await page.waitForTimeout(1000); + await page.waitForTimeout(5000); if (iconGroupAllCount === 0 || toyBrickAllCount === 0) { expect(false).toBe(true);