mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-10 07:48:37 +01:00
add proper limit + csv lock
This commit is contained in:
@@ -9,12 +9,28 @@ const debugMode = process.dev;
|
||||
const { alerts, closeAlert } = useAlert();
|
||||
|
||||
const { showDialog, closeDialog, dialogComponent, dialogParams, dialogStyle, dialogClosable } = useCustomDialog();
|
||||
|
||||
const { visible } = usePricingDrawer();
|
||||
|
||||
const { data: planData } = useFetch('/api/project/plan', {
|
||||
...signHeaders(),
|
||||
lazy: true
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<div class="w-dvw h-dvh bg-lyx-background-light relative">
|
||||
|
||||
<Transition name="pdrawer">
|
||||
<LazyPricingDrawer @onCloseClick="visible = false" :currentSub="planData?.premium_type || 0"
|
||||
class="bg-black fixed right-0 top-0 w-full xl:w-[60vw] xl:min-w-[65rem] h-full z-[20]" v-if=visible>
|
||||
</LazyPricingDrawer>
|
||||
</Transition>
|
||||
|
||||
|
||||
<div class="fixed top-4 right-8 z-[999] flex flex-col gap-2" v-if="alerts.length > 0">
|
||||
<div v-for="alert of alerts"
|
||||
class="w-[30vw] min-w-[20rem] relative bg-[#151515] overflow-hidden border-solid border-[2px] border-[#262626] rounded-lg p-6 drop-shadow-lg">
|
||||
@@ -64,3 +80,19 @@ const { showDialog, closeDialog, dialogComponent, dialogParams, dialogStyle, dia
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.pdrawer-enter-active,
|
||||
.pdrawer-leave-active {
|
||||
transition: all .5s ease-in-out;
|
||||
}
|
||||
|
||||
.pdrawer-enter-from,
|
||||
.pdrawer-leave-to {
|
||||
transform: translateX(100%)
|
||||
}
|
||||
|
||||
.pdrawer-enter-to,
|
||||
.pdrawer-leave-from {
|
||||
transform: translateX(0)
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -138,7 +138,7 @@ watch(selected, () => {
|
||||
<template #option="{ option, active, selected }">
|
||||
<div class="flex items-center gap-2">
|
||||
<div>
|
||||
<img class="h-5 bg-black rounded-full" :src="'logo_32.png'" alt="Litlyx logo">
|
||||
<img class="h-5 bg-black rounded-full" :src="'/logo_32.png'" alt="Litlyx logo">
|
||||
</div>
|
||||
<div> {{ option.name }} </div>
|
||||
</div>
|
||||
@@ -147,7 +147,7 @@ watch(selected, () => {
|
||||
<template #label>
|
||||
<div class="flex items-center gap-2">
|
||||
<div>
|
||||
<img class="h-5 bg-black rounded-full" :src="'logo_32.png'" alt="Litlyx logo">
|
||||
<img class="h-5 bg-black rounded-full" :src="'/logo_32.png'" alt="Litlyx logo">
|
||||
</div>
|
||||
<div> {{ activeProject?.name || '???' }} </div>
|
||||
</div>
|
||||
|
||||
@@ -69,7 +69,7 @@ async function onUpgradeClick() {
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex gap-2" v-for="feature of data.features">
|
||||
<div class="h-6 w-6">
|
||||
<img class="w-full h-full" :src="'check.png'" alt="Check">
|
||||
<img class="w-full h-full" :src="'/check.png'" alt="Check">
|
||||
</div>
|
||||
<div>{{ feature }}</div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import type { PricingCardProp } from './PricingCardGeneric.vue';
|
||||
|
||||
|
||||
const activeProject = useActiveProject();
|
||||
|
||||
const props = defineProps<{ currentSub: number }>();
|
||||
|
||||
const freePricing: PricingCardProp[] = [
|
||||
@@ -20,7 +17,7 @@ const freePricing: PricingCardProp[] = [
|
||||
'Unlimited reports',
|
||||
'AI Tokens: 10',
|
||||
'Server type: SHARED',
|
||||
'Projects: max 2',
|
||||
'Projects: max 1',
|
||||
'Data retention: 2 Months'
|
||||
],
|
||||
cta: 'Start For Free now!',
|
||||
|
||||
@@ -46,11 +46,6 @@ const { data: invoices, refresh: invoicesRefresh, pending: invoicesPending } = u
|
||||
lazy: true
|
||||
})
|
||||
|
||||
const showPricingDrawer = ref<boolean>(false);
|
||||
function onPlanUpgradeClick() {
|
||||
showPricingDrawer.value = true;
|
||||
}
|
||||
|
||||
function openInvoice(link: string) {
|
||||
window.open(link, '_blank');
|
||||
}
|
||||
@@ -77,18 +72,13 @@ const entries: SettingsTemplateEntry[] = [
|
||||
]
|
||||
|
||||
|
||||
const { visible } = usePricingDrawer();
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative">
|
||||
|
||||
<Transition name="pdrawer">
|
||||
<PricingDrawer @onCloseClick="showPricingDrawer = false" :currentSub="planData?.premium_type || 0"
|
||||
class="bg-black fixed right-0 top-0 w-full xl:w-[60vw] xl:min-w-[65rem] h-full z-[20]"
|
||||
v-if=showPricingDrawer>
|
||||
</PricingDrawer>
|
||||
</Transition>
|
||||
|
||||
<div v-if="invoicesPending || planPending"
|
||||
class="backdrop-blur-[1px] z-[20] mt-20 w-full h-full flex items-center justify-center font-bold">
|
||||
<i class="fas fa-spinner text-[2rem] text-accent animate-[spin_1s_linear_infinite] duration-500"></i>
|
||||
@@ -138,7 +128,7 @@ const entries: SettingsTemplateEntry[] = [
|
||||
<div class="poppins"> Expire date:</div>
|
||||
<div> {{ prettyExpireDate }}</div>
|
||||
</div>
|
||||
<div v-if="!isGuest" @click="onPlanUpgradeClick()"
|
||||
<div v-if="!isGuest" @click="visible = true"
|
||||
class="cursor-pointer flex items-center gap-2 text-[.9rem] text-white font-semibold bg-accent px-4 py-1 rounded-lg drop-shadow-[0_0_8px_#000000]">
|
||||
<div class="poppins"> Upgrade plan </div>
|
||||
<i class="fas fa-arrow-up-right"></i>
|
||||
|
||||
9
dashboard/composables/usePricingDrawer.ts
Normal file
9
dashboard/composables/usePricingDrawer.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
|
||||
|
||||
const pricingDrawerVisible = ref<boolean>(false);
|
||||
|
||||
|
||||
export function usePricingDrawer() {
|
||||
return { visible: pricingDrawerVisible };
|
||||
}
|
||||
@@ -6,6 +6,8 @@ definePageMeta({ layout: 'dashboard' });
|
||||
|
||||
const activeProject = useActiveProject();
|
||||
|
||||
const isPremium = computed(() => (activeProject.value?.premium_type || 0) > 0);
|
||||
|
||||
const metricsInfo = ref<number>(0);
|
||||
|
||||
const columns = [
|
||||
@@ -36,7 +38,36 @@ onMounted(async () => {
|
||||
metricsInfo.value = counts.eventsCount;
|
||||
});
|
||||
|
||||
const creatingCsv = ref<boolean>(false);
|
||||
|
||||
async function downloadCSV() {
|
||||
creatingCsv.value = true;
|
||||
const result = await $fetch(`/api/project/generate_csv?mode=events&slice=${options.indexOf(selectedTimeFrom.value)}`, signHeaders());
|
||||
const blob = new Blob([result], { type: 'text/csv' });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'ReportVisits.csv';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
creatingCsv.value = false;
|
||||
}
|
||||
|
||||
const options = ['Last day', 'Last week', 'Last month', 'Total']
|
||||
const selectedTimeFrom = ref<string>(options[0]);
|
||||
|
||||
const showWarning = computed(() => {
|
||||
return options.indexOf(selectedTimeFrom.value) > 1
|
||||
})
|
||||
|
||||
|
||||
const pricingDrawer = usePricingDrawer();
|
||||
|
||||
function goToUpgrade() {
|
||||
pricingDrawer.visible.value = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -47,14 +78,38 @@ onMounted(async () => {
|
||||
|
||||
<div class="w-full h-dvh flex flex-col">
|
||||
|
||||
<div v-if="creatingCsv"
|
||||
class="fixed z-[100] flex items-center justify-center left-0 top-0 w-full h-full bg-black/60 backdrop-blur-[4px]">
|
||||
<div class="poppins text-[2rem]">
|
||||
Creating csv...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end px-12 py-3">
|
||||
<div
|
||||
|
||||
<div class="flex justify-end px-12 py-3 items-center gap-2">
|
||||
|
||||
<div v-if="showWarning" class="text-orange-400 flex gap-2 items-center">
|
||||
<i class="far fa-warning "></i>
|
||||
<div> It can take a few minutes </div>
|
||||
</div>
|
||||
<div class="w-[15rem] flex flex-col gap-0">
|
||||
<USelectMenu v-model="selectedTimeFrom" :options="options"></USelectMenu>
|
||||
</div>
|
||||
|
||||
<div v-if="isPremium" @click="downloadCSV()"
|
||||
class="bg-[#57c78fc0] hover:bg-[#57c78fab] cursor-pointer text-text poppins font-semibold px-8 py-2 rounded-lg">
|
||||
Download CSV
|
||||
</div>
|
||||
|
||||
<div v-if="!isPremium" @click="goToUpgrade()"
|
||||
class="bg-[#57c78f46] hover:bg-[#57c78f42] flex gap-4 items-center cursor-pointer text-text poppins font-semibold px-8 py-2 rounded-lg">
|
||||
<i class="far fa-lock"></i>
|
||||
Upgrade plan for CSV
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<UTable v-if="tableData" class="utable px-8" :ui="{
|
||||
wrapper: 'overflow-auto w-full h-full',
|
||||
thead: 'sticky top-0 bg-menu',
|
||||
|
||||
@@ -6,6 +6,8 @@ definePageMeta({ layout: 'dashboard' });
|
||||
|
||||
const activeProject = useActiveProject();
|
||||
|
||||
const isPremium = computed(() => (activeProject.value?.premium_type || 0) > 0);
|
||||
|
||||
const metricsInfo = ref<number>(0);
|
||||
|
||||
const columns = [
|
||||
@@ -68,6 +70,12 @@ const showWarning = computed(() => {
|
||||
return options.indexOf(selectedTimeFrom.value) > 1
|
||||
})
|
||||
|
||||
const pricingDrawer = usePricingDrawer();
|
||||
|
||||
function goToUpgrade() {
|
||||
pricingDrawer.visible.value = true;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@@ -83,7 +91,9 @@ const showWarning = computed(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex justify-end px-12 py-3 items-center gap-2">
|
||||
|
||||
<div v-if="showWarning" class="text-orange-400 flex gap-2 items-center">
|
||||
<i class="far fa-warning "></i>
|
||||
<div> It can take a few minutes </div>
|
||||
@@ -91,12 +101,21 @@ const showWarning = computed(() => {
|
||||
<div class="w-[15rem] flex flex-col gap-0">
|
||||
<USelectMenu v-model="selectedTimeFrom" :options="options"></USelectMenu>
|
||||
</div>
|
||||
<div @click="downloadCSV()"
|
||||
|
||||
<div v-if="isPremium" @click="downloadCSV()"
|
||||
class="bg-[#57c78fc0] hover:bg-[#57c78fab] cursor-pointer text-text poppins font-semibold px-8 py-2 rounded-lg">
|
||||
Download CSV
|
||||
</div>
|
||||
|
||||
<div v-if="!isPremium" @click="goToUpgrade()"
|
||||
class="bg-[#57c78f46] hover:bg-[#57c78f42] flex gap-4 items-center cursor-pointer text-text poppins font-semibold px-8 py-2 rounded-lg">
|
||||
<i class="far fa-lock"></i>
|
||||
Upgrade plan for CSV
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<UTable v-if="tableData" class="utable px-8" :ui="{
|
||||
wrapper: 'overflow-auto w-full h-full',
|
||||
thead: 'sticky top-0 bg-menu',
|
||||
|
||||
@@ -75,6 +75,12 @@ const { snapshot } = useSnapshot();
|
||||
|
||||
const refreshKey = computed(() => `${snapshot.value._id.toString() + activeProject.value?._id.toString()}`);
|
||||
|
||||
const pricingDrawer = usePricingDrawer();
|
||||
|
||||
function goToUpgrade() {
|
||||
pricingDrawer.visible.value = true;
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
@@ -94,11 +100,11 @@ const refreshKey = computed(() => `${snapshot.value._id.toString() + activeProje
|
||||
Limit reached
|
||||
</div>
|
||||
<div class="poppins text-[#fbbf24]">
|
||||
Litlyx has stopped to collect yur data. Please upgrade the plan for a minimal data loss.
|
||||
Litlyx cannot receive new data as you reached your plan's limit. Resume all the great features and collect even more data with a higher plan.
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<LyxUiButton type="outline"> Upgrade </LyxUiButton>
|
||||
<LyxUiButton type="outline" @click="goToUpgrade()"> Upgrade </LyxUiButton>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@ definePageMeta({ layout: 'header' });
|
||||
|
||||
<template>
|
||||
|
||||
<div class="home h-full overflow-y-auto relative">
|
||||
<!-- <div class="home h-full overflow-y-auto relative">
|
||||
|
||||
<div class="absolute top-0 left-0 w-full h-full flex flex-col items-center z-0 overflow-hidden">
|
||||
<HomeBgGrid :size="50" :spacing="18" opacity="0.3" class="w-fit h-fit"></HomeBgGrid>
|
||||
@@ -96,6 +96,6 @@ definePageMeta({ layout: 'header' });
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
</template>
|
||||
|
||||
@@ -18,6 +18,10 @@ export default defineEventHandler(async event => {
|
||||
const project = await ProjectModel.findById(project_id);
|
||||
if (!project) return setResponseStatus(event, 400, 'Project not found');
|
||||
|
||||
const PREMIUM_TYPE = project.premium_type;
|
||||
|
||||
if (PREMIUM_TYPE === 0) return setResponseStatus(event, 400, 'Project not premium');
|
||||
|
||||
const { mode, slice } = getQuery(event);
|
||||
|
||||
let timeSub = 1000 * 60 * 60 * 24;
|
||||
|
||||
Reference in New Issue
Block a user