screenshot-to-code/frontend/src/components/hosted/AvatarDropdown.tsx
2024-01-11 11:03:48 -08:00

132 lines
4.6 KiB
TypeScript

import { useClerk, useUser } from "@clerk/clerk-react";
import { Avatar, AvatarImage, AvatarFallback } from "../ui/avatar";
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuLabel,
} from "../ui/dropdown-menu";
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 [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 (
<>
<DropdownMenu open={isOpen} onOpenChange={open}>
<DropdownMenuTrigger asChild>
<Avatar className="w-8 h-8 cursor-pointer">
<AvatarImage src={user?.imageUrl} alt="Profile image" />
<AvatarFallback>{user?.firstName}</AvatarFallback>
</Avatar>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align="end">
{/* Free users */}
{isFreeUser && (
<DropdownMenuItem asChild={true}>
<a onClick={() => setPricingDialogOpen(true)}>Get pro</a>
</DropdownMenuItem>
)}
{/* Paying user */}
{!isFreeUser && (
<>
<DropdownMenuLabel>
{capitalize(subscriberTier) + " Subscriber"}
</DropdownMenuLabel>
{/* Loading credit usage */}
{isLoadingUsage && (
<DropdownMenuItem className="text-xs text-gray-700">
Loading credit usage...
</DropdownMenuItem>
)}
{/* Credits usage */}
{!isLoadingUsage && (
<>
<DropdownMenuItem>
<Progress value={(usedCredits / totalCredits) * 100} />
</DropdownMenuItem>
<DropdownMenuItem className="text-xs text-gray-700">
{usedCredits} out of {totalCredits} credits used for{" "}
{new Date().toLocaleString("default", { month: "long" })}.
{subscriberTier !== "pro" && (
<> Upgrade to Pro to get more credits.</>
)}
</DropdownMenuItem>
</>
)}
<DropdownMenuSeparator />
<DropdownMenuItem asChild={true}>
<a href="mailto:support@picoapps.xyz" target="_blank">
Email support
</a>
</DropdownMenuItem>
<DropdownMenuItem asChild={true}>
<StripeCustomerPortalLink label="Manage billing" />
</DropdownMenuItem>
<DropdownMenuItem asChild={true}>
<StripeCustomerPortalLink label="Cancel subscription" />
</DropdownMenuItem>
</>
)}
{/* All users */}
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => signOut()}>Log out</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</>
);
}