-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #23 from saas-kits/feat/pricing-refactoring
Feat/pricing refactoring
- Loading branch information
Showing
15 changed files
with
354 additions
and
293 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { cn } from "@/lib/utils" | ||
|
||
type PricingCardProps = { | ||
children: React.ReactNode | ||
isFeatured?: boolean | ||
} | ||
export const PricingCard = ({ children, isFeatured }: PricingCardProps) => { | ||
return ( | ||
<div | ||
className={cn("rounded-[13px] p-px", { | ||
"bg-gradient-to-b from-zinc-400 to-white dark:from-zinc-500 dark:to-black": | ||
isFeatured, | ||
"bg-gradient-to-b from-border to-white dark:to-black": !isFeatured, | ||
})} | ||
> | ||
<div className="relative h-full w-full rounded-xl bg-white p-6 pb-24 dark:bg-background"> | ||
{children} | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
type FeatureListContainerProps = { | ||
children: React.ReactNode | ||
} | ||
|
||
export const FeatureListContainer = ({ | ||
children, | ||
}: FeatureListContainerProps) => { | ||
return ( | ||
<ul className="mt-8 space-y-3 text-sm leading-6 xl:mt-10">{children}</ul> | ||
) | ||
} | ||
|
||
export const CTAContainer = ({ children }: FeatureListContainerProps) => { | ||
return ( | ||
<div className="absolute bottom-0 left-0 mx-6 mb-6 mt-8 w-[calc(100%-48px)]"> | ||
{children} | ||
</div> | ||
) | ||
} | ||
|
||
type FeaturedBadgeContainerProps = { | ||
children: React.ReactNode | ||
} | ||
|
||
export const FeaturedBadgeContainer = ({children}: FeaturedBadgeContainerProps) => { | ||
return ( | ||
<div className="absolute -top-2 left-0 flex h-4 w-full items-center justify-center text-sm"> | ||
<span className="rounded-full bg-black px-4 py-1 text-xs font-semibold text-white dark:bg-white dark:text-black">{children}</span> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
|
||
import { CheckIcon, Cross2Icon } from "@radix-ui/react-icons" | ||
import clsx from "clsx" | ||
|
||
export type FeatureType = { | ||
name: string | ||
isAvailable: boolean | ||
inProgress: boolean | ||
} | ||
|
||
export const Feature = ({ name, isAvailable, inProgress }: FeatureType) => ( | ||
<li | ||
className={clsx( | ||
inProgress && "text-muted", | ||
"flex gap-x-3 text-muted-foreground" | ||
)} | ||
> | ||
{/* If in progress return disabled */} | ||
{!isAvailable ? ( | ||
<Cross2Icon className={"h-6 w-5 flex-none"} aria-hidden="true" /> | ||
) : ( | ||
<CheckIcon className={"h-6 w-5 flex-none"} aria-hidden="true" /> | ||
)} | ||
{name}{" "} | ||
{inProgress && ( | ||
<span className="text-xs font-semibold leading-6 text-muted-foreground"> | ||
(Coming Soon) | ||
</span> | ||
)} | ||
</li> | ||
) | ||
|
||
type FeatureTitleProps = { | ||
children: React.ReactNode | ||
} | ||
|
||
export const FeatureTitle = ({ children }: FeatureTitleProps) => { | ||
return <div className="text-base font-semibold leading-8">{children}</div> | ||
} | ||
|
||
type FeatureDescriptionProps = { | ||
children: React.ReactNode | ||
} | ||
|
||
export const FeatureDescription = ({ children }: FeatureDescriptionProps) => { | ||
return ( | ||
<p className="wrap-balance mt-4 text-sm font-light leading-5 text-muted-foreground"> | ||
{children} | ||
</p> | ||
) | ||
} | ||
|
||
type FeaturePriceProps = { | ||
interval: string | ||
price: string | ||
} | ||
|
||
export const FeaturePrice = ({ | ||
interval, | ||
price, | ||
}: FeaturePriceProps) => { | ||
return ( | ||
<h4 className="mt-6 text-4xl font-bold tracking-tight"> | ||
{price} | ||
<span className="text-sm font-semibold leading-6 text-muted-foreground"> | ||
/{interval} | ||
</span> | ||
</h4> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs" | ||
|
||
type PricingSwitchProps = { | ||
onSwitch: (value: string) => void | ||
} | ||
|
||
export const PricingSwitch = ({ onSwitch }: PricingSwitchProps) => ( | ||
<Tabs defaultValue="0" className="mx-auto w-40" onValueChange={onSwitch}> | ||
<TabsList> | ||
<TabsTrigger value="0">Monthly</TabsTrigger> | ||
<TabsTrigger value="1">Yearly</TabsTrigger> | ||
</TabsList> | ||
</Tabs> | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,17 @@ | ||
import { CURRENCIES } from "@/services/stripe/plans.config" | ||
import { clsx, type ClassValue } from "clsx" | ||
import { twMerge } from "tailwind-merge" | ||
|
||
export function cn(...inputs: ClassValue[]) { | ||
return twMerge(clsx(inputs)) | ||
} | ||
|
||
export const getformattedCurrency = ( | ||
amount: number, | ||
defaultCurrency: CURRENCIES | ||
) => { | ||
return new Intl.NumberFormat("en-US", { | ||
style: "currency", | ||
currency: defaultCurrency, | ||
}).format(amount) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import { useState } from "react" | ||
import { NavLink, useLoaderData } from "@remix-run/react" | ||
|
||
import { getformattedCurrency } from "@/lib/utils" | ||
import { Button } from "@/components/ui/button" | ||
import { | ||
CTAContainer, | ||
FeaturedBadgeContainer, | ||
FeatureListContainer, | ||
PricingCard, | ||
} from "@/components/pricing/containers" | ||
import { | ||
Feature, | ||
FeatureDescription, | ||
FeaturePrice, | ||
FeatureTitle, | ||
FeatureType, | ||
} from "@/components/pricing/feature" | ||
import { PricingSwitch } from "@/components/pricing/pricing-switch" | ||
|
||
import type { loader } from "./route" | ||
|
||
export const Pricing = () => { | ||
const { plans, defaultCurrency } = useLoaderData<typeof loader>() | ||
const [interval, setInterval] = useState<"month" | "year">("month") | ||
|
||
return ( | ||
<div> | ||
<div className="mx-auto max-w-7xl px-6 lg:px-8"> | ||
<div className="mx-auto max-w-4xl text-center"> | ||
<h1 className="wrap-balance mt-16 bg-black bg-gradient-to-br bg-clip-text text-center text-4xl font-medium leading-tight text-transparent dark:from-white dark:to-[hsla(0,0%,100%,.5)] sm:text-5xl sm:leading-tight"> | ||
Pricing Plans | ||
</h1> | ||
</div> | ||
<p className="wrap-balance mt-6 text-center text-lg font-light leading-7 text-muted-foreground"> | ||
{/* TODO: add content here @keyur */} | ||
Lorem, ipsum dolor sit amet consectetur adipisicing elit. Rerum | ||
quisquam, iusto voluptatem dolore voluptas non laboriosam soluta quos | ||
quod eos! Sapiente archit | ||
</p> | ||
<div className="mt-16 flex justify-center"></div> | ||
<PricingSwitch | ||
onSwitch={(value) => setInterval(value === "0" ? "month" : "year")} | ||
/> | ||
|
||
<div className="isolate mx-auto mt-10 grid max-w-md grid-cols-1 gap-8 lg:max-w-5xl lg:grid-cols-3"> | ||
{plans.map((plan) => { | ||
const discount = plan.prices[0].amount * 12 - plan.prices[1].amount | ||
const showDiscount = | ||
interval === "year" && plan.prices[0].amount !== 0 | ||
const planPrice = plan.prices.find( | ||
(p) => p.currency === defaultCurrency && p.interval == interval | ||
)?.amount as number | ||
|
||
return ( | ||
<PricingCard key={plan.id} isFeatured={showDiscount}> | ||
{showDiscount && discount > 0 && ( | ||
<FeaturedBadgeContainer> | ||
Save {getformattedCurrency(discount, defaultCurrency)} | ||
</FeaturedBadgeContainer> | ||
)} | ||
<FeatureTitle>{plan.name}</FeatureTitle> | ||
<FeatureDescription>{plan.description}</FeatureDescription> | ||
<FeaturePrice | ||
interval={interval} | ||
price={getformattedCurrency(planPrice, defaultCurrency)} | ||
/> | ||
<FeatureListContainer> | ||
{(plan.listOfFeatures as FeatureType[]).map( | ||
(feature, index) => ( | ||
<Feature | ||
key={index} | ||
name={feature.name} | ||
isAvailable={feature.isAvailable} | ||
inProgress={feature.inProgress} | ||
/> | ||
) | ||
)} | ||
</FeatureListContainer> | ||
<CTAContainer> | ||
<NavLink to="/login"> | ||
<Button className="w-full">Choose Plan</Button> | ||
</NavLink> | ||
</CTAContainer> | ||
</PricingCard> | ||
) | ||
})} | ||
</div> | ||
</div> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.