Upgrade prompts
This commit is contained in:
@@ -1,20 +1,20 @@
|
||||
import { ExclamationCircleIcon } from "@heroicons/react/20/solid";
|
||||
import { ArrowUpCircleIcon } from "@heroicons/react/24/outline";
|
||||
import tileBgPath from "~/assets/images/error-banner-tile@2x.png";
|
||||
import { MatchedOrganization } from "~/hooks/useOrganizations";
|
||||
import { MatchedOrganization, useOrganization } from "~/hooks/useOrganizations";
|
||||
import { useCurrentPlan } from "~/routes/_app.orgs.$organizationSlug/route";
|
||||
import { formatNumberCompact } from "~/utils/numberFormatter";
|
||||
import { v3BillingPath } from "~/utils/pathBuilder";
|
||||
import { LinkButton } from "../../primitives/Buttons";
|
||||
import { Icon } from "../../primitives/Icon";
|
||||
import { Paragraph } from "../../primitives/Paragraph";
|
||||
|
||||
type UpgradePromptProps = {
|
||||
runsEnabled: boolean;
|
||||
runCountCap: number;
|
||||
planPath: string;
|
||||
};
|
||||
export function UpgradePrompt() {
|
||||
const organization = useOrganization();
|
||||
const plan = useCurrentPlan();
|
||||
|
||||
if (!plan || !plan.v3Usage.hasExceededFreeTier) {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function UpgradePrompt({ runsEnabled, runCountCap, planPath }: UpgradePromptProps) {
|
||||
return (
|
||||
<div
|
||||
className="flex h-10 items-center justify-between border border-error bg-repeat py-0 pl-3 pr-2"
|
||||
@@ -23,17 +23,14 @@ export function UpgradePrompt({ runsEnabled, runCountCap, planPath }: UpgradePro
|
||||
<div className="flex items-center gap-2">
|
||||
<Icon icon={ExclamationCircleIcon} className="h-5 w-5 text-error" />
|
||||
<Paragraph variant="small" className="text-error">
|
||||
{runsEnabled
|
||||
? `You have exceeded the monthly ${formatNumberCompact(runCountCap)} runs
|
||||
limit`
|
||||
: `No runs are executing because you have exceeded the free limit`}
|
||||
You have exceeded the monthly $
|
||||
{(plan.v3Subscription?.plan?.limits.includedUsage ?? 500) / 100} free credits.
|
||||
</Paragraph>
|
||||
</div>
|
||||
<LinkButton
|
||||
variant={"primary/small"}
|
||||
LeadingIcon={ArrowUpCircleIcon}
|
||||
leadingIconClassName="px-0"
|
||||
to={planPath}
|
||||
to={v3BillingPath(organization)}
|
||||
>
|
||||
Upgrade
|
||||
</LinkButton>
|
||||
@@ -43,19 +40,6 @@ export function UpgradePrompt({ runsEnabled, runCountCap, planPath }: UpgradePro
|
||||
|
||||
export function useShowUpgradePrompt(organization?: MatchedOrganization) {
|
||||
const currentPlan = useCurrentPlan();
|
||||
const shouldShow =
|
||||
organization !== undefined &&
|
||||
currentPlan !== undefined &&
|
||||
currentPlan.usage.exceededRunCount &&
|
||||
currentPlan.usage.runCountCap !== undefined;
|
||||
|
||||
if (!shouldShow) {
|
||||
return { shouldShow };
|
||||
}
|
||||
|
||||
return {
|
||||
shouldShow,
|
||||
runCountCap: currentPlan.usage.runCountCap!,
|
||||
runsEnabled: organization.runsEnabled,
|
||||
};
|
||||
const shouldShow = currentPlan?.v3Usage.hasExceededFreeTier === true;
|
||||
return { shouldShow };
|
||||
}
|
||||
|
||||
@@ -104,7 +104,6 @@ type SideMenuProps = {
|
||||
export function SideMenu({ user, project, organization, organizations }: SideMenuProps) {
|
||||
const borderRef = useRef<HTMLDivElement>(null);
|
||||
const [showHeaderDivider, setShowHeaderDivider] = useState(false);
|
||||
const { isManagedCloud } = useFeatures();
|
||||
const currentPlan = useCurrentPlan();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -308,17 +307,12 @@ export function SideMenu({ user, project, organization, organizations }: SideMen
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
{currentPlan &&
|
||||
!currentPlan.v3Subscription?.isPaying &&
|
||||
currentPlan.v3Subscription.plan &&
|
||||
currentPlan.v3Usage && (
|
||||
<FreePlanUsage
|
||||
to={v3BillingPath(organization)}
|
||||
percentage={
|
||||
currentPlan.v3Usage.cents / currentPlan.v3Subscription.plan.limits.includedUsage
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{currentPlan?.v3Subscription?.isPaying === false && (
|
||||
<FreePlanUsage
|
||||
to={v3BillingPath(organization)}
|
||||
percentage={currentPlan.v3Usage.usagePercentage}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ArrowUpRightIcon } from "@heroicons/react/20/solid";
|
||||
import { Link, useNavigation } from "@remix-run/react";
|
||||
import { useOptionalOrganization } from "~/hooks/useOrganizations";
|
||||
import { useOptionalProject } from "~/hooks/useProject";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { plansPath } from "~/utils/pathBuilder";
|
||||
import { UpgradePrompt, useShowUpgradePrompt } from "../billing/v2/UpgradePrompt";
|
||||
@@ -23,7 +22,6 @@ export function NavBar({ children }: WithChildren) {
|
||||
const showUpgradePrompt = useShowUpgradePrompt(organization);
|
||||
const navigation = useNavigation();
|
||||
const isLoading = navigation.state === "loading" || navigation.state === "submitting";
|
||||
const project = useOptionalProject();
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -31,13 +29,7 @@ export function NavBar({ children }: WithChildren) {
|
||||
<div className="flex w-full items-center justify-between pl-3 pr-1">{children}</div>
|
||||
<LoadingBarDivider isLoading={isLoading} />
|
||||
</div>
|
||||
{showUpgradePrompt.shouldShow && organization && (
|
||||
<UpgradePrompt
|
||||
runsEnabled={showUpgradePrompt.runsEnabled}
|
||||
runCountCap={showUpgradePrompt.runCountCap}
|
||||
planPath={plansPath(organization)}
|
||||
/>
|
||||
)}
|
||||
{showUpgradePrompt.shouldShow && organization && <UpgradePrompt />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,16 +27,9 @@ export function useCurrentPlan(matches?: UIMatch[]) {
|
||||
matches,
|
||||
});
|
||||
|
||||
const result = data?.currentPlan
|
||||
? {
|
||||
...data.currentPlan,
|
||||
v3Usage: data.usage,
|
||||
}
|
||||
: undefined;
|
||||
console.log(data?.currentPlan);
|
||||
|
||||
console.log("useCurrentPlan", result);
|
||||
|
||||
return result;
|
||||
return data?.currentPlan;
|
||||
}
|
||||
|
||||
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
|
||||
@@ -66,18 +59,24 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
const usage = await getUsage(organization.id, { from: firstDayOfMonth, to: tomorrow });
|
||||
|
||||
let hasExceededFreeTier = false;
|
||||
let usagePercentage = 0;
|
||||
if (plan?.v3Subscription && !plan.v3Subscription.isPaying && plan.v3Subscription.plan && usage) {
|
||||
hasExceededFreeTier = usage.cents > plan.v3Subscription.plan.limits.includedUsage;
|
||||
usagePercentage = usage.cents / plan.v3Subscription.plan.limits.includedUsage;
|
||||
}
|
||||
|
||||
return typedjson({
|
||||
organizations,
|
||||
organization,
|
||||
project,
|
||||
isImpersonating: !!impersonationId,
|
||||
currentPlan: plan,
|
||||
usage,
|
||||
currentPlan: { ...plan, v3Usage: { ...usage, hasExceededFreeTier, usagePercentage } },
|
||||
});
|
||||
};
|
||||
|
||||
export default function Organization() {
|
||||
const { organization, project, organizations, isImpersonating, usage } =
|
||||
const { organization, project, organizations, isImpersonating } =
|
||||
useTypedLoaderData<typeof loader>();
|
||||
const user = useUser();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user