mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-09 23:48:36 +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 { 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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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!',
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
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 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',
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user