diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-icon.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-icon.tsx
index fe457749..99b90806 100644
--- a/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-icon.tsx
+++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-icon.tsx
@@ -21,11 +21,11 @@ import { toast } from 'sonner';
import type { EventMeta } from '@mixan/db';
-const variants = cva('flex items-center justify-center shrink-0', {
+const variants = cva('flex items-center justify-center shrink-0 rounded-full', {
variants: {
size: {
- sm: 'w-6 h-6 rounded',
- default: 'w-12 h-12 rounded-xl',
+ sm: 'w-6 h-6',
+ default: 'w-10 h-10',
},
},
defaultVariants: {
diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-list-item copy.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-list-item copy.tsx
new file mode 100644
index 00000000..9fac09a9
--- /dev/null
+++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-list-item copy.tsx
@@ -0,0 +1,275 @@
+'use client';
+
+import { ExpandableListItem } from '@/components/general/ExpandableListItem';
+import { ProfileAvatar } from '@/components/profiles/ProfileAvatar';
+import { SerieIcon } from '@/components/report/chart/SerieIcon';
+import { KeyValue, KeyValueSubtle } from '@/components/ui/key-value';
+import { useAppParams } from '@/hooks/useAppParams';
+import {
+ useEventQueryFilters,
+ useEventQueryNamesFilter,
+} from '@/hooks/useEventQueryFilters';
+import { cn } from '@/utils/cn';
+import { getProfileName } from '@/utils/getters';
+import { round } from '@/utils/math';
+import Link from 'next/link';
+import { uniq } from 'ramda';
+
+import type { IServiceCreateEventPayload } from '@mixan/db';
+
+import { EventIcon } from './event-icon';
+
+type EventListItemProps = IServiceCreateEventPayload;
+
+export function EventListItem(props: EventListItemProps) {
+ const {
+ profile,
+ createdAt,
+ name,
+ properties,
+ path,
+ duration,
+ referrer,
+ referrerName,
+ referrerType,
+ brand,
+ model,
+ browser,
+ browserVersion,
+ os,
+ osVersion,
+ city,
+ region,
+ country,
+ continent,
+ device,
+ projectId,
+ meta,
+ } = props;
+ const params = useAppParams();
+ const [, setEvents] = useEventQueryNamesFilter({ shallow: false });
+ const [, setFilter] = useEventQueryFilters({ shallow: false });
+ const keyValueList = [
+ {
+ name: 'Duration',
+ value: duration ? round(duration / 1000, 1) : undefined,
+ },
+ {
+ name: 'Referrer',
+ value: referrer,
+ onClick() {
+ setFilter('referrer', referrer ?? '');
+ },
+ },
+ {
+ name: 'Referrer name',
+ value: referrerName,
+ onClick() {
+ setFilter('referrer_name', referrerName ?? '');
+ },
+ },
+ {
+ name: 'Referrer type',
+ value: referrerType,
+ onClick() {
+ setFilter('referrer_type', referrerType ?? '');
+ },
+ },
+ {
+ name: 'Brand',
+ value: brand,
+ onClick() {
+ setFilter('brand', brand ?? '');
+ },
+ },
+ {
+ name: 'Model',
+ value: model,
+ onClick() {
+ setFilter('model', model ?? '');
+ },
+ },
+ {
+ name: 'Browser',
+ value: browser,
+ onClick() {
+ setFilter('browser', browser ?? '');
+ },
+ },
+ {
+ name: 'Browser version',
+ value: browserVersion,
+ onClick() {
+ setFilter('browser_version', browserVersion ?? '');
+ },
+ },
+ {
+ name: 'OS',
+ value: os,
+ onClick() {
+ setFilter('os', os ?? '');
+ },
+ },
+ {
+ name: 'OS version',
+ value: osVersion,
+ onClick() {
+ setFilter('os_version', osVersion ?? '');
+ },
+ },
+ {
+ name: 'City',
+ value: city,
+ onClick() {
+ setFilter('city', city ?? '');
+ },
+ },
+ {
+ name: 'Region',
+ value: region,
+ onClick() {
+ setFilter('region', region ?? '');
+ },
+ },
+ {
+ name: 'Country',
+ value: country,
+ onClick() {
+ setFilter('country', country ?? '');
+ },
+ },
+ {
+ name: 'Continent',
+ value: continent,
+ onClick() {
+ setFilter('continent', continent ?? '');
+ },
+ },
+ {
+ name: 'Device',
+ value: device,
+ onClick() {
+ setFilter('device', device ?? '');
+ },
+ },
+ ].filter((item) => typeof item.value === 'string' && item.value);
+
+ const propertiesList = Object.entries(properties)
+ .map(([name, value]) => ({
+ name,
+ value: value as string | number | undefined,
+ }))
+ .filter((item) => typeof item.value === 'string' && item.value);
+
+ return (
+
+
+
+ {!!profile && (
+
+
+
+ {getProfileName(profile)}
+
+
+ )}
+
+
+ {meta?.conversion && '⭐️ '}
+ {name}
+
+ {' at '}
+ {path}
+ {' from '}
+
+ {city || 'Unknown'}, {country}
+
+ {' using '}
+
+ {brand || device}
+
+
+
+
+ );
+ // return (
+ // setEvents((p) => uniq([...p, name]))}>
+ // {name.split('_').join(' ')}
+ //
+ // }
+ // content={
+ // <>
+ //
+ // {profile?.id === props.deviceId && (
+ //
+ // )}
+ // {profile && (
+ //
+ // )}
+ // {path && (
+ // {
+ // setFilter('path', path);
+ // }}
+ // />
+ // )}
+ // >
+ // }
+ // image={}
+ // >
+ //
+ // {propertiesList.length > 0 && (
+ //
+ //
Your properties
+ //
+ // {propertiesList.map((item) => (
+ // {
+ // setFilter(
+ // `properties.${item.name}`,
+ // item.value ? String(item.value) : '',
+ // 'is'
+ // );
+ // }}
+ // />
+ // ))}
+ //
+ //
+ // )}
+ //
+ //
Properties
+ //
+ // {keyValueList.map((item) => (
+ // item.onClick?.()}
+ // key={item.name}
+ // name={item.name}
+ // value={item.value}
+ // />
+ // ))}
+ //
+ //
+ //
+ //
+ // );
+}
diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-list-item.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-list-item.tsx
index 6ee378f6..17964aac 100644
--- a/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-list-item.tsx
+++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-list-item.tsx
@@ -1,6 +1,8 @@
'use client';
import { ExpandableListItem } from '@/components/general/ExpandableListItem';
+import { ProfileAvatar } from '@/components/profiles/ProfileAvatar';
+import { SerieIcon } from '@/components/report/chart/SerieIcon';
import { KeyValue, KeyValueSubtle } from '@/components/ui/key-value';
import { useAppParams } from '@/hooks/useAppParams';
import {
@@ -10,6 +12,7 @@ import {
import { cn } from '@/utils/cn';
import { getProfileName } from '@/utils/getters';
import { round } from '@/utils/math';
+import Link from 'next/link';
import { uniq } from 'ramda';
import type { IServiceCreateEventPayload } from '@mixan/db';
@@ -159,75 +162,164 @@ export function EventListItem(props: EventListItemProps) {
.filter((item) => typeof item.value === 'string' && item.value);
return (
- setEvents((p) => uniq([...p, name]))}>
- {name.split('_').join(' ')}
-
- }
- content={
- <>
-
- {profile?.id === props.deviceId && (
-
- )}
- {profile && (
-
- )}
- {path && (
- {
- setFilter('path', path);
- }}
- />
- )}
- >
- }
- image={}
- >
-
- {propertiesList.length > 0 && (
-
-
Your properties
-
- {propertiesList.map((item) => (
- {
- setFilter(
- `properties.${item.name}`,
- item.value ? String(item.value) : '',
- 'is'
- );
- }}
- />
- ))}
-
-
- )}
-
-
Properties
-
- {keyValueList.map((item) => (
- item.onClick?.()}
- key={item.name}
- name={item.name}
- value={item.value}
- />
- ))}
-
+
+
+
+
+
{name.replace(/_/g, ' ')}
+
+
+ {createdAt.toLocaleTimeString()}
-
+
+ {path &&
}
+ {profile && (
+
+ {profile.avatar && }
+ {getProfileName(profile)}
+ >
+ }
+ href={`/${params.organizationId}/${params.projectId}/profiles/${profile.id}`}
+ />
+ )}
+
+
+ {city}
+ >
+ }
+ />
+
+
+ {brand}
+ >
+ }
+ />
+ {browser !== 'WebKit' && browser !== '' && (
+
+
+ {browser}
+ >
+ }
+ />
+ )}
+ {/* {!!profile && (
+
+
+
+ {getProfileName(profile)}
+
+
+ )}
+
+
+ {meta?.conversion && '⭐️ '}
+ {name}
+
+ {' at '}
+ {path}
+ {' from '}
+
+ {city || 'Unknown'}, {country}
+
+ {' using '}
+
+ {brand || device}
+
+
*/}
+
+
);
+ // return (
+ //
setEvents((p) => uniq([...p, name]))}>
+ // {name.split('_').join(' ')}
+ //
+ // }
+ // content={
+ // <>
+ //
+ // {profile?.id === props.deviceId && (
+ //
+ // )}
+ // {profile && (
+ //
+ // )}
+ // {path && (
+ // {
+ // setFilter('path', path);
+ // }}
+ // />
+ // )}
+ // >
+ // }
+ // image={}
+ // >
+ //
+ // {propertiesList.length > 0 && (
+ //
+ //
Your properties
+ //
+ // {propertiesList.map((item) => (
+ // {
+ // setFilter(
+ // `properties.${item.name}`,
+ // item.value ? String(item.value) : '',
+ // 'is'
+ // );
+ // }}
+ // />
+ // ))}
+ //
+ //
+ // )}
+ //
+ //
Properties
+ //
+ // {keyValueList.map((item) => (
+ // item.onClick?.()}
+ // key={item.name}
+ // name={item.name}
+ // value={item.value}
+ // />
+ // ))}
+ //
+ //
+ //
+ //
+ // );
}
diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-list.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-list.tsx
index 16d8ad51..1f01a0cf 100644
--- a/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-list.tsx
+++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-list.tsx
@@ -60,14 +60,14 @@ export function EventList({ data, count }: EventListProps) {
count={count}
take={50}
/>
-
+
{data.map((item, index, list) => (
{showDateHeader(
item.createdAt,
list[index - 1]?.createdAt
) && (
-
+
{item.createdAt.toLocaleDateString()}
)}
diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/layout-project-selector.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/layout-project-selector.tsx
index 52a86986..8232925a 100644
--- a/apps/web/src/app/(app)/[organizationId]/[projectId]/layout-project-selector.tsx
+++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/layout-project-selector.tsx
@@ -19,6 +19,7 @@ export default function LayoutProjectSelector({
return (
span]:rounded-xl',
+ default: 'h-12 w-12 rounded-full [&>span]:rounded-full',
sm: 'h-6 w-6 rounded [&>span]:rounded',
xs: 'h-4 w-4 rounded [&>span]:rounded',
},
diff --git a/apps/web/src/components/report/chart/SerieIcon.tsx b/apps/web/src/components/report/chart/SerieIcon.tsx
index a79160d3..68803599 100644
--- a/apps/web/src/components/report/chart/SerieIcon.tsx
+++ b/apps/web/src/components/report/chart/SerieIcon.tsx
@@ -32,7 +32,9 @@ const createImageIcon = (url: string) => {
const createFlagIcon = (url: string) => {
return function (props: LucideProps) {
- return ;
+ return (
+
+ );
} as LucideIcon;
};
diff --git a/apps/web/src/components/ui/combobox.tsx b/apps/web/src/components/ui/combobox.tsx
index 4db8263a..1e88e49a 100644
--- a/apps/web/src/components/ui/combobox.tsx
+++ b/apps/web/src/components/ui/combobox.tsx
@@ -36,6 +36,7 @@ export interface ComboboxProps {
size?: ButtonProps['size'];
label?: string;
align?: 'start' | 'end' | 'center';
+ portal?: boolean;
}
export type ExtendedComboboxProps = Omit<
@@ -57,6 +58,7 @@ export function Combobox({
icon: Icon,
size,
align = 'start',
+ portal,
}: ComboboxProps) {
const [open, setOpen] = React.useState(false);
const [search, setSearch] = React.useState('');
@@ -87,7 +89,11 @@ export function Combobox({
)}
-
+
{searchable === true && (
{name}
diff --git a/apps/web/src/components/ui/popover.tsx b/apps/web/src/components/ui/popover.tsx
index 966a3175..68538cf5 100644
--- a/apps/web/src/components/ui/popover.tsx
+++ b/apps/web/src/components/ui/popover.tsx
@@ -8,22 +8,32 @@ const PopoverTrigger = PopoverPrimitive.Trigger;
const PopoverContent = React.forwardRef<
React.ElementRef
,
- React.ComponentPropsWithoutRef
->(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
-
-));
+ React.ComponentPropsWithoutRef & {
+ portal?: boolean;
+ }
+>(({ className, align = 'center', sideOffset = 4, portal, ...props }, ref) => {
+ const node = (
+
+ );
+
+ if (portal) {
+ return {node};
+ }
+
+ return node;
+});
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverTrigger, PopoverContent };
diff --git a/apps/web/tailwind.config.js b/apps/web/tailwind.config.js
index 655b7fcd..f4ceb134 100644
--- a/apps/web/tailwind.config.js
+++ b/apps/web/tailwind.config.js
@@ -38,7 +38,7 @@ const twColors = [
'grey',
'slate',
];
-const twColorVariants = ['50', '100', '200', '700'];
+const twColorVariants = ['50', '100', '200', '700', '800', '900'];
/** @type {import('tailwindcss').Config} */
const config = {