Mamadou DICKO 2023-10-26 09:48:41 +02:00 committed by GitHub
parent c3acb2901c
commit 7e70e4fc84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 109 additions and 932 deletions

View File

@ -19,13 +19,17 @@ vi.mock("@/lib/context/SupabaseProvider", () => ({
useSupabase: () => mockUseSupabase(),
}));
vi.mock("@/lib/assets/QuivrLogo", () => ({
QuivrLogo: () => <div>LOGO</div>,
}));
vi.mock("@/services/analytics/june/useEventTracking", () => ({
useEventTracking: () => ({ track: vi.fn() }),
}));
describe("Login component", () => {
afterEach(() => {
vi.clearAllMocks();
vi.restoreAllMocks();
});
it("redirects to /upload if user is already signed in and is not coming from another page", () => {
@ -68,7 +72,7 @@ describe("Login component", () => {
session: { user: undefined },
});
const { getByTestId } = render(<Login />);
const signInForm = getByTestId("sign-in-form");
const signInForm = getByTestId("sign-in-card");
expect(signInForm).toBeDefined();
});
});

View File

@ -1,4 +1,5 @@
import { useTranslation } from "react-i18next";
import { FcGoogle } from "react-icons/fc";
import Button from "@/lib/components/ui/Button";
@ -12,10 +13,11 @@ export const GoogleLoginButton = (): JSX.Element => {
<Button
onClick={() => void signInWithGoogle()}
isLoading={isPending}
variant={"danger"}
type="button"
data-testid="google-login-button"
className="font-normal bg-white text-black py-2 hover:text-white"
>
<FcGoogle />
{t("googleLogin", { ns: "login" })}
</Button>
);

View File

@ -1,13 +1,14 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useSupabase } from "@/lib/context/SupabaseProvider";
import { useToast } from "@/lib/hooks";
type UseMagicLinkLoginProps = {
email: string;
setEmail: (email: string) => void;
};
import { useSupabase } from "@/lib/context/SupabaseProvider";
import { useToast } from "@/lib/hooks";
export const useMagicLinkLogin = ({
email,
setEmail,
@ -17,14 +18,14 @@ export const useMagicLinkLogin = ({
} => {
const { supabase } = useSupabase();
const [isPending, setIsPending] = useState(false);
const { t } = useTranslation("login");
const { publish } = useToast();
const handleMagicLinkLogin = async () => {
if (email === "") {
publish({
variant: "danger",
text: "Please enter your email address",
text: t("errorMailMissed"),
});
return;

View File

@ -24,11 +24,11 @@ export const MagicLinkLogin = ({
return (
<Button
type="button"
variant={"tertiary"}
onClick={() => void handleMagicLinkLogin()}
isLoading={isPending}
className="bg-black text-white py-2 font-normal"
>
{t("magicLink", { ns: "login" })}
{t("magicLink")}
</Button>
);
};

View File

@ -1,50 +0,0 @@
import { act, renderHook } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { usePasswordForgotten } from "../usePasswordForgotten";
const mockResetPasswordForEmail = vi.fn(() => ({ error: null }));
const mockUseSupabase = () => ({
supabase: {
auth: {
resetPasswordForEmail: mockResetPasswordForEmail,
},
},
});
vi.mock("@/lib/context/SupabaseProvider", () => ({
useSupabase: () => mockUseSupabase(),
}));
const setEmail = vi.fn();
describe("usePassword", () => {
it("should not call resetPasswordForEmail if email is empty", async () => {
const { result } = renderHook(() =>
usePasswordForgotten({
email: "",
setEmail,
})
);
await act(() => result.current.handleRecoverPassword());
expect(mockResetPasswordForEmail).toHaveBeenCalledTimes(0);
});
it("should call resetPasswordForEmail with proper arguments", async () => {
const email = "user@quivr.app";
const { result } = renderHook(() =>
usePasswordForgotten({
email,
setEmail,
})
);
await act(() => result.current.handleRecoverPassword());
expect(mockResetPasswordForEmail).toHaveBeenCalledTimes(1);
expect(mockResetPasswordForEmail).toHaveBeenCalledWith(email, {
redirectTo: `${window.location.origin}/recover-password`,
});
});
});

View File

@ -1,59 +0,0 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useSupabase } from "@/lib/context/SupabaseProvider";
import { useToast } from "@/lib/hooks";
type UsePasswordForgottenProps = {
email: string;
setEmail: (email: string) => void;
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const usePasswordForgotten = ({
email,
setEmail,
}: UsePasswordForgottenProps) => {
const [isPending, setIsPending] = useState(false);
const { supabase } = useSupabase();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const {t, i18n} = useTranslation(["login"]);
const { publish } = useToast();
const handleRecoverPassword = async () => {
if (email === "") {
publish({
variant: "danger",
text: t("errorMailMissed",{ ns: 'login' })
});
return;
}
setIsPending(true);
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${window.location.origin}/recover-password`,
});
if (error) {
publish({
variant: "danger",
text: error.message,
});
} else {
publish({
variant: "success",
text: t("recoveryMailSended",{ ns: 'login' })
});
setEmail("");
}
setIsPending(false);
};
return {
isPending,
handleRecoverPassword,
};
};

View File

@ -1,33 +0,0 @@
/* eslint-disable */
"use client";
import Button from "@/lib/components/ui/Button";
import { usePasswordForgotten } from "./hooks/usePasswordForgotten";
import { useTranslation } from "react-i18next";
type PasswordForgottenProps = {
email: string;
setEmail: (email: string) => void;
};
export const PasswordForgotten = ({
email,
setEmail,
}: PasswordForgottenProps) => {
const { isPending, handleRecoverPassword } = usePasswordForgotten({
email,
setEmail,
});
const {t, i18n} = useTranslation(["login"]);
return (
<Button
type="button"
variant={"tertiary"}
onClick={handleRecoverPassword}
isLoading={isPending}
>
{t("forgottenPassword",{ ns: 'login' })}
</Button>
);
};

View File

@ -1,38 +0,0 @@
import { act, renderHook } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { useLogin } from "../useLogin";
const mockSignInWithPassword = vi.fn(() => ({ error: null }));
const mockUseSupabase = () => ({
supabase: {
auth: {
signInWithPassword: mockSignInWithPassword,
},
},
});
vi.mock("@/lib/context/SupabaseProvider", () => ({
useSupabase: () => mockUseSupabase(),
}));
describe("useLogin", () => {
it("should call signInWithPassword with user email and password", async () => {
const { result } = renderHook(() => useLogin());
const email = "user@quivr.com";
const password = "password";
act(() => result.current.setEmail(email));
act(() => result.current.setPassword(password));
await act(() => result.current.handleLogin());
expect(mockSignInWithPassword).toHaveBeenCalledTimes(1);
expect(mockSignInWithPassword).toHaveBeenCalledWith({
email,
password,
});
});
});

View File

@ -1,56 +1,16 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useSupabase } from "@/lib/context/SupabaseProvider";
import { redirectToPreviousPageOrChatPage } from "@/lib/helpers/redirectToPreviousPageOrChatPage";
import { useToast } from "@/lib/hooks";
import { useEventTracking } from "@/services/analytics/june/useEventTracking";
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const useLogin = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [isPending, setIsPending] = useState(false);
const { publish } = useToast();
const { supabase, session } = useSupabase();
const { session } = useSupabase();
const { track } = useEventTracking();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { t } = useTranslation(["login"]);
const handleLogin = async () => {
setIsPending(true);
const { error } = await supabase.auth.signInWithPassword({
email: email,
password: password,
});
if (error) {
console.log(error.message);
if (error.message.includes("Failed")) {
publish({
variant: "danger",
text: t("Failedtofetch", { ns: "login" }),
});
} else if (error.message.includes("Invalid")) {
publish({
variant: "danger",
text: t("Invalidlogincredentials", { ns: "login" }),
});
} else {
publish({
variant: "danger",
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call
text: error.message,
});
}
} else {
publish({
variant: "success",
text: t("loginSuccess", { ns: "login" }),
});
}
setIsPending(false);
};
useEffect(() => {
if (session?.user !== undefined) {
@ -60,11 +20,7 @@ export const useLogin = () => {
}, [session?.user]);
return {
handleLogin,
setEmail,
setPassword,
email,
isPending,
password,
};
};

View File

@ -3,78 +3,49 @@ import Link from "next/link";
import { Suspense } from "react";
import { useTranslation } from "react-i18next";
import Button from "@/lib/components/ui/Button";
import Card from "@/lib/components/ui/Card";
import { QuivrLogo } from "@/lib/assets/QuivrLogo";
import { Divider } from "@/lib/components/ui/Divider";
import Field from "@/lib/components/ui/Field";
import PageHeading from "@/lib/components/ui/PageHeading";
import { GoogleLoginButton } from "./components/GoogleLogin";
import { MagicLinkLogin } from "./components/MagicLinkLogin";
import { PasswordForgotten } from "./components/PasswordForgotten";
import { useLogin } from "./hooks/useLogin";
const Main = (): JSX.Element => {
const { handleLogin, setEmail, setPassword, email, isPending, password } =
useLogin();
const { setEmail, email } = useLogin();
const { t } = useTranslation(["translation", "login"]);
return (
<main>
<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: "login" })}
subtitle={t("subtitle", { ns: "login" })}
/>
<Card className="max-w-md w-full p-5 sm:p-10 text-left">
<form
data-testid="sign-in-form"
onSubmit={(e) => {
e.preventDefault();
void handleLogin();
}}
className="flex flex-col gap-2"
>
<div className="w-screen h-screen bg-ivory" data-testid="sign-in-card">
<main className="h-full flex flex-col items-center justify-center">
<section className="w-full md:w-1/2 lg:w-1/3 flex flex-col gap-2">
<Link href="/" className="flex justify-center">
<QuivrLogo size={80} color="black" />
</Link>
<p className="text-center text-4xl font-medium">
{t("talk_to", { ns: "login" })}{" "}
<span className="text-primary">Quivr</span>
</p>
<div className="mt-5 flex flex-col">
<Field
name="email"
required
type="email"
placeholder={t("email")}
onChange={(e) => setEmail(e.target.value)}
value={email}
label="Email"
inputClassName="py-1 mt-1 mb-3"
/>
<Field
name="password"
required
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder={t("password")}
/>
<div className="flex flex-col items-center justify-center mt-2 gap-2">
<Button
data-testid="submit-login"
type="submit"
isLoading={isPending}
>
{t("loginButton")}
</Button>
<PasswordForgotten setEmail={setEmail} email={email} />
<Link href="/signup">{t("signup", { ns: "login" })}</Link>
</div>
<Divider text={t("or")} />
<div className="flex flex-col items-center justify-center mt-2 gap-2">
<GoogleLoginButton />
</div>
<Divider text={t("or")} />
<MagicLinkLogin email={email} setEmail={setEmail} />
</form>
</Card>
</section>
</main>
<Divider text={t("or")} className="my-3 uppercase" />
<GoogleLoginButton />
</div>
<p className="text-[10px] text-center">
{t("restriction_message", { ns: "login" })}
</p>
</section>
</main>
</div>
);
};

View File

@ -1,131 +0,0 @@
/* eslint-disable max-lines */
import { fireEvent, render } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import RecoverPassword from "../page";
const mockUsePathname = vi.fn(() => "/previous-page");
const mockRedirect = vi.fn((url: string) => ({ url }));
vi.mock("next/navigation", () => ({
redirect: (url: string) => mockRedirect(url),
usePathname: () => mockUsePathname(),
}));
const mockUseSupabase = vi.fn(() => ({
supabase: {
auth: {
updateUser: vi.fn(),
},
},
session: {
user: {},
},
}));
vi.mock("@/lib/context/SupabaseProvider", () => ({
useSupabase: () => mockUseSupabase(),
}));
const mockPublish = vi.fn();
vi.mock("@/lib/hooks/useToast", () => ({
useToast: () => ({
publish: mockPublish,
}),
}));
const mockTrack = vi.fn();
vi.mock("@/services/analytics/useEventTracking", () => ({
useEventTracking: () => ({
track: mockTrack,
}),
}));
describe("RecoverPassword component", () => {
it("should render the password update form", () => {
mockUseSupabase.mockReturnValue({
//@ts-expect-error doing this for testing purposes
session: { user: undefined },
});
const { getByTestId } = render(<RecoverPassword />);
const passwordField = getByTestId("password-field");
const updateButton = getByTestId("update-button");
expect(passwordField).toBeDefined();
expect(updateButton).toBeDefined();
});
it.skip("should update the password and shows success toast", async () => {
const updateUserMock = vi.fn(() => ({
data: {},
}));
mockUseSupabase.mockReturnValue({
supabase: {
auth: {
updateUser: updateUserMock,
},
},
session: { user: {} },
});
const { getByTestId } = render(<RecoverPassword />);
const passwordField = getByTestId("password-field");
const updateButton = getByTestId("update-button");
const newPassword = "new-password";
fireEvent.change(passwordField, { target: { value: newPassword } });
fireEvent.click(updateButton);
expect(mockTrack).toHaveBeenCalledTimes(1);
expect(mockTrack).toHaveBeenCalledWith("UPDATE_PASSWORD");
return new Promise<void>((resolve) => {
setTimeout(() => {
expect(mockPublish).toHaveBeenCalledTimes(1);
expect(mockPublish).toHaveBeenCalledWith({
variant: "success",
text: "Password updated successfully!",
});
expect(updateUserMock).toHaveBeenCalledTimes(1);
expect(updateUserMock).toHaveBeenCalledWith({
password: newPassword,
});
resolve();
}, 0);
});
});
it.skip("should show error toast when password update fails", async () => {
const errorMessage = "Password update failed";
const updateUserMock = vi.fn(() => ({
error: { message: errorMessage },
}));
mockUseSupabase.mockReturnValue({
supabase: {
auth: {
updateUser: updateUserMock,
},
},
session: { user: {} },
});
const { getByTestId } = render(<RecoverPassword />);
const passwordField = getByTestId("password-field");
const updateButton = getByTestId("update-button");
fireEvent.change(passwordField, { target: { value: "new-password" } });
fireEvent.click(updateButton);
expect(mockPublish).toHaveBeenCalledTimes(1);
return new Promise<void>((resolve) => {
setTimeout(() => {
expect(mockPublish).toHaveBeenCalledWith({
variant: "danger",
text: `Error: ${errorMessage}`,
});
resolve();
}, 0);
});
});
});

View File

@ -1,91 +0,0 @@
/* eslint-disable */
"use client";
import { useState } from "react";
import Button from "@/lib/components/ui/Button";
import Card from "@/lib/components/ui/Card";
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/june/useEventTracking";
import { Suspense } from "react";
import { useTranslation } from "react-i18next";
export default function RecoverPassword() {
const { supabase, session } = useSupabase();
const [password, setPassword] = useState("");
const [isPending, setIsPending] = useState(false);
const { track } = useEventTracking();
const { t } = useTranslation(["translation", "updatePassword"]);
const { publish } = useToast();
function ChangePassword() {
const handleChangePassword = async () => {
void track("UPDATE_PASSWORD");
setIsPending(true);
const { error } = await supabase.auth.updateUser({
password: password,
});
if (error) {
console.error("Error while resetting password:", error.message);
publish({
variant: "danger",
text: `Error: ${error.message}`,
});
} else {
publish({
variant: "success",
text: t("passwordUpdated", { ns: "updatePassword" }),
});
}
setIsPending(false);
};
if (session?.user === undefined) {
redirectToLogin();
}
return (
<main>
<section className="min-h-[80vh] w-full h-full outline-none flex flex-col gap-5 items-center justify-center p-6">
<PageHeading title={t("title", { ns: "updatePassword" })} />
<Card className="max-w-md w-full p-5 sm:p-10 text-left">
<form
onSubmit={(e) => {
e.preventDefault();
handleChangePassword();
}}
className="flex flex-col gap-2"
>
<Field
name="New password"
required
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder={t("newPassword", { ns: "updatePassword" })}
data-testid="password-field"
/>
<div className="flex flex-col items-center justify-center mt-2 gap-2">
<Button isLoading={isPending} data-testid="update-button">
{t("updateButton")}
</Button>
</div>
</form>
</Card>
</section>
</main>
);
}
return (
<Suspense fallback={"Loading..."}>
<ChangePassword />
</Suspense>
);
}

View File

@ -1,66 +0,0 @@
import { fireEvent, render } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import SignUp from "../page";
const mockHandleSignUp = vi.fn(() => ({}));
vi.mock("next/navigation", () => ({
useRouter: () => ({ replace: vi.fn() }),
}));
vi.mock("@/lib/context/SupabaseProvider", () => ({
useSupabase: () => ({}),
}));
describe("SignUp component", () => {
it("should render correctly", () => {
const { getByTestId } = render(<SignUp />);
const signUpPage = getByTestId("sign-up-page");
expect(signUpPage).toBeDefined();
const signUpForm = getByTestId("sign-up-form");
expect(signUpForm).toBeDefined();
const emailInput = getByTestId("email-field");
expect(emailInput).toBeDefined();
const passwordInput = getByTestId("password-field");
expect(passwordInput).toBeDefined();
const signUpButton = getByTestId("sign-up-button");
expect(signUpButton).toBeDefined();
});
it("should correctly fill the email and password fields", () => {
const { getByTestId } = render(<SignUp />);
const emailInput = getByTestId("email-field") as HTMLInputElement;
const passwordInput = getByTestId("password-field") as HTMLInputElement;
fireEvent.change(emailInput, { target: { value: "user@quivr.app" } });
fireEvent.change(passwordInput, { target: { value: "password123" } });
expect(emailInput.value).toBe("user@quivr.app");
expect(passwordInput.value).toBe("password123");
});
it("should call handleSignUp on submit", () => {
vi.mock("../hooks/useSignUp", async () => {
const functions = await vi.importActual<
typeof import("../hooks/useSignUp")
>("../hooks/useSignUp");
return {
useSignUp: () => ({
...functions.useSignUp(),
handleSignUp: () => mockHandleSignUp(),
}),
};
});
const { getByTestId } = render(<SignUp />);
const submitForm = getByTestId("sign-up-form") as HTMLFormElement;
fireEvent.submit(submitForm);
expect(mockHandleSignUp).toHaveBeenCalled();
});
});

View File

@ -1,50 +0,0 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useSupabase } from "@/lib/context/SupabaseProvider";
import { useToast } from "@/lib/hooks/useToast";
import { useEventTracking } from "@/services/analytics/june/useEventTracking";
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const useSignUp = () => {
const { supabase } = useSupabase();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [isPending, setIsPending] = useState(false);
const { track } = useEventTracking();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { t } = useTranslation(["signUp"]);
const { publish } = useToast();
const handleSignUp = async () => {
void track("SIGNUP");
setIsPending(true);
const { error } = await supabase.auth.signUp({
email: email,
password: password,
});
if (error) {
console.error("Error signing up:", error.message);
publish({
variant: "danger",
text: t("errorSignUp", { errorMessage: error.message }),
});
} else {
publish({
variant: "success",
text: t("mailSended"),
});
}
setIsPending(false);
};
return {
handleSignUp,
setEmail,
password,
setPassword,
isPending,
email,
};
};

View File

@ -1,64 +0,0 @@
"use client";
import Link from "next/link";
import { useTranslation } from "react-i18next";
import Button from "@/lib/components/ui/Button";
import Card from "@/lib/components/ui/Card";
import Field from "@/lib/components/ui/Field";
import PageHeading from "@/lib/components/ui/PageHeading";
import { useSignUp } from "./hooks/useSignUp";
const SignUp = (): JSX.Element => {
const { handleSignUp, isPending, email, password, setEmail, setPassword } =
useSignUp();
const { t } = useTranslation(["translation", "signUp"]);
return (
<main data-testid="sign-up-page">
<section className="min-h-[80vh] w-full h-full outline-none flex flex-col gap-5 items-center justify-center p-6">
<PageHeading
title={t("title", { ns: "signUp" })}
subtitle={t("subtitle", { ns: "signUp" })}
/>
<Card className="max-w-md w-full p-5 sm:p-10 text-left">
<form
onSubmit={(e) => {
e.preventDefault();
void handleSignUp();
}}
className="flex flex-col gap-2"
data-testid="sign-up-form"
>
<Field
name="email"
required
type="email"
placeholder={t("email")}
value={email}
onChange={(e) => setEmail(e.target.value)}
data-testid="email-field"
/>
<Field
name="password"
required
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder={t("password")}
data-testid="password-field"
/>
<div className="flex flex-col items-center justify-center mt-2 gap-2">
<Button data-testid="sign-up-button" isLoading={isPending}>
{t("signUpButton", { ns: "signUp" })}
</Button>
<Link href="/login">{t("login", { ns: "signUp" })}</Link>
</div>
</form>
</Card>
</section>
</main>
);
};
export default SignUp;

View File

@ -1,9 +1,9 @@
import Link from "next/link";
import { QuivrLogo } from "@/lib/assets/QuivrLogo";
import { cn } from "@/lib/utils";
import { PopoverMenuMobile } from "./components/PopoverMenuMobile";
import { QuivrLogo } from "./components/QuivrLogo";
import { useHomeHeader } from "./hooks/useHomeHeader";
import { linkStyle } from "./styles";

View File

@ -1,10 +1,9 @@
import * as Popover from "@radix-ui/react-popover";
import { LuMenu, LuX } from "react-icons/lu";
import { QuivrLogo } from "@/lib/assets/QuivrLogo";
import { cn } from "@/lib/utils";
import { QuivrLogo } from "./QuivrLogo";
type PopoverMenuMobileProps = {
navLinks: JSX.Element[];
color?: "white" | "black";

View File

@ -33,7 +33,7 @@ export const useHomeHeader = ({ color }: UseHomeHeaderProps) => {
rightIcon: null,
},
{ href: "/blog", label: t("blog"), rightIcon: null, newTab: true },
{ href: "/signup", label: t("sign_up") },
{ href: "/login", label: t("sign_up") },
{ href: "/login", label: t("sign_in") },
];

View File

@ -31,11 +31,11 @@ export const DemoSection = (): JSX.Element => {
)}
</div>
<Link
href="/signup"
href="/login"
onClick={(event) => {
onLinkClick({
href: "/signup",
label: "SIGN_UP",
href: "/login",
label: "SIGN_IN",
event,
});
}}

View File

@ -21,11 +21,11 @@ export const FooterSection = (): JSX.Element => {
</p>
<div className="flex items-center justify-center gap-5 flex-wrap">
<Link
href="/signup"
href="/login"
onClick={(event) => {
onLinkClick({
href: "/signup",
label: "SIGN_UP",
href: "/login",
label: "SIGN_IN",
event,
});
}}

View File

@ -26,11 +26,11 @@ export const IntroSection = (): JSX.Element => {
</div>
<div className="flex flex-col items-start sm:flex-row sm:items-center gap-5">
<Link
href="/signup"
href="/login"
onClick={(event) =>
onLinkClick({
href: "/signup",
label: "SIGN_UP",
href: "/login",
label: "SIGN_IN",
event,
})
}

View File

@ -57,11 +57,11 @@ export const SecuritySection = (): JSX.Element => {
</div>
<div className="flex md:justify-end w-full">
<Link
href="/signup"
href="/login"
onClick={(event) => {
onLinkClick({
href: "/signup",
label: "SIGN_UP",
href: "/login",
label: "SIGN_IN",
event,
});
}}

View File

@ -22,11 +22,11 @@ export const UseCases = (): JSX.Element => {
<UseCasesListing />
<div className="mt-10 flex md:justify-center">
<Link
href="/signup"
href="/login"
onClick={(event) => {
onLinkClick({
href: "/signup",
label: "SIGN_UP",
href: "/login",
label: "SIGN_IN",
event,
});
}}

View File

@ -10,8 +10,14 @@ const Footer = (): JSX.Element => {
const path = usePathname();
const isHomePage = path === "/";
const isContactPage = path === "/contact";
const isLoginPage = path === "/login";
if (session?.user !== undefined || isHomePage || isContactPage) {
if (
session?.user !== undefined ||
isHomePage ||
isContactPage ||
isLoginPage
) {
return <></>;
}

View File

@ -8,7 +8,7 @@ export const AuthButtons = (): JSX.Element => {
const pathname = usePathname();
const { t } = useTranslation();
if (pathname === "/signup") {
if (pathname === "/login") {
return (
<Link href={"/login"}>
<Button variant={"secondary"}>{t("loginButton")}</Button>
@ -17,7 +17,7 @@ export const AuthButtons = (): JSX.Element => {
}
if (pathname === "/login") {
return (
<Link href={"/signup"}>
<Link href={"/login"}>
<Button variant={"secondary"}>{t("signUpButton")}</Button>
</Link>
);

View File

@ -16,8 +16,9 @@ export const NavBar = (): JSX.Element => {
const isHomePage = path === "/";
const isContactPage = path === "/contact";
const isLoginPage = path === "/login";
if (pageHasSidebar || isHomePage || isContactPage) {
if (pageHasSidebar || isHomePage || isContactPage || isLoginPage) {
return <></>;
}

View File

@ -12,9 +12,7 @@ import knowlegde_en from "../../../public/locales/en/knowledge.json";
import login_en from "../../../public/locales/en/login.json";
import logout_en from "../../../public/locales/en/logout.json";
import monetization_en from "../../../public/locales/en/monetization.json";
import signUp_en from "../../../public/locales/en/signUp.json";
import translation_en from "../../../public/locales/en/translation.json";
import updatePassword_en from "../../../public/locales/en/updatePassword.json";
import upload_en from "../../../public/locales/en/upload.json";
import user_en from "../../../public/locales/en/user.json";
// import all namespaces Spanish
@ -30,9 +28,7 @@ import knowlegde_es from "../../../public/locales/es/knowledge.json";
import login_es from "../../../public/locales/es/login.json";
import logout_es from "../../../public/locales/es/logout.json";
import monetization_es from "../../../public/locales/es/monetization.json";
import signUp_es from "../../../public/locales/es/signUp.json";
import translation_es from "../../../public/locales/es/translation.json";
import updatePassword_es from "../../../public/locales/es/updatePassword.json";
import upload_es from "../../../public/locales/es/upload.json";
import user_es from "../../../public/locales/es/user.json";
// import all namespaces French
@ -48,9 +44,7 @@ import knowlegde_fr from "../../../public/locales/fr/knowledge.json";
import login_fr from "../../../public/locales/fr/login.json";
import logout_fr from "../../../public/locales/fr/logout.json";
import monetization_fr from "../../../public/locales/fr/monetization.json";
import signUp_fr from "../../../public/locales/fr/signUp.json";
import translation_fr from "../../../public/locales/fr/translation.json";
import updatePassword_fr from "../../../public/locales/fr/updatePassword.json";
import upload_fr from "../../../public/locales/fr/upload.json";
import user_fr from "../../../public/locales/fr/user.json";
// import all namespaces Portuguese
@ -66,9 +60,7 @@ import knowlegde_ptbr from "../../../public/locales/pt-br/knowledge.json";
import login_ptbr from "../../../public/locales/pt-br/login.json";
import logout_ptbr from "../../../public/locales/pt-br/logout.json";
import monetization_ptbr from "../../../public/locales/pt-br/monetization.json";
import signUp_ptbr from "../../../public/locales/pt-br/signUp.json";
import translation_ptbr from "../../../public/locales/pt-br/translation.json";
import updatePassword_ptbr from "../../../public/locales/pt-br/updatePassword.json";
import upload_ptbr from "../../../public/locales/pt-br/upload.json";
import user_ptbr from "../../../public/locales/pt-br/user.json";
// import all namespaces Russian
@ -84,9 +76,7 @@ import knowlegde_ru from "../../../public/locales/ru/knowledge.json";
import login_ru from "../../../public/locales/ru/login.json";
import logout_ru from "../../../public/locales/ru/logout.json";
import monetization_ru from "../../../public/locales/ru/monetization.json";
import signUp_ru from "../../../public/locales/ru/signUp.json";
import translation_ru from "../../../public/locales/ru/translation.json";
import updatePassword_ru from "../../../public/locales/ru/updatePassword.json";
import upload_ru from "../../../public/locales/ru/upload.json";
import user_ru from "../../../public/locales/ru/user.json";
// import all namespaces Simplified Chinese
@ -102,9 +92,7 @@ import knowlegde_zh_cn from "../../../public/locales/zh-cn/knowledge.json";
import login_zh_cn from "../../../public/locales/zh-cn/login.json";
import logout_zh_cn from "../../../public/locales/zh-cn/logout.json";
import monetization_zh_cn from "../../../public/locales/zh-cn/monetization.json";
import signUp_zh_cn from "../../../public/locales/zh-cn/signUp.json";
import translation_zh_cn from "../../../public/locales/zh-cn/translation.json";
import updatePassword_zh_cn from "../../../public/locales/zh-cn/updatePassword.json";
import upload_zh_cn from "../../../public/locales/zh-cn/upload.json";
import user_zh_cn from "../../../public/locales/zh-cn/user.json";
@ -121,9 +109,7 @@ export type Translations = {
login: typeof import("../../../public/locales/en/login.json");
logout: typeof import("../../../public/locales/en/logout.json");
monetization: typeof import("../../../public/locales/en/monetization.json");
signUp: typeof import("../../../public/locales/en/signUp.json");
translation: typeof import("../../../public/locales/en/translation.json");
updatePassword: typeof import("../../../public/locales/en/updatePassword.json");
upload: typeof import("../../../public/locales/en/upload.json");
user: typeof import("../../../public/locales/en/user.json");
knowledge: typeof import("../../../public/locales/en/knowledge.json");
@ -151,9 +137,7 @@ export const resources: Record<SupportedLanguages, Translations> = {
login: login_en,
logout: logout_en,
monetization: monetization_en,
signUp: signUp_en,
translation: translation_en,
updatePassword: updatePassword_en,
upload: upload_en,
user: user_en,
delete_or_unsubscribe_from_brain: delete_brain_en,
@ -170,9 +154,7 @@ export const resources: Record<SupportedLanguages, Translations> = {
login: login_es,
logout: logout_es,
monetization: monetization_es,
signUp: signUp_es,
translation: translation_es,
updatePassword: updatePassword_es,
upload: upload_es,
user: user_es,
delete_or_unsubscribe_from_brain: delete_brain_es,
@ -189,9 +171,7 @@ export const resources: Record<SupportedLanguages, Translations> = {
login: login_fr,
logout: logout_fr,
monetization: monetization_fr,
signUp: signUp_fr,
translation: translation_fr,
updatePassword: updatePassword_fr,
upload: upload_fr,
user: user_fr,
delete_or_unsubscribe_from_brain: delete_brain_fr,
@ -208,9 +188,7 @@ export const resources: Record<SupportedLanguages, Translations> = {
login: login_ptbr,
logout: logout_ptbr,
monetization: monetization_ptbr,
signUp: signUp_ptbr,
translation: translation_ptbr,
updatePassword: updatePassword_ptbr,
upload: upload_ptbr,
user: user_ptbr,
delete_or_unsubscribe_from_brain: delete_brain_ptbr,
@ -227,9 +205,7 @@ export const resources: Record<SupportedLanguages, Translations> = {
login: login_ru,
logout: logout_ru,
monetization: monetization_ru,
signUp: signUp_ru,
translation: translation_ru,
updatePassword: updatePassword_ru,
upload: upload_ru,
user: user_ru,
delete_or_unsubscribe_from_brain: delete_brain_ru,
@ -246,9 +222,7 @@ export const resources: Record<SupportedLanguages, Translations> = {
login: login_zh_cn,
logout: logout_zh_cn,
monetization: monetization_zh_cn,
signUp: signUp_zh_cn,
translation: translation_zh_cn,
updatePassword: updatePassword_zh_cn,
upload: upload_zh_cn,
user: user_zh_cn,
delete_or_unsubscribe_from_brain: delete_brain_zh_cn,

View File

@ -1,15 +1,8 @@
{
"title": "Login",
"subtitle": "Welcome back",
"signup": "Don't have an account? Sign up",
"forgottenPassword": "Password forgotten",
"googleLogin": "Login with Google",
"magicLink": "Send Magic Link",
"loginSuccess": "Successfully logged in",
"googleLogin": "Continue with Google",
"magicLink": "Continue with email",
"errorMailMissed": "Please enter your email address",
"recoveryMailSended": "Recovery mail will be sent if email recognized",
"Invalidlogincredentials" : "Invalid login credentials",
"password.updated": "Password updated successfully!",
"new_password": "New Password",
"Failedtofetch": "Failed to fetch data"
"talk_to": "Talk to",
"restriction_message": "Unpaid users have access to a free and limited demo of Quivr",
"email":"Email address"
}

View File

@ -1,13 +0,0 @@
{
"title": "Sign Up",
"subtitle": "Create your account",
"email": "Email",
"password": "Password",
"loginButton": "Login",
"signUpButton": "Sign up",
"or": "or",
"login": "Already registered? Sign in",
"googleLogin": "Login with Google",
"errorSignUp": "Error signing up: {{errorMessage}}",
"mailSended": "Confirmation Email sent, please check your email"
}

View File

@ -4,7 +4,6 @@
"description": "Quivr is your second brain in the cloud, designed to easily store and retrieve unstructured information.",
"toastDismiss": "dismiss",
"email": "Email",
"password": "Password",
"or": "or",
"and": "and",
"loginButton": "Login",

View File

@ -1,5 +0,0 @@
{
"title": "Update Password",
"passwordUpdated": "Password updated successfully!",
"newPassword": "New Password"
}

View File

@ -1,15 +1,8 @@
{
"errorMailMissed": "Por favor ingresa tu dirección de email",
"Failedtofetch": "Error al obtener datos",
"forgottenPassword": "Olvidé mi contraseña",
"googleLogin": "Iniciar con Google",
"Invalidlogincredentials": "Credenciales inválidas",
"loginSuccess": "Sesión iniciada correctamente",
"magicLink": "Enviar Enlace Mágico",
"new_password": "Nueva contraseña",
"password.updated": "¡Contraseña actualizada!",
"recoveryMailSended": "El correo de recuperación será enviado si el email es reconocido",
"signup": "¿No tienes una cuenta? Registrate aquí",
"subtitle": "Un gusto tenerte de vuelta",
"title": "Iniciar sesión"
"googleLogin": "Continuar con Google",
"magicLink": "Continuar con correo electrónico",
"errorMailMissed": "Por favor, ingrese su dirección de correo electrónico",
"talk_to": "Hablar con",
"restriction_message": "Los usuarios no pagos tienen acceso a una demostración gratuita y limitada de Quivr",
"email":"Dirección de correo electrónico"
}

View File

@ -1,13 +0,0 @@
{
"email": "Correo electrónico",
"errorSignUp": "Error al registrarse: {{errorMessage}}",
"googleLogin": "Iniciar con Google",
"login": "¿Ya tienes una cuenta? Inicia sesión",
"loginButton": "Iniciar sesión",
"mailSended": "Correo de confirmación enviado, por favor verifica tu buzón",
"or": "o",
"password": "Contraseña",
"signUpButton": "Registrarse",
"subtitle": "Crea tu cuenta",
"title": "Registrarse"
}

View File

@ -18,7 +18,6 @@
"or": "o",
"and": "y",
"Owner": "Propietario",
"password": "Contraseña",
"resetButton": "Restaurar",
"shareButton": "Compartir",
"signUpButton": "Registrarse",

View File

@ -1,5 +0,0 @@
{
"newPassword": "Nueva contraseña",
"passwordUpdated": "¡Contraseña actualizada!",
"title": "Actualizar Contraseña"
}

View File

@ -1,15 +1,8 @@
{
"title": "Connexion",
"subtitle": "Bienvenue",
"signup": "Vous n'avez pas de compte ? Inscrivez-vous",
"forgottenPassword": "Mot de passe oublié",
"googleLogin": "Connexion avec Google",
"magicLink": "Envoyer le lien magique",
"loginSuccess": "Connexion réussie",
"errorMailMissed": "Veuillez entrer votre adresse e-mail",
"recoveryMailSended": "Un mail de récupération sera envoyé si l'email est reconnu",
"Invalidlogincredentials": "Identifiants de connexion invalides",
"password.updated": "Mot de passe mis à jour avec succès !",
"new_password": "Nouveau mot de passe",
"Failedtofetch": "Échec de la récupération"
"googleLogin": "Continuer avec Google",
"magicLink": "Continuer avec e-mail",
"errorMailMissed": "Veuillez saisir votre adresse e-mail",
"talk_to": "Parler à",
"restriction_message": "Les utilisateurs non payants ont accès à une démonstration gratuite et limitée de Quivr",
"email":"Adresse e-mail"
}

View File

@ -1,13 +0,0 @@
{
"title": "Inscription",
"subtitle": "Créez votre compte",
"email": "Email",
"password": "Mot de passe",
"loginButton": "Connexion",
"signUpButton": "S'inscrire",
"or": "ou",
"login": "Déjà inscrit ? Connectez-vous",
"googleLogin": "Connexion avec Google",
"errorSignUp": "Erreur lors de l'inscription : {{errorMessage}}",
"mailSended": "Email de confirmation envoyé, veuillez vérifier votre email"
}

View File

@ -4,7 +4,6 @@
"description": "Quivr est votre deuxième cerveau dans le nuage, conçu pour stocker et récupérer facilement des informations non structurées.",
"toastDismiss": "ignorer",
"email": "Email",
"password": "Mot de passe",
"or": "ou",
"and": "et",
"loginButton": "Connexion",

View File

@ -1,5 +0,0 @@
{
"title": "Mettre à jour le mot de passe",
"passwordUpdated": "Mot de passe mis à jour avec succès !",
"newPassword": "Nouveau mot de passe"
}

View File

@ -1,15 +1,8 @@
{
"title": "Login",
"subtitle": "Bem-vindo de volta",
"signup": "Não tem uma conta? Cadastre-se",
"forgottenPassword": "Esqueci a senha",
"googleLogin": "Entrar com o Google",
"magicLink": "Enviar Link Mágico",
"loginSuccess": "Login realizado com sucesso",
"errorMailMissed": "Por favor, insira seu endereço de email",
"recoveryMailSended": "Um email de recuperação será enviado se o email for reconhecido",
"Invalidlogincredentials": "Credenciais de login inválidas",
"password.updated": "Senha atualizada com sucesso!",
"new_password": "Nova Senha",
"Failedtofetch": "Falha ao buscar os dados"
"googleLogin": "Continuar com o Google",
"magicLink": "Continuar com o e-mail",
"errorMailMissed": "Por favor, insira seu endereço de e-mail",
"talk_to": "Converse com",
"restriction_message": "Usuários não pagos têm acesso a uma demonstração gratuita e limitada do Quivr",
"email":"Email address"
}

View File

@ -1,13 +0,0 @@
{
"title": "Cadastro",
"subtitle": "Crie sua conta",
"email": "Email",
"password": "Senha",
"loginButton": "Login",
"signUpButton": "Cadastre-se",
"or": "ou",
"login": "Já possui cadastro? Faça login",
"googleLogin": "Entrar com o Google",
"errorSignUp": "Erro ao se cadastrar: {{errorMessage}}",
"mailSended": "Email de confirmação enviado, por favor verifique seu email"
}

View File

@ -4,7 +4,6 @@
"description": "Quivr é o seu segundo cérebro na nuvem, projetado para armazenar e recuperar facilmente informações não estruturadas.",
"toastDismiss": "fechar",
"email": "Email",
"password": "Senha",
"or": "ou",
"and": "e",
"loginButton": "Entrar",

View File

@ -1,5 +0,0 @@
{
"title": "Atualizar Senha",
"passwordUpdated": "Senha atualizada com sucesso!",
"newPassword": "Nova Senha"
}

View File

@ -1,15 +1,8 @@
{
"title": "Вход",
"subtitle": "С возвращением",
"signup": "Нет аккаунта? Зарегистрируйтесь",
"forgottenPassword": "Забыли пароль",
"googleLogin": "Войти с Google",
"magicLink": "Отправить магическую ссылку",
"loginSuccess": "Успешный вход",
"errorMailMissed": "Пожалуйста, введите ваш адрес электронной почты",
"recoveryMailSended": "Восстановительное письмо будет отправлено, если адрес электронной почты распознан",
"Invalidlogincredentials": "Недопустимые учетные данные для входа",
"password.updated": "Пароль успешно обновлен!",
"new_password": "Новый пароль",
"Failedtofetch": "Не удалось получить данные"
}
"googleLogin": "Продолжить с Google",
"magicLink": "Продолжить с электронной почтой",
"errorMailMissed": "Пожалуйста, введите ваш адрес электронной почты",
"talk_to": "Общение с",
"restriction_message": "Неоплаченным пользователям доступен бесплатный и ограниченный демонстрационный доступ к Quivr",
"email":"Адрес электронной почты"
}

View File

@ -1,13 +0,0 @@
{
"title": "Регистрация",
"subtitle": "Создайте свой аккаунт",
"email": "Email",
"password": "Пароль",
"loginButton": "Вход",
"signUpButton": "Зарегистрироваться",
"or": "или",
"login": "Уже зарегистрированы? Войти",
"googleLogin": "Войти с Google",
"errorSignUp": "Ошибка при регистрации: {{errorMessage}}",
"mailSended": "Письмо с подтверждением отправлено, пожалуйста, проверьте вашу почту"
}

View File

@ -4,7 +4,6 @@
"description": "Quivr - это ваш второй мозг в облаке, предназначенный для легкого хранения и извлечения неструктурированной информации.",
"toastDismiss": "закрыть",
"email": "Email",
"password": "Пароль",
"or": "или",
"and": "и",
"loginButton": "Войти",

View File

@ -1,5 +0,0 @@
{
"title": "Обновление пароля",
"passwordUpdated": "Пароль успешно обновлен!",
"newPassword": "Новый пароль"
}

View File

@ -1,15 +1,8 @@
{
"title": "登录",
"subtitle": "欢迎回来",
"signup": "没有账户吗?注册",
"forgottenPassword": "忘记密码",
"googleLogin": "使用Google登录",
"magicLink": "Send Magic Link",
"loginSuccess": "登录成功",
"errorMailMissed": "请输入您的电子邮件地址",
"recoveryMailSended": "如果电子邮件被识别,将发送恢复邮件",
"Invalidlogincredentials": "无效的登录凭据",
"password.updated": "密码更新成功!",
"new_password": "新密码",
"Failedtofetch": "获取数据失败"
"googleLogin": "使用Google继续",
"magicLink": "使用电子邮件继续",
"errorMailMissed": "请输入您的电子邮件地址",
"talk_to": "与之交谈",
"restriction_message": "未付费用户可以访问 Quivr 的免费和有限演示",
"email":"电子邮件地址"
}

View File

@ -1,13 +0,0 @@
{
"title": "注册",
"subtitle": "创建您的帐户",
"email": "Email",
"password": "Password",
"loginButton": "登录",
"signUpButton": "注册",
"or": "或",
"login": "已经注册?登录",
"googleLogin": "使用Google登录",
"errorSignUp": "注册时出错: {{errorMessage}}",
"mailSended": "确认邮件已发送,请检查您的邮箱"
}

View File

@ -4,7 +4,6 @@
"description": "Quivr 是您在云中的第二个大脑,让您轻松存储和检索非结构化信息。",
"toastDismiss": "dismiss",
"email": "Email",
"password": "密码",
"or": "或",
"and": "和",
"loginButton": "登录",

View File

@ -1,5 +0,0 @@
{
"title": "更新密码",
"passwordUpdated": "密码更新成功!",
"newPassword": "新密码"
}

View File

@ -22,6 +22,7 @@ module.exports = {
"msg-header-gray": "#8F8F8F",
"msg-purple": "#E0DDFC",
"onboarding-yellow-bg": "#F6EFDE",
ivory: "#FCFAF6",
},
},
},