update: pricing plan with usage base

This commit is contained in:
Karrix 2024-01-21 15:35:14 +08:00 committed by BennyKok
parent 8a134ed39e
commit ac8f6e2808
5 changed files with 152 additions and 7 deletions

View File

@ -1,6 +1,11 @@
import { checkMarkIcon } from "../const/Icon"; import { checkMarkIcon, crossMarkIcon } from "../const/Icon";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { getPricing } from "@/server/linkToPricing"; import {
getPricing,
getSubscriptionItem,
getUsage,
setUsage,
} from "@/server/linkToPricing";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
type Tier = { type Tier = {
@ -11,10 +16,18 @@ type Tier = {
description: string; description: string;
features: string[]; features: string[];
featured: boolean; featured: boolean;
priority?: TierPriority;
}; };
enum TierPriority {
Free = "free",
Pro = "pro",
Enterprise = "enterprise",
}
export default function PricingList() { export default function PricingList() {
const [productTiers, setProductTiers] = useState<Tier[]>(); const [productTiers, setProductTiers] = useState<Tier[]>();
const [userUsageId, setUserUsageId] = useState<number>(0);
useEffect(() => { useEffect(() => {
(async () => { (async () => {
@ -40,26 +53,81 @@ export default function PricingList() {
name: item.attributes.name, name: item.attributes.name,
id: item.id, id: item.id,
href: item.attributes.buy_now_url, href: item.attributes.buy_now_url,
priceMonthly: item.attributes.price_formatted.split("/")[0], priceMonthly:
item.attributes.price_formatted.split("/")[0] == "Usage-based"
? "$20.00"
: item.attributes.price_formatted.split("/")[0],
description: description, description: description,
features: features, features: features,
// if name contains pro, it's featured // if name contains pro, it's featured
featured: item.attributes.name.toLowerCase().includes("pro"), featured: item.attributes.name.toLowerCase().includes("pro"),
// give priority if name contain in enum
priority: Object.values(TierPriority).find((priority) =>
item.attributes.name.toLowerCase().includes(priority)
),
}; };
}); });
console.log(newProductTiers); // sort newProductTiers by priority
newProductTiers.sort((a, b) => {
if (!a.priority) return 1;
if (!b.priority) return -1;
return (
Object.values(TierPriority).indexOf(a.priority) -
Object.values(TierPriority).indexOf(b.priority)
);
});
setProductTiers(newProductTiers); setProductTiers(newProductTiers);
})(); })();
}, []); }, []);
useEffect(() => {
(async () => {
// const currentUser = await getUserData();
const userUsage = await getUsage();
const userSubscription = await getSubscriptionItem();
// const setUserUsage = await setUsage(236561, 10);
// console.log(currentUser);
console.log(userSubscription);
})();
}, []);
const setUserUsage = async (id: number, quantity: number) => {
try {
const response = await setUsage(id, quantity);
console.log(response);
} catch (error) {
console.error(error);
}
};
return ( return (
<div className="relative isolate px-6 py-24 lg:px-8"> <div className="relative isolate px-6 py-24 lg:px-8">
<div className="mx-auto max-w-2xl text-center lg:max-w-4xl"> <div className="mx-auto max-w-2xl text-center lg:max-w-4xl">
<h2 className="text-base font-semibold leading-7 text-indigo-600"> <h2 className="text-base font-semibold leading-7 text-indigo-600">
Pricing Pricing
</h2> </h2>
<div className="flex flex-col">
<button
className="mt-2 text-base font-semibold leading-7 text-indigo-600 bg-black"
onClick={() => {
setUserUsage(192385, 10);
}}
>
Set
</button>
<button className="mt-2 text-base font-semibold leading-7 text-indigo-600 bg-slate-400">
Get
</button>
</div>
<p className="mt-2 text-4xl font-bold tracking-tight text-gray-900 sm:text-5xl"> <p className="mt-2 text-4xl font-bold tracking-tight text-gray-900 sm:text-5xl">
The right price for you, whoever you are The right price for you, whoever you are
</p> </p>
@ -68,7 +136,7 @@ export default function PricingList() {
Qui iusto aut est earum eos quae. Eligendi est at nam aliquid ad quo Qui iusto aut est earum eos quae. Eligendi est at nam aliquid ad quo
reprehenderit in aliquid fugiat dolorum voluptatibus. reprehenderit in aliquid fugiat dolorum voluptatibus.
</p> </p>
<div className="mx-auto mt-16 grid max-w-lg grid-cols-1 items-center gap-y-6 sm:mt-20 sm:gap-y-0 lg:max-w-4xl lg:grid-cols-2"> <div className="mx-auto mt-16 grid max-w-lg grid-cols-1 items-center gap-y-6 sm:mt-20 sm:gap-y-0 lg:max-w-4xl lg:grid-cols-2 xl:max-w-6xl xl:grid-cols-3">
{productTiers && {productTiers &&
productTiers.map((tier, tierIdx) => ( productTiers.map((tier, tierIdx) => (
<div <div
@ -107,9 +175,9 @@ export default function PricingList() {
{tier.features.map((feature) => ( {tier.features.map((feature) => (
<li key={feature} className="flex gap-x-3"> <li key={feature} className="flex gap-x-3">
<div className="flex justify-center items-center"> <div className="flex justify-center items-center">
{checkMarkIcon} {feature.includes("[x]") ? crossMarkIcon : checkMarkIcon}
</div> </div>
{feature} {feature.replace("[x]", "")}
</li> </li>
))} ))}
</ul> </ul>

View File

@ -13,3 +13,25 @@ export const checkMarkIcon = (
/> />
</svg> </svg>
); );
export const crossMarkIcon = (
<svg
xmlns="http://www.w3.org/2000/svg"
x="0px"
y="0px"
width="20"
height="20"
viewBox="0 0 48 48"
>
<path
fill="#F44336"
d="M21.5 4.5H26.501V43.5H21.5z"
transform="rotate(45.001 24 24)"
/>
<path
fill="#F44336"
d="M21.5 4.5H26.5V43.501H21.5z"
transform="rotate(135.008 24 24)"
/>
</svg>
);

View File

@ -85,6 +85,13 @@ export function Navbar() {
</div> </div>
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
{isDesktop && <NavbarMenu />} {isDesktop && <NavbarMenu />}
<Button
asChild
variant="link"
className="rounded-full aspect-square p-2 mr-4"
>
<a href="/pricing">Pricing</a>
</Button>
<Button <Button
asChild asChild
variant="link" variant="link"

View File

@ -8,6 +8,7 @@ import {
text, text,
timestamp, timestamp,
uuid, uuid,
real,
} from "drizzle-orm/pg-core"; } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod"; import { createInsertSchema } from "drizzle-zod";
import { z } from "zod"; import { z } from "zod";
@ -331,8 +332,22 @@ export const apiKeyTable = dbSchema.table("api_keys", {
updated_at: timestamp("updated_at").defaultNow().notNull(), updated_at: timestamp("updated_at").defaultNow().notNull(),
}); });
export const userUsageTable = dbSchema.table("user_usage", {
id: uuid("id").primaryKey().defaultRandom().notNull(),
user_id: text("user_id")
.references(() => usersTable.id, {
onDelete: "cascade",
})
.notNull(),
usage_time: real("usage_time").default(0).notNull(),
created_at: timestamp("created_at").defaultNow().notNull(),
updated_at: timestamp("updated_at").defaultNow().notNull(),
});
export type UserType = InferSelectModel<typeof usersTable>; export type UserType = InferSelectModel<typeof usersTable>;
export type WorkflowType = InferSelectModel<typeof workflowTable>; export type WorkflowType = InferSelectModel<typeof workflowTable>;
export type MachineType = InferSelectModel<typeof machinesTable>; export type MachineType = InferSelectModel<typeof machinesTable>;
export type WorkflowVersionType = InferSelectModel<typeof workflowVersionTable>; export type WorkflowVersionType = InferSelectModel<typeof workflowVersionTable>;
export type DeploymentType = InferSelectModel<typeof deploymentsTable>; export type DeploymentType = InferSelectModel<typeof deploymentsTable>;
export type UserUsageType = InferSelectModel<typeof userUsageTable>;

View File

@ -10,3 +10,36 @@ export async function getPricing() {
return products; return products;
} }
export async function getUsage() {
const usageRecord = await ls.getUsageRecords();
return usageRecord;
}
export async function setUsage(id: number, quantity: number) {
const setUsage = await ls.createUsageRecord({
subscriptionItemId: id,
quantity: quantity,
});
return setUsage;
}
export async function getSubscription() {
const subscription = await ls.getSubscriptions();
return subscription;
}
export async function getSubscriptionItem() {
const subscriptionItem = await ls.getSubscriptionItems();
return subscriptionItem;
}
export async function getUserData() {
const user = await ls.getUser();
return user;
}