This commit is contained in:
Emily
2024-10-07 15:26:57 +02:00
parent c2846ca595
commit b27cacf4e6
50 changed files with 512 additions and 583 deletions

View File

@@ -1,6 +1,5 @@
<script lang="ts" setup>
import type { TProject } from '@schema/ProjectSchema';
import CreateSnapshot from './dialog/CreateSnapshot.vue';
export type Entry = {
@@ -27,7 +26,8 @@ type Props = {
const route = useRoute();
const props = defineProps<Props>();
const { user, userRoles, setLoggedUser } = useLoggedUser()
const { userRoles, setLoggedUser } = useLoggedUser();
const { projectList } = useProject();
const debugMode = process.dev;
@@ -100,9 +100,6 @@ function onLogout() {
router.push('/login');
}
const { projects } = useProjectsList();
const activeProject = useActiveProject();
const { data: maxProjects } = useFetch("/api/user/max_projects", {
headers: computed(() => {
return {
@@ -111,9 +108,6 @@ const { data: maxProjects } = useFetch("/api/user/max_projects", {
})
});
const isPremium = computed(() => {
return activeProject.value?.premium;
})
const pricingDrawer = usePricingDrawer();
@@ -147,7 +141,7 @@ const pricingDrawer = usePricingDrawer();
</div>
<NuxtLink to="/project_creation" v-if="projects && (projects.length < (maxProjects || 1))"
<NuxtLink to="/project_creation" v-if="projectList && (projectList.length < (maxProjects || 1))"
class="flex items-center text-[.8rem] gap-1 justify-end pt-2 pr-2 text-lyx-text-dark hover:text-lyx-text cursor-pointer">
<div><i class="fas fa-plus"></i></div>
<div> Create new project </div>
@@ -256,7 +250,7 @@ const pricingDrawer = usePricingDrawer();
<div class="manrope grow">
{{ entry.label }}
</div>
<div v-if="entry.premiumOnly && !isPremium" class="flex items-center">
<div v-if="entry.premiumOnly && !userRoles.isPremium" class="flex items-center">
<i class="fal fa-lock"></i>
</div>
</NuxtLink>

View File

@@ -1,6 +1,6 @@
<script lang="ts" setup>
const activeProject = useActiveProject();
const { project } = useProject();
const { createAlert } = useAlert();
import 'highlight.js/styles/stackoverflow-dark.css';
@@ -18,7 +18,7 @@ onMounted(() => {
function copyProjectId() {
if (!navigator.clipboard) alert('You can\'t copy in HTTP');
navigator.clipboard.writeText(activeProject.value?._id?.toString() || '');
navigator.clipboard.writeText(project.value?._id?.toString() || '');
createAlert('Success', 'Project id copied successfully.', 'far fa-circle-check', 5000);
}
@@ -30,7 +30,7 @@ function copyScript() {
const createScriptText = () => {
return [
'<script defer ',
`data-project="${activeProject.value?._id}" `,
`data-project="${project.value?._id}" `,
'src="https://cdn.jsdelivr.net/gh/litlyx/litlyx-js/browser/litlyx.js"></',
'script>'
].join('')
@@ -43,7 +43,7 @@ function copyScript() {
const scriptText = computed(() => {
return [
`<script defer data-project="${activeProject.value?._id.toString()}"`,
`<script defer data-project="${project.value?._id.toString()}"`,
`\nsrc="https://cdn.jsdelivr.net/gh/litlyx/litlyx-js/browser/litlyx.js">\n<`,
`/script>`
].join('');
@@ -57,7 +57,7 @@ function reloadPage() {
<template>
<div v-if="!firstInteraction && activeProject" class="mt-[5vh] flex flex-col">
<div v-if="!firstInteraction && project" class="mt-[5vh] flex flex-col">
<div class="flex gap-4 items-center justify-center">
<div class="animate-pulse w-[1.5rem] h-[1.5rem] bg-accent rounded-full"> </div>
@@ -109,7 +109,7 @@ 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 flex-col items-end">
<div class="w-full text-[.9rem] text-[#acacac]"> {{ activeProject?._id }} </div>
<div class="w-full text-[.9rem] text-[#acacac]"> {{ project?._id }} </div>
<LyxUiButton type="secondary" @click="copyProjectId()"> Copy </LyxUiButton>
</div>
</CardTitled>

View File

@@ -4,13 +4,12 @@ import type { TProject } from '@schema/ProjectSchema';
const { user } = useLoggedUser()
const { projectList, actions, project } = useProject();
const { data: guestProjects } = useGuestProjectsList()
const { projectList, guestProjectList, actions, project } = useProject();
const selectorProjects = computed(() => {
const result: TProject[] = [];
if (projectList.value) result.push(...projectList.value);
if (guestProjects.value) result.push(...guestProjects.value);
if (guestProjectList.value) result.push(...guestProjectList.value);
return result;
});

View File

@@ -77,7 +77,7 @@ const chartData = ref<ChartData<'doughnut'>>({
const { doughnutChartProps, doughnutChartRef } = useDoughnutChart({ chartData: chartData, options: chartOptions });
const activeProjectId = useActiveProjectId();
const { projectId } = useProject();
const { safeSnapshotDates } = useSnapshot();
@@ -105,7 +105,7 @@ const headers = computed(() => {
'Authorization': authorizationHeaderComputed.value,
'x-schema': 'events',
'x-limit': "6",
'x-pid': activeProjectId.data.value || ''
'x-pid': projectId.value || ''
}
});

View File

@@ -38,7 +38,7 @@ function showAnomalyInfoAlert() {
<div class="flex gap-2 items-center text-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-[1.2rem]"> {{ onlineUsers }} Online users</div>
<div class="poppins font-medium text-[1.2rem]"> {{ onlineUsers.data }} Online users</div>
</div>
<div class="grow"></div>

View File

@@ -1,7 +1,6 @@
<script setup lang="ts">
import type { ChartData, ChartOptions } from 'chart.js';
import { defineChartComponent } from 'vue-chart-3';
registerChartComponents();
const FunnelChart = defineChartComponent('funnel', 'funnel');
@@ -90,17 +89,13 @@ onMounted(async () => {
});
const activeProjectId = useActiveProjectId();
const { safeSnapshotDates } = useSnapshot();
const eventsCount = await useFetch<{ _id: string, count: number }[]>(`/api/data/query`, {
...signHeaders({
'x-pid': activeProjectId.data.value || '',
'x-schema': 'events',
'x-from': safeSnapshotDates.value.from,
'x-to': safeSnapshotDates.value.to,
'x-query-limit': '1000'
}), lazy: true
lazy: true, headers: useComputedHeaders({
limit: 1000,
custom: {
'schema': 'events'
}
})
});

View File

@@ -1,16 +1,15 @@
<script lang="ts" setup>
const activeProject = useActiveProject();
const eventNames = ref<string[]>([]);
const eventNames = await useFetch<string[]>(`/api/data/events_data/names`, {
headers: useComputedHeaders()
});
const selectedEventName = ref<string>();
const metadataFields = ref<string[]>([]);
const selectedMetadataField = ref<string>();
const metadataFieldGrouped = ref<any[]>([]);
onMounted(async () => {
eventNames.value = await $fetch<string[]>(`/api/metrics/${activeProject.value?._id.toString()}/events/names`, signHeaders());
});
watch(selectedEventName, () => {
getMetadataFields();
@@ -21,7 +20,9 @@ watch(selectedMetadataField, () => {
});
async function getMetadataFields() {
metadataFields.value = await $fetch<string[]>(`/api/metrics/${activeProject.value?._id.toString()}/events/metadata_fields?name=${selectedEventName.value}`, signHeaders());
metadataFields.value = await $fetch<string[]>(`/api/metrics/events_data/metadata_fields?name=${selectedEventName.value}`, {
headers: useComputedHeaders().value
});
selectedMetadataField.value = undefined;
currentSearchText.value = "";
}
@@ -38,10 +39,12 @@ async function getMetadataFieldGrouped() {
name: selectedEventName.value,
field: selectedMetadataField.value
}
const queryParamsString = Object.keys(queryParams).map((key) => `${key}=${queryParams[key]}`).join('&');
metadataFieldGrouped.value = await $fetch<string[]>(`/api/metrics/${activeProject.value?._id.toString()}/events/metadata_field_group?${queryParamsString}`, signHeaders());
metadataFieldGrouped.value = await $fetch<string[]>(`/api/metrics/events_data/metadata_field_group?${queryParamsString}`, {
headers: useComputedHeaders().value
});
}
@@ -75,7 +78,7 @@ const canSearch = computed(() => {
<div class="flex flex-col gap-2">
<USelectMenu searchable searchable-placeholder="Search an event..." class="w-full"
placeholder="Select an event" :options="eventNames" v-model="selectedEventName">
placeholder="Select an event" :options="eventNames.data.value || []" v-model="selectedEventName">
</USelectMenu>
<USelectMenu v-if="metadataFields.length > 0" searchable searchable-placeholder="Search a field..."

View File

@@ -6,7 +6,6 @@ import DateService, { type Slice } from '@services/DateService';
const props = defineProps<{ slice: Slice }>();
const slice = computed(() => props.slice);
const activeProject = useActiveProject();
const { safeSnapshotDates } = useSnapshot()
const body = computed(() => {
@@ -90,8 +89,10 @@ function onResponse(e: any) {
if (e.response.status != 500) errorData.value = { errored: false, text: '' }
}
const eventsStackedData = useFetch(`/api/metrics/${activeProject.value?._id}/timeline/events_stacked`, {
method: 'POST', body, lazy: true, immediate: false, transform: transformResponse, ...signHeaders(),
const eventsStackedData = useFetch(`/api/timeline/events_stacked`, {
method: 'POST', body, lazy: true, immediate: false,
transform: transformResponse,
headers: useComputedHeaders(),
onResponseError,
onResponse
});

View File

@@ -1,13 +1,11 @@
<script lang="ts" setup>
const activeProject = useActiveProject();
const eventNames = await useFetch<string[]>(`/api/data/events_data/names`, {
headers: useComputedHeaders()
});
const eventNames = ref<string[]>([]);
const selectedEventName = ref<string>();
onMounted(async () => {
eventNames.value = await $fetch<string[]>(`/api/metrics/${activeProject.value?._id.toString()}/events/names`, signHeaders());
});
const userFlowData = ref<any>();
const analyzing = ref<boolean>(false);
@@ -26,7 +24,10 @@ async function getUserFlowData() {
const queryParamsString = Object.keys(queryParams).map((key) => `${key}=${queryParams[key]}`).join('&');
userFlowData.value = await $fetch(`/api/metrics/${activeProject.value?._id.toString()}/events/flow_from_name?${queryParamsString}`, signHeaders());
userFlowData.value = await $fetch(`/api/data/events_data/flow_from_name?${queryParamsString}`, {
headers: useComputedHeaders().value
});
analyzing.value = false;
}
@@ -38,11 +39,12 @@ async function analyzeEvent() {
<template>
<CardTitled title="Event User Flow"
sub="Track your user's journey from external links to in-app events, maintaining a complete view of their path from entry to engagement." class="w-full p-4">
sub="Track your user's journey from external links to in-app events, maintaining a complete view of their path from entry to engagement."
class="w-full p-4">
<div class="p-2 flex flex-col gap-3">
<USelectMenu searchable searchable-placeholder="Search an event..." class="w-full"
placeholder="Select an event" :options="eventNames" v-model="selectedEventName">
placeholder="Select an event" :options="eventNames.data.value || []" v-model="selectedEventName">
</USelectMenu>
<div v-if="selectedEventName && !analyzing" class="flex justify-center">
<div @click="analyzeEvent()"

View File

@@ -4,17 +4,9 @@ import type { PricingCardProp } from './PricingCardGeneric.vue';
const { data: planData, refresh: refreshPlanData } = useFetch('/api/project/plan', {
...signHeaders(),
lazy: true
lazy: true, headers: useComputedHeaders({ useSnapshotDates: false })
});
const activeProject = useActiveProject();
watch(activeProject, () => {
refreshPlanData();
});
function getPricingsData() {
const freePricing: PricingCardProp[] = [
@@ -190,15 +182,18 @@ function getPricingsData() {
return { freePricing, customPricing, slidePricings }
}
const { projectId } = useProject();
const emits = defineEmits<{
(evt: 'onCloseClick'): void
}>();
async function onLifetimeUpgradeClick() {
const res = await $fetch<string>(`/api/pay/${activeProject.value?._id.toString()}/create-onetime`, {
...signHeaders({ 'content-type': 'application/json' }),
const res = await $fetch<string>(`/api/pay/create-onetime`, {
...signHeaders({
'content-type': 'application/json',
'x-pid': projectId.value ?? ''
}),
method: 'POST',
body: JSON.stringify({ planId: 2001 })
})
@@ -218,7 +213,8 @@ async function onLifetimeUpgradeClick() {
<div class="flex gap-8 mt-10 h-max xl:flex-row flex-col">
<PricingCardGeneric class="flex-1" :datas="getPricingsData().freePricing"></PricingCardGeneric>
<PricingCardGeneric class="flex-1" :datas="getPricingsData().slidePricings" :default-index="2"></PricingCardGeneric>
<PricingCardGeneric class="flex-1" :datas="getPricingsData().slidePricings" :default-index="2">
</PricingCardGeneric>
<PricingCardGeneric class="flex-1" :datas="getPricingsData().customPricing"></PricingCardGeneric>
</div>

View File

@@ -2,7 +2,7 @@
import type { TApiSettings } from '@schema/ApiSettingsSchema';
import type { SettingsTemplateEntry } from './Template.vue';
const { project } = useProject();
const { project, actions, projectList } = useProject();
const entries: SettingsTemplateEntry[] = [
{ id: 'pname', title: 'Name', text: 'Project name' },
@@ -21,7 +21,7 @@ const newApiKeyName = ref<string>('');
async function updateApiKeys() {
newApiKeyName.value = '';
apiKeys.value = await $fetch<TApiSettings[]>('/api/keys/get_all', signHeaders({
'x-pid': project.value?._id.toString() ?? ''
'x-pid': project.value?._id.toString() ?? ''
}));
}
@@ -77,7 +77,10 @@ const canChange = computed(() => {
async function changeProjectName() {
await $fetch("/api/project/change_name", {
method: 'POST',
...signHeaders({ 'Content-Type': 'application/json' }),
...signHeaders({
'Content-Type': 'application/json',
'x-pid': project.value?._id.toString() ?? ''
}),
body: JSON.stringify({ name: projectNameInputVal.value })
});
location.reload();
@@ -92,14 +95,17 @@ async function deleteProject() {
await $fetch('/api/project/delete', {
method: 'DELETE',
...signHeaders({ 'Content-Type': 'application/json' }),
...signHeaders({
'Content-Type': 'application/json',
'x-pid': project.value?._id.toString() ?? ''
}),
body: JSON.stringify({ project_id: project.value._id.toString() })
});
const projectsList = useProjectsList()
await projectsList.refresh();
const firstProjectId = projectsList.data.value?.[0]?._id.toString();
await actions.refreshProjectsList()
const firstProjectId = projectList.value?.[0]?._id.toString();
if (firstProjectId) {
await setActiveProject(firstProjectId);
}

View File

@@ -3,16 +3,16 @@ import dayjs from 'dayjs';
import type { SettingsTemplateEntry } from './Template.vue';
import { getPlanFromId, PREMIUM_PLAN, type PREMIUM_TAG } from '@data/PREMIUM';
const activeProject = useActiveProject();
const { projectId, isGuest } = useProject();
definePageMeta({ layout: 'dashboard' });
const { data: planData, refresh: planRefresh, pending: planPending } = useFetch('/api/project/plan', {
...signHeaders(), lazy: true
const { data: planData, pending: planPending } = useFetch('/api/project/plan', {
lazy: true, headers: useComputedHeaders({ useSnapshotDates: false })
});
const { data: customerAddress, refresh: refreshCustomerAddress } = useFetch(`/api/pay/${activeProject.value?._id.toString()}/customer_info`, {
...signHeaders(), lazy: true
const { data: customerAddress } = useFetch(`/api/pay/customer_info`, {
lazy: true, headers: useComputedHeaders({ useSnapshotDates: false })
});
const percent = computed(() => {
@@ -45,8 +45,8 @@ const prettyExpireDate = computed(() => {
});
const { data: invoices, refresh: invoicesRefresh, pending: invoicesPending } = useFetch(`/api/pay/${activeProject.value?._id.toString()}/invoices`, {
...signHeaders(), lazy: true
const { data: invoices, pending: invoicesPending } = useFetch(`/api/pay/invoices`, {
lazy: true, headers: useComputedHeaders({ useSnapshotDates: false })
})
function openInvoice(link: string) {
@@ -75,7 +75,7 @@ const entries: SettingsTemplateEntry[] = [
]
watch(customerAddress, () => {
console.log('UPDATE',customerAddress.value)
console.log('UPDATE', customerAddress.value)
if (!customerAddress.value) return;
currentBillingInfo.value = customerAddress.value;
});
@@ -92,21 +92,22 @@ const currentBillingInfo = ref<any>({
const { createAlert } = useAlert()
async function saveBillingInfo() {
try {
const res = await $fetch(`/api/pay/${activeProject.value?._id.toString()}/update_customer`, {
method: 'POST',
...signHeaders({
'Content-Type': 'application/json'
}),
body: JSON.stringify(currentBillingInfo.value)
});
const res = await $fetch(`/api/pay/update_customer`, {
method: 'POST',
...signHeaders({
'Content-Type': 'application/json',
'x-pid': projectId.value ?? ''
}),
body: JSON.stringify(currentBillingInfo.value)
});
createAlert('Customer updated','Customer updated successfully', 'far fa-check', 5000);
createAlert('Customer updated', 'Customer updated successfully', 'far fa-check', 5000);
} catch(ex) {
createAlert('Error updating customer','An error occurred while updating the customer', 'far fa-error', 8000);
}
} catch (ex) {
createAlert('Error updating customer', 'An error occurred while updating the customer', 'far fa-error', 8000);
}
}

View File

@@ -1,11 +1,10 @@
<script setup lang="ts">
import type { SettingsTemplateEntry } from './Template.vue';
const activeProject = useActiveProject();
const { projectId } = useProject();
definePageMeta({ layout: 'dashboard' });
const columns = [
{ key: 'me', label: '' },
{ key: 'email', label: 'Email' },
@@ -15,7 +14,9 @@ const columns = [
// { key: 'pending', label: 'Pending' },
]
const { data: members, refresh: refreshMembers, pending: pendingMembers } = useFetch('/api/project/members/list', signHeaders());
const { data: members, refresh: refreshMembers } = useFetch('/api/project/members/list', {
headers: useComputedHeaders({ useSnapshotDates: false })
});
const showAddMember = ref<boolean>(false);
@@ -30,7 +31,10 @@ async function kickMember(email: string) {
await $fetch('/api/project/members/kick', {
method: 'POST',
...signHeaders({ 'Content-Type': 'application/json' }),
...signHeaders({
'Content-Type': 'application/json',
'x-pid': projectId.value ?? ''
}),
body: JSON.stringify({ email }),
onResponseError({ request, response, options }) {
alert(response.statusText);
@@ -55,7 +59,10 @@ async function addMember() {
await $fetch('/api/project/members/add', {
method: 'POST',
...signHeaders({ 'Content-Type': 'application/json' }),
...signHeaders({
'Content-Type': 'application/json',
'x-pid': projectId.value ?? ''
}),
body: JSON.stringify({ email: addMemberEmail.value }),
onResponseError({ request, response, options }) {
alert(response.statusText);
@@ -71,10 +78,6 @@ async function addMember() {
}
watch(activeProject, () => {
refreshMembers();
})
const entries: SettingsTemplateEntry[] = [
{ id: 'add', title: 'Add member', text: 'Add new member to project' },
{ id: 'members', title: 'Members', text: 'Manage members of current project' },