mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-10 07:48:37 +01:00
fix pricing + stripe payments
This commit is contained in:
@@ -13,11 +13,11 @@ export type PricingCardProp = {
|
|||||||
planId: number
|
planId: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{ datas: PricingCardProp[] }>();
|
const props = defineProps<{ datas: PricingCardProp[], defaultIndex?: number }>();
|
||||||
|
|
||||||
const activeProject = useActiveProject();
|
const activeProject = useActiveProject();
|
||||||
|
|
||||||
const currentIndex = ref<number>(0);
|
const currentIndex = ref<number>(props.defaultIndex || 0);
|
||||||
|
|
||||||
const data = computed(() => {
|
const data = computed(() => {
|
||||||
return props.datas[currentIndex.value];
|
return props.datas[currentIndex.value];
|
||||||
@@ -37,13 +37,19 @@ async function onUpgradeClick() {
|
|||||||
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="relative bg-[#151515] outline outline-[1px] outline-[#262626] py-8 px-10 rounded-lg w-full max-w-[30rem]">
|
<div
|
||||||
|
class="relative bg-[#151515] outline outline-[1px] outline-[#262626] py-8 px-10 rounded-lg w-full max-w-[30rem]">
|
||||||
|
|
||||||
<div class="flex flex-col gap-3 text-center">
|
<div class="flex flex-col gap-3 text-center pt-3">
|
||||||
<div class="poppins text-xl font-light"> {{ data.title }} </div>
|
<div v-if="data.active"
|
||||||
<div v-if="data.active" class="absolute right-6 top-3 poppins text-[.75rem] bg-[#222A42] outline outline-[1px] outline-[#5680F8] px-3 py-[.1rem] rounded-xl">
|
class="absolute right-6 top-3 poppins text-[.75rem] bg-[#222A42] outline outline-[1px] outline-[#5680F8] px-3 py-[.1rem] rounded-sm">
|
||||||
Active
|
Active
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="!data.active && data.title === 'Growth'"
|
||||||
|
class="absolute right-6 top-3 poppins text-[.75rem] bg-[#fbbe244f] outline outline-[1px] outline-[#fbbf24] px-3 py-[.1rem] rounded-sm">
|
||||||
|
Most popular
|
||||||
|
</div>
|
||||||
|
<div class="poppins text-xl font-light"> {{ data.title }} </div>
|
||||||
<div class="poppins text-4xl font-medium"> {{ data.price }} </div>
|
<div class="poppins text-4xl font-medium"> {{ data.price }} </div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ const customPricing: PricingCardProp[] = [
|
|||||||
'DB instance: DEDICATED',
|
'DB instance: DEDICATED',
|
||||||
'Dedicated operator',
|
'Dedicated operator',
|
||||||
'White label',
|
'White label',
|
||||||
'Custom Charts',
|
|
||||||
'Custom Data Aggregation'
|
'Custom Data Aggregation'
|
||||||
],
|
],
|
||||||
cta: 'Let\'s Talk!',
|
cta: 'Let\'s Talk!',
|
||||||
@@ -179,10 +178,22 @@ const emits = defineEmits<{
|
|||||||
(evt: 'onCloseClick'): void
|
(evt: 'onCloseClick'): void
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const activeProject = useActiveProject()
|
||||||
|
|
||||||
|
async function onLifetimeUpgradeClick() {
|
||||||
|
const res = await $fetch<string>(`/api/pay/${activeProject.value?._id.toString()}/create-onetime`, {
|
||||||
|
...signHeaders({ 'content-type': 'application/json' }),
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ planId: 2001 })
|
||||||
|
})
|
||||||
|
if (!res) alert('Something went wrong');
|
||||||
|
window.open(res);
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="p-8 overflow-y-auto xl:overflow-y-hidden">
|
<div class="p-8 overflow-y-auto">
|
||||||
|
|
||||||
<div @click="$emit('onCloseClick')"
|
<div @click="$emit('onCloseClick')"
|
||||||
class="cursor-pointer fixed top-4 right-4 rounded-full bg-menu drop-shadow-[0_0_2px_#CCCCCCCC] w-9 h-9 flex items-center justify-center">
|
class="cursor-pointer fixed top-4 right-4 rounded-full bg-menu drop-shadow-[0_0_2px_#CCCCCCCC] w-9 h-9 flex items-center justify-center">
|
||||||
@@ -191,10 +202,56 @@ const emits = defineEmits<{
|
|||||||
|
|
||||||
<div class="flex gap-8 mt-10 h-max xl:flex-row flex-col">
|
<div class="flex gap-8 mt-10 h-max xl:flex-row flex-col">
|
||||||
<PricingCardGeneric class="flex-1" :datas="freePricing"></PricingCardGeneric>
|
<PricingCardGeneric class="flex-1" :datas="freePricing"></PricingCardGeneric>
|
||||||
<PricingCardGeneric class="flex-1" :datas="slidePricings"></PricingCardGeneric>
|
<PricingCardGeneric class="flex-1" :datas="slidePricings" :default-index="2"></PricingCardGeneric>
|
||||||
<PricingCardGeneric class="flex-1" :datas="customPricing"></PricingCardGeneric>
|
<PricingCardGeneric class="flex-1" :datas="customPricing"></PricingCardGeneric>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<LyxUiCard class="w-full mt-6">
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<div>
|
||||||
|
<span class="text-lyx-primary font-semibold text-[1.4rem]">
|
||||||
|
LIFETIME DEAL
|
||||||
|
</span>
|
||||||
|
<span class="text-lyx-text-dark text-[.8rem]"> (Growh plan) </span>
|
||||||
|
</div>
|
||||||
|
<div class="text-[2rem]"> € 2.399,00 </div>
|
||||||
|
<div> Up to 500.000 visits/events per month </div>
|
||||||
|
<LyxUiButton type="primary" @click="onLifetimeUpgradeClick()"> Purchase </LyxUiButton>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-evenly grow">
|
||||||
|
<div class="flex flex-col justify-evenly">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<img class="h-6" :src="'/check.png'" alt="Check">
|
||||||
|
<div> Slack support </div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<img class="h-6" :src="'/check.png'" alt="Check">
|
||||||
|
<div> Unlimited domanis </div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<img class="h-6" :src="'/check.png'" alt="Check">
|
||||||
|
<div> Unlimited reports </div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col justify-evenly">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<img class="h-6" :src="'/check.png'" alt="Check">
|
||||||
|
<div> AI Tokens: 3.000 / month </div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<img class="h-6" :src="'/check.png'" alt="Check">
|
||||||
|
<div> Server type: SHARED </div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<img class="h-6" :src="'/check.png'" alt="Check">
|
||||||
|
<div> Data retention: 1 Year </div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</LyxUiCard>
|
||||||
|
|
||||||
<div class="flex justify-between items-center mt-10 flex-col xl:flex-row">
|
<div class="flex justify-between items-center mt-10 flex-col xl:flex-row">
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<div class="poppins text-[2rem] font-semibold">
|
<div class="poppins text-[2rem] font-semibold">
|
||||||
@@ -212,5 +269,8 @@ const emits = defineEmits<{
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
40
dashboard/server/api/pay/[project_id]/create-onetime.post.ts
Normal file
40
dashboard/server/api/pay/[project_id]/create-onetime.post.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { getPlanFromId } from "@data/PREMIUM";
|
||||||
|
import { getUserProjectFromId } from "~/server/LIVE_DEMO_DATA";
|
||||||
|
import StripeService from '~/server/services/StripeService';
|
||||||
|
|
||||||
|
|
||||||
|
export default defineEventHandler(async event => {
|
||||||
|
|
||||||
|
const project_id = getRequestProjectId(event);
|
||||||
|
if (!project_id) return;
|
||||||
|
|
||||||
|
const user = getRequestUser(event);
|
||||||
|
const project = await getUserProjectFromId(project_id, user);
|
||||||
|
if (!project) return;
|
||||||
|
|
||||||
|
const body = await readBody(event);
|
||||||
|
|
||||||
|
const { planId } = body;
|
||||||
|
|
||||||
|
const PLAN = getPlanFromId(planId);
|
||||||
|
|
||||||
|
if (!PLAN) {
|
||||||
|
console.error('PLAN', planId, 'NOT EXIST');
|
||||||
|
return setResponseStatus(event, 400, 'Plan not exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
const intent = await StripeService.createOnetimePayment(
|
||||||
|
StripeService.testMode ? PLAN.PRICE_TEST : PLAN.PRICE,
|
||||||
|
'https://dashboard.litlyx.com/payment_ok',
|
||||||
|
project_id,
|
||||||
|
project.customer_id
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!intent) {
|
||||||
|
console.error('Cannot create Intent', { plan: PLAN });
|
||||||
|
return setResponseStatus(event, 400, 'Cannot create intent');
|
||||||
|
}
|
||||||
|
|
||||||
|
return intent.url;
|
||||||
|
|
||||||
|
});
|
||||||
@@ -63,6 +63,36 @@ async function onPaymentFailed(event: Event.InvoicePaymentFailedEvent) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function onPaymentOnetimeSuccess(event: Event.PaymentIntentSucceededEvent) {
|
||||||
|
const customer_id = event.data.object.customer as string;
|
||||||
|
const project = await ProjectModel.findOne({ customer_id });
|
||||||
|
if (!project) return { error: 'CUSTOMER NOT EXIST' }
|
||||||
|
|
||||||
|
if (event.data.object.status === 'succeeded') {
|
||||||
|
|
||||||
|
const PLAN = getPlanFromPrice(event.data.object.metadata.price, StripeService.testMode || false);
|
||||||
|
if (!PLAN) return { error: 'Plan not found' }
|
||||||
|
const dummyPlan = PLAN.ID + 3000;
|
||||||
|
|
||||||
|
const subscription = await StripeService.createOneTimeSubscriptionDummy(customer_id, dummyPlan);
|
||||||
|
if (!subscription) return { error: 'Error creating subscription' }
|
||||||
|
|
||||||
|
const allSubscriptions = await StripeService.getAllSubscriptions(customer_id);
|
||||||
|
if (!allSubscriptions) return;
|
||||||
|
for (const subscription of allSubscriptions.data) {
|
||||||
|
if (subscription.id === subscription.id) continue;
|
||||||
|
await StripeService.deleteSubscription(subscription.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
await addSubscriptionToProject(project._id.toString(), PLAN, subscription.id, subscription.current_period_start, subscription.current_period_end)
|
||||||
|
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { received: true, warn: 'object status not succeeded' }
|
||||||
|
}
|
||||||
|
|
||||||
async function onPaymentSuccess(event: Event.InvoicePaidEvent) {
|
async function onPaymentSuccess(event: Event.InvoicePaidEvent) {
|
||||||
|
|
||||||
const customer_id = event.data.object.customer as string;
|
const customer_id = event.data.object.customer as string;
|
||||||
@@ -201,7 +231,11 @@ export default defineEventHandler(async event => {
|
|||||||
|
|
||||||
const eventData = StripeService.parseWebhook(body, signature);
|
const eventData = StripeService.parseWebhook(body, signature);
|
||||||
if (!eventData) return;
|
if (!eventData) return;
|
||||||
|
|
||||||
|
// console.log('WEBHOOK FIRED', eventData.type);
|
||||||
|
|
||||||
if (eventData.type === 'invoice.paid') return await onPaymentSuccess(eventData);
|
if (eventData.type === 'invoice.paid') return await onPaymentSuccess(eventData);
|
||||||
|
if (eventData.type === 'payment_intent.succeeded') return await onPaymentOnetimeSuccess(eventData);
|
||||||
if (eventData.type === 'invoice.payment_failed') return await onPaymentFailed(eventData);
|
if (eventData.type === 'invoice.payment_failed') return await onPaymentFailed(eventData);
|
||||||
if (eventData.type === 'customer.subscription.deleted') return await onSubscriptionDeleted(eventData);
|
if (eventData.type === 'customer.subscription.deleted') return await onSubscriptionDeleted(eventData);
|
||||||
if (eventData.type === 'customer.subscription.created') return await onSubscriptionCreated(eventData);
|
if (eventData.type === 'customer.subscription.created') return await onSubscriptionCreated(eventData);
|
||||||
|
|||||||
@@ -16,6 +16,25 @@ export default defineEventHandler(async event => {
|
|||||||
const project = await ProjectModel.findById(project_id);
|
const project = await ProjectModel.findById(project_id);
|
||||||
if (!project) return setResponseStatus(event, 400, 'Project not found');
|
if (!project) return setResponseStatus(event, 400, 'Project not found');
|
||||||
|
|
||||||
|
|
||||||
|
if (project.subscription_id === 'onetime') {
|
||||||
|
|
||||||
|
const projectLimits = await ProjectLimitModel.findOne({ project_id });
|
||||||
|
if (!projectLimits) return setResponseStatus(event, 400, 'Project limits not found');
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
premium: project.premium,
|
||||||
|
premium_type: project.premium_type,
|
||||||
|
billing_start_at: projectLimits.billing_start_at,
|
||||||
|
billing_expire_at: projectLimits.billing_expire_at,
|
||||||
|
limit: projectLimits.limit,
|
||||||
|
count: projectLimits.events + projectLimits.visits,
|
||||||
|
subscription_status: StripeService.isDisabled() ? 'Disabled mode' : ('One time payment')
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
const subscription = await StripeService.getSubscription(project.subscription_id);
|
const subscription = await StripeService.getSubscription(project.subscription_id);
|
||||||
|
|
||||||
const projectLimits = await ProjectLimitModel.findOne({ project_id });
|
const projectLimits = await ProjectLimitModel.findOne({ project_id });
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getPlanFromTag } from '@data/PREMIUM';
|
import { getPlanFromId, getPlanFromTag } from '@data/PREMIUM';
|
||||||
import Stripe from 'stripe';
|
import Stripe from 'stripe';
|
||||||
|
|
||||||
class StripeService {
|
class StripeService {
|
||||||
@@ -29,6 +29,33 @@ class StripeService {
|
|||||||
return this.stripe.webhooks.constructEvent(body, sig, this.webhookSecret);
|
return this.stripe.webhooks.constructEvent(body, sig, this.webhookSecret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async createOnetimePayment(price: string, success_url: string, pid: string, customer?: string) {
|
||||||
|
if (this.disabledMode) return;
|
||||||
|
if (!this.stripe) throw Error('Stripe not initialized');
|
||||||
|
|
||||||
|
const checkout = await this.stripe.checkout.sessions.create({
|
||||||
|
allow_promotion_codes: true,
|
||||||
|
payment_method_types: ['card'],
|
||||||
|
invoice_creation: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
line_items: [
|
||||||
|
{ price, quantity: 1 }
|
||||||
|
],
|
||||||
|
payment_intent_data: {
|
||||||
|
metadata: {
|
||||||
|
pid, price
|
||||||
|
}
|
||||||
|
},
|
||||||
|
customer,
|
||||||
|
success_url,
|
||||||
|
mode: 'payment'
|
||||||
|
});
|
||||||
|
|
||||||
|
return checkout;
|
||||||
|
}
|
||||||
|
|
||||||
async cretePayment(price: string, success_url: string, pid: string, customer?: string) {
|
async cretePayment(price: string, success_url: string, pid: string, customer?: string) {
|
||||||
if (this.disabledMode) return;
|
if (this.disabledMode) return;
|
||||||
if (!this.stripe) throw Error('Stripe not initialized');
|
if (!this.stripe) throw Error('Stripe not initialized');
|
||||||
@@ -50,6 +77,13 @@ class StripeService {
|
|||||||
return checkout;
|
return checkout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getPriceData(priceId: string) {
|
||||||
|
if (this.disabledMode) return;
|
||||||
|
if (!this.stripe) throw Error('Stripe not initialized');
|
||||||
|
const priceData = await this.stripe.prices.retrieve(priceId);
|
||||||
|
return priceData;
|
||||||
|
}
|
||||||
|
|
||||||
async deleteSubscription(subscriptionId: string) {
|
async deleteSubscription(subscriptionId: string) {
|
||||||
if (this.disabledMode) return;
|
if (this.disabledMode) return;
|
||||||
if (!this.stripe) throw Error('Stripe not initialized');
|
if (!this.stripe) throw Error('Stripe not initialized');
|
||||||
@@ -78,7 +112,6 @@ class StripeService {
|
|||||||
return invoices;
|
return invoices;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async getCustomer(customer_id: string) {
|
async getCustomer(customer_id: string) {
|
||||||
if (this.disabledMode) return;
|
if (this.disabledMode) return;
|
||||||
if (!this.stripe) throw Error('Stripe not initialized');
|
if (!this.stripe) throw Error('Stripe not initialized');
|
||||||
@@ -100,8 +133,27 @@ class StripeService {
|
|||||||
return deleted;
|
return deleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createOneTimeCoupon() {
|
||||||
|
if (this.disabledMode) return;
|
||||||
|
if (!this.stripe) throw Error('Stripe not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
async createOneTimeSubscriptionDummy(customer_id: string, planId: number) {
|
||||||
|
if (this.disabledMode) return;
|
||||||
|
if (!this.stripe) throw Error('Stripe not initialized');
|
||||||
|
|
||||||
|
const PLAN = getPlanFromId(planId);
|
||||||
|
if (!PLAN) throw Error('Plan not found');
|
||||||
|
|
||||||
|
const subscription = await this.stripe.subscriptions.create({
|
||||||
|
customer: customer_id,
|
||||||
|
items: [
|
||||||
|
{ price: this.testMode ? PLAN.PRICE_TEST : PLAN.PRICE, quantity: 1 }
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
return subscription;
|
||||||
|
}
|
||||||
|
|
||||||
async createFreeSubscription(customer_id: string) {
|
async createFreeSubscription(customer_id: string) {
|
||||||
if (this.disabledMode) return;
|
if (this.disabledMode) return;
|
||||||
|
|||||||
@@ -84,11 +84,9 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="px-10 pt-6 lg:pt-0">
|
<div class="px-10 pt-6 lg:pt-0">
|
||||||
|
|
||||||
|
<MainButton link="https://dashboard.litlyx.com" class="!whitespace-nowrap">
|
||||||
<button class="poppins w-fit cursor-pointer px-4 py-1 rounded-md outline outline-[1px] text-text bg-lyx-primary-dark outline-lyx-primary hover:bg-lyx-primary-hover button !whitespace-nowrap" onclick="window.location='https://dashboard.litlyx.com';">
|
|
||||||
Get started
|
Get started
|
||||||
</button>
|
</MainButton>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
35
landing/nuxt.config.ts
Normal file
35
landing/nuxt.config.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||||
|
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
colorMode: { preference: 'dark', },
|
||||||
|
devtools: { enabled: false },
|
||||||
|
app: {
|
||||||
|
head: {
|
||||||
|
script: [
|
||||||
|
{
|
||||||
|
src: 'https://cdn.jsdelivr.net/gh/litlyx/litlyx-js/browser/litlyx.js',
|
||||||
|
'data-project': '6643cd08a1854e3b81722ab5',
|
||||||
|
defer: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pages: true,
|
||||||
|
ssr: true,
|
||||||
|
routeRules: {
|
||||||
|
'/': {
|
||||||
|
prerender: true
|
||||||
|
},
|
||||||
|
'/**': {
|
||||||
|
prerender: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
css: ['~/assets/scss/main.scss'],
|
||||||
|
modules: ['@nuxt/ui'],
|
||||||
|
devServer: {
|
||||||
|
host: '0.0.0.0',
|
||||||
|
},
|
||||||
|
components: true,
|
||||||
|
extends: ['../lyx-ui']
|
||||||
|
})
|
||||||
|
|
||||||
@@ -40,7 +40,6 @@ const customPricing: PricingCardProp[] = [
|
|||||||
'DB instance: DEDICATED',
|
'DB instance: DEDICATED',
|
||||||
'Dedicated operator',
|
'Dedicated operator',
|
||||||
'White label',
|
'White label',
|
||||||
'Custom Charts',
|
|
||||||
'Custom Data Aggregation'
|
'Custom Data Aggregation'
|
||||||
],
|
],
|
||||||
cta: 'Let\'s Talk!',
|
cta: 'Let\'s Talk!',
|
||||||
@@ -182,6 +181,54 @@ const slidePricings: PricingCardProp[] = [
|
|||||||
</PricingCardGeneric>
|
</PricingCardGeneric>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<LyxUiCard class="w-full mt-6 max-w-[96rem]">
|
||||||
|
<div class="flex flex-col lg:flex-row">
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<div>
|
||||||
|
<span class="text-lyx-primary font-semibold text-[1.4rem]">
|
||||||
|
LIFETIME DEAL
|
||||||
|
</span>
|
||||||
|
<span class="text-lyx-text-dark text-[.8rem]"> (Growh plan) </span>
|
||||||
|
</div>
|
||||||
|
<div class="text-[2rem]"> € 2.399,00 </div>
|
||||||
|
<div> Up to 500.000 visits/events per month </div>
|
||||||
|
<LyxUiButton type="primary" link="https://dashboard.litlyx.com"> Start for free now </LyxUiButton>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-evenly grow flex-col gap-2 lg:gap-0 lg:flex-row mt-4 lg:mt-0">
|
||||||
|
<div class="flex flex-col justify-evenly gap-2 lg:gap-0">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<img class="h-6" :src="'/check.png'" alt="Check">
|
||||||
|
<div> Slack support </div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<img class="h-6" :src="'/check.png'" alt="Check">
|
||||||
|
<div> Unlimited domanis </div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<img class="h-6" :src="'/check.png'" alt="Check">
|
||||||
|
<div> Unlimited reports </div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col justify-evenly gap-2 lg:gap-0">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<img class="h-6" :src="'/check.png'" alt="Check">
|
||||||
|
<div> AI Tokens: 3.000 / month </div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<img class="h-6" :src="'/check.png'" alt="Check">
|
||||||
|
<div> Server type: SHARED </div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<img class="h-6" :src="'/check.png'" alt="Check">
|
||||||
|
<div> Data retention: 1 Year </div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</LyxUiCard>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- <div class="flex gap-8 h-max flex-col lg:flex-row">
|
<!-- <div class="flex gap-8 h-max flex-col lg:flex-row">
|
||||||
<PricingCard class="flex-1" :data="starterTierCardData"></PricingCard>
|
<PricingCard class="flex-1" :data="starterTierCardData"></PricingCard>
|
||||||
<PricingCard class="flex-1" :data="accelerationTierCardData"></PricingCard>
|
<PricingCard class="flex-1" :data="accelerationTierCardData"></PricingCard>
|
||||||
@@ -233,7 +280,7 @@ const slidePricings: PricingCardProp[] = [
|
|||||||
<UAccordion :ui="{
|
<UAccordion :ui="{
|
||||||
wrapper: 'w-full',
|
wrapper: 'w-full',
|
||||||
item: {
|
item: {
|
||||||
padding: 'pl-8'
|
padding: 'pl-8',
|
||||||
}
|
}
|
||||||
}" color="white" variant="ghost" size="xl" :items="[
|
}" color="white" variant="ghost" size="xl" :items="[
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ export const PREMIUM_TAGS = [
|
|||||||
'GROWTH',
|
'GROWTH',
|
||||||
'EXPANSION',
|
'EXPANSION',
|
||||||
'SCALING',
|
'SCALING',
|
||||||
'UNICORN'
|
'UNICORN',
|
||||||
|
'LIFETIME_GROWTH_ONETIME',
|
||||||
|
'GROWTH_DUMMY'
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
|
||||||
@@ -95,6 +97,20 @@ export const PREMIUM_PLAN: Record<PREMIUM_TAG, PREMIUM_DATA> = {
|
|||||||
PRICE: 'price_1Pdt2LB2lPUiVs9VGBFAIG9G',
|
PRICE: 'price_1Pdt2LB2lPUiVs9VGBFAIG9G',
|
||||||
PRICE_TEST: ''
|
PRICE_TEST: ''
|
||||||
},
|
},
|
||||||
|
LIFETIME_GROWTH_ONETIME: {
|
||||||
|
ID: 2001,
|
||||||
|
COUNT_LIMIT: 500_000,
|
||||||
|
AI_MESSAGE_LIMIT: 3_000,
|
||||||
|
PRICE: 'price_1PvewGB2lPUiVs9VLheJC8s1',
|
||||||
|
PRICE_TEST: 'price_1Pvf7LB2lPUiVs9VMFNyzpim'
|
||||||
|
},
|
||||||
|
GROWTH_DUMMY: {
|
||||||
|
ID: 5001,
|
||||||
|
COUNT_LIMIT: 500_000,
|
||||||
|
AI_MESSAGE_LIMIT: 3_000,
|
||||||
|
PRICE: 'price_1PvgoRB2lPUiVs9VC51YBT7J',
|
||||||
|
PRICE_TEST: 'price_1PvgRTB2lPUiVs9V3kFSNC3G'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomPremiumPriceModel.find({}).then(custom_prices => {
|
CustomPremiumPriceModel.find({}).then(custom_prices => {
|
||||||
|
|||||||
Reference in New Issue
Block a user