new selfhosted version

This commit is contained in:
antonio
2025-11-28 14:11:51 +01:00
parent afda29997d
commit 951860f67e
1046 changed files with 72586 additions and 574750 deletions

View File

@@ -0,0 +1,30 @@
<script lang="ts" setup>
import { LoaderCircle } from 'lucide-vue-next';
const emits = defineEmits<{ (event: 'confirm'): void }>();
const { close } = useDialog();
const loading = ref<boolean>(false);
function onConfirm() {
loading.value = true;
emits('confirm')
}
</script>
<template>
<div v-if="!loading" class="flex flex-col gap-4">
<div>
Are you sure to cancel your current plan?
</div>
<div class="flex justify-end gap-2">
<Button variant="secondary" @click="close()"> Back </Button>
<Button variant="destructive" @click="onConfirm()"> Cancel </Button>
</div>
</div>
<div v-if="loading" class="flex items-center justify-center my-4">
<LoaderCircle class="size-10 animate-[spin_1s_ease-in-out_infinite] duration-500">
</LoaderCircle>
</div>
</template>

View File

@@ -1,38 +0,0 @@
<script lang="ts" setup>
const emit = defineEmits(['success', 'cancel'])
</script>
<template>
<UModal :ui="{
strategy: 'override',
overlay: {
background: 'bg-lyx-background/85'
},
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">
<div class="flex flex-col gap-3">
<div class="font-medium">
Are you sure to logout ?
</div>
<div class="flex justify-end gap-2">
<LyxUiButton type="secondary" @click="emit('cancel')">
Cancel
</LyxUiButton>
<LyxUiButton @click="emit('success')" type="danger">
Confirm
</LyxUiButton>
</div>
</div>
</div>
</UModal>
</template>

View File

@@ -1,30 +1,23 @@
<script lang="ts" setup>
const { closeDialog } = useCustomDialog();
import type { DateRange } from 'reka-ui'
import { Button } from '@/components/ui/button'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { RangeCalendar } from '@/components/ui/range-calendar'
import { CalendarIcon } from 'lucide-vue-next'
import { CalendarDate, DateFormatter, getLocalTimeZone } from '@internationalized/date'
import { sub, format, isSameDay, type Duration, startOfDay, endOfDay } from 'date-fns'
const ranges = [
{ label: 'Last 7 days', duration: { days: 7 } },
{ label: 'Last 14 days', duration: { days: 14 } },
{ label: 'Last 30 days', duration: { days: 30 } },
{ label: 'Last 3 months', duration: { months: 3 } },
{ label: 'Last 6 months', duration: { months: 6 } },
{ label: 'Last year', duration: { years: 1 } }
]
const selected = ref<{ start: Date, end: Date }>({ start: sub(new Date(), { days: 14 }), end: new Date() })
function isRangeSelected(duration: Duration) {
return isSameDay(selected.value.start, sub(new Date(), duration)) && isSameDay(selected.value.end, new Date())
}
function selectRange(duration: Duration) {
selected.value = { start: sub(new Date(), duration), end: new Date() }
}
const emits = defineEmits<{
(event: 'confirm', data: { name: string, color: string, from: string, to: string }): void
}>();
const { close } = useDialog();
const currentColor = ref<string>("#5680F8");
const colorpicker = ref<HTMLInputElement | null>(null);
const colorpicker = useTemplateRef<HTMLInputElement>('colorpicker');
const snapshotName = ref<string>("");
function showColorPicker() {
colorpicker.value?.click();
@@ -34,83 +27,64 @@ function onColorChange() {
currentColor.value = colorpicker.value?.value || '#000000';
}
const snapshotName = ref<string>("");
const value = ref<DateRange>({
start: new CalendarDate(new Date().getFullYear(), new Date().getUTCMonth(), 1),
end: new CalendarDate(new Date().getFullYear(), new Date().getUTCMonth(), new Date().getDate())
}) as Ref<DateRange>;
const { updateSnapshots, snapshot, snapshots } = useSnapshot();
const { createAlert } = useAlert()
const canCreate = computed(() => {
return snapshotName.value.trim().length > 2 && snapshotName.value.trim().length < 22 && value.value.start && value.value.end
})
async function confirmSnapshot() {
await $fetch("/api/snapshot/create", {
method: 'POST',
headers: useComputedHeaders({ useSnapshotDates: false }).value,
body: JSON.stringify({
name: snapshotName.value,
color: currentColor.value,
from: startOfDay(selected.value.start),
to: endOfDay(selected.value.end)
})
});
const df = new DateFormatter('en-US', { dateStyle: 'medium' })
await updateSnapshots();
closeDialog();
createAlert('Timeframe created', 'Timeframe created successfully', 'far fa-circle-check', 5000);
const newSnapshot = snapshots.value.at(-1);
if (newSnapshot) snapshot.value = newSnapshot;
}
const popoverOpen = ref<boolean>(false);
</script>
<template>
<div class="w-full h-full flex flex-col">
<div class="flex flex-col gap-4">
<div class="poppins text-center text-lyx-lightmode-text dark:text-lyx-text">
Create a timeframe
</div>
<div class="mt-10 flex items-center gap-2">
<div :style="`background-color: ${currentColor};`" @click="showColorPicker"
class="w-6 h-6 rounded-full aspect-[1/1] relative cursor-pointer">
<div class="relative flex items-center gap-2">
<div @click="showColorPicker" :style="`background-color:${currentColor};`"
class="absolute left-2 shrink-0 size-4 rounded-full">
<input @input="onColorChange" ref="colorpicker" class="relative w-0 h-0 z-[-100]" type="color">
</div>
<div class="grow">
<LyxUiInput placeholder="Timeframe name" v-model="snapshotName" class="px-4 py-1 w-full"></LyxUiInput>
</div>
<Input v-model="snapshotName" class="pl-7" placeholder="Timeframe name"></Input>
</div>
<div class="mt-4 justify-center flex w-full">
<UPopover class="w-full" :popper="{ placement: 'bottom' }">
<UButton class="w-full" color="primary" variant="solid">
<div class="flex items-center justify-center w-full gap-2">
<i class="i-heroicons-calendar-days-20-solid"></i>
{{ selected.start.toLocaleDateString() }} - {{ selected.end.toLocaleDateString() }}
</div>
</UButton>
<template #panel="{ close }">
<div class="flex items-center sm:divide-x divide-gray-200 dark:divide-gray-800">
<div class="hidden sm:flex flex-col py-4">
<UButton v-for="(range, index) in ranges" :key="index" :label="range.label" color="gray"
variant="ghost" class="rounded-none px-6"
:class="[isRangeSelected(range.duration) ? 'bg-gray-100 dark:bg-gray-800' : 'hover:bg-gray-50 dark:hover:bg-gray-800/50']"
truncate @click="selectRange(range.duration)" />
</div>
<Popover v-model:open="popoverOpen">
<PopoverTrigger as-child>
<Button variant="outline">
<CalendarIcon class="mr-2 h-4 w-4" />
<template v-if="value.start">
<template v-if="value.end">
{{ df.format(value.start.toDate(getLocalTimeZone())) }} - {{
df.format(value.end.toDate(getLocalTimeZone())) }}
</template>
<DatePicker v-model="selected" @close="close" />
</div>
</template>
</UPopover>
<template v-else>
{{ df.format(value.start.toDate(getLocalTimeZone())) }}
</template>
</template>
<template v-else>
Pick a date
</template>
</Button>
</PopoverTrigger>
<PopoverContent class="w-auto p-4 flex flex-col items-end relative z-[90]">
<RangeCalendar v-model="value" initial-focus :number-of-months="2"
@update:start-value="(startDate) => value.start = startDate" />
<Button @click="popoverOpen = false;"> Confirm </Button>
</PopoverContent>
</Popover>
<div class="flex justify-end gap-2">
<Button variant="secondary" @click="close()"> Back </Button>
<Button :disabled="!canCreate"
@click="(value.start && value.end) ? emits('confirm', { name: snapshotName, color: currentColor, from: value.start.toString(), to: value.end.toString() }) : null">
Create
</Button>
</div>
<div class="grow"></div>
<div class="flex items-center justify-around gap-4">
<LyxUiButton @click="closeDialog()" type="secondary" class="w-full text-center">
Cancel
</LyxUiButton>
<LyxUiButton @click="confirmSnapshot()" type="primary" class="w-full text-center"
:disabled="snapshotName.trim().length == 0">
Confirm
</LyxUiButton>
</div>
</div>
</template>

View File

@@ -0,0 +1,20 @@
<script lang="ts" setup>
const emits = defineEmits<{ (event: 'confirm', data?: any): void }>();
const props = defineProps<{ data: { label: string, confirm?: string, back?: string, metadata?: any } }>();
const { close } = useDialog();
</script>
<template>
<div class="flex flex-col gap-4">
<div>
{{ data.label }}
</div>
<div class="flex justify-end gap-2">
<Button variant="secondary" @click="close()"> {{ data.back ?? 'Back' }}</Button>
<Button variant="destructive" @click="emits('confirm', data.metadata)"> {{ data.confirm ?? 'Delete' }}
</Button>
</div>
</div>
</template>

View File

@@ -0,0 +1,31 @@
<script lang="ts" setup>
import { LoaderCircle } from 'lucide-vue-next';
const emits = defineEmits<{ (event: 'confirm'): void }>();
const { close } = useDialog();
const loading = ref<boolean>(false);
function onConfirm() {
loading.value = true;
emits('confirm')
}
</script>
<template>
<div v-if="!loading" class="flex flex-col gap-4">
<div>
Are you sure to delete your account?
</div>
<div class="flex justify-end gap-2">
<Button variant="secondary" @click="close()"> Back </Button>
<Button variant="destructive" @click="onConfirm()"> Delete </Button>
</div>
</div>
<div v-if="loading" class="flex items-center justify-center my-4">
<LoaderCircle class="size-10 animate-[spin_1s_ease-in-out_infinite] duration-500">
</LoaderCircle>
</div>
</template>

View File

@@ -1,84 +1,19 @@
<script lang="ts" setup>
import type { ButtonType } from '../LyxUi/Button.vue';
const emit = defineEmits(['success', 'cancel'])
const props = defineProps<{
buttonType: ButtonType,
message: string,
deleteData: { isAll: boolean, visits: boolean, sessions: boolean, events: boolean, domain: string }
}>();
const isDone = ref<boolean>(false);
const canDelete = ref<boolean>(false);
async function deleteData() {
try {
if (props.deleteData.isAll) {
await $fetch('/api/settings/delete_all', {
method: 'DELETE',
headers: useComputedHeaders({ useSnapshotDates: false }).value,
})
} else {
await $fetch('/api/settings/delete_domain', {
method: 'DELETE',
headers: useComputedHeaders({ useSnapshotDates: false, custom: { 'Content-Type': 'application/json' } }).value,
body: JSON.stringify({
domain: props.deleteData.domain,
visits: props.deleteData.visits,
sessions: props.deleteData.sessions,
events: props.deleteData.events,
})
})
}
} catch (ex) {
alert('Something went wrong');
console.error(ex);
}
isDone.value = true;
}
const emits = defineEmits<{ (event: 'confirm'): void }>();
const props = defineProps<{ data: { domain: string } }>();
const { close } = useDialog();
</script>
<template>
<UModal :ui="{
strategy: 'override',
overlay: {
background: 'bg-lyx-background/85'
},
background: 'bg-lyx-lightmode-widget dark:bg-lyx-widget',
ring: 'border-solid border-[1px] border-[#262626]'
}">
<div class="h-full flex flex-col gap-2 p-4">
<div class="font-semibold text-[1.2rem]"> {{ isDone ? "Data Deletion Scheduled" : "Are you sure ?" }}</div>
<div v-if="!isDone">
{{ message }}
</div>
<div v-if="isDone">
Your data deletion request is being processed and will be reflected in your project dashboard within a
few minutes.
</div>
<div class="grow"></div>
<div v-if="!isDone">
<UCheckbox v-model="canDelete" label="Confirm data delete"></UCheckbox>
</div>
<div v-if="!isDone" class="flex justify-end gap-2">
<LyxUiButton type="secondary" @click="emit('cancel')"> Cancel </LyxUiButton>
<LyxUiButton :disabled="!canDelete" @click="canDelete ? deleteData() : () => { }" :type="buttonType">
Confirm </LyxUiButton>
</div>
<div v-if="isDone" class="flex justify-end w-full">
<LyxUiButton type="secondary" @click="emit('success')"> Dismiss </LyxUiButton>
</div>
<div class="flex flex-col gap-4">
<div>
Are you sure to delete data from domain {{ props.data.domain }}?
</div>
</UModal>
<div class="flex justify-end gap-2">
<Button variant="secondary" @click="close()"> Back </Button>
<Button variant="destructive" @click="emits('confirm')"> Delete </Button>
</div>
</div>
</template>

View File

@@ -0,0 +1,19 @@
<script lang="ts" setup>
const emits = defineEmits<{ (event: 'confirm'): void }>();
const props = defineProps<{ data: { project_id: string, project_name: string } }>();
const { close } = useDialog();
</script>
<template>
<div class="flex flex-col gap-4">
<div>
Are you sure to delete the project: {{ props.data.project_name }}?
</div>
<div class="flex justify-end gap-2">
<Button variant="secondary" @click="close()"> Back </Button>
<Button variant="destructive" @click="emits('confirm')"> Delete </Button>
</div>
</div>
</template>

View File

@@ -0,0 +1,19 @@
<script lang="ts" setup>
const emits = defineEmits<{ (event: 'confirm'): void }>();
const props = defineProps<{ data: { snapshot: GenericSnapshot } }>();
const { close } = useDialog();
</script>
<template>
<div class="flex flex-col gap-4">
<div>
Are you sure to delete the snapshot: {{ props.data.snapshot.name }}?
</div>
<div class="flex justify-end gap-2">
<Button variant="secondary" @click="close()"> Back </Button>
<Button variant="destructive" @click="emits('confirm')"> Delete </Button>
</div>
</div>
</template>

View File

@@ -1,56 +0,0 @@
<script lang="ts" setup>
const { createAlert } = useAlert();
const { close } = useModal()
const text = ref<string>("");
async function sendFeedback() {
if (text.value.length < 5) return;
try {
const res = await $fetch('/api/feedback/add', {
headers: useComputedHeaders({
useSnapshotDates: false,
custom: { 'Content-Type': 'application/json' }
}).value,
method:'POST',
body: JSON.stringify({ text: text.value })
});
createAlert('Success', 'Feedback sent successfully.', 'far fa-circle-check', 5000);
close();
} catch (ex) {
console.error(ex);
createAlert('Error', 'Error sending feedback. Please try again later', 'far fa-triangle-exclamation', 5000);
}
}
</script>
<template>
<UModal :ui="{
strategy: 'override',
overlay: {
background: 'bg-lyx-background/85'
},
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">
<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] 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>
<LyxUiButton :disabled="text.length < 5" @click="sendFeedback()" type="primary"> Send </LyxUiButton>
</div>
</div>
</div>
</UModal>
</template>

View File

@@ -1,58 +0,0 @@
<script lang="ts" setup>
const { createAlert } = useAlert();
const { close } = useModal()
function copyEmail() {
if (!navigator.clipboard) alert('You can\'t copy in HTTP');
navigator.clipboard.writeText('help@litlyx.com');
createAlert('Success', 'Email copied successfully.', 'far fa-circle-check', 5000);
}
</script>
<template>
<UModal :ui="{
strategy: 'override',
overlay: {
background: 'bg-lyx-background/85'
},
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">
<div class="flex flex-col gap-3">
<div class="font-medium">
Contact Support
</div>
<div class="dark:text-lyx-text-dark">
Contact Support for any questions or issues you have.
</div>
<div class="dark:bg-lyx-widget-lighter bg-lyx-lightmode-widget h-[1px]"></div>
<div class="flex items-center justify-between gap-4">
<div class="p-2 bg-lyx-lightmode-widget dark:bg-[#1c1b1b] rounded-md w-full">
<div class="w-full text-[.9rem] dark:text-[#acacac]"> help@litlyx.com </div>
</div>
<LyxUiButton type="secondary" @click="copyEmail()"> Copy </LyxUiButton>
<LyxUiButton type="secondary" to="mailto:help@litlyx.com"> Send </LyxUiButton>
</div>
<div class="dark:text-lyx-text-dark mt-2">
or text us on Discord, we will reply to you personally.
</div>
<LyxUiButton to="https://discord.gg/9cQykjsmWX" target="_blank" type="secondary">
Discord Support
</LyxUiButton>
</div>
</div>
</UModal>
</template>

View File

@@ -1,86 +0,0 @@
<script lang="ts" setup>
import Accept_invite from '~/pages/accept_invite.vue';
const { createAlert } = useAlert();
const { close } = useModal()
const emit = defineEmits(['success', 'cancel'])
const props = defineProps<{
invites: {
project_name: string, project_id: string
}[]
}>();
async function acceptInvite(project_id: string) {
try {
await $fetch('/api/project/members/accept', {
method: 'POST',
body: JSON.stringify({ project_id }),
headers: useComputedHeaders({
custom: {
'Content-Type': 'application/json'
}
}).value
});
emit('success');
} catch (ex) {
console.error(ex);
alert('Error accepting invite');
emit('cancel');
}
}
async function declineInvite(project_id: string) {
try {
await $fetch('/api/project/members/decline', {
method: 'POST',
body: JSON.stringify({ project_id }),
headers: useComputedHeaders({
custom: {
'Content-Type': 'application/json'
}
}).value
});
emit('success');
} catch (ex) {
console.error(ex);
alert('Error accepting invite');
emit('cancel');
}
}
</script>
<template>
<UModal :ui="{
strategy: 'override',
overlay: {
background: 'bg-lyx-background/85'
},
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-8 p-6">
<div class="flex flex-col gap-6" v-for="invite of invites">
<div class="dark:text-lyx-text text-lyx-lightmode-text">
You are invited to join
<span class="font-semibold">{{ invite.project_name }}</span>.
Do you accept?
</div>
<div class="flex gap-4 w-full justify-end">
<LyxUiButton @click="declineInvite(invite.project_id)" type="secondary"> Decline </LyxUiButton>
<LyxUiButton @click="acceptInvite(invite.project_id)" type="primary"> Accept </LyxUiButton>
</div>
</div>
</div>
</UModal>
</template>

View File

@@ -0,0 +1,19 @@
<script lang="ts" setup>
const emits = defineEmits<{ (event: 'confirm'): void }>();
const props = defineProps<{ data: { email: string } }>();
const { close } = useDialog();
</script>
<template>
<div class="flex flex-col gap-4">
<div>
Are you sure to remove {{ props.data.email }}?
</div>
<div class="flex justify-end gap-2">
<Button variant="secondary" @click="close()"> Undo </Button>
<Button variant="destructive" @click="emits('confirm')"> Confirm </Button>
</div>
</div>
</template>

View File

@@ -0,0 +1,93 @@
<script lang="ts" setup>
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import type { MemberWithPermissions } from '~/server/api/members/list';
import type { TPermission } from '~/shared/schema/TeamMemberSchema'
import { Toggle } from '@/components/ui/toggle'
const emits = defineEmits<{ (event: 'confirm', data: TPermission): void }>();
const props = defineProps<{ data: { member: MemberWithPermissions } }>();
const { close } = useDialog();
const domainStore = useDomainStore();
const currentPermission = ref<TPermission>({
webAnalytics: props.data.member.permission.webAnalytics,
events: props.data.member.permission.events,
ai: props.data.member.permission.ai,
domains: props.data.member.permission.domains
})
function onCheckboxClick(domain: string) {
if (currentPermission.value.domains.includes(domain)) {
const index = currentPermission.value.domains.indexOf(domain);
currentPermission.value.domains.splice(index, 1);
} else {
currentPermission.value.domains.push(domain)
}
}
</script>
<template>
<div class="flex flex-col gap-4">
<div> Select user permissions </div>
<div class="flex gap-3 justify-center">
<Toggle v-model="currentPermission.webAnalytics" class="flex-1" variant="outline">
<div> Web Analytics </div>
</Toggle>
<Toggle v-model="currentPermission.events" class="flex-1" variant="outline">
<div> Events </div>
</Toggle>
<Toggle v-model="currentPermission.ai" class="flex-1" variant="outline">
<div> AI </div>
</Toggle>
</div>
<div> Select what domains is allowed to see </div>
<div class="flex justify-center">
<Tabs default-value="all" class="w-full">
<TabsList class="flex justify-center w-full">
<TabsTrigger @click="currentPermission.domains = []" value="none">
No domains
</TabsTrigger>
<TabsTrigger @click="currentPermission.domains = ['*']" value="all">
All domains
</TabsTrigger>
<TabsTrigger @click="currentPermission.domains = []" value="custom">
Custom domains
</TabsTrigger>
</TabsList>
<TabsContent value="none">
<div class="text-center">
The user cannot access any domain.
</div>
</TabsContent>
<TabsContent value="all">
<div class="text-center">
The user can access all domains.
</div>
</TabsContent>
<TabsContent value="custom">
<ScrollArea type="always" class="h-[15rem] flex flex-col gap-1 w-full mt-3">
<div @click="onCheckboxClick(domain.name)"
class="flex items-center gap-2 w-fit cursor-pointer select-none"
v-for="domain of domainStore.domains.filter(e => e._id !== '*')">
<Checkbox :model-value="currentPermission.domains.includes(domain.name)"></Checkbox>
<div> {{ truncateText(domain.name, 35) }} </div>
</div>
</ScrollArea>
</TabsContent>
</Tabs>
</div>
<div class="flex justify-end gap-2">
<Button variant="secondary" @click="close()"> Cancel </Button>
<Button @click="emits('confirm', currentPermission)"> Save </Button>
</div>
</div>
</template>

View File

@@ -1,118 +0,0 @@
<script lang="ts" setup>
import { useSelectMenuStyle } from '~/composables/ui/useSelectMenuStyle';
import type { TTeamMember } from '~/shared/schema/TeamMemberSchema';
const emit = defineEmits(['success', 'cancel'])
const props = defineProps<{ member_id: string }>();
const { domainList, domain, setActiveDomain, refreshDomains, refreshingDomains } = useDomain();
const { data: member } = useFetch<TTeamMember>(`/api/project/members/get?member_id=${props.member_id}`, {
headers: useComputedHeaders({})
})
const { createAlert } = useAlert()
async function save(member_id: string) {
if (!member.value) return;
const res = await $fetch('/api/project/members/edit', {
method: 'POST',
headers: useComputedHeaders({ custom: { 'Content-Type': 'application/json' } }).value,
body: JSON.stringify({
member_id,
webAnalytics: member.value.permission.webAnalytics,
events: member.value.permission.events,
ai: member.value.permission.ai,
domains: member.value.permission.domains
})
});
createAlert('Saved', 'Permission saved successfully', 'fas fa-check', 2500);
emit('success')
}
</script>
<template>
<UModal :ui="{
strategy: 'override',
overlay: {
background: 'bg-lyx-background/85'
},
background: 'bg-lyx-lightmode-widget dark:bg-lyx-widget',
ring: 'border-solid border-[1px] border-[#262626]'
}">
<div class="p-8">
<div v-if="member" class="manage flex flex-col gap-4">
<div class="flex flex-col gap-1">
<div class="poppins text-[1.1rem]"> Manage permissions </div>
<div class="poppins text-[.9rem] dark:text-lyx-text-dark"> Choose what this member can do on this project. </div>
</div>
<LyxUiSeparator></LyxUiSeparator>
<div class="flex flex-col gap-1">
<div>
<div class="mb-1"> Select what domain is allowed to see: </div>
<div class="mb-1">
<USelectMenu v-model="member.permission.domains" :options="domainList" multiple
value-attribute="_id">
<template #option="{ option, active, selected }">
<div class="flex items-center gap-2">
<div>
<img class="h-5 bg-black rounded-full" :src="'/logo_32.png'"
alt="Litlyx logo">
</div>
<div> {{ option._id }} </div>
</div>
</template>
<template #label="e">
<div class="flex items-center gap-2">
<div>
<img class="h-5 bg-black rounded-full" :src="'/logo_32.png'"
alt="Litlyx logo">
</div>
<div>
{{
member.permission.domains.length > 2 ?
`${member.permission.domains.length} domains` :
(member.permission.domains.map(e => e).join(' & ') || 'No domains')
}}
</div>
</div>
</template>
</USelectMenu>
</div>
</div>
<div class="flex items-center gap-2">
<UCheckbox v-model="member.permission.webAnalytics"></UCheckbox>
<div> Allow web analytics page </div>
</div>
<div class="flex items-center gap-2">
<UCheckbox v-model="member.permission.events"></UCheckbox>
<div> Allow events page </div>
</div>
<div class="flex items-center gap-2">
<UCheckbox v-model="member.permission.ai"></UCheckbox>
<div> Allow to use AI data analyst </div>
</div>
</div>
</div>
<div class="flex gap-2 justify-end mt-8">
<LyxUiButton class="!w-[6rem] text-center" type="secondary" @click="emit('cancel')"> Cancel </LyxUiButton>
<LyxUiButton class="!w-[6rem] text-center" v-if="member?.permission" @click="save(member._id.toString())" type="primary">
Save
</LyxUiButton>
</div>
</div>
</UModal>
</template>

View File

@@ -0,0 +1,18 @@
<script lang="ts" setup>
const emits = defineEmits<{
(event: 'confirm', data: number): void
}>()
const props = defineProps<{ data: { contentText: string } }>();
</script>
<template>
<div>
TEST DIALOG CONTENT
<Button @click="emits('confirm', 123)">
{{ data.contentText }}
</Button>
</div>
</template>

View File

@@ -0,0 +1,33 @@
<script lang="ts" setup>
const address = ref<string>('');
const description = ref<string>('');
const canAdd = computed(() => address.value.length > 0);
const loading = ref<boolean>(false);
const emits = defineEmits<{ (event: 'confirm', data: { address: string, description: string }): void }>();
</script>
<template>
<div class="flex flex-col gap-2">
<Label class="text-base"> Add IP to Block List</Label>
<Label> IP Address </Label>
<Input v-model="address"></Input>
<Label> Description </Label>
<Input v-model="description"></Input>
<div class="text-sm text-muted-foreground">
Once added, we will start rejecting traffic from this IP within a few minutes.
</div>
<Button v-if="loading" disabled>
<Loader class="!size-6"></Loader>
</Button>
<Button v-else @click="emits('confirm', { address, description }), loading = true" :disabled="!canAdd">
Add IP Address
</Button>
</div>
</template>

View File

@@ -0,0 +1,27 @@
<script lang="ts" setup>
const domain = ref<string>('');
const canAdd = computed(() => domain.value.length > 0);
const loading = ref<boolean>(false);
const emits = defineEmits<{ (event: 'confirm', domain: string): void }>();
</script>
<template>
<div class="flex flex-col gap-2">
<Label class="text-base"> Add Domain to Allow List</Label>
<Input v-model="domain"></Input>
<div class="text-sm text-muted-foreground">
You can use a wildcard (*) to match multiple hostnames.
For example, *.domain.com will only record traffic on the main domain and all the subdomains.
</div>
<div class="text-sm text-muted-foreground">
NB: Once added, we will start allowing traffic only from matching hostnames within a few minutes.
</div>
<Button v-if="loading" disabled>
<Loader class="!size-6"></Loader>
</Button>
<Button v-else @click="emits('confirm', domain), loading = true" :disabled="!canAdd"> Add domain </Button>
</div>
</template>

View File

@@ -0,0 +1,22 @@
<script lang="ts" setup>
const { close } = useDialog();
const emits = defineEmits<{ (event: 'confirm'): void }>();
const props = defineProps<{ data: { address: string } }>();
</script>
<template>
<div class="flex flex-col gap-2">
<Label class="text-base"> Address delete </Label>
<div class="text-base">
Are you sure to delete the blacklisted IP address <b>{{ props.data.address }}</b>
</div>
<div class="flex justify-end gap-2">
<Button variant="secondary" @click="close()"> Back </Button>
<Button variant="destructive" @click="emits('confirm')"> Delete </Button>
</div>
</div>
</template>

View File

@@ -0,0 +1,22 @@
<script lang="ts" setup>
const { close } = useDialog();
const emits = defineEmits<{ (event: 'confirm'): void }>();
const props = defineProps<{ data: { domain: string } }>();
</script>
<template>
<div class="flex flex-col gap-2">
<Label class="text-base"> Domain delete </Label>
<div class="text-base">
Are you sure to delete the whitelisted domain <b>{{ props.data.domain }}</b>
</div>
<div class="flex justify-end gap-2">
<Button variant="secondary" @click="close()"> Back </Button>
<Button variant="destructive" @click="emits('confirm')"> Delete </Button>
</div>
</div>
</template>