197 lines
6.2 KiB
TypeScript
197 lines
6.2 KiB
TypeScript
"use client";
|
|
|
|
import React, { useEffect } from "react";
|
|
import PageHeader from "@/sections/PageHeader";
|
|
import { Price, Product, SourceModal } from "@/types";
|
|
import { Check } from "@styled-icons/entypo/Check";
|
|
import { ChangeEventHandler, useState } from "react";
|
|
import { apiPrices } from "./actions";
|
|
import Newsletter from "@/sections/Newsletter/Newsletter";
|
|
import { createCheckoutSession, isAuthenticated } from "@/app/(auth)/actions";
|
|
import { toast } from "react-toastify";
|
|
import { useRouter } from "next/navigation";
|
|
|
|
export default function PricingPage() {
|
|
const [isAnnual, setIsAnnual] = useState(false);
|
|
const [products, setProducts] = useState<Product[]>([]);
|
|
|
|
const handleToggle = () => {
|
|
setIsAnnual((prev) => !prev);
|
|
};
|
|
useEffect(() => {
|
|
(async () => {
|
|
const resposeProducts: Product[] = await apiPrices();
|
|
setProducts(resposeProducts);
|
|
})();
|
|
}, []);
|
|
|
|
return (
|
|
<main
|
|
id="content"
|
|
role="main"
|
|
className="h-full flex flex-col min-h-screen mx-auto w-screen overflow-hidden"
|
|
>
|
|
<PageHeader
|
|
title="Pricing"
|
|
subtitle={
|
|
<>
|
|
{" "}
|
|
<h2 className="h3 text-primary-content content text-center max-w-6xl mx-auto px-6">
|
|
Select a subscription plan for your team or try advanced
|
|
functionality for free.
|
|
</h2>
|
|
</>
|
|
}
|
|
/>
|
|
<div className="flex items-center justify-center mt-10">
|
|
<PriceToggle isAnnual={isAnnual} onChange={handleToggle} />
|
|
</div>
|
|
<div className="max-w-6xl mx-auto mb-24 h-full">
|
|
<div
|
|
className={`w-screen lg:w-full flex gap-x-4 lg:justify-center gap-y-8 px-6 pt-12 overflow-x-scroll`}
|
|
>
|
|
{products.map((x, i) => (
|
|
<PriceCard key={i} product={x} isAnnual={isAnnual} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
<Newsletter />
|
|
</main>
|
|
);
|
|
}
|
|
|
|
function PriceCard({
|
|
product,
|
|
isAnnual,
|
|
}: {
|
|
product: Product;
|
|
isAnnual: boolean;
|
|
}) {
|
|
const router = useRouter();
|
|
const openSignUpModalOnPriceClick = (price: Price) => {
|
|
const signUpModal = document.getElementById("sign-up-modal");
|
|
if (!signUpModal) return;
|
|
signUpModal.setAttribute("price_id", price.price_id);
|
|
signUpModal.setAttribute("name", SourceModal.SignUpViaPurchase);
|
|
signUpModal.click();
|
|
};
|
|
const generateCheckoutPage = async (price: Price) => {
|
|
try {
|
|
const checkoutSessionResponse = await createCheckoutSession(
|
|
price.price_id
|
|
);
|
|
router.push(checkoutSessionResponse.url);
|
|
} catch (error) {
|
|
if (error instanceof Error) {
|
|
toast.error(error.message, {
|
|
position: "bottom-left",
|
|
autoClose: 5000,
|
|
hideProgressBar: false,
|
|
closeOnClick: true,
|
|
pauseOnHover: true,
|
|
draggable: true,
|
|
progress: undefined,
|
|
theme: "colored",
|
|
});
|
|
}
|
|
}
|
|
};
|
|
const submitForm = async (price: Price) => {
|
|
const userIsAuthenticated = await isAuthenticated();
|
|
if (userIsAuthenticated) {
|
|
await generateCheckoutPage(price);
|
|
} else {
|
|
openSignUpModalOnPriceClick(price);
|
|
}
|
|
};
|
|
return (
|
|
<div className="relative w-64 sm:w-80 bg-gray-100 dark:bg-gray-800 p-6">
|
|
<div className="flex flex-col h-full">
|
|
<div className="mb-12 relative">
|
|
{false && (
|
|
<p className="absolute top-[-10px] left-[50%] -translate-x-1/2 font-architects-daughter text-xl text-pink-default text-center">
|
|
Popular
|
|
</p>
|
|
)}
|
|
|
|
<h1 className="text-center text-3xl font-inter font-bold pt-6 text-black dark:text-white">
|
|
{product.name}
|
|
</h1>
|
|
<h3 className="text-center pt-4 text-black dark:text-white">
|
|
{product.description}
|
|
</h3>
|
|
</div>
|
|
<div className="pb-12">
|
|
<ul className="flex flex-col gap-y-3 mx-12">
|
|
{product.metadata?.benefits?.map((x, i) => (
|
|
<li key={i} className="flex items-center gap-x-4 flex-nowrap">
|
|
<Check className=" self-start" color="#FF0DCA" size={24} />
|
|
|
|
<p className="w-40 text-black dark:text-white overflow-clip text-ellipsis">
|
|
{x}
|
|
</p>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
<div className="flex flex-col mx-auto mt-auto">
|
|
<div className="flex flex-row mx-auto gap-x-4 justify-center items-center mb-2">
|
|
<h1 className="h2 text-black dark:text-white">
|
|
$
|
|
{isAnnual
|
|
? product.yearlyPrice.unit_amount / 100
|
|
: product.monthlyPrice.unit_amount / 100}
|
|
</h1>
|
|
<p className="w-16 leading-5 text-sm text-black dark:text-white">
|
|
per user per {isAnnual ? "year" : "month"}
|
|
</p>
|
|
</div>
|
|
<button
|
|
onClick={() =>
|
|
submitForm(isAnnual ? product.yearlyPrice : product.monthlyPrice)
|
|
}
|
|
className=" mx-auto flex text-sm font-semibold py-2 px-20 m-2 text-white bg-gradient-to-r from-pink-default to-purple-default rounded-full mb-4 cursor-pointer group-hover:animate-bounce"
|
|
>
|
|
Try it!
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function PriceToggle({
|
|
isAnnual,
|
|
onChange,
|
|
}: {
|
|
isAnnual: boolean;
|
|
onChange?: ChangeEventHandler<HTMLInputElement>;
|
|
}) {
|
|
return (
|
|
<>
|
|
<label className="themeSwitcherTwo shadow-card relative inline-flex cursor-pointer select-none items-center justify-center rounded-sm dark:bg-gray-550 p-1">
|
|
<input
|
|
type="checkbox"
|
|
className="sr-only"
|
|
checked={isAnnual}
|
|
onChange={onChange}
|
|
/>
|
|
<span
|
|
className={`flex items-center space-x-[6px] rounded py-2 px-[18px] text-sm font-medium ${
|
|
!isAnnual ? "text-white bg-pink-550" : "text-body-color"
|
|
}`}
|
|
>
|
|
Monthly Billing
|
|
</span>
|
|
<span
|
|
className={`flex items-center space-x-[6px] rounded py-2 px-[18px] text-sm font-medium ${
|
|
isAnnual ? "text-white bg-pink-550" : "text-body-color"
|
|
}`}
|
|
>
|
|
Yearly Billing
|
|
</span>
|
|
</label>
|
|
</>
|
|
);
|
|
}
|