update: pricing plan with usage base
This commit is contained in:
parent
8a134ed39e
commit
ac8f6e2808
@ -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>
|
||||||
|
@ -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>
|
||||||
|
);
|
||||||
|
@ -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"
|
||||||
|
@ -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>;
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user