Skip to content

Commit 297754c

Browse files
committed
docs: add pricing page
1 parent 7273dbf commit 297754c

File tree

10 files changed

+381
-13
lines changed

10 files changed

+381
-13
lines changed

src/components/ui/accordion.tsx

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"use client";
2+
3+
import * as React from "react";
4+
import * as AccordionPrimitive from "@radix-ui/react-accordion";
5+
import { ChevronDown } from "lucide-react";
6+
import { cn } from "@/utils/classes";
7+
8+
const Accordion = AccordionPrimitive.Root;
9+
10+
const AccordionItem = React.forwardRef<
11+
React.ElementRef<typeof AccordionPrimitive.Item>,
12+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
13+
>(({ className, ...props }, ref) => (
14+
<AccordionPrimitive.Item ref={ref} className={cn("border-b", className)} {...props} />
15+
));
16+
AccordionItem.displayName = "AccordionItem";
17+
18+
const AccordionTrigger = React.forwardRef<
19+
React.ElementRef<typeof AccordionPrimitive.Trigger>,
20+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
21+
>(({ className, children, ...props }, ref) => (
22+
<AccordionPrimitive.Header className="flex">
23+
<AccordionPrimitive.Trigger
24+
ref={ref}
25+
className={cn(
26+
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
27+
className
28+
)}
29+
{...props}
30+
>
31+
{children}
32+
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
33+
</AccordionPrimitive.Trigger>
34+
</AccordionPrimitive.Header>
35+
));
36+
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
37+
38+
const AccordionContent = React.forwardRef<
39+
React.ElementRef<typeof AccordionPrimitive.Content>,
40+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
41+
>(({ className, children, ...props }, ref) => (
42+
<AccordionPrimitive.Content
43+
ref={ref}
44+
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
45+
{...props}
46+
>
47+
<div className={cn("pb-4 pt-0", className)}>{children}</div>
48+
</AccordionPrimitive.Content>
49+
));
50+
51+
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
52+
53+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };

src/components/ui/switch.tsx

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"use client";
2+
3+
import * as React from "react";
4+
import * as SwitchPrimitives from "@radix-ui/react-switch";
5+
import { cn } from "@/utils/classes";
6+
7+
const Switch = React.forwardRef<
8+
React.ElementRef<typeof SwitchPrimitives.Root>,
9+
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
10+
>(({ className, ...props }, ref) => (
11+
<SwitchPrimitives.Root
12+
className={cn(
13+
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
14+
className
15+
)}
16+
{...props}
17+
ref={ref}
18+
>
19+
<SwitchPrimitives.Thumb
20+
className={cn(
21+
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
22+
)}
23+
/>
24+
</SwitchPrimitives.Root>
25+
));
26+
Switch.displayName = SwitchPrimitives.Root.displayName;
27+
28+
export { Switch };

src/config/site-config.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export const siteConfig = {
1616
},
1717
],
1818
creator: "mehdibha",
19-
thumbnail: "/images/thumbnail.jpg",
19+
thumbnail: "/images/thumbnail.png",
2020
twitter: {
2121
creator: "@mehdibha_",
2222
},

src/lib/pages/pricing/cta.tsx

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React from "react";
2+
import Link from "next/link";
3+
import { Button } from "@/components/ui/button";
4+
import { cn } from "@/utils/classes";
5+
6+
interface CallToActionProps {
7+
headline: string;
8+
subheadline: string;
9+
cta: {
10+
label: string;
11+
href: string;
12+
};
13+
className?: string;
14+
}
15+
16+
export const CallToAction = (props: CallToActionProps) => {
17+
const { headline, subheadline, cta, className } = props;
18+
return (
19+
<section className={cn("mx-auto max-w-xl text-center", className)}>
20+
<h2 className="font-display text-4xl font-semibold tracking-tighter sm:text-5xl">
21+
{headline}
22+
</h2>
23+
<p className="mt-4 text-muted-foreground">{subheadline}</p>
24+
<Button asChild size="lg" className="mt-8">
25+
<Link href={cta.href}>{cta.label}</Link>
26+
</Button>
27+
</section>
28+
);
29+
};

src/lib/pages/pricing/faq.tsx

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React from "react";
2+
import {
3+
Accordion,
4+
AccordionContent,
5+
AccordionItem,
6+
AccordionTrigger,
7+
} from "@/components/ui/accordion";
8+
9+
interface FAQProps {
10+
questions: { question: string; answer: string }[];
11+
className?: string;
12+
}
13+
14+
export const FAQ = (props: FAQProps) => {
15+
const { questions, className } = props;
16+
17+
return (
18+
<section className={className}>
19+
<h2 className="text-center text-3xl font-semibold tracking-tight sm:text-4xl">
20+
Frequently asked questions
21+
</h2>
22+
<div className="container mt-8 max-w-3xl">
23+
<Accordion type="single" collapsible className="w-full">
24+
{questions.map((elem, index) => (
25+
<AccordionItem key={index} value={index.toString()}>
26+
<AccordionTrigger className="text-lg font-semibold">
27+
{elem.question}
28+
</AccordionTrigger>
29+
<AccordionContent className="text-md pb-8">{elem.answer}</AccordionContent>
30+
</AccordionItem>
31+
))}
32+
</Accordion>
33+
</div>
34+
</section>
35+
);
36+
};

src/lib/pages/pricing/index.tsx

+97-9
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,102 @@
1-
import { PricingPlan } from "./pricing-plan";
1+
"use client";
22

3-
export default function Pricing() {
3+
import React from "react";
4+
import { CallToAction } from "./cta";
5+
import { FAQ } from "./faq";
6+
import { PricingComparaison } from "./pricing-comparaison";
7+
import type { Plan } from "./types";
8+
9+
const headline = "Simple pricing";
10+
const subheadline = "Use rCopy for free. Upgrade to enable to access premium templates.";
11+
const pricingPlans: Plan[] = [
12+
{
13+
name: "Free",
14+
description: "Use rCopy for free",
15+
featured: false,
16+
price: {
17+
monthly: "$0",
18+
yearly: "$0",
19+
},
20+
features: [
21+
"Free hosting on 'turbocharger.cc'",
22+
"Optimized SEO",
23+
"Has 'Built with Turbocharger' branding",
24+
],
25+
cta: {
26+
label: "Get started",
27+
href: "#",
28+
},
29+
},
30+
{
31+
name: "Pro",
32+
featured: true,
33+
price: { monthly: "$19", yearly: "$190" },
34+
description: "Perfect for small / medium sized businesses.",
35+
features: [
36+
"Everything in Free.",
37+
"Basic analytics",
38+
"Remove 'Built with Turbocharger' branding",
39+
],
40+
cta: {
41+
label: "Get started",
42+
href: "#",
43+
},
44+
},
45+
{
46+
name: "Entreprise",
47+
price: { monthly: "$39", yearly: "$390" },
48+
description: "For even the biggest enterprise companies.",
49+
featured: false,
50+
features: ["Everything in Personal site.", "Advanced analytics", "Priority support"],
51+
cta: {
52+
label: "Get started",
53+
href: "#",
54+
},
55+
},
56+
];
57+
const questions = [
58+
{
59+
question: "How does turbocharger works?",
60+
answer:
61+
"Turbocharger is a monorepo starter that comes with Next.js, Tailwind CSS, Shadcn-ui, Server components, and more. It's a great way to start your next project.",
62+
},
63+
{
64+
question: "How do I create a website with turbocharger?",
65+
answer: "You can create a website with turbocharger by following the documentation.",
66+
},
67+
{
68+
question: "How much does turbocharger cost?",
69+
answer: "It's free to use turbocharger",
70+
},
71+
{
72+
question: "Can I use turbocharger for free?",
73+
answer: "Yes, you can use turbocharger for free.",
74+
},
75+
];
76+
const cta = {
77+
headline: "Get started today",
78+
subheadline: "Start creating your own react project today.",
79+
cta: {
80+
label: "Get started",
81+
href: "#",
82+
},
83+
};
84+
85+
export default function PricingPage() {
486
return (
5-
<div className="min-h-screen">
6-
<h1>Pricing</h1>
7-
<p>
8-
This is a simple example of a Next.js app with a custom routing solution that uses
9-
the file system to define routes.
10-
</p>
11-
<PricingPlan />
87+
<div className="container py-24">
88+
<h2 className="text-center font-display text-5xl font-bold tracking-tight">
89+
{headline}
90+
</h2>
91+
<p className="mt-2 text-center text-lg text-muted-foreground">{subheadline}</p>
92+
<PricingComparaison plans={pricingPlans} className="mt-8" />
93+
<FAQ questions={questions} className="mt-32" />
94+
<CallToAction
95+
headline={cta.headline}
96+
subheadline={cta.subheadline}
97+
cta={cta.cta}
98+
className="mt-32"
99+
/>
12100
</div>
13101
);
14102
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"use client";
2+
3+
import React from "react";
4+
import { Label } from "@/components/ui/label";
5+
import { Switch } from "@/components/ui/switch";
6+
import { cn } from "@/utils/classes";
7+
import { PricingPlan } from "./pricing-plan";
8+
import type { Plan } from "./types";
9+
10+
interface PricingProps {
11+
plans: Plan[];
12+
className?: string;
13+
}
14+
15+
export const PricingComparaison = (props: PricingProps) => {
16+
const { plans, className } = props;
17+
const [billing, setBilling] = React.useState<"monthly" | "yearly">("monthly");
18+
19+
return (
20+
<div className={className}>
21+
<div className="flex items-center justify-center space-x-4">
22+
<Label htmlFor="subscription" className="text-xl">
23+
Monthly
24+
</Label>
25+
<Switch
26+
checked={!(billing === "monthly")}
27+
onCheckedChange={(checked) => setBilling(checked ? "yearly" : "monthly")}
28+
id="subscription"
29+
/>
30+
<Label htmlFor="subscription" className="text-xl">
31+
Yearly
32+
</Label>
33+
</div>
34+
<div
35+
className={cn("mt-16 grid grid-cols-3 items-center", {
36+
"mx-auto max-w-3xl grid-cols-2": plans.length === 2,
37+
})}
38+
>
39+
{plans.map((plan, index) => (
40+
<PricingPlan
41+
key={plan.name}
42+
{...plan}
43+
billing={billing}
44+
className={
45+
index === 0
46+
? "rounded-l-3xl"
47+
: index === 1
48+
? "rounded-3xl"
49+
: index === 2
50+
? "rounded-r-3xl"
51+
: "rounded-3xl"
52+
}
53+
/>
54+
))}
55+
</div>
56+
</div>
57+
);
58+
};
+63-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,64 @@
1-
export const PricingPlan = () => {
2-
return <div>pricing plan</div>;
1+
import { CheckCircleIcon } from "lucide-react";
2+
import { Button } from "@/components/ui/button";
3+
import { cn } from "@/utils/classes";
4+
import type { Plan } from "./types";
5+
6+
interface PlanProps extends Plan {
7+
billing: "monthly" | "yearly";
8+
className?: string;
9+
}
10+
export const PricingPlan = (props: PlanProps) => {
11+
const {
12+
name,
13+
price,
14+
description,
15+
features,
16+
featured = false,
17+
billing,
18+
className,
19+
} = props;
20+
21+
return (
22+
<section
23+
className={cn(
24+
"relative flex min-h-[530px] flex-col border border-gray-400 bg-card px-6 py-16 shadow-2xl dark:border-gray-800 sm:px-8",
25+
featured && "z-10 border-none ring-4 ring-ring/80",
26+
className
27+
)}
28+
>
29+
{featured && (
30+
<div className="absolute right-8 top-[-4px] z-[-1] translate-y-[-100%] rounded-t-lg bg-ring/80 px-4 py-1 text-white shadow-lg">
31+
Most popular
32+
</div>
33+
)}
34+
<h3 className="mt-5 text-xl font-bold">{name}</h3>
35+
<p className={cn("mt-2 text-base")}>{description}</p>
36+
<p className="order-first text-5xl font-light tracking-tight">
37+
{billing === "monthly" ? price.monthly : price.yearly}
38+
<span className="ml-2 text-base font-normal text-muted-foreground">
39+
billed {billing}
40+
</span>
41+
</p>
42+
<ul
43+
role="list"
44+
className={cn("order-last mt-10 flex min-h-[100px] flex-col gap-y-3 text-sm", {
45+
"min-h-[220px]": featured,
46+
})}
47+
>
48+
{features.map((feature) => (
49+
<li key={feature} className="flex">
50+
<CheckCircleIcon />
51+
<span className="ml-4">{feature}</span>
52+
</li>
53+
))}
54+
</ul>
55+
<Button
56+
variant={featured ? "default" : "outline"}
57+
className="mt-8"
58+
aria-label={`Get started with the ${name} plan for ${billing === "monthly" ? price.monthly : price.yearly}`}
59+
>
60+
Get started
61+
</Button>
62+
</section>
63+
);
364
};

0 commit comments

Comments
 (0)