Skip to content

Commit ad640dd

Browse files
jsfezgregberge
authored andcommitted
feat: rework pricing page
1 parent 8e899bb commit ad640dd

10 files changed

+4785
-5761
lines changed

app/pricing/DollarFormatter.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const dollarFormatter = new Intl.NumberFormat(undefined, {
2+
style: "currency",
3+
currency: "USD",
4+
maximumSignificantDigits: 10,
5+
});

app/pricing/PricingCard.tsx

+201
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
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+
);

app/pricing/PricingFaq.tsx

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import {
2+
Accordion,
3+
AccordionContent,
4+
AccordionItem,
5+
AccordionTrigger,
6+
} from "@/components/Accordion";
7+
import { Link } from "@/components/Link";
8+
9+
export const FAQ = ({
10+
hobbyPlanScreenshotCount,
11+
}: {
12+
hobbyPlanScreenshotCount: number;
13+
}) => (
14+
<Accordion type="single" collapsible className="w-full max-w-2xl text-left">
15+
<AccordionItem value="apart">
16+
<AccordionTrigger>
17+
What sets Argos apart from other visual testing tools?
18+
</AccordionTrigger>
19+
<AccordionContent>
20+
Argos focuses on providing a user-friendly experience with simplicity at
21+
its core. Currently, our unique features include managing flaky tests,
22+
and we are working on offering zero-configuration visual testing.
23+
</AccordionContent>
24+
</AccordionItem>
25+
<AccordionItem value="argos-plan">
26+
<AccordionTrigger>Which Argos plan is right for me?</AccordionTrigger>
27+
<AccordionContent>
28+
The Hobby plan is designed for personal GitHub repositories, providing
29+
up to {hobbyPlanScreenshotCount.toLocaleString()} screenshots. If you're
30+
seeking to collaborate as a team, need a higher screenshot limit, or
31+
wish to use Argos on a repository within a private GitHub organization,
32+
our Pro plan is the necessary choice.
33+
</AccordionContent>
34+
</AccordionItem>
35+
<AccordionItem value="mobile-testing">
36+
<AccordionTrigger>
37+
Can Argos be used for mobile app testing?
38+
</AccordionTrigger>
39+
<AccordionContent>
40+
Yes, Argos can be used for mobile app testing. As long as you can send
41+
screenshots to Argos, it can be used to test your app.
42+
</AccordionContent>
43+
</AccordionItem>
44+
<AccordionItem value="private">
45+
<AccordionTrigger>Are my screenshots private?</AccordionTrigger>
46+
<AccordionContent>
47+
Screenshots for open-source projects are public, while those for private
48+
repositories are restricted to team members. With the Pro plan, you can
49+
choose to restrict access to public repository screenshots to your team
50+
only.
51+
</AccordionContent>
52+
</AccordionItem>
53+
<AccordionItem value="usage">
54+
<AccordionTrigger>How does Argos determine usage?</AccordionTrigger>
55+
<AccordionContent>
56+
Usage is calculated based on the number of screenshots uploaded during
57+
successful builds. Screenshots uploaded during failed builds are not
58+
counted towards your usage.
59+
</AccordionContent>
60+
</AccordionItem>
61+
<AccordionItem value="limit">
62+
<AccordionTrigger>
63+
What happens if I exceed the plan's screenshot limit?
64+
</AccordionTrigger>
65+
<AccordionContent>
66+
<ul className="list-inside list-disc">
67+
<li>
68+
<span className="font-semibold">Regular plans: </span> you will not
69+
be able to upload any additional screenshots until your billing
70+
period renews.
71+
</li>
72+
<li>
73+
<span className="font-semibold">Usage-based plans:</span> you will
74+
be charged for every additional screenshot.
75+
</li>
76+
</ul>
77+
</AccordionContent>
78+
</AccordionItem>
79+
<AccordionItem value="feedback">
80+
<AccordionTrigger>
81+
How can I get support or provide feedback and feature requests?
82+
</AccordionTrigger>
83+
<AccordionContent>
84+
For all plans, you can reach out to our customer support and provide
85+
feedback or request new features through our{" "}
86+
<Link href="https://argos-ci.com/discord" className="text-primary-300">
87+
Argos Discord channel
88+
</Link>
89+
. Additionally, you can submit feature requests and feedback by{" "}
90+
<Link
91+
href="https://github.com/argos-ci/argos/issues"
92+
className="text-primary-300"
93+
>
94+
creating a GitHub issue
95+
</Link>
96+
.
97+
</AccordionContent>
98+
</AccordionItem>
99+
</Accordion>
100+
);

0 commit comments

Comments
 (0)