diff --git a/litellm/ui/litellm-dashboard/src/app/onboarding/page.tsx b/litellm/ui/litellm-dashboard/src/app/onboarding/page.tsx deleted file mode 100644 index 3bdf57907ee..00000000000 --- a/litellm/ui/litellm-dashboard/src/app/onboarding/page.tsx +++ /dev/null @@ -1,150 +0,0 @@ -"use client"; -import React, { Suspense, useEffect, useState } from "react"; -import { useSearchParams } from "next/navigation"; -import { Card, Title, Text, TextInput, Callout, Button, Grid, Col } from "@tremor/react"; -import { RiCheckboxCircleLine } from "@remixicon/react"; -import { - getOnboardingCredentials, - claimOnboardingToken, - getUiConfig, - getProxyBaseUrl, -} from "@/components/networking"; -import { jwtDecode } from "jwt-decode"; -import { Form, Button as Button2 } from "antd"; -import { getCookie } from "@/utils/cookieUtils"; - -function OnboardingContent() { - const [form] = Form.useForm(); - const searchParams = useSearchParams()!; - const token = getCookie("token"); - const inviteID = searchParams.get("invitation_id"); - const action = searchParams.get("action"); - const [accessToken, setAccessToken] = useState(null); - const [defaultUserEmail, setDefaultUserEmail] = useState(""); - const [userEmail, setUserEmail] = useState(""); - const [userID, setUserID] = useState(null); - const [loginUrl, setLoginUrl] = useState(""); - const [jwtToken, setJwtToken] = useState(""); - const [getUiConfigLoading, setGetUiConfigLoading] = useState(true); - - useEffect(() => { - getUiConfig().then((data) => { - // get the information for constructing the proxy base url, and then set the token and auth loading - console.log("ui config in onboarding.tsx:", data); - setGetUiConfigLoading(false); - }); - }, []); - - useEffect(() => { - if (!inviteID || getUiConfigLoading) { - // wait for the ui config to be loaded - return; - } - - getOnboardingCredentials(inviteID).then((data) => { - const login_url = data.login_url; - console.log("login_url:", login_url); - setLoginUrl(login_url); - - const token = data.token; - const decoded = jwtDecode(token) as { [key: string]: any }; - setJwtToken(token); - - console.log("decoded:", decoded); - setAccessToken(decoded.key); - - console.log("decoded user email:", decoded.user_email); - const user_email = decoded.user_email; - setUserEmail(user_email); - - const user_id = decoded.user_id; - setUserID(user_id); - }); - }, [inviteID, getUiConfigLoading]); - - const handleSubmit = (formValues: Record) => { - console.log("in handle submit. accessToken:", accessToken, "token:", jwtToken, "formValues:", formValues); - if (!accessToken || !jwtToken) { - return; - } - - formValues.user_email = userEmail; - - if (!userID || !inviteID) { - return; - } - claimOnboardingToken(accessToken, inviteID, userID, formValues.password).then((data) => { - // set cookie "token" to jwtToken - document.cookie = "token=" + jwtToken; - - const proxyBaseUrl = getProxyBaseUrl(); - console.log("proxyBaseUrl:", proxyBaseUrl); - - // Construct the full redirect URL using the proxyBaseUrl which includes the server root path - let redirectUrl = proxyBaseUrl ? `${proxyBaseUrl}/ui/?login=success` : "/ui/?login=success"; - console.log("redirecting to:", redirectUrl); - - window.location.href = redirectUrl; - }); - - // redirect to login page - }; - return ( -
- - 🚅 LiteLLM - {action === "reset_password" ? "Reset Password" : "Sign up"} - - {action === "reset_password" - ? "Reset your password to access Admin UI." - : "Claim your user account to login to Admin UI."} - - - {action !== "reset_password" && ( - - - SSO is under the Enterprise Tier. - - - - - - - )} - -
- <> - - - - - - - - - -
- {action === "reset_password" ? "Reset Password" : "Sign Up"} -
-
-
-
- ); -} - -export default function Onboarding() { - return ( - Loading...}> - - - ); -} diff --git a/ui/litellm-dashboard/src/app/onboarding/OnboardingErrorView.test.tsx b/ui/litellm-dashboard/src/app/onboarding/OnboardingErrorView.test.tsx index f2252a047a6..d7a7ffb1b15 100644 --- a/ui/litellm-dashboard/src/app/onboarding/OnboardingErrorView.test.tsx +++ b/ui/litellm-dashboard/src/app/onboarding/OnboardingErrorView.test.tsx @@ -4,19 +4,19 @@ import { describe, it, expect } from "vitest"; import { OnboardingErrorView } from "./OnboardingErrorView"; describe("OnboardingErrorView", () => { - it("shows the failed to load invitation message", () => { + it("should show the failed to load invitation message", () => { render(); expect(screen.getByText("Failed to load invitation")).toBeInTheDocument(); }); - it("shows the expiry description", () => { + it("should show the expiry description", () => { render(); expect( screen.getByText("The invitation link may be invalid or expired.") ).toBeInTheDocument(); }); - it("renders a Back to Login link pointing to /ui/login", () => { + it("should render a Back to Login link pointing to /ui/login", () => { render(); // antd Button with href renders as an element const link = screen.getByRole("link", { name: "Back to Login" }); diff --git a/ui/litellm-dashboard/src/app/onboarding/OnboardingFormBody.test.tsx b/ui/litellm-dashboard/src/app/onboarding/OnboardingFormBody.test.tsx index a5bfae3f880..f742176d1ba 100644 --- a/ui/litellm-dashboard/src/app/onboarding/OnboardingFormBody.test.tsx +++ b/ui/litellm-dashboard/src/app/onboarding/OnboardingFormBody.test.tsx @@ -17,55 +17,55 @@ describe("OnboardingFormBody", () => { vi.clearAllMocks(); }); - it("shows 'Sign Up' heading for signup variant", () => { + it("should show 'Sign Up' heading for signup variant", () => { render(); expect(screen.getByRole("heading", { name: "Sign Up" })).toBeInTheDocument(); }); - it("shows 'Reset Password' heading for reset_password variant", () => { + it("should show 'Reset Password' heading for reset_password variant", () => { render(); expect(screen.getByRole("heading", { name: "Reset Password" })).toBeInTheDocument(); }); - it("shows SSO alert for signup variant", () => { + it("should show SSO alert for signup variant", () => { render(); expect(screen.getByText("SSO")).toBeInTheDocument(); }); - it("hides SSO alert for reset_password variant", () => { + it("should hide SSO alert for reset_password variant", () => { render(); expect(screen.queryByText("SSO")).not.toBeInTheDocument(); }); - it("pre-fills the email field with userEmail", async () => { + it("should pre-fill the email field with userEmail", async () => { render(); await waitFor(() => { expect(screen.getByLabelText("Email Address")).toHaveValue("user@example.com"); }); }); - it("disables the email field", () => { + it("should disable the email field", () => { render(); expect(screen.getByLabelText("Email Address")).toBeDisabled(); }); - it("shows claimError message when claimError is set", () => { + it("should show claimError message when claimError is set", () => { render(); expect(screen.getByText("Something went wrong")).toBeInTheDocument(); }); - it("does not show claimError message when claimError is null", () => { + it("should not show claimError message when claimError is null", () => { render(); expect(screen.queryByText("Something went wrong")).not.toBeInTheDocument(); }); - it("shows a loading indicator on the submit button when isPending is true", () => { + it("should show a loading indicator on the submit button when isPending is true", () => { render(); // antd v5 renders a loading icon with aria-label="loading" inside the button expect(screen.getByRole("img", { name: "loading" })).toBeInTheDocument(); }); - it("calls onSubmit with the typed password on form submit", async () => { + it("should call onSubmit with the typed password on form submit", async () => { const user = userEvent.setup(); const onSubmit = vi.fn(); render(); @@ -80,7 +80,7 @@ describe("OnboardingFormBody", () => { }); }); - it("shows 'Reset Password' on the submit button for reset_password variant", () => { + it("should show 'Reset Password' on the submit button for reset_password variant", () => { render(); expect( screen.getByRole("button", { name: /reset password/i }) diff --git a/ui/litellm-dashboard/src/app/onboarding/OnboardingLoadingView.test.tsx b/ui/litellm-dashboard/src/app/onboarding/OnboardingLoadingView.test.tsx index b84c678b9dc..21c5ccf69d0 100644 --- a/ui/litellm-dashboard/src/app/onboarding/OnboardingLoadingView.test.tsx +++ b/ui/litellm-dashboard/src/app/onboarding/OnboardingLoadingView.test.tsx @@ -4,12 +4,12 @@ import { describe, it, expect } from "vitest"; import { OnboardingLoadingView } from "./OnboardingLoadingView"; describe("OnboardingLoadingView", () => { - it("renders a spinner container", () => { + it("should render a spinner container", () => { const { container } = render(); expect(container.firstChild).toBeInTheDocument(); }); - it("applies centering layout classes", () => { + it("should apply centering layout classes", () => { const { container } = render(); expect(container.firstChild).toHaveClass("flex", "justify-center"); }); diff --git a/ui/litellm-dashboard/src/app/onboarding/page.tsx b/ui/litellm-dashboard/src/app/onboarding/page.tsx index d7840a7be3a..f424c9e6288 100644 --- a/ui/litellm-dashboard/src/app/onboarding/page.tsx +++ b/ui/litellm-dashboard/src/app/onboarding/page.tsx @@ -1,8 +1,11 @@ "use client"; import React, { Suspense } from "react"; import { useSearchParams } from "next/navigation"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { OnboardingForm } from "./OnboardingForm"; +const queryClient = new QueryClient(); + function OnboardingContent() { const searchParams = useSearchParams()!; const action = searchParams.get("action"); @@ -12,12 +15,14 @@ function OnboardingContent() { export default function Onboarding() { return ( - Loading... - } - > - - + + Loading... + } + > + + + ); }