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:
Mamadou DICKO 2023-07-13 18:05:08 +02:00 committed by GitHub
parent f65044e152
commit bd0aa01ba1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 106 additions and 45 deletions

View File

@ -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", () => {

View File

@ -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,

View File

@ -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">

View File

@ -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 (

View File

@ -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 (

View File

@ -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 (

View File

@ -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 (

View File

@ -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(

View File

@ -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(

View File

@ -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`

View 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);
});
});

View 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);
};