add proper limit + csv lock

This commit is contained in:
Emily
2024-09-02 15:24:29 +02:00
parent 87b1f9caf9
commit 944996eb15
11 changed files with 139 additions and 27 deletions

View File

@@ -9,12 +9,28 @@ const debugMode = process.dev;
const { alerts, closeAlert } = useAlert(); const { alerts, closeAlert } = useAlert();
const { showDialog, closeDialog, dialogComponent, dialogParams, dialogStyle, dialogClosable } = useCustomDialog(); const { showDialog, closeDialog, dialogComponent, dialogParams, dialogStyle, dialogClosable } = useCustomDialog();
const { visible } = usePricingDrawer();
const { data: planData } = useFetch('/api/project/plan', {
...signHeaders(),
lazy: true
});
</script> </script>
<template> <template>
<div class="w-dvw h-dvh bg-lyx-background-light relative"> <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 class="fixed top-4 right-8 z-[999] flex flex-col gap-2" v-if="alerts.length > 0">
<div v-for="alert of alerts" <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"> 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> </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>

View File

@@ -138,7 +138,7 @@ watch(selected, () => {
<template #option="{ option, active, selected }"> <template #option="{ option, active, selected }">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div> <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>
<div> {{ option.name }} </div> <div> {{ option.name }} </div>
</div> </div>
@@ -147,7 +147,7 @@ watch(selected, () => {
<template #label> <template #label>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div> <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>
<div> {{ activeProject?.name || '???' }} </div> <div> {{ activeProject?.name || '???' }} </div>
</div> </div>

View File

@@ -69,7 +69,7 @@ async function onUpgradeClick() {
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<div class="flex gap-2" v-for="feature of data.features"> <div class="flex gap-2" v-for="feature of data.features">
<div class="h-6 w-6"> <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>
<div>{{ feature }}</div> <div>{{ feature }}</div>
</div> </div>

View File

@@ -1,9 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { PricingCardProp } from './PricingCardGeneric.vue'; import type { PricingCardProp } from './PricingCardGeneric.vue';
const activeProject = useActiveProject();
const props = defineProps<{ currentSub: number }>(); const props = defineProps<{ currentSub: number }>();
const freePricing: PricingCardProp[] = [ const freePricing: PricingCardProp[] = [
@@ -20,7 +17,7 @@ const freePricing: PricingCardProp[] = [
'Unlimited reports', 'Unlimited reports',
'AI Tokens: 10', 'AI Tokens: 10',
'Server type: SHARED', 'Server type: SHARED',
'Projects: max 2', 'Projects: max 1',
'Data retention: 2 Months' 'Data retention: 2 Months'
], ],
cta: 'Start For Free now!', cta: 'Start For Free now!',

View File

@@ -46,11 +46,6 @@ const { data: invoices, refresh: invoicesRefresh, pending: invoicesPending } = u
lazy: true lazy: true
}) })
const showPricingDrawer = ref<boolean>(false);
function onPlanUpgradeClick() {
showPricingDrawer.value = true;
}
function openInvoice(link: string) { function openInvoice(link: string) {
window.open(link, '_blank'); window.open(link, '_blank');
} }
@@ -77,18 +72,13 @@ const entries: SettingsTemplateEntry[] = [
] ]
const { visible } = usePricingDrawer();
</script> </script>
<template> <template>
<div class="relative"> <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" <div v-if="invoicesPending || planPending"
class="backdrop-blur-[1px] z-[20] mt-20 w-full h-full flex items-center justify-center font-bold"> 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> <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 class="poppins"> Expire date:</div>
<div> {{ prettyExpireDate }}</div> <div> {{ prettyExpireDate }}</div>
</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]"> 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> <div class="poppins"> Upgrade plan </div>
<i class="fas fa-arrow-up-right"></i> <i class="fas fa-arrow-up-right"></i>

View File

@@ -0,0 +1,9 @@
const pricingDrawerVisible = ref<boolean>(false);
export function usePricingDrawer() {
return { visible: pricingDrawerVisible };
}

View File

@@ -6,6 +6,8 @@ definePageMeta({ layout: 'dashboard' });
const activeProject = useActiveProject(); const activeProject = useActiveProject();
const isPremium = computed(() => (activeProject.value?.premium_type || 0) > 0);
const metricsInfo = ref<number>(0); const metricsInfo = ref<number>(0);
const columns = [ const columns = [
@@ -36,7 +38,36 @@ onMounted(async () => {
metricsInfo.value = counts.eventsCount; 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> </script>
@@ -47,14 +78,38 @@ onMounted(async () => {
<div class="w-full h-dvh flex flex-col"> <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"> class="bg-[#57c78fc0] hover:bg-[#57c78fab] cursor-pointer text-text poppins font-semibold px-8 py-2 rounded-lg">
Download CSV Download CSV
</div> </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>
</div>
<UTable v-if="tableData" class="utable px-8" :ui="{ <UTable v-if="tableData" class="utable px-8" :ui="{
wrapper: 'overflow-auto w-full h-full', wrapper: 'overflow-auto w-full h-full',
thead: 'sticky top-0 bg-menu', thead: 'sticky top-0 bg-menu',

View File

@@ -6,6 +6,8 @@ definePageMeta({ layout: 'dashboard' });
const activeProject = useActiveProject(); const activeProject = useActiveProject();
const isPremium = computed(() => (activeProject.value?.premium_type || 0) > 0);
const metricsInfo = ref<number>(0); const metricsInfo = ref<number>(0);
const columns = [ const columns = [
@@ -68,6 +70,12 @@ const showWarning = computed(() => {
return options.indexOf(selectedTimeFrom.value) > 1 return options.indexOf(selectedTimeFrom.value) > 1
}) })
const pricingDrawer = usePricingDrawer();
function goToUpgrade() {
pricingDrawer.visible.value = true;
}
</script> </script>
@@ -83,7 +91,9 @@ const showWarning = computed(() => {
</div> </div>
</div> </div>
<div class="flex justify-end px-12 py-3 items-center gap-2"> <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"> <div v-if="showWarning" class="text-orange-400 flex gap-2 items-center">
<i class="far fa-warning "></i> <i class="far fa-warning "></i>
<div> It can take a few minutes </div> <div> It can take a few minutes </div>
@@ -91,12 +101,21 @@ const showWarning = computed(() => {
<div class="w-[15rem] flex flex-col gap-0"> <div class="w-[15rem] flex flex-col gap-0">
<USelectMenu v-model="selectedTimeFrom" :options="options"></USelectMenu> <USelectMenu v-model="selectedTimeFrom" :options="options"></USelectMenu>
</div> </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"> class="bg-[#57c78fc0] hover:bg-[#57c78fab] cursor-pointer text-text poppins font-semibold px-8 py-2 rounded-lg">
Download CSV Download CSV
</div> </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>
</div>
<UTable v-if="tableData" class="utable px-8" :ui="{ <UTable v-if="tableData" class="utable px-8" :ui="{
wrapper: 'overflow-auto w-full h-full', wrapper: 'overflow-auto w-full h-full',
thead: 'sticky top-0 bg-menu', thead: 'sticky top-0 bg-menu',

View File

@@ -75,6 +75,12 @@ const { snapshot } = useSnapshot();
const refreshKey = computed(() => `${snapshot.value._id.toString() + activeProject.value?._id.toString()}`); const refreshKey = computed(() => `${snapshot.value._id.toString() + activeProject.value?._id.toString()}`);
const pricingDrawer = usePricingDrawer();
function goToUpgrade() {
pricingDrawer.visible.value = true;
}
</script> </script>
@@ -94,11 +100,11 @@ const refreshKey = computed(() => `${snapshot.value._id.toString() + activeProje
Limit reached Limit reached
</div> </div>
<div class="poppins text-[#fbbf24]"> <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> </div>
<div> <div>
<LyxUiButton type="outline"> Upgrade </LyxUiButton> <LyxUiButton type="outline" @click="goToUpgrade()"> Upgrade </LyxUiButton>
</div> </div>
</div> </div>

View File

@@ -7,7 +7,7 @@ definePageMeta({ layout: 'header' });
<template> <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"> <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> <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> </div>
</div> </div> -->
</template> </template>

View File

@@ -18,6 +18,10 @@ 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');
const PREMIUM_TYPE = project.premium_type;
if (PREMIUM_TYPE === 0) return setResponseStatus(event, 400, 'Project not premium');
const { mode, slice } = getQuery(event); const { mode, slice } = getQuery(event);
let timeSub = 1000 * 60 * 60 * 24; let timeSub = 1000 * 60 * 60 * 24;