diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index 35efeaa6..01d7728f 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -34,6 +34,7 @@ "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-popover": "^1.0.7", + "@radix-ui/react-portal": "^1.1.1", "@radix-ui/react-progress": "^1.0.3", "@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-scroll-area": "^1.0.5", diff --git a/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/dashboards/list-dashboards/index.tsx b/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/dashboards/list-dashboards/index.tsx index 07e24066..30c13f12 100644 --- a/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/dashboards/list-dashboards/index.tsx +++ b/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/dashboards/list-dashboards/index.tsx @@ -17,7 +17,7 @@ const ListDashboardsServer = async ({ projectId }: Props) => { return ( - ; + ); }; diff --git a/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/layout-menu.tsx b/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/layout-menu.tsx index 0e51bb9b..9c631642 100644 --- a/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/layout-menu.tsx +++ b/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/layout-menu.tsx @@ -9,6 +9,7 @@ import { useUser } from '@clerk/nextjs'; import { GanttChartIcon, Globe2Icon, + LayersIcon, LayoutPanelTopIcon, PlusIcon, ScanEyeIcon, @@ -89,6 +90,11 @@ export default function LayoutMenu({ dashboards }: LayoutMenuProps) { label="Dashboards" href={`/${params.organizationSlug}/${projectId}/dashboards`} /> + + + + Pages + + + {tab === 'pages' && } + + ); +} diff --git a/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/pages/pages-table.tsx b/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/pages/pages-table.tsx new file mode 100644 index 00000000..0dd9c348 --- /dev/null +++ b/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/pages/pages-table.tsx @@ -0,0 +1,114 @@ +'use client'; + +import { LazyChart } from '@/components/report/chart/LazyChart'; +import { useNumber } from '@/hooks/useNumerFormatter'; +import { cn } from '@/utils/cn'; +import { ExternalLinkIcon } from 'lucide-react'; + +import type { IServicePage } from '@openpanel/db'; + +export function PagesTable({ data }: { data: IServicePage[] }) { + const number = useNumber(); + const cell = + 'flex min-h-12 whitespace-nowrap px-4 align-middle shadow-[0_0_0_0.5px] shadow-border'; + return ( +
+
+
+
+ Views +
+
+ Path +
+
+ Chart +
+
+ {data.map((item, index) => { + return ( +
+
+ {number.short(item.count)} +
+
+ {item.title} + {item.origin ? ( + + + {item.path} + + ) : ( + + {item.path} + + )} +
+
+ +
+
+ ); + })} +
+
+ ); +} diff --git a/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/pages/pages.tsx b/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/pages/pages.tsx new file mode 100644 index 00000000..ef6ac836 --- /dev/null +++ b/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/pages/pages.tsx @@ -0,0 +1,65 @@ +'use client'; + +import { TableButtons } from '@/components/data-table'; +import { Pagination } from '@/components/pagination'; +import { Input } from '@/components/ui/input'; +import { TableSkeleton } from '@/components/ui/table'; +import { useDebounceValue } from '@/hooks/useDebounceValue'; +import { api } from '@/trpc/client'; +import { Loader2Icon } from 'lucide-react'; +import { parseAsInteger, useQueryState } from 'nuqs'; + +import { PagesTable } from './pages-table'; + +export function Pages({ projectId }: { projectId: string }) { + const take = 20; + const [cursor, setCursor] = useQueryState( + 'cursor', + parseAsInteger.withDefault(0) + ); + const [search, setSearch] = useQueryState('search', { + defaultValue: '', + shallow: true, + }); + const debouncedSearch = useDebounceValue(search, 500); + const query = api.event.pages.useQuery( + { + projectId, + cursor, + take, + search: debouncedSearch, + }, + { + keepPreviousData: true, + } + ); + const data = query.data ?? []; + + return ( + <> + + { + setSearch(e.target.value); + setCursor(0); + }} + /> + + {query.isLoading ? ( + + ) : ( + + )} + + + ); +} diff --git a/apps/dashboard/src/components/events/table/index.tsx b/apps/dashboard/src/components/events/table/index.tsx index 875565e2..5717dadf 100644 --- a/apps/dashboard/src/components/events/table/index.tsx +++ b/apps/dashboard/src/components/events/table/index.tsx @@ -3,8 +3,10 @@ import { DataTable } from '@/components/data-table'; import { FullPageEmptyState } from '@/components/full-page-empty-state'; import { Pagination } from '@/components/pagination'; import { Button } from '@/components/ui/button'; +import { TableSkeleton } from '@/components/ui/table'; import type { UseQueryResult } from '@tanstack/react-query'; import { GanttChartIcon } from 'lucide-react'; +import { column } from 'mathjs'; import type { IServiceEvent } from '@openpanel/db'; @@ -25,15 +27,7 @@ export const EventsTable = ({ query, ...props }: Props) => { const { data, isFetching, isLoading } = query; if (isLoading) { - return ( -
-
-
-
-
-
-
- ); + return ; } if (data?.length === 0) { diff --git a/apps/dashboard/src/components/grid-table.tsx b/apps/dashboard/src/components/grid-table.tsx index 6a6ea926..9553cf2f 100644 --- a/apps/dashboard/src/components/grid-table.tsx +++ b/apps/dashboard/src/components/grid-table.tsx @@ -60,7 +60,7 @@ export const GridCell: React.FC< }) => ( +
{children}
diff --git a/apps/dashboard/src/components/profiles/table/index.tsx b/apps/dashboard/src/components/profiles/table/index.tsx index e16a4f5e..6672c2a6 100644 --- a/apps/dashboard/src/components/profiles/table/index.tsx +++ b/apps/dashboard/src/components/profiles/table/index.tsx @@ -3,6 +3,7 @@ import { DataTable } from '@/components/data-table'; import { FullPageEmptyState } from '@/components/full-page-empty-state'; import { Pagination } from '@/components/pagination'; import { Button } from '@/components/ui/button'; +import { TableSkeleton } from '@/components/ui/table'; import type { UseQueryResult } from '@tanstack/react-query'; import { GanttChartIcon } from 'lucide-react'; @@ -26,15 +27,7 @@ export const ProfilesTable = ({ type, query, ...props }: Props) => { const { data, isFetching, isLoading } = query; if (isLoading) { - return ( -
-
-
-
-
-
-
- ); + return ; } if (data?.length === 0) { diff --git a/apps/dashboard/src/components/report/chart/ChartEmpty.tsx b/apps/dashboard/src/components/report/chart/ChartEmpty.tsx index d2f862c3..e8ec49e2 100644 --- a/apps/dashboard/src/components/report/chart/ChartEmpty.tsx +++ b/apps/dashboard/src/components/report/chart/ChartEmpty.tsx @@ -1,8 +1,8 @@ import { FullPageEmptyState } from '@/components/full-page-empty-state'; -import { cn } from '@/utils/cn'; import { useChartContext } from './ChartProvider'; import { MetricCardEmpty } from './MetricCard'; +import { ResponsiveContainer } from './ResponsiveContainer'; export function ChartEmpty() { const { editMode, chartType } = useChartContext(); @@ -20,12 +20,10 @@ export function ChartEmpty() { } return ( -
- No data -
+ +
+ No data +
+
); } diff --git a/apps/dashboard/src/components/report/chart/ChartLoading.tsx b/apps/dashboard/src/components/report/chart/ChartLoading.tsx index 360f5431..181672f2 100644 --- a/apps/dashboard/src/components/report/chart/ChartLoading.tsx +++ b/apps/dashboard/src/components/report/chart/ChartLoading.tsx @@ -1,15 +1,20 @@ import { cn } from '@/utils/cn'; +import { ResponsiveContainer } from './ResponsiveContainer'; + interface ChartLoadingProps { className?: string; + aspectRatio?: number; } -export function ChartLoading({ className }: ChartLoadingProps) { +export function ChartLoading({ className, aspectRatio }: ChartLoadingProps) { return ( -
+ +
+ ); } diff --git a/apps/dashboard/src/components/report/chart/ChartProvider.tsx b/apps/dashboard/src/components/report/chart/ChartProvider.tsx index 9362a24c..fcfa22a0 100644 --- a/apps/dashboard/src/components/report/chart/ChartProvider.tsx +++ b/apps/dashboard/src/components/report/chart/ChartProvider.tsx @@ -6,6 +6,9 @@ import type { LucideIcon } from 'lucide-react'; import type { IChartProps, IChartSerie } from '@openpanel/validation'; export interface IChartContextType extends IChartProps { + hideXAxis?: boolean; + hideYAxis?: boolean; + aspectRatio?: number; editMode?: boolean; hideID?: boolean; onClick?: (item: IChartSerie) => void; diff --git a/apps/dashboard/src/components/report/chart/LazyChart.tsx b/apps/dashboard/src/components/report/chart/LazyChart.tsx index a3c72832..41c02d76 100644 --- a/apps/dashboard/src/components/report/chart/LazyChart.tsx +++ b/apps/dashboard/src/components/report/chart/LazyChart.tsx @@ -1,13 +1,17 @@ 'use client'; import React, { useEffect, useRef } from 'react'; +import { cn } from '@/utils/cn'; import { useInViewport } from 'react-in-viewport'; import type { IChartRoot } from '.'; import { ChartRoot } from '.'; import { ChartLoading } from './ChartLoading'; -export function LazyChart(props: IChartRoot) { +export function LazyChart({ + className, + ...props +}: IChartRoot & { className?: string }) { const ref = useRef(null); const once = useRef(false); const { inViewport } = useInViewport(ref, undefined, { @@ -21,11 +25,11 @@ export function LazyChart(props: IChartRoot) { }, [inViewport]); return ( -
+
{once.current || inViewport ? ( ) : ( - + )}
); diff --git a/apps/dashboard/src/components/report/chart/ReportChartTooltip.tsx b/apps/dashboard/src/components/report/chart/ReportChartTooltip.tsx index 012159a1..cb6d7245 100644 --- a/apps/dashboard/src/components/report/chart/ReportChartTooltip.tsx +++ b/apps/dashboard/src/components/report/chart/ReportChartTooltip.tsx @@ -1,9 +1,11 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { useFormatDateInterval } from '@/hooks/useFormatDateInterval'; -import { useMappings } from '@/hooks/useMappings'; import { useNumber } from '@/hooks/useNumerFormatter'; import type { IRechartPayloadItem } from '@/hooks/useRechartDataModel'; import type { IToolTipProps } from '@/types'; +import * as Portal from '@radix-ui/react-portal'; +import { bind } from 'bind-event-listener'; +import throttle from 'lodash.throttle'; import { PreviousDiffIndicator } from '../PreviousDiffIndicator'; import { useChartContext } from './ChartProvider'; @@ -24,11 +26,35 @@ export function ReportChartTooltip({ const { unit, interval } = useChartContext(); const formatDate = useFormatDateInterval(interval); const number = useNumber(); - if (!active || !payload) { - return null; - } + const [position, setPosition] = useState<{ x: number; y: number } | null>( + null + ); - if (!payload.length) { + const inactive = !active || !payload?.length; + useEffect(() => { + const setPositionThrottled = throttle(setPosition, 50); + const unsubMouseMove = bind(window, { + type: 'mousemove', + listener(event) { + if (!inactive) { + setPositionThrottled({ x: event.clientX, y: event.clientY + 20 }); + } + }, + }); + const unsubDragEnter = bind(window, { + type: 'pointerdown', + listener() { + setPosition(null); + }, + }); + + return () => { + unsubMouseMove(); + unsubDragEnter(); + }; + }, [inactive]); + + if (inactive) { return null; } @@ -41,55 +67,81 @@ export function ReportChartTooltip({ const visible = sorted.slice(0, limit); const hidden = sorted.slice(limit); + const correctXPosition = (x: number | undefined) => { + if (!x) { + return undefined; + } + + const tooltipWidth = 300; + const screenWidth = window.innerWidth; + const newX = x; + + if (newX + tooltipWidth > screenWidth) { + return screenWidth - tooltipWidth; + } + return newX; + }; + return ( -
- {visible.map((item, index) => { - // If we have a component, payload can be nested - const payload = item.payload.payload ?? item.payload; - const data = ( - item.dataKey.includes(':') - ? // @ts-expect-error - payload[`${item.dataKey.split(':')[0]}:payload`] - : payload - ) as IRechartPayloadItem; + +
+ {visible.map((item, index) => { + // If we have a component, payload can be nested + const payload = item.payload.payload ?? item.payload; + const data = ( + item.dataKey.includes(':') + ? // @ts-expect-error + payload[`${item.dataKey.split(':')[0]}:payload`] + : payload + ) as IRechartPayloadItem; - return ( - - {index === 0 && data.date && ( -
-
{formatDate(new Date(data.date))}
-
- )} -
-
-
-
- - + return ( + + {index === 0 && data.date && ( +
+
{formatDate(new Date(data.date))}
-
-
- {number.formatWithUnit(data.count, unit)} - {!!data.previous && ( - - ({number.formatWithUnit(data.previous.value, unit)}) - - )} + )} +
+
+
+
+ +
+
+
+ {number.formatWithUnit(data.count, unit)} + {!!data.previous && ( + + ({number.formatWithUnit(data.previous.value, unit)}) + + )} +
- + +
-
- - ); - })} - {hidden.length > 0 && ( -
and {hidden.length} more...
- )} -
+ + ); + })} + {hidden.length > 0 && ( +
+ and {hidden.length} more... +
+ )} +
+ ); } diff --git a/apps/dashboard/src/components/report/chart/ReportHistogramChart.tsx b/apps/dashboard/src/components/report/chart/ReportHistogramChart.tsx index 60c2db17..a9559429 100644 --- a/apps/dashboard/src/components/report/chart/ReportHistogramChart.tsx +++ b/apps/dashboard/src/components/report/chart/ReportHistogramChart.tsx @@ -6,10 +6,9 @@ import { useVisibleSeries } from '@/hooks/useVisibleSeries'; import type { IChartData } from '@/trpc/client'; import { cn } from '@/utils/cn'; import { getChartColor, theme } from '@/utils/theme'; +import { useTheme } from 'next-themes'; import { Bar, BarChart, CartesianGrid, Tooltip, XAxis, YAxis } from 'recharts'; -import type { IInterval } from '@openpanel/validation'; - import { getYAxisWidth } from './chart-utils'; import { useChartContext } from './ChartProvider'; import { ReportChartTooltip } from './ReportChartTooltip'; @@ -21,7 +20,11 @@ interface ReportHistogramChartProps { } function BarHover({ x, y, width, height, top, left, right, bottom }: any) { - const bg = theme?.colors?.slate?.['200'] as string; + const themeMode = useTheme(); + const bg = + themeMode?.theme === 'dark' + ? theme.colors['def-100'] + : theme.colors['def-300']; return ( -
- +
+ {({ width, height }) => ( - + { return ( + + + + + + {previous && ( )} ); diff --git a/apps/dashboard/src/components/report/chart/ReportLineChart.tsx b/apps/dashboard/src/components/report/chart/ReportLineChart.tsx index ece9e258..3e542f63 100644 --- a/apps/dashboard/src/components/report/chart/ReportLineChart.tsx +++ b/apps/dashboard/src/components/report/chart/ReportLineChart.tsx @@ -45,7 +45,11 @@ export function ReportLineChart({ data }: ReportLineChartProps) { endDate, range, lineType, + aspectRatio, + hideXAxis, + hideYAxis, } = useChartContext(); + const dataLength = data.series[0]?.data?.length || 0; const references = api.reference.getChartReferences.useQuery( { projectId, @@ -120,11 +124,10 @@ export function ReportLineChart({ data }: ReportLineChartProps) { }, [series]); const isAreaStyle = series.length === 1; - return ( <> -
- +
+ {({ width, height }) => ( ))} } /> React.ReactNode; + aspectRatio?: number; + children: + | ((props: { width: number; height: number }) => React.ReactNode) + | React.ReactNode; } -export function ResponsiveContainer({ children }: ResponsiveContainerProps) { +export function ResponsiveContainer({ + children, + aspectRatio = 0.5625, +}: ResponsiveContainerProps) { const maxHeight = 300; - const minHeight = 200; + return (
- - {({ width }) => - children({ - width, - height: Math.min(maxHeight, width * 0.5625), - }) - } - + {typeof children === 'function' ? ( + + {({ width }) => + children({ + width, + height: Math.min( + maxHeight, + width * aspectRatio || DEFAULT_ASPECT_RATIO + ), + }) + } + + ) : ( + children + )}
); } diff --git a/apps/dashboard/src/components/report/chart/index.tsx b/apps/dashboard/src/components/report/chart/index.tsx index 2daac349..cdbd0419 100644 --- a/apps/dashboard/src/components/report/chart/index.tsx +++ b/apps/dashboard/src/components/report/chart/index.tsx @@ -1,6 +1,7 @@ 'use client'; import { Suspense, useEffect, useState } from 'react'; +import * as Portal from '@radix-ui/react-portal'; import type { IChartProps } from '@openpanel/validation'; @@ -24,14 +25,18 @@ export function ChartRoot(props: IChartContextType) { return props.chartType === 'metric' ? ( ) : ( - + ); } return ( : + props.chartType === 'metric' ? ( + + ) : ( + + ) } > @@ -49,9 +54,13 @@ interface ChartRootShortcutProps { interval?: IChartProps['interval']; events: IChartProps['events']; breakdowns?: IChartProps['breakdowns']; + lineType?: IChartProps['lineType']; + hideXAxis?: boolean; + aspectRatio?: number; } export const ChartRootShortcut = ({ + hideXAxis, projectId, range = '7d', previous = false, @@ -59,19 +68,25 @@ export const ChartRootShortcut = ({ interval = 'day', events, breakdowns, + aspectRatio, + lineType = 'monotone', }: ChartRootShortcutProps) => { return ( - + + + ); }; diff --git a/apps/dashboard/src/components/ui/table.tsx b/apps/dashboard/src/components/ui/table.tsx index fdddb30b..a04429a1 100644 --- a/apps/dashboard/src/components/ui/table.tsx +++ b/apps/dashboard/src/components/ui/table.tsx @@ -110,6 +110,39 @@ const TableCaption = React.forwardRef< )); TableCaption.displayName = 'TableCaption'; +export function TableSkeleton({ + rows = 10, + cols = 2, +}: { + rows?: number; + cols?: number; +}) { + return ( + + + + {Array.from({ length: cols }).map((_, j) => ( + +
+ + ))} + + + + {Array.from({ length: rows }).map((_, i) => ( + + {Array.from({ length: cols }).map((_, j) => ( + +
+ + ))} + + ))} + +
+ ); +} + export { Table, TableHeader, diff --git a/apps/dashboard/src/components/widget.tsx b/apps/dashboard/src/components/widget.tsx index b806ebcb..52141e7c 100644 --- a/apps/dashboard/src/components/widget.tsx +++ b/apps/dashboard/src/components/widget.tsx @@ -37,7 +37,7 @@ export function WidgetTitle({ )} > {Icon && ( -
+
)} diff --git a/packages/constants/index.ts b/packages/constants/index.ts index 604f7800..fba70c97 100644 --- a/packages/constants/index.ts +++ b/packages/constants/index.ts @@ -1,5 +1,6 @@ import { isSameDay, isSameMonth } from 'date-fns'; +export const DEFAULT_ASPECT_RATIO = 0.5625; export const NOT_SET_VALUE = '(not set)'; export const timeWindows = { diff --git a/packages/db/src/services/event.service.ts b/packages/db/src/services/event.service.ts index 71888789..a3ce3cc0 100644 --- a/packages/db/src/services/event.service.ts +++ b/packages/db/src/services/event.service.ts @@ -28,6 +28,15 @@ export type IImportedEvent = Omit< properties: Record; }; +export type IServicePage = { + path: string; + count: number; + project_id: string; + first_seen: string; + title: string; + origin: string; +}; + export interface IClickhouseEvent { id: string; name: string; @@ -493,3 +502,30 @@ export async function getLastScreenViewFromProfileId({ return eventInDb || null; } + +export async function getTopPages({ + projectId, + cursor, + take, + search, +}: { + projectId: string; + cursor?: number; + take: number; + search?: string; +}) { + const res = await chQuery(` + SELECT path, count(*) as count, project_id, first_value(created_at) as first_seen, max(properties['__title']) as title, origin + FROM events_v2 + WHERE name = 'screen_view' + AND project_id = ${escape(projectId)} + AND created_at > now() - INTERVAL 30 DAY + ${search ? `AND path LIKE '%${search}%'` : ''} + GROUP BY path, project_id, origin + ORDER BY count desc + LIMIT ${take} + OFFSET ${Math.max(0, (cursor ?? 0) * take)} + `); + + return res; +} diff --git a/packages/trpc/src/routers/event.ts b/packages/trpc/src/routers/event.ts index 084ba703..28a30040 100644 --- a/packages/trpc/src/routers/event.ts +++ b/packages/trpc/src/routers/event.ts @@ -8,6 +8,7 @@ import { db, getEventList, getEvents, + getTopPages, TABLE_NAMES, } from '@openpanel/db'; import { zChartEventFilter } from '@openpanel/validation'; @@ -69,7 +70,6 @@ export const eventRouter = createTRPCRouter({ z.object({ projectId: z.string(), cursor: z.number().optional(), - limit: z.number().default(8), profileId: z.string().optional(), take: z.number().default(50), events: z.array(z.string()).optional(), @@ -165,4 +165,17 @@ export const eventRouter = createTRPCRouter({ count: counts[0]?.count ?? 0, }; }), + + pages: protectedProcedure + .input( + z.object({ + projectId: z.string(), + cursor: z.number().optional(), + take: z.number().default(20), + search: z.string().optional(), + }) + ) + .query(async ({ input }) => { + return getTopPages(input); + }), }); diff --git a/packages/trpc/src/routers/organization.ts b/packages/trpc/src/routers/organization.ts index 42802e1e..6bbbe36b 100644 --- a/packages/trpc/src/routers/organization.ts +++ b/packages/trpc/src/routers/organization.ts @@ -5,7 +5,7 @@ import { z } from 'zod'; import { db } from '@openpanel/db'; import { zInviteUser } from '@openpanel/validation'; -import { getOrganizationAccess, getOrganizationAccessCached } from '../access'; +import { getOrganizationAccess } from '../access'; import { TRPCAccessError } from '../errors'; import { createTRPCRouter, protectedProcedure } from '../trpc'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d585d543..72b2650f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -233,6 +233,9 @@ importers: '@radix-ui/react-popover': specifier: ^1.0.7 version: 1.0.7(@types/react-dom@18.2.19)(@types/react@18.2.56)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-portal': + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@18.2.19)(@types/react@18.2.56)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-progress': specifier: ^1.0.3 version: 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.56)(react-dom@18.2.0)(react@18.2.0) @@ -5574,6 +5577,19 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-compose-refs@1.1.0(@types/react@18.2.56)(react@18.2.0): + resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.56 + react: 18.2.0 + dev: false + /@radix-ui/react-context@1.0.0(react@18.2.0): resolution: {integrity: sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==} peerDependencies: @@ -5982,6 +5998,27 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-portal@1.1.1(@types/react-dom@18.2.19)(@types/react@18.2.56)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.2.19)(@types/react@18.2.56)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.2.56)(react@18.2.0) + '@types/react': 18.2.56 + '@types/react-dom': 18.2.19 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-presence@1.0.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==} peerDependencies: @@ -6050,6 +6087,26 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-primitive@2.0.0(@types/react-dom@18.2.19)(@types/react@18.2.56)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/react-slot': 1.1.0(@types/react@18.2.56)(react@18.2.0) + '@types/react': 18.2.56 + '@types/react-dom': 18.2.19 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-progress@1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.56)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-5G6Om/tYSxjSeEdrb1VfKkfZfn/1IlPWd731h2RfPuSbIfNUgfqAwbKfJCg/PP6nuUCTrYzalwHSpSinoWoCag==} peerDependencies: @@ -6185,6 +6242,20 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-slot@1.1.0(@types/react@18.2.56)(react@18.2.0): + resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.2.56)(react@18.2.0) + '@types/react': 18.2.56 + react: 18.2.0 + dev: false + /@radix-ui/react-switch@1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.56)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-mxm87F88HyHztsI7N+ZUmEoARGkC22YVW5CaC+Byc+HRpuvCrOBPTAnXgf+tZ/7i0Sg/eOePGdMhUKhPaQEqow==} peerDependencies: @@ -6450,6 +6521,19 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.2.56)(react@18.2.0): + resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.56 + react: 18.2.0 + dev: false + /@radix-ui/react-use-previous@1.0.1(@types/react@18.2.56)(react@18.2.0): resolution: {integrity: sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==} peerDependencies: