pricing plans in settings
This commit is contained in:
99
app/settings/components/billing/billing-history.tsx
Normal file
99
app/settings/components/billing/billing-history.tsx
Normal file
@ -0,0 +1,99 @@
|
||||
const payments = [
|
||||
{
|
||||
id: 1,
|
||||
date: new Date(),
|
||||
description: "",
|
||||
amount: "340 USD",
|
||||
href: "",
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
date: new Date(),
|
||||
description: "",
|
||||
amount: "340 USD",
|
||||
href: "",
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
date: new Date(),
|
||||
description: "",
|
||||
amount: "340 USD",
|
||||
href: "",
|
||||
},
|
||||
];
|
||||
|
||||
export default function BillingHistory() {
|
||||
return (
|
||||
<section>
|
||||
<div className="bg-white pt-6 shadow sm:rounded-md sm:overflow-hidden">
|
||||
<div className="px-4 sm:px-6">
|
||||
<h2 className="text-lg leading-6 font-medium text-gray-900">Billing history</h2>
|
||||
</div>
|
||||
<div className="mt-6 flex flex-col">
|
||||
<div className="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
||||
<div className="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
|
||||
<div className="overflow-hidden border-t border-gray-200">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Date
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Description
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Amount
|
||||
</th>
|
||||
{/*
|
||||
`relative` is added here due to a weird bug in Safari that causes `sr-only` headings to introduce overflow on the body on mobile.
|
||||
*/}
|
||||
<th
|
||||
scope="col"
|
||||
className="relative px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
<span className="sr-only">View receipt</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{payments.map((payment) => (
|
||||
<tr key={payment.id}>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
||||
<time>{payment.date.toDateString()}</time>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
{payment.description}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
{payment.amount}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
<a
|
||||
href={payment.href}
|
||||
className="text-primary-600 hover:text-primary-900"
|
||||
>
|
||||
View receipt
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
126
app/settings/components/billing/plans.tsx
Normal file
126
app/settings/components/billing/plans.tsx
Normal file
@ -0,0 +1,126 @@
|
||||
import { HiCheck } from "react-icons/hi";
|
||||
import * as Panelbear from "@panelbear/panelbear-js";
|
||||
import clsx from "clsx";
|
||||
|
||||
import useSubscription from "../../hooks/use-subscription";
|
||||
|
||||
export default function Plans() {
|
||||
const { subscription, subscribe } = useSubscription();
|
||||
|
||||
return (
|
||||
<div className="mt-6 flex flex-row-reverse flex-wrap-reverse gap-x-4">
|
||||
{pricing.tiers.map((tier) => {
|
||||
const isCurrentTier = subscription?.paddlePlanId === tier.planId;
|
||||
const cta = isCurrentTier ? "Current plan" : !!subscription ? `Switch to ${tier.title}` : "Subscribe";
|
||||
|
||||
return (
|
||||
<div
|
||||
key={tier.title}
|
||||
className={clsx(
|
||||
tier.yearly && "mb-4",
|
||||
"relative p-4 bg-white border border-gray-200 rounded-xl shadow-sm flex flex-grow w-1/3 flex-col",
|
||||
)}
|
||||
>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-xl font-mackinac font-semibold text-gray-900">{tier.title}</h3>
|
||||
{tier.yearly ? (
|
||||
<p className="absolute top-0 py-1.5 px-4 bg-primary-500 rounded-full text-xs font-semibold uppercase tracking-wide text-white transform -translate-y-1/2">
|
||||
Get 2 months free!
|
||||
</p>
|
||||
) : null}
|
||||
<p className="mt-4 flex items-baseline text-gray-900">
|
||||
<span className="text-2xl font-extrabold tracking-tight">{tier.price}€</span>
|
||||
<span className="ml-1 text-lg font-semibold">{tier.frequency}</span>
|
||||
</p>
|
||||
{tier.yearly ? (
|
||||
<p className="text-gray-500 text-sm">Billed yearly ({tier.price * 12}€)</p>
|
||||
) : null}
|
||||
<p className="mt-6 text-gray-500">{tier.description}</p>
|
||||
|
||||
<ul role="list" className="mt-6 space-y-6">
|
||||
{tier.features.map((feature) => (
|
||||
<li key={feature} className="flex">
|
||||
<HiCheck className="flex-shrink-0 w-6 h-6 text-[#0eb56f]" aria-hidden="true" />
|
||||
<span className="ml-3 text-gray-500">{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
{tier.unavailableFeatures.map((feature) => (
|
||||
<li key={feature} className="flex">
|
||||
<span className="ml-9 text-gray-400">
|
||||
{~feature.indexOf("(coming soon)")
|
||||
? feature.slice(0, feature.indexOf("(coming soon)"))
|
||||
: feature}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<button
|
||||
disabled={isCurrentTier}
|
||||
onClick={() => {
|
||||
subscribe({ planId: tier.planId });
|
||||
Panelbear.track(`Subscribe to ${tier.title}`);
|
||||
}}
|
||||
className={clsx(
|
||||
!isCurrentTier
|
||||
? "bg-primary-500 text-white hover:bg-primary-600"
|
||||
: "bg-primary-50 text-primary-700 cursor-not-allowed",
|
||||
"mt-8 block w-full py-3 px-6 border border-transparent rounded-md text-center font-medium",
|
||||
)}
|
||||
>
|
||||
{cta}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const paidFeatures = [
|
||||
"SMS",
|
||||
"MMS (coming soon)",
|
||||
"Calls",
|
||||
"SMS forwarding (coming soon)",
|
||||
"Call forwarding (coming soon)",
|
||||
"Voicemail (coming soon)",
|
||||
"Call recording (coming soon)",
|
||||
];
|
||||
const pricing = {
|
||||
tiers: [
|
||||
{
|
||||
title: "Free",
|
||||
planId: "free",
|
||||
price: 0,
|
||||
frequency: "",
|
||||
description: "The essentials to let you try Shellphone.",
|
||||
features: ["SMS (send only)"],
|
||||
unavailableFeatures: paidFeatures.slice(1),
|
||||
cta: "Subscribe",
|
||||
yearly: false,
|
||||
},
|
||||
{
|
||||
title: "Monthly",
|
||||
planId: "727540",
|
||||
price: 15,
|
||||
frequency: "/month",
|
||||
description: "Text and call anyone, anywhere in the world.",
|
||||
features: paidFeatures,
|
||||
unavailableFeatures: [],
|
||||
cta: "Subscribe",
|
||||
yearly: false,
|
||||
},
|
||||
{
|
||||
title: "Yearly",
|
||||
planId: "727544",
|
||||
price: 12.5,
|
||||
frequency: "/month",
|
||||
description: "Text and call anyone, anywhere in the world, all year long.",
|
||||
features: paidFeatures,
|
||||
unavailableFeatures: [],
|
||||
cta: "Subscribe",
|
||||
yearly: true,
|
||||
},
|
||||
],
|
||||
};
|
9
app/settings/components/divider.tsx
Normal file
9
app/settings/components/divider.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
export default function Divider() {
|
||||
return (
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-gray-300" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user