refactor: queries loading order and wrapper order (#3603)
* Added loading page * Removed unused loadings and changed loading to LoadingPage * Refactored ComponentsComponent to receive info from parent * refactored headerTabsComponent to receive loading from parent * Added loading of folders into MyCollectionComponent * removed unused loading and folderSelected * updated get config api call to update everything * Make app wait for autoLogin to be set to execute everything else * changed API type to not contain params if its undefined * Updated get autologin to do all logic regarding autologin * Updated other queries with the new useQueryFunctionType type * Updated App.tsx with new gets and configurations and added a loader before loading the router * Made ProtectedRoute refresh on authentication change * Fixed order of wrappers in order for Auth and API context to have access to router * Made loading only exist in one place * 📝 (folders.spec.ts): remove unused test for adding folder by drag and drop to improve test suite cleanliness and maintainability. * Fixed flow dropping to another folder * ✨ (folders.spec.ts): add test for adding folder by drag and drop functionality 🔧 (auto-save-off.spec.ts): add click event for "Save And Exit" button 🔧 (dragAndDrop.spec.ts): change dispatchEvent to getByTestId and add assertions for specific text visibility 🔧 (store-shard-3.spec.ts): increase timeout for page.waitForTimeout to improve test reliability * ✅ (folders.spec.ts): update test description to be more descriptive and accurate * test: improve timeout for page.waitForSelector in auto-save-off.spec.ts * feat: add replace button functionality to main page The code changes include adding the functionality for the replace button on the main page. This allows users to replace a flow or a component. The replace button is now visible on the page, and clicking on it triggers the appropriate action. Recent user commits: - test: improve timeout for page.waitForSelector in auto-save-off.spec.ts - ✅ (folders.spec.ts): update test description to be more descriptive and accurate - ✨ (folders.spec.ts): add test for adding folder by drag and drop functionality - 🔧 (auto-save-off.spec.ts): add click event for "Save And Exit" button - 🔧 (dragAndDrop.spec.ts): change dispatchEvent to getByTestId and add assertions for specific text visibility - 🔧 (store-shard-3.spec.ts): increase timeout for page.waitForTimeout to improve test reliability Recent repository commits: - test: improve timeout for page.waitForSelector in auto-save-off.spec.ts - ✅ (folders.spec.ts): update test description to be more descriptive and accurate - ✨ (folders.spec.ts): add test for adding folder by drag and drop functionality - 🔧 (auto-save-off.spec.ts): add click event for "Save And Exit" button - 🔧 (dragAndDrop.spec.ts): change dispatchEvent to getByTestId and add assertions for specific text visibility - 🔧 (store-shard-3.spec.ts): increase timeout for page.waitForTimeout to improve test reliability - Fixed flow dropping to another folder - 📝 (folders.spec.ts): remove unused test for adding folder by drag and drop to improve test suite cleanliness and maintainability. - Made loading only exist in one place - Fixed order of wrappers in order for Auth and API context to have access to router - Made ProtectedRoute refresh on authentication change - Updated App.tsx with new gets and configurations and added a loader before loading the router - Updated other queries with the new useQueryFunctionType type --------- Co-authored-by: cristhianzl <cristhian.lousa@gmail.com>
This commit is contained in:
parent
e7a48d58e6
commit
af052285ec
35 changed files with 393 additions and 371 deletions
|
|
@ -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
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="loading-page-panel">
|
||||
<LoadingComponent remSize={50} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Suspense fallback={<LoadingPage />}>
|
||||
<RouterProvider router={router} />
|
||||
</Suspense>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="flex h-screen w-screen items-center justify-center">
|
||||
<LoadingComponent remSize={30} />
|
||||
</div>
|
||||
);
|
||||
return <LoadingPage />;
|
||||
} else if ((userData && !isAdmin) || autoLogin) {
|
||||
return <Navigate to="/" replace />;
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<Users | null>(null);
|
||||
const setLoading = useAlertStore((state) => state.setLoading);
|
||||
const [apiKey, setApiKey] = useState<string | null>(
|
||||
cookies.get(LANGFLOW_API_TOKEN),
|
||||
);
|
||||
|
|
@ -71,7 +69,6 @@ export function AuthProvider({ children }): React.ReactElement {
|
|||
},
|
||||
onError: () => {
|
||||
setUserData(null);
|
||||
setLoading(false);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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<undefined, any> = (
|
||||
options?,
|
||||
export interface AutoLoginResponse {
|
||||
frontend_timeout: number;
|
||||
auto_saving: boolean;
|
||||
auto_saving_interval: number;
|
||||
health_check_max_retries: number;
|
||||
}
|
||||
|
||||
export const useGetAutoLogin: useQueryFunctionType<undefined, undefined> = (
|
||||
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<any> => {
|
||||
const res = await api.get(`${getURL("AUTOLOGIN")}`);
|
||||
return res.data;
|
||||
};
|
||||
async function getAutoLoginFn(): Promise<null> {
|
||||
try {
|
||||
const response = await api.get<Users>(`${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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<undefined, undefined> = (
|
||||
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<ConfigResponse>(`${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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ type tagsQueryResponse = Array<ITagsDataArray>;
|
|||
export const useGetTagsQuery: useQueryFunctionType<
|
||||
undefined,
|
||||
tagsQueryResponse
|
||||
> = (_, options) => {
|
||||
> = (options) => {
|
||||
const { query } = UseRequestProcessor();
|
||||
|
||||
const getTagsFn = async () => {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ interface versionQueryResponse {
|
|||
export const useGetVersionQuery: useQueryFunctionType<
|
||||
undefined,
|
||||
versionQueryResponse
|
||||
> = (_, options) => {
|
||||
> = (options) => {
|
||||
const { query } = UseRequestProcessor();
|
||||
|
||||
const getVersionFn = async () => {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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(
|
||||
<ContextWrapper>
|
||||
<App />
|
||||
</ContextWrapper>,
|
||||
);
|
||||
root.render(<App />);
|
||||
reportWebVitals();
|
||||
|
|
|
|||
|
|
@ -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<loginInputStateType>(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) => {
|
||||
|
|
|
|||
40
src/frontend/src/pages/AppInitPage/index.tsx
Normal file
40
src/frontend/src/pages/AppInitPage/index.tsx
Normal file
|
|
@ -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) && <LoadingPage overlay />}
|
||||
{isFetched && <Outlet />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -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}
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"loading-page-panel absolute left-0 top-0 z-[999]",
|
||||
isLoading ? "" : "hidden",
|
||||
)}
|
||||
>
|
||||
<LoadingComponent remSize={50} />
|
||||
</div>
|
||||
<Outlet />
|
||||
</>
|
||||
</ErrorBoundary>
|
||||
|
|
|
|||
15
src/frontend/src/pages/LoadingPage/index.tsx
Normal file
15
src/frontend/src/pages/LoadingPage/index.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import LoadingComponent from "@/components/loadingComponent";
|
||||
import { cn } from "@/utils/utils";
|
||||
|
||||
export function LoadingPage({ overlay = false }: { overlay?: boolean }) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-screen w-screen items-center justify-center bg-background",
|
||||
overlay && "fixed left-0 top-0 z-[999]",
|
||||
)}
|
||||
>
|
||||
<LoadingComponent remSize={50} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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({
|
|||
<>
|
||||
<div className="flex w-full gap-4 pb-5">
|
||||
<HeaderComponent
|
||||
disabled={
|
||||
isLoading ||
|
||||
isLoadingFolders ||
|
||||
isLoadingCurrentFolder ||
|
||||
data?.length === 0
|
||||
}
|
||||
disabled={isLoading || data?.length === 0}
|
||||
shouldSelectAll={shouldSelectAll}
|
||||
setShouldSelectAll={setShouldSelectAll}
|
||||
handleDelete={() => handleSelectOptionsChange("delete")}
|
||||
|
|
@ -243,16 +230,11 @@ export default function ComponentsComponent({
|
|||
data-testid="cards-wrapper"
|
||||
>
|
||||
<div className="flex w-full flex-col gap-4">
|
||||
{!isLoading &&
|
||||
!isLoadingFolders &&
|
||||
!isLoadingCurrentFolder &&
|
||||
data?.length === 0 ? (
|
||||
{!isLoading && data?.length === 0 ? (
|
||||
<EmptyComponent />
|
||||
) : (
|
||||
<div className="grid w-full gap-4 md:grid-cols-2 lg:grid-cols-2">
|
||||
{data?.length > 0 &&
|
||||
isLoadingFolders === false &&
|
||||
isLoadingCurrentFolder === false ? (
|
||||
{data?.length > 0 && isLoading === false ? (
|
||||
<>
|
||||
{data?.map((item) => (
|
||||
<FormProvider {...methods} key={item.id}>
|
||||
|
|
|
|||
|
|
@ -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) => {
|
|||
<>
|
||||
<div className="relative flex items-end gap-4">
|
||||
<InputSearchComponent
|
||||
loading={isLoading}
|
||||
loading={loading}
|
||||
value={inputValue}
|
||||
onChange={(e) => {
|
||||
setSearchFlowsComponents(e.target.value);
|
||||
|
|
@ -33,7 +36,7 @@ const HeaderTabsSearchComponent = ({}: HeaderTabsSearchComponentProps) => {
|
|||
<TabsSearchComponent
|
||||
tabsOptions={["All", "Flows", "Components"]}
|
||||
setActiveTab={setTabActive}
|
||||
loading={isLoading}
|
||||
loading={loading}
|
||||
tabActive={tabActive}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<>
|
||||
<HeaderTabsSearchComponent />
|
||||
<HeaderTabsSearchComponent loading={isLoading || isLoadingFolders} />
|
||||
<div className="mt-5 flex h-full flex-col">
|
||||
<ComponentsComponent key={type} type={type} />
|
||||
<ComponentsComponent
|
||||
key={type}
|
||||
type={type}
|
||||
currentFolder={data}
|
||||
isLoading={isLoading || isLoadingFolders}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="flex h-full w-full flex-col items-center justify-center align-middle">
|
||||
{!currentSavedFlow ? (
|
||||
<div>
|
||||
<LoadingComponent remSize={24}></LoadingComponent>
|
||||
</div>
|
||||
) : (
|
||||
{currentSavedFlow && (
|
||||
<IOModal open={true} setOpen={() => {}} isPlayground>
|
||||
<></>
|
||||
</IOModal>
|
||||
|
|
|
|||
|
|
@ -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([
|
||||
<Route path="/" element={<AppWrapperPage />}>
|
||||
<Route
|
||||
path=""
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<Outlet />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
>
|
||||
<Route path="" element={<DashboardWrapperPage />}>
|
||||
<Route path="" element={<HomePage />}>
|
||||
<Route index element={<Navigate replace to={"all"} />} />
|
||||
<Route
|
||||
path="flows/"
|
||||
element={<MyCollectionComponent key="flows" type="flow" />}
|
||||
>
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
<ContextWrapper>
|
||||
<Outlet />
|
||||
</ContextWrapper>
|
||||
}
|
||||
>
|
||||
<Route path="" element={<AppInitPage />}>
|
||||
<Route path="" element={<AppWrapperPage />}>
|
||||
<Route
|
||||
path=""
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<Outlet />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
>
|
||||
<Route path="" element={<DashboardWrapperPage />}>
|
||||
<Route path="" element={<HomePage />}>
|
||||
<Route index element={<Navigate replace to={"all"} />} />
|
||||
<Route
|
||||
path="flows/"
|
||||
element={<MyCollectionComponent key="flows" type="flow" />}
|
||||
>
|
||||
<Route
|
||||
path="folder/:folderId"
|
||||
element={<MyCollectionComponent key="flows" type="flow" />}
|
||||
/>
|
||||
</Route>
|
||||
<Route
|
||||
path="components/"
|
||||
element={
|
||||
<MyCollectionComponent key="components" type="component" />
|
||||
}
|
||||
>
|
||||
<Route
|
||||
path="folder/:folderId"
|
||||
element={
|
||||
<MyCollectionComponent
|
||||
key="components"
|
||||
type="component"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
<Route
|
||||
path="all/"
|
||||
element={<MyCollectionComponent key="all" type="all" />}
|
||||
>
|
||||
<Route
|
||||
path="folder/:folderId"
|
||||
element={<MyCollectionComponent key="all" type="all" />}
|
||||
/>
|
||||
</Route>
|
||||
</Route>
|
||||
<Route path="/settings" element={<SettingsPage />}>
|
||||
<Route index element={<Navigate replace to={"general"} />} />
|
||||
<Route
|
||||
path="global-variables"
|
||||
element={<GlobalVariablesPage />}
|
||||
/>
|
||||
<Route path="api-keys" element={<ApiKeysPage />} />
|
||||
<Route
|
||||
path="general/:scrollId?"
|
||||
element={
|
||||
<AuthSettingsGuard>
|
||||
<GeneralPage />
|
||||
</AuthSettingsGuard>
|
||||
}
|
||||
/>
|
||||
<Route path="shortcuts" element={<ShortcutsPage />} />
|
||||
<Route path="messages" element={<MessagesPage />} />
|
||||
</Route>
|
||||
<Route
|
||||
path="folder/:folderId"
|
||||
element={<MyCollectionComponent key="flows" type="flow" />}
|
||||
/>
|
||||
</Route>
|
||||
<Route
|
||||
path="components/"
|
||||
element={
|
||||
<MyCollectionComponent key="components" type="component" />
|
||||
}
|
||||
>
|
||||
<Route
|
||||
path="folder/:folderId"
|
||||
path="/store"
|
||||
element={
|
||||
<MyCollectionComponent key="components" type="component" />
|
||||
<StoreGuard>
|
||||
<StorePage />
|
||||
</StoreGuard>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/store/:id/"
|
||||
element={
|
||||
<StoreGuard>
|
||||
<StorePage />
|
||||
</StoreGuard>
|
||||
}
|
||||
/>
|
||||
<Route path="/account">
|
||||
<Route path="delete" element={<DeleteAccountPage />}></Route>
|
||||
</Route>
|
||||
<Route
|
||||
path="/admin"
|
||||
element={
|
||||
<ProtectedAdminRoute>
|
||||
<AdminPage />
|
||||
</ProtectedAdminRoute>
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
<Route
|
||||
path="all/"
|
||||
element={<MyCollectionComponent key="all" type="all" />}
|
||||
>
|
||||
<Route
|
||||
path="folder/:folderId"
|
||||
element={<MyCollectionComponent key="all" type="all" />}
|
||||
/>
|
||||
<Route path="/flow/:id/">
|
||||
<Route path="" element={<DashboardWrapperPage />}>
|
||||
<Route path="folder/:folderId/" element={<FlowPage />} />
|
||||
<Route path="" element={<FlowPage />} />
|
||||
</Route>
|
||||
<Route path="view" element={<ViewPage />} />
|
||||
</Route>
|
||||
<Route path="/playground/:id/">
|
||||
<Route path="" element={<PlaygroundPage />} />
|
||||
</Route>
|
||||
</Route>
|
||||
<Route path="/settings" element={<SettingsPage />}>
|
||||
<Route index element={<Navigate replace to={"general"} />} />
|
||||
<Route path="global-variables" element={<GlobalVariablesPage />} />
|
||||
<Route path="api-keys" element={<ApiKeysPage />} />
|
||||
<Route
|
||||
path="general/:scrollId?"
|
||||
element={
|
||||
<AuthSettingsGuard>
|
||||
<GeneralPage />
|
||||
</AuthSettingsGuard>
|
||||
}
|
||||
/>
|
||||
<Route path="shortcuts" element={<ShortcutsPage />} />
|
||||
<Route path="messages" element={<MessagesPage />} />
|
||||
</Route>
|
||||
<Route
|
||||
path="/store"
|
||||
path="/login"
|
||||
element={
|
||||
<StoreGuard>
|
||||
<StorePage />
|
||||
</StoreGuard>
|
||||
<ProtectedLoginRoute>
|
||||
<LoginPage />
|
||||
</ProtectedLoginRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/store/:id/"
|
||||
path="/signup"
|
||||
element={
|
||||
<StoreGuard>
|
||||
<StorePage />
|
||||
</StoreGuard>
|
||||
<ProtectedLoginRoute>
|
||||
<SignUp />
|
||||
</ProtectedLoginRoute>
|
||||
}
|
||||
/>
|
||||
<Route path="/account">
|
||||
<Route path="delete" element={<DeleteAccountPage />}></Route>
|
||||
</Route>
|
||||
<Route
|
||||
path="/admin"
|
||||
path="/login/admin"
|
||||
element={
|
||||
<ProtectedAdminRoute>
|
||||
<AdminPage />
|
||||
</ProtectedAdminRoute>
|
||||
<ProtectedLoginRoute>
|
||||
<LoginAdminPage />
|
||||
</ProtectedLoginRoute>
|
||||
}
|
||||
/>
|
||||
</Route>
|
||||
<Route path="/flow/:id/">
|
||||
<Route path="" element={<DashboardWrapperPage />}>
|
||||
<Route path="folder/:folderId/" element={<FlowPage />} />
|
||||
<Route path="" element={<FlowPage />} />
|
||||
</Route>
|
||||
<Route path="view" element={<ViewPage />} />
|
||||
</Route>
|
||||
<Route path="/playground/:id/">
|
||||
<Route path="" element={<PlaygroundPage />} />
|
||||
<Route path="*" element={<Navigate replace to="/" />} />
|
||||
</Route>
|
||||
</Route>
|
||||
<Route
|
||||
path="/login"
|
||||
element={
|
||||
<ProtectedLoginRoute>
|
||||
<LoginPage />
|
||||
</ProtectedLoginRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/signup"
|
||||
element={
|
||||
<ProtectedLoginRoute>
|
||||
<SignUp />
|
||||
</ProtectedLoginRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/login/admin"
|
||||
element={
|
||||
<ProtectedLoginRoute>
|
||||
<LoginAdminPage />
|
||||
</ProtectedLoginRoute>
|
||||
}
|
||||
/>
|
||||
<Route path="*" element={<Navigate replace to="/" />} />
|
||||
</Route>,
|
||||
]),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ const useAlertStore = create<AlertStoreType>((set, get) => ({
|
|||
notificationCenter: false,
|
||||
notificationList: [],
|
||||
tempNotificationList: [],
|
||||
loading: true,
|
||||
setErrorData: (newState: { title: string; list?: Array<string> }) => {
|
||||
if (newState.title && newState.title !== "") {
|
||||
set({
|
||||
|
|
@ -151,9 +150,6 @@ const useAlertStore = create<AlertStoreType>((set, get) => ({
|
|||
),
|
||||
});
|
||||
},
|
||||
setLoading: (newState: boolean) => {
|
||||
set({ loading: newState });
|
||||
},
|
||||
clearTempNotificationList: () => {
|
||||
set({ tempNotificationList: [] });
|
||||
},
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ const useAuthStore = create<AuthStoreType>((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,
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ import { create } from "zustand";
|
|||
import { FoldersStoreType } from "../types/zustand/folders";
|
||||
|
||||
export const useFolderStore = create<FoldersStoreType>((set, get) => ({
|
||||
selectedFolder: null,
|
||||
setSelectedFolder: (folder) => set(() => ({ selectedFolder: folder })),
|
||||
loadingById: false,
|
||||
setMyCollectionId: (myCollectionId) => {
|
||||
set({ myCollectionId });
|
||||
|
|
|
|||
|
|
@ -248,7 +248,6 @@ export type ResponseErrorDetailAPI = {
|
|||
};
|
||||
export type useQueryFunctionType<T = undefined, R = any> = T extends undefined
|
||||
? (
|
||||
params?: T,
|
||||
options?: Omit<UseQueryOptions, "queryFn" | "queryKey">,
|
||||
) => UseQueryResult<R>
|
||||
: (
|
||||
|
|
|
|||
|
|
@ -15,6 +15,4 @@ export type AlertStoreType = {
|
|||
removeFromTempNotificationList: (index: string) => void;
|
||||
clearNotificationList: () => void;
|
||||
removeFromNotificationList: (index: string) => void;
|
||||
loading: boolean;
|
||||
setLoading: (newState: boolean) => void;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 }) => {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue