diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json
index fdef1dbd..4b69026e 100644
--- a/apps/dashboard/package.json
+++ b/apps/dashboard/package.json
@@ -48,6 +48,7 @@
"@trpc/server": "^10.45.1",
"@types/d3": "^7.4.3",
"bcrypt": "^5.1.1",
+ "bind-event-listener": "^3.0.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"cmdk": "^0.2.1",
diff --git a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/dashboards/list-dashboards.tsx b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/dashboards/list-dashboards.tsx
index bce4e21b..59b75752 100644
--- a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/dashboards/list-dashboards.tsx
+++ b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/dashboards/list-dashboards.tsx
@@ -1,10 +1,9 @@
'use client';
import { api, handleErrorToastOptions } from '@/app/_trpc/client';
-import { Card, CardActions, CardActionsItem } from '@/components/Card';
-import { FullPageEmptyState } from '@/components/FullPageEmptyState';
+import { Card, CardActions, CardActionsItem } from '@/components/card';
+import { FullPageEmptyState } from '@/components/full-page-empty-state';
import { Button } from '@/components/ui/button';
-import { ToastAction } from '@/components/ui/toast';
import { useAppParams } from '@/hooks/useAppParams';
import { pushModal } from '@/modals';
import { LayoutPanelTopIcon, Pencil, PlusIcon, Trash } from 'lucide-react';
diff --git a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/events/event-conversions-list/event-conversions-list.tsx b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/events/event-conversions-list/event-conversions-list.tsx
index 54c015b7..240bc8a9 100644
--- a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/events/event-conversions-list/event-conversions-list.tsx
+++ b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/events/event-conversions-list/event-conversions-list.tsx
@@ -1,7 +1,7 @@
'use client';
import { Fragment } from 'react';
-import { Widget, WidgetHead } from '@/components/Widget';
+import { Widget, WidgetHead } from '@/components/widget';
import { isSameDay } from 'date-fns';
import type { IServiceCreateEventPayload } from '@openpanel/db';
diff --git a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/events/event-conversions-list/index.tsx b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/events/event-conversions-list/index.tsx
index 9dfc78a8..ba50a6f3 100644
--- a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/events/event-conversions-list/index.tsx
+++ b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/events/event-conversions-list/index.tsx
@@ -1,4 +1,4 @@
-import { Widget } from '@/components/Widget';
+import { Widget } from '@/components/widget';
import { db, getEvents } from '@openpanel/db';
diff --git a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/events/event-list.tsx b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/events/event-list.tsx
index a6ecf777..9d8b0282 100644
--- a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/events/event-list.tsx
+++ b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/events/event-list.tsx
@@ -1,8 +1,8 @@
'use client';
import { Fragment, Suspense } from 'react';
-import { FullPageEmptyState } from '@/components/FullPageEmptyState';
-import { Pagination } from '@/components/Pagination';
+import { FullPageEmptyState } from '@/components/full-page-empty-state';
+import { Pagination } from '@/components/pagination';
import { ChartSwitch, ChartSwitchShortcut } from '@/components/report/chart';
import { Button } from '@/components/ui/button';
import { useAppParams } from '@/hooks/useAppParams';
diff --git a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/layout-sidebar.tsx b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/layout-sidebar.tsx
index bb2f402e..1b5552b3 100644
--- a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/layout-sidebar.tsx
+++ b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/layout-sidebar.tsx
@@ -1,7 +1,7 @@
'use client';
import { useEffect, useState } from 'react';
-import { Logo } from '@/components/Logo';
+import { Logo } from '@/components/logo';
import { buttonVariants } from '@/components/ui/button';
import { cn } from '@/utils/cn';
import { Rotate as Hamburger } from 'hamburger-react';
diff --git a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/profiles/[profileId]/page.tsx b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/profiles/[profileId]/page.tsx
index 7a1132ea..88357c9c 100644
--- a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/profiles/[profileId]/page.tsx
+++ b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/profiles/[profileId]/page.tsx
@@ -1,16 +1,15 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
import { OverviewFiltersButtons } from '@/components/overview/filters/overview-filters-buttons';
import { OverviewFiltersDrawer } from '@/components/overview/filters/overview-filters-drawer';
-import { ProfileAvatar } from '@/components/profiles/ProfileAvatar';
+import { ProfileAvatar } from '@/components/profiles/profile-avatar';
import { ChartSwitch } from '@/components/report/chart';
import { SerieIcon } from '@/components/report/chart/SerieIcon';
-import { Widget, WidgetBody, WidgetHead } from '@/components/Widget';
+import { Widget, WidgetBody, WidgetHead } from '@/components/widget';
import {
eventQueryFiltersParser,
eventQueryNamesFilter,
} from '@/hooks/useEventQueryFilters';
import { getExists } from '@/server/pageExists';
-import { getProfileName } from '@/utils/getters';
import { notFound } from 'next/navigation';
import { parseAsInteger, parseAsString } from 'nuqs';
@@ -20,6 +19,7 @@ import {
getEventList,
getEventsCount,
getProfileById,
+ getProfileName,
} from '@openpanel/db';
import type { IChartEvent, IChartInput } from '@openpanel/validation';
diff --git a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/profiles/_profile-list.tsx b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/profiles/_profile-list.tsx
deleted file mode 100644
index e8610a59..00000000
--- a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/profiles/_profile-list.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-'use client';
-
-import { Suspense } from 'react';
-import { FullPageEmptyState } from '@/components/FullPageEmptyState';
-import { Pagination } from '@/components/Pagination';
-import { Button } from '@/components/ui/button';
-import { useCursor } from '@/hooks/useCursor';
-import { useEventQueryFilters } from '@/hooks/useEventQueryFilters';
-import { UsersIcon } from 'lucide-react';
-
-import type { IServiceProfile } from '@openpanel/db';
-
-import { ProfileListItem } from './profile-list-item';
-
-interface ProfileListProps {
- data: IServiceProfile[];
- count: number;
-}
-export function ProfileList({ data, count }: ProfileListProps) {
- const { cursor, setCursor } = useCursor();
- const [filters] = useEventQueryFilters();
-
- return (
-
-
- {data.length === 0 ? (
-
- {cursor !== 0 ? (
- <>
- Looks like you have reached the end of the list
-
- >
- ) : (
- <>
- {filters.length ? (
- Could not find any profiles with your filter
- ) : (
- No profiles have been created yet
- )}
- >
- )}
-
- ) : (
- <>
-
-
- {data.map((item) => (
-
- ))}
-
-
- >
- )}
-
-
- );
-}
diff --git a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/profiles/profile-last-seen/index.tsx b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/profiles/profile-last-seen/index.tsx
index 5b48b3ab..174668b3 100644
--- a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/profiles/profile-last-seen/index.tsx
+++ b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/profiles/profile-last-seen/index.tsx
@@ -3,7 +3,7 @@ import {
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip';
-import { Widget, WidgetBody, WidgetHead } from '@/components/Widget';
+import { Widget, WidgetBody, WidgetHead } from '@/components/widget';
import { cn } from '@/utils/cn';
import { chQuery } from '@openpanel/db';
diff --git a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/profiles/profile-list-item.tsx b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/profiles/profile-list-item.tsx
deleted file mode 100644
index 7822e586..00000000
--- a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/profiles/profile-list-item.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-'use client';
-
-import { ExpandableListItem } from '@/components/general/ExpandableListItem';
-import { ProfileAvatar } from '@/components/profiles/ProfileAvatar';
-import { KeyValue, KeyValueSubtle } from '@/components/ui/key-value';
-import { useAppParams } from '@/hooks/useAppParams';
-import { useEventQueryFilters } from '@/hooks/useEventQueryFilters';
-import { getProfileName } from '@/utils/getters';
-
-import type { IServiceProfile } from '@openpanel/db';
-
-type ProfileListItemProps = IServiceProfile;
-
-export function ProfileListItem(props: ProfileListItemProps) {
- const { id, properties, createdAt } = props;
- const params = useAppParams();
- const [, setFilter] = useEventQueryFilters({ shallow: false });
-
- const renderContent = () => {
- return (
- <>
-
-
- >
- );
- };
-
- return (
- }
- >
- <>
- {properties && (
-
-
Properties
-
- {Object.entries(properties)
- .filter(([, value]) => !!value)
- .map(([key, value]) => (
- setFilter(`properties.${key}`, value)}
- key={key}
- name={key}
- value={value}
- />
- ))}
-
-
- )}
- >
-
- );
-}
diff --git a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/profiles/profile-list/profile-list.tsx b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/profiles/profile-list/profile-list.tsx
index f803dd9f..53004398 100644
--- a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/profiles/profile-list/profile-list.tsx
+++ b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/profiles/profile-list/profile-list.tsx
@@ -1,18 +1,19 @@
'use client';
-import { ListPropertiesIcon } from '@/components/events/ListPropertiesIcon';
-import { FullPageEmptyState } from '@/components/FullPageEmptyState';
-import { Pagination } from '@/components/Pagination';
-import { ProfileAvatar } from '@/components/profiles/ProfileAvatar';
+import { ListPropertiesIcon } from '@/components/events/list-properties-icon';
+import { FullPageEmptyState } from '@/components/full-page-empty-state';
+import { Pagination } from '@/components/pagination';
+import { ProfileAvatar } from '@/components/profiles/profile-avatar';
import { Button } from '@/components/ui/button';
import { Tooltiper } from '@/components/ui/tooltip';
-import { Widget, WidgetHead } from '@/components/Widget';
+import { Widget, WidgetHead } from '@/components/widget';
import { WidgetTable } from '@/components/widget-table';
import { useAppParams } from '@/hooks/useAppParams';
import { useCursor } from '@/hooks/useCursor';
import { UsersIcon } from 'lucide-react';
import Link from 'next/link';
+import { getProfileName } from '@openpanel/db';
import type { IServiceProfile } from '@openpanel/db';
interface ProfileListProps {
@@ -49,7 +50,7 @@ export function ProfileList({ data, count }: ProfileListProps) {
className="flex gap-2 items-center font-medium"
>
- {profile.firstName} {profile.lastName}
+ {getProfileName(profile)}
);
},
diff --git a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/profiles/profile-top/index.tsx b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/profiles/profile-top/index.tsx
index 6870e9bb..b448ee00 100644
--- a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/profiles/profile-top/index.tsx
+++ b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/profiles/profile-top/index.tsx
@@ -1,10 +1,10 @@
-import { ListPropertiesIcon } from '@/components/events/ListPropertiesIcon';
-import { ProfileAvatar } from '@/components/profiles/ProfileAvatar';
-import { Widget, WidgetHead } from '@/components/Widget';
+import { ListPropertiesIcon } from '@/components/events/list-properties-icon';
+import { ProfileAvatar } from '@/components/profiles/profile-avatar';
+import { Widget, WidgetHead } from '@/components/widget';
import { WidgetTable } from '@/components/widget-table';
import Link from 'next/link';
-import { chQuery, getProfiles } from '@openpanel/db';
+import { chQuery, getProfileName, getProfiles } from '@openpanel/db';
interface Props {
projectId: string;
@@ -24,7 +24,7 @@ export default async function ProfileTopServer({
const list = res.map((item) => {
return {
count: item.count,
- ...(profiles.find((p) => p.id === item.profile_id) ?? {}),
+ ...(profiles.find((p) => p.id === item.profile_id)! ?? {}),
};
});
@@ -35,7 +35,7 @@ export default async function ProfileTopServer({
!!item.id)}
- keyExtractor={(item) => item.id!}
+ keyExtractor={(item) => item.id}
columns={[
{
name: 'Name',
@@ -46,7 +46,7 @@ export default async function ProfileTopServer({
className="flex gap-2 items-center font-medium"
>
- {profile.firstName} {profile.lastName}
+ {getProfileName(profile)}
);
},
diff --git a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/settings/clients/list-clients.tsx b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/settings/clients/list-clients.tsx
index ff1a48cb..f8e61509 100644
--- a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/settings/clients/list-clients.tsx
+++ b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/settings/clients/list-clients.tsx
@@ -2,9 +2,8 @@
import { StickyBelowHeader } from '@/app/(app)/[organizationId]/[projectId]/layout-sticky-below-header';
import { columns } from '@/components/clients/table';
-import { DataTable } from '@/components/DataTable';
+import { DataTable } from '@/components/data-table';
import { Button } from '@/components/ui/button';
-import { useAppParams } from '@/hooks/useAppParams';
import { pushModal } from '@/modals';
import { PlusIcon } from 'lucide-react';
@@ -14,17 +13,12 @@ interface ListClientsProps {
clients: Awaited>;
}
export default function ListClients({ clients }: ListClientsProps) {
- const organizationId = useAppParams().organizationId;
-
return (
<>
-
-
+
>
);
diff --git a/apps/dashboard/src/components/report/sidebar/ReportBreakdowns.tsx b/apps/dashboard/src/components/report/sidebar/ReportBreakdowns.tsx
index bd23be75..60e49148 100644
--- a/apps/dashboard/src/components/report/sidebar/ReportBreakdowns.tsx
+++ b/apps/dashboard/src/components/report/sidebar/ReportBreakdowns.tsx
@@ -1,7 +1,7 @@
'use client';
import { api } from '@/app/_trpc/client';
-import { ColorSquare } from '@/components/ColorSquare';
+import { ColorSquare } from '@/components/color-square';
import { Combobox } from '@/components/ui/combobox';
import { useAppParams } from '@/hooks/useAppParams';
import { useDispatch, useSelector } from '@/redux';
diff --git a/apps/dashboard/src/components/report/sidebar/ReportEvents.tsx b/apps/dashboard/src/components/report/sidebar/ReportEvents.tsx
index db2f93f2..67b433a3 100644
--- a/apps/dashboard/src/components/report/sidebar/ReportEvents.tsx
+++ b/apps/dashboard/src/components/report/sidebar/ReportEvents.tsx
@@ -1,9 +1,9 @@
'use client';
-import { ColorSquare } from '@/components/ColorSquare';
-import { Dropdown } from '@/components/Dropdown';
+import { ColorSquare } from '@/components/color-square';
import { Checkbox } from '@/components/ui/checkbox';
import { Combobox } from '@/components/ui/combobox';
+import { DropdownMenuComposed } from '@/components/ui/dropdown-menu';
import { Input } from '@/components/ui/input';
import { useAppParams } from '@/hooks/useAppParams';
import { useDebounceFn } from '@/hooks/useDebounceFn';
@@ -94,7 +94,7 @@ export function ReportEvents() {
{/* Segment and Filter buttons */}
- {
dispatch(
changeEvent({
@@ -166,7 +166,7 @@ export function ReportEvents() {
>
)}
-
+
{/* */}
diff --git a/apps/dashboard/src/components/report/sidebar/filters/FilterItem.tsx b/apps/dashboard/src/components/report/sidebar/filters/FilterItem.tsx
index b41f9ca9..fb37473d 100644
--- a/apps/dashboard/src/components/report/sidebar/filters/FilterItem.tsx
+++ b/apps/dashboard/src/components/report/sidebar/filters/FilterItem.tsx
@@ -1,8 +1,8 @@
import { api } from '@/app/_trpc/client';
-import { ColorSquare } from '@/components/ColorSquare';
-import { Dropdown } from '@/components/Dropdown';
+import { ColorSquare } from '@/components/color-square';
import { Button } from '@/components/ui/button';
import { ComboboxAdvanced } from '@/components/ui/combobox-advanced';
+import { DropdownMenuComposed } from '@/components/ui/dropdown-menu';
import { RenderDots } from '@/components/ui/RenderDots';
import { useAppParams } from '@/hooks/useAppParams';
import { useMappings } from '@/hooks/useMappings';
@@ -104,7 +104,7 @@ export function FilterItem({ filter, event }: FilterProps) {
-
({
value: key,
@@ -115,7 +115,7 @@ export function FilterItem({ filter, event }: FilterProps) {
{operators[filter.operator]}
-
+
{
+ children: React.ReactNode;
+ label?: string;
+ items: {
+ label: string;
+ value: Value;
+ }[];
+ onChange?: (value: Value) => void;
+}
+
+export function DropdownMenuComposed({
+ children,
+ label,
+ items,
+ onChange,
+}: DropdownProps) {
+ return (
+
+ {children}
+
+ {label && (
+ <>
+ {label}
+
+ >
+ )}
+
+ {items.map((item) => (
+ {
+ onChange?.(item.value);
+ }}
+ >
+ {item.label}
+
+ ))}
+
+
+
+ );
+}
+
export {
DropdownMenu,
DropdownMenuTrigger,
diff --git a/apps/dashboard/src/components/ui/key-value.tsx b/apps/dashboard/src/components/ui/key-value.tsx
index be2a718f..7fe01bbf 100644
--- a/apps/dashboard/src/components/ui/key-value.tsx
+++ b/apps/dashboard/src/components/ui/key-value.tsx
@@ -1,17 +1,16 @@
-import { isValidElement } from 'react';
import { cn } from '@/utils/cn';
import Link from 'next/link';
interface KeyValueProps {
name: string;
- value: unknown;
+ value: any;
onClick?: () => void;
href?: string;
}
export function KeyValue({ href, onClick, name, value }: KeyValueProps) {
const clickable = href || onClick;
- const Component = href ? (Link as any) : onClick ? 'button' : 'div';
+ const Component = (href ? Link : onClick ? 'button' : 'div') as 'button';
return (
);
}
-
-export function KeyValueSubtle({ href, onClick, name, value }: KeyValueProps) {
- const clickable = href || onClick;
- const Component = href ? (Link as any) : onClick ? 'button' : 'div';
- return (
-
- {name}
-
- {value}
-
-
- );
-}
diff --git a/apps/dashboard/src/hooks/useProfileProperties.ts b/apps/dashboard/src/hooks/useProfileProperties.ts
index 3cd44a50..154db512 100644
--- a/apps/dashboard/src/hooks/useProfileProperties.ts
+++ b/apps/dashboard/src/hooks/useProfileProperties.ts
@@ -1,9 +1,8 @@
import { api } from '@/app/_trpc/client';
-export function useProfileProperties(projectId: string, event?: string) {
+export function useProfileProperties(projectId: string) {
const query = api.profile.properties.useQuery({
projectId: projectId,
- event,
});
return query.data ?? [];
diff --git a/apps/dashboard/src/hooks/useQueryParams.ts b/apps/dashboard/src/hooks/useQueryParams.ts
deleted file mode 100644
index afe65fc1..00000000
--- a/apps/dashboard/src/hooks/useQueryParams.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { useMemo } from 'react';
-import { useRouter } from 'next/router';
-import type { z } from 'zod';
-
-export function useQueryParams(zod: Z) {
- const router = useRouter();
- const value = zod.safeParse(router.query);
-
- return useMemo(() => {
- function setQueryParams(newValue: Partial>) {
- return router
- .replace({
- pathname: router.pathname,
- query: {
- ...router.query,
- ...newValue,
- },
- })
- .catch(() => {
- // ignore
- });
- }
-
- if (value.success) {
- return { ...value.data, setQueryParams } as z.infer & {
- setQueryParams: typeof setQueryParams;
- };
- }
- return { ...router.query, setQueryParams } as z.infer & {
- setQueryParams: typeof setQueryParams;
- };
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [router.asPath, value.success]);
-}
diff --git a/apps/dashboard/src/hooks/useRefetchActive.ts b/apps/dashboard/src/hooks/useRefetchActive.ts
deleted file mode 100644
index 5f96d435..00000000
--- a/apps/dashboard/src/hooks/useRefetchActive.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { useQueryClient } from '@tanstack/react-query';
-
-export function useRefetchActive() {
- const client = useQueryClient();
- return () => client.refetchQueries({ type: 'active' });
-}
diff --git a/apps/dashboard/src/hooks/useRouterBeforeLeave.ts b/apps/dashboard/src/hooks/useRouterBeforeLeave.ts
deleted file mode 100644
index cfe061f9..00000000
--- a/apps/dashboard/src/hooks/useRouterBeforeLeave.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { useEffect, useRef } from 'react';
-import { useRouter } from 'next/router';
-
-export function useRouterBeforeLeave(callback: () => void) {
- const router = useRouter();
- const prevUrl = useRef(router.asPath);
-
- useEffect(() => {
- const handleRouteChange = (url: string) => {
- if (prevUrl.current !== url) {
- callback();
- }
- prevUrl.current = url;
- };
-
- router.events.on('routeChangeStart', handleRouteChange);
- return () => {
- router.events.off('routeChangeStart', handleRouteChange);
- };
- }, [router, callback]);
-}
diff --git a/apps/dashboard/src/hooks/useSetCookie.ts b/apps/dashboard/src/hooks/useSetCookie.ts
deleted file mode 100644
index 58097ecd..00000000
--- a/apps/dashboard/src/hooks/useSetCookie.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { usePathname, useRouter } from 'next/navigation';
-
-export function useSetCookie() {
- const router = useRouter();
- const pathname = usePathname();
- return (key: string, value: string, path?: string) => {
- fetch(`/api/cookie?${key}=${value}`).then(() => {
- if (path && path !== pathname) {
- router.refresh();
- router.replace(path);
- } else {
- router.refresh();
- }
- });
- };
-}
diff --git a/apps/dashboard/src/mappings.json b/apps/dashboard/src/mappings.json
index d5cb7c91..f2d7122d 100644
--- a/apps/dashboard/src/mappings.json
+++ b/apps/dashboard/src/mappings.json
@@ -1,146 +1,6 @@
[
{
- "id": "clbow140n0228u99ui1t24l85",
- "name": "Mjölkproteinfritt"
- },
- {
- "id": "cl04a3trn0015ix50tzy40pyf",
- "name": "Måltider"
- },
- {
- "id": "cl04a5p7s0401ix505n75yfcn",
- "name": "Svårighetsgrad"
- },
- {
- "id": "cl04a7k3i0813ix50aau6yxqg",
- "name": "Tid"
- },
- {
- "id": "cl04a47fu0103ix50dqckz2vc",
- "name": "Frukost"
- },
- {
- "id": "cl04a4hvu0152ix50o0w4iy8l",
- "name": "Mellis"
- },
- {
- "id": "cl04a58ju0281ix50kdmcwst6",
- "name": "Dessert"
- },
- {
- "id": "cl04a5fjc0321ix50xiwhuydy",
- "name": "Smakportioner"
- },
- {
- "id": "cl04a5kcu0361ix50bnmbhoxz",
- "name": "Plockmat"
- },
- {
- "id": "cl04a60sk0496ix50et419drf",
- "name": "Medium"
- },
- {
- "id": "cl04a67lx0536ix50sstoxnhi",
- "name": "Avancerat"
- },
- {
- "id": "cl04a7qi60850ix50je7vaxo3",
- "name": "0-10 min"
- },
- {
- "id": "cl04a7vxi0890ix50veumcuyu",
- "name": "10-20 min"
- },
- {
- "id": "cl04a82bj0930ix50bboh3tl9",
- "name": "20-30 min"
- },
- {
- "id": "cl04a8a7a0970ix50uet02cqh",
- "name": "30-40 min"
- },
- {
- "id": "cl04a8g151010ix50z4cnf2kg",
- "name": "40-50 min"
- },
- {
- "id": "cl04a8mqy1050ix50z0d1ho1a",
- "name": "50-60 min"
- },
- {
- "id": "cl04a5ujg0447ix50vd3vor87",
- "name": "Lätt"
- },
- {
- "id": "cl04a4qv60201ix50b8q5kn9r",
- "name": "Lunch & Middag"
- },
- {
- "id": "clak50jem0072ri9ugwygg5ko",
- "name": "Annat"
- },
- {
- "id": "clak510qm0120ri9upqkca39s",
- "name": "För hela familjen"
- },
- {
- "id": "clak59l8x0085yd9uzllcuci5",
- "name": "Under 3 ingredienser"
- },
- {
- "id": "clak59l8y0087yd9u53qperp8",
- "name": "Under 5 ingredienser"
- },
- {
- "id": "claslo2sg0404no9uo2tckm5i",
- "name": "Huvudingredienser"
- },
- {
- "id": "claslv9s20491no9ugo4fd9ns",
- "name": "Fisk"
- },
- {
- "id": "claslv9s30493no9umug5po29",
- "name": "Kyckling"
- },
- {
- "id": "claslv9s40495no9umor61pql",
- "name": "Kött"
- },
- {
- "id": "claslv9s40497no9uttwkt47n",
- "name": "Korv"
- },
- {
- "id": "claslv9s50499no9uch0lhs9i",
- "name": "Vegetariskt"
- },
- {
- "id": "clb1y44f40128np9uufck0iqf",
- "name": "Årstider"
- },
- {
- "id": "clb1y4ks80202np9uh43c84ts",
- "name": "Jul"
- },
- {
- "id": "clbovy0fd0081u99u8dr0yplr",
- "name": "Allergier"
- },
- {
- "id": "clbow140p0230u99uk9e7g1u1",
- "name": "Äggfritt"
- },
- {
- "id": "clbow140q0232u99uy3lwukvc",
- "name": "Vetefritt"
- },
- {
- "id": "clbow140q0234u99uiyrujxd4",
- "name": "Glutenfritt"
- },
- {
- "id": "clbow140r0236u99u5333gpei",
- "name": "Nötfritt"
+ "id": "123",
+ "name": "123"
}
]
diff --git a/apps/dashboard/src/modals/AddDashboard.tsx b/apps/dashboard/src/modals/AddDashboard.tsx
index fde7d54a..64f8059a 100644
--- a/apps/dashboard/src/modals/AddDashboard.tsx
+++ b/apps/dashboard/src/modals/AddDashboard.tsx
@@ -1,8 +1,8 @@
'use client';
import { api, handleError } from '@/app/_trpc/client';
-import { ButtonContainer } from '@/components/ButtonContainer';
-import { InputWithLabel } from '@/components/forms/InputWithLabel';
+import { ButtonContainer } from '@/components/button-container';
+import { InputWithLabel } from '@/components/forms/input-with-label';
import { Button } from '@/components/ui/button';
import { useAppParams } from '@/hooks/useAppParams';
import { zodResolver } from '@hookform/resolvers/zod';
diff --git a/apps/dashboard/src/modals/AddProject.tsx b/apps/dashboard/src/modals/AddProject.tsx
index 6ee2c799..6032cc03 100644
--- a/apps/dashboard/src/modals/AddProject.tsx
+++ b/apps/dashboard/src/modals/AddProject.tsx
@@ -1,8 +1,8 @@
'use client';
import { api, handleError } from '@/app/_trpc/client';
-import { ButtonContainer } from '@/components/ButtonContainer';
-import { InputWithLabel } from '@/components/forms/InputWithLabel';
+import { ButtonContainer } from '@/components/button-container';
+import { InputWithLabel } from '@/components/forms/input-with-label';
import { Button } from '@/components/ui/button';
import { zodResolver } from '@hookform/resolvers/zod';
import { useRouter } from 'next/navigation';
diff --git a/apps/dashboard/src/modals/AddReference.tsx b/apps/dashboard/src/modals/AddReference.tsx
index b71802f5..833f0d90 100644
--- a/apps/dashboard/src/modals/AddReference.tsx
+++ b/apps/dashboard/src/modals/AddReference.tsx
@@ -1,8 +1,8 @@
'use client';
import { api, handleError } from '@/app/_trpc/client';
-import { ButtonContainer } from '@/components/ButtonContainer';
-import { InputWithLabel } from '@/components/forms/InputWithLabel';
+import { ButtonContainer } from '@/components/button-container';
+import { InputWithLabel } from '@/components/forms/input-with-label';
import { Button } from '@/components/ui/button';
import { Calendar } from '@/components/ui/calendar';
import { useAppParams } from '@/hooks/useAppParams';
diff --git a/apps/dashboard/src/modals/Confirm.tsx b/apps/dashboard/src/modals/Confirm.tsx
index 126d17ba..395e9765 100644
--- a/apps/dashboard/src/modals/Confirm.tsx
+++ b/apps/dashboard/src/modals/Confirm.tsx
@@ -1,6 +1,6 @@
'use client';
-import { ButtonContainer } from '@/components/ButtonContainer';
+import { ButtonContainer } from '@/components/button-container';
import { Button } from '@/components/ui/button';
import { popModal } from '.';
diff --git a/apps/dashboard/src/modals/EditClient.tsx b/apps/dashboard/src/modals/EditClient.tsx
index 4b295e47..962a4696 100644
--- a/apps/dashboard/src/modals/EditClient.tsx
+++ b/apps/dashboard/src/modals/EditClient.tsx
@@ -1,8 +1,8 @@
'use client';
import { api, handleError } from '@/app/_trpc/client';
-import { ButtonContainer } from '@/components/ButtonContainer';
-import { InputWithLabel } from '@/components/forms/InputWithLabel';
+import { ButtonContainer } from '@/components/button-container';
+import { InputWithLabel } from '@/components/forms/input-with-label';
import { Button } from '@/components/ui/button';
import { zodResolver } from '@hookform/resolvers/zod';
import { useRouter } from 'next/navigation';
diff --git a/apps/dashboard/src/modals/EditDashboard.tsx b/apps/dashboard/src/modals/EditDashboard.tsx
index 52b8c500..a9b1e424 100644
--- a/apps/dashboard/src/modals/EditDashboard.tsx
+++ b/apps/dashboard/src/modals/EditDashboard.tsx
@@ -1,8 +1,8 @@
'use client';
import { api, handleError } from '@/app/_trpc/client';
-import { ButtonContainer } from '@/components/ButtonContainer';
-import { InputWithLabel } from '@/components/forms/InputWithLabel';
+import { ButtonContainer } from '@/components/button-container';
+import { InputWithLabel } from '@/components/forms/input-with-label';
import { Button } from '@/components/ui/button';
import { zodResolver } from '@hookform/resolvers/zod';
import { useRouter } from 'next/navigation';
diff --git a/apps/dashboard/src/modals/EditProject.tsx b/apps/dashboard/src/modals/EditProject.tsx
index f5c1a8e4..e148a67a 100644
--- a/apps/dashboard/src/modals/EditProject.tsx
+++ b/apps/dashboard/src/modals/EditProject.tsx
@@ -1,8 +1,8 @@
'use client';
import { api, handleError } from '@/app/_trpc/client';
-import { ButtonContainer } from '@/components/ButtonContainer';
-import { InputWithLabel } from '@/components/forms/InputWithLabel';
+import { ButtonContainer } from '@/components/button-container';
+import { InputWithLabel } from '@/components/forms/input-with-label';
import { Button } from '@/components/ui/button';
import { zodResolver } from '@hookform/resolvers/zod';
import { useRouter } from 'next/navigation';
diff --git a/apps/dashboard/src/modals/EditReport.tsx b/apps/dashboard/src/modals/EditReport.tsx
index bcbabab6..42f299be 100644
--- a/apps/dashboard/src/modals/EditReport.tsx
+++ b/apps/dashboard/src/modals/EditReport.tsx
@@ -1,7 +1,7 @@
'use client';
-import { ButtonContainer } from '@/components/ButtonContainer';
-import { InputWithLabel } from '@/components/forms/InputWithLabel';
+import { ButtonContainer } from '@/components/button-container';
+import { InputWithLabel } from '@/components/forms/input-with-label';
import { Button } from '@/components/ui/button';
import { zodResolver } from '@hookform/resolvers/zod';
import type { SubmitHandler } from 'react-hook-form';
diff --git a/apps/dashboard/src/modals/SaveReport.tsx b/apps/dashboard/src/modals/SaveReport.tsx
index 337301b4..b1ca490f 100644
--- a/apps/dashboard/src/modals/SaveReport.tsx
+++ b/apps/dashboard/src/modals/SaveReport.tsx
@@ -1,8 +1,8 @@
'use client';
import { api, handleError } from '@/app/_trpc/client';
-import { ButtonContainer } from '@/components/ButtonContainer';
-import { InputWithLabel } from '@/components/forms/InputWithLabel';
+import { ButtonContainer } from '@/components/button-container';
+import { InputWithLabel } from '@/components/forms/input-with-label';
import { Button } from '@/components/ui/button';
import { Combobox } from '@/components/ui/combobox';
import { Label } from '@/components/ui/label';
@@ -36,7 +36,7 @@ export default function SaveReport({ report }: SaveReportProps) {
const searchParams = useSearchParams();
const dashboardId = searchParams?.get('dashboardId') ?? undefined;
- const save = api.report.save.useMutation({
+ const save = api.report.create.useMutation({
onError: handleError,
onSuccess(res) {
toast('Success', {
diff --git a/apps/dashboard/src/modals/ShareOverviewModal.tsx b/apps/dashboard/src/modals/ShareOverviewModal.tsx
index 89cfe684..1e79e8c6 100644
--- a/apps/dashboard/src/modals/ShareOverviewModal.tsx
+++ b/apps/dashboard/src/modals/ShareOverviewModal.tsx
@@ -1,8 +1,8 @@
'use client';
import { api, handleError } from '@/app/_trpc/client';
-import { ButtonContainer } from '@/components/ButtonContainer';
-import { InputWithLabel } from '@/components/forms/InputWithLabel';
+import { ButtonContainer } from '@/components/button-container';
+import { InputWithLabel } from '@/components/forms/input-with-label';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { useAppParams } from '@/hooks/useAppParams';
diff --git a/apps/dashboard/src/server/api/root.ts b/apps/dashboard/src/server/api/root.ts
index 0b09b4b7..17fecfc4 100644
--- a/apps/dashboard/src/server/api/root.ts
+++ b/apps/dashboard/src/server/api/root.ts
@@ -11,7 +11,6 @@ import { projectRouter } from './routers/project';
import { referenceRouter } from './routers/reference';
import { reportRouter } from './routers/report';
import { shareRouter } from './routers/share';
-import { uiRouter } from './routers/ui';
import { userRouter } from './routers/user';
/**
@@ -29,7 +28,6 @@ export const appRouter = createTRPCRouter({
client: clientRouter,
event: eventRouter,
profile: profileRouter,
- ui: uiRouter,
share: shareRouter,
onboarding: onboardingRouter,
reference: referenceRouter,
diff --git a/apps/dashboard/src/server/api/routers/chart.helpers.ts b/apps/dashboard/src/server/api/routers/chart.helpers.ts
index 220dc277..204f1e42 100644
--- a/apps/dashboard/src/server/api/routers/chart.helpers.ts
+++ b/apps/dashboard/src/server/api/routers/chart.helpers.ts
@@ -1,10 +1,17 @@
-import { getDaysOldDate } from '@/utils/date';
import { round } from '@/utils/math';
+import { subDays } from 'date-fns';
import * as mathjs from 'mathjs';
-import { sort } from 'ramda';
+import { repeat, reverse, sort } from 'ramda';
import { alphabetIds, NOT_SET_VALUE } from '@openpanel/constants';
-import { chQuery, convertClickhouseDateToJs, getChartSql } from '@openpanel/db';
+import {
+ chQuery,
+ convertClickhouseDateToJs,
+ createSqlBuilder,
+ formatClickhouseDate,
+ getChartSql,
+ getEventFiltersWhereClause,
+} from '@openpanel/db';
import type {
IChartEvent,
IChartInput,
@@ -317,7 +324,7 @@ export function getDatesFromRange(range: IChartRange) {
let days = 1;
if (range === '24h') {
- const startDate = getDaysOldDate(days);
+ const startDate = subDays(new Date(), days);
const endDate = new Date();
return {
startDate: startDate.toUTCString(),
@@ -337,7 +344,7 @@ export function getDatesFromRange(range: IChartRange) {
days = 365;
}
- const startDate = getDaysOldDate(days);
+ const startDate = subDays(new Date(), days);
startDate.setUTCHours(0, 0, 0, 0);
const endDate = new Date();
endDate.setUTCHours(23, 59, 59, 999);
@@ -347,10 +354,197 @@ export function getDatesFromRange(range: IChartRange) {
};
}
-export function getChartStartEndDate(
- input: Pick
-) {
- return input.startDate && input.endDate
- ? { startDate: input.startDate, endDate: input.endDate }
- : getDatesFromRange(input.range);
+export function getChartStartEndDate({
+ startDate,
+ endDate,
+ range,
+}: Pick) {
+ return startDate && endDate
+ ? { startDate: startDate, endDate: endDate }
+ : getDatesFromRange(range);
+}
+
+export function getChartPrevStartEndDate({
+ startDate,
+ endDate,
+ range,
+}: {
+ startDate: string;
+ endDate: string;
+ range: IChartRange;
+}) {
+ let diff = 0;
+
+ switch (range) {
+ case '30min': {
+ diff = 1000 * 60 * 30;
+ break;
+ }
+ case '1h': {
+ diff = 1000 * 60 * 60;
+ break;
+ }
+ case '24h':
+ case 'today': {
+ diff = 1000 * 60 * 60 * 24;
+ break;
+ }
+ case '7d': {
+ diff = 1000 * 60 * 60 * 24 * 7;
+ break;
+ }
+ case '14d': {
+ diff = 1000 * 60 * 60 * 24 * 14;
+ break;
+ }
+ case '1m': {
+ diff = 1000 * 60 * 60 * 24 * 30;
+ break;
+ }
+ case '3m': {
+ diff = 1000 * 60 * 60 * 24 * 90;
+ break;
+ }
+ case '6m': {
+ diff = 1000 * 60 * 60 * 24 * 180;
+ break;
+ }
+ }
+
+ return {
+ startDate: new Date(new Date(startDate).getTime() - diff).toISOString(),
+ endDate: new Date(new Date(endDate).getTime() - diff).toISOString(),
+ };
+}
+
+export async function getFunnelData({ projectId, ...payload }: IChartInput) {
+ const { startDate, endDate } = getChartStartEndDate(payload);
+
+ if (payload.events.length === 0) {
+ return {
+ totalSessions: 0,
+ steps: [],
+ };
+ }
+
+ const funnels = payload.events.map((event) => {
+ const { sb, getWhere } = createSqlBuilder();
+ sb.where = getEventFiltersWhereClause(event.filters);
+ sb.where.name = `name = '${event.name}'`;
+ return getWhere().replace('WHERE ', '');
+ });
+
+ const innerSql = `SELECT
+ session_id,
+ windowFunnel(6048000000000000,'strict_increase')(toUnixTimestamp(created_at), ${funnels.join(', ')}) AS level
+ FROM events
+ WHERE (project_id = '${projectId}' AND created_at >= '${formatClickhouseDate(startDate)}') AND (created_at <= '${formatClickhouseDate(endDate)}')
+ GROUP BY session_id`;
+
+ const sql = `SELECT level, count() AS count FROM (${innerSql}) GROUP BY level ORDER BY level DESC`;
+
+ const [funnelRes, sessionRes] = await Promise.all([
+ chQuery<{ level: number; count: number }>(sql),
+ chQuery<{ count: number }>(
+ `SELECT count(name) as count FROM events WHERE project_id = '${projectId}' AND name = 'session_start' AND (created_at >= '${formatClickhouseDate(startDate)}') AND (created_at <= '${formatClickhouseDate(endDate)}')`
+ ),
+ ]);
+
+ if (funnelRes[0]?.level !== payload.events.length) {
+ funnelRes.unshift({
+ level: payload.events.length,
+ count: 0,
+ });
+ }
+
+ const totalSessions = sessionRes[0]?.count ?? 0;
+ const filledFunnelRes = funnelRes.reduce(
+ (acc, item, index) => {
+ const diff =
+ index !== 0 ? (acc[acc.length - 1]?.level ?? 0) - item.level : 1;
+
+ if (diff > 1) {
+ acc.push(
+ ...reverse(
+ repeat({}, diff - 1).map((_, index) => ({
+ count: acc[acc.length - 1]?.count ?? 0,
+ level: item.level + index + 1,
+ }))
+ )
+ );
+ }
+
+ return [
+ ...acc,
+ {
+ count: item.count + (acc[acc.length - 1]?.count ?? 0),
+ level: item.level,
+ },
+ ];
+ },
+ [] as typeof funnelRes
+ );
+
+ const steps = reverse(filledFunnelRes)
+ .filter((item) => item.level !== 0)
+ .reduce(
+ (acc, item, index, list) => {
+ const prev = list[index - 1] ?? { count: totalSessions };
+ return [
+ ...acc,
+ {
+ event: payload.events[item.level - 1]!,
+ before: prev.count,
+ current: item.count,
+ dropoff: {
+ count: prev.count - item.count,
+ percent: 100 - (item.count / prev.count) * 100,
+ },
+ percent: (item.count / totalSessions) * 100,
+ prevPercent: (prev.count / totalSessions) * 100,
+ },
+ ];
+ },
+ [] as {
+ event: IChartEvent;
+ before: number;
+ current: number;
+ dropoff: {
+ count: number;
+ percent: number;
+ };
+ percent: number;
+ prevPercent: number;
+ }[]
+ );
+
+ return {
+ totalSessions,
+ steps,
+ };
+}
+
+export async function getSeriesFromEvents(input: IChartInput) {
+ const { startDate, endDate } =
+ input.startDate && input.endDate
+ ? {
+ startDate: input.startDate,
+ endDate: input.endDate,
+ }
+ : getDatesFromRange(input.range);
+
+ const series = (
+ await Promise.all(
+ input.events.map(async (event) =>
+ getChartData({
+ ...input,
+ startDate,
+ endDate,
+ event,
+ })
+ )
+ )
+ ).flat();
+
+ return withFormula(input, series);
}
diff --git a/apps/dashboard/src/server/api/routers/chart.ts b/apps/dashboard/src/server/api/routers/chart.ts
index f4da0c29..20be96e7 100644
--- a/apps/dashboard/src/server/api/routers/chart.ts
+++ b/apps/dashboard/src/server/api/routers/chart.ts
@@ -4,132 +4,20 @@ import {
publicProcedure,
} from '@/server/api/trpc';
import { average, max, min, round, sum } from '@/utils/math';
-import { flatten, map, pipe, prop, repeat, reverse, sort, uniq } from 'ramda';
+import { flatten, map, pipe, prop, sort, uniq } from 'ramda';
import { z } from 'zod';
-import {
- chQuery,
- createSqlBuilder,
- formatClickhouseDate,
- getEventFiltersWhereClause,
-} from '@openpanel/db';
+import { chQuery, createSqlBuilder } from '@openpanel/db';
import { zChartInput } from '@openpanel/validation';
import type { IChartEvent, IChartInput } from '@openpanel/validation';
import {
- getChartData,
+ getChartPrevStartEndDate,
getChartStartEndDate,
- getDatesFromRange,
- withFormula,
+ getFunnelData,
+ getSeriesFromEvents,
} from './chart.helpers';
-async function getFunnelData({ projectId, ...payload }: IChartInput) {
- const { startDate, endDate } = getChartStartEndDate(payload);
-
- if (payload.events.length === 0) {
- return {
- totalSessions: 0,
- steps: [],
- };
- }
-
- const funnels = payload.events.map((event) => {
- const { sb, getWhere } = createSqlBuilder();
- sb.where = getEventFiltersWhereClause(event.filters);
- sb.where.name = `name = '${event.name}'`;
- return getWhere().replace('WHERE ', '');
- });
-
- const innerSql = `SELECT
- session_id,
- windowFunnel(6048000000000000,'strict_increase')(toUnixTimestamp(created_at), ${funnels.join(', ')}) AS level
- FROM events
- WHERE (project_id = '${projectId}' AND created_at >= '${formatClickhouseDate(startDate)}') AND (created_at <= '${formatClickhouseDate(endDate)}')
- GROUP BY session_id`;
-
- const sql = `SELECT level, count() AS count FROM (${innerSql}) GROUP BY level ORDER BY level DESC`;
-
- const [funnelRes, sessionRes] = await Promise.all([
- chQuery<{ level: number; count: number }>(sql),
- chQuery<{ count: number }>(
- `SELECT count(name) as count FROM events WHERE project_id = '${projectId}' AND name = 'session_start' AND (created_at >= '${formatClickhouseDate(startDate)}') AND (created_at <= '${formatClickhouseDate(endDate)}')`
- ),
- ]);
-
- if (funnelRes[0]?.level !== payload.events.length) {
- funnelRes.unshift({
- level: payload.events.length,
- count: 0,
- });
- }
-
- const totalSessions = sessionRes[0]?.count ?? 0;
- const filledFunnelRes = funnelRes.reduce(
- (acc, item, index) => {
- const diff =
- index !== 0 ? (acc[acc.length - 1]?.level ?? 0) - item.level : 1;
-
- if (diff > 1) {
- acc.push(
- ...reverse(
- repeat({}, diff - 1).map((_, index) => ({
- count: acc[acc.length - 1]?.count ?? 0,
- level: item.level + index + 1,
- }))
- )
- );
- }
-
- return [
- ...acc,
- {
- count: item.count + (acc[acc.length - 1]?.count ?? 0),
- level: item.level,
- },
- ];
- },
- [] as typeof funnelRes
- );
-
- const steps = reverse(filledFunnelRes)
- .filter((item) => item.level !== 0)
- .reduce(
- (acc, item, index, list) => {
- const prev = list[index - 1] ?? { count: totalSessions };
- return [
- ...acc,
- {
- event: payload.events[item.level - 1]!,
- before: prev.count,
- current: item.count,
- dropoff: {
- count: prev.count - item.count,
- percent: 100 - (item.count / prev.count) * 100,
- },
- percent: (item.count / totalSessions) * 100,
- prevPercent: (prev.count / totalSessions) * 100,
- },
- ];
- },
- [] as {
- event: IChartEvent;
- before: number;
- current: number;
- dropoff: {
- count: number;
- percent: number;
- };
- percent: number;
- prevPercent: number;
- }[]
- );
-
- return {
- totalSessions,
- steps,
- };
-}
-
type PreviousValue = {
value: number;
diff: number | null;
@@ -243,7 +131,7 @@ export const chartRouter = createTRPCRouter({
.replace(/^properties\./, '')
.replace('.*.', '.%.')}')) as values`;
} else {
- sb.select.values = `${property} as values`;
+ sb.select.values = `distinct ${property} as values`;
}
const events = await chQuery<{ values: string[] }>(getSql());
@@ -266,57 +154,19 @@ export const chartRouter = createTRPCRouter({
// TODO: Make this private
chart: publicProcedure.input(zChartInput).query(async ({ input }) => {
- const { startDate, endDate } = getChartStartEndDate(input);
- let diff = 0;
+ const currentPeriod = getChartStartEndDate(input);
+ const previousPeriod = getChartPrevStartEndDate({
+ range: input.range,
+ ...currentPeriod,
+ });
- switch (input.range) {
- case '30min': {
- diff = 1000 * 60 * 30;
- break;
- }
- case '1h': {
- diff = 1000 * 60 * 60;
- break;
- }
- case '24h':
- case 'today': {
- diff = 1000 * 60 * 60 * 24;
- break;
- }
- case '7d': {
- diff = 1000 * 60 * 60 * 24 * 7;
- break;
- }
- case '14d': {
- diff = 1000 * 60 * 60 * 24 * 14;
- break;
- }
- case '1m': {
- diff = 1000 * 60 * 60 * 24 * 30;
- break;
- }
- case '3m': {
- diff = 1000 * 60 * 60 * 24 * 90;
- break;
- }
- case '6m': {
- diff = 1000 * 60 * 60 * 24 * 180;
- break;
- }
- }
-
- const promises = [getSeriesFromEvents(input)];
+ const promises = [getSeriesFromEvents({ ...input, ...currentPeriod })];
if (input.previous) {
promises.push(
getSeriesFromEvents({
...input,
- ...{
- startDate: new Date(
- new Date(startDate).getTime() - diff
- ).toISOString(),
- endDate: new Date(new Date(endDate).getTime() - diff).toISOString(),
- },
+ ...previousPeriod,
})
);
}
@@ -407,11 +257,11 @@ export const chartRouter = createTRPCRouter({
final.metrics.max = max(final.series.map((item) => item.metrics.max));
final.metrics.previous = {
sum: getPreviousMetric(
- sum(final.series.map((item) => item.metrics.sum)),
+ final.metrics.sum,
sum(final.series.map((item) => item.metrics.previous.sum?.value ?? 0))
),
average: getPreviousMetric(
- round(average(final.series.map((item) => item.metrics.average)), 2),
+ final.metrics.average,
round(
average(
final.series.map(
@@ -422,15 +272,16 @@ export const chartRouter = createTRPCRouter({
)
),
min: getPreviousMetric(
- min(final.series.map((item) => item.metrics.min)),
+ final.metrics.min,
min(final.series.map((item) => item.metrics.previous.min?.value ?? 0))
),
max: getPreviousMetric(
- max(final.series.map((item) => item.metrics.max)),
+ final.metrics.max,
max(final.series.map((item) => item.metrics.previous.max?.value ?? 0))
),
};
+ // Sort by sum
final.series = final.series.sort((a, b) => {
if (input.chartType === 'linear') {
const sumA = a.data.reduce((acc, item) => acc + (item.count ?? 0), 0);
@@ -441,16 +292,11 @@ export const chartRouter = createTRPCRouter({
}
});
- // await new Promise((res) => {
- // setTimeout(() => {
- // res();
- // }, 100);
- // });
return final;
}),
});
-function getPreviousMetric(
+export function getPreviousMetric(
current: number,
previous: number | null
): PreviousValue {
@@ -483,28 +329,3 @@ function getPreviousMetric(
value: previous,
};
}
-
-async function getSeriesFromEvents(input: IChartInput) {
- const { startDate, endDate } =
- input.startDate && input.endDate
- ? {
- startDate: input.startDate,
- endDate: input.endDate,
- }
- : getDatesFromRange(input.range);
-
- const series = (
- await Promise.all(
- input.events.map(async (event) =>
- getChartData({
- ...input,
- startDate,
- endDate,
- event,
- })
- )
- )
- ).flat();
-
- return withFormula(input, series);
-}
diff --git a/apps/dashboard/src/server/api/routers/client.ts b/apps/dashboard/src/server/api/routers/client.ts
index aa989c74..f88b1a19 100644
--- a/apps/dashboard/src/server/api/routers/client.ts
+++ b/apps/dashboard/src/server/api/routers/client.ts
@@ -1,9 +1,9 @@
import { randomUUID } from 'crypto';
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
-import { db } from '@/server/db';
import { z } from 'zod';
import { hashPassword, stripTrailingSlash } from '@openpanel/common';
+import { db, transformClient } from '@openpanel/db';
export const clientRouter = createTRPCRouter({
list: protectedProcedure
@@ -76,7 +76,7 @@ export const clientRouter = createTRPCRouter({
});
return {
- ...client,
+ ...transformClient(client),
secret: input.cors ? null : secret,
};
}),
diff --git a/apps/dashboard/src/server/api/routers/dashboard.ts b/apps/dashboard/src/server/api/routers/dashboard.ts
index 029bcf31..cf875407 100644
--- a/apps/dashboard/src/server/api/routers/dashboard.ts
+++ b/apps/dashboard/src/server/api/routers/dashboard.ts
@@ -1,60 +1,20 @@
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
-import { db, getId } from '@/server/db';
+import { getId } from '@/utils/getDbId';
import { PrismaError } from 'prisma-error-enum';
import { z } from 'zod';
+import { db, getDashboardsByProjectId } from '@openpanel/db';
import type { Prisma } from '@openpanel/db';
export const dashboardRouter = createTRPCRouter({
- get: protectedProcedure
- .input(z.object({ id: z.string() }))
- .query(async ({ input }) => {
- return db.dashboard.findUnique({
- where: {
- id: input.id,
- },
- });
- }),
list: protectedProcedure
.input(
- z
- .object({
- projectId: z.string(),
- })
- .or(
- z.object({
- organizationId: z.string(),
- })
- )
+ z.object({
+ projectId: z.string(),
+ })
)
.query(async ({ input }) => {
- if ('projectId' in input) {
- return db.dashboard.findMany({
- where: {
- project_id: input.projectId,
- },
- orderBy: {
- createdAt: 'desc',
- },
- include: {
- project: true,
- },
- });
- } else {
- return db.dashboard.findMany({
- where: {
- project: {
- organization_slug: input.organizationId,
- },
- },
- include: {
- project: true,
- },
- orderBy: {
- createdAt: 'desc',
- },
- });
- }
+ return getDashboardsByProjectId(input.projectId);
}),
create: protectedProcedure
.input(
diff --git a/apps/dashboard/src/server/api/routers/onboarding.ts b/apps/dashboard/src/server/api/routers/onboarding.ts
index e046f5c9..9da3096d 100644
--- a/apps/dashboard/src/server/api/routers/onboarding.ts
+++ b/apps/dashboard/src/server/api/routers/onboarding.ts
@@ -4,7 +4,12 @@ import { clerkClient } from '@clerk/nextjs';
import { z } from 'zod';
import { hashPassword, stripTrailingSlash } from '@openpanel/common';
-import { db } from '@openpanel/db';
+import {
+ db,
+ transformClient,
+ transformOrganization,
+ transformProject,
+} from '@openpanel/db';
export const onboardingRouter = createTRPCRouter({
organziation: protectedProcedure
@@ -41,12 +46,12 @@ export const onboardingRouter = createTRPCRouter({
});
return {
- client: {
+ client: transformClient({
...client,
secret: input.cors ? null : secret,
- },
- project,
- organization: org,
+ }),
+ project: transformProject(project),
+ organization: transformOrganization(org),
};
}
diff --git a/apps/dashboard/src/server/api/routers/organization.ts b/apps/dashboard/src/server/api/routers/organization.ts
index 395e323c..dc648f8b 100644
--- a/apps/dashboard/src/server/api/routers/organization.ts
+++ b/apps/dashboard/src/server/api/routers/organization.ts
@@ -9,7 +9,6 @@ export const organizationRouter = createTRPCRouter({
list: protectedProcedure.query(() => {
return clerkClient.organizations.getOrganizationList();
}),
- // first: protectedProcedure.query(() => getCurrentOrganization()),
get: protectedProcedure
.input(
z.object({
diff --git a/apps/dashboard/src/server/api/routers/profile.ts b/apps/dashboard/src/server/api/routers/profile.ts
index 2e3ee6a6..5d3c6a78 100644
--- a/apps/dashboard/src/server/api/routers/profile.ts
+++ b/apps/dashboard/src/server/api/routers/profile.ts
@@ -3,71 +3,12 @@ import {
protectedProcedure,
publicProcedure,
} from '@/server/api/trpc';
-import { db } from '@/server/db';
import { flatten, map, pipe, prop, sort, uniq } from 'ramda';
import { z } from 'zod';
import { chQuery, createSqlBuilder } from '@openpanel/db';
export const profileRouter = createTRPCRouter({
- list: protectedProcedure
- .input(
- z.object({
- query: z.string().nullable(),
- projectId: z.string(),
- take: z.number().default(100),
- skip: z.number().default(0),
- })
- )
- .query(async ({ input: { take, skip, projectId, query } }) => {
- return db.profile.findMany({
- take,
- skip,
- where: {
- project_id: projectId,
- ...(query
- ? {
- OR: [
- {
- first_name: {
- contains: query,
- mode: 'insensitive',
- },
- },
- {
- last_name: {
- contains: query,
- mode: 'insensitive',
- },
- },
- {
- email: {
- contains: query,
- mode: 'insensitive',
- },
- },
- ],
- }
- : {}),
- },
- orderBy: {
- createdAt: 'desc',
- },
- });
- }),
- get: protectedProcedure
- .input(
- z.object({
- id: z.string(),
- })
- )
- .query(async ({ input: { id } }) => {
- return db.profile.findUniqueOrThrow({
- where: {
- id,
- },
- });
- }),
properties: protectedProcedure
.input(z.object({ projectId: z.string() }))
.query(async ({ input: { projectId } }) => {
@@ -88,6 +29,7 @@ export const profileRouter = createTRPCRouter({
uniq
)(properties);
}),
+
values: publicProcedure
.input(
z.object({
diff --git a/apps/dashboard/src/server/api/routers/project.ts b/apps/dashboard/src/server/api/routers/project.ts
index 9b8f87d3..61ffa9bb 100644
--- a/apps/dashboard/src/server/api/routers/project.ts
+++ b/apps/dashboard/src/server/api/routers/project.ts
@@ -1,8 +1,9 @@
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
-import { db, getId } from '@/server/db';
-import { slug } from '@/utils/slug';
+import { getId } from '@/utils/getDbId';
import { z } from 'zod';
+import { db, getProjectsByOrganizationSlug } from '@openpanel/db';
+
export const projectRouter = createTRPCRouter({
list: protectedProcedure
.input(
@@ -12,26 +13,9 @@ export const projectRouter = createTRPCRouter({
)
.query(async ({ input: { organizationId } }) => {
if (organizationId === null) return [];
+ return getProjectsByOrganizationSlug(organizationId);
+ }),
- return db.project.findMany({
- where: {
- organization_slug: organizationId,
- },
- });
- }),
- get: protectedProcedure
- .input(
- z.object({
- id: z.string(),
- })
- )
- .query(({ input: { id } }) => {
- return db.project.findUniqueOrThrow({
- where: {
- id,
- },
- });
- }),
update: protectedProcedure
.input(
z.object({
diff --git a/apps/dashboard/src/server/api/routers/report.ts b/apps/dashboard/src/server/api/routers/report.ts
index 3767a594..95a68860 100644
--- a/apps/dashboard/src/server/api/routers/report.ts
+++ b/apps/dashboard/src/server/api/routers/report.ts
@@ -1,57 +1,11 @@
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
-import { db } from '@/server/db';
import { z } from 'zod';
-import { transformReport } from '@openpanel/db';
+import { db } from '@openpanel/db';
import { zChartInput } from '@openpanel/validation';
export const reportRouter = createTRPCRouter({
- get: protectedProcedure
- .input(
- z.object({
- id: z.string(),
- })
- )
- .query(({ input: { id } }) => {
- return db.report
- .findUniqueOrThrow({
- where: {
- id,
- },
- })
- .then(transformReport);
- }),
- list: protectedProcedure
- .input(
- z.object({
- projectId: z.string(),
- dashboardId: z.string(),
- })
- )
- .query(async ({ input: { projectId, dashboardId } }) => {
- const [dashboard, reports] = await db.$transaction([
- db.dashboard.findUniqueOrThrow({
- where: {
- id: dashboardId,
- },
- }),
- db.report.findMany({
- where: {
- project_id: projectId,
- dashboard_id: dashboardId,
- },
- orderBy: {
- createdAt: 'desc',
- },
- }),
- ]);
-
- return {
- reports: reports.map(transformReport),
- dashboard,
- };
- }),
- save: protectedProcedure
+ create: protectedProcedure
.input(
z.object({
report: zChartInput.omit({ projectId: true }),
diff --git a/apps/dashboard/src/server/api/routers/share.ts b/apps/dashboard/src/server/api/routers/share.ts
index 450d0044..2b456940 100644
--- a/apps/dashboard/src/server/api/routers/share.ts
+++ b/apps/dashboard/src/server/api/routers/share.ts
@@ -1,7 +1,7 @@
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
-import { db } from '@/server/db';
import ShortUniqueId from 'short-unique-id';
+import { db } from '@openpanel/db';
import { zShareOverview } from '@openpanel/validation';
const uid = new ShortUniqueId({ length: 6 });
diff --git a/apps/dashboard/src/server/api/routers/ui.ts b/apps/dashboard/src/server/api/routers/ui.ts
deleted file mode 100644
index 18ea02c5..00000000
--- a/apps/dashboard/src/server/api/routers/ui.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
-import { z } from 'zod';
-
-export const uiRouter = createTRPCRouter({
- breadcrumbs: protectedProcedure
- .input(
- z.object({
- url: z.string(),
- })
- )
- .query(({ input: { url } }) => {
- const parts = url.split('/').filter(Boolean);
- return parts;
- }),
-});
diff --git a/apps/dashboard/src/server/auth.ts b/apps/dashboard/src/server/auth.ts
deleted file mode 100644
index 7ae3487b..00000000
--- a/apps/dashboard/src/server/auth.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export async function getSession() {
- return true;
-}
diff --git a/apps/dashboard/src/server/cache.ts b/apps/dashboard/src/server/cache.ts
deleted file mode 100644
index 1ce18bb9..00000000
--- a/apps/dashboard/src/server/cache.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-const cache = new Map<
- string,
- {
- expires: number;
- data: any;
- }
->();
-
-export function get(key: string) {
- const hit = cache.get(key);
- if (hit) {
- if (hit.expires > Date.now()) {
- return hit.data;
- }
- cache.delete(key);
- }
- return null;
-}
-
-export function set(key: string, expires: number, data: any) {
- cache.set(key, {
- expires: Date.now() + expires,
- data,
- });
-}
-
-export async function getOr(
- key: string,
- expires: number,
- fn: () => Promise
-): Promise {
- const hit = get(key);
- if (hit) {
- return hit;
- }
- const data = await fn();
- set(key, expires, data);
- return data;
-}
diff --git a/apps/dashboard/src/styles/globals.css b/apps/dashboard/src/styles/globals.css
index 08cd73d3..8737cbb2 100644
--- a/apps/dashboard/src/styles/globals.css
+++ b/apps/dashboard/src/styles/globals.css
@@ -126,44 +126,6 @@
}
}
-.resizer {
- position: absolute;
- right: 0;
- top: 0;
- height: 100%;
- width: 5px;
- background: rgba(0, 0, 0, 0.1);
- cursor: col-resize;
- user-select: none;
- touch-action: none;
-}
-
-.resizer.isResizing {
- @apply bg-black;
- opacity: 1;
-}
-
-@media (hover: hover) {
- .resizer {
- opacity: 0;
- }
-
- *:hover > .resizer {
- opacity: 1;
- }
-}
-
-/* For Webkit-based browsers (Chrome, Safari and Opera) */
-.scrollbar-hide::-webkit-scrollbar {
- display: none;
-}
-
-/* For IE, Edge and Firefox */
-.scrollbar-hide {
- -ms-overflow-style: none; /* IE and Edge */
- scrollbar-width: none; /* Firefox */
-}
-
/* Rechart */
.recharts-wrapper .recharts-cartesian-grid-horizontal line:first-child,
diff --git a/apps/dashboard/src/utils/date.ts b/apps/dashboard/src/utils/date.ts
index bf8179dc..4f23fe6e 100644
--- a/apps/dashboard/src/utils/date.ts
+++ b/apps/dashboard/src/utils/date.ts
@@ -1,9 +1,3 @@
-export function getDaysOldDate(days: number) {
- const date = new Date();
- date.setDate(date.getDate() - days);
- return date;
-}
-
export function dateDifferanceInDays(date1: Date, date2: Date) {
const diffTime = Math.abs(date2.getTime() - date1.getTime());
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
diff --git a/apps/dashboard/src/server/db.ts b/apps/dashboard/src/utils/getDbId.ts
similarity index 83%
rename from apps/dashboard/src/server/db.ts
rename to apps/dashboard/src/utils/getDbId.ts
index 183f31b3..600be848 100644
--- a/apps/dashboard/src/server/db.ts
+++ b/apps/dashboard/src/utils/getDbId.ts
@@ -1,8 +1,6 @@
-import { slug } from '@/utils/slug';
-
import { db } from '@openpanel/db';
-export { db } from '@openpanel/db';
+import { slug } from './slug';
export async function getId(tableName: 'project' | 'dashboard', name: string) {
const newId = slug(name);
@@ -15,7 +13,7 @@ export async function getId(tableName: 'project' | 'dashboard', name: string) {
}
// @ts-expect-error
- const existingProject = await db[tableName]!.findUnique({
+ const existingProject = await db[tableName].findUnique({
where: {
id: newId,
},
diff --git a/apps/dashboard/src/utils/getters.ts b/apps/dashboard/src/utils/getters.ts
deleted file mode 100644
index 1257b551..00000000
--- a/apps/dashboard/src/utils/getters.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import type { IServiceProfile } from '@openpanel/db';
-
-export function getProfileName(profile: IServiceProfile | undefined | null) {
- if (!profile) return 'No profile';
- return [profile.first_name, profile.last_name].filter(Boolean).join(' ');
-}
diff --git a/packages/db/src/services/clients.service.ts b/packages/db/src/services/clients.service.ts
index bd29220a..417d3df2 100644
--- a/packages/db/src/services/clients.service.ts
+++ b/packages/db/src/services/clients.service.ts
@@ -1,14 +1,22 @@
import type { Client } from '../prisma-client';
import { db } from '../prisma-client';
+import { transformProject } from './project.service';
import type { IServiceProject } from './project.service';
-export type IServiceClient = Client;
-export type IServiceClientWithProject = Client & {
+export type IServiceClient = ReturnType;
+export type IServiceClientWithProject = IServiceClient & {
project: Exclude;
};
-export function getClientsByOrganizationId(organizationId: string) {
- return db.client.findMany({
+export function transformClient({ organization_slug, ...client }: Client) {
+ return {
+ ...client,
+ organizationSlug: organization_slug,
+ };
+}
+
+export async function getClientsByOrganizationId(organizationId: string) {
+ const clients = await db.client.findMany({
where: {
organization_slug: organizationId,
},
@@ -16,4 +24,15 @@ export function getClientsByOrganizationId(organizationId: string) {
project: true,
},
});
+
+ return clients
+ .map((client) => {
+ return {
+ ...transformClient(client),
+ project: transformProject(client.project),
+ };
+ })
+ .filter(
+ (client): client is IServiceClientWithProject => client.project !== null
+ );
}
diff --git a/packages/db/src/services/dashboard.service.ts b/packages/db/src/services/dashboard.service.ts
index 52bae331..16f4e65e 100644
--- a/packages/db/src/services/dashboard.service.ts
+++ b/packages/db/src/services/dashboard.service.ts
@@ -23,22 +23,6 @@ export async function getDashboardById(id: string, projectId: string) {
return dashboard;
}
-export async function getDashboardsByOrganization(organizationSlug: string) {
- return db.dashboard.findMany({
- where: {
- organization_slug: organizationSlug,
- },
- include: {
- project: true,
- },
- orderBy: {
- reports: {
- _count: 'desc',
- },
- },
- });
-}
-
export function getDashboardsByProjectId(projectId: string) {
return db.dashboard.findMany({
where: {
diff --git a/packages/db/src/services/organization.service.ts b/packages/db/src/services/organization.service.ts
index 0327e658..a4456d41 100644
--- a/packages/db/src/services/organization.service.ts
+++ b/packages/db/src/services/organization.service.ts
@@ -12,7 +12,7 @@ export type IServiceOrganization = Awaited<
export type IServiceInvites = Awaited>;
-function transformOrganization(org: Organization) {
+export function transformOrganization(org: Organization) {
return {
id: org.id,
name: org.name,
diff --git a/packages/db/src/services/profile.service.ts b/packages/db/src/services/profile.service.ts
index 9dbc7fcd..5a1451cf 100644
--- a/packages/db/src/services/profile.service.ts
+++ b/packages/db/src/services/profile.service.ts
@@ -129,7 +129,7 @@ export async function getProfilesByExternalId(
export type IServiceProfile = Omit<
IClickhouseProfile,
- 'max_created_at' | 'properties'
+ 'max_created_at' | 'properties' | 'first_name' | 'last_name'
> & {
firstName: string;
lastName: string;
@@ -169,12 +169,14 @@ export interface IServiceUpsertProfile {
function transformProfile({
max_created_at,
+ first_name,
+ last_name,
...profile
}: IClickhouseProfile): IServiceProfile {
return {
...profile,
- firstName: profile.first_name,
- lastName: profile.last_name,
+ firstName: first_name,
+ lastName: last_name,
properties: toObject(profile.properties),
createdAt: new Date(max_created_at),
};
@@ -216,3 +218,8 @@ export async function upsertProfile({
],
});
}
+
+export function getProfileName(profile: IServiceProfile | undefined | null) {
+ if (!profile) return 'No name';
+ return [profile.firstName, profile.lastName].filter(Boolean).join(' ');
+}
diff --git a/packages/db/src/services/project.service.ts b/packages/db/src/services/project.service.ts
index 47cc2a14..044cdc21 100644
--- a/packages/db/src/services/project.service.ts
+++ b/packages/db/src/services/project.service.ts
@@ -1,9 +1,9 @@
import type { Project } from '../prisma-client';
import { db } from '../prisma-client';
-export type IServiceProject = ReturnType;
+export type IServiceProject = ReturnType;
-function transform({ organization_slug, ...project }: Project) {
+export function transformProject({ organization_slug, ...project }: Project) {
return {
organizationSlug: organization_slug,
...project,
@@ -21,7 +21,7 @@ export async function getProjectById(id: string) {
return null;
}
- return transform(res);
+ return transformProject(res);
}
export async function getProjectsByOrganizationSlug(slug: string) {
@@ -31,5 +31,5 @@ export async function getProjectsByOrganizationSlug(slug: string) {
},
});
- return res.map(transform);
+ return res.map(transformProject);
}
diff --git a/packages/db/src/services/reference.service.ts b/packages/db/src/services/reference.service.ts
index 3a8023b3..043a6cc0 100644
--- a/packages/db/src/services/reference.service.ts
+++ b/packages/db/src/services/reference.service.ts
@@ -1,13 +1,11 @@
import type { Prisma, Reference } from '../prisma-client';
import { db } from '../prisma-client';
-// import type { Report as DbReport } from '../prisma-client';
-
export type IServiceReference = Omit & {
projectId: string;
};
-export function transform({
+export function transformReference({
project_id,
...item
}: Reference): IServiceReference {
@@ -28,7 +26,7 @@ export async function getReferenceById(id: string) {
return null;
}
- return transform(reference);
+ return transformReference(reference);
}
export async function getReferences({
@@ -46,5 +44,5 @@ export async function getReferences({
skip,
});
- return references.map(transform);
+ return references.map(transformReference);
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 335af6e2..8bff5760 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -216,6 +216,9 @@ importers:
bcrypt:
specifier: ^5.1.1
version: 5.1.1
+ bind-event-listener:
+ specifier: ^3.0.0
+ version: 3.0.0
class-variance-authority:
specifier: ^0.7.0
version: 0.7.0
@@ -3162,7 +3165,7 @@ packages:
'@expo/rudder-sdk-node': 1.1.1
'@expo/spawn-async': 1.5.0
'@expo/xcpretty': 4.3.1
- '@react-native/dev-middleware': 0.73.7
+ '@react-native/dev-middleware': 0.73.8
'@urql/core': 2.3.6(graphql@15.8.0)
'@urql/exchange-retry': 0.3.0(graphql@15.8.0)
accepts: 1.3.8
@@ -5992,25 +5995,6 @@ packages:
engines: {node: '>=18'}
dev: false
- /@react-native/dev-middleware@0.73.7:
- resolution: {integrity: sha512-BZXpn+qKp/dNdr4+TkZxXDttfx8YobDh8MFHsMk9usouLm22pKgFIPkGBV0X8Do4LBkFNPGtrnsKkWk/yuUXKg==}
- engines: {node: '>=18'}
- dependencies:
- '@isaacs/ttlcache': 1.4.1
- '@react-native/debugger-frontend': 0.73.3
- chrome-launcher: 0.15.2
- chromium-edge-launcher: 1.0.0
- connect: 3.7.0
- debug: 2.6.9
- node-fetch: 2.7.0
- open: 7.4.2
- serve-static: 1.15.0
- temp-dir: 2.0.0
- transitivePeerDependencies:
- - encoding
- - supports-color
- dev: false
-
/@react-native/dev-middleware@0.73.8:
resolution: {integrity: sha512-oph4NamCIxkMfUL/fYtSsE+JbGOnrlawfQ0kKtDQ5xbOjPKotKoXqrs1eGwozNKv7FfQ393stk1by9a6DyASSg==}
engines: {node: '>=18'}
@@ -7666,6 +7650,10 @@ packages:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
engines: {node: '>=8'}
+ /bind-event-listener@3.0.0:
+ resolution: {integrity: sha512-PJvH288AWQhKs2v9zyfYdPzlPqf5bXbGMmhmUIY9x4dAUGIWgomO771oBQNwJnMQSnUIXhKu6sgzpBRXTlvb8Q==}
+ dev: false
+
/bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
dependencies: