update guests logic + fix pdf

This commit is contained in:
Emily
2025-02-10 16:28:34 +01:00
parent abc485a9ef
commit 346eecc928
25 changed files with 81 additions and 51 deletions

View File

@@ -2,7 +2,7 @@
import type { TApiSettings } from '@schema/ApiSettingsSchema'; import type { TApiSettings } from '@schema/ApiSettingsSchema';
import type { SettingsTemplateEntry } from './Template.vue'; import type { SettingsTemplateEntry } from './Template.vue';
const { project } = useProject(); const { project, isGuest } = useProject();
const entries: SettingsTemplateEntry[] = [ const entries: SettingsTemplateEntry[] = [
{ id: 'acodes', title: 'Appsumo codes', text: 'Redeem appsumo codes' }, { id: 'acodes', title: 'Appsumo codes', text: 'Redeem appsumo codes' },
@@ -39,7 +39,7 @@ async function redeemCode() {
<template> <template>
<SettingsTemplate :entries="entries" :key="project?.name || 'NONE'"> <SettingsTemplate v-if="!isGuest" :entries="entries" :key="project?.name || 'NONE'">
<template #acodes> <template #acodes>
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<LyxUiInput class="w-full px-4 py-2" placeholder="Appsumo code" v-model="currentCode"></LyxUiInput> <LyxUiInput class="w-full px-4 py-2" placeholder="Appsumo code" v-model="currentCode"></LyxUiInput>
@@ -58,4 +58,9 @@ async function redeemCode() {
</div> </div>
</template> </template>
</SettingsTemplate> </SettingsTemplate>
<div v-if="isGuest" class="text-lyx-text-darker flex w-full h-full justify-center mt-20">
Guests cannot view billing
</div>
</template> </template>

View File

@@ -2,6 +2,9 @@
import DeleteDomainData from '../dialog/DeleteDomainData.vue'; import DeleteDomainData from '../dialog/DeleteDomainData.vue';
import type { SettingsTemplateEntry } from './Template.vue'; import type { SettingsTemplateEntry } from './Template.vue';
const { isGuest } = useProject();
const entries: SettingsTemplateEntry[] = [ const entries: SettingsTemplateEntry[] = [
{ id: 'delete_dns', title: 'Delete domain data', text: 'Delete data of a specific domain from this project' }, { id: 'delete_dns', title: 'Delete domain data', text: 'Delete data of a specific domain from this project' },
{ id: 'delete_data', title: 'Delete project data', text: 'Delete all data from this project' }, { id: 'delete_data', title: 'Delete project data', text: 'Delete all data from this project' },
@@ -105,7 +108,7 @@ const sessionsLabel = computed(() => {
<div class="flex flex-col"> <div class="flex flex-col">
<!-- <div class="text-[.9rem] text-lyx-text-darker"> Select a domain </div> --> <!-- <div class="text-[.9rem] text-lyx-text-darker"> Select a domain </div> -->
<USelectMenu placeholder="Select a domain" :uiMenu="{ <USelectMenu v-if="!isGuest" placeholder="Select a domain" :uiMenu="{
select: 'bg-lyx-lightmode-widget-light !ring-lyx-lightmode-widget dark:!bg-lyx-widget-light !shadow-none focus:!ring-lyx-widget-lighter dark:!ring-lyx-widget-lighter', select: 'bg-lyx-lightmode-widget-light !ring-lyx-lightmode-widget dark:!bg-lyx-widget-light !shadow-none focus:!ring-lyx-widget-lighter dark:!ring-lyx-widget-lighter',
base: '!bg-lyx-lightmode-widget dark:!bg-lyx-widget', base: '!bg-lyx-lightmode-widget dark:!bg-lyx-widget',
option: { option: {
@@ -114,6 +117,8 @@ const sessionsLabel = computed(() => {
} }
}" :options="domains.data.value ?? []" v-model="selectedDomain"></USelectMenu> }" :options="domains.data.value ?? []" v-model="selectedDomain"></USelectMenu>
<div v-if="isGuest" class="text-lyx-text-darker"> Guests cannot delete data</div>
<div v-if="selectedDomain" class="flex flex-col gap-2 mt-4"> <div v-if="selectedDomain" class="flex flex-col gap-2 mt-4">
<div class="text-[.9rem] text-lyx-text-dark"> Select data to delete </div> <div class="text-[.9rem] text-lyx-text-dark"> Select data to delete </div>
@@ -141,7 +146,7 @@ const sessionsLabel = computed(() => {
</template> </template>
<template #delete_data> <template #delete_data>
<div <div v-if="!isGuest"
class="outline rounded-lg w-full px-8 py-4 flex flex-col gap-4 outline-[1px] outline-[#541c15] bg-lyx-lightmode-widget-light dark:bg-[#1e1412]"> class="outline rounded-lg w-full px-8 py-4 flex flex-col gap-4 outline-[1px] outline-[#541c15] bg-lyx-lightmode-widget-light dark:bg-[#1e1412]">
<div class="poppins font-semibold"> This operation will reset this project to it's initial state (0 <div class="poppins font-semibold"> This operation will reset this project to it's initial state (0
visits 0 events 0 sessions) </div> visits 0 events 0 sessions) </div>
@@ -151,6 +156,7 @@ const sessionsLabel = computed(() => {
</div> </div>
</div> </div>
<div v-if="isGuest" class="text-lyx-text-darker"> Guests cannot delete data</div>
</template> </template>
</SettingsTemplate> </SettingsTemplate>
</template> </template>

View File

@@ -156,21 +156,29 @@ function copyProjectId() {
<template> <template>
<SettingsTemplate :entries="entries" :key="project?.name || 'NONE'"> <SettingsTemplate :entries="entries" :key="project?.name || 'NONE'">
<template #pname> <template #pname>
<div class="flex flex-col gap-2">
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<LyxUiInput class="w-full px-4 py-2" :disabled="isGuest" v-model="projectNameInputVal"></LyxUiInput> <LyxUiInput class="w-full px-4 py-2" :disabled="isGuest" v-model="projectNameInputVal"></LyxUiInput>
<LyxUiButton v-if="!isGuest" @click="changeProjectName()" :disabled="!canChange" type="primary"> Change <LyxUiButton v-if="!isGuest" @click="changeProjectName()" :disabled="!canChange" type="primary">
Change
</LyxUiButton> </LyxUiButton>
</div> </div>
<div v-if="isGuest" class="text-lyx-text-darker"> *Guests cannot change project name </div>
</div>
</template> </template>
<template #api> <template #api>
<div class="flex items-center gap-4" v-if="apiKeys && apiKeys.length < 5"> <div class="flex flex-col gap-2" v-if="apiKeys && apiKeys.length < 5">
<LyxUiInput class="grow px-4 py-2" :disabled="isGuest" placeholder="ApiKeyName" v-model="newApiKeyName"> <div class="flex items-center gap-4">
<LyxUiInput class="grow px-4 py-2" :disabled="isGuest" placeholder="ApiKeyName"
v-model="newApiKeyName">
</LyxUiInput> </LyxUiInput>
<LyxUiButton v-if="!isGuest" @click="createApiKey()" :disabled="newApiKeyName.length < 3" <LyxUiButton v-if="!isGuest" @click="createApiKey()" :disabled="newApiKeyName.length < 3"
type="primary"> type="primary">
<i class="far fa-plus"></i> <i class="far fa-plus"></i>
</LyxUiButton> </LyxUiButton>
</div> </div>
<div v-if="isGuest" class="text-lyx-text-darker"> *Guests cannot manage api keys </div>
</div>
<LyxUiCard v-if="apiKeys && apiKeys.length > 0" class="w-full flex flex-col gap-4 items-center mt-4"> <LyxUiCard v-if="apiKeys && apiKeys.length > 0" class="w-full flex flex-col gap-4 items-center mt-4">
<div v-for="apiKey of apiKeys" class="flex flex-col w-full"> <div v-for="apiKey of apiKeys" class="flex flex-col w-full">
@@ -212,6 +220,7 @@ function copyProjectId() {
Delete project Delete project
</LyxUiButton> </LyxUiButton>
</div> </div>
<div v-if="isGuest"> *Guests cannot delete project </div>
</template> </template>
</SettingsTemplate> </SettingsTemplate>
</template> </template>

View File

@@ -123,7 +123,7 @@ const { showDrawer } = useDrawer();
<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>
</div> </div>
<SettingsTemplate v-if="!invoicesPending && !planPending" :entries="entries"> <SettingsTemplate v-if="!invoicesPending && !planPending && !isGuest" :entries="entries">
<template #info> <template #info>
<div v-if="!isGuest"> <div v-if="!isGuest">
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
@@ -267,6 +267,10 @@ const { showDrawer } = useDrawer();
</CardTitled> </CardTitled>
</template> </template>
</SettingsTemplate> </SettingsTemplate>
<div v-if="isGuest" class="text-lyx-text-darker flex w-full h-full justify-center mt-20">
Guests cannot view billing
</div>
</div> </div>

View File

@@ -98,6 +98,7 @@ const entries: SettingsTemplateEntry[] = [
User should have been registered to Litlyx User should have been registered to Litlyx
</div> </div>
</div> </div>
<div v-if="isGuest" class="text-lyx-text-darker"> Guests cannot add members</div>
</template> </template>
<template #members> <template #members>

View File

@@ -398,24 +398,29 @@ async function clearAllChats() {
<div :class="{ '!text-green-500': debugModeAi }" class="cursor-pointer text-red-500 w-fit" <div :class="{ '!text-green-500': debugModeAi }" class="cursor-pointer text-red-500 w-fit"
v-if="userRoles.isAdmin.value" @click="debugModeAi = !debugModeAi"> Debug mode </div> v-if="userRoles.isAdmin.value" @click="debugModeAi = !debugModeAi"> Debug mode </div>
<div class="flex justify-between items-center pt-3"> <div class="flex pt-3 px-4">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div class="bg-accent w-5 h-5 rounded-full animate-pulse"> <!-- <div class="bg-accent w-4 h-4 rounded-full animate-pulse">
</div> </div> -->
<div class="manrope font-semibold text-lyx-lightmode-text dark:text-text-dirty"> {{ <div class="manrope font-semibold text-lyx-lightmode-text dark:text-text-dirty">
chatsRemaining }} messages left {{ chatsRemaining }} messages left
</div> </div>
</div> </div>
<LyxUiButton v-if="!selfhosted" type="primary" class="text-[.9rem] text-center " @click="showDrawer('PRICING')"> <div class="grow"></div>
<LyxUiButton v-if="!selfhosted" type="primary" class="text-[.9rem] text-center "
@click="showDrawer('PRICING')">
Upgrade Upgrade
</LyxUiButton> </LyxUiButton>
</div> </div>
<div class="flex items-center gap-4"> <div class="dark:bg-lyx-widget-light bg-lyx-lightmode-widget-light h-[1px]"></div>
<div class="flex items-center gap-4 px-4 mt-4">
<div class="poppins font-semibold text-[1.1rem]"> History </div> <div class="poppins font-semibold text-[1.1rem]"> History </div>
<div class="grow"></div>
<LyxUiButton v-if="chatsList && chatsList.length > 0" @click="clearAllChats()" type="secondary" <LyxUiButton v-if="chatsList && chatsList.length > 0" @click="clearAllChats()" type="secondary"
class="text-center text-[.8rem]"> class="text-center text-[.8rem]">
Clear all Clear all chats
</LyxUiButton> </LyxUiButton>
</div> </div>

View File

@@ -16,7 +16,7 @@ const canDownload = computed(() => {
const metricsInfo = ref<number>(0); const metricsInfo = ref<number>(0);
const columns = [ const columns = [
{ key: 'website', label: 'Website', sortable: true }, { key: 'website', label: 'Domain', sortable: true },
{ key: 'page', label: 'Page', sortable: true }, { key: 'page', label: 'Page', sortable: true },
{ key: 'referrer', label: 'Referrer', sortable: true }, { key: 'referrer', label: 'Referrer', sortable: true },
{ key: 'browser', label: 'Browser', sortable: true }, { key: 'browser', label: 'Browser', sortable: true },

View File

@@ -71,7 +71,7 @@ async function createProject() {
<div class="flex flex-col items-center justify-center pt-[12rem] gap-12 relative z-[10]"> <div class="flex flex-col items-center justify-center pt-[12rem] gap-12 relative z-[10]">
<div class="text-[3rem] font-semibold text-center text-lyx-lightmode-text dark:text-lyx-text"> <div class="text-[3rem] font-semibold text-center text-lyx-lightmode-text dark:text-lyx-text">
Create your {{ isFirstProject ? 'first' : '' }} project Create {{ isFirstProject ? '' : 'a new' }} {{ isFirstProject ? 'your first' : '' }} project
</div> </div>
<div v-if="isFirstProject" class="text-[1.5rem]"> <div v-if="isFirstProject" class="text-[1.5rem]">

View File

@@ -16,7 +16,7 @@ const items = [
</script> </script>
<template> <template>
<div class="lg:px-10 lg:py-8 h-dvh overflow-y-auto overflow-x-hidden hide-scrollbars"> <div class="lg:px-10 lg:py-8 h-dvh overflow-y-auto overflow-x-hidden hide-scrollbars !pb-[10rem]">
<div class="poppins font-semibold text-[1.3rem] lg:px-0 px-4 lg:py-0 py-4"> Settings </div> <div class="poppins font-semibold text-[1.3rem] lg:px-0 px-4 lg:py-0 py-4"> Settings </div>

View File

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -3,7 +3,7 @@ import StripeService from '~/server/services/StripeService';
export default defineEventHandler(async event => { export default defineEventHandler(async event => {
const data = await getRequestDataOld(event, { requireSchema: false, allowLitlyx: false }); const data = await getRequestData(event, []);
if (!data) return; if (!data) return;
const { project } = data; const { project } = data;

View File

@@ -12,7 +12,7 @@ export type InvoiceData = {
} }
export default defineEventHandler(async event => { export default defineEventHandler(async event => {
const data = await getRequestDataOld(event, { requireSchema: false, allowLitlyx: false }); const data = await getRequestData(event, []);
if (!data) return; if (!data) return;
const { project, pid } = data; const { project, pid } = data;

View File

@@ -22,7 +22,7 @@ function getPlanToActivate(current_plan_id: number) {
export default defineEventHandler(async event => { export default defineEventHandler(async event => {
const data = await getRequestDataOld(event, { requireSchema: false, allowGuests: false, allowLitlyx: false }); const data = await getRequestData(event, []);
if (!data) return; if (!data) return;
const { project, pid, user } = data; const { project, pid, user } = data;

View File

@@ -4,7 +4,7 @@ import StripeService from '~/server/services/StripeService';
export default defineEventHandler(async event => { export default defineEventHandler(async event => {
const data = await getRequestDataOld(event, { requireSchema: false, allowLitlyx: false }); const data = await getRequestData(event, []);
if (!data) return; if (!data) return;
const { project } = data; const { project } = data;

View File

@@ -33,17 +33,19 @@ function formatNumberK(value: string | number, decimals: number = 1) {
const LINE_SPACING = 0.5; const LINE_SPACING = 0.5;
const resourcePath = process.env.MODE === 'TEST' ? './public/pdf/' : '../public/pdf/';
function createPdf(data: PDFGenerationData) { function createPdf(data: PDFGenerationData) {
const pdf = new pdfkit({ size: 'A4', margins: { top: 50, bottom: 50, left: 50, right: 50 }, }); const pdf = new pdfkit({ size: 'A4', margins: { top: 50, bottom: 50, left: 50, right: 50 }, });
pdf.fillColor('#ffffff').rect(0, 0, pdf.page.width, pdf.page.height).fill('#000000'); pdf.fillColor('#ffffff').rect(0, 0, pdf.page.width, pdf.page.height).fill('#000000');
pdf.font('./server/pdf/pdf_fonts/Poppins-Bold.ttf').fontSize(16).fillColor('#ffffff'); pdf.font(resourcePath + 'pdf_fonts/Poppins-Bold.ttf').fontSize(16).fillColor('#ffffff');
pdf.text(`Project name: ${data.projectName}`, { align: 'left' }).moveDown(LINE_SPACING); pdf.text(`Project name: ${data.projectName}`, { align: 'left' }).moveDown(LINE_SPACING);
pdf.text(`Timeframe name: ${data.snapshotName}`, { align: 'left' }).moveDown(LINE_SPACING); pdf.text(`Timeframe name: ${data.snapshotName}`, { align: 'left' }).moveDown(LINE_SPACING);
pdf.font('./server/pdf/pdf_fonts/Poppins-Regular.ttf').fontSize(12).fillColor('#ffffff') pdf.font(resourcePath + 'pdf_fonts/Poppins-Regular.ttf').fontSize(12).fillColor('#ffffff')
pdf.text(`Total visits: ${data.totalVisits}`, { align: 'left' }).moveDown(LINE_SPACING); pdf.text(`Total visits: ${data.totalVisits}`, { align: 'left' }).moveDown(LINE_SPACING);
pdf.text(`Average visits per day: ${data.avgVisitsDay}`, { align: 'left' }).moveDown(LINE_SPACING); pdf.text(`Average visits per day: ${data.avgVisitsDay}`, { align: 'left' }).moveDown(LINE_SPACING);
@@ -64,16 +66,16 @@ function createPdf(data: PDFGenerationData) {
pdf.text('Average growth:', { align: 'left' }).moveDown(LINE_SPACING); pdf.text('Average growth:', { align: 'left' }).moveDown(LINE_SPACING);
pdf.text(`${data.avgGrowthText}`, { align: 'left' }).moveDown(LINE_SPACING); pdf.text(`${data.avgGrowthText}`, { align: 'left' }).moveDown(LINE_SPACING);
pdf.font('./server/pdf/pdf_fonts/Poppins-Italic.ttf') pdf.font(resourcePath + 'pdf_fonts/Poppins-Italic.ttf')
.text('This gives you an idea of the average growth your website is experiencing over time.', { align: 'left' }) .text('This gives you an idea of the average growth your website is experiencing over time.', { align: 'left' })
.moveDown(LINE_SPACING); .moveDown(LINE_SPACING);
pdf.font('./server/pdf/pdf_fonts/Poppins-Regular.ttf') pdf.font(resourcePath + 'pdf_fonts/Poppins-Regular.ttf')
.fontSize(10) .fontSize(10)
.fillColor('#ffffff') .fillColor('#ffffff')
.text('Created with Litlyx.com', 50, 760, { align: 'center' }); .text('Created with Litlyx.com', 50, 760, { align: 'center' });
pdf.image('./server/pdf/pdf_images/logo.png', 460, 700, { width: 100 }); pdf.image(resourcePath + 'pdf_images/logo.png', 460, 700, { width: 100 });
pdf.end(); pdf.end();
return pdf; return pdf;

View File

@@ -3,7 +3,7 @@ import StripeService from '~/server/services/StripeService';
export default defineEventHandler(async event => { export default defineEventHandler(async event => {
const data = await getRequestDataOld(event, { requireSchema: false, allowLitlyx: false }); const data = await getRequestData(event, []);
if (!data) return; if (!data) return;
const { project, project_id } = data; const { project, project_id } = data;

View File

@@ -3,11 +3,10 @@ import { EventModel } from "@schema/metrics/EventSchema";
import { SessionModel } from "@schema/metrics/SessionSchema"; import { SessionModel } from "@schema/metrics/SessionSchema";
import { VisitModel } from "@schema/metrics/VisitSchema"; import { VisitModel } from "@schema/metrics/VisitSchema";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { getRequestDataOld } from "~/server/utils/getRequestData";
export default defineEventHandler(async event => { export default defineEventHandler(async event => {
const data = await getRequestDataOld(event, { requireSchema: false }); const data = await getRequestData(event, []);
if (!data) return; if (!data) return;
const { project_id } = data; const { project_id } = data;

View File

@@ -3,11 +3,10 @@ import { EventModel } from "@schema/metrics/EventSchema";
import { SessionModel } from "@schema/metrics/SessionSchema"; import { SessionModel } from "@schema/metrics/SessionSchema";
import { VisitModel } from "@schema/metrics/VisitSchema"; import { VisitModel } from "@schema/metrics/VisitSchema";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { getRequestDataOld } from "~/server/utils/getRequestData";
export default defineEventHandler(async event => { export default defineEventHandler(async event => {
const data = await getRequestDataOld(event, { requireSchema: false }); const data = await getRequestData(event, []);
if (!data) return; if (!data) return;
const { project_id } = data; const { project_id } = data;