mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-10 15:58:38 +01:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b5d23566c | ||
|
|
dbcda95823 | ||
|
|
fb89c87489 | ||
|
|
b59eea47e9 | ||
|
|
473331047d | ||
|
|
5af77ff63e | ||
|
|
e6e2340432 | ||
|
|
0b90c2fe3c | ||
|
|
a6d1797a4f | ||
|
|
d1abe1a91f | ||
|
|
b733cd2a68 | ||
|
|
88ebfc188c |
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"files.exclude": {
|
||||
"**/node_modules": true
|
||||
}
|
||||
}
|
||||
30
Dockerfile
30
Dockerfile
@@ -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" ]
|
||||
@@ -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>
|
||||
|
||||
@@ -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,13 +120,16 @@ 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"> {{
|
||||
<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]">
|
||||
@@ -133,10 +137,11 @@ function openExternalLink(link: string) {
|
||||
</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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -27,7 +27,6 @@ const chartOptions = ref<ChartOptions<'doughnut'>>({
|
||||
position: 'top',
|
||||
align: 'center',
|
||||
labels: {
|
||||
color: 'white',
|
||||
font: {
|
||||
family: 'Poppins',
|
||||
size: 16
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -46,11 +46,11 @@ 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',
|
||||
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 || []" v-model="selectedEventName">
|
||||
@@ -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`"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -106,11 +106,11 @@ 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',
|
||||
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'
|
||||
}
|
||||
}" :options="domains.data.value ?? []" v-model="selectedDomain"></USelectMenu>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -4,6 +4,7 @@ import VueMarkdown from 'vue-markdown-render';
|
||||
|
||||
definePageMeta({ layout: 'dashboard' });
|
||||
|
||||
const selfhosted = useSelfhosted();
|
||||
|
||||
const debugModeAi = ref<boolean>(false);
|
||||
|
||||
@@ -87,6 +88,7 @@ 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -268,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>
|
||||
@@ -280,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>
|
||||
@@ -290,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,
|
||||
@@ -364,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"
|
||||
@@ -381,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]">
|
||||
@@ -396,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>
|
||||
@@ -414,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>
|
||||
@@ -424,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>
|
||||
@@ -458,7 +465,6 @@ async function clearAllChats() {
|
||||
font-weight: bold;
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 0.5em;
|
||||
color: white;
|
||||
}
|
||||
|
||||
p:last-of-type {
|
||||
|
||||
@@ -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>
|
||||
@@ -126,13 +126,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">
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
|
||||
150
dashboard/pages/forgot_password.vue
Normal file
150
dashboard/pages/forgot_password.vue
Normal 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>
|
||||
@@ -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,13 +236,17 @@ async function signInWithCredentials() {
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex flex-col gap-2 mt-4">
|
||||
|
||||
<RouterLink tag="div" to="/register"
|
||||
class="mt-4 text-center text-lyx-text-dark underline cursor-pointer z-[100]">
|
||||
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>
|
||||
|
||||
<div v-if="isNoAuth" @click="loginWithoutAuth"
|
||||
class="hover:bg-accent 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">
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
definePageMeta({ layout: 'dashboard' });
|
||||
|
||||
const selfhosted = useSelfhosted();
|
||||
|
||||
const items = [
|
||||
{ label: 'General', slot: 'general' },
|
||||
@@ -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>
|
||||
|
||||
@@ -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 }
|
||||
});
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
10
dashboard/server/api/user/password/can_change.ts
Normal file
10
dashboard/server/api/user/password/can_change.ts
Normal 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 }
|
||||
});
|
||||
33
dashboard/server/api/user/password/change.post.ts
Normal file
33
dashboard/server/api/user/password/change.post.ts
Normal 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' }
|
||||
|
||||
});
|
||||
26
dashboard/server/api/user/password/reset.ts
Normal file
26
dashboard/server/api/user/password/reset.ts
Normal 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' }
|
||||
|
||||
});
|
||||
@@ -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();
|
||||
|
||||
};
|
||||
@@ -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',
|
||||
|
||||
@@ -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: ""
|
||||
|
||||
840
pnpm-lock.yaml
generated
840
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
109
shared/services/email_templates/ResetPasswordEmail.ts
Normal file
109
shared/services/email_templates/ResetPasswordEmail.ts
Normal 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>
|
||||
|
||||
`
|
||||
Reference in New Issue
Block a user