|
| 1 | +import clsx from "clsx"; |
| 2 | +import { CheckCircleIcon, CircleIcon, InfoIcon } from "lucide-react"; |
| 3 | +import * as React from "react"; |
| 4 | +import { twc } from "react-twc"; |
| 5 | + |
| 6 | +import { Button, ButtonProps } from "@/components/Button"; |
| 7 | +import { Link } from "@/components/Link"; |
| 8 | +import { Tooltip } from "@/components/Tooltip"; |
| 9 | + |
| 10 | +import { dollarFormatter } from "./DollarFormatter"; |
| 11 | + |
| 12 | +const PricingCardBody = twc.div`p-8 text-left text-low`; |
| 13 | +const Title = twc.div`mb-2 text-xl font-semibold text`; |
| 14 | +const Description = twc.div`my-2 h-12 last-of-type:mb-0`; |
| 15 | +const Badges = twc.div`block h-8`; |
| 16 | +const Badge = twc.div`text-primary-300 mb-10 w-fit rounded border border-violet-6 px-2.5 py-0.5 text-xs font-medium text`; |
| 17 | +const Paragraph = twc.p`block text-sm min-h-5`; |
| 18 | + |
| 19 | +const Features = twc.ul`mt-4 mb-6 flex flex-col gap-4`; |
| 20 | +const FeaturesCaption = twc(Paragraph)``; |
| 21 | +const Feature = ({ |
| 22 | + children, |
| 23 | + optional, |
| 24 | + className, |
| 25 | +}: { |
| 26 | + children: React.ReactNode; |
| 27 | + optional?: boolean; |
| 28 | + className?: string; |
| 29 | +}) => { |
| 30 | + const Icon = optional ? CircleIcon : CheckCircleIcon; |
| 31 | + return ( |
| 32 | + <li className={clsx("flex items-center gap-2 leading-tight", className)}> |
| 33 | + <Icon className="size-4 shrink-0 text-violet-11" /> |
| 34 | + {children} |
| 35 | + </li> |
| 36 | + ); |
| 37 | +}; |
| 38 | + |
| 39 | +const InfoTooltip = (props: { children: React.ReactNode }) => { |
| 40 | + return ( |
| 41 | + <Tooltip content={props.children}> |
| 42 | + <InfoIcon className="-mt-[2px] ml-1 inline-block h-5 w-5 text" /> |
| 43 | + </Tooltip> |
| 44 | + ); |
| 45 | +}; |
| 46 | + |
| 47 | +const PricingCard = ({ |
| 48 | + children, |
| 49 | + emphasis, |
| 50 | +}: { |
| 51 | + children: React.ReactNode; |
| 52 | + emphasis?: boolean; |
| 53 | +}) => ( |
| 54 | + <div |
| 55 | + className={clsx( |
| 56 | + "border-border w-full flex-1 shrink-0 basis-80 rounded-xl border bg-violet-1", |
| 57 | + emphasis ? "border-2 border-violet-6 pt-4" : "md:mt-4", |
| 58 | + )} |
| 59 | + > |
| 60 | + {children} |
| 61 | + </div> |
| 62 | +); |
| 63 | + |
| 64 | +const Price = ({ |
| 65 | + amount, |
| 66 | + recurring, |
| 67 | + fixedPrice, |
| 68 | +}: { |
| 69 | + amount: number; |
| 70 | + recurring: boolean; |
| 71 | + fixedPrice: boolean; |
| 72 | +}) => ( |
| 73 | + <div className="flex flex-col gap-1 pt-6"> |
| 74 | + <Paragraph>{fixedPrice ? null : "Starting at"}</Paragraph> |
| 75 | + <div className="flex items-baseline"> |
| 76 | + <span className="text-3xl font-semibold text"> |
| 77 | + <span className="tracking-tight">{dollarFormatter.format(amount)}</span> |
| 78 | + </span> |
| 79 | + {recurring && <span className="ml-1 text-lg text-low">/mo</span>} |
| 80 | + </div> |
| 81 | + <Paragraph> |
| 82 | + {fixedPrice ? "forever" : "Billed monthly based on usage"} |
| 83 | + </Paragraph> |
| 84 | + </div> |
| 85 | +); |
| 86 | + |
| 87 | +const CTA = ({ |
| 88 | + children, |
| 89 | + href, |
| 90 | + ...props |
| 91 | +}: ButtonProps & { |
| 92 | + children: React.ReactNode; |
| 93 | + href: string; |
| 94 | +}) => ( |
| 95 | + <Button |
| 96 | + className="mb-6 mt-10 w-full justify-center" |
| 97 | + size="large" |
| 98 | + asChild |
| 99 | + {...props} |
| 100 | + > |
| 101 | + <Link href={href} passHref> |
| 102 | + {children} |
| 103 | + </Link> |
| 104 | + </Button> |
| 105 | +); |
| 106 | + |
| 107 | +export const PricingCards = ({ |
| 108 | + hobbyPlanScreenshotCount, |
| 109 | + proPlanFlatPrice, |
| 110 | + proPlanScreenshotCount, |
| 111 | + additionalScreenshotPrice, |
| 112 | + githubSSOPrice, |
| 113 | +}: { |
| 114 | + hobbyPlanScreenshotCount: number; |
| 115 | + proPlanFlatPrice: number; |
| 116 | + proPlanScreenshotCount: number; |
| 117 | + additionalScreenshotPrice: number; |
| 118 | + githubSSOPrice: number; |
| 119 | +}) => ( |
| 120 | + <div className="grid w-full grid-cols-1 justify-center gap-6 md:grid-cols-3"> |
| 121 | + <PricingCard> |
| 122 | + <PricingCardBody> |
| 123 | + <Badges /> |
| 124 | + <Title>Hobby Plan</Title> |
| 125 | + <Description>For personal projects.</Description> |
| 126 | + <Price amount={0} recurring={false} fixedPrice={true} /> |
| 127 | + <CTA href="https://app.argos-ci.com/login" variant="outline"> |
| 128 | + Start Hobby Plan |
| 129 | + </CTA> |
| 130 | + |
| 131 | + <Features> |
| 132 | + <FeaturesCaption /> |
| 133 | + <Feature> |
| 134 | + Up to {hobbyPlanScreenshotCount.toLocaleString()} screenshots |
| 135 | + </Feature> |
| 136 | + <Feature>Unlimited Playwright Traces</Feature> |
| 137 | + <Feature>Visual changes detection</Feature> |
| 138 | + <Feature>GitHub & GitLab integration</Feature> |
| 139 | + <Feature>Community Support</Feature> |
| 140 | + </Features> |
| 141 | + </PricingCardBody> |
| 142 | + </PricingCard> |
| 143 | + |
| 144 | + <PricingCard emphasis> |
| 145 | + <PricingCardBody> |
| 146 | + <Badges> |
| 147 | + <Badge>Most Popular</Badge> |
| 148 | + </Badges> |
| 149 | + <Title>Pro Plan</Title> |
| 150 | + <Description>Unlimited screenshots and team collaboration.</Description> |
| 151 | + <Price amount={proPlanFlatPrice} recurring={true} fixedPrice={false} /> |
| 152 | + <CTA href="https://app.argos-ci.com/signup?plan=pro"> |
| 153 | + Start Free Trial |
| 154 | + </CTA> |
| 155 | + |
| 156 | + <Features> |
| 157 | + <FeaturesCaption>Everything in Hobby, plus:</FeaturesCaption> |
| 158 | + <Feature> |
| 159 | + Includes {proPlanScreenshotCount.toLocaleString()} screenshots |
| 160 | + </Feature> |
| 161 | + <Feature> |
| 162 | + {dollarFormatter.format(additionalScreenshotPrice)} per extra |
| 163 | + screenshot |
| 164 | + </Feature> |
| 165 | + <Feature>Pro Support</Feature> |
| 166 | + <Feature>Collaborative review</Feature> |
| 167 | + <div className="mt-4 text-sm font-medium">Optional</div> |
| 168 | + <Feature optional> |
| 169 | + GitHub SSO |
| 170 | + <div className="text-primary-300 ml-auto w-fit rounded border border-dashed border-violet-6 px-2.5 py-0.5 text-xs"> |
| 171 | + {dollarFormatter.format(githubSSOPrice)} /mo |
| 172 | + </div> |
| 173 | + </Feature> |
| 174 | + </Features> |
| 175 | + </PricingCardBody> |
| 176 | + </PricingCard> |
| 177 | + |
| 178 | + <PricingCard> |
| 179 | + <PricingCardBody> |
| 180 | + <Badges /> |
| 181 | + <Title>Enterprise</Title> |
| 182 | + <Description>Tailored solutions with premium features.</Description> |
| 183 | + <div className="mb-6 mt-12 flex items-baseline pt-2 text-3xl font-semibold text"> |
| 184 | + Custom |
| 185 | + </div> |
| 186 | + <CTA href="mailto:[email protected]" variant="outline"> |
| 187 | + Contact Sales |
| 188 | + </CTA> |
| 189 | + |
| 190 | + <Features> |
| 191 | + <FeaturesCaption>Everything in Pro, plus:</FeaturesCaption> |
| 192 | + <Feature>Custom amount of screenshots</Feature> |
| 193 | + <Feature>Dedicated Support</Feature> |
| 194 | + <Feature>GitHub SSO</Feature> |
| 195 | + <Feature>SAML authentication</Feature> |
| 196 | + <Feature>SLA for 99.99% Uptime</Feature> |
| 197 | + </Features> |
| 198 | + </PricingCardBody> |
| 199 | + </PricingCard> |
| 200 | + </div> |
| 201 | +); |
0 commit comments