From 5256d428e078aa1cc87b3356692084393f4f92e7 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Thu, 11 Jan 2024 09:55:13 -0800 Subject: [PATCH] Show number of credits left --- .../src/components/hosted/AvatarDropdown.tsx | 68 ++++++++++++++++++- frontend/src/components/hosted/types.ts | 5 ++ 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/hosted/AvatarDropdown.tsx b/frontend/src/components/hosted/AvatarDropdown.tsx index e7a1bc4..3f3b6e3 100644 --- a/frontend/src/components/hosted/AvatarDropdown.tsx +++ b/frontend/src/components/hosted/AvatarDropdown.tsx @@ -11,20 +11,60 @@ import { import { useStore } from "../../store/store"; import { capitalize } from "./utils"; import StripeCustomerPortalLink from "./StripeCustomerPortalLink"; +import { Progress } from "../ui/progress"; +import { useAuthenticatedFetch } from "./useAuthenticatedFetch"; +import { SAAS_BACKEND_URL } from "../../config"; +import { CreditsUsage } from "./types"; +import { useState } from "react"; +import toast from "react-hot-toast"; export default function AvatarDropdown() { - const { user, isLoaded, isSignedIn } = useUser(); - const { signOut } = useClerk(); + const [isOpen, setIsOpen] = useState(false); + const [isLoadingUsage, setIsLoadingUsage] = useState(false); + const [usedCredits, setUsedCredits] = useState(0); + const [totalCredits, setTotalCredits] = useState(0); + const subscriberTier = useStore((state) => state.subscriberTier); const setPricingDialogOpen = useStore((state) => state.setPricingDialogOpen); const isFreeUser = subscriberTier === "free"; + const { user, isLoaded, isSignedIn } = useUser(); + const { signOut } = useClerk(); + const authenticatedFetch = useAuthenticatedFetch(); + + async function open(isOpen: boolean) { + setIsOpen(isOpen); + + // Do not fetch usage if the user is a free user + // or that information hasn't loaded yet + // or the dropdown is closed + if (isFreeUser || !subscriberTier || !isOpen) return; + + setIsLoadingUsage(true); + + try { + const res: CreditsUsage = await authenticatedFetch( + SAAS_BACKEND_URL + "/credits/usage", + "POST" + ); + + setUsedCredits(res.used_monthly_credits); + setTotalCredits(res.total_monthly_credits); + } catch (e) { + toast.error( + "Failed to fetch credit usage. Please contact support to get this issue fixed." + ); + } finally { + setIsLoadingUsage(false); + } + } + // If Clerk is still loading or user is logged out, don't show anything if (!isLoaded || !isSignedIn) return null; return ( <> - + @@ -44,6 +84,28 @@ export default function AvatarDropdown() { {capitalize(subscriberTier) + " Subscriber"} + + {/* Loading credit usage */} + {isLoadingUsage && ( + + Loading credit usage... + + )} + + {/* Credits usage */} + {!isLoadingUsage && ( + <> + + + + + {usedCredits} out of {totalCredits} credits used. + {subscriberTier !== "pro" && ( + <> Upgrade to Pro to get more credits. + )} + + + )} diff --git a/frontend/src/components/hosted/types.ts b/frontend/src/components/hosted/types.ts index b8b17bd..7fa3f7e 100644 --- a/frontend/src/components/hosted/types.ts +++ b/frontend/src/components/hosted/types.ts @@ -10,3 +10,8 @@ export interface UserResponse { export interface PortalSessionResponse { url: string; } + +export interface CreditsUsage { + total_monthly_credits: number; + used_monthly_credits: number; +}