mirror of
https://github.com/QuivrHQ/quivr.git
synced 2024-09-11 14:36:35 +03:00
Shareable brain 6 (#628)
* feat: add redirectToLogin helper * feat: use redirectToLogin instead of redirect('login') * feat: redirect to initial targeted page after login
This commit is contained in:
parent
f65044e152
commit
bd0aa01ba1
@ -3,15 +3,10 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import Login from "../page";
|
||||
|
||||
const mockUseSearchParams = vi.fn(() => ({
|
||||
get: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockRedirect = vi.fn((url: string) => ({ url }));
|
||||
|
||||
vi.mock("next/navigation", () => ({
|
||||
redirect: (url: string) => mockRedirect(url),
|
||||
useSearchParams: () => mockUseSearchParams(),
|
||||
}));
|
||||
|
||||
const mockUseSupabase = vi.fn(() => ({
|
||||
@ -40,13 +35,31 @@ describe("Login component", () => {
|
||||
});
|
||||
|
||||
it('redirects to "/previous-page" if user is already signed in and previous page is set', () => {
|
||||
const previousPageUrl = "/my-interesting-page";
|
||||
mockUseSearchParams.mockReturnValue({
|
||||
get: vi.fn(() => previousPageUrl),
|
||||
const currentPage = "/my-awesome-page";
|
||||
|
||||
Object.defineProperty(window, "location", {
|
||||
value: { pathname: currentPage },
|
||||
});
|
||||
|
||||
const sessionStorageData: Record<string, string> = {
|
||||
"previous-page": currentPage,
|
||||
};
|
||||
|
||||
const sessionStorageMock = {
|
||||
getItem: vi.fn((key: string) => sessionStorageData[key]),
|
||||
setItem: vi.fn(
|
||||
(key: string, value: string) => (sessionStorageData[key] = value)
|
||||
),
|
||||
removeItem: vi.fn((key: string) => delete sessionStorageData[key]),
|
||||
};
|
||||
|
||||
Object.defineProperty(window, "sessionStorage", {
|
||||
value: sessionStorageMock,
|
||||
});
|
||||
|
||||
render(<Login />);
|
||||
expect(mockRedirect).toHaveBeenCalledTimes(1);
|
||||
expect(mockRedirect).toHaveBeenCalledWith(previousPageUrl);
|
||||
expect(mockRedirect).toHaveBeenCalledWith(currentPage);
|
||||
});
|
||||
|
||||
it("should render the login form when user is not signed in", () => {
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { useState } from "react";
|
||||
import { redirect } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||
import { useToast } from "@/lib/hooks";
|
||||
import { useEventTracking } from "@/services/analytics/useEventTracking";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useLogin = () => {
|
||||
@ -9,7 +11,9 @@ export const useLogin = () => {
|
||||
const [password, setPassword] = useState("");
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
const { publish } = useToast();
|
||||
const { supabase } = useSupabase();
|
||||
const { supabase, session } = useSupabase();
|
||||
|
||||
const { track } = useEventTracking();
|
||||
|
||||
const handleLogin = async () => {
|
||||
setIsPending(true);
|
||||
@ -32,6 +36,20 @@ export const useLogin = () => {
|
||||
setIsPending(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (session?.user !== undefined) {
|
||||
void track("SIGNED_IN");
|
||||
|
||||
const previousPage = sessionStorage.getItem("previous-page");
|
||||
if (previousPage === null) {
|
||||
redirect("/upload");
|
||||
} else {
|
||||
sessionStorage.removeItem("previous-page");
|
||||
redirect(previousPage);
|
||||
}
|
||||
}
|
||||
}, [session?.user]);
|
||||
|
||||
return {
|
||||
handleLogin,
|
||||
setEmail,
|
||||
|
@ -1,39 +1,22 @@
|
||||
/* eslint-disable */
|
||||
"use client";
|
||||
import Link from "next/link";
|
||||
import { redirect, useSearchParams } from "next/navigation";
|
||||
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
import Card from "@/lib/components/ui/Card";
|
||||
import { Divider } from "@/lib/components/ui/Divider";
|
||||
import Field from "@/lib/components/ui/Field";
|
||||
import PageHeading from "@/lib/components/ui/PageHeading";
|
||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||
|
||||
import { useEventTracking } from "@/services/analytics/useEventTracking";
|
||||
import { GoogleLoginButton } from "./components/GoogleLogin";
|
||||
import { MagicLinkLogin } from "./components/MagicLinkLogin";
|
||||
import { PasswordForgotten } from "./components/PasswordForgotten";
|
||||
import { useLogin } from "./hooks/useLogin";
|
||||
|
||||
export default function Login() {
|
||||
const { session } = useSupabase();
|
||||
const { track } = useEventTracking();
|
||||
const { handleLogin, setEmail, setPassword, email, isPending, password } =
|
||||
useLogin();
|
||||
|
||||
const params = useSearchParams();
|
||||
const previousPage = params?.get("previous-page");
|
||||
|
||||
if (session?.user !== undefined) {
|
||||
void track("SIGNED_IN");
|
||||
if (previousPage === undefined || previousPage === null) {
|
||||
redirect("/upload");
|
||||
} else {
|
||||
redirect(previousPage);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<main>
|
||||
<section className="w-full min-h-[80vh] h-full outline-none flex flex-col gap-5 items-center justify-center p-6">
|
||||
|
@ -1,6 +1,5 @@
|
||||
/* eslint-disable */
|
||||
"use client";
|
||||
import { redirect, usePathname } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
@ -9,6 +8,7 @@ import Field from "@/lib/components/ui/Field";
|
||||
import PageHeading from "@/lib/components/ui/PageHeading";
|
||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||
import { useToast } from "@/lib/hooks/useToast";
|
||||
import { redirectToLogin } from "@/lib/router/redirectToLogin";
|
||||
import { useEventTracking } from "@/services/analytics/useEventTracking";
|
||||
|
||||
export default function RecoverPassword() {
|
||||
@ -16,12 +16,12 @@ export default function RecoverPassword() {
|
||||
const [password, setPassword] = useState("");
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
const { track } = useEventTracking();
|
||||
const pathname = usePathname();
|
||||
|
||||
const { publish } = useToast();
|
||||
const handleChangePassword = async () => {
|
||||
void track("UPDATE_PASSWORD");
|
||||
setIsPending(true);
|
||||
const { data, error } = await supabase.auth.updateUser({
|
||||
const { error } = await supabase.auth.updateUser({
|
||||
password: password,
|
||||
});
|
||||
|
||||
@ -41,7 +41,7 @@ export default function RecoverPassword() {
|
||||
};
|
||||
|
||||
if (session?.user === undefined) {
|
||||
redirect(`/login?previous-page=${pathname}`);
|
||||
redirectToLogin();
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -1,9 +1,9 @@
|
||||
"use client";
|
||||
import { redirect } from "next/navigation";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
import { ChatsProvider } from "@/lib/context/ChatsProvider/chats-provider";
|
||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||
import { redirectToLogin } from "@/lib/router/redirectToLogin";
|
||||
|
||||
import { ChatsList } from "./components/ChatsList";
|
||||
|
||||
@ -14,7 +14,7 @@ interface LayoutProps {
|
||||
const Layout = ({ children }: LayoutProps): JSX.Element => {
|
||||
const { session } = useSupabase();
|
||||
if (session === null) {
|
||||
redirect("/login");
|
||||
redirectToLogin();
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -1,8 +1,8 @@
|
||||
/* eslint-disable */
|
||||
"use client";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||
import { redirectToLogin } from "@/lib/router/redirectToLogin";
|
||||
import { ApiKeyConfig, ConfigForm, ConfigTitle } from "./components";
|
||||
|
||||
// TODO: Use states instead of NEXTJS router to open and close modal
|
||||
@ -10,7 +10,7 @@ const ConfigPage = (): JSX.Element => {
|
||||
const { session } = useSupabase();
|
||||
|
||||
if (session === null) {
|
||||
redirect("/login");
|
||||
redirectToLogin();
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -2,12 +2,12 @@
|
||||
"use client";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
import Spinner from "@/lib/components/ui/Spinner";
|
||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||
|
||||
import { redirectToLogin } from "@/lib/router/redirectToLogin";
|
||||
import DocumentItem from "./DocumentItem";
|
||||
import { useExplore } from "./hooks/useExplore";
|
||||
|
||||
@ -15,7 +15,7 @@ const ExplorePage = (): JSX.Element => {
|
||||
const { session } = useSupabase();
|
||||
const { documents, setDocuments, isPending } = useExplore();
|
||||
if (session === null) {
|
||||
redirect("/login");
|
||||
redirectToLogin();
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -1,11 +1,11 @@
|
||||
/* eslint-disable */
|
||||
import { redirect } from "next/navigation";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
|
||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||
import { useAxios, useToast } from "@/lib/hooks";
|
||||
import { useEventTracking } from "@/services/analytics/useEventTracking";
|
||||
|
||||
import { redirectToLogin } from "@/lib/router/redirectToLogin";
|
||||
import { UUID } from "crypto";
|
||||
import { isValidUrl } from "../helpers/isValidUrl";
|
||||
|
||||
@ -18,7 +18,7 @@ export const useCrawler = () => {
|
||||
const { track } = useEventTracking();
|
||||
|
||||
if (session === null) {
|
||||
redirect("/login");
|
||||
redirectToLogin();
|
||||
}
|
||||
|
||||
const crawlWebsite = useCallback(
|
||||
|
@ -1,11 +1,11 @@
|
||||
/* eslint-disable */
|
||||
import { redirect } from "next/navigation";
|
||||
import { useCallback, useState } from "react";
|
||||
import { FileRejection, useDropzone } from "react-dropzone";
|
||||
|
||||
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||
import { useAxios, useToast } from "@/lib/hooks";
|
||||
import { redirectToLogin } from "@/lib/router/redirectToLogin";
|
||||
import { useEventTracking } from "@/services/analytics/useEventTracking";
|
||||
import { UUID } from "crypto";
|
||||
|
||||
@ -20,7 +20,7 @@ export const useFileUploader = () => {
|
||||
const { axiosInstance } = useAxios();
|
||||
|
||||
if (session === null) {
|
||||
redirect("/login");
|
||||
redirectToLogin();
|
||||
}
|
||||
|
||||
const upload = useCallback(
|
||||
|
@ -1,6 +1,5 @@
|
||||
/* eslint-disable */
|
||||
"use client";
|
||||
import { redirect } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import Spinner from "@/lib/components/ui/Spinner";
|
||||
@ -8,19 +7,20 @@ import { useAxios } from "@/lib/hooks";
|
||||
import { UserStats } from "@/lib/types/User";
|
||||
|
||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||
import { redirectToLogin } from "@/lib/router/redirectToLogin";
|
||||
import { UserStatistics } from "./components/UserStatistics";
|
||||
|
||||
const UserPage = (): JSX.Element => {
|
||||
const [userStats, setUserStats] = useState<UserStats>();
|
||||
const { session } = useSupabase();
|
||||
const { axiosInstance } = useAxios();
|
||||
|
||||
if (session === null) {
|
||||
redirect("/login");
|
||||
redirectToLogin();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const fetchUserStats = async () => {
|
||||
// setIsPending(true);
|
||||
try {
|
||||
console.log(
|
||||
`Fetching user stats from ${process.env.NEXT_PUBLIC_BACKEND_URL}/user`
|
||||
|
37
frontend/lib/router/__tests__/redirectToLogin.test.ts
Normal file
37
frontend/lib/router/__tests__/redirectToLogin.test.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { RedirectType } from "next/dist/client/components/redirect";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { redirectToLogin } from "../redirectToLogin";
|
||||
|
||||
const currentPage = "/my-awesome-page";
|
||||
|
||||
const redirectMock = vi.fn((...params: unknown[]) => ({ params }));
|
||||
|
||||
vi.mock("next/navigation", () => ({
|
||||
redirect: (...params: unknown[]) => redirectMock(...params),
|
||||
}));
|
||||
|
||||
const sessionStorageData: Record<string, string> = {};
|
||||
|
||||
const sessionStorageMock = {
|
||||
getItem: vi.fn((key: string) => sessionStorageData[key]),
|
||||
setItem: vi.fn(
|
||||
(key: string, value: string) => (sessionStorageData[key] = value)
|
||||
),
|
||||
};
|
||||
|
||||
Object.defineProperty(window, "sessionStorage", {
|
||||
value: sessionStorageMock,
|
||||
});
|
||||
Object.defineProperty(window, "location", {
|
||||
value: { pathname: currentPage },
|
||||
});
|
||||
|
||||
describe("redirectToLogin", () => {
|
||||
it("should save previous page before redirection", () => {
|
||||
redirectToLogin(RedirectType.push);
|
||||
expect(sessionStorage.getItem("previous-page")).toBe(currentPage);
|
||||
expect(redirectMock).toHaveBeenCalledTimes(1);
|
||||
expect(redirectMock).toHaveBeenCalledWith("/login", RedirectType.push);
|
||||
});
|
||||
});
|
10
frontend/lib/router/redirectToLogin.ts
Normal file
10
frontend/lib/router/redirectToLogin.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { RedirectType } from "next/dist/client/components/redirect";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
type RedirectToLogin = (type?: RedirectType) => never;
|
||||
|
||||
export const redirectToLogin: RedirectToLogin = (type?: RedirectType) => {
|
||||
sessionStorage.setItem("previous-page", window.location.pathname);
|
||||
|
||||
redirect("/login", type);
|
||||
};
|
Loading…
Reference in New Issue
Block a user