improve(public): list all prices instead of slider + rewrite article
This commit is contained in:
41
apps/public/components/faq.tsx
Normal file
41
apps/public/components/faq.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import Script from 'next/script';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from './ui/accordion';
|
||||
|
||||
export const Faqs = ({ children }: { children: React.ReactNode }) => (
|
||||
<Accordion
|
||||
type="single"
|
||||
collapsible
|
||||
className="w-full max-w-screen-md self-center border rounded-lg [&_button]:px-4 bg-background-dark [&_div.answer]:bg-background-light"
|
||||
>
|
||||
{children}
|
||||
</Accordion>
|
||||
);
|
||||
|
||||
export const FaqItem = ({
|
||||
question,
|
||||
children,
|
||||
}: { question: string; children: string }) => (
|
||||
<AccordionItem
|
||||
value={question}
|
||||
itemScope
|
||||
itemProp="mainEntity"
|
||||
itemType="https://schema.org/Question"
|
||||
className="[&_[role=region]]:px-4"
|
||||
>
|
||||
<AccordionTrigger className="text-left" itemProp="name">
|
||||
{question}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent
|
||||
itemProp="acceptedAnswer"
|
||||
itemScope
|
||||
itemType="https://schema.org/Answer"
|
||||
>
|
||||
{children}
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
);
|
||||
@@ -1,12 +1,14 @@
|
||||
import { cn } from '@/lib/utils';
|
||||
import Image from 'next/image';
|
||||
|
||||
export function Figure({
|
||||
src,
|
||||
alt,
|
||||
caption,
|
||||
}: { src: string; alt: string; caption: string }) {
|
||||
className,
|
||||
}: { src: string; alt: string; caption: string; className?: string }) {
|
||||
return (
|
||||
<figure className="-mx-4">
|
||||
<figure className={cn('-mx-4', className)}>
|
||||
<Image
|
||||
src={src}
|
||||
alt={alt || caption}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { cn } from '@/lib/utils';
|
||||
import { PRICING } from '@openpanel/payments/src/prices';
|
||||
import { CheckIcon, ChevronRightIcon, DollarSignIcon } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { DoubleSwirl } from '../Swirls';
|
||||
import { PricingSlider } from '../pricing-slider';
|
||||
import { Section, SectionHeader } from '../section';
|
||||
import { Tag } from '../tag';
|
||||
import { Button } from '../ui/button';
|
||||
import { Tooltiper } from '../ui/tooltip';
|
||||
|
||||
export default Pricing;
|
||||
export function Pricing({ className }: { className?: string }) {
|
||||
@@ -17,7 +18,7 @@ export function Pricing({ className }: { className?: string }) {
|
||||
)}
|
||||
>
|
||||
<DoubleSwirl className="absolute top-0 left-0" />
|
||||
<div className="container relative z-10">
|
||||
<div className="container relative z-10 col">
|
||||
<SectionHeader
|
||||
tag={
|
||||
<Tag variant={'dark'}>
|
||||
@@ -29,11 +30,10 @@ export function Pricing({ className }: { className?: string }) {
|
||||
description="Just pick how many events you want to track each month. No hidden costs."
|
||||
/>
|
||||
|
||||
<div className="grid md:grid-cols-[400px_1fr] gap-8">
|
||||
<div className="grid self-center md:grid-cols-[200px_1fr] lg:grid-cols-[300px_1fr] gap-8">
|
||||
<div className="col gap-4">
|
||||
<h3 className="font-medium text-xl text-background/90 dark:text-foreground/90">
|
||||
Stop overpaying <br />
|
||||
for features
|
||||
Stop overpaying for features
|
||||
</h3>
|
||||
<ul className="gap-1 col text-background/70 dark:text-foreground/70">
|
||||
<li className="flex items-center gap-2">
|
||||
@@ -56,6 +56,10 @@ export function Pricing({ className }: { className?: string }) {
|
||||
<CheckIcon className="text-background/30 dark:text-foreground/30 size-4" />{' '}
|
||||
Unlimited tracked profiles
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<CheckIcon className="text-background/30 dark:text-foreground/30 size-4" />{' '}
|
||||
Yes, we have no limits or hidden costs
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<Button
|
||||
@@ -71,10 +75,112 @@ export function Pricing({ className }: { className?: string }) {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="col justify-between pt-14 gap-4">
|
||||
<PricingSlider />
|
||||
<div className="col justify-between gap-4 max-w-lg">
|
||||
<div className="space-y-2">
|
||||
{PRICING.map((tier) => (
|
||||
<div
|
||||
key={tier.events}
|
||||
className={cn(
|
||||
'group col',
|
||||
'backdrop-blur-3xl bg-foreground/70 dark:bg-background-dark/70',
|
||||
'p-4 py-2 border border-background/20 dark:border-foreground/20 rounded-lg hover:bg-background/5 dark:hover:bg-foreground/5 transition-colors',
|
||||
'mx-2',
|
||||
tier.discount &&
|
||||
'mx-0 px-6 py-3 !bg-emerald-900/20 hover:!bg-emerald-900/30',
|
||||
tier.popular &&
|
||||
'mx-0 px-6 py-3 !bg-orange-900/20 hover:!bg-orange-900/30',
|
||||
)}
|
||||
>
|
||||
<div className="row justify-between">
|
||||
<div>
|
||||
{new Intl.NumberFormat('en-US', {}).format(tier.events)}{' '}
|
||||
<span className="text-muted-foreground text-sm max-[420px]:hidden">
|
||||
events / month
|
||||
</span>
|
||||
</div>
|
||||
<div className="row gap-4">
|
||||
{tier.popular && (
|
||||
<>
|
||||
<Tag variant="dark" className="hidden md:inline-flex">
|
||||
🔥 Popular
|
||||
</Tag>
|
||||
<span className="md:hidden">🔥</span>
|
||||
</>
|
||||
)}
|
||||
{tier.discount && (
|
||||
<>
|
||||
<Tag
|
||||
variant="dark"
|
||||
className="hidden md:inline-flex whitespace-nowrap"
|
||||
>
|
||||
💸 Discount
|
||||
</Tag>
|
||||
<span className="md:hidden">💸</span>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="text-sm text-muted-foreground">
|
||||
<div className="row gap-1">
|
||||
{tier.discount && (
|
||||
<span className={cn('text-md font-semibold')}>
|
||||
{new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
}).format(tier.price * (1 - tier.discount.amount))}
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
className={cn(
|
||||
'text-md font-semibold',
|
||||
tier.discount && 'line-through opacity-50',
|
||||
)}
|
||||
>
|
||||
{new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
}).format(tier.price)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{tier.discount && (
|
||||
<div className="text-sm text-muted-foreground mt-2">
|
||||
Limited discount code available:{' '}
|
||||
<Tooltiper
|
||||
content={`Get ${tier.discount.amount * 100}% off your first year`}
|
||||
delayDuration={0}
|
||||
side="bottom"
|
||||
>
|
||||
<strong>{tier.discount.code}</strong>
|
||||
</Tooltiper>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<div
|
||||
className={cn(
|
||||
'group',
|
||||
'row justify-between p-4 py-2 border border-background/20 dark:border-foreground/20 rounded-lg hover:bg-background/5 dark:hover:bg-foreground/5 transition-colors',
|
||||
'mx-2',
|
||||
)}
|
||||
>
|
||||
<div className="whitespace-nowrap">
|
||||
Over{' '}
|
||||
{new Intl.NumberFormat('en-US', {}).format(
|
||||
PRICING[PRICING.length - 1].events,
|
||||
)}
|
||||
</div>
|
||||
<div className="text-md font-semibold">
|
||||
<Link
|
||||
href="mailto:support@openpanel.dev"
|
||||
className="group-hover:underline"
|
||||
>
|
||||
Contact us
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="self-center text-sm text-muted-foreground mt-4 text-center max-w-[70%] w-full">
|
||||
<strong className="text-background/80 dark:text-foreground/80">
|
||||
All features are included upfront - no hidden costs.
|
||||
</strong>{' '}
|
||||
|
||||
@@ -31,7 +31,7 @@ const AccordionTrigger = ({
|
||||
}: React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger> & {
|
||||
ref?: React.RefObject<React.ElementRef<typeof AccordionPrimitive.Trigger>>;
|
||||
}) => (
|
||||
<AccordionPrimitive.Header className="flex">
|
||||
<AccordionPrimitive.Header className="flex not-prose">
|
||||
<AccordionPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
@@ -57,10 +57,17 @@ const AccordionContent = ({
|
||||
}) => (
|
||||
<AccordionPrimitive.Content
|
||||
ref={ref}
|
||||
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
||||
className="overflow-hidden transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
||||
{...props}
|
||||
>
|
||||
<div className={cn('pb-4 pt-0', className)}>{children}</div>
|
||||
<div
|
||||
className={cn(
|
||||
'pb-4 pt-0 [&>*:first-child]:mt-0 [&>*:last-child]:mb-0',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</AccordionPrimitive.Content>
|
||||
);
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ const Tooltip = TooltipPrimitive.Root;
|
||||
|
||||
const TooltipTrigger = TooltipPrimitive.Trigger;
|
||||
|
||||
const TooltipPortal = TooltipPrimitive.Portal;
|
||||
|
||||
const TooltipContent = ({
|
||||
ref,
|
||||
className,
|
||||
@@ -32,3 +34,20 @@ const TooltipContent = ({
|
||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
||||
|
||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
||||
|
||||
export const Tooltiper = ({
|
||||
children,
|
||||
content,
|
||||
delayDuration = 0,
|
||||
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> & {
|
||||
delayDuration?: number;
|
||||
}) => (
|
||||
<Tooltip delayDuration={delayDuration}>
|
||||
<TooltipTrigger asChild>{children}</TooltipTrigger>
|
||||
<TooltipPortal>
|
||||
<TooltipContent {...props}>{content}</TooltipContent>
|
||||
</TooltipPortal>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user