feat: upgrade button in user settings (#1484)

# Description

Epic: #1429
User Story: #1431

- Add an upgrade button in user settings.
- Remove hover links on sidebar buttons (otherwise the link could
partially hide the button)

## Screenshots (if appropriate):

<img width="749" alt="image"
src="https://github.com/StanGirard/quivr/assets/67386567/6265ba2b-8d91-4ee8-abb3-98417ad91076">

<img width="803" alt="image"
src="https://github.com/StanGirard/quivr/assets/67386567/c13ce60b-a54d-44d7-a622-bcb1200ddb81">
This commit is contained in:
Matthieu Jacq 2023-10-25 12:42:53 +02:00 committed by GitHub
parent 7038cddd2f
commit ee7af51c4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 89 additions and 45 deletions

View File

@ -17,7 +17,7 @@ export const BrainsList = (): JSX.Element => {
const { t } = useTranslation(["brain", "chat"]);
return (
<Sidebar showButtons={["user", "upgradeToPlus"]}>
<Sidebar showButtons={["upgradeToPlus", "user"]}>
<div className="flex flex-col p-2 gap-2">
<Link href="/chat">
<Button type="button" className="bg-primary text-white py-2 w-full">

View File

@ -15,7 +15,7 @@ export const ChatsList = (): JSX.Element => {
const { shouldDisplayWelcomeChat } = useOnboarding();
return (
<Sidebar showButtons={["myBrains", "user", "upgradeToPlus"]}>
<Sidebar showButtons={["myBrains", "upgradeToPlus", "user"]}>
<div className="flex flex-col flex-1 h-full" data-testid="chats-list">
<div className="pt-2">
<NewChatButton />

View File

@ -3,9 +3,11 @@
import Link from "next/link";
import { useTranslation } from "react-i18next";
import { StripePricingModal } from "@/lib/components/Stripe";
import Button from "@/lib/components/ui/Button";
import Card, { CardBody, CardHeader } from "@/lib/components/ui/Card";
import { useSupabase } from "@/lib/context/SupabaseProvider";
import { useUserData } from "@/lib/hooks/useUserData";
import { redirectToLogin } from "@/lib/router/redirectToLogin";
import { UserStatistics } from "./components";
@ -15,13 +17,20 @@ import ThemeSelect from "./components/ThemeSelect/ThemeSelect";
const UserPage = (): JSX.Element => {
const { session } = useSupabase();
const { userData } = useUserData();
const is_premium = userData?.is_premium;
if (!session) {
redirectToLogin();
}
const { user } = session;
const { t } = useTranslation(["translation", "user", "config"]);
const { t } = useTranslation([
"translation",
"user",
"config",
"monetization",
]);
return (
<main className="container lg:w-2/3 mx-auto py-10 px-5">
@ -32,18 +41,22 @@ const UserPage = (): JSX.Element => {
</h2>
</CardHeader>
<CardBody>
<p className="mb-3">
<strong>{t("email")}:</strong> <span>{user.email}</span>
</p>
<div className="inline-block">
<CardBody className="flex flex-col items-stretch max-w-max gap-2">
<div className="flex gap-5 items-center">
<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>
</div>
{is_premium === true ? null : (
<StripePricingModal
Trigger={<Button>{t("monetization:upgrade")}</Button>}
/>
)}
</CardBody>
</Card>

View File

@ -12,12 +12,16 @@ type SidebarFooterProps = {
export const SidebarFooter = ({
showButtons,
}: SidebarFooterProps): JSX.Element => {
const buttons = {
myBrains: <BrainManagementButton />,
upgradeToPlus: <UpgradeToPlus />,
user: <UserButton />,
};
return (
<div className="bg-gray-50 dark:bg-gray-900 border-t dark:border-white/10 mt-auto p-2">
<div className="max-w-screen-xl flex justify-center items-center flex-col">
{showButtons.includes("myBrains") && <BrainManagementButton />}
{showButtons.includes("upgradeToPlus") && <UpgradeToPlus />}
{showButtons.includes("user") && <UserButton />}
{showButtons.map((button) => buttons[button])}
</div>
</div>
);

View File

@ -1,22 +1,20 @@
import Link from "next/link";
import { useTranslation } from "react-i18next";
import { FaBrain } from "react-icons/fa";
import { sidebarLinkStyle } from "@/lib/components/Sidebar/components/SidebarFooter/styles/SidebarLinkStyle";
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
import { SidebarFooterButton } from "./SidebarFooterButton";
export const BrainManagementButton = (): JSX.Element => {
const { currentBrainId } = useBrainContext();
const { t } = useTranslation("brain");
return (
<Link
<SidebarFooterButton
href={`/brains-management/${currentBrainId ?? ""}`}
className={sidebarLinkStyle}
icon={<FaBrain className="w-8 h-8" />}
label={t("myBrains")}
data-testid="brain-management-button"
>
<FaBrain className="w-8 h-8" />
<span>{t("myBrains")}</span>
</Link>
/>
);
};

View File

@ -0,0 +1,34 @@
import { useRouter } from "next/navigation";
type SidebarFooterButtonProps = {
icon: JSX.Element;
label: string | JSX.Element;
href?: string;
onClick?: () => void;
};
export const SidebarFooterButton = ({
icon,
label,
href,
onClick,
}: SidebarFooterButtonProps): JSX.Element => {
const router = useRouter();
if (href !== undefined) {
onClick = () => {
void router.push(href);
};
}
return (
<button
type="button"
className="w-full rounded-lg px-5 py-2 text-base flex justify-start items-center gap-4 hover:bg-gray-200 dark:hover:bg-gray-800 hover:text-primary focus:outline-none"
onClick={onClick}
>
{icon}
<span className="text-ellipsis overflow-hidden">{label}</span>
</button>
);
};

View File

@ -5,7 +5,7 @@ import { FiUser } from "react-icons/fi";
import { StripePricingModal } from "@/lib/components/Stripe";
import { useUserData } from "@/lib/hooks/useUserData";
import { sidebarLinkStyle } from "../styles/SidebarLinkStyle";
import { SidebarFooterButton } from "./SidebarFooterButton";
export const UpgradeToPlus = (): JSX.Element => {
const { userData } = useUserData();
@ -20,15 +20,17 @@ export const UpgradeToPlus = (): JSX.Element => {
return (
<StripePricingModal
Trigger={
<button type="button" className={sidebarLinkStyle}>
<FiUser className="w-8 h-8" />
<span>
{t("upgrade")}{" "}
<span className="rounded bg-primary/50 py-1 px-3 text-xs">
{t("new")}
</span>
</span>
</button>
<SidebarFooterButton
icon={<FiUser className="w-8 h-8" />}
label={
<>
{t("upgrade")}{" "}
<span className="rounded bg-primary/50 py-1 px-3 text-xs">
{t("new")}
</span>
</>
}
/>
}
/>
);

View File

@ -1,21 +1,18 @@
import Link from "next/link";
import { Avatar } from "@/lib/components/ui/Avatar";
import { useSupabase } from "@/lib/context/SupabaseProvider";
import { SidebarFooterButton } from "./SidebarFooterButton";
import { useGravatar } from "../../../../../hooks/useGravatar";
import { sidebarLinkStyle } from "../styles/SidebarLinkStyle";
export const UserButton = (): JSX.Element => {
const { session } = useSupabase();
const { gravatarUrl } = useGravatar();
return (
<Link aria-label="account" className={sidebarLinkStyle} href={"/user"}>
<Avatar url={gravatarUrl} alt="user-gravatar" />
<span className="text-ellipsis overflow-hidden">
{session?.user.email ?? ""}
</span>
</Link>
<SidebarFooterButton
href={"/user"}
icon={<Avatar url={gravatarUrl} />}
label={session?.user.email ?? ""}
/>
);
};

View File

@ -1,2 +0,0 @@
export const sidebarLinkStyle =
"w-full rounded-lg px-5 py-2 text-base flex justify-start items-center gap-4 hover:bg-gray-200 dark:hover:bg-gray-800 focus:outline-none";

View File

@ -4,20 +4,18 @@ import { cn } from "@/lib/utils";
type AvatarProps = {
url: string;
alt: string;
imgClassName?: string;
className?: string;
};
export const Avatar = ({
url,
alt,
imgClassName,
className,
}: AvatarProps): JSX.Element => {
return (
<div className={cn("relative w-8 h-8", className)}>
<div className={cn("relative w-8 h-8 shrink-0", className)}>
<Image
alt={alt}
alt="avatar"
fill={true}
sizes="32px"
src={url}