Moved the free plan usage bar into it’s own component and added it to storybook

This commit is contained in:
James Ritchie
2023-11-28 11:12:30 +00:00
parent 935849d169
commit dd585f996b
3 changed files with 88 additions and 38 deletions
@@ -0,0 +1,39 @@
import { ArrowUpCircleIcon } from "@heroicons/react/24/outline";
import { motion, useMotionValue, useTransform } from "framer-motion";
import { Paragraph } from "../primitives/Paragraph";
import { Link } from "@remix-run/react";
import { cn } from "~/utils/cn";
export function FreePlanUsage({ to, percentage }: { to: string; percentage: number }) {
const widthProgress = useMotionValue(percentage * 100);
const color = useTransform(
widthProgress,
[0, 74, 75, 95, 100],
["#22C55E", "#22C55E", "#F59E0B", "#F43F5E", "#F43F5E"]
);
return (
<div className="rounded border border-slate-900 bg-[#101722] p-2.5">
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-1">
<ArrowUpCircleIcon className="h-5 w-5 text-dimmed" />
<Paragraph className="text-2sm text-bright">Free Plan</Paragraph>
</div>
<Link to={to} className="text-2sm text-indigo-500">
Learn more
</Link>
</div>
<div className="relative mt-3 h-1 rounded-full bg-[#0B1018]">
<motion.div
initial={{ width: 0 }}
animate={{ width: percentage * 100 + "%" }}
style={{
backgroundColor: color,
}}
transition={{ duration: 1, type: "spring" }}
className={cn("absolute left-0 top-0 h-full rounded-full")}
/>
</div>
</div>
);
}
@@ -4,12 +4,10 @@ import {
ChartBarIcon,
EllipsisHorizontalIcon,
} from "@heroicons/react/20/solid";
import { ArrowUpCircleIcon } from "@heroicons/react/24/outline";
import { UserGroupIcon, UserPlusIcon } from "@heroicons/react/24/solid";
import { Link, useNavigation } from "@remix-run/react";
import { useNavigation } from "@remix-run/react";
import { IconExclamationCircle } from "@tabler/icons-react";
import { SlackIcon } from "@trigger.dev/companyicons";
import { motion, useMotionValue, useTransform } from "framer-motion";
import { AnchorHTMLAttributes, Fragment, useEffect, useRef, useState } from "react";
import { useFeatures } from "~/hooks/useFeatures";
import { MatchedOrganization } from "~/hooks/useOrganizations";
@@ -37,6 +35,7 @@ import { Feedback } from "../Feedback";
import { ImpersonationBanner } from "../ImpersonationBanner";
import { LogoIcon } from "../LogoIcon";
import { UserProfilePhoto } from "../UserProfilePhoto";
import { FreePlanUsage } from "../billing/FreePlanUsage";
import { Button, LinkButton } from "../primitives/Buttons";
import { Icon } from "../primitives/Icon";
import { type IconNames } from "../primitives/NamedIcon";
@@ -222,7 +221,7 @@ export function SideMenu({ user, project, organization, organizations }: SideMen
</Button>
}
/>
<FreePlanUsage to={organizationBillingPath(organization)} percentage={0.95} />
<FreePlanUsage to={organizationBillingPath(organization)} percentage={0.75} />
</div>
</div>
</div>
@@ -440,37 +439,3 @@ function SideMenuItem({
function MenuCount({ count }: { count: number | string }) {
return <div className="rounded-full bg-slate-900 px-2 py-1 text-xxs text-dimmed">{count}</div>;
}
function FreePlanUsage({ to, percentage }: { to: string; percentage: number }) {
const widthProgress = useMotionValue(percentage * 100);
const color = useTransform(
widthProgress,
[0, 74, 75, 95, 100],
["#22C55E", "#22C55E", "#F59E0B", "#F43F5E", "#F43F5E"]
);
return (
<div className="rounded border border-slate-900 bg-[#101722] p-2.5">
<div className="flex items-center justify-between">
<div className="flex items-center gap-1">
<ArrowUpCircleIcon className="h-5 w-5 text-dimmed" />
<Paragraph className="text-2sm text-bright">Free Plan</Paragraph>
</div>
<Link to={to} className="text-2sm text-indigo-500">
Learn more
</Link>
</div>
<div className="relative mt-3 h-1 rounded-full bg-[#0B1018]">
<motion.div
initial={{ width: 0 }}
animate={{ width: percentage * 100 + "%" }}
style={{
backgroundColor: color,
}}
transition={{ duration: 1, type: "spring" }}
className={cn("absolute left-0 top-0 h-full rounded-full")}
/>
</div>
</div>
);
}
@@ -0,0 +1,46 @@
import type { Meta, StoryObj } from "@storybook/react";
import { FreePlanUsage } from "../billing/FreePlanUsage";
import { organizationBillingPath } from "~/utils/pathBuilder";
import { MatchedOrganization } from "~/hooks/useOrganizations";
const meta: Meta<typeof FreePlanUsageBar> = {
title: "Billing/FreePlanUsage",
component: FreePlanUsageBar,
};
export default meta;
type Story = StoryObj<typeof FreePlanUsageBar>;
const mockOrganization: MatchedOrganization = {
id: "mockID",
title: "mockTitle",
slug: "mockSlug",
projects: [
{ id: "mockId1", slug: "mockSlug1", name: "mockName1", jobCount: 1 },
{ id: "mockId2", slug: "mockSlug2", name: "mockName2", jobCount: 2 },
],
hasUnconfiguredIntegrations: false,
memberCount: 1,
};
export const ProgressBar: Story = {
args: {
organization: mockOrganization,
},
render: (args) => <FreePlanUsageBar {...args} />,
};
type FreePlanUsageBarProps = {
organization: MatchedOrganization;
};
function FreePlanUsageBar({ organization }: FreePlanUsageBarProps) {
return (
<div className="flex h-screen flex-col items-center justify-center p-12">
<div className="w-fit">
<FreePlanUsage to={organizationBillingPath(organization)} percentage={0.75} />
</div>
</div>
);
}