mirror of
https://github.com/StanGirard/quivr.git
synced 2024-11-27 18:32:55 +03:00
feat: manage plan (#1488)
# Description - add crown on premium user - link to manage plan in page `/user` ## ⚠️ Before merging Setup env variable: ```env NEXT_PUBLIC_STRIPE_MANAGE_PLAN_URL=<ignore-me-or-change-me> ``` ## Screenshots (if appropriate): <img width="290" alt="image" src="https://github.com/StanGirard/quivr/assets/67386567/a87b0f7e-b07c-4f4e-b9d2-515ac25ebf05"> <img width="318" alt="image" src="https://github.com/StanGirard/quivr/assets/67386567/6a4f4f72-8c75-45da-9468-cae1a8d28935">
This commit is contained in:
parent
41563767ad
commit
d311a53b6f
@ -18,3 +18,4 @@ NEXT_PUBLIC_CMS_URL=https://cms.quivr.app
|
|||||||
|
|
||||||
NEXT_PUBLIC_STRIPE_PRICING_TABLE_ID=<ignore-me-or-change-me>
|
NEXT_PUBLIC_STRIPE_PRICING_TABLE_ID=<ignore-me-or-change-me>
|
||||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=<ignore-me-or-change-me>
|
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=<ignore-me-or-change-me>
|
||||||
|
NEXT_PUBLIC_STRIPE_MANAGE_PLAN_URL=<ignore-me-or-change-me>
|
||||||
|
29
frontend/app/user/components/StripePricingOrManageButton.tsx
Normal file
29
frontend/app/user/components/StripePricingOrManageButton.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { useFeatureIsOn } from "@growthbook/growthbook-react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import { StripePricingModal } from "@/lib/components/Stripe";
|
||||||
|
import Button from "@/lib/components/ui/Button";
|
||||||
|
import { useUserData } from "@/lib/hooks/useUserData";
|
||||||
|
|
||||||
|
const MANAGE_PLAN_URL = process.env.NEXT_PUBLIC_STRIPE_MANAGE_PLAN_URL;
|
||||||
|
|
||||||
|
export const StripePricingOrManageButton = (): JSX.Element => {
|
||||||
|
const { t } = useTranslation("monetization");
|
||||||
|
const { userData } = useUserData();
|
||||||
|
const monetizationIsOn = useFeatureIsOn("monetization");
|
||||||
|
|
||||||
|
if (!monetizationIsOn) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const is_premium = userData?.is_premium ?? false;
|
||||||
|
if (is_premium) {
|
||||||
|
return (
|
||||||
|
<a href={MANAGE_PLAN_URL} target="_blank" rel="noopener">
|
||||||
|
<Button className="w-full">{t("manage_plan")}</Button>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <StripePricingModal Trigger={<Button>{t("upgrade")}</Button>} />;
|
||||||
|
};
|
@ -1 +1,2 @@
|
|||||||
export { UserStatistics } from "./UserStatistics";
|
export { UserStatistics } from "./UserStatistics";
|
||||||
|
export { StripePricingOrManageButton } from "./StripePricingOrManageButton";
|
||||||
|
@ -1,36 +1,26 @@
|
|||||||
/* eslint-disable max-lines */
|
|
||||||
"use client";
|
"use client";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { StripePricingModal } from "@/lib/components/Stripe";
|
|
||||||
import Button from "@/lib/components/ui/Button";
|
import Button from "@/lib/components/ui/Button";
|
||||||
import Card, { CardBody, CardHeader } from "@/lib/components/ui/Card";
|
import Card, { CardBody, CardHeader } from "@/lib/components/ui/Card";
|
||||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||||
import { useUserData } from "@/lib/hooks/useUserData";
|
|
||||||
import { redirectToLogin } from "@/lib/router/redirectToLogin";
|
import { redirectToLogin } from "@/lib/router/redirectToLogin";
|
||||||
|
|
||||||
import { UserStatistics } from "./components";
|
import { StripePricingOrManageButton, UserStatistics } from "./components";
|
||||||
import { ApiKeyConfig } from "./components/ApiKeyConfig";
|
import { ApiKeyConfig } from "./components/ApiKeyConfig";
|
||||||
import LanguageSelect from "./components/LanguageDropDown/LanguageSelect";
|
import LanguageSelect from "./components/LanguageDropDown/LanguageSelect";
|
||||||
import ThemeSelect from "./components/ThemeSelect/ThemeSelect";
|
import ThemeSelect from "./components/ThemeSelect/ThemeSelect";
|
||||||
|
|
||||||
const UserPage = (): JSX.Element => {
|
const UserPage = (): JSX.Element => {
|
||||||
const { session } = useSupabase();
|
const { session } = useSupabase();
|
||||||
const { userData } = useUserData();
|
|
||||||
const is_premium = userData?.is_premium;
|
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
redirectToLogin();
|
redirectToLogin();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { user } = session;
|
const { user } = session;
|
||||||
const { t } = useTranslation([
|
const { t } = useTranslation(["translation", "user", "config"]);
|
||||||
"translation",
|
|
||||||
"user",
|
|
||||||
"config",
|
|
||||||
"monetization",
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="container lg:w-2/3 mx-auto py-10 px-5">
|
<main className="container lg:w-2/3 mx-auto py-10 px-5">
|
||||||
@ -52,11 +42,7 @@ const UserPage = (): JSX.Element => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
{is_premium === true ? null : (
|
<StripePricingOrManageButton />
|
||||||
<StripePricingModal
|
|
||||||
Trigger={<Button>{t("monetization:upgrade")}</Button>}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
@ -27,8 +27,10 @@ export const SidebarFooterButton = ({
|
|||||||
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"
|
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}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
{icon}
|
<span className="w-8 shrink-0">{icon}</span>
|
||||||
<span className="text-ellipsis overflow-hidden">{label}</span>
|
<span className="w-full text-ellipsis overflow-hidden text-start">
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -23,12 +23,12 @@ export const UpgradeToPlus = (): JSX.Element => {
|
|||||||
<SidebarFooterButton
|
<SidebarFooterButton
|
||||||
icon={<FiUser className="w-8 h-8" />}
|
icon={<FiUser className="w-8 h-8" />}
|
||||||
label={
|
label={
|
||||||
<>
|
<div className="flex justify-between items-center w-full">
|
||||||
{t("upgrade")}{" "}
|
{t("upgrade")}
|
||||||
<span className="rounded bg-primary/50 py-1 px-3 text-xs">
|
<span className="rounded bg-primary/30 py-1 px-3 text-xs">
|
||||||
{t("new")}
|
{t("new")}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
import { FaCrown } from "react-icons/fa";
|
||||||
|
|
||||||
import { Avatar } from "@/lib/components/ui/Avatar";
|
import { Avatar } from "@/lib/components/ui/Avatar";
|
||||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||||
|
import { useUserData } from "@/lib/hooks/useUserData";
|
||||||
|
|
||||||
import { SidebarFooterButton } from "./SidebarFooterButton";
|
import { SidebarFooterButton } from "./SidebarFooterButton";
|
||||||
import { useGravatar } from "../../../../../hooks/useGravatar";
|
import { useGravatar } from "../../../../../hooks/useGravatar";
|
||||||
@ -7,12 +10,21 @@ import { useGravatar } from "../../../../../hooks/useGravatar";
|
|||||||
export const UserButton = (): JSX.Element => {
|
export const UserButton = (): JSX.Element => {
|
||||||
const { session } = useSupabase();
|
const { session } = useSupabase();
|
||||||
const { gravatarUrl } = useGravatar();
|
const { gravatarUrl } = useGravatar();
|
||||||
|
const { userData } = useUserData();
|
||||||
|
const is_premium = userData?.is_premium ?? false;
|
||||||
|
const email = session?.user.email ?? "";
|
||||||
|
const label = (
|
||||||
|
<span className="flex justify-between items-center flex-nowrap gap-1 w-full">
|
||||||
|
<span className="text-ellipsis overflow-hidden">{email}</span>
|
||||||
|
{is_premium && <FaCrown className="w-5 h-5 shrink-0" />}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarFooterButton
|
<SidebarFooterButton
|
||||||
href={"/user"}
|
href={"/user"}
|
||||||
icon={<Avatar url={gravatarUrl} />}
|
icon={<Avatar url={gravatarUrl} />}
|
||||||
label={session?.user.email ?? ""}
|
label={label}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"upgrade": "Upgrade to plus",
|
"upgrade": "Upgrade to plus",
|
||||||
"new": "New"
|
"new": "New",
|
||||||
|
"manage_plan": "Manage my plan"
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"upgrade": "Actualizar a plus",
|
"upgrade": "Actualizar a plus",
|
||||||
"new": "Nuevo"
|
"new": "Nuevo",
|
||||||
|
"manage_plan": "Gestionar mi plan"
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"upgrade": "Passer à la version plus",
|
"upgrade": "Passer à la version plus",
|
||||||
"new": "Nouveau"
|
"new": "Nouveau",
|
||||||
|
"manage_plan": "Gérer mon plan"
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"upgrade": "Atualizar para o plus",
|
"upgrade": "Atualizar para o plus",
|
||||||
"new": "Novo"
|
"new": "Novo",
|
||||||
|
"manage_plan": "Gerenciar meu plano"
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"upgrade": "Обновить до плюса",
|
"upgrade": "Обновить до плюса",
|
||||||
"new": "Новый"
|
"new": "Новый",
|
||||||
|
"manage_plan": "Управлять моим планом"
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"upgrade": "升级至高级版",
|
"upgrade": "升级至高级版",
|
||||||
"new": "新"
|
"new": "新",
|
||||||
|
"manage_plan": "管理我的计划"
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user