From 9cbf0760a02c3054428cdb176ace9f4a547e7d89 Mon Sep 17 00:00:00 2001 From: Karrix Date: Tue, 16 Jan 2024 02:38:44 +0800 Subject: [PATCH] feat: implement lemonsqueezy to pricing table --- web/package.json | 1 + .../pricing/components/pricePlanList.tsx | 133 ++++++++++++ .../pricing/components/pricingPlanTable.tsx | 191 ------------------ web/src/app/(app)/pricing/const/Icon.tsx | 15 ++ web/src/app/(app)/pricing/page.tsx | 4 +- web/src/server/linkToPricing.ts | 12 ++ 6 files changed, 163 insertions(+), 193 deletions(-) create mode 100644 web/src/app/(app)/pricing/components/pricePlanList.tsx delete mode 100644 web/src/app/(app)/pricing/components/pricingPlanTable.tsx create mode 100644 web/src/app/(app)/pricing/const/Icon.tsx create mode 100644 web/src/server/linkToPricing.ts diff --git a/web/package.json b/web/package.json index 588cbb8..1df4f33 100644 --- a/web/package.json +++ b/web/package.json @@ -26,6 +26,7 @@ "@hono/zod-openapi": "^0.9.5", "@hono/zod-validator": "^0.1.11", "@hookform/resolvers": "^3.3.2", + "@lemonsqueezy/lemonsqueezy.js": "^1.2.5", "@mdx-js/loader": "^3.0.0", "@mdx-js/react": "^3.0.0", "@neondatabase/serverless": "^0.6.0", diff --git a/web/src/app/(app)/pricing/components/pricePlanList.tsx b/web/src/app/(app)/pricing/components/pricePlanList.tsx new file mode 100644 index 0000000..19d30c9 --- /dev/null +++ b/web/src/app/(app)/pricing/components/pricePlanList.tsx @@ -0,0 +1,133 @@ +import { checkMarkIcon } from "../const/Icon"; +import { cn } from "@/lib/utils"; +import { getPricing } from "@/server/linkToPricing"; +import { useEffect, useState } from "react"; + +type Tier = { + name: string; + id: string; + href: string; + priceMonthly: string; + description: string; + features: string[]; + featured: boolean; +}; + +export default function PricingList() { + const [productTiers, setProductTiers] = useState(); + + useEffect(() => { + (async () => { + const product = await getPricing(); + + if (!product) return; + + const newProductTiers: Tier[] = product.data.map((item) => { + // Create a new DOMParser instance + const parser = new DOMParser(); + // Parse the description HTML string to a new document + const doc = parser.parseFromString( + item.attributes.description, + "text/html" + ); + // Extract the description and features + const description = doc.querySelector("p")?.textContent || ""; + const features = Array.from(doc.querySelectorAll("ul > li")).map( + (li) => li.textContent || "" + ); + + return { + name: item.attributes.name, + id: item.id, + href: item.attributes.buy_now_url, + priceMonthly: item.attributes.price_formatted.split("/")[0], + description: description, + features: features, + + // if name contains pro, it's featured + featured: item.attributes.name.toLowerCase().includes("pro"), + }; + }); + + console.log(newProductTiers); + setProductTiers(newProductTiers); + })(); + }, []); + + return ( +
+
+

+ Pricing +

+

+ The right price for you, whoever you are +

+
+

+ Qui iusto aut est earum eos quae. Eligendi est at nam aliquid ad quo + reprehenderit in aliquid fugiat dolorum voluptatibus. +

+
+ {productTiers && + productTiers.map((tier, tierIdx) => ( +
+

+ {tier.name} +

+

+ + {tier.priceMonthly} + + /month +

+

+ {tier.description} +

+
    + {tier.features.map((feature) => ( +
  • +
    + {checkMarkIcon} +
    + {feature} +
  • + ))} +
+ + Get started today + +
+ ))} +
+
+ ); +} diff --git a/web/src/app/(app)/pricing/components/pricingPlanTable.tsx b/web/src/app/(app)/pricing/components/pricingPlanTable.tsx deleted file mode 100644 index ca49212..0000000 --- a/web/src/app/(app)/pricing/components/pricingPlanTable.tsx +++ /dev/null @@ -1,191 +0,0 @@ -import { cn } from "@/lib/utils"; -import { RadioGroup } from "@headlessui/react"; -import { useState } from "react"; - -// Define a type for the frequency options -type FrequencyOption = { - value: keyof TierPrice; // Use 'keyof TierPrice' instead of string - label: string; - priceSuffix: string; -}; - -// Define a type for the tier prices -type TierPrice = { - monthly: string; - annually: string; -}; - -// Define a type for the tier options -type TierOption = { - name: string; - id: string; - href: string; - price: TierPrice; - description: string; - features: string[]; - mostPopular: boolean; -}; - -// Define an interface for the pricing structure -interface Pricing { - frequencies: FrequencyOption[]; - tiers: TierOption[]; -} - -const checkMarkIcon = ( - -); - -const pricing: Pricing = { - frequencies: [ - { value: "monthly", label: "Monthly", priceSuffix: "/month" }, - { value: "annually", label: "Annually", priceSuffix: "/year" }, - ], - tiers: [ - { - name: "Hobby", - id: "tier-hobby", - href: "#", - price: { monthly: "$15", annually: "$144" }, - description: "The essentials to provide your best work for clients.", - features: ["5 products", "Up to 1,000 subscribers", "Basic analytics"], - mostPopular: false, - }, - { - name: "Startup", - id: "tier-startup", - href: "#", - price: { monthly: "$60", annually: "$576" }, - description: "A plan that scales with your rapidly growing business.", - features: [ - "25 products", - "Up to 10,000 subscribers", - "Advanced analytics", - "24-hour support response time", - "Marketing automations", - ], - mostPopular: true, - }, - ], -}; - -export function PricingPlan() { - const [frequency, setFrequency] = useState( - pricing.frequencies[0] - ); - - return ( -
-
-
-

- Pricing -

-

- Pricing plans for teams of all sizes -

-
-

- Choose an affordable plan that’s packed with the best features for - engaging your audience, creating customer loyalty, and driving sales. -

-
- - - Payment frequency - - {pricing.frequencies.map((option) => ( - { - return cn( - checked ? "bg-indigo-600 text-white" : "text-gray-500", - "cursor-pointer rounded-full px-2.5 py-1" - ); - }} - > - {option.label} - - ))} - -
-
- {pricing.tiers.map((tier) => ( -
-

- {tier.name} -

-

- {tier.description} -

-

- - {tier.price[frequency.value]} - - - {frequency.priceSuffix} - -

- - Buy plan - -
    - {tier.features.map((feature) => ( -
  • -
    - {checkMarkIcon} -
    - {feature} -
  • - ))} -
-
- ))} -
-
-
- ); -} diff --git a/web/src/app/(app)/pricing/const/Icon.tsx b/web/src/app/(app)/pricing/const/Icon.tsx new file mode 100644 index 0000000..2142282 --- /dev/null +++ b/web/src/app/(app)/pricing/const/Icon.tsx @@ -0,0 +1,15 @@ +export const checkMarkIcon = ( + +); diff --git a/web/src/app/(app)/pricing/page.tsx b/web/src/app/(app)/pricing/page.tsx index 3d237bd..fd94f22 100644 --- a/web/src/app/(app)/pricing/page.tsx +++ b/web/src/app/(app)/pricing/page.tsx @@ -1,12 +1,12 @@ "use client"; import { GpuPricingPlan } from "@/app/(app)/pricing/components/gpuPricingTable"; -import { PricingPlan } from "@/app/(app)/pricing/components/pricingPlanTable"; +import PricingList from "@/app/(app)/pricing/components/pricePlanList"; export default function Home() { return (
- +
); diff --git a/web/src/server/linkToPricing.ts b/web/src/server/linkToPricing.ts new file mode 100644 index 0000000..bc4d5c3 --- /dev/null +++ b/web/src/server/linkToPricing.ts @@ -0,0 +1,12 @@ +"use server"; + +import { LemonSqueezy } from "@lemonsqueezy/lemonsqueezy.js"; +import "server-only"; + +const ls = new LemonSqueezy(process.env.LEMONSQUEEZY_API_KEY || ""); + +export async function getPricing() { + const products = await ls.getProducts(); + + return products; +}