Skip to content

Commit

Permalink
feat: add pricing on landingpage with minor refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
shyamlohar committed Dec 27, 2023
1 parent 713a746 commit e4b48f6
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 91 deletions.
4 changes: 2 additions & 2 deletions app/components/pricing/feature.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
import { CheckIcon, Cross2Icon } from "@radix-ui/react-icons"
import clsx from "clsx"

type Feature = {
export type FeatureType = {
name: string
isAvailable: boolean
inProgress: boolean
}

export const Feature = ({ name, isAvailable, inProgress }: Feature) => (
export const Feature = ({ name, isAvailable, inProgress }: FeatureType) => (
<li
className={clsx(
inProgress && "text-muted",
Expand Down
92 changes: 92 additions & 0 deletions app/routes/_index/pricing.tsx
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>
)
}
35 changes: 32 additions & 3 deletions app/routes/_index/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
} from "@remix-run/node"

import { mergeMeta } from "@/lib/server/seo/seo-helpers"
import buildTags from "@/lib/server/seo/seo-utils"

import Faqs from "./faq"
import { FeatureSection } from "./feature-section"
Expand All @@ -14,15 +13,44 @@ import FeaturesVariantB from "./features-variant-b"
import Footer from "./footer"
import { HeroSection } from "./hero-section"
import { LogoCloud } from "./logo-cloud"
import { getAllPlans } from "@/models/plan"
import { getUserCurrencyFromRequest } from "@/utils/currency"
import { authenticator } from "@/services/auth.server"
import { Pricing } from "./pricing"

const loginFeatures = [
"Lorem ipsum, dolor sit amet consectetur adipisicing elit aute id magna.",
"Anim aute id magna aliqua ad ad non deserunt sunt. Qui irure qui lorem cupidatat commodo.",
"Ac tincidunt sapien vehicula erat auctor pellentesque rhoncus.",
]

export const loader = ({}: LoaderFunctionArgs) => {
return json({ hostUrl: process.env.HOST_URL })
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await authenticator.isAuthenticated(request, {
successRedirect: "/dashboard",
})

let plans = await getAllPlans()

const defaultCurrency = getUserCurrencyFromRequest(request)

plans = plans
.map((plan) => {
return {
...plan,
prices: plan.prices
.filter((price) => price.currency === defaultCurrency)
.map((price) => ({
...price,
amount: price.amount / 100,
})),
}
})
.sort((a, b) => a.prices[0].amount - b.prices[0].amount)

return {
plans,
defaultCurrency,
}
}

export const meta: MetaFunction = mergeMeta(
Expand All @@ -46,6 +74,7 @@ export default function Index() {
lightFeatureImage="/login-light.jpeg"
/>
<FeaturesVariantB />
<Pricing/>
<Faqs />
<Footer />
</div>
Expand Down
137 changes: 51 additions & 86 deletions app/routes/dashboard/plans/route.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
import { useState } from "react"
import { createCheckoutSession } from "@/models/checkout"
import { getAllPlans, getPlanByIdWithPrices } from "@/models/plan"
import { getSubscriptionByUserId } from "@/models/subscription"
import { getUserById } from "@/models/user"
import { authenticator } from "@/services/auth.server"
import { getUserCurrencyFromRequest } from "@/utils/currency"
import { CheckIcon, Cross2Icon } from "@radix-ui/react-icons"
import { redirect, type LoaderFunctionArgs } from "@remix-run/node"
import { Form, useLoaderData } from "@remix-run/react"
import clsx from "clsx"
import { useState } from "react"

import { getformattedCurrency } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import {
CTAContainer,
FeatureListContainer,
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 { Button } from "@/components/ui/button"

// TODO: to be discussed with Keyur
declare global {
Expand All @@ -35,12 +36,6 @@ BigInt.prototype.toJSON = function () {
return this.toString()
}

type Feature = {
name: string
isAvailable: boolean
inProgress: boolean
}

export const loader = async ({ request }: LoaderFunctionArgs) => {
const session = await authenticator.isAuthenticated(request, {
failureRedirect: "/login",
Expand Down Expand Up @@ -117,40 +112,12 @@ export const action = async ({ request }: LoaderFunctionArgs) => {
return redirect(checkout.url as string)
}

const Feature = ({ name, isAvailable, inProgress }: Feature) => (
<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>
)

export default function PlansPage() {
const { plans, subscription, defaultCurrency } =
useLoaderData<typeof loader>()
const [interval, setInterval] = useState<"month" | "year">("month")
// render shadcn ui pricing table using Card

const getformattedCurrency = (amount: number) => {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: defaultCurrency,
}).format(amount)
}
return (
<div>
<div className="mx-auto max-w-7xl px-6 lg:px-8">
Expand Down Expand Up @@ -180,54 +147,52 @@ export default function PlansPage() {
)?.amount as number

return (
<>
<PricingCard key={plan.id} isFeatured={showDiscount}>
{showDiscount && discount > 0 && (
<FeaturedBadgeContainer>
Save {getformattedCurrency(discount)}
</FeaturedBadgeContainer>
)}
<FeatureTitle>{plan.name}</FeatureTitle>
<FeatureDescription>{plan.description}</FeatureDescription>
<FeaturePrice
interval={interval}
price={getformattedCurrency(planPrice)}
/>
<FeatureListContainer>
{(plan.listOfFeatures as Feature[]).map(
(feature, index) => (
<Feature
key={index}
name={feature.name}
isAvailable={feature.isAvailable}
inProgress={feature.inProgress}
/>
)
)}
</FeatureListContainer>
<CTAContainer>
<Form method="post">
<input type="hidden" name="planId" value={plan.id} />
<input type="hidden" name="interval" value={interval} />
<input
type="hidden"
name="currency"
value={defaultCurrency}
<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}
/>

<Button
className="mt-8 w-full"
disabled={subscription?.planId === plan.id}
type="submit"
>
{subscription?.planId === plan.id
? "Current Plan"
: "Choose Plan"}
</Button>
</Form>
</CTAContainer>
</PricingCard>
</>
)
)}
</FeatureListContainer>
<CTAContainer>
<Form method="post">
<input type="hidden" name="planId" value={plan.id} />
<input type="hidden" name="interval" value={interval} />
<input
type="hidden"
name="currency"
value={defaultCurrency}
/>

<Button
className="mt-8 w-full"
disabled={subscription?.planId === plan.id}
type="submit"
>
{subscription?.planId === plan.id
? "Current Plan"
: "Choose Plan"}
</Button>
</Form>
</CTAContainer>
</PricingCard>
)
})}
</div>
Expand Down

0 comments on commit e4b48f6

Please sign in to comment.