Mamadou DICKO 2023-10-30 14:52:47 +01:00 committed by GitHub
parent e3a99d1ace
commit 9be4a57979
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 90 additions and 127 deletions

View File

@ -1,51 +0,0 @@
import { render } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import Logout from "../page";
// mocking related hooks
const mockUseLogout = vi.fn(() => ({
handleLogout: vi.fn(),
isPending: false,
}));
vi.mock("../hooks/useLogout", () => ({
useLogout: () => mockUseLogout(),
}));
describe("Logout component", () => {
it("should render correctly", () => {
const { getByTestId } = render(<Logout />);
const logoutPage = getByTestId("logout-page");
expect(logoutPage).toBeDefined();
});
it("should call handleLogout 1 time when logout button is clicked", () => {
const mockHandleLogout = vi.fn();
mockUseLogout.mockReturnValue({
handleLogout: mockHandleLogout,
isPending: false,
});
const { getByTestId } = render(<Logout />);
const logoutButton = getByTestId("logout-button");
logoutButton.click();
expect(mockHandleLogout).toHaveBeenCalledTimes(1);
});
it("should not call handleLogout when isPending is true", () => {
const mockHandleLogout = vi.fn();
mockUseLogout.mockReturnValue({
handleLogout: mockHandleLogout,
isPending: true,
});
const { getByTestId } = render(<Logout />);
const logoutButton = getByTestId("logout-button");
logoutButton.click();
expect(mockHandleLogout).toHaveBeenCalledTimes(0);
});
});

View File

@ -1,50 +0,0 @@
/* eslint-disable */
"use client";
import Link from "next/link";
import Button from "@/lib/components/ui/Button";
import Card from "@/lib/components/ui/Card";
import PageHeading from "@/lib/components/ui/PageHeading";
import { useLogout } from "./hooks/useLogout";
import { useTranslation } from "react-i18next";
import { Suspense } from "react";
export default function Logout() {
const {t, i18n} = useTranslation(["translation","logout"]);
const { handleLogout, isPending } = useLogout();
function Logout() {
return (
<main data-testid="logout-page">
<section className="w-full min-h-[80vh] h-full outline-none flex flex-col gap-5 items-center justify-center p-6">
<PageHeading title={t("title",{ ns: "logout" })} subtitle={t("subtitle",{ ns: "logout" })} />
<Card className="max-w-md w-full p-5 sm:p-10 text-center flex flex-col items-center gap-5">
<h2 className="text-lg">{t("areYouSure",{ ns: "logout" })}</h2>
<div className="flex gap-5 items-center justify-center">
<Link href={"/"}>
<Button variant={"primary"}>{t("cancel",{ ns: "logout" })}</Button>
</Link>
<Button
isLoading={isPending}
variant={"danger"}
onClick={() => handleLogout()}
data-testid="logout-button"
>
{t("logoutButton")}
</Button>
</div>
</Card>
</section>
</main>
);
}
return (
<Suspense fallback={"Loading..."}>
<Logout />
</Suspense>
)
}

View File

@ -9,7 +9,7 @@ import { PublicBrain } from "@/lib/context/BrainProvider/types";
export const useBrainsLibrary = () => {
const [searchBarText, setSearchBarText] = useState("");
const { getPublicBrains } = useBrainApi();
const { data: publicBrains = [] } = useQuery({
const { data: publicBrains = [], isLoading } = useQuery({
queryKey: [PUBLIC_BRAINS_KEY],
queryFn: getPublicBrains,
});
@ -39,5 +39,6 @@ export const useBrainsLibrary = () => {
displayingPublicBrains,
searchBarText,
setSearchBarText,
isLoading,
};
};

View File

@ -3,12 +3,13 @@
import { useTranslation } from "react-i18next";
import Field from "@/lib/components/ui/Field";
import Spinner from "@/lib/components/ui/Spinner";
import { PublicBrainItem } from "./components/PublicBrainItem/PublicBrainItem";
import { useBrainsLibrary } from "./hooks/useBrainsLibrary";
const BrainsLibrary = (): JSX.Element => {
const { displayingPublicBrains, searchBarText, setSearchBarText } =
const { displayingPublicBrains, searchBarText, setSearchBarText, isLoading } =
useBrainsLibrary();
const { t } = useTranslation("brain");
@ -23,6 +24,11 @@ const BrainsLibrary = (): JSX.Element => {
placeholder={t("public_brains_search_bar_placeholder")}
/>
</div>
{isLoading && (
<div className="flex justify-center items-center flex-1">
<Spinner className="text-4xl" />
</div>
)}
<div className="flex flex-wrap justify-stretch w-full">
{displayingPublicBrains.map((brain) => (

View File

@ -44,7 +44,7 @@ export const ChatInput = ({
onClick={() => setShouldDisplayFeedCard(true)}
tooltip={t("add_content_card_button_tooltip")}
>
<PiPaperclipFill className="text-3xl" />
<PiPaperclipFill size={38} />
</Button>
)}

View File

@ -0,0 +1,51 @@
import { useTranslation } from "react-i18next";
import Button from "@/lib/components/ui/Button";
import { Modal } from "@/lib/components/ui/Modal";
import { useLogoutModal } from "./hooks/useLogoutModal";
export const LogoutModal = (): JSX.Element => {
const { t } = useTranslation(["translation", "logout"]);
const {
handleLogout,
isLoggingOut,
isLogoutModalOpened,
setIsLogoutModalOpened,
} = useLogoutModal();
return (
<Modal
Trigger={
<Button className="px-3 py-2" variant="secondary">
{t("logoutButton")}
</Button>
}
isOpen={isLogoutModalOpened}
setOpen={setIsLogoutModalOpened}
CloseTrigger={<div />}
>
<div className="text-center flex flex-col items-center gap-5">
<h2 className="text-lg font-medium mb-5">
{t("areYouSure", { ns: "logout" })}
</h2>
<div className="flex gap-5 items-center justify-center">
<Button
onClick={() => setIsLogoutModalOpened(false)}
variant={"primary"}
>
{t("cancel", { ns: "logout" })}
</Button>
<Button
isLoading={isLoggingOut}
variant={"danger"}
onClick={() => void handleLogout()}
data-testid="logout-button"
>
{t("logoutButton")}
</Button>
</div>
</div>
</Modal>
);
};

View File

@ -1,7 +1,7 @@
import { act, renderHook } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { useLogout } from "../useLogout";
import { useLogoutModal } from "../useLogoutModal";
const mockSignOut = vi.fn(() => ({ error: null }));
@ -28,9 +28,9 @@ Object.defineProperty(window, "localStorage", {
},
});
describe("useLogout", () => {
describe("useLogoutModal", () => {
it("should call signOut", async () => {
const { result } = renderHook(() => useLogout());
const { result } = renderHook(() => useLogoutModal());
await act(() => result.current.handleLogout());

View File

@ -7,19 +7,20 @@ import { useToast } from "@/lib/hooks";
import { useEventTracking } from "@/services/analytics/june/useEventTracking";
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const useLogout = () => {
export const useLogoutModal = () => {
const { supabase } = useSupabase();
const [isPending, setIsPending] = useState(false);
const [isLoggingOut, setIsLoggingOut] = useState(false);
const [isLogoutModalOpened, setIsLogoutModalOpened] = useState(false);
const { track } = useEventTracking();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { t, i18n } = useTranslation(["translation", "logout"]);
const { t } = useTranslation(["translation", "logout"]);
const { publish } = useToast();
const router = useRouter();
const handleLogout = async () => {
setIsPending(true);
setIsLoggingOut(true);
const { error } = await supabase.auth.signOut();
void track("LOGOUT");
localStorage.clear();
@ -37,11 +38,13 @@ export const useLogout = () => {
});
router.replace("/");
}
setIsPending(false);
setIsLoggingOut(false);
};
return {
handleLogout,
isPending,
isLoggingOut,
isLogoutModalOpened,
setIsLogoutModalOpened,
};
};

View File

@ -10,6 +10,7 @@ import { redirectToLogin } from "@/lib/router/redirectToLogin";
import { StripePricingOrManageButton, UserStatistics } from "./components";
import { ApiKeyConfig } from "./components/ApiKeyConfig";
import LanguageSelect from "./components/LanguageDropDown/LanguageSelect";
import { LogoutModal } from "./components/LogoutCard/LogoutModal";
import ThemeSelect from "./components/ThemeSelect/ThemeSelect";
const UserPage = (): JSX.Element => {
@ -42,11 +43,8 @@ const UserPage = (): JSX.Element => {
<p>
<strong>{t("email")}:</strong> <span>{user.email}</span>
</p>
<Link href={"/logout"}>
<Button className="px-3 py-2" variant="secondary">
{t("logoutButton")}
</Button>
</Link>
<LogoutModal />
</div>
<StripePricingOrManageButton />
</CardBody>

View File

@ -1,7 +1,12 @@
import { FaSpinner } from "react-icons/fa";
const Spinner = (): JSX.Element => {
return <FaSpinner className="animate-spin m-5" />;
import { cn } from "@/lib/utils";
type SpinnerProps = {
className?: string;
};
const Spinner = ({ className }: SpinnerProps): JSX.Element => {
return <FaSpinner className={cn("animate-spin m-5", className)} />;
};
export default Spinner;

View File

@ -1,8 +1,8 @@
{
"title": "Logout",
"subtitle": "See you next time",
"areYouSure": "Are you sure you want to sign out?",
"cancel": "Go back",
"areYouSure": "Are you sure you want to sign out ?",
"cancel": "Cancel",
"error": "Error on logout {{errorMessage}}",
"loggedOut": "Logged out successfully"
}

View File

@ -1,6 +1,6 @@
{
"areYouSure": "¿Seguro que quieres cerrar la sesión?",
"cancel": "Regresar",
"cancel": "Cancelar",
"error": "Error al cerrar sesión {{errorMessage}}",
"loggedOut": "Sesión finalizada",
"subtitle": "Hasta pronto",

View File

@ -2,7 +2,7 @@
"title": "Déconnexion",
"subtitle": "À la prochaine",
"areYouSure": "Êtes-vous sûr de vouloir vous déconnecter ?",
"cancel": "Retour",
"cancel": "Annuler",
"error": "Erreur lors de la déconnexion {{errorMessage}}",
"loggedOut": "Déconnexion réussie"
}

View File

@ -2,7 +2,7 @@
"title": "Sair",
"subtitle": "Até a próxima vez",
"areYouSure": "Você tem certeza de que deseja sair?",
"cancel": "Voltar",
"cancel": "Cancelar",
"error": "Erro ao fazer logout {{errorMessage}}",
"loggedOut": "Saiu com sucesso"
}

View File

@ -2,7 +2,7 @@
"title": "Выход",
"subtitle": "Увидимся в следующий раз",
"areYouSure": "Вы уверены, что хотите выйти?",
"cancel": "Вернуться",
"cancel": "Отмена",
"error": "Ошибка при выходе: {{errorMessage}}",
"loggedOut": "Успешный выход"
}

View File

@ -2,7 +2,7 @@
"title": "注销",
"subtitle": "下次见",
"areYouSure": "是否确实要注销?",
"cancel": "返回",
"cancel": "取消",
"error": "注销时出错 {{errorMessage}}",
"loggedOut": "注销成功"
}