17 Commits

Author SHA1 Message Date
Emily
6b5d23566c fix SELFHOST env on docker-compose 2025-01-17 18:07:53 +01:00
Emily
dbcda95823 fix selfhost 2025-01-17 17:40:20 +01:00
Emily
fb89c87489 fix selfhost 2025-01-17 16:44:22 +01:00
Emily
b59eea47e9 active snapshot on creation 2025-01-17 16:44:16 +01:00
Emily
473331047d fix dockercompose 2025-01-16 18:21:59 +01:00
Emily
5af77ff63e update docker-compose 2025-01-16 18:16:16 +01:00
Emily
e6e2340432 fix lightmode 2025-01-16 16:47:35 +01:00
Emily
0b90c2fe3c add lightmode to alerts 2025-01-15 16:34:10 +01:00
Emily
a6d1797a4f add lightmode 2025-01-15 16:31:10 +01:00
Emily
d1abe1a91f change packages 2025-01-15 14:45:02 +01:00
Emily
b733cd2a68 navbar lightmode 2025-01-14 17:38:33 +01:00
Emily
88ebfc188c add password reset + password change 2025-01-13 17:01:34 +01:00
antonio
ab95772dd4 read-me 2025-01-13 15:28:20 +01:00
Emily
0d5dbc69ad update docker compose 2025-01-13 15:24:14 +01:00
Emily
8a359936d1 . 2025-01-04 18:16:37 +01:00
Emily
ffd2e96138 fix emails templates 2025-01-04 18:14:12 +01:00
Emily
b8e434be9a fix chat limit reached message 2025-01-04 15:52:32 +01:00
68 changed files with 2244 additions and 490 deletions

View File

@@ -1,5 +0,0 @@
{
"files.exclude": {
"**/node_modules": true
}
}

View File

@@ -1,30 +0,0 @@
FROM node:21-alpine as base
FROM base as build
RUN npm i -g pnpm
RUN npm i -g pm2
# COPY --link dashboard/package.json dashboard/pnpm-lock.yaml ./
# RUN npm install --production=false
WORKDIR /home/app
COPY --link dashboard ./dashboard
COPY --link lyx-ui ./lyx-ui
COPY --link consumer ./consumer
COPY --link producer ./producer
COPY --link shared ./shared
WORKDIR /home/app/producer
RUN pnpm install
WORKDIR /home/app/consumer
RUN pnpm install
WORKDIR /home/app/dashboard
RUN pnpm install
RUN pnpm run dev
# CMD [ "node", "/home/app/.output/server/index.mjs" ]

View File

@@ -108,12 +108,9 @@ curl -X POST "https://broker.litlyx.com/event" \
To self-host the Litlyx dashboard, first **fork** this repository.
Then run the following command:
```bash
docker-compose build
```
You can find our Docker images on DockerHub for more.
after the build finishes, run:
Then run the following command:
```bash
docker-compose up
```

View File

@@ -16,18 +16,18 @@ const { drawerVisible, hideDrawer, drawerClasses } = useDrawer();
<template>
<div class="w-dvw h-dvh bg-lyx-background-light relative">
<div class="w-dvw h-dvh bg-lyx-lightmode-background-light dark:bg-lyx-background-light relative">
<Transition name="drawer">
<LazyDrawerGeneric @onCloseClick="hideDrawer()" :class="drawerClasses"
class="bg-black fixed right-0 top-0 w-full xl:w-[60vw] xl:min-w-[65rem] h-full z-[20]" v-if="drawerVisible">
class="bg-lyx-lightmode-background-light dark:bg-black fixed right-0 top-0 w-full xl:w-[60vw] xl:min-w-[65rem] h-full z-[20]" v-if="drawerVisible">
</LazyDrawerGeneric>
</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">
class="w-[30vw] min-w-[20rem] relative bg-lyx-lightmode-background dark:bg-[#151515] overflow-hidden border-solid border-[2px] border-lyx-lightmode-widget dark:border-[#262626] rounded-lg p-6 drop-shadow-lg">
<div class="flex items-start gap-4">
<div> <i :class="alert.icon"></i> </div>
<div class="grow">
@@ -56,8 +56,8 @@ const { drawerVisible, hideDrawer, drawerClasses } = useDrawer();
</div>
<div v-if="showDialog"
class="custom-dialog w-full h-full flex items-center justify-center lg:pl-32 lg:p-20 p-4 absolute left-0 top-0 z-[100] backdrop-blur-[2px] bg-black/50">
<div :style="dialogStyle" class="bg-lyx-widget rounded-xl relative outline outline-1 outline-lyx-widget-lighter">
class="custom-dialog w-full h-full flex items-center justify-center lg:pl-32 lg:p-20 p-4 absolute left-0 top-0 z-[100] backdrop-blur-[2px] dark:bg-black/50">
<div :style="dialogStyle" class="bg-lyx-lightmode-widget-light outline-lyx-lightmode-widget dark:bg-lyx-widget dark:outline-lyx-widget-lighter rounded-xl relative outline outline-1">
<div v-if="dialogClosable" class="flex justify-end absolute z-[100] right-8 top-8">
<i @click="closeDialog()" class="fas fa-close text-[1.6rem] hover:text-gray-500 cursor-pointer"></i>
</div>

View File

@@ -54,7 +54,7 @@ function openExternalLink(link: string) {
<div class="flex justify-between mb-3">
<div class="flex flex-col gap-1">
<div class="flex gap-4 items-center">
<div class="poppins font-semibold text-[1.4rem] text-text">
<div class="poppins font-semibold text-[1.4rem] text-lyx-lightmode-text dark:text-lyx-text">
{{ label }}
</div>
<div class="flex items-center">
@@ -63,7 +63,7 @@ function openExternalLink(link: string) {
</div>
</div>
<div class="poppins text-[1rem] text-text-sub/90">
<div class="poppins text-[1rem] text-lyx-ligtmode-text-darker dark:text-text-sub/90">
{{ desc }}
</div>
</div>
@@ -81,7 +81,8 @@ function openExternalLink(link: string) {
</div>
<div class="h-full flex flex-col">
<div class="flex justify-between font-bold text-text-sub/80 text-[1.1rem] mb-4">
<div
class="flex justify-between font-bold lyx-text-lightmode-text-dark dark:text-text-sub/80 text-[1.1rem] mb-4">
<div class="flex items-center gap-2">
<div v-if="isDetailView" class="flex items-center justify-center">
<i @click="$emit('showGeneral')"
@@ -107,7 +108,7 @@ function openExternalLink(link: string) {
<div class="flex gap-1 items-center" @click="showDetails(element._id)"
:class="{ 'cursor-pointer line-active': interactive }">
<div class="absolute rounded-sm w-full h-full bg-[#92abcf38]"
<div class="absolute rounded-sm w-full h-full bg-[#6f829c38] dark:bg-[#92abcf38]"
:style="'width:' + 100 / maxData * element.count + '%;'"></div>
<div class="flex px-2 py-1 relative items-center gap-4">
@@ -119,24 +120,28 @@ function openExternalLink(link: string) {
<i v-else :class="iconProvider(element)?.[1]"></i>
</div>
<span class="text-ellipsis line-clamp-1 ui-font z-[20] text-[.95rem] text-text/70">
<span
class="text-ellipsis line-clamp-1 ui-font z-[20] text-[.95rem] text-lyx-lightmode-text-dark dark:text-text/70">
{{ elementTextTransformer?.(element._id) || element._id }}
</span>
</div>
</div>
</div>
<div class="text-text font-semibold text-[.9rem] md:text-[1rem] manrope"> {{
formatNumberK(element.count) }} </div>
<div
class="text-lyx-lightmode-text dark:text-lyx-text font-semibold text-[.9rem] md:text-[1rem] manrope">
{{
formatNumberK(element.count) }} </div>
</div>
<div v-if="props.data.length == 0" class="flex justify-center text-text-sub font-light text-[1.1rem]">
No data yet
</div>
</div>
<div v-if="!hideShowMore" class="flex justify-center mt-4 text-text-sub/90 items-end grow">
<div @click="$emit('showMore')"
class="poppins hover:bg-black cursor-pointer w-fit px-6 py-1 rounded-lg border-[1px] border-text-sub text-[.9rem]">
<LyxUiButton type="outline" @click="$emit('showMore')">
Show more
</div>
</LyxUiButton>
</div>
</div>

View File

@@ -23,6 +23,19 @@ type Props = {
sections: Section[]
}
const colorMode = useColorMode()
const isDark = computed({
get() {
return colorMode.value === 'dark'
},
set() {
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
}
})
const route = useRoute();
const props = defineProps<Props>();
@@ -108,7 +121,7 @@ const { data: maxProjects } = useFetch("/api/user/max_projects", {
</script>
<template>
<div class="CVerticalNavigation border-solid border-[#202020] border-r-[1px] h-full w-[20rem] bg-lyx-background flex shadow-[1px_0_10px_#000000] rounded-r-lg"
<div class="CVerticalNavigation border-solid border-[#D9D9E0] dark:border-[#202020] border-r-[1px] h-full w-[20rem] bg-lyx-lightmode-background dark:bg-lyx-background flex shadow-[1px_0_10px_#000000]"
:class="{
'absolute top-0 w-full md:w-[20rem] z-[45] open': isOpen,
'hidden lg:flex': !isOpen
@@ -157,7 +170,7 @@ const { data: maxProjects } = useFetch("/api/user/max_projects", {
<div class="w-full flex-col px-2">
<div class="flex mb-2 items-center justify-between">
<div class="flex mb-2 items-center justify-between text-lyx-lightmode-text dark:text-lyx-text">
<div class="poppins text-[.8rem]">
Snapshots
</div>
@@ -170,7 +183,7 @@ const { data: maxProjects } = useFetch("/api/user/max_projects", {
</UTooltip> -->
<UTooltip text="Create new snapshot">
<LyxUiButton @click="openSnapshotDialog()" type="outlined" class="!px-3 !py-1">
<div><i class="fas fa-plus text-[.9rem]"></i></div>
<div><i class="fas fa-plus text-[.8rem]"></i></div>
</LyxUiButton>
</UTooltip>
</div>
@@ -179,11 +192,11 @@ const { data: maxProjects } = useFetch("/api/user/max_projects", {
<div class="flex items-center gap-2">
<USelectMenu :uiMenu="{
select: '!bg-lyx-widget-light !shadow-none focus:!ring-lyx-widget-lighter !ring-lyx-widget-lighter',
base: '!bg-lyx-widget',
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',
option: {
base: 'hover:!bg-lyx-widget-lighter cursor-pointer',
active: '!bg-lyx-widget-lighter'
base: 'hover:!bg-lyx-lightmode-widget-light dark:hover:!bg-lyx-widget-lighter cursor-pointer',
active: '!bg-lyx-lightmode-widget-light dark:!bg-lyx-widget-lighter'
}
}" class="w-full" v-model="snapshot" :options="snapshotsItems">
<template #label>
@@ -204,7 +217,8 @@ const { data: maxProjects } = useFetch("/api/user/max_projects", {
</div>
<div v-if="snapshot" class="flex flex-col text-[.7rem] mt-2">
<div class="flex gap-1 items-center justify-center text-lyx-text-dark">
<div
class="flex gap-1 items-center justify-center text-lyx-lightmode-text-dark dark:text-lyx-text-dark">
<div class="poppins">
{{ new Date(snapshot.from).toLocaleString().split(',')[0].trim() }}
</div>
@@ -237,14 +251,14 @@ const { data: maxProjects } = useFetch("/api/user/max_projects", {
</div>
<div class="w-full flex mt-4">
<LyxUiButton type="outline" class="w-full text-center text-[.7rem]">
<LyxUiButton type="outline" class="w-full text-center text-[.8rem]">
Export report
</LyxUiButton>
</div>
</div>
<div class="bg-[#202020] h-[1px] w-full"></div>
<div class="bg-lyx-lightmode-widget dark:bg-[#202020] h-[1px] w-full"></div>
<div class="flex flex-col h-full">
@@ -253,11 +267,11 @@ const { data: maxProjects } = useFetch("/api/user/max_projects", {
<div v-for="entry of section.entries" :class="{ 'grow flex items-end': entry.grow }">
<div v-if="(!entry.adminOnly || (userRoles.isAdmin.value && !isAdminHidden))"
class="bg-lyx-background w-full cursor-pointer text-lyx-text-dark py-[.35rem] px-2 rounded-lg text-[.95rem] flex items-center"
class="bg-lyx-lightmode-background text-lyx-lightmode-text-dark dark:bg-lyx-background dark:text-lyx-text-dark w-full cursor-pointer py-[.35rem] px-2 rounded-lg text-[.95rem] flex items-center"
:class="{
'!text-lyx-text-darker pointer-events-none': entry.disabled,
'bg-lyx-background-lighter !text-lyx-text/90': route.path == (entry.to || '#'),
'hover:bg-lyx-background-light hover:!text-lyx-text/90': route.path != (entry.to || '#'),
'bg-lyx-lightmode-background-light !text-lyx-lightmode-text dark:bg-lyx-background-lighter dark:!text-lyx-text': route.path == (entry.to || '#'),
'hover:bg-lyx-lightmode-background-light hover:!text-lyx-lightmode-text dark:hover:bg-lyx-background-light dark:hover:!text-lyx-text': route.path != (entry.to || '#'),
}">
<NuxtLink @click="close() && entry.action?.()" :target="entry.external ? '_blank' : ''"
@@ -281,36 +295,25 @@ const { data: maxProjects } = useFetch("/api/user/max_projects", {
<div class="grow"></div>
<div class="bg-[#202020] h-[1px] w-full px-4 mb-3"></div>
<div class="bg-lyx-lightmode-widget dark:bg-[#202020] h-[1px] w-full px-4 mb-3"></div>
<div class="flex justify-end px-2">
<div class="grow flex gap-3">
<!-- <NuxtLink to="https://github.com/litlyx/litlyx" target="_blank"
class="cursor-pointer hover:text-lyx-text text-lyx-text-dark">
<i class="fab fa-github"></i>
</NuxtLink> -->
<!-- <NuxtLink to="https://discord.gg/9cQykjsmWX" target="_blank"
class="cursor-pointer hover:text-lyx-text text-lyx-text-dark">
<i class="fab fa-discord"></i>
</NuxtLink> -->
<NuxtLink to="https://x.com/litlyx" target="_blank"
class="cursor-pointer hover:text-lyx-text text-lyx-text-dark">
<i class="fab fa-x-twitter"></i>
</NuxtLink>
<!-- <NuxtLink to="https://dev.to/litlyx-org" target="_blank"
class="cursor-pointer hover:text-lyx-text text-lyx-text-dark">
<i class="fab fa-dev"></i>
</NuxtLink> -->
<div>
<i @click="isDark = !isDark" class="cursor-pointer hover:text-lyx-lightmode-text text-lyx-lightmode-text-dark dark:hover:text-lyx-text dark:text-lyx-text-dark"
:class="isDark ? 'far fa-moon' : 'far fa-sun'"></i>
</div>
<NuxtLink to="/admin" v-if="userRoles.isAdmin.value"
class="cursor-pointer hover:text-lyx-text text-lyx-text-dark">
<i class="fas fa-cat"></i>
class="cursor-pointer hover:text-lyx-lightmode-text text-lyx-lightmode-text-dark dark:hover:text-lyx-text dark:text-lyx-text-dark">
<i class="far fa-cat"></i>
</NuxtLink>
</div>
<UTooltip text="Logout" :popper="{ arrow: true, placement: 'top' }">
<div @click="onLogout()" class="cursor-pointer hover:text-lyx-text text-lyx-text-dark">
<div @click="onLogout()" class="cursor-pointer hover:text-lyx-lightmode-text text-lyx-lightmode-text-dark dark:hover:text-lyx-text dark:text-lyx-text-dark">
<i class="far fa-arrow-right-from-bracket scale-x-[-100%]"></i>
</div>
</UTooltip>

View File

@@ -9,10 +9,10 @@ const props = defineProps<{ title: string, sub?: string }>();
<div class="flex flex-col gap-4 h-full">
<div class="flex items-center">
<div class="flex flex-col grow">
<div class="poppins font-semibold text-[1rem] md:text-[1.3rem] text-text">
<div class="poppins font-semibold text-[1rem] md:text-[1.3rem] text-lyx-lightmode-text-dark dark:text-text">
{{ props.title }}
</div>
<div v-if="props.sub" class="poppins text-[.7rem] md:text-[1rem] text-text-sub">
<div v-if="props.sub" class="poppins text-[.7rem] md:text-[1rem] text-lyx-lightmode-text-darker dark:text-text-sub">
{{ props.sub }}
</div>
</div>

View File

@@ -11,9 +11,9 @@ const activeTabIndex = ref<number>(0);
<div>
<div class="flex">
<div v-for="(tab, index) of items" @click="activeTabIndex = index"
class="px-6 pb-3 poppins font-medium text-lyx-text-darker border-b-[1px] border-lyx-text-darker" :class="{
class="px-6 pb-3 poppins font-medium text-lyx-lightmode-text dark:text-lyx-text-darker border-b-[1px] border-lyx-text-darker" :class="{
'!border-[#88A7FF] !text-[#88A7FF]': activeTabIndex === index,
'hover:border-lyx-text-dark hover:text-lyx-text-dark cursor-pointer': activeTabIndex !== index
'hover:border-lyx-lightmode-text-dark hover:text-lyx-lightmode-text-dark/60 dark:hover:border-lyx-text-dark dark:hover:text-lyx-text-dark cursor-pointer': activeTabIndex !== index
}">
{{ tab.label }}
</div>

View File

@@ -67,7 +67,7 @@ function reloadPage() {
<div class="flex items-center justify-center">
<div class="mr-4 animate-pulse w-[1rem] h-[1rem] bg-accent rounded-full"> </div>
<div class="text-text/90 poppins text-[1.1rem] font-medium">
<div class="text-lyx-lightmode-text dark:text-text/90 poppins text-[1.1rem] font-medium">
Waiting for your first visit
</div>
<LyxUiButton class="ml-6" type="secondary" @click="reloadPage()">
@@ -122,8 +122,8 @@ function reloadPage() {
<CardTitled class="h-full w-full" title="Project id"
sub="This is the identifier for this project, used to forward data">
<div class="flex items-center justify-between gap-4 mt-6">
<div class="p-2 bg-[#1c1b1b] rounded-md w-full">
<div class="w-full text-[.9rem] text-[#acacac]"> {{ project?._id }} </div>
<div class="p-2 bg-lyx-lightmode-widget dark:bg-[#1c1b1b] rounded-md w-full">
<div class="w-full text-[.9rem] dark:text-[#acacac]"> {{ project?._id }} </div>
</div>
<LyxUiButton type="secondary" @click="copyProjectId()"> Copy </LyxUiButton>
</div>

View File

@@ -8,12 +8,18 @@ const props = defineProps<{ type: ButtonType, link?: string, target?: string, di
<template>
<NuxtLink tag="div" :to="disabled ? '' : link" :target="target"
class="poppins w-fit cursor-pointer px-4 py-1 rounded-md outline outline-[1px] text-text" :class="{
'bg-lyx-primary-dark outline-lyx-primary hover:bg-lyx-primary-hover': type === 'primary',
'bg-lyx-widget-lighter outline-lyx-widget-lighter hover:bg-lyx-widget-light': type === 'secondary',
'bg-lyx-transparent outline-lyx-widget-lighter hover:bg-lyx-widget-light': (type === 'outline' || type === 'outlined'),
'bg-lyx-danger-dark outline-lyx-danger hover:bg-lyx-danger': type === 'danger',
'!bg-lyx-widget !outline-lyx-widget-lighter !cursor-not-allowed': disabled === true,
class="poppins w-fit cursor-pointer px-4 py-1 rounded-md outline outline-[1px] text-lyx-lightmode-text dark:text-lyx-text"
:class="{
'bg-[#85a3ff] hover:bg-[#9db5fc] outline-lyx-lightmode-widget-light dark:bg-lyx-primary-dark dark:outline-lyx-primary dark:hover:bg-lyx-primary-hover': type === 'primary',
'bg-lyx-lightmode-widget-light outline-lyx-lightmode-widget dark:bg-lyx-widget-lighter hover:bg-lyx-lightmode-widget dark:outline-lyx-widget-lighter dark:hover:bg-lyx-widget-light': type === 'secondary',
'bg-lyx-transparent outline-lyx-lightmode-widget hover:bg-lyx-lightmode-widget-light dark:outline-lyx-widget-lighter dark:hover:bg-lyx-widget-light': (type === 'outline' || type === 'outlined'),
'bg-[#fcd1cb] hover:bg-[#f8c5be] dark:bg-lyx-danger-dark outline-lyx-danger dark:hover:bg-lyx-danger': type === 'danger',
'text-lyx-text !bg-lyx-widget !outline-lyx-widget-lighter !cursor-not-allowed': disabled === true,
}">
<slot></slot>
</NuxtLink>

View File

@@ -4,7 +4,7 @@
</script>
<template>
<div class="w-fit h-fit rounded-md bg-lyx-widget p-4 outline outline-[1px] outline-lyx-background-lighter">
<div class="w-fit h-fit rounded-md bg-lyx-lightmode-background outline-lyx-lightmode-widget dark:bg-lyx-widget dark:outline-lyx-background-lighter p-4 outline outline-[1px] ">
<slot></slot>
</div>
</template>

View File

@@ -19,6 +19,6 @@ const handleChange = (event: Event) => {
<template>
<input
class="bg-lyx-widget-light text-lyx-text-dark poppins rounded-md outline outline-[1px] outline-lyx-widget-lighter"
class="bg-lyx-lightmode-widget-light outline-lyx-lightmode-widget text-lyx-lightmode-text dark:bg-lyx-widget-light dark:text-lyx-text-dark poppins rounded-md outline outline-[1px] dark:outline-lyx-widget-lighter"
:type="props.type ?? 'text'" :placeholder="props.placeholder" :value="props.modelValue" @input="handleChange">
</template>

View File

@@ -109,11 +109,11 @@ const showOnboarding = computed(() => {
<div v-if="page == 0" class="bg-lyx-background-light mt-[10vh] w-[50vw] min-w-[400px] h-fit p-8 rounded-md">
<div v-if="page == 0" class="bg-lyx-lightmode-background-light dark:bg-lyx-background-light mt-[10vh] w-[50vw] min-w-[400px] h-fit p-8 rounded-md">
<div class="text-lyx-text text-[1.4rem] text-center font-medium"> Getting Started </div>
<div class="text-lyx-lightmode-text dark:text-lyx-text text-[1.4rem] text-center font-medium"> Getting Started </div>
<div class="text-lyx-text mt-4">
<div class="text-lyx-lightmode-text dark:text-lyx-text mt-4">
For the current project do you already have other Analytics tools implemented (e.g. GA4) or Litlyx is
going to be your first/main analytics?
</div>
@@ -122,7 +122,7 @@ const showOnboarding = computed(() => {
<div v-for="(e, i) of analyticsList">
<div @click="selectIndex(i)"
:class="{ 'outline outline-[1px] outline-[#5680f8]': selectedIndex == i }"
class="bg-lyx-widget-light text-center p-2 rounded-md cursor-pointer">
class="bg-lyx-lightmode-widget-light dark:bg-lyx-widget-light text-center p-2 rounded-md cursor-pointer">
{{ e }}
</div>
</div>
@@ -140,11 +140,11 @@ const showOnboarding = computed(() => {
</div>
</div>
<div v-if="page == 1" class="bg-lyx-background-light mt-[10vh] w-[50vw] min-w-[400px] h-fit p-8 rounded-md">
<div v-if="page == 1" class="bg-lyx-lightmode-background-light dark:bg-lyx-background-light mt-[10vh] w-[50vw] min-w-[400px] h-fit p-8 rounded-md">
<div class="text-lyx-text text-[1.4rem] text-center font-medium"> Getting Started </div>
<div class="text-lyx-lightmode-text dark:text-lyx-text text-[1.4rem] text-center font-medium"> Getting Started </div>
<div class="text-lyx-text mt-4">
<div class="text-lyx-lightmode-text dark:text-lyx-text mt-4">
What is your job title ?
</div>
@@ -152,7 +152,7 @@ const showOnboarding = computed(() => {
<div v-for="(e, i) of jobsList">
<div @click="selectIndex2(i)"
:class="{ 'outline outline-[1px] outline-[#5680f8]': selectedIndex2 == i }"
class="bg-lyx-widget-light text-center p-2 rounded-md cursor-pointer">
class="bg-lyx-lightmode-widget-light dark:bg-lyx-widget-light text-center p-2 rounded-md cursor-pointer">
{{ e }}
</div>
</div>

View File

@@ -4,7 +4,7 @@ import type { TProject } from '@schema/project/ProjectSchema';
const { user } = useLoggedUser()
const { projectList, guestProjectList,allProjectList, actions, project } = useProject();
const { projectList, guestProjectList, allProjectList, actions, project } = useProject();
function isProjectMine(owner?: string) {
@@ -22,11 +22,11 @@ function onChange(e: TProject) {
<template>
<USelectMenu :uiMenu="{
select: '!bg-lyx-widget-light !shadow-none focus:!ring-lyx-widget-lighter !ring-lyx-widget-lighter',
base: '!bg-lyx-widget',
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',
option: {
base: 'hover:!bg-lyx-widget-lighter cursor-pointer',
active: '!bg-lyx-widget-lighter'
base: 'hover:!bg-lyx-lightmode-widget-light dark:hover:!bg-lyx-widget-lighter cursor-pointer',
active: '!bg-lyx-lightmode-widget-light dark:!bg-lyx-widget-lighter'
}
}" class="w-full" v-if="allProjectList" @change="onChange" :value="project" :options="allProjectList">

View File

@@ -16,12 +16,12 @@ const emits = defineEmits<{
<template>
<div class="flex gap-2 border-[1px] border-lyx-widget-lighter p-1 md:p-2 rounded-xl bg-lyx-widget">
<div class="flex gap-2 border-[1px] p-1 md:p-2 rounded-xl bg-lyx-lightmode-widget-light border-lyx-lightmode-widget dark:bg-lyx-widget dark:border-lyx-widget-lighter">
<div @click="opt.disabled ? ()=>{}: $emit('changeIndex', index)" v-for="(opt, index) of options"
class="hover:bg-lyx-widget-lighter/60 select-btn-animated cursor-pointer rounded-lg poppins font-regular px-2 md:px-3 py-1 text-[.8rem] md:text-[1rem]"
class="hover:bg-lyx-lightmode-widget dark:hover:bg-lyx-widget-lighter/60 select-btn-animated cursor-pointer rounded-lg poppins font-regular px-2 md:px-3 py-1 text-[.8rem] md:text-[1rem]"
:class="{
'bg-lyx-widget-lighter hover:!bg-lyx-widget-lighter': currentIndex == index && !opt.disabled,
'hover:!bg-lyx-widget !cursor-not-allowed text-lyx-widget-lighter': opt.disabled
'bg-lyx-lightmode-widget hover:!bg-lyx-lightmode-widget dark:bg-lyx-widget-lighter dark:hover:!bg-lyx-widget-lighter': currentIndex == index && !opt.disabled,
'hover:!bg-lyx-lightmode-widget-light text-lyx-lightmode-widget dark:hover:!bg-lyx-widget !cursor-not-allowed dark:!text-lyx-widget-lighter': opt.disabled
}">
{{ opt.label }}
</div>

View File

@@ -333,8 +333,8 @@ const legendClasses = ref<string[]>([
<div class="flex gap-6 w-full justify-between lg:flex-row flex-col">
<LyxUiButton type="secondary" :to="isLiveDemo ? '#' : '/analyst'" :disabled="isLiveDemo">
<div class="flex items-center gap-2 px-10">
<i class="far fa-sparkles text-yellow-400"></i>
<div class="poppins text-lyx-text"> Ask AI </div>
<i class="far fa-sparkles text-yellow-600 dark:text-yellow-400"></i>
<div class="poppins text-lyx-lightmode-text dark:text-lyx-text"> Ask AI </div>
</div>
</LyxUiButton>
<div class="flex gap-6">
@@ -352,7 +352,7 @@ const legendClasses = ref<string[]>([
<div id='external-tooltip' ref="externalTooltipElement" class="z-[400]">
<LyxUiCard>
<LyxUiCard class="text-lyx-lightmode-text dark:text-lyx-text">
<div class="flex gap-2 items-center">
<div> Date: </div>
<div v-if="currentTooltipData"> {{ currentTooltipData.date }}</div>

View File

@@ -29,12 +29,12 @@ const { showDrawer } = useDrawer();
</div>
<div class="flex flex-col grow">
<div class="flex items-center gap-2">
<div class="brockmann text-text-dirty text-[1.2rem] 2xl:text-[1.4rem]">
<div class="brockmann text-lyx-lightmode-text-dark dark:text-text-dirty text-[1.2rem] 2xl:text-[1.4rem]">
{{ value }}
</div>
<div class="poppins text-text-sub text-[.65rem] 2xl:text-[.8rem]"> {{ avg }} </div>
<div class="poppins text-lyx-lightmode-darker dark:text-text-sub text-[.65rem] 2xl:text-[.8rem]"> {{ avg }} </div>
</div>
<div class="poppins text-text-sub text-[.9rem] 2xl:text-[1rem]"> {{ text }} </div>
<div class="poppins text-lyx-lightmode-darker dark:text-text-sub text-[.9rem] 2xl:text-[1rem]"> {{ text }} </div>
</div>
<div class="flex flex-col items-center gap-1">

View File

@@ -13,8 +13,8 @@ const columns = [
<template>
<div class="w-full h-full bg-bg rounded-xl p-8">
<div class="full h-full overflow-y-auto">
<div class="w-full h-full bg-lyx-lightmode-background dark:bg-lyx-background-light rounded-xl p-8">
<div class="full h-full overflow-y-auto text-lyx-lightmode-text dark:text-lyx-text">
<UTable :columns="columns" :rows="dialogBarData" :loading="isDataLoading" v-if="dialogBarData">
<template #count-data="{ row }">
<div class="font-bold"> {{ formatNumberK(row.count) }} </div>

View File

@@ -27,7 +27,6 @@ const chartOptions = ref<ChartOptions<'doughnut'>>({
position: 'top',
align: 'center',
labels: {
color: 'white',
font: {
family: 'Poppins',
size: 16

View File

@@ -101,8 +101,8 @@ const todayIndex = computed(() => {
<DashboardCountCard :todayIndex="todayIndex" :ready="!visitsData.pending.value" icon="far fa-earth"
text="Total visits" :value="formatNumberK(visitsData.data.value?.data.reduce((a, e) => a + e, 0) || '...')"
:avg="formatNumberK(avgVisitDay) + '/day'" :data="visitsData.data.value?.data"
tooltipText="Sum of all page views on your website."
:labels="visitsData.data.value?.labels" color="#5655d7">
tooltipText="Sum of all page views on your website." :labels="visitsData.data.value?.labels"
color="#5655d7">
</DashboardCountCard>
<DashboardCountCard :todayIndex="todayIndex" :ready="!bouncingRateData.pending.value" icon="far fa-chart-user"
@@ -115,16 +115,15 @@ const todayIndex = computed(() => {
<DashboardCountCard :todayIndex="todayIndex" :ready="!sessionsData.pending.value" icon="far fa-user"
text="Unique visitors"
:value="formatNumberK(sessionsData.data.value?.data.reduce((a, e) => a + e, 0) || '...')"
tooltipText="Count of distinct users visiting your website."
:avg="formatNumberK(avgSessionsDay) + '/day'" :data="sessionsData.data.value?.data"
:labels="sessionsData.data.value?.labels" color="#4abde8">
tooltipText="Count of distinct users visiting your website." :avg="formatNumberK(avgSessionsDay) + '/day'"
:data="sessionsData.data.value?.data" :labels="sessionsData.data.value?.labels" color="#4abde8">
</DashboardCountCard>
<DashboardCountCard :todayIndex="todayIndex" :ready="!sessionsDurationData.pending.value" icon="far fa-timer"
text="Visit duration" :value="avgSessionDuration" :data="sessionsDurationData.data.value?.data"
tooltipText="Average time users spend on your website."
:labels="sessionsDurationData.data.value?.labels" color="#f56523">
tooltipText="Average time users spend on your website." :labels="sessionsDurationData.data.value?.labels"
color="#f56523">
</DashboardCountCard>
</div>

View File

@@ -34,10 +34,10 @@ function showAnomalyInfoAlert() {
<template>
<div
class="w-full px-6 pb-2 lg:pb-6 font-bold text-text-sub/40 flex flex-col xl:flex-row text-lg gap-2 xl:gap-12 lg:text-2xl">
<div class="w-full px-6 pb-2 lg:pb-6 font-bold flex flex-col xl:flex-row text-lg gap-2 xl:gap-12 lg:text-2xl">
<div class="flex gap-2 items-center text-text/90 justify-center md:justify-start">
<div
class="flex gap-2 items-center text-lyx-lightmode-text/90 dark:text-lyx-text/90 justify-center md:justify-start">
<div class="animate-pulse w-[1rem] h-[1rem] bg-green-400 rounded-full"> </div>
<div class="poppins font-medium text-[.9rem]"> {{ onlineUsers.data }} Online users</div>
</div>
@@ -63,7 +63,8 @@ function showAnomalyInfoAlert() {
</div>
</div> -->
<div v-if="!selfhosted" class="flex gap-2 items-center text-text/90 justify-center md:justify-start">
<div v-if="!selfhosted"
class="flex gap-2 items-center text-lyx-lightmode-text/90 dark:text-lyx-text/90 justify-center md:justify-start">
<div class="animate-pulse w-[1rem] h-[1rem] bg-green-400 rounded-full"> </div>
<div class="poppins font-regular text-[.9rem]"> AI Anomaly Detector </div>
<div class="flex items-center">

View File

@@ -36,7 +36,7 @@ function onColorChange() {
const snapshotName = ref<string>("");
const { updateSnapshots } = useSnapshot();
const { updateSnapshots, snapshot, snapshots } = useSnapshot();
const { createAlert } = useAlert()
async function confirmSnapshot() {
@@ -54,6 +54,9 @@ async function confirmSnapshot() {
await updateSnapshots();
closeDialog();
createAlert('Snapshot created', 'Snapshot created successfully', 'far fa-circle-check', 5000);
const newSnapshot = snapshots.value.at(-1);
if (newSnapshot) snapshot.value = newSnapshot;
}
</script>
@@ -61,7 +64,7 @@ async function confirmSnapshot() {
<template>
<div class="w-full h-full flex flex-col">
<div class="poppins text-center">
<div class="poppins text-center text-lyx-lightmode-text dark:text-lyx-text">
Create a snapshot
</div>

View File

@@ -35,7 +35,7 @@ async function sendFeedback() {
overlay: {
background: 'bg-lyx-background/85'
},
background: 'bg-lyx-widget',
background: 'dark:bg-lyx-widget bg-lyx-lightmode-widget-light',
ring: 'border-solid border-[1px] border-[#262626]'
}">
<div class="h-full flex flex-col gap-2 p-4">
@@ -43,7 +43,7 @@ async function sendFeedback() {
<div class="flex flex-col gap-3">
<div> Share everything with us. </div>
<textarea v-model="text" placeholder="Leave your feedback"
class="p-2 w-full h-[8rem] resize-none rounded-md outline outline-[2px] outline-[#3a3f47]"></textarea>
class="p-2 w-full h-[8rem] dark:bg-lyx-widget bg-lyx-lightmode-widget-light resize-none rounded-md outline outline-[2px] outline-[#3a3f47]"></textarea>
<div class="flex justify-between items-center">
<div>Need help ? Check the docs <a href="https://docs.litlyx.com" target="_blank"
class="text-blue-500">here</a> </div>

View File

@@ -10,7 +10,7 @@ const { drawerComponent } = useDrawer();
<div class="p-8 overflow-y-auto">
<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 dark:bg-menu drop-shadow-[0_0_2px_#CCCCCCCC] w-9 h-9 flex items-center justify-center">
<i class="fas fa-close text-[1.6rem]"></i>
</div>

View File

@@ -195,19 +195,27 @@ function getPricingsData() {
<div class="flex justify-between items-center mt-10 flex-col xl:flex-row">
<div class="flex flex-col gap-2">
<div class="poppins text-[1.1rem] text-lyx-lightmode-text dark:text-yellow-400 mb-2">
*Plan upgrades are applicable exclusively to this project(workspace).
</div>
<div class="poppins text-[2rem] font-semibold">
Do you need help ?
</div>
<div class="poppins text-[1.2rem] text-text/90">
<div class="poppins text-[1.2rem]">
We respond in max. 1-2 days
</div>
</div>
<div class="mt-2">
<div class="rounded-lg px-10 py-3 bg-[#151515]">
<a href="mailto:help@litlyx.com" class="poppins text-[1.3rem]">
<div class="flex flex-col gap-2">
<LyxUiButton type="secondary">
<a href="mailto:help@litlyx.com" class="poppins text-[1.1rem]">
help@litlyx.com
</a>
</div>
</LyxUiButton>
<LyxUiButton type="secondary">
<a href="https://discord.com/invite/9cQykjsmWX" class="poppins text-[1.1rem]">
Discord support
</a>
</LyxUiButton>
</div>
</div>

View File

@@ -81,11 +81,11 @@ const canSearch = computed(() => {
<div class="flex-[2]">
<div class="flex flex-col gap-2">
<USelectMenu :uiMenu="{
select: '!bg-lyx-widget-light !shadow-none focus:!ring-lyx-widget-lighter !ring-lyx-widget-lighter',
base: '!bg-lyx-widget',
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',
option: {
base: 'hover:!bg-lyx-widget-lighter cursor-pointer',
active: '!bg-lyx-widget-lighter'
base: 'hover:!bg-lyx-lightmode-widget-light dark:hover:!bg-lyx-widget-lighter cursor-pointer',
active: '!bg-lyx-lightmode-widget-light dark:!bg-lyx-widget-lighter'
}
}" searchable searchable-placeholder="Search an event..." class="w-full"
placeholder="Select an event" :options="eventNames.data.value || []"
@@ -93,11 +93,11 @@ const canSearch = computed(() => {
</USelectMenu>
<USelectMenu :uiMenu="{
select: '!bg-lyx-widget-light !shadow-none focus:!ring-lyx-widget-lighter !ring-lyx-widget-lighter',
base: '!bg-lyx-widget',
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',
option: {
base: 'hover:!bg-lyx-widget-lighter cursor-pointer',
active: '!bg-lyx-widget-lighter'
base: 'hover:!bg-lyx-lightmode-widget-light dark:hover:!bg-lyx-widget-lighter cursor-pointer',
active: '!bg-lyx-lightmode-widget-light dark:!bg-lyx-widget-lighter'
}
}" searchable searchable-placeholder="Search a field..." class="w-full"
placeholder="Select a field" :options="metadataFields" v-model="selectedMetadataField">
@@ -110,7 +110,7 @@ const canSearch = computed(() => {
</div>
<div v-if="canSearch" class="h-full flex items-center text-[1.2rem]">
<div class="bg-lyx-widget-light flex items-center rounded-md pl-4">
<div class="bg-lyx-lightmode-widget dark:bg-lyx-widget-light flex items-center rounded-md pl-4">
<div><i class="far fa-search"></i></div>
<input class="bg-transparent px-4 py-2 text-[1rem] outline-none" type="text"
placeholder="Filter by metadata name" v-model="currentSearchText">
@@ -121,7 +121,7 @@ const canSearch = computed(() => {
<div class="flex flex-wrap gap-2 lg:mt-4 mt-10">
<div class="bg-lyx-widget-light text-lyx-text-dark px-3 py-2 rounded-md w-fit"
<div class="bg-lyx-lightmode-widget dark:bg-lyx-widget-light text-lyx-lightmode-text dark:text-lyx-text-dark px-3 py-2 rounded-md w-fit"
v-for="item of metadataFieldGroupedFiltered">
<div class="flex gap-2 items-center">
<div> {{ item._id || 'OLD_EVENTS' }} </div>

View File

@@ -46,12 +46,12 @@ async function analyzeEvent() {
<div class="py-2 flex items-center gap-3">
<USelectMenu :uiMenu="{
select: '!bg-lyx-widget-light !shadow-none focus:!ring-lyx-widget-lighter !ring-lyx-widget-lighter',
base: '!bg-lyx-widget',
option: {
base: 'hover:!bg-lyx-widget-lighter cursor-pointer',
active: '!bg-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',
option: {
base: 'hover:!bg-lyx-lightmode-widget-light dark:hover:!bg-lyx-widget-lighter cursor-pointer',
active: '!bg-lyx-lightmode-widget-light dark:!bg-lyx-widget-lighter'
}
}" searchable searchable-placeholder="Search an event..." class="w-full" placeholder="Select an event"
:options="eventNames.data.value || []" v-model="selectedEventName">
</USelectMenu>
@@ -71,7 +71,7 @@ async function analyzeEvent() {
</div>
<div class="flex flex-col gap-2" v-if="userFlowData">
<div class="flex gap-4 items-center bg-bg py-2 px-2 bg-lyx-widget-light rounded-lg"
<div class="flex gap-4 items-center bg-bg py-2 px-2 bg-lyx-lightmode-widget dark:bg-lyx-widget-light rounded-lg"
v-for="(count, referrer) in userFlowData">
<div class="w-5 h-5 flex items-center justify-center">
<img :src="`https://s2.googleusercontent.com/s2/favicons?domain=${referrer}&sz=64`"

View File

@@ -22,86 +22,27 @@ const widthHeight = computed(() => {
return 9 + props.size * props.spacing;
});
const colorMode = useColorMode();
</script>
<template>
<div class="w-fit h-fit">
<svg xmlns="http://www.w3.org/2000/svg" :width="widthHeight" :height="widthHeight" :style="`opacity: ${props.opacity};`"
fill="none">
<template v-for="(p, x) of sizeArr">
<template v-for="(p, y) of sizeArr">
<circle :cx="9 + (spacing * x)" :cy="9 + (spacing * y)" r="1" fill="#fff"
<circle :cx="9 + (spacing * x)" :cy="9 + (spacing * y)" r="1" :fill="colorMode.value === 'light' ? '#000' : '#FFF'"
:fill-opacity="calculateOpacity(x, y)" />
</template>
</template>
<!-- <circle cx="27" cy="9" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="45" cy="9" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="63" cy="9" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="81" cy="9" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="99" cy="9" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="117" cy="9" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="135" cy="9" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="9" cy="27" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="27" cy="27" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="45" cy="27" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="63" cy="27" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="81" cy="27" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="99" cy="27" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="117" cy="27" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="135" cy="27" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="9" cy="45" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="27" cy="45" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="45" cy="45" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="63" cy="45" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="81" cy="45" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="99" cy="45" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="117" cy="45" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="135" cy="45" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="9" cy="63" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="27" cy="63" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="45" cy="63" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="63" cy="63" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="81" cy="63" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="99" cy="63" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="117" cy="63" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="135" cy="63" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="9" cy="81" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="27" cy="81" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="45" cy="81" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="63" cy="81" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="81" cy="81" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="99" cy="81" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="117" cy="81" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="135" cy="81" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="9" cy="99" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="27" cy="99" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="45" cy="99" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="63" cy="99" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="81" cy="99" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="99" cy="99" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="117" cy="99" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="135" cy="99" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="9" cy="117" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="27" cy="117" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="45" cy="117" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="63" cy="117" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="81" cy="117" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="99" cy="117" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="117" cy="117" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="135" cy="117" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="9" cy="135" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="27" cy="135" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="45" cy="135" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="63" cy="135" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="81" cy="135" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="99" cy="135" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="117" cy="135" r="1" fill="#fff" fill-opacity=".9" />
<circle cx="135" cy="135" r="1" fill="#fff" fill-opacity=".9" />
-->
</svg>
</div>

View File

@@ -41,7 +41,7 @@ async function onUpgradeClick() {
<template>
<div
class="relative bg-[#151515] outline outline-[1px] outline-[#262626] py-8 px-10 rounded-lg w-full max-w-[30rem]">
class="relative bg-lyx-lightmode-widget-light dark:bg-[#151515] outline outline-[1px] outline-lyx-lightmode-widget dark:outline-[#262626] py-8 px-10 rounded-lg w-full max-w-[30rem]">
<div class="flex flex-col gap-3 text-center pt-3">
<div v-if="data.active"
@@ -56,7 +56,7 @@ async function onUpgradeClick() {
<div class="poppins text-4xl font-medium"> {{ data.price }} </div>
</div>
<div class="sep bg-[#262626] h-[1px] my-8"></div>
<div class="sep bg-lyx-lightmode-widget dark:bg-[#262626] h-[1px] my-8"></div>
<div class="flex flex-col text-center h-[6rem] justify-center gap-2">
<div v-if="datas.length > 1">
@@ -76,7 +76,7 @@ async function onUpgradeClick() {
</div>
</div>
<div class="sep bg-[#262626] h-[1px] my-8"></div>
<div class="sep bg-lyx-lightmode-widget dark:bg-[#262626] h-[1px] my-8"></div>
<div class="flex flex-col gap-2">
<div class="flex gap-2" v-for="feature of data.features">

View File

@@ -2,12 +2,18 @@
import type { SettingsTemplateEntry } from './Template.vue';
const entries: SettingsTemplateEntry[] = [
{ id: 'change_pass', title: 'Change password', text: 'Change your password' },
{ id: 'delete', title: 'Delete account', text: 'Delete your account' },
]
const { user } = useLoggedUser();
const { setToken } = useAccessToken();
const canChangePassword = useFetch('/api/user/password/can_change', {
headers: useComputedHeaders({ useSnapshotDates: false })
});
async function deleteAccount() {
const sure = confirm("Are you sure you want to delete this account ?");
if (!sure) return;
@@ -20,17 +26,63 @@ async function deleteAccount() {
location.href = "/login"
}
const old_password = ref<string>("");
const new_password = ref<string>("");
const { createAlert } = useAlert()
async function changePassword() {
try {
const res = await $fetch("/api/user/password/change", {
...signHeaders({ 'Content-Type': 'application/json' }),
method: "POST",
body: JSON.stringify({ old_password: old_password.value, new_password: new_password.value })
})
if (!res) throw Error('No response');
if (res.error) return createAlert('Error', res.message, 'far fa-triangle-exclamation', 5000);
old_password.value = '';
new_password.value = '';
return createAlert('Success', 'Password changed successfully', 'far fa-circle-check', 5000);
} catch (ex) {
console.error(ex);
createAlert('Error', 'Internal error', 'far fa-triangle-exclamation', 5000);
}
}
</script>
<template>
<SettingsTemplate :entries="entries">
<template #change_pass>
<div v-if="canChangePassword.data.value?.can_change">
<div class="flex flex-col gap-4">
<LyxUiInput type="password" class="py-1 px-2" v-model="old_password" placeholder="Current password"></LyxUiInput>
<LyxUiInput type="password" class="py-1 px-2" v-model="new_password" placeholder="New password"></LyxUiInput>
<LyxUiButton type="primary" @click="changePassword()"> Change password </LyxUiButton>
</div>
</div>
<div v-if="!canChangePassword.data.value?.can_change">
You cannot change the password for accounts created using social login options.
</div>
</template>
<template #delete>
<div
class="outline rounded-lg w-full px-8 py-4 flex flex-col gap-4 outline-[1px] outline-[#541c15] 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"> Deleting this account will also remove its projects </div>
<div @click="deleteAccount()"
class="text-[#e95b61] poppins font-semibold cursor-pointer hover:text-black hover:bg-red-700 outline rounded-lg w-fit px-8 py-2 outline-[1px] outline-[#532b26] bg-[#291415]">
class="text-[#e95b61] poppins font-semibold cursor-pointer hover:text-black hover:bg-red-700 outline rounded-lg w-fit px-8 py-2 outline-[1px] outline-[#532b26] bg-lyx-lightmode-widget-light dark:bg-[#291415]">
Delete account
</div>
</div>

View File

@@ -53,6 +53,9 @@ async function redeemCode() {
<div class="text-lyx-text-darker mt-1 text-[.9rem] poppins">
Redeemed codes: {{ valid_codes.data.value?.count || '0' }}
</div>
<div class="poppins text-[1.1rem] text-lyx-lightmode-text dark:text-yellow-400 mb-2">
*Plan upgrades are applicable exclusively to this project(workspace).
</div>
</template>
</SettingsTemplate>
</template>

View File

@@ -106,12 +106,12 @@ const sessionsLabel = computed(() => {
<!-- <div class="text-[.9rem] text-lyx-text-darker"> Select a domain </div> -->
<USelectMenu placeholder="Select a domain" :uiMenu="{
select: '!bg-lyx-widget-light !shadow-none focus:!ring-lyx-widget-lighter !ring-lyx-widget-lighter',
base: '!bg-lyx-widget',
option: {
base: 'hover:!bg-lyx-widget-lighter cursor-pointer',
active: '!bg-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',
option: {
base: 'hover:!bg-lyx-lightmode-widget-light dark:hover:!bg-lyx-widget-lighter cursor-pointer',
active: '!bg-lyx-lightmode-widget-light dark:!bg-lyx-widget-lighter'
}
}" :options="domains.data.value ?? []" v-model="selectedDomain"></USelectMenu>
<div v-if="selectedDomain" class="flex flex-col gap-2 mt-4">
@@ -140,15 +140,17 @@ const sessionsLabel = computed(() => {
</div>
</template>
<template #delete_data>
<div
class="outline rounded-lg w-full px-8 py-4 flex flex-col gap-4 outline-[1px] outline-[#541c15] 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
visits 0 events 0 sessions)</div>
visits 0 events 0 sessions) </div>
<div @click="openDeleteAllDomainDataDialog()"
class="text-[#e95b61] poppins font-semibold cursor-pointer hover:text-black hover:bg-red-700 outline rounded-lg w-fit px-8 py-2 outline-[1px] outline-[#532b26] bg-[#291415]">
class="text-[#e95b61] poppins font-semibold cursor-pointer hover:text-black hover:bg-red-700 outline rounded-lg w-fit px-8 py-2 outline-[1px] outline-[#532b26] bg-lyx-lightmode-widget-light dark:bg-[#291415]">
Delete all data
</div>
</div>
</template>
</SettingsTemplate>
</template>

View File

@@ -20,10 +20,10 @@ const props = defineProps<SettingsTemplateProp>();
<div v-for="(entry, index) of props.entries" class="flex flex-col">
<div class="flex xl:flex-row flex-col gap-4 xl:gap-0">
<div class="xl:flex-[2]">
<div class="poppins font-medium text-lyx-text">
<div class="poppins font-medium text-lyx-lightmode-text dark:text-lyx-text">
{{ entry.title }}
</div>
<div class="poppins font-regular text-lyx-text-dark whitespace-pre-wrap">
<div class="poppins font-regular text-lyx-lightmode-text-dark dark:text-lyx-text-dark whitespace-pre-wrap">
{{ entry.text }}
</div>
</div>
@@ -31,7 +31,7 @@ const props = defineProps<SettingsTemplateProp>();
<slot :name="entry.id"></slot>
</div>
</div>
<div v-if="index < props.entries.length - 1" class="h-[2px] bg-lyx-widget-lighter w-full my-10"></div>
<div v-if="index < props.entries.length - 1" class="h-[2px] bg-lyx-lightmode-widget dark:bg-lyx-widget-lighter w-full my-10"></div>
</div>
</div>
</template>

View File

@@ -68,7 +68,7 @@ function getPremiumPrice(type: number) {
}
const entries: SettingsTemplateEntry[] = [
{ id: 'plan', title: 'Current plan', text: 'Manage current plat for this project' },
{ id: 'plan', title: 'Current plan', text: 'Manage current plan for this project' },
{ id: 'usage', title: 'Usage', text: 'Show usage of current project' },
{ id: 'info', title: 'Billing address', text: 'This will be reflected in every upcoming invoice,\npast invoices are not affected' },
{ id: 'invoices', title: 'Invoices', text: 'Manage invoices of current project' },
@@ -127,25 +127,25 @@ const { showDrawer } = useDrawer();
<template #info>
<div v-if="!isGuest">
<div class="flex flex-col gap-4">
<LyxUiInput class="px-2 py-2 !bg-[#161616]" placeholder="Address line 1"
<LyxUiInput class="px-2 py-2 dark:!bg-[#161616]" placeholder="Address line 1"
v-model="currentBillingInfo.line1">
</LyxUiInput>
<LyxUiInput class="px-2 py-2 !bg-[#161616]" placeholder="Address line 2"
<LyxUiInput class="px-2 py-2 dark:!bg-[#161616]" placeholder="Address line 2"
v-model="currentBillingInfo.line2">
</LyxUiInput>
<div class="flex gap-4 w-full">
<LyxUiInput class="px-2 py-2 w-full !bg-[#161616]" placeholder="Country"
<LyxUiInput class="px-2 py-2 w-full dark:!bg-[#161616]" placeholder="Country"
v-model="currentBillingInfo.country">
</LyxUiInput>
<LyxUiInput class="px-2 py-2 w-full !bg-[#161616]" placeholder="Postal code"
<LyxUiInput class="px-2 py-2 w-full dark:!bg-[#161616]" placeholder="Postal code"
v-model="currentBillingInfo.postal_code">
</LyxUiInput>
</div>
<div class="flex gap-4 w-full">
<LyxUiInput class="px-2 py-2 w-full !bg-[#161616]" placeholder="City"
<LyxUiInput class="px-2 py-2 w-full dark:!bg-[#161616]" placeholder="City"
v-model="currentBillingInfo.city">
</LyxUiInput>
<LyxUiInput class="px-2 py-2 w-full !bg-[#161616]" placeholder="State"
<LyxUiInput class="px-2 py-2 w-full dark:!bg-[#161616]" placeholder="State"
v-model="currentBillingInfo.state">
</LyxUiInput>
</div>
@@ -175,7 +175,7 @@ const { showDrawer } = useDrawer();
<div class="flex items-center gap-1">
<div class="poppins font-semibold text-[2rem]">
{{ getPremiumPrice(planData.premium_type) }} </div>
<div class="poppins text-text-sub mt-2"> per month </div>
<div class="poppins text-lyx-lightmode-text-dark dark:text-text-sub mt-2"> per month </div>
</div>
</div>
<div class="flex flex-col">
@@ -194,7 +194,7 @@ const { showDrawer } = useDrawer();
<div class="my-4 w-full bg-gray-400/30 h-[1px]">
</div>
<div class="flex justify-between px-8 flex-col lg:flex-row gap-2 lg:gap-0 items-center">
<div class="flex gap-2 text-text-sub text-[.9rem]">
<div class="flex gap-2 text-lyx-lightmode-text-dark dark:text-text-sub text-[.9rem]">
<div class="poppins"> Expire date:</div>
<div> {{ prettyExpireDate }}</div>
</div>
@@ -212,7 +212,7 @@ const { showDrawer } = useDrawer();
<div class="poppins font-semibold text-[1.1rem]">
Usage
</div>
<div class="poppins text-text-sub text-[.9rem]">
<div class="poppins text-lyx-lightmode-text-dark dark:text-text-sub text-[.9rem]">
Check the usage limits of your project.
</div>
</div>
@@ -240,7 +240,7 @@ const { showDrawer } = useDrawer();
<div class="flex flex-col gap-2">
<div class="flex justify-between items-center bg-[#161616] p-4 rounded-lg"
<div class="flex justify-between items-center outline-[1px] outline outline-lyx-lightmode-widget dark:outline-none bg-lyx-lightmode-widget-light dark:bg-[#161616] p-4 rounded-lg"
v-for="invoice of invoices">
<div> <i class="fal fa-file-invoice"></i> </div>

View File

@@ -3,5 +3,5 @@
const app = useRuntimeConfig();
export function useSelfhosted() {
return app.public.SELFHOSTED === 'TRUE';
return app.public.SELFHOSTED.toString() === 'TRUE' || app.public.SELFHOSTED.toString() === 'true';
}

View File

@@ -12,6 +12,8 @@ const modal = useModal();
const selfhosted = useSelfhosted();
console.log({ selfhosted })
const sections: Section[] = [
{
title: '',
@@ -32,6 +34,7 @@ const sections: Section[] = [
action() {
modal.open(DialogFeedback, {});
},
disabled: selfhosted
},
{
label: 'Documentation', to: 'https://docs.litlyx.com', icon: 'fal fa-book', external: true,
@@ -92,11 +95,11 @@ const { isOpen, close, open } = useMenu();
</CVerticalNavigation>
<div class="overflow-hidden w-full bg-lyx-background relative h-full">
<div class="overflow-hidden w-full bg-lyx-lightmode-background dark:bg-lyx-background relative h-full">
<div v-if="showDialog" class="barrier w-full h-full z-[34] absolute bg-black/50 backdrop-blur-[2px]">
<i
class="z-[40] absolute right-12 top-8 fas fa-times text-text-sub text-[1.8rem] lg:text-[3rem]"></i>
class="z-[40] absolute right-12 top-8 fas fa-times text-lyx-lightmode-text-dark dark:text-text-sub text-[1.8rem] lg:text-[3rem]"></i>
</div>
<div @click="closeDialog()" class="w-full h-full z-[35] absolute top-0 left-0 px-4 lg:px-60 py-20"

View File

@@ -45,7 +45,8 @@ export default defineNuxtRouteMiddleware(async (to, from) => {
to.path != '/login' &&
to.path != '/register' &&
to.path != '/live_demo' &&
to.path != '/jwt_login'
to.path != '/jwt_login' &&
to.path != '/forgot_password'
) {
console.log('REDIRECT TO LOGIN', to.path);
return '/login';

View File

@@ -55,8 +55,8 @@ export default defineNuxtConfig({
STRIPE_SECRET_TEST: process.env.STRIPE_SECRET_TEST,
STRIPE_WH_SECRET_TEST: process.env.STRIPE_WH_SECRET_TEST,
NOAUTH_USER_EMAIL: process.env.NOAUTH_USER_EMAIL,
NOAUTH_USER_NAME: process.env.NOAUTH_USER_NAME || 'FALSE',
SELFHOSTED: process.env.SELFHOSTED,
NOAUTH_USER_NAME: process.env.NOAUTH_USER_NAME,
SELFHOSTED: process.env.SELFHOSTED || 'FALSE',
public: {
AUTH_MODE: process.env.AUTH_MODE,
GITHUB_CLIENT_ID: process.env.GITHUB_AUTH_CLIENT_ID || 'NONE',

View File

@@ -39,6 +39,9 @@
"zod": "^3.24.1"
},
"devDependencies": {
"@nuxt/components": "^2.2.1",
"@nuxt/types": "^2.18.1",
"@nuxt/typescript-build": "^3.0.2",
"@nuxt/ui": "^2.15.2",
"@types/jsonwebtoken": "^9.0.6",
"@types/pdfkit": "^0.13.4",

View File

@@ -4,6 +4,7 @@ import VueMarkdown from 'vue-markdown-render';
definePageMeta({ layout: 'dashboard' });
const selfhosted = useSelfhosted();
const debugModeAi = ref<boolean>(false);
@@ -23,7 +24,7 @@ const { data: chatsRemaining, refresh: reloadChatsRemaining } = useFetch(`/api/a
const currentText = ref<string>("");
const loading = ref<boolean>(false);
const canSend = ref<boolean>(false);
const canSend = ref<boolean>(true);
const currentChatId = ref<string>("");
const currentChatMessages = ref<{ role: string, content: string, charts?: any[], tool_calls?: any }[]>([]);
@@ -75,6 +76,8 @@ async function pollSendMessageStatus(chat_id: string, times: number, updateStatu
setTimeout(() => pollSendMessageStatus(chat_id, times + 1, updateStatus), (times > 10 ? 2000 : 1000));
} else {
canSend.value = true;
typer.stop();
const messages = await $fetch(`/api/ai/${chat_id}/get_messages`, {
@@ -84,12 +87,15 @@ async function pollSendMessageStatus(chat_id: string, times: number, updateStatu
currentChatMessages.value = messages.map(e => ({ ...e, charts: e.charts.map(k => JSON.parse(k)) })) as any;
currentChatMessageDelta.value = '';
loading.value = false;
}
}
async function sendMessage() {
if (canSend.value === false) return;
if (loading.value) return;
if (!project.value) return;
@@ -128,25 +134,25 @@ async function sendMessage() {
currentChatMessageDelta.value = status;
});
canSend.value = true;
} catch (ex: any) {
if (ex.message.includes('CHAT_LIMIT_REACHED')) {
currentChatMessages.value.push({
role: 'assistant',
content: 'You have reached your current tier chat limit.\n Upgrade to an higher tier. <a style="color: blue; text-decoration: underline;" href="/plans"> Upgrade now. </a>',
content: `Chat limit reached.
Upgrade your plan to continue chatting.`
});
}
if (ex.message.includes('Unauthorized')) {
} else if (ex.message.includes('Unauthorized')) {
currentChatMessages.value.push({
role: 'assistant',
content: 'To use AI you need to provide AI_ORG, AI_PROJECT and AI_KEY in docker compose',
});
} else {
currentChatMessages.value.push({ role: 'assistant', content: ex.message, });
}
currentChatMessages.value.push({ role: 'assistant', content: ex.message, });
loading.value = false;
canSend.value = true;
@@ -264,7 +270,9 @@ async function clearAllChats() {
</div>
<div class="flex flex-col xl:grid xl:grid-cols-2 gap-4 mt-6">
<div v-for="prompt of defaultPrompts" @click="currentText = prompt"
class="bg-lyx-widget-light hover:bg-lyx-widget-lighter cursor-pointer p-4 rounded-lg poppins text-center whitespace-pre-wrap flex items-center justify-center text-[.9rem]">
class="
bg-lyx-lightmode-widget-light dark:bg-lyx-widget-light hover:bg-lyx-lightmode-widget dark:hover:bg-lyx-widget outline-[1px] outline outline-lyx-lightmode-widget dark:outline-none
cursor-pointer p-4 rounded-lg poppins text-center whitespace-pre-wrap flex items-center justify-center text-[.9rem]">
{{ prompt }}
</div>
</div>
@@ -276,7 +284,7 @@ async function clearAllChats() {
<div class="flex w-full flex-col" v-for="(message, messageIndex) of currentChatMessages">
<div v-if="message.role === 'user'" class="flex justify-end w-full poppins text-[1.1rem]">
<div class="bg-lyx-widget-light px-5 py-3 rounded-lg">
<div class="bg-lyx-lightmode-widget dark:bg-lyx-widget-light px-5 py-3 rounded-lg">
{{ message.content }}
</div>
</div>
@@ -286,7 +294,7 @@ async function clearAllChats() {
<div class="flex items-center justify-center shrink-0">
<img class="h-[3.5rem] w-auto" :src="'analyst.png'">
</div>
<div class="max-w-[70%] text-text/90 ai-message">
<div class="max-w-[70%] text-lyx-lightmode-text dark:text-text/90 ai-message">
<vue-markdown v-if="message.content" :source="message.content" :options="{
html: true,
@@ -360,9 +368,10 @@ async function clearAllChats() {
<div class="flex gap-2 items-center md:absolute fixed bottom-8 left-0 w-full px-10 xl:px-28">
<input @keydown="onKeyDown" v-model="currentText"
class="bg-lyx-widget-light w-full focus:outline-none px-4 py-2 rounded-lg" type="text">
class="bg-lyx-lightmode-widget-light dark:bg-lyx-widget-light w-full dark:focus:outline-none px-4 py-2 rounded-lg outline-[1px] outline outline-lyx-lightmode-widget dark:outline-none"
type="text">
<div @click="sendMessage()"
class="bg-lyx-widget-light hhover:bg-lyx-widget-light cursor-pointer px-4 py-2 rounded-full">
class="bg-lyx-lightmode-widget-light hover:bg-lyx-lightmode-widget dark:bg-lyx-widget-light dark:hover:bg-lyx-widget-light cursor-pointer px-4 py-2 rounded-full">
<i class="far fa-arrow-up"></i>
</div>
<div @click="menuOpen = !menuOpen"
@@ -377,7 +386,8 @@ async function clearAllChats() {
<div :class="{
'absolute top-0 left-0 w-full': menuOpen,
'hidden xl:flex': !menuOpen
}" class="flex-[2] bg-lyx-background-light p-6 flex flex-col gap-4 h-full overflow-hidden">
}"
class="flex-[2] bg-lyx-lightmode-background border-l-[1px] dark:border-l-0 dark:bg-lyx-background-light p-6 flex flex-col gap-4 h-full overflow-hidden">
<div class="gap-2 flex flex-col">
<div class="xl:hidden absolute right-6 top-2 text-[1.5rem]">
@@ -392,10 +402,11 @@ async function clearAllChats() {
<div class="flex items-center gap-2">
<div class="bg-accent w-5 h-5 rounded-full animate-pulse">
</div>
<div class="manrope font-semibold text-text-dirty"> {{ chatsRemaining }} remaining requests
<div class="manrope font-semibold text-lyx-lightmode-text dark:text-text-dirty"> {{
chatsRemaining }} remaining requests
</div>
</div>
<LyxUiButton type="primary" class="text-[.9rem] text-center " @click="showDrawer('PRICING')">
<LyxUiButton v-if="!selfhosted" type="primary" class="text-[.9rem] text-center " @click="showDrawer('PRICING')">
Upgrade
</LyxUiButton>
</div>
@@ -410,7 +421,7 @@ async function clearAllChats() {
<div class="px-2">
<div @click="openChat()"
class="bg-lyx-widget-light cursor-pointer hover:bg-lyx-widget rounded-lg px-4 py-3 poppins flex gap-4 items-center">
class="bg-lyx-lightmode-widget-light hover:bg-lyx-lightmode-widget dark:bg-lyx-widget-light cursor-pointer dark:hover:bg-lyx-widget rounded-lg px-4 py-3 poppins flex gap-4 items-center outline-[1px] outline outline-lyx-lightmode-widget dark:outline-none">
<div> <i class="fas fa-plus"></i> </div>
<div> New chat </div>
</div>
@@ -420,7 +431,7 @@ async function clearAllChats() {
<div class="overflow-y-auto">
<div class="flex flex-col gap-2 px-2">
<div :class="{ '!bg-accent/60': chat._id.toString() === currentChatId }"
class="flex text-lyx-text-dark text-[.9rem] font-light rounded-lg items-center gap-4 w-full px-4 bg-lyx-widget-light hover:bg-lyx-widget"
class="flex text-lyx-lightmode-text-dark dark:text-lyx-text-dark text-[.9rem] font-light rounded-lg items-center gap-4 w-full px-4 bg-lyx-lightmode-widget-light dark:bg-lyx-widget-light hover:bg-lyx-lightmode-widget dark:hover:bg-lyx-widget outline-[1px] outline outline-lyx-lightmode-widget dark:outline-none"
v-for="chat of viewChatsList">
<i @click="deleteChat(chat._id.toString())"
class="far fa-trash hover:text-gray-300 cursor-pointer"></i>
@@ -454,7 +465,6 @@ async function clearAllChats() {
font-weight: bold;
margin-top: 1.5em;
margin-bottom: 0.5em;
color: white;
}
p:last-of-type {

View File

@@ -73,7 +73,7 @@ const showWarning = computed(() => {
const { showDrawer } = useDrawer();
function goToUpgrade() {
showDrawer('PRICING');
showDrawer('PRICING');
}
</script>
@@ -86,7 +86,7 @@ function goToUpgrade() {
<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]">
class="fixed z-[100] flex items-center justify-center left-0 top-0 w-full h-full bg-lyx-lightmode-background-light dark:bg-black/60 backdrop-blur-[4px]">
<div class="poppins text-[2rem]">
Creating csv...
</div>
@@ -101,11 +101,11 @@ function goToUpgrade() {
</div>
<div class="w-[15rem] flex flex-col gap-0">
<USelectMenu :uiMenu="{
select: '!bg-lyx-widget-light !shadow-none focus:!ring-lyx-widget-lighter !ring-lyx-widget-lighter',
base: '!bg-lyx-widget',
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',
option: {
base: 'hover:!bg-lyx-widget-lighter cursor-pointer',
active: '!bg-lyx-widget-lighter'
base: 'hover:!bg-lyx-lightmode-widget-light dark:hover:!bg-lyx-widget-lighter cursor-pointer',
active: '!bg-lyx-lightmode-widget-light dark:!bg-lyx-widget-lighter'
}
}" v-model="selectedTimeFrom" :options="options"></USelectMenu>
</div>
@@ -125,14 +125,14 @@ function goToUpgrade() {
<UTable v-if="tableData" class="utable px-8" :ui="{
wrapper: 'overflow-auto w-full h-full',
thead: 'sticky top-0 bg-menu',
wrapper: 'overflow-auto w-full h-full',
thead: 'sticky top-0 bg-lyx-lightmode-background-light dark:bg-menu',
td: {
color: 'text-[#ffffffb3]',
base: 'border-r border-l border-gray-300/20'
color: 'text-lyx-lightmode-text dark:text-[#ffffffb3]',
base: 'border-r border-l border-lyx-lightmode-widget dark:border-gray-300/20'
},
th: { color: 'text-text-sub' },
tbody: 'divide-y divide-gray-300/20',
th: { color: 'text-lyx-lightmode-text dark:text-text-sub' },
tbody: 'divide-y divide-lyx-lightmode-widget dark:divide-gray-300/20',
divide: '',
}" v-model:sort="sort" :columns="selectedColumns" :rows="tableData" :loading="loadingData" sort-mode="manual">

View File

@@ -91,7 +91,7 @@ function goToUpgrade() {
<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]">
class="fixed z-[100] flex items-center justify-center left-0 top-0 w-full h-full bg-lyx-lightmode-background-light dark:bg-black/60 backdrop-blur-[4px]">
<div class="poppins text-[2rem]">
Creating csv...
</div>
@@ -106,11 +106,11 @@ function goToUpgrade() {
</div>
<div class="w-[15rem] flex flex-col gap-0">
<USelectMenu :uiMenu="{
select: '!bg-lyx-widget-light !shadow-none focus:!ring-lyx-widget-lighter !ring-lyx-widget-lighter',
base: '!bg-lyx-widget',
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',
option: {
base: 'hover:!bg-lyx-widget-lighter cursor-pointer',
active: '!bg-lyx-widget-lighter'
base: 'hover:!bg-lyx-lightmode-widget-light dark:hover:!bg-lyx-widget-lighter cursor-pointer',
active: '!bg-lyx-lightmode-widget-light dark:!bg-lyx-widget-lighter'
}
}" v-model="selectedTimeFrom" :options="options"></USelectMenu>
</div>
@@ -131,13 +131,13 @@ function goToUpgrade() {
<UTable v-if="tableData" class="utable px-8" :ui="{
wrapper: 'overflow-auto w-full h-full',
thead: 'sticky top-0 bg-menu',
thead: 'sticky top-0 bg-lyx-lightmode-background-light dark:bg-menu',
td: {
color: 'text-[#ffffffb3]',
base: 'border-r border-l border-gray-300/20'
color: 'text-lyx-lightmode-text dark:text-[#ffffffb3]',
base: 'border-r border-l border-lyx-lightmode-widget dark:border-gray-300/20'
},
th: { color: 'text-text-sub' },
tbody: 'divide-y divide-gray-300/20',
th: { color: 'text-lyx-lightmode-text dark:text-text-sub' },
tbody: 'divide-y divide-lyx-lightmode-widget dark:divide-gray-300/20',
divide: '',
}" v-model:sort="sort" :columns="selectedColumns" :rows="tableData" :loading="loadingData" sort-mode="manual">

View File

@@ -0,0 +1,150 @@
<script setup lang="ts">
definePageMeta({ layout: 'none' });
const email = ref<string>("");
const emailSended = ref<boolean>(false);
const { createAlert } = useAlert();
async function resetPassword() {
try {
const res = await $fetch('/api/user/password/reset', {
headers: { 'Content-Type': 'application/json' },
method: 'POST',
body: JSON.stringify({ email: email.value })
})
if (!res) throw Error('No response');
if (res.error) return createAlert('Error', res.message, 'far fa-triangle-exclamation', 5000);
emailSended.value = true;
return createAlert('Success', 'Email sent', 'far fa-circle-check', 5000);
} catch (ex) {
console.error(ex);
createAlert('Error', 'Internal error', 'far fa-triangle-exclamation', 5000);
}
}
</script>
<template>
<div class="home w-full h-full">
<div class="flex h-full">
<div class="flex-1 flex flex-col items-center pt-20 xl:pt-[22vh]">
<div class="rotating-thing absolute top-0"></div>
<div class="mb-8 bg-black rounded-xl">
<img class="w-[5rem]" :src="'logo.png'">
</div>
<div class="text-lyx-lightmode-text dark:text-lyx-text text-[2.2rem] font-bold poppins">
Reset password
</div>
<div class="text-lyx-lightmode-text dark:text-lyx-text/80 text-[1.2rem] font-light text-center w-[70%] poppins mt-2">
Enter your user account's verified email address and we will send you a temporary password.
</div>
<div class="mt-12">
<div v-if="!emailSended" class="flex flex-col gap-2 z-[110]">
<div class="flex flex-col gap-4 z-[100] w-[20vw] min-w-[20rem]">
<LyxUiInput class="px-3 py-2" placeholder="Email" v-model="email"></LyxUiInput>
</div>
<div class="flex justify-center mt-4">
<LyxUiButton @click="resetPassword()" class="text-center z-[110]" type="primary">
Reset password
</LyxUiButton>
</div>
<NuxtLink to="/login"
class="mt-4 text-center text-lyx-lightmode-text dark:text-lyx-text-dark underline cursor-pointer z-[110]">
Go back
</NuxtLink>
</div>
<div v-if="emailSended" class="mt-12 flex flex-col text-center text-[1.1rem] z-[100]">
<div>
Check your email inbox.
</div>
<RouterLink tag="div" to="/login"
class="mt-6 text-center text-lyx-lightmode-text dark:text-lyx-text-dark underline cursor-pointer z-[110]">
Go back
</RouterLink>
</div>
</div>
</div>
<div class="grow flex-1 items-center justify-center hidden lg:flex">
<!-- <GlobeSvg></GlobeSvg> -->
<img :src="'image-bg.png'" class="h-full py-6">
</div>
</div>
<!-- <div class="flex flex-col items-center justify-center mt-40 gap-20">
<div class="google-login text-gray-700" :class="{ disabled: !isReady }" @click="login">
<div class="icon">
<i class="fab fa-google"></i>
</div>
<div> Continua con Google </div>
</div>
</div> -->
</div>
</template>
<style scoped lang="scss">
.rotating-thing {
height: 100%;
aspect-ratio: 1 / 1;
opacity: 0.15;
background: radial-gradient(51.24% 31.29% at 50% 50%, rgb(51, 58, 232) 0%, rgba(51, 58, 232, 0) 100%);
animation: 12s linear 0s infinite normal none running spin;
}
.google-login {
cursor: pointer;
font-weight: bold;
background-color: #fcefed;
padding: 1rem 2rem;
border-radius: 1rem;
display: flex;
gap: 1rem;
align-items: center;
&.disabled {
filter: brightness(50%);
}
i {
font-size: 1.5rem;
}
}
</style>

View File

@@ -154,6 +154,7 @@ async function signInWithCredentials() {
}
}
</script>
@@ -161,21 +162,21 @@ async function signInWithCredentials() {
<div class="home w-full h-full">
<div class="flex h-full">
<div class="flex h-full bg-lyx-lightmode-background dark:bg-lyx-background">
<div class="flex-1 flex flex-col items-center pt-20 xl:pt-[22vh]">
<div class="rotating-thing absolute top-0"></div>
<!-- <div class="rotating-thing absolute top-0"></div> -->
<div class="mb-8 bg-black rounded-xl">
<img class="w-[5rem]" :src="'logo.png'">
</div>
<div class="text-text text-[2.2rem] font-bold poppins">
<div class="text-lyx-lightmode-text dark:text-lyx-text text-[2.2rem] font-bold poppins">
Sign in
</div>
<div class="text-text/80 text-[1.2rem] font-light text-center w-[70%] poppins mt-2">
<div class="text-lyx-lightmode-text/80 dark:text-lyx-text/80 text-[1.2rem] font-light text-center w-[70%] poppins mt-2">
Track web analytics and custom events
with extreme simplicity in under 30 sec.
<br>
@@ -195,6 +196,13 @@ async function signInWithCredentials() {
</LyxUiInput>
</div>
<div class="flex justify-end">
<RouterLink tag="div" to="/forgot_password"
class="text-center text-lyx-lightmode-text dark:text-lyx-text-dark underline cursor-pointer z-[110]">
Forgot password?
</RouterLink>
</div>
<div class="flex justify-center mt-4 z-[100]">
<LyxUiButton @click="signInWithCredentials()" class="text-center" type="primary">
Sign in
@@ -202,17 +210,17 @@ async function signInWithCredentials() {
</div>
<div @click="goBackToEmailLogin()"
class="mt-4 text-center text-lyx-text-dark underline cursor-pointer z-[100]">
class="mt-4 text-center text-lyx-lightmode-text dark:text-lyx-text-dark underline cursor-pointer z-[100]">
Go back
</div>
</div>
<div v-if="!isNoAuth && !isEmailLogin" class="flex flex-col gap-2">
<div v-if="!isNoAuth && !isEmailLogin" class="flex flex-col text-lyx-lightmode-text dark:text-lyx-text gap-2">
<div @click="login"
class="hover:bg-lyx-primary cursor-pointer flex text-[1.3rem] gap-4 items-center border-[1px] border-gray-400 rounded-lg px-8 py-3 relative z-[2]">
class="hover:bg-lyx-primary bg-white dark:bg-transparent cursor-pointer flex text-[1.3rem] gap-4 items-center border-[1px] border-gray-400 rounded-lg px-8 py-3 relative z-[2]">
<div class="flex items-center">
<i class="fab fa-google"></i>
</div>
@@ -220,7 +228,7 @@ async function signInWithCredentials() {
</div>
<div @click="isEmailLogin = true"
class="hover:bg-[#262626] cursor-pointer flex text-[1.3rem] gap-4 items-center border-[1px] border-gray-400 rounded-lg px-8 py-3 relative z-[2]">
class="hover:bg-[#d3d3d3] dark:hover:bg-[#262626] bg-white dark:bg-transparent cursor-pointer flex text-[1.3rem] gap-4 items-center border-[1px] border-gray-400 rounded-lg px-8 py-3 relative z-[2]">
<div class="flex items-center">
<i class="far fa-envelope"></i>
</div>
@@ -228,10 +236,14 @@ async function signInWithCredentials() {
</div>
<RouterLink tag="div" to="/register"
class="mt-4 text-center text-lyx-text-dark underline cursor-pointer z-[100]">
You don't have an account ? Sign up
</RouterLink>
<div class="flex flex-col gap-2 mt-4">
<RouterLink tag="div" to="/register"
class="text-center text-lyx-lightmode-text-dark dark:text-lyx-text-dark underline cursor-pointer z-[100]">
You don't have an account ? Sign up
</RouterLink>
</div>
</div>
@@ -245,7 +257,7 @@ async function signInWithCredentials() {
</div>
<div class="text-[.9rem] poppins mt-20 text-text-sub text-center relative z-[2]">
<div class="text-[.9rem] poppins mt-20 text-lyx-lightmode-text-dark dark:text-lyx-text-dark text-center relative z-[2]">
By continuing you are accepting
<br>
our

View File

@@ -56,6 +56,7 @@ async function createProject() {
}
</script>
@@ -69,7 +70,7 @@ async function createProject() {
<div class="flex flex-col items-center justify-center pt-[12rem] gap-12 relative z-[10]">
<div class="text-[3rem] font-semibold text-center">
<div class="text-[3rem] font-semibold text-center text-lyx-lightmode-text dark:text-lyx-text">
Create your {{ isFirstProject ? 'first' : '' }} project
</div>
@@ -78,15 +79,20 @@ async function createProject() {
</div>
<div class="w-[20rem] flex flex-col gap-2">
<div class="text-lg text-text-sub font-semibold">
<div class="text-lg text-lyx-lightmode-text-dark dark:text-text-sub font-semibold">
{{ isFirstProject ? 'Choose a name' : 'Project name' }}
</div>
<CInput placeholder="ProjectName" :readonly="creating" v-model="projectName"></CInput>
<!-- <CInput placeholder="ProjectName" :readonly="creating" v-model="projectName"></CInput> -->
<LyxUiInput class="py-2 px-2" placeholder="Insert" :readonly="creating" v-model="projectName">
</LyxUiInput>
</div>
<div>
<CButton :loading="creating" @click="createProject()" :disabled="projectName.length < 2"
class="rounded-lg w-[10rem] text-md font-semibold" label="Create"></CButton>
<LyxUiButton type="primary" @click="createProject()" :disabled="projectName.length < 2">
Create
</LyxUiButton>
</div>
</div>

View File

@@ -47,21 +47,21 @@ async function registerAccount() {
<div class="home w-full h-full">
<div class="flex h-full">
<div class="flex h-full bg-lyx-lightmode-background dark:bg-lyx-background">
<div class="flex-1 flex flex-col items-center pt-20 xl:pt-[22vh]">
<div class="rotating-thing absolute top-0"></div>
<!-- <div class="rotating-thing absolute top-0"></div> -->
<div class="mb-8 bg-black rounded-xl">
<img class="w-[5rem]" :src="'logo.png'">
</div>
<div class="text-text text-[2.2rem] font-bold poppins">
<div class="text-lyx-lightmode-text dark:text-lyx-text text-[2.2rem] font-bold poppins">
Sign up
</div>
<div class="text-text/80 text-[1.2rem] font-light text-center w-[70%] poppins mt-2">
<div class="text-lyx-lightmode-text dark:text-lyx-text/80 text-[1.2rem] font-light text-center w-[70%] poppins mt-2">
Track web analytics and custom events
with extreme simplicity in under 30 sec.
<br>
@@ -95,7 +95,7 @@ async function registerAccount() {
</div>
<RouterLink to="/login"
class="mt-4 text-center text-lyx-text-dark underline cursor-pointer z-[100]">
class="mt-4 text-center text-lyx-lightmode-text dark:text-lyx-text-dark underline cursor-pointer z-[100]">
Go back to login
</RouterLink>
@@ -112,13 +112,13 @@ async function registerAccount() {
Please check your inbox to confirm your account and complete your registration.
</div>
<RouterLink tag="div" to="/login"
class="mt-6 text-center text-lyx-text-dark underline cursor-pointer">
class="mt-6 text-center text-lyx-lightmode-text dark:text-lyx-text-dark underline cursor-pointer">
Go back
</RouterLink>
</div>
<div v-if="!emailSended"
class="text-[.9rem] poppins mt-5 xl:mt-20 text-text-sub text-center relative z-[2]">
class="text-[.9rem] poppins mt-5 xl:mt-20 text-lyx-lightmode-text dark:text-lyx-text-dark text-center relative z-[2]">
By continuing you are accepting
<br>
our

View File

@@ -33,7 +33,7 @@ const columns = [
<div class="home w-full h-full px-10 pt-6 overflow-y-auto">
<div class="flex gap-2 items-center text-text/90 justify-end">
<div class="flex gap-2 items-center text-lyx-lightmode-text dark:text-text/90 justify-end">
<div class="animate-pulse w-[1rem] h-[1rem] bg-green-400 rounded-full"> </div>
<div class="poppins font-regular text-[1rem]"> AI Anomaly Detector </div>
<div class="flex items-center">
@@ -47,7 +47,7 @@ const columns = [
<template #scan-data="{ row }">
<div class="text-lyx-text-dark">
<div class="text-lyx-lightmode-text dark:text-lyx-text-dark">
{{ new Date(row.data.created_at).toLocaleString() }}
</div>
</template>
@@ -59,7 +59,7 @@ const columns = [
</template>
<template #data-data="{ row }">
<div class="text-lyx-text-dark">
<div class="text-lyx-lightmode-text dark:text-lyx-text-dark">
<div v-if="row.type === 'domain'">
{{ row.data.domain }}
</div>

View File

@@ -2,6 +2,7 @@
definePageMeta({ layout: 'dashboard' });
const selfhosted = useSelfhosted();
const items = [
{ label: 'General', slot: 'general' },
@@ -16,7 +17,7 @@ const items = [
<template>
<div class="lg:px-10 lg:py-8 h-dvh overflow-y-auto overflow-x-hidden hide-scrollbars">
<div class="poppins font-semibold text-[1.3rem] lg:px-0 px-4 lg:py-0 py-4"> Settings </div>
<CustomTab :items="items" class="mt-8">
@@ -30,10 +31,18 @@ const items = [
<SettingsMembers :key="refreshKey"></SettingsMembers>
</template>
<template #billing>
<SettingsBilling :key="refreshKey"></SettingsBilling>
<SettingsBilling v-if="!selfhosted" :key="refreshKey"></SettingsBilling>
<div class="flex popping text-[1.2rem] font-semibold justify-center mt-[20vh] text-lyx-lightmode-text dark:text-lyx-text"
v-if="selfhosted">
Billing disabled in self-host mode
</div>
</template>
<template #codes>
<SettingsCodes :key="refreshKey"></SettingsCodes>
<SettingsCodes v-if="!selfhosted" :key="refreshKey"></SettingsCodes>
<div class="flex popping text-[1.2rem] font-semibold justify-center mt-[20vh] text-lyx-lightmode-text dark:text-lyx-text"
v-if="selfhosted">
Codes disabled in self-host mode
</div>
</template>
<template #account>
<SettingsAccount :key="refreshKey"></SettingsAccount>

View File

@@ -1,11 +1,17 @@
import { OnboardingModel } from '@schema/OnboardingSchema';
const { SELFHOSTED } = useRuntimeConfig();
export default defineEventHandler(async event => {
const data = await getRequestData(event);
if (!data) return;
const exist = await OnboardingModel.exists({ user_id: data.user.id });
if (SELFHOSTED.toString() === 'TRUE' || SELFHOSTED.toString() === 'true') {
return { exists: true }
}
return { exist: exist != null }
});

View File

@@ -66,7 +66,7 @@ export default defineEventHandler(async event => {
const { project, project_id, user } = data;
if (SELFHOSTED !== 'TRUE') {
if (SELFHOSTED.toString() !== 'TRUE' && SELFHOSTED.toString() !== 'true') {
const PREMIUM_TYPE = project.premium_type;
if (PREMIUM_TYPE === 0) return setResponseStatus(event, 400, 'Project not premium');
}

View File

@@ -0,0 +1,10 @@
import { PasswordModel } from "@schema/PasswordSchema";
export default defineEventHandler(async event => {
const userData = getRequestUser(event);
if (!userData?.logged) return;
const hasPassword = await PasswordModel.exists({ email: userData.user.email });
if (hasPassword) return { can_change: true };
return { can_change: false }
});

View File

@@ -0,0 +1,33 @@
import crypto from 'crypto';
import { PasswordModel } from '@schema/PasswordSchema';
export default defineEventHandler(async event => {
const userData = getRequestUser(event);
if (!userData?.logged) return;
const { old_password, new_password } = await readBody(event);
if (new_password.length < 6) return { error: true, message: 'Password too short' }
const target = await PasswordModel.findOne({ email: userData.user.email });
if (!target) return { error: true, message: 'Internal error. User not found.' }
const hashOld = crypto.createHash('sha256');
const hashedPasswordOld = hashOld.update(old_password + '_litlyx').digest('hex');
if (target.password !== hashedPasswordOld) {
return { error: true, message: 'Old password not correct' }
}
const hashNew = crypto.createHash('sha256');
const hashedPasswordNew = hashNew.update(new_password + '_litlyx').digest('hex');
target.password = hashedPasswordNew;
await target.save();
return { error: false, message: 'Success' }
});

View File

@@ -0,0 +1,26 @@
import crypto from 'crypto';
import { PasswordModel } from '@schema/PasswordSchema';
import EmailService from '@services/EmailService'
export default defineEventHandler(async event => {
const { email } = await readBody(event);
const target = await PasswordModel.findOne({ email });
if (!target) return { error: true, message: 'Internal error. User not found.' }
const newPass = crypto.randomBytes(5).toString('hex');
const hash = crypto.createHash('sha256');
const hashedPassword = hash.update(newPass + '_litlyx').digest('hex');
target.password = hashedPassword;
await target.save();
await EmailService.sendResetPasswordEmail(email, newPass);
return { error: false, message: 'Password changed' }
});

View File

@@ -42,6 +42,12 @@ export default async () => {
logger.info('[SERVER] Completed');
logger.warn('[ANOMALY LOOP] Disabled');
logger.warn(`[SELFHOSTED_SERVER] ${config.SELFHOSTED}`);
logger.warn(`[SELFHOSTED_CLIENT] ${config.public.SELFHOSTED}`);
logger.warn(`[AUTH] ${config.public.AUTH_MODE}`);
// anomalyLoop();
};

View File

@@ -34,16 +34,31 @@ module.exports = {
dark: '#222A42',
hover: '#2A3450'
},
"lyx-lightmode-text": {
DEFAULT: '#0A0A0A',
dark: '#121212',
darker: '#212121',
},
"lyx-text": {
DEFAULT: '#FFFFFF',
dark: '#D4D4D4',
darker: '#6A6A6A'
darker: '#6A6A6A',
},
"lyx-lightmode-widget": {
DEFAULT: '#D9D9E0',
light: '#EEEDEF',
lighter: '#FF0000'
},
"lyx-widget": {
DEFAULT: '#0E0E0E',
light: '#1E1E1E',
lighter: '#262626'
},
"lyx-lightmode-background": {
DEFAULT: '#F2F2F2',
light: '#D4D4D4',
lighter: '#6A6A6A'
},
"lyx-background": {
DEFAULT: '#0A0A0A',
light: '#121212',

View File

@@ -22,7 +22,7 @@ services:
command: redis-server --save 20 1 --loglevel warning --requirepass litlyx
producer:
image: litlyx-producer
image: litlyx/litlyx-producer
restart: always
ports:
- "3099:3099"
@@ -37,7 +37,7 @@ services:
dockerfile: ./producer/Dockerfile
consumer:
image: litlyx-consumer
image: litlyx/litlyx-consumer
restart: always
ports:
- "3011:3031"
@@ -58,7 +58,7 @@ services:
dockerfile: ./consumer/Dockerfile
dashboard:
image: litlyx-dashboard
image: litlyx/litlyx-dashboard
restart: always
ports:
- "3000:3000"
@@ -95,6 +95,7 @@ services:
NUXT_NOAUTH_USER_NAME: "defaultuser"
NUXT_SELFHOSTED: 'true'
NUXT_PUBLIC_SELFHOSTED: 'true'
# Optional - Used for tests
# NUXT_STRIPE_SECRET_TEST: ""
@@ -111,4 +112,4 @@ services:
# GOOGLE_AUTH_CLIENT_ID: ""
volumes:
mongo-data:
mongo-data:

View File

@@ -13,8 +13,5 @@
"dayjs": "^1.11.13",
"mongoose": "^8.3.2",
"redis": "^4.7.0"
},
"devDependencies": {
"globs": "^0.1.4"
}
}

891
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,7 @@
"dev": "node scripts/start_dev.js",
"compile": "tsc",
"build_project": "node ../scripts/build.js",
"build": "npm run compile && npm run build"
"build": "npm run compile && npm run build_project"
},
"keywords": [],
"author": "Emily",

View File

@@ -2,9 +2,11 @@ import DateService, { Slice } from '@services/DateService';
import { Model, Types } from "mongoose";
export async function getAggregation(model: Model<any>, pid: Types.ObjectId, from: number, to: number, slice: Slice) {
export async function getDayAggregation(model: Model<any>, pid: Types.ObjectId, from: number, to: number) {
const { group, sort, fromParts } = DateService.getQueryDateRange(slice);
const sort = { created_at: 1 };
const group = { $dateToString: { format: "%Y-%m-%d", date: "$created_at" } };
const fromParts = { year: { $year: "$created_at" }, month: { $month: "$created_at" }, day: { $dayOfMonth: "$created_at" } };
const result = model.aggregate([
{
@@ -16,7 +18,7 @@ export async function getAggregation(model: Model<any>, pid: Types.ObjectId, fro
{ $group: { _id: group, count: { $sum: 1 } } },
{ $sort: sort },
{ $project: { _id: { $dateFromParts: fromParts }, count: "$count" } }
]);
] as any);
return result;

View File

@@ -7,7 +7,7 @@ import { VisitModel } from '@schema/metrics/VisitSchema'
import * as url from 'url';
import { ProjectModel } from "@schema/project/ProjectSchema";
import { getAggregation } from "./Aggregations";
import { getDayAggregation } from "./Aggregations";
type TAvgInput = { _id: string, count: number }
@@ -75,9 +75,9 @@ export async function findAnomalies(project_id: string, callback: AnomalyCallbac
const from = Date.now() - 1000 * 60 * 60 * 24 * 30;
const to = Date.now() - 1000 * 60 * 60 * 24;
const visitsTimelineData = await getAggregation(VisitModel, pid, from, to, 'day');
const visitsTimelineData = await getDayAggregation(VisitModel, pid, from, to);
const eventsTimelineData = await getAggregation(EventModel, pid, from, to, 'day');
const eventsTimelineData = await getDayAggregation(EventModel, pid, from, to);
const websites: { _id: string, count: number }[] = await VisitModel.aggregate([

View File

@@ -7,6 +7,7 @@ import { PURCHASE_EMAIL } from './email_templates/PurchaseEmail';
import { ANOMALY_VISITS_EVENTS_EMAIL } from './email_templates/AnomalyUsageEmail';
import { ANOMALY_DOMAIN_EMAIL } from './email_templates/AnomalyDomainEmail';
import { CONFIRM_EMAIL } from './email_templates/ConfirmEmail';
import { RESET_PASSWORD_EMAIL } from './email_templates/ResetPasswordEmail';
class EmailService {
@@ -19,7 +20,7 @@ class EmailService {
async sendLimitEmail50(target: string, projectName: string) {
try {
const sendSmtpEmail = new SendSmtpEmail();
sendSmtpEmail.subject = "You've reached 50% limit on Litlyx";
sendSmtpEmail.subject = "You've reached 50% limit on Litlyx";
sendSmtpEmail.sender = { "name": "Litlyx", "email": "help@litlyx.com" };
sendSmtpEmail.to = [{ "email": target }];
@@ -38,7 +39,7 @@ class EmailService {
async sendLimitEmail90(target: string, projectName: string) {
try {
const sendSmtpEmail = new SendSmtpEmail();
sendSmtpEmail.subject = "You've reached 90% limit on Litlyx";
sendSmtpEmail.subject = "You've reached 90% limit on Litlyx";
sendSmtpEmail.sender = { "name": "Litlyx", "email": "help@litlyx.com" };
sendSmtpEmail.to = [{ "email": target }];
sendSmtpEmail.htmlContent = LIMIT_90_EMAIL
@@ -55,7 +56,7 @@ class EmailService {
async sendLimitEmailMax(target: string, projectName: string) {
try {
const sendSmtpEmail = new SendSmtpEmail();
sendSmtpEmail.subject = "You've reached your limit on Litlyx!";
sendSmtpEmail.subject = "🚨 You've reached your limit on Litlyx!";
sendSmtpEmail.sender = { "name": "Litlyx", "email": "help@litlyx.com" };
sendSmtpEmail.to = [{ "email": target }];
sendSmtpEmail.htmlContent = LIMIT_MAX_EMAIL
@@ -108,17 +109,17 @@ class EmailService {
}) {
try {
const sendSmtpEmail = new SendSmtpEmail();
sendSmtpEmail.subject = "🚨 Unexpected Activity Detected by our AI";
sendSmtpEmail.subject = "🔍 Unexpected Activity Detected by our AI";
sendSmtpEmail.sender = { "name": "Litlyx", "email": "help@litlyx.com" };
sendSmtpEmail.to = [{ "email": target }];
sendSmtpEmail.htmlContent = ANOMALY_VISITS_EVENTS_EMAIL
.replace(/\[Project Name\]/, projectName)
.replace(/\[ENTRIES\]/,
[
...data.visits.map(e => (`<li> Visits in date ${e._id} [ ${e.count} ] </li>`)),
...data.events.map(e => (`<li> Events in date ${e._id} [ ${e.count} ] </li>`))
...data.visits.map(e => (`<li> Visits in date ${new Date(e._id).toLocaleDateString('en-EN')} [ ${e.count} ] </li>`)),
...data.events.map(e => (`<li> Events in date ${new Date(e._id).toLocaleDateString('en-EN')} [ ${e.count} ] </li>`))
]
.join('<br>')
.join('')
)
.toString();
await this.apiInstance.sendTransacEmail(sendSmtpEmail);
@@ -132,15 +133,15 @@ class EmailService {
async sendAnomalyDomainEmail(target: string, projectName: string, domains: string[]) {
try {
const sendSmtpEmail = new SendSmtpEmail();
sendSmtpEmail.subject = "🚨 Anomaly detected by our AI";
sendSmtpEmail.subject = "🔍 Suspicious dns detected by our AI";
sendSmtpEmail.sender = { "name": "Litlyx", "email": "help@litlyx.com" };
sendSmtpEmail.to = [{ "email": target }];
sendSmtpEmail.htmlContent = ANOMALY_DOMAIN_EMAIL
.replace(/\[Project Name\]/, projectName)
.replace(/\[CURRENT_DATE\]/, new Date().toLocaleDateString('en-EN'))
.replace(/\[DNS_ENTRIES\]/,
domains.map(e => (`<li> ${e} </li>`)).join('<br>')
)
// .replace(/\[DNS_ENTRIES\]/,
// domains.map(e => (`<li> ${e} </li>`)).join('<br>')
.replace(/\[DNS_ENTRIES\]/, domains[0])
.toString();
await this.apiInstance.sendTransacEmail(sendSmtpEmail);
return true;
@@ -168,6 +169,24 @@ class EmailService {
}
}
async sendResetPasswordEmail(target: string, newPassword: string) {
try {
const sendSmtpEmail = new SendSmtpEmail();
sendSmtpEmail.subject = "Password reset";
sendSmtpEmail.sender = { "name": "Litlyx", "email": "no-reply@litlyx.com" };
sendSmtpEmail.to = [{ "email": target }];
sendSmtpEmail.htmlContent = RESET_PASSWORD_EMAIL
.replace(/\[NEW_PASSWORD\]/, newPassword)
.toString();
await this.apiInstance.sendTransacEmail(sendSmtpEmail);
return true;
} catch (ex) {
console.error('ERROR SENDING EMAIL', ex);
return false;
}
}
}
const instance = new EmailService();

View File

@@ -1,55 +1,159 @@
export const ANOMALY_DOMAIN_EMAIL = `<!DOCTYPE html>
<html lang="en">
export const ANOMALY_DOMAIN_EMAIL = `<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html dir="ltr" lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>❗️ Anomaly detected by our AI</title>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
<meta name="x-apple-disable-message-reformatting" />
<!--$-->
</head>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
<!-- Email Content -->
<body
style='background-color:#fff;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif'>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation"
style="max-width:37.5em">
<tbody>
<tr style="width:100%">
<td>
<p>Dear User,</p>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation"
style="border:1px solid rgb(0,0,0, 0.1);border-radius:3px;overflow:hidden">
<tbody>
<tr>
<td>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0"
role="presentation">
<tbody style="width:100%">
<tr style="width:100%">
<img src="https://litlyx.com/images/locker2.png"
style="display:block;outline:none;border:none;text-decoration:none;max-width:100%"
width="620" />
</tr>
</tbody>
</table>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0"
role="presentation" style="padding:20px;padding-bottom:0">
<tbody style="width:100%">
<tr style="width:100%">
<td data-id="__react-email-column">
<h1 style="font-size:32px;font-weight:bold;text-align:center">
Dear user
</h1>
<h2 style="font-size:26px;font-weight:bold;text-align:center">
Our AI Agent noticed a recent Anomaly on your project on Litlyx.
</h2>
<p style="font-size:16px;line-height:24px;margin:16px 0">
<b>Time: </b> [CURRENT_DATE]
<!-- September 7, 2022 at 10:58 AM -->
</p>
<p
style="font-size:16px;line-height:24px;margin:16px 0;margin-top:-5px">
<b>Project: </b> [Project Name]
</p>
<p
style="font-size:16px;line-height:24px;margin:16px 0;margin-top:-5px">
<b>Suspicious DNS: </b> [DNS_ENTRIES]
</p>
<p style="font-size:16px;line-height:24px;margin:16px 0">
If this was you, there&#x27;s nothing else you
need to do.
</p>
<p>We wanted to let you know that <strong>[Project Name]</strong> on <strong>Litlyx</strong> has an anomaly that our AI agent detected.</p>
</td>
</tr>
</tbody>
</table>
<p> <strong>Anomaly</strong>: Suspicious DNS </p>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0"
role="presentation" style="padding:20px;padding-top:0">
<tbody style="width:100%">
<tr style="width:100%">
<td colspan="2" data-id="__react-email-column"
style="display:flex;justify-content:center;width:100%">
<a style="line-height:100%;text-decoration:none;display:inline-block;max-width:100%;mso-padding-alt:0px;background-color:#5680f8;border-radius:3px;color:#FFF;font-weight:bold;border:1px solid rgb(0,0,0, 0.1);cursor:pointer;padding:12px 30px 12px 30px"
target="_blank" href="https://dashboard.litlyx.com"><span></span><span
style="max-width:100%;display:inline-block;line-height:120%;mso-padding-alt:0px;mso-text-raise:9px">
Go to Dashboard</span><span></span></a>
</td>
</tr>
</tbody>
</table>
<p> Message: </p>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0"
role="presentation" style="padding:20px;padding-bottom:0">
<tbody style="width:100%">
<tr style="width:100%">
<td data-id="__react-email-column">
<h3>If this wasn't you..</h3>
<ul>
[DNS_ENTRIES]
</ul>
<p>you should reach out to the webmasters of
the websites that have duplicated your content and request them
to remove it or give you proper attribution (if available).</p>
<p> Date: [CURRENT_DATE] </p>
<p>You can also use <a href="https://www.whois.com/whois/"
style="color: #D32F2F; text-decoration: none;">https://www.whois.com/whois/</a>
to get the contact details of the webmaster or domain owner.</p>
<p> Are logging data in your project. Is that you? </p>
<p>If webmasters don't respond or cooperate, <strong>you can file a
DMCA complaint here:</strong> <a
href="https://support.google.com/legal/answer/3110420?hl=en"
style="color: #D32F2F; text-decoration: none;">https://support.google.com/legal/answer/3110420?hl=en</a>
<strong>with Google to request the removal of the duplicate
content from their search results.</strong></p>
<p>You can analyze a suspicious DNS on your Litlyx dashboard. Visit the Security tab to find out more.</p>
<h3>Please refer to this for more information:</h3>
<h3>What can I do?</h3>
<ul>
<li><a href="https://support.google.com/legal/answer/3110420?hl=en&sjid=14235884554806745995-AP&authuser=2"
style="color: #D32F2F; text-decoration: none;">Report
Content for Legal Reasons</a></li>
<li><a href="https://www.dmca.com/FAQ/How-can-I-get-a-webpage-removed-from-Google-search-results"
style="color: #D32F2F; text-decoration: none;">How can I
get a webpage removed from Google search results?</a>
</li>
</ul>
<p>To resolve this issue, you should reach out to the webmasters of the websites that have duplicated your content and request them to remove it or give you proper attribution (if available).</p>
<p>Your safety is our main priority.</p>
<p>You can also use <a href="https://www.whois.com/whois/" style="color: #D32F2F; text-decoration: none;">https://www.whois.com/whois/</a> to get the contact details of the webmaster or domain owner.</p>
<p>Thank you for choosing Litlyx every day as your analytics tool!
</p>
<p>If webmasters don't respond or cooperate, <strong>you can file a DMCA complaint here:</strong> <a href="https://support.google.com/legal/answer/3110420?hl=en" style="color: #D32F2F; text-decoration: none;">https://support.google.com/legal/answer/3110420?hl=en</a> <strong>with Google to request the removal of the duplicate content from their search results.</strong></p>
<p>Antonio,</p>
<p>CEO | Litlyx</p>
</td>
</tr>
</tbody>
</table>
<h3>Please refer to this for more information:</h3>
<ul>
<li><a href="https://support.google.com/legal/answer/3110420?hl=en&sjid=14235884554806745995-AP&authuser=2" style="color: #D32F2F; text-decoration: none;">Report Content for Legal Reasons</a></li>
<li><a href="https://www.dmca.com/FAQ/How-can-I-get-a-webpage-removed-from-Google-search-results" style="color: #D32F2F; text-decoration: none;">How can I get a webpage removed from Google search results?</a></li>
</ul>
<p>Your safety is our main priority.</p>
<p>Thank you for choosing Litlyx every day as your analytics tool!</p>
<p>Antonio,</p>
<p>CEO | Litlyx</p>
</td>
</tr>
</tbody>
</table>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation"
style="padding:45px 0 0 0">
<tbody>
<tr>
<td>
<img src="https://react-email-demo-lpdmf0ryo-resend.vercel.app/static/yelp-footer.png"
style="display:block;outline:none;border:none;text-decoration:none;max-width:100%"
width="620" />
</td>
</tr>
</tbody>
</table>
<p style="font-size:12px;line-height:24px;margin:16px 0;text-align:center;color:rgb(0,0,0, 0.7)">
2024 © Litlyx. All rights reserved.
<br>
Litlyx S.R.L. - Viale Tirreno, 187 - 00141 Rome - P.IVA: 17814721001- REA: RM-1743194
</p>
</td>
</tr>
</tbody>
</table>
</body>
</html>
`

View File

@@ -1,50 +1,152 @@
export const ANOMALY_VISITS_EVENTS_EMAIL = `<!DOCTYPE html>
<html lang="en">
export const ANOMALY_VISITS_EVENTS_EMAIL = `<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html dir="ltr" lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🚨 Unexpected Activity Detected by our AI</title>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
<meta name="x-apple-disable-message-reformatting" />
<!--$-->
</head>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
<!-- Email Content -->
<body
style='background-color:#fff;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif'>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation"
style="max-width:37.5em">
<tbody>
<tr style="width:100%">
<td>
<p>Dear User,</p>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation"
style="border:1px solid rgb(0,0,0, 0.1);border-radius:3px;overflow:hidden">
<tbody>
<tr>
<td>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0"
role="presentation">
<tbody style="width:100%">
<tr style="width:100%">
<img src="https://litlyx.com/images/locker2.png"
style="display:block;outline:none;border:none;text-decoration:none;max-width:100%"
width="620" />
</tr>
</tbody>
</table>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0"
role="presentation" style="padding:20px;padding-bottom:0">
<tbody style="width:100%">
<tr style="width:100%">
<td data-id="__react-email-column">
<h1 style="font-size:32px;font-weight:bold;text-align:center">
Dear user
</h1>
<h2 style="font-size:26px;font-weight:bold;text-align:center">
Our AI Agent noticed a recent unexpected usage on your project
on Litlyx.
</h2>
<p
style="font-size:16px;line-height:24px;margin:16px 0;margin-top:-5px">
<b>Project: </b> [Project Name]
</p>
<p
style="font-size:16px;line-height:24px;margin:16px 0;margin-top:-5px">
<b>Info: </b> [ENTRIES]
</p>
<p>If this spike in activity is expected, theres no need to worry.
However, if you believe this could be unexpected or suspicious,
we recommend taking a closer look at your data on the
<strong>Litlyx Dashboard</strong>. You can analyze your recent
traffic and event logs to identify any irregularities or
potential issues.</p>
<p>We wanted to let you know that <strong>[Project Name]</strong> on <strong>Litlyx</strong> is receiving an unexpected amount of visits or events. This could indicate unusual activity that might require your attention.</p>
<p> <strong>Anomaly</strong>: Unexpected usage </p>
</td>
</tr>
</tbody>
</table>
<p> Message: </p>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0"
role="presentation" style="padding:20px;padding-top:0">
<tbody style="width:100%">
<tr style="width:100%">
<td colspan="2" data-id="__react-email-column"
style="display:flex;justify-content:center;width:100%">
<a style="line-height:100%;text-decoration:none;display:inline-block;max-width:100%;mso-padding-alt:0px;background-color:#5680f8;border-radius:3px;color:#FFF;font-weight:bold;border:1px solid rgb(0,0,0, 0.1);cursor:pointer;padding:12px 30px 12px 30px"
target="_blank"
href="https://dashboard.litlyx.com"><span></span><span
style="max-width:100%;display:inline-block;line-height:120%;mso-padding-alt:0px;mso-text-raise:9px">
Go to Dashboard</span><span></span></a>
</td>
</tr>
</tbody>
</table>
<ul>
[ENTRIES]
</ul>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0"
role="presentation" style="padding:20px;padding-bottom:0">
<tbody style="width:100%">
<tr style="width:100%">
<td data-id="__react-email-column">
<h3>What can I do?</h3>
<p>If this spike in activity is expected, theres no need to worry. However, if you believe this could be unexpected or suspicious, we recommend taking a closer look at your data on the <strong>Litlyx Dashboard</strong>. You can analyze your recent traffic and event logs to identify any irregularities or potential issues.</p>
<p>To better understand the situation, you can:</p>
<h3>What can I do?</h3>
<ol>
<li>Review your traffic sources to see where the visits or
events are coming from.</li>
<li>Check for any unexpected patterns, such as a high number of
visits from unknown sources or abnormal event triggers.</li>
<li>Check your code to find bugs on a specific action that is
triggered in loops.</li>
</ol>
<p>To better understand the situation, you can:</p>
<p>If you need help understanding this activity or have any
concerns, feel free to reach out to our support team at <a
href="mailto:help@litlyx.com"
style="color: #D32F2F; text-decoration: none;"><strong>help@litlyx.com</strong></a>.
We are here to assist you!</p>
<ol>
<li>Review your traffic sources to see where the visits or events are coming from.</li>
<li>Check for any unexpected patterns, such as a high number of visits from unknown sources or abnormal event triggers.</li>
<li>Check your code to find bugs on a specific action that is triggered in loops.</li>
</ol>
<p><strong>Your safety and data integrity are our top
priorities.</strong></p>
<p>If you need help understanding this activity or have any concerns, feel free to reach out to our support team at <a href="mailto:help@litlyx.com" style="color: #D32F2F; text-decoration: none;"><strong>help@litlyx.com</strong></a>. We are here to assist you!</p>
<p>Thank you for trusting Litlyx as your analytics tool!</p>
<p><strong>Your safety and data integrity are our top priorities.</strong></p>
<p>Best regards,</p>
<p>Thank you for trusting Litlyx as your analytics tool!</p>
<p>Antonio,</p>
<p>CEO | Litlyx</p>
</td>
</tr>
</tbody>
</table>
<p>Best regards,</p>
<p>Antonio,</p>
<p>CEO | Litlyx</p>
</td>
</tr>
</tbody>
</table>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation"
style="padding:45px 0 0 0">
<tbody>
<tr>
<td>
<img src="https://react-email-demo-lpdmf0ryo-resend.vercel.app/static/yelp-footer.png"
style="display:block;outline:none;border:none;text-decoration:none;max-width:100%"
width="620" />
</td>
</tr>
</tbody>
</table>
<p style="font-size:12px;line-height:24px;margin:16px 0;text-align:center;color:rgb(0,0,0, 0.7)">
2024 © Litlyx. All rights reserved.
<br>
Litlyx S.R.L. - Viale Tirreno, 187 - 00141 Rome - P.IVA: 17814721001- REA: RM-1743194
</p>
</td>
</tr>
</tbody>
</table>
</body>
</html>
`

View File

@@ -1,31 +1,116 @@
export const LIMIT_50_EMAIL = `<!DOCTYPE html>
<html lang="en">
export const LIMIT_50_EMAIL = `<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html dir="ltr" lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Youve reached 50% limit on Litlyx</title>
<link rel="preload" as="image" href="https://react-email-demo-lpdmf0ryo-resend.vercel.app/static/airbnb-logo.png" />
<link rel="preload" as="image"
href="https://react-email-demo-lpdmf0ryo-resend.vercel.app/static/airbnb-review-user.jpg" />
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
<meta name="x-apple-disable-message-reformatting" />
<!--$-->
</head>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
<p>Dear User,</p>
<body
style='background-color:#ffffff;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif'>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation"
style="max-width:100%;margin:0 auto;padding:20px 0 48px;width:580px">
<tbody>
<tr style="width:100%">
<td>
<p>We wanted to let you know that <strong>[Project Name]</strong> on <strong>Litlyx</strong> has reached 50% of its data collection limit for this month.</p>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation">
<tbody>
<tr>
<td>
<img alt="Founder" height="96"
src="https://litlyx.com/images/founder.jpg"
style="display:block;outline:none;border:none;text-decoration:none;margin:0 auto;margin-bottom:16px;border-radius:50%"
width="96" />
</td>
</tr>
</tbody>
</table>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation"
style="padding-bottom:20px">
<tbody>
<tr>
<td>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0"
role="presentation">
<tbody style="width:100%">
<tr style="width:100%">
<p
style="font-size:32px;line-height:1.3;margin:16px 0;font-weight:700;color:#484848">
Youve Reached 50% of Your Litlyx Project Limit on [Project Name]
</p>
<p
style="font-size:18px;line-height:1.4;margin:16px 0;color:#484848;padding:24px;background-color:#f2f3f3;border-radius:4px">
To avoid losing precious data, please remember to monitor your usage
on the <strong>Litlyx Dashboard</strong>. You can find your current
usage details under <strong>Settings > Billing Tab</strong>
</p>
<p>To avoid losing precious data, please remember to monitor your usage on the <strong>Litlyx Dashboard</strong>. You can find your current usage details under <strong>Settings > Billing Tab</strong>.</p>
<p>If you need more data collection storage, you may consider upgrading
your plan to get additional benefits and ensure uninterrupted data
collection.</p>
<p>If you need more data collection storage, you may consider upgrading your plan to get additional benefits and ensure uninterrupted data collection.</p>
<p>Feel free to reply to this email or contact us at <a
href="mailto:help@litlyx.com"
style="color: #FF5733; text-decoration: none;">help@litlyx.com</a>
if you have any questions or need assistance.</p>
<p>Feel free to reply to this email or contact us at <a href="mailto:help@litlyx.com" style="color: #FF5733; text-decoration: none;">help@litlyx.com</a> if you have any questions or need assistance.</p>
<p>Thank you for choosing Litlyx every day as your analytics tool.</p>
<p>Thank you for choosing Litlyx every day as your analytics tool.</p>
<p>Have a nice day!</p>
<p>Have a nice day!</p>
<p>Antonio,</p>
<p>CEO | Litlyx</p>
<p>Antonio,</p>
<p>CEO | Litlyx</p>
<a href="https://dashboard.litlyx.com/"
style="line-height:100%;text-decoration:none;display:block;max-width:100%;mso-padding-alt:0px;background-color:#5680f8;border-radius:3px;color:#fff;font-size:18px;padding-top:19px;padding-bottom:19px;text-align:center;width:100%;padding:19px 0px 19px 0px"
target="_blank"><span>
</span><span
style="max-width:100%;display:inline-block;line-height:120%;mso-padding-alt:0px;mso-text-raise:14.25px">Go to Dashboard</span><span></span></a>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<hr
style="width:100%;border:none;border-top:1px solid #eaeaea;border-color:#cccccc;margin:20px 0" />
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation">
<tbody>
<tr>
<td>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0"
role="presentation">
<tbody style="width:100%">
<tr style="width:100%">
<p
style="font-size:14px;line-height:24px;margin:16px 0;color:#9ca299;margin-bottom:10px">
2024 © Litlyx. All rights reserved.
<br>
Litlyx S.R.L. - Viale Tirreno, 187 - 00141 Rome - P.IVA:
17814721001- REA: RM-1743194
</p>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<!--/$-->
</body>
</html>
</html>
`

View File

@@ -1,31 +1,117 @@
export const LIMIT_90_EMAIL = `<!DOCTYPE html>
<html lang="en">
export const LIMIT_90_EMAIL = `<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html dir="ltr" lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Youve reached 90% limit on Litlyx</title>
<link rel="preload" as="image" href="https://react-email-demo-lpdmf0ryo-resend.vercel.app/static/airbnb-logo.png" />
<link rel="preload" as="image"
href="https://react-email-demo-lpdmf0ryo-resend.vercel.app/static/airbnb-review-user.jpg" />
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
<meta name="x-apple-disable-message-reformatting" />
<!--$-->
</head>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
<p>Dear User,</p>
<body
style='background-color:#ffffff;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif'>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation"
style="max-width:100%;margin:0 auto;padding:20px 0 48px;width:580px">
<tbody>
<tr style="width:100%">
<td>
<p>We wanted to let you know that <strong>[Project Name]</strong> on <strong>Litlyx</strong> has reached 90% of its data collection limit for this month.</p>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation">
<tbody>
<tr>
<td>
<img alt="Founder" height="96"
src="https://litlyx.com/images/founder.jpg"
style="display:block;outline:none;border:none;text-decoration:none;margin:0 auto;margin-bottom:16px;border-radius:50%"
width="96" />
</td>
</tr>
</tbody>
</table>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation"
style="padding-bottom:20px">
<tbody>
<tr>
<td>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0"
role="presentation">
<tbody style="width:100%">
<tr style="width:100%">
<p
style="font-size:32px;line-height:1.3;margin:16px 0;font-weight:700;color:#484848">
Youve Reached 90% of Your Litlyx Project Limit on [Project Name]
</p>
<p
style="font-size:18px;line-height:1.4;margin:16px 0;color:#484848;padding:24px;background-color:#f2f3f3;border-radius:4px">
To avoid losing precious data, please remember to monitor your usage
on the <strong>Litlyx Dashboard</strong>. You can find your current
usage details under <strong>Settings > Billing Tab</strong>
</p>
<p>To avoid losing precious data, please remember to monitor your usage on the <strong>Litlyx Dashboard</strong>. You can find your current usage details under <strong>Settings > Billing Tab</strong>.</p>
<p>If you need more data collection storage, you may consider upgrading
your plan to get additional benefits and ensure uninterrupted data
collection.</p>
<p>If you need more data collection storage, you may consider upgrading your plan to get additional benefits and ensure uninterrupted data collection.</p>
<p>Feel free to reply to this email or contact us at <a
href="mailto:help@litlyx.com"
style="color: #FF5733; text-decoration: none;">help@litlyx.com</a>
if you have any questions or need assistance.</p>
<p>Feel free to reply to this email or contact us at <a href="mailto:help@litlyx.com" style="color: #FF0000; text-decoration: none;">help@litlyx.com</a> if you have any questions or need assistance.</p>
<p>Thank you for choosing Litlyx every day as your analytics tool.</p>
<p>Thank you for choosing Litlyx every day as your analytics tool.</p>
<p>Have a nice day!</p>
<p>Have a nice day!</p>
<p>Antonio,</p>
<p>CEO | Litlyx</p>
<p>Antonio,</p>
<p>CEO | Litlyx</p>
<a href="https://dashboard.litlyx.com/"
style="line-height:100%;text-decoration:none;display:block;max-width:100%;mso-padding-alt:0px;background-color:#5680f8;border-radius:3px;color:#fff;font-size:18px;padding-top:19px;padding-bottom:19px;text-align:center;width:100%;padding:19px 0px 19px 0px"
target="_blank"><span>
</span><span
style="max-width:100%;display:inline-block;line-height:120%;mso-padding-alt:0px;mso-text-raise:14.25px">Go to Dashboard</span><span></span></a>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<hr
style="width:100%;border:none;border-top:1px solid #eaeaea;border-color:#cccccc;margin:20px 0" />
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation">
<tbody>
<tr>
<td>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0"
role="presentation">
<tbody style="width:100%">
<tr style="width:100%">
<p
style="font-size:14px;line-height:24px;margin:16px 0;color:#9ca299;margin-bottom:10px">
2024 © Litlyx. All rights reserved.
<br>
Litlyx S.R.L. - Viale Tirreno, 187 - 00141 Rome - P.IVA:
17814721001- REA: RM-1743194
</p>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<!--/$-->
</body>
</html>
`

View File

@@ -1,32 +1,138 @@
export const LIMIT_MAX_EMAIL = `<!DOCTYPE html>
<html lang="en">
export const LIMIT_MAX_EMAIL = `<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html dir="ltr" lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>❗️ Youve reached your limit on Litlyx!</title>
<link rel="preload" as="image" href="https://react-email-demo-lpdmf0ryo-resend.vercel.app/static/airbnb-logo.png" />
<link rel="preload" as="image"
href="https://react-email-demo-lpdmf0ryo-resend.vercel.app/static/airbnb-review-user.jpg" />
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
<meta name="x-apple-disable-message-reformatting" />
<!--$-->
</head>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
<p>Dear User,</p>
<body
style='background-color:#ffffff;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif'>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation"
style="max-width:100%;margin:0 auto;padding:20px 0 48px;width:580px">
<tbody>
<tr style="width:100%">
<td>
<p>We wanted to let you know that <strong>[Project Name]</strong> on <strong>Litlyx</strong> has reached the limit of your current plan.</p>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation">
<tbody>
<tr>
<td>
<img alt="Founder" height="96"
src="https://litlyx.com/images/founder.jpg"
style="display:block;outline:none;border:none;text-decoration:none;margin:0 auto;margin-bottom:16px;border-radius:50%"
width="96" />
</td>
</tr>
</tbody>
</table>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation"
style="padding-bottom:20px">
<tbody>
<tr>
<td>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0"
role="presentation">
<tbody style="width:100%">
<tr style="width:100%">
<p
style="font-size:32px;line-height:1.3;margin:16px 0;font-weight:700;color:#484848">
Youve Reached Your Litlyx Project Limit on [Project Name]
</p>
<p
style="font-size:18px;line-height:1.4;margin:16px 0;color:#484848;padding:24px;background-color:#ffbb03;border-radius:4px">
We noticed that Litlyx has stopped collecting data for your project.
</p>
<p>To avoid losing precious data, please remember to monitor your usage on the <strong>Litlyx Dashboard</strong>. You can find your current usage details under <strong>Settings > Billing Tab</strong>.</p>
<p>
To help you avoid losing valuable insights, we recommend keeping an
eye on your usage via the Litlyx Dashboard.
</p>
<p>If you need more data collection storage, you may consider upgrading your plan to get additional benefits and ensure uninterrupted data collection.</p>
<p>
You can view your current usage details under Settings > Billing
Tab.
</p>
<p>Feel free to reply to this email or contact us at <a href="mailto:help@litlyx.com" style="color: #D32F2F; text-decoration: none;">help@litlyx.com</a> if you have any questions or need assistance.</p>
<p>
If you need additional storage for data collection, consider
upgrading your plan to unlock more benefits and ensure uninterrupted
service.
</p>
<p style="font-weight: 700;">
As a token of appreciation, we're offering you 25% off for life at
checkout with the code LIT25.
</p>
Thank you for choosing Litlyx as your trusted analytics tool.
<p></p>
<p>Thank you for choosing Litlyx every day as your analytics tool.</p>
<p>
If you have any questions or need assistance, feel free to reply to
this email or contact us at <a href="mailto:help@litlyx.com"
style="color: #FF5733; text-decoration: none;">help@litlyx.com</a>
</p>
<p>Have a nice day!</p>
<p>
Have a great day!
</p>
<p>Antonio,</p>
<p>CEO | Litlyx</p>
<p>
Antonio
CEO | Litlyx
</p>
<a href="https://dashboard.litlyx.com/"
style="line-height:100%;text-decoration:none;display:block;max-width:100%;mso-padding-alt:0px;background-color:#5680f8;border-radius:3px;color:#fff;font-size:18px;padding-top:19px;padding-bottom:19px;text-align:center;width:100%;padding:19px 0px 19px 0px"
target="_blank"><span>
</span><span
style="max-width:100%;display:inline-block;line-height:120%;mso-padding-alt:0px;mso-text-raise:14.25px">Go
to Dashboard</span><span></span></a>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<hr
style="width:100%;border:none;border-top:1px solid #eaeaea;border-color:#cccccc;margin:20px 0" />
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation">
<tbody>
<tr>
<td>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0"
role="presentation">
<tbody style="width:100%">
<tr style="width:100%">
<p
style="font-size:14px;line-height:24px;margin:16px 0;color:#9ca299;margin-bottom:10px">
2024 © Litlyx. All rights reserved.
<br>
Litlyx S.R.L. - Viale Tirreno, 187 - 00141 Rome - P.IVA:
17814721001- REA: RM-1743194
</p>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<!--/$-->
</body>
</html>
`

View File

@@ -0,0 +1,109 @@
export const RESET_PASSWORD_EMAIL = `<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html dir="ltr" lang="en">
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
<meta name="x-apple-disable-message-reformatting" />
<!--$-->
</head>
<body
style='background-color:#fff;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif'>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation"
style="max-width:37.5em">
<tbody>
<tr style="width:100%">
<td>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation"
style="border:1px solid rgb(0,0,0, 0.1);border-radius:3px;overflow:hidden">
<tbody>
<tr>
<td>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0"
role="presentation">
<tbody style="width:100%">
<tr style="width:100%">
<img src="https://litlyx.com/images/locker2.png"
style="display:block;outline:none;border:none;text-decoration:none;max-width:100%"
width="620" />
</tr>
</tbody>
</table>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0"
role="presentation" style="padding:20px;padding-bottom:0">
<tbody style="width:100%">
<tr style="width:100%">
<td data-id="__react-email-column">
<h1 style="font-size:32px;font-weight:bold;text-align:center">
Dear user
</h1>
<h2 style="font-size:26px;font-weight:bold;text-align:center">
Below is the temporary password for logging in again to the
Litlyx dashboard.
</h2>
<p style="font-size:16px;line-height:24px;margin:16px 0">
<b>Temporary Password: </b> [NEW_PASSWORD]
<!-- September 7, 2022 at 10:58 AM -->
</p>
<p style="font-size:16px;line-height:24px;margin:16px 0">
Please ensure that you change your password as soon as possible.
To do so, go to <b>Settings > Account</b> in the dashboard. <br>
If you need further assistance, feel free to contact us at
<a href="mailto:help@litlyx.com">help@litlyx.com</a>.
</p>
</td>
</tr>
</tbody>
</table>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0"
role="presentation" style="padding:20px;padding-top:0">
<tbody style="width:100%">
<tr style="width:100%">
<td colspan="2" data-id="__react-email-column"
style="display:flex;justify-content:center;width:100%">
<a style="line-height:100%;text-decoration:none;display:inline-block;max-width:100%;mso-padding-alt:0px;background-color:#5680f8;border-radius:3px;color:#FFF;font-weight:bold;border:1px solid rgb(0,0,0, 0.1);cursor:pointer;padding:12px 30px 12px 30px"
target="_blank"
href="https://dashboard.litlyx.com"><span></span><span
style="max-width:100%;display:inline-block;line-height:120%;mso-padding-alt:0px;mso-text-raise:9px">
Go to Dashboard</span><span></span></a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<table align="center" width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation"
style="padding:45px 0 0 0">
<tbody>
<tr>
<td>
<img src="https://react-email-demo-lpdmf0ryo-resend.vercel.app/static/yelp-footer.png"
style="display:block;outline:none;border:none;text-decoration:none;max-width:100%"
width="620" />
</td>
</tr>
</tbody>
</table>
<p style="font-size:12px;line-height:24px;margin:16px 0;text-align:center;color:rgb(0,0,0, 0.7)">
2024 © Litlyx. All rights reserved.
<br>
Litlyx S.R.L. - Viale Tirreno, 187 - 00141 Rome - P.IVA: 17814721001- REA: RM-1743194
</p>
</td>
</tr>
</tbody>
</table>
</body>
</html>
`