diff --git a/apps/public/package.json b/apps/public/package.json index 8024ffe6..9d9523d1 100644 --- a/apps/public/package.json +++ b/apps/public/package.json @@ -31,30 +31,20 @@ "email-validator": "^2.0.4", "embla-carousel-autoplay": "8.0.0-rc22", "embla-carousel-react": "8.0.0-rc22", - "hamburger-react": "^2.5.0", "lucide-react": "^0.323.0", "next": "~14.0.4", - "nuqs": "^1.15.2", "react": "18.2.0", "react-animate-height": "^3.2.3", "react-dom": "18.2.0", - "react-in-viewport": "1.0.0-alpha.30", - "react-responsive": "^9.0.2", "react-syntax-highlighter": "^15.5.0", - "sharp": "^0.33.2", "tailwind-merge": "^1.14.0", - "tailwindcss-animate": "^1.0.7", - "usehooks-ts": "^2.9.1", - "zod": "^3.22.4" + "tailwindcss-animate": "^1.0.7" }, "devDependencies": { "@mixan/eslint-config": "workspace:*", "@mixan/prettier-config": "workspace:*", "@mixan/tsconfig": "workspace:*", - "@types/bcrypt": "^5.0.0", - "@types/lodash.debounce": "^4.0.9", "@types/node": "^18.16.0", - "@types/ramda": "^0.29.6", "@types/react": "^18.2.20", "@types/react-dom": "^18.2.7", "@types/react-syntax-highlighter": "^15.5.9", diff --git a/apps/public/src/utils/clipboard.ts b/apps/public/src/utils/clipboard.ts deleted file mode 100644 index 171fc623..00000000 --- a/apps/public/src/utils/clipboard.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { toast } from 'sonner'; - -export function clipboard(value: string | number) { - navigator.clipboard.writeText(value.toString()); - toast({ - title: 'Copied to clipboard', - description: value.toString(), - }); -} diff --git a/apps/public/src/utils/constants.ts b/apps/public/src/utils/constants.ts deleted file mode 100644 index bebe213f..00000000 --- a/apps/public/src/utils/constants.ts +++ /dev/null @@ -1,99 +0,0 @@ -export const operators = { - is: 'Is', - isNot: 'Is not', - contains: 'Contains', - doesNotContain: 'Not contains', -} as const; - -export const chartTypes = { - linear: 'Linear', - bar: 'Bar', - histogram: 'Histogram', - pie: 'Pie', - metric: 'Metric', - area: 'Area', - map: 'Map', -} as const; - -export const lineTypes = { - monotone: 'Monotone', - monotoneX: 'Monotone X', - monotoneY: 'Monotone Y', - linear: 'Linear', - natural: 'Natural', - basis: 'Basis', - step: 'Step', - stepBefore: 'Step before', - stepAfter: 'Step after', - basisClosed: 'Basis closed', - basisOpen: 'Basis open', - bumpX: 'Bump X', - bumpY: 'Bump Y', - bump: 'Bump', - linearClosed: 'Linear closed', -} as const; - -export const intervals = { - minute: 'minute', - day: 'day', - hour: 'hour', - month: 'month', -} as const; - -export const alphabetIds = [ - 'A', - 'B', - 'C', - 'D', - 'E', - 'F', - 'G', - 'H', - 'I', - 'J', -] as const; - -export const timeRanges = { - '30min': '30min', - '1h': '1h', - today: 'today', - '24h': '24h', - '7d': '7d', - '14d': '14d', - '1m': '1m', - '3m': '3m', - '6m': '6m', - '1y': '1y', -} as const; - -export const metrics = { - sum: 'sum', - average: 'average', - min: 'min', - max: 'max', -} as const; - -export function isMinuteIntervalEnabledByRange(range: keyof typeof timeRanges) { - return range === '30min' || range === '1h'; -} - -export function isHourIntervalEnabledByRange(range: keyof typeof timeRanges) { - return ( - isMinuteIntervalEnabledByRange(range) || - range === 'today' || - range === '24h' - ); -} - -export function getDefaultIntervalByRange( - range: keyof typeof timeRanges -): keyof typeof intervals { - if (range === '30min' || range === '1h') { - return 'minute'; - } else if (range === 'today' || range === '24h') { - return 'hour'; - } else if (range === '7d' || range === '14d' || range === '1m') { - return 'day'; - } - return 'month'; -} diff --git a/apps/public/src/utils/date.ts b/apps/public/src/utils/date.ts deleted file mode 100644 index bf8179dc..00000000 --- a/apps/public/src/utils/date.ts +++ /dev/null @@ -1,32 +0,0 @@ -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)); -} - -export function getLocale() { - if (typeof navigator === 'undefined') { - return 'en-US'; - } - - return navigator.language ?? 'en-US'; -} - -export function formatDate(date: Date) { - return new Intl.DateTimeFormat(getLocale()).format(date); -} - -export function formatDateTime(date: Date) { - return new Intl.DateTimeFormat(getLocale(), { - day: 'numeric', - month: 'numeric', - year: 'numeric', - hour: 'numeric', - minute: 'numeric', - }).format(date); -} diff --git a/apps/public/src/utils/getters.ts b/apps/public/src/utils/getters.ts deleted file mode 100644 index 53d3e784..00000000 --- a/apps/public/src/utils/getters.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { Profile } from '@mixan/db'; - -export function getProfileName(profile: Profile | undefined | null) { - if (!profile) return 'No profile'; - return [profile.first_name, profile.last_name].filter(Boolean).join(' '); -} diff --git a/apps/public/src/utils/math.ts b/apps/public/src/utils/math.ts deleted file mode 100644 index 9360880c..00000000 --- a/apps/public/src/utils/math.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { isNumber } from 'mathjs'; - -export const round = (num: number, decimals = 2) => { - const factor = Math.pow(10, decimals); - return Math.round((num + Number.EPSILON) * factor) / factor; -}; - -export const average = (arr: (number | null)[]) => { - const filtered = arr.filter(isNumber); - return filtered.reduce((p, c) => p + c, 0) / filtered.length; -}; - -export const sum = (arr: (number | null)[]): number => - round(arr.filter(isNumber).reduce((acc, item) => acc + item, 0)); - -export const min = (arr: (number | null)[]): number => - Math.min(...arr.filter(isNumber)); - -export const max = (arr: (number | null)[]): number => - Math.max(...arr.filter(isNumber)); - -export const isFloat = (n: number) => n % 1 !== 0; diff --git a/apps/public/src/utils/slug.ts b/apps/public/src/utils/slug.ts deleted file mode 100644 index 2c448002..00000000 --- a/apps/public/src/utils/slug.ts +++ /dev/null @@ -1,18 +0,0 @@ -import _slugify from 'slugify'; - -const slugify = (str: string) => { - return _slugify( - str - .replace('å', 'a') - .replace('ä', 'a') - .replace('ö', 'o') - .replace('Å', 'A') - .replace('Ä', 'A') - .replace('Ö', 'O'), - { lower: true, strict: true, trim: true } - ); -}; - -export function slug(str: string): string { - return slugify(str); -} diff --git a/apps/public/src/utils/theme.ts b/apps/public/src/utils/theme.ts deleted file mode 100644 index 6fccc0fa..00000000 --- a/apps/public/src/utils/theme.ts +++ /dev/null @@ -1,17 +0,0 @@ -import resolveConfig from 'tailwindcss/resolveConfig'; - -import tailwinConfig from '../../tailwind.config'; - -export const resolvedTailwindConfig = resolveConfig(tailwinConfig); - -export const theme = resolvedTailwindConfig.theme as Record; - -export function getChartColor(index: number): string { - const colors = theme?.colors ?? {}; - const chartColors: string[] = Object.keys(colors) - .filter((key) => key.startsWith('chart-')) - .map((key) => colors[key]) - .filter((item): item is string => typeof item === 'string'); - - return chartColors[index % chartColors.length]!; -} diff --git a/apps/public/src/utils/truncate.ts b/apps/public/src/utils/truncate.ts deleted file mode 100644 index 9cecae76..00000000 --- a/apps/public/src/utils/truncate.ts +++ /dev/null @@ -1,6 +0,0 @@ -export function truncate(str: string, len: number) { - if (str.length <= len) { - return str; - } - return str.slice(0, len) + '...'; -} diff --git a/apps/public/src/utils/validation.ts b/apps/public/src/utils/validation.ts deleted file mode 100644 index 3b57c8bd..00000000 --- a/apps/public/src/utils/validation.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { z } from 'zod'; - -import { - chartTypes, - intervals, - lineTypes, - metrics, - operators, - timeRanges, -} from './constants'; - -export function objectToZodEnums( - obj: Record -): [K, ...K[]] { - const [firstKey, ...otherKeys] = Object.keys(obj) as K[]; - return [firstKey!, ...otherKeys]; -} - -export const mapKeys = objectToZodEnums; - -export const zChartEvent = z.object({ - id: z.string(), - name: z.string(), - displayName: z.string().optional(), - property: z.string().optional(), - segment: z.enum([ - 'event', - 'user', - 'user_average', - 'one_event_per_user', - 'property_sum', - 'property_average', - ]), - filters: z.array( - z.object({ - id: z.string(), - name: z.string(), - operator: z.enum(objectToZodEnums(operators)), - value: z.array(z.string().or(z.number()).or(z.boolean()).or(z.null())), - }) - ), -}); -export const zChartBreakdown = z.object({ - id: z.string(), - name: z.string(), -}); - -export const zChartEvents = z.array(zChartEvent); -export const zChartBreakdowns = z.array(zChartBreakdown); - -export const zChartType = z.enum(objectToZodEnums(chartTypes)); - -export const zLineType = z.enum(objectToZodEnums(lineTypes)); - -export const zTimeInterval = z.enum(objectToZodEnums(intervals)); - -export const zMetric = z.enum(objectToZodEnums(metrics)); - -export const zChartInput = z.object({ - name: z.string(), - chartType: zChartType, - lineType: zLineType, - interval: zTimeInterval, - events: zChartEvents, - breakdowns: zChartBreakdowns, - range: z.enum(objectToZodEnums(timeRanges)), - previous: z.boolean(), - formula: z.string().optional(), - metric: zMetric, - unit: z.string().optional(), - previousIndicatorInverted: z.boolean().optional(), - projectId: z.string(), - startDate: z.string().nullish(), - endDate: z.string().nullish(), -}); diff --git a/apps/sdk-api/src/utils/parseReferrer.ts b/apps/sdk-api/src/utils/parseReferrer.ts index bec05f1f..39c8d83d 100644 --- a/apps/sdk-api/src/utils/parseReferrer.ts +++ b/apps/sdk-api/src/utils/parseReferrer.ts @@ -15,7 +15,8 @@ function getHostname(url: string | undefined) { } export function parseReferrer(url: string | undefined) { - const match = referrers[getHostname(url)]; + const hostname = getHostname(url); + const match = referrers[hostname] ?? referrers[hostname.replace('www.', '')]; return { name: match?.name ?? '', diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index e739dac4..a5b22b06 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -55,6 +55,8 @@ COPY packages/db/package.json packages/db/package.json COPY packages/redis/package.json packages/redis/package.json COPY packages/queue/package.json packages/queue/package.json COPY packages/common/package.json packages/common/package.json +COPY packages/constants/package.json packages/constants/package.json +COPY packages/validation/package.json packages/validation/package.json COPY packages/types/package.json packages/types/package.json # BUILD @@ -92,11 +94,15 @@ COPY --from=build /app/packages/db /app/packages/db COPY --from=build /app/packages/redis /app/packages/redis COPY --from=build /app/packages/common /app/packages/common COPY --from=build /app/packages/queue /app/packages/queue +COPY --from=build /app/packages/constants /app/packages/constants +COPY --from=build /app/packages/validation /app/packages/validation COPY --from=build /app/packages/types /app/packages/types # Packages node_modules COPY --from=prod /app/packages/db/node_modules /app/packages/db/node_modules COPY --from=prod /app/packages/redis/node_modules /app/packages/redis/node_modules COPY --from=prod /app/packages/common/node_modules /app/packages/common/node_modules +COPY --from=prod /app/packages/constants/node_modules /app/packages/constants/node_modules +COPY --from=prod /app/packages/validation/node_modules /app/packages/validation/node_modules COPY --from=prod /app/packages/queue/node_modules /app/packages/queue/node_modules RUN pnpm db:codegen diff --git a/apps/web/package.json b/apps/web/package.json index 3e708142..17fac2bf 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -16,6 +16,8 @@ "@clickhouse/client": "^0.2.9", "@hookform/resolvers": "^3.3.4", "@mixan/common": "workspace:^", + "@mixan/constants": "workspace:^", + "@mixan/validation": "workspace:^", "@mixan/db": "workspace:^", "@mixan/queue": "workspace:^", "@mixan/types": "workspace:*", @@ -90,8 +92,8 @@ "@types/lodash.throttle": "^4.1.9", "@types/node": "^18.19.15", "@types/ramda": "^0.29.10", - "@types/react": "^18.2.55", - "@types/react-dom": "^18.2.19", + "@types/react": "^18.2.20", + "@types/react-dom": "^18.2.7", "@types/react-syntax-highlighter": "^15.5.11", "@types/request-ip": "^0.0.41", "@typescript-eslint/eslint-plugin": "^6.21.0", diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/[dashboardId]/list-reports.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/[dashboardId]/list-reports.tsx index 51ba9ce5..cf25f406 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/[dashboardId]/list-reports.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/[dashboardId]/list-reports.tsx @@ -5,7 +5,6 @@ import { StickyBelowHeader } from '@/app/(app)/[organizationId]/[projectId]/layo import { LazyChart } from '@/components/report/chart/LazyChart'; import { ReportRange } from '@/components/report/ReportRange'; import { Button } from '@/components/ui/button'; -import { Combobox } from '@/components/ui/combobox'; import { DropdownMenu, DropdownMenuContent, @@ -14,14 +13,15 @@ import { DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { useAppParams } from '@/hooks/useAppParams'; -import type { getReportsByDashboardId } from '@/server/services/reports.service'; -import type { IChartRange } from '@/types'; import { cn } from '@/utils/cn'; -import { getDefaultIntervalByRange, timeRanges } from '@/utils/constants'; import { ChevronRight, MoreHorizontal, PlusIcon, Trash } from 'lucide-react'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; +import { getDefaultIntervalByRange } from '@mixan/constants'; +import type { getReportsByDashboardId } from '@mixan/db'; +import type { IChartRange } from '@mixan/validation'; + interface ListReportsProps { reports: Awaited>; } diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/[dashboardId]/page.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/[dashboardId]/page.tsx index 759abf69..dc6d34f1 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/[dashboardId]/page.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/[dashboardId]/page.tsx @@ -1,9 +1,9 @@ import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import { getExists } from '@/server/pageExists'; -import { getDashboardById } from '@/server/services/dashboard.service'; -import { getReportsByDashboardId } from '@/server/services/reports.service'; import { notFound } from 'next/navigation'; +import { getDashboardById, getReportsByDashboardId } from '@mixan/db'; + import { ListReports } from './list-reports'; interface PageProps { diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/list-dashboards.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/list-dashboards.tsx index cf9c0d04..73ab9ecb 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/list-dashboards.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/list-dashboards.tsx @@ -7,12 +7,13 @@ import { Button } from '@/components/ui/button'; import { ToastAction } from '@/components/ui/toast'; import { useAppParams } from '@/hooks/useAppParams'; import { pushModal } from '@/modals'; -import type { IServiceDashboards } from '@/server/services/dashboard.service'; import { LayoutPanelTopIcon, Pencil, PlusIcon, Trash } from 'lucide-react'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { toast } from 'sonner'; +import type { IServiceDashboards } from '@mixan/db'; + interface ListDashboardsProps { dashboards: IServiceDashboards; } diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/page.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/page.tsx index c516815b..71275e46 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/page.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/dashboards/page.tsx @@ -1,6 +1,7 @@ import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import { getExists } from '@/server/pageExists'; -import { getDashboardsByProjectId } from '@/server/services/dashboard.service'; + +import { getDashboardsByProjectId } from '@mixan/db'; import { HeaderDashboards } from './header-dashboards'; import { ListDashboards } from './list-dashboards'; diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-list-item.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-list-item.tsx index 69dbba51..cea0a06b 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-list-item.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-list-item.tsx @@ -1,13 +1,16 @@ 'use client'; -import type { RouterOutputs } from '@/app/_trpc/client'; import { ExpandableListItem } from '@/components/general/ExpandableListItem'; import { KeyValue, KeyValueSubtle } from '@/components/ui/key-value'; import { useAppParams } from '@/hooks/useAppParams'; -import { useEventQueryFilters } from '@/hooks/useEventQueryFilters'; +import { + useEventQueryFilters, + useEventQueryNamesFilter, +} from '@/hooks/useEventQueryFilters'; import { cn } from '@/utils/cn'; import { getProfileName } from '@/utils/getters'; import { round } from '@/utils/math'; +import { uniq } from 'ramda'; import type { IServiceCreateEventPayload } from '@mixan/db'; @@ -40,7 +43,8 @@ export function EventListItem({ meta, }: EventListItemProps) { const params = useAppParams(); - const eventQueryFilters = useEventQueryFilters({ shallow: false }); + const [, setEvents] = useEventQueryNamesFilter({ shallow: false }); + const [, setFilter] = useEventQueryFilters({ shallow: false }); const keyValueList = [ { name: 'Duration', @@ -50,98 +54,98 @@ export function EventListItem({ name: 'Referrer', value: referrer, onClick() { - eventQueryFilters.referrer.set(referrer ?? null); + setFilter('referrer', referrer ?? ''); }, }, { name: 'Referrer name', value: referrerName, onClick() { - eventQueryFilters.referrerName.set(referrerName ?? null); + setFilter('referrer_name', referrerName ?? ''); }, }, { name: 'Referrer type', value: referrerType, onClick() { - eventQueryFilters.referrerType.set(referrerType ?? null); + setFilter('referrer_type', referrerType ?? ''); }, }, { name: 'Brand', value: brand, onClick() { - eventQueryFilters.brand.set(brand ?? null); + setFilter('brand', brand ?? ''); }, }, { name: 'Model', value: model, onClick() { - eventQueryFilters.model.set(model ?? null); + setFilter('model', model ?? ''); }, }, { name: 'Browser', value: browser, onClick() { - eventQueryFilters.browser.set(browser ?? null); + setFilter('browser', browser ?? ''); }, }, { name: 'Browser version', value: browserVersion, onClick() { - eventQueryFilters.browserVersion.set(browserVersion ?? null); + setFilter('browser_version', browserVersion ?? ''); }, }, { name: 'OS', value: os, onClick() { - eventQueryFilters.os.set(os ?? null); + setFilter('os', os ?? ''); }, }, { name: 'OS cersion', value: osVersion, onClick() { - eventQueryFilters.osVersion.set(osVersion ?? null); + setFilter('os_version', osVersion ?? ''); }, }, { name: 'City', value: city, onClick() { - eventQueryFilters.city.set(city ?? null); + setFilter('city', city ?? ''); }, }, { name: 'Region', value: region, onClick() { - eventQueryFilters.region.set(region ?? null); + setFilter('region', region ?? ''); }, }, { name: 'Country', value: country, onClick() { - eventQueryFilters.country.set(country ?? null); + setFilter('country', country ?? ''); }, }, { name: 'Continent', value: continent, onClick() { - eventQueryFilters.continent.set(continent ?? null); + setFilter('continent', continent ?? ''); }, }, { name: 'Device', value: device, onClick() { - eventQueryFilters.device.set(device ?? null); + setFilter('device', device ?? ''); }, }, ].filter((item) => typeof item.value === 'string' && item.value); @@ -156,7 +160,11 @@ export function EventListItem({ return ( setEvents((p) => uniq([...p, name]))}> + {name.split('_').join(' ')} + + } content={ <> @@ -172,7 +180,7 @@ export function EventListItem({ name="Path" value={path} onClick={() => { - eventQueryFilters.path.set(path); + setFilter('path', path); }} /> )} @@ -191,6 +199,13 @@ export function EventListItem({ key={item.name} name={item.name} value={item.value} + onClick={() => { + setFilter( + `properties.${item.name}`, + item.value ? String(item.value) : '', + 'is' + ); + }} /> ))} diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-list.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-list.tsx index 824ad7c8..22e2838f 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-list.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/events/event-list.tsx @@ -5,7 +5,7 @@ import { FullPageEmptyState } from '@/components/FullPageEmptyState'; import { Pagination } from '@/components/Pagination'; import { Button } from '@/components/ui/button'; import { useCursor } from '@/hooks/useCursor'; -import { useEventFilters } from '@/hooks/useEventQueryFilters'; +import { useEventQueryFilters } from '@/hooks/useEventQueryFilters'; import { GanttChartIcon } from 'lucide-react'; import type { IServiceCreateEventPayload } from '@mixan/db'; @@ -18,8 +18,7 @@ interface EventListProps { } export function EventList({ data, count }: EventListProps) { const { cursor, setCursor } = useCursor(); - const filters = useEventFilters(); - + const [filters] = useEventQueryFilters(); return (
diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/events/page.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/events/page.tsx index 406cfa0b..da080402 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/events/page.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/events/page.tsx @@ -1,7 +1,10 @@ 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 { getEventFilters } from '@/hooks/useEventQueryFilters'; +import { + eventQueryFiltersParser, + eventQueryNamesFilter, +} from '@/hooks/useEventQueryFilters'; import { getExists } from '@/server/pageExists'; import { getEventList, getEventsCount } from '@mixan/db'; @@ -15,27 +18,9 @@ interface PageProps { organizationId: string; }; searchParams: { + events?: string; cursor?: string; - path?: string; - device?: string; - referrer?: string; - referrerName?: string; - referrerType?: string; - utmSource?: string; - utmMedium?: string; - utmCampaign?: string; - utmContent?: string; - utmTerm?: string; - continent?: string; - country?: string; - region?: string; - city?: string; - browser?: string; - browserVersion?: string; - os?: string; - osVersion?: string; - brand?: string; - model?: string; + f?: string; }; } @@ -59,53 +44,13 @@ export default async function Page({ cursor: parseQueryAsNumber(searchParams.cursor), projectId, take: 50, - filters: getEventFilters({ - path: searchParams.path ?? null, - device: searchParams.device ?? null, - referrer: searchParams.referrer ?? null, - referrerName: searchParams.referrerName ?? null, - referrerType: searchParams.referrerType ?? null, - utmSource: searchParams.utmSource ?? null, - utmMedium: searchParams.utmMedium ?? null, - utmCampaign: searchParams.utmCampaign ?? null, - utmContent: searchParams.utmContent ?? null, - utmTerm: searchParams.utmTerm ?? null, - continent: searchParams.continent ?? null, - country: searchParams.country ?? null, - region: searchParams.region ?? null, - city: searchParams.city ?? null, - browser: searchParams.browser ?? null, - browserVersion: searchParams.browserVersion ?? null, - os: searchParams.os ?? null, - osVersion: searchParams.osVersion ?? null, - brand: searchParams.brand ?? null, - model: searchParams.model ?? null, - }), + events: eventQueryNamesFilter.parse(searchParams.events ?? ''), + filters: eventQueryFiltersParser.parse(searchParams.f ?? '') ?? undefined, }), getEventsCount({ projectId, - filters: getEventFilters({ - path: searchParams.path ?? null, - device: searchParams.device ?? null, - referrer: searchParams.referrer ?? null, - referrerName: searchParams.referrerName ?? null, - referrerType: searchParams.referrerType ?? null, - utmSource: searchParams.utmSource ?? null, - utmMedium: searchParams.utmMedium ?? null, - utmCampaign: searchParams.utmCampaign ?? null, - utmContent: searchParams.utmContent ?? null, - utmTerm: searchParams.utmTerm ?? null, - continent: searchParams.continent ?? null, - country: searchParams.country ?? null, - region: searchParams.region ?? null, - city: searchParams.city ?? null, - browser: searchParams.browser ?? null, - browserVersion: searchParams.browserVersion ?? null, - os: searchParams.os ?? null, - osVersion: searchParams.osVersion ?? null, - brand: searchParams.brand ?? null, - model: searchParams.model ?? null, - }), + events: eventQueryNamesFilter.parse(searchParams.events ?? ''), + filters: eventQueryFiltersParser.parse(searchParams.f ?? '') ?? undefined, }), getExists(organizationId, projectId), ]); @@ -116,6 +61,7 @@ export default async function Page({ >; + projects: Awaited>; } export default function LayoutProjectSelector({ projects, diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/layout-sidebar.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/layout-sidebar.tsx index f928e337..ffcc8ea9 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/layout-sidebar.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/layout-sidebar.tsx @@ -2,13 +2,13 @@ import { useEffect, useState } from 'react'; import { Logo } from '@/components/Logo'; -import type { IServiceDashboards } from '@/server/services/dashboard.service'; -import type { IServiceOrganization } from '@/server/services/organization.service'; import { cn } from '@/utils/cn'; import { Rotate as Hamburger } from 'hamburger-react'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; +import type { IServiceDashboards, IServiceOrganization } from '@mixan/db'; + import LayoutMenu from './layout-menu'; import LayoutOrganizationSelector from './layout-organization-selector'; diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/layout.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/layout.tsx index 610c7297..37ac878a 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/layout.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/layout.tsx @@ -1,5 +1,7 @@ -import { getDashboardsByOrganization } from '@/server/services/dashboard.service'; -import { getCurrentOrganizations } from '@/server/services/organization.service'; +import { + getCurrentOrganizations, + getDashboardsByOrganization, +} from '@mixan/db'; import { LayoutSidebar } from './layout-sidebar'; diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/overview-metrics.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/overview-metrics.tsx index 0ba87f7d..9ca8bd81 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/overview-metrics.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/overview-metrics.tsx @@ -4,17 +4,18 @@ import { WidgetHead } from '@/components/overview/overview-widget'; import { useOverviewOptions } from '@/components/overview/useOverviewOptions'; import { Chart } from '@/components/report/chart'; import { Widget, WidgetBody } from '@/components/Widget'; -import { useEventFilters } from '@/hooks/useEventQueryFilters'; -import type { IChartInput } from '@/types'; +import { useEventQueryFilters } from '@/hooks/useEventQueryFilters'; import { cn } from '@/utils/cn'; +import type { IChartInput } from '@mixan/validation'; + interface OverviewMetricsProps { projectId: string; } export default function OverviewMetrics({ projectId }: OverviewMetricsProps) { const { previous, range, interval, metric, setMetric } = useOverviewOptions(); - const filters = useEventFilters(); + const [filters] = useEventQueryFilters(); const reports = [ { diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/page-layout.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/page-layout.tsx index 6aadde05..c020f49c 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/page-layout.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/page-layout.tsx @@ -1,4 +1,4 @@ -import { getCurrentProjects } from '@/server/services/project.service'; +import { getProjectsByOrganizationSlug } from '@mixan/db'; import LayoutProjectSelector from './layout-project-selector'; @@ -13,7 +13,7 @@ export default async function PageLayout({ title, organizationSlug, }: PageLayoutProps) { - const projects = await getCurrentProjects(organizationSlug); + const projects = await getProjectsByOrganizationSlug(organizationSlug); return ( <> diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/profiles/[profileId]/list-profile-events.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/profiles/[profileId]/list-profile-events.tsx index 0f18e4ca..6af713dc 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/profiles/[profileId]/list-profile-events.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/profiles/[profileId]/list-profile-events.tsx @@ -18,7 +18,7 @@ export default function ListProfileEvents({ projectId, profileId, }: ListProfileEvents) { - const pagination = usePagination(); + const pagination = usePagination(50); const [eventFilters, setEventFilters] = useQueryState( 'events', parseAsJson().withDefault([]) diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/profiles/[profileId]/page.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/profiles/[profileId]/page.tsx index 0227cbcd..f36c6106 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/profiles/[profileId]/page.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/profiles/[profileId]/page.tsx @@ -3,13 +3,11 @@ import { ListProperties } from '@/components/events/ListProperties'; import { ProfileAvatar } from '@/components/profiles/ProfileAvatar'; import { Widget, WidgetBody, WidgetHead } from '@/components/Widget'; import { getExists } from '@/server/pageExists'; -import { - getProfileById, - getProfilesByExternalId, -} from '@/server/services/profile.service'; import { formatDateTime } from '@/utils/date'; import { getProfileName } from '@/utils/getters'; +import { getProfileById, getProfilesByExternalId } from '@mixan/db'; + import ListProfileEvents from './list-profile-events'; interface PageProps { diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/profiles/profile-list-item.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/profiles/profile-list-item.tsx index 374ea9b1..6b681ae6 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/profiles/profile-list-item.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/profiles/profile-list-item.tsx @@ -1,6 +1,5 @@ 'use client'; -import { useMemo } from 'react'; import type { RouterOutputs } from '@/app/_trpc/client'; import { ListProperties } from '@/components/events/ListProperties'; import { ExpandableListItem } from '@/components/general/ExpandableListItem'; @@ -16,24 +15,24 @@ export function ProfileListItem(props: ProfileListItemProps) { const { id, properties, createdAt } = props; const params = useAppParams(); - const bullets = useMemo(() => { - const bullets: React.ReactNode[] = [ - {formatDateTime(createdAt)}, - - See profile - , - ]; - - return bullets; - }, [createdAt, id, params]); + const renderContent = () => { + return ( + <> + {formatDateTime(createdAt)} + + See profile + + + ); + }; return ( } > diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/reports/[reportId]/page.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/reports/[reportId]/page.tsx index 78e2ade5..a24f7d6a 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/reports/[reportId]/page.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/reports/[reportId]/page.tsx @@ -1,10 +1,10 @@ import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import { getExists } from '@/server/pageExists'; -import { getOrganizationBySlug } from '@/server/services/organization.service'; -import { getReportById } from '@/server/services/reports.service'; import { Pencil } from 'lucide-react'; import { notFound } from 'next/navigation'; +import { getOrganizationBySlug, getReportById } from '@mixan/db'; + import ReportEditor from '../report-editor'; interface PageProps { diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/reports/page.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/reports/page.tsx index 3882b01d..4a97bcc5 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/reports/page.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/reports/page.tsx @@ -1,9 +1,10 @@ import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import { getExists } from '@/server/pageExists'; -import { getOrganizationBySlug } from '@/server/services/organization.service'; import { Pencil } from 'lucide-react'; import { notFound } from 'next/navigation'; +import { getOrganizationBySlug } from '@mixan/db'; + import ReportEditor from './report-editor'; interface PageProps { diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/reports/report-editor.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/reports/report-editor.tsx index 0304b616..41b04289 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/reports/report-editor.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/reports/report-editor.tsx @@ -19,9 +19,10 @@ import { Button } from '@/components/ui/button'; import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'; import { useAppParams } from '@/hooks/useAppParams'; import { useDispatch, useSelector } from '@/redux'; -import type { IServiceReport } from '@/server/services/reports.service'; import { GanttChartSquareIcon } from 'lucide-react'; +import type { IServiceReport } from '@mixan/db'; + interface ReportEditorProps { report: IServiceReport | null; } diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/clients/list-clients.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/clients/list-clients.tsx index f8b42db3..36a35df4 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/clients/list-clients.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/clients/list-clients.tsx @@ -6,9 +6,10 @@ import { DataTable } from '@/components/DataTable'; import { Button } from '@/components/ui/button'; import { useAppParams } from '@/hooks/useAppParams'; import { pushModal } from '@/modals'; -import type { getClientsByOrganizationId } from '@/server/services/clients.service'; import { PlusIcon } from 'lucide-react'; +import type { getClientsByOrganizationId } from '@mixan/db'; + interface ListClientsProps { clients: Awaited>; } diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/clients/page.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/clients/page.tsx index d002a92b..2cfc1321 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/clients/page.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/clients/page.tsx @@ -1,6 +1,7 @@ import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import { getExists } from '@/server/pageExists'; -import { getClientsByOrganizationId } from '@/server/services/clients.service'; + +import { getClientsByOrganizationId } from '@mixan/db'; import ListClients from './list-clients'; diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/organization/edit-organization.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/organization/edit-organization.tsx index 4b186136..c972ba90 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/organization/edit-organization.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/organization/edit-organization.tsx @@ -4,12 +4,13 @@ import { api, handleError } from '@/app/_trpc/client'; import { InputWithLabel } from '@/components/forms/InputWithLabel'; import { Button } from '@/components/ui/button'; import { Widget, WidgetBody, WidgetHead } from '@/components/Widget'; -import type { getOrganizationBySlug } from '@/server/services/organization.service'; import { useRouter } from 'next/navigation'; import { useForm } from 'react-hook-form'; import { toast } from 'sonner'; import { z } from 'zod'; +import type { getOrganizationBySlug } from '@mixan/db'; + const validator = z.object({ id: z.string().min(2), name: z.string().min(2), diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/organization/invite-user.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/organization/invite-user.tsx index 6f8a56d3..c221d811 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/organization/invite-user.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/organization/invite-user.tsx @@ -2,7 +2,6 @@ import { api } from '@/app/_trpc/client'; import { InputWithLabel } from '@/components/forms/InputWithLabel'; import { Button } from '@/components/ui/button'; import { useAppParams } from '@/hooks/useAppParams'; -import { zInviteUser } from '@/utils/validation'; import { zodResolver } from '@hookform/resolvers/zod'; import { SendIcon } from 'lucide-react'; import { useRouter } from 'next/navigation'; @@ -10,6 +9,8 @@ import { useForm } from 'react-hook-form'; import { toast } from 'sonner'; import type { z } from 'zod'; +import { zInviteUser } from '@mixan/validation'; + type IForm = z.infer; export function InviteUser() { diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/organization/invited-users.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/organization/invited-users.tsx index cf598941..19a40c14 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/organization/invited-users.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/organization/invited-users.tsx @@ -9,7 +9,8 @@ import { TableRow, } from '@/components/ui/table'; import { Widget, WidgetBody, WidgetHead } from '@/components/Widget'; -import type { IServiceInvites } from '@/server/services/organization.service'; + +import type { IServiceInvites } from '@mixan/db'; import { InviteUser } from './invite-user'; diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/organization/page.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/organization/page.tsx index 4d161a1f..b7c2f794 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/organization/page.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/organization/page.tsx @@ -1,11 +1,9 @@ import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; -import { - getInvites, - getOrganizationBySlug, -} from '@/server/services/organization.service'; import { clerkClient } from '@clerk/nextjs'; import { notFound } from 'next/navigation'; +import { getInvites, getOrganizationBySlug } from '@mixan/db'; + import EditOrganization from './edit-organization'; import InvitedUsers from './invited-users'; diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/profile/edit-profile.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/profile/edit-profile.tsx index 2d678a66..c404eb46 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/profile/edit-profile.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/profile/edit-profile.tsx @@ -4,13 +4,14 @@ import { api, handleError } from '@/app/_trpc/client'; import { InputWithLabel } from '@/components/forms/InputWithLabel'; import { Button } from '@/components/ui/button'; import { Widget, WidgetBody, WidgetHead } from '@/components/Widget'; -import type { getUserById } from '@/server/services/user.service'; import { zodResolver } from '@hookform/resolvers/zod'; import { useRouter } from 'next/navigation'; import { useForm } from 'react-hook-form'; import { toast } from 'sonner'; import { z } from 'zod'; +import type { getUserById } from '@mixan/db'; + const validator = z.object({ firstName: z.string().min(2), lastName: z.string().min(2), diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/profile/page.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/profile/page.tsx index 8572024b..4399831b 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/profile/page.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/profile/page.tsx @@ -1,8 +1,9 @@ import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import { getExists } from '@/server/pageExists'; -import { getUserById } from '@/server/services/user.service'; import { auth } from '@clerk/nextjs'; +import { getUserById } from '@mixan/db'; + import EditProfile from './edit-profile'; import { Logout } from './logout'; diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/projects/list-projects.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/projects/list-projects.tsx index 9534ba20..d64854d1 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/projects/list-projects.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/projects/list-projects.tsx @@ -6,11 +6,12 @@ import { columns } from '@/components/projects/table'; import { Button } from '@/components/ui/button'; import { useAppParams } from '@/hooks/useAppParams'; import { pushModal } from '@/modals'; -import type { getProjectsByOrganizationId } from '@/server/services/project.service'; import { PlusIcon } from 'lucide-react'; +import type { getProjectsByOrganizationSlug } from '@mixan/db'; + interface ListProjectsProps { - projects: Awaited>; + projects: Awaited>; } export default function ListProjects({ projects }: ListProjectsProps) { const organizationId = useAppParams().organizationId; diff --git a/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/projects/page.tsx b/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/projects/page.tsx index 2417af46..421aea11 100644 --- a/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/projects/page.tsx +++ b/apps/web/src/app/(app)/[organizationId]/[projectId]/settings/projects/page.tsx @@ -1,6 +1,7 @@ import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import { getExists } from '@/server/pageExists'; -import { getProjectsByOrganizationSlug } from '@/server/services/project.service'; + +import { getProjectsByOrganizationSlug } from '@mixan/db'; import ListProjects from './list-projects'; diff --git a/apps/web/src/app/(app)/[organizationId]/page.tsx b/apps/web/src/app/(app)/[organizationId]/page.tsx index 3b96d59b..465b8cff 100644 --- a/apps/web/src/app/(app)/[organizationId]/page.tsx +++ b/apps/web/src/app/(app)/[organizationId]/page.tsx @@ -1,5 +1,5 @@ -import { getOrganizationBySlug } from '@/server/services/organization.service'; -import { getProjectWithMostEvents } from '@/server/services/project.service'; +import { getOrganizationBySlug } from '@mixan/db'; +import { getProjectWithMostEvents } from '@mixan/db'; import { notFound, redirect } from 'next/navigation'; import PageLayout from './[projectId]/page-layout'; diff --git a/apps/web/src/app/(app)/page.tsx b/apps/web/src/app/(app)/page.tsx index 51cb12cc..dd5b1034 100644 --- a/apps/web/src/app/(app)/page.tsx +++ b/apps/web/src/app/(app)/page.tsx @@ -1,7 +1,8 @@ -import { getCurrentOrganizations } from '@/server/services/organization.service'; import { CreateOrganization } from '@clerk/nextjs'; import { redirect } from 'next/navigation'; +import { getCurrentOrganizations } from '@mixan/db'; + export default async function Page() { const organizations = await getCurrentOrganizations(); diff --git a/apps/web/src/app/(public)/share/overview/[id]/page.tsx b/apps/web/src/app/(public)/share/overview/[id]/page.tsx index 2641b3cb..d746df0b 100644 --- a/apps/web/src/app/(public)/share/overview/[id]/page.tsx +++ b/apps/web/src/app/(public)/share/overview/[id]/page.tsx @@ -11,10 +11,9 @@ import OverviewTopEvents from '@/components/overview/overview-top-events'; import OverviewTopGeo from '@/components/overview/overview-top-geo'; import OverviewTopPages from '@/components/overview/overview-top-pages'; import OverviewTopSources from '@/components/overview/overview-top-sources'; -import { getOrganizationBySlug } from '@/server/services/organization.service'; import { notFound } from 'next/navigation'; -import { getShareOverviewById } from '@mixan/db'; +import { getOrganizationBySlug, getShareOverviewById } from '@mixan/db'; interface PageProps { params: { diff --git a/apps/web/src/app/api/trpc/[trpc]/route.ts b/apps/web/src/app/api/trpc/[trpc]/route.ts index c0b27472..ca979c6e 100644 --- a/apps/web/src/app/api/trpc/[trpc]/route.ts +++ b/apps/web/src/app/api/trpc/[trpc]/route.ts @@ -1,5 +1,4 @@ import { appRouter } from '@/server/api/root'; -import { getSession } from '@/server/auth'; import { getAuth } from '@clerk/nextjs/server'; import { fetchRequestHandler } from '@trpc/server/adapters/fetch'; @@ -9,9 +8,7 @@ const handler = (req: Request) => req, router: appRouter, async createContext({ req }) { - console.log('------- createContext --------'); const session = getAuth(req as any); - console.log('session', JSON.stringify(session, null, 2)); return { session, }; diff --git a/apps/web/src/app/manifest.ts b/apps/web/src/app/manifest.ts new file mode 100644 index 00000000..4fb65d4c --- /dev/null +++ b/apps/web/src/app/manifest.ts @@ -0,0 +1,20 @@ +import type { MetadataRoute } from 'next'; + +export default function manifest(): MetadataRoute.Manifest { + return { + name: 'Openpanel.dev', + short_name: 'Openpanel.dev', + description: '', + start_url: '/', + display: 'standalone', + background_color: '#fff', + theme_color: '#fff', + icons: [ + { + src: 'https://openpanel.dev/favicon.ico', + sizes: 'any', + type: 'image/x-icon', + }, + ], + }; +} diff --git a/apps/web/src/components/clients/ClientActions.tsx b/apps/web/src/components/clients/ClientActions.tsx index 30e831d6..06be845d 100644 --- a/apps/web/src/components/clients/ClientActions.tsx +++ b/apps/web/src/components/clients/ClientActions.tsx @@ -2,12 +2,13 @@ import { api } from '@/app/_trpc/client'; import { pushModal, showConfirm } from '@/modals'; -import type { IClientWithProject } from '@/types'; import { clipboard } from '@/utils/clipboard'; import { MoreHorizontal } from 'lucide-react'; import { useRouter } from 'next/navigation'; import { toast } from 'sonner'; +import type { IServiceClientWithProject } from '@mixan/db'; + import { Button } from '../ui/button'; import { DropdownMenu, @@ -18,7 +19,7 @@ import { DropdownMenuTrigger, } from '../ui/dropdown-menu'; -export function ClientActions(client: IClientWithProject) { +export function ClientActions(client: IServiceClientWithProject) { const { id } = client; const router = useRouter(); const deletion = api.client.remove.useMutation({ diff --git a/apps/web/src/components/clients/table.tsx b/apps/web/src/components/clients/table.tsx index b6b45453..ed563b50 100644 --- a/apps/web/src/components/clients/table.tsx +++ b/apps/web/src/components/clients/table.tsx @@ -1,10 +1,11 @@ -import type { IClientWithProject } from '@/types'; import { formatDate } from '@/utils/date'; import type { ColumnDef } from '@tanstack/react-table'; +import type { IServiceClientWithProject } from '@mixan/db'; + import { ClientActions } from './ClientActions'; -export const columns: ColumnDef[] = [ +export const columns: ColumnDef[] = [ { accessorKey: 'name', header: 'Name', diff --git a/apps/web/src/components/general/ExpandableListItem.tsx b/apps/web/src/components/general/ExpandableListItem.tsx index 9b585de9..c25e0d38 100644 --- a/apps/web/src/components/general/ExpandableListItem.tsx +++ b/apps/web/src/components/general/ExpandableListItem.tsx @@ -8,7 +8,7 @@ import { Button } from '../ui/button'; interface ExpandableListItemProps { children: React.ReactNode; content: React.ReactNode; - title: string; + title: React.ReactNode; image?: React.ReactNode; initialOpen?: boolean; className?: string; @@ -29,7 +29,7 @@ export function ExpandableListItem({
{image}
- {title} +
{title}
{!!content && (
{content} diff --git a/apps/web/src/components/navbar/Breadcrumbs.tsx b/apps/web/src/components/navbar/Breadcrumbs.tsx deleted file mode 100644 index 983b1baf..00000000 --- a/apps/web/src/components/navbar/Breadcrumbs.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { api } from '@/app/_trpc/client'; -import { useOrganizationParams } from '@/hooks/useOrganizationParams'; -import { ChevronRight, HomeIcon } from 'lucide-react'; -import Link from 'next/link'; -import { usePathname } from 'next/navigation'; - -import { Container } from '../Container'; - -export function Breadcrumbs() { - const params = useOrganizationParams(); - - const org = api.organization.get.useQuery( - { - id: params.organizationId, - }, - { - enabled: !!params.organizationId, - staleTime: Infinity, - } - ); - - const pro = api.project.get.useQuery( - { - id: params.projectId, - }, - { - enabled: !!params.projectId, - staleTime: Infinity, - } - ); - - const dashboard = api.dashboard.get.useQuery( - { - id: params.dashboardId, - }, - { - enabled: !!params.dashboardId, - staleTime: Infinity, - } - ); - - return ( -
- - {org.isLoading && pro.isLoading && ( -
- )} - {org.data && ( - <> - - - {org.data.name} - - - )} - - {org.data && pro.data && ( - <> - - - {pro.data.name} - - - )} - - {org.data && pro.data && dashboard.data && ( - <> - - - {dashboard.data.name} - - - )} -
-
- ); -} diff --git a/apps/web/src/components/navbar/NavbarCreate.tsx b/apps/web/src/components/navbar/NavbarCreate.tsx deleted file mode 100644 index 50a28d1c..00000000 --- a/apps/web/src/components/navbar/NavbarCreate.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuGroup, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu'; -import { useOrganizationParams } from '@/hooks/useOrganizationParams'; -import { LineChart } from 'lucide-react'; -import Link from 'next/link'; - -export function NavbarCreate() { - const params = useOrganizationParams(); - return ( - - - - - - Actions - - - - - - Create a report - - - - - - ); -} diff --git a/apps/web/src/components/navbar/NavbarMenu.tsx b/apps/web/src/components/navbar/NavbarMenu.tsx deleted file mode 100644 index db02c057..00000000 --- a/apps/web/src/components/navbar/NavbarMenu.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { useOrganizationParams } from '@/hooks/useOrganizationParams'; -import { cn } from '@/utils/cn'; -import type { LinkProps } from 'next/link'; -import Link from 'next/link'; - -import { strip } from '@mixan/common'; - -import { NavbarUserDropdown } from './NavbarUserDropdown'; - -function Item({ - children, - ...props -}: LinkProps & { children: React.ReactNode }) { - return ( - -
- {children} - - ); -} - -export function NavbarMenu() { - const params = useOrganizationParams(); - return ( -
- {params.projectId && ( - - Dashboards - - )} - {params.projectId && ( - - Events - - )} - {params.projectId && ( - - Profiles - - )} - {params.projectId && ( - - Create report - - )} - -
- ); -} diff --git a/apps/web/src/components/navbar/NavbarUserDropdown.tsx b/apps/web/src/components/navbar/NavbarUserDropdown.tsx deleted file mode 100644 index 5ac5eb2c..00000000 --- a/apps/web/src/components/navbar/NavbarUserDropdown.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { Avatar, AvatarFallback } from '@/components/ui/avatar'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuGroup, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu'; -import { useOrganizationParams } from '@/hooks/useOrganizationParams'; -import { User } from 'lucide-react'; -import { signOut, useSession } from 'next-auth/react'; -import Link from 'next/link'; - -export function NavbarUserDropdown() { - const params = useOrganizationParams(); - const session = useSession(); - const user = session.data?.user; - - return ( - - - - {user?.name?.charAt(0) ?? '🤠'} - - - - - - - - Organization - - - - - - Projects - - - - - - Clients - - - - - - Profile - - - - { - signOut().catch(console.error); - }} - > - - Logout - - - - - ); -} diff --git a/apps/web/src/components/overview/filters/overview-filters-buttons.tsx b/apps/web/src/components/overview/filters/overview-filters-buttons.tsx index a98e5f2f..2a7a3460 100644 --- a/apps/web/src/components/overview/filters/overview-filters-buttons.tsx +++ b/apps/web/src/components/overview/filters/overview-filters-buttons.tsx @@ -1,10 +1,13 @@ 'use client'; import { Button } from '@/components/ui/button'; -import { useEventQueryFilters } from '@/hooks/useEventQueryFilters'; +import { + useEventQueryFilters, + useEventQueryNamesFilter, +} from '@/hooks/useEventQueryFilters'; import { cn } from '@/utils/cn'; import { X } from 'lucide-react'; -import { Options as NuqsOptions } from 'nuqs'; +import type { Options as NuqsOptions } from 'nuqs'; interface OverviewFiltersButtonsProps { className?: string; @@ -15,25 +18,40 @@ export function OverviewFiltersButtons({ className, nuqsOptions, }: OverviewFiltersButtonsProps) { - const eventQueryFilters = useEventQueryFilters(nuqsOptions); - const filters = Object.entries(eventQueryFilters).filter( - ([, filter]) => filter.get !== null - ); - if (filters.length === 0) return null; + const [events, setEvents] = useEventQueryNamesFilter(nuqsOptions); + const [filters, setFilter] = useEventQueryFilters(nuqsOptions); + if (filters.length === 0 && events.length === 0) return null; return (
- {filters.map(([key, filter]) => ( + {events.map((event) => ( ))} + {filters.map((filter) => { + if (!filter.value[0]) { + return null; + } + + return ( + + ); + })}
); } diff --git a/apps/web/src/components/overview/filters/overview-filters-drawer-content.tsx b/apps/web/src/components/overview/filters/overview-filters-drawer-content.tsx index 3d270514..045b5818 100644 --- a/apps/web/src/components/overview/filters/overview-filters-drawer-content.tsx +++ b/apps/web/src/components/overview/filters/overview-filters-drawer-content.tsx @@ -1,93 +1,131 @@ -'use client'; - -import { api } from '@/app/_trpc/client'; import { Button } from '@/components/ui/button'; import { Combobox } from '@/components/ui/combobox'; -import { useEventQueryFilters } from '@/hooks/useEventQueryFilters'; +import { ComboboxAdvanced } from '@/components/ui/combobox-advanced'; +import { SheetHeader, SheetTitle } from '@/components/ui/sheet'; +import { useEventNames } from '@/hooks/useEventNames'; +import { useEventProperties } from '@/hooks/useEventProperties'; +import { + useEventQueryFilters, + useEventQueryNamesFilter, +} from '@/hooks/useEventQueryFilters'; +import { useEventValues } from '@/hooks/useEventValues'; import { XIcon } from 'lucide-react'; -import { Options as NuqsOptions } from 'nuqs'; +import type { Options as NuqsOptions } from 'nuqs'; + +import type { + IChartEventFilter, + IChartEventFilterOperator, + IChartEventFilterValue, +} from '@mixan/validation'; interface OverviewFiltersProps { projectId: string; nuqsOptions?: NuqsOptions; + enableEventsFilter?: boolean; } export function OverviewFiltersDrawerContent({ projectId, nuqsOptions, + enableEventsFilter, }: OverviewFiltersProps) { - const eventQueryFilters = useEventQueryFilters(nuqsOptions); + const [filters, setFilter] = useEventQueryFilters(nuqsOptions); + const [event, setEvent] = useEventQueryNamesFilter(nuqsOptions); + const eventNames = useEventNames(projectId); + const eventProperties = useEventProperties(projectId); return (
-

Overview filters

- { - // @ts-expect-error - eventQueryFilters[value].set(''); - }} - value="" - placeholder="Filter by..." - label="What do you want to filter by?" - items={Object.entries(eventQueryFilters) - .filter(([, filter]) => filter.get === null) - .map(([name]) => ({ - label: name, - value: name, + + Overview filters + + +
+ {enableEventsFilter && ( + ({ + label: item.name, + value: item.name, + }))} + placeholder="Select event" + /> + )} + { + setFilter(value, ''); + }} + value="" + placeholder="Filter by property" + label="What do you want to filter by?" + items={eventProperties.map((item) => ({ + label: item, + value: item, }))} - searchable - /> + searchable + /> +
- {Object.entries(eventQueryFilters) - .filter(([, filter]) => filter.get !== null) - .map(([name, filter]) => ( - - ))} + {filters + .filter((filter) => filter.value[0] !== null) + .map((filter) => { + return ( + + ); + })}
); } export function FilterOption({ - name, - get, - set, + setFilter, projectId, -}: { - name: string; - get: string | null; - set: (value: string | null) => void; + ...filter +}: IChartEventFilter & { projectId: string; + setFilter: ( + name: string, + value: IChartEventFilterValue, + operator: IChartEventFilterOperator + ) => void; }) { - const { data } = api.chart.values.useQuery({ + const values = useEventValues( projectId, - event: name === 'path' ? 'screen_view' : 'session_start', - property: name, - }); + filter.name === 'path' ? 'screen_view' : 'session_start', + filter.name + ); return (
-
{name}
+
{filter.name}
set(value)} + onChange={(value) => setFilter(filter.name, value, filter.operator)} placeholder={'Select a value'} - items={ - data?.values.filter(Boolean).map((value) => ({ - value, - label: value, - })) ?? [] - } - value={get} + items={values.map((value) => ({ + value, + label: value, + }))} + value={String(filter.value[0] ?? '')} /> -
diff --git a/apps/web/src/components/overview/filters/overview-filters-drawer.tsx b/apps/web/src/components/overview/filters/overview-filters-drawer.tsx index 63070e05..cd2b3969 100644 --- a/apps/web/src/components/overview/filters/overview-filters-drawer.tsx +++ b/apps/web/src/components/overview/filters/overview-filters-drawer.tsx @@ -3,18 +3,20 @@ import { Button } from '@/components/ui/button'; import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'; import { FilterIcon } from 'lucide-react'; -import { Options as NuqsOptions } from 'nuqs'; +import type { Options as NuqsOptions } from 'nuqs'; import { OverviewFiltersDrawerContent } from './overview-filters-drawer-content'; interface OverviewFiltersDrawerProps { projectId: string; nuqsOptions?: NuqsOptions; + enableEventsFilter?: boolean; } export function OverviewFiltersDrawer({ projectId, nuqsOptions, + enableEventsFilter, }: OverviewFiltersDrawerProps) { return ( @@ -27,6 +29,7 @@ export function OverviewFiltersDrawer({ diff --git a/apps/web/src/components/overview/overview-live-histogram.tsx b/apps/web/src/components/overview/overview-live-histogram.tsx index d47f18a7..cf39f154 100644 --- a/apps/web/src/components/overview/overview-live-histogram.tsx +++ b/apps/web/src/components/overview/overview-live-histogram.tsx @@ -1,10 +1,11 @@ 'use client'; -import type { IChartInput } from '@/types'; import { cn } from '@/utils/cn'; import { ChevronsUpDownIcon } from 'lucide-react'; import AnimateHeight from 'react-animate-height'; +import type { IChartInput } from '@mixan/validation'; + import { Chart } from '../report/chart'; import { Widget, WidgetBody, WidgetHead } from '../Widget'; import { useOverviewOptions } from './useOverviewOptions'; diff --git a/apps/web/src/components/overview/overview-top-devices.tsx b/apps/web/src/components/overview/overview-top-devices.tsx index 593b9f8e..246f3c64 100644 --- a/apps/web/src/components/overview/overview-top-devices.tsx +++ b/apps/web/src/components/overview/overview-top-devices.tsx @@ -1,10 +1,7 @@ 'use client'; import { Chart } from '@/components/report/chart'; -import { - useEventFilters, - useEventQueryFilters, -} from '@/hooks/useEventQueryFilters'; +import { useEventQueryFilters } from '@/hooks/useEventQueryFilters'; import { cn } from '@/utils/cn'; import { Widget, WidgetBody } from '../Widget'; @@ -19,9 +16,7 @@ export default function OverviewTopDevices({ projectId, }: OverviewTopDevicesProps) { const { interval, range, previous } = useOverviewOptions(); - const filters = useEventFilters(); - const { device, browser, browserVersion, os, osVersion } = - useEventQueryFilters(); + const [filters, setFilter] = useEventQueryFilters(); const [widget, setWidget, widgets] = useOverviewWidget('tech', { devices: { title: 'Top devices', @@ -190,21 +185,21 @@ export default function OverviewTopDevices({ onClick={(item) => { switch (widget.key) { case 'devices': - device.set(item.name); + setFilter('device', item.name); break; case 'browser': setWidget('browser_version'); - browser.set(item.name); + setFilter('browser', item.name); break; case 'browser_version': - browserVersion.set(item.name); + setFilter('browser_version', item.name); break; case 'os': setWidget('os_version'); - os.set(item.name); + setFilter('os', item.name); break; case 'os_version': - osVersion.set(item.name); + setFilter('os_version', item.name); break; } }} diff --git a/apps/web/src/components/overview/overview-top-events.tsx b/apps/web/src/components/overview/overview-top-events.tsx index ee2695ac..f783e316 100644 --- a/apps/web/src/components/overview/overview-top-events.tsx +++ b/apps/web/src/components/overview/overview-top-events.tsx @@ -1,7 +1,7 @@ 'use client'; import { Chart } from '@/components/report/chart'; -import { useEventFilters } from '@/hooks/useEventQueryFilters'; +import { useEventQueryFilters } from '@/hooks/useEventQueryFilters'; import { cn } from '@/utils/cn'; import { Widget, WidgetBody } from '../Widget'; @@ -16,7 +16,7 @@ export default function OverviewTopEvents({ projectId, }: OverviewTopEventsProps) { const { interval, range, previous } = useOverviewOptions(); - const filters = useEventFilters(); + const [filters] = useEventQueryFilters(); const [widget, setWidget, widgets] = useOverviewWidget('ev', { all: { title: 'Top events', diff --git a/apps/web/src/components/overview/overview-top-geo.tsx b/apps/web/src/components/overview/overview-top-geo.tsx index fb78e79b..b058bdf3 100644 --- a/apps/web/src/components/overview/overview-top-geo.tsx +++ b/apps/web/src/components/overview/overview-top-geo.tsx @@ -1,10 +1,7 @@ 'use client'; import { Chart } from '@/components/report/chart'; -import { - useEventFilters, - useEventQueryFilters, -} from '@/hooks/useEventQueryFilters'; +import { useEventQueryFilters } from '@/hooks/useEventQueryFilters'; import { cn } from '@/utils/cn'; import { Widget, WidgetBody } from '../Widget'; @@ -17,8 +14,7 @@ interface OverviewTopGeoProps { } export default function OverviewTopGeo({ projectId }: OverviewTopGeoProps) { const { interval, range, previous } = useOverviewOptions(); - const filters = useEventFilters(); - const { region, country, city } = useEventQueryFilters(); + const [filters, setFilter] = useEventQueryFilters(); const [widget, setWidget, widgets] = useOverviewWidget('geo', { map: { title: 'Map', @@ -160,14 +156,14 @@ export default function OverviewTopGeo({ projectId }: OverviewTopGeoProps) { switch (widget.key) { case 'countries': setWidget('regions'); - country.set(item.name); + setFilter('country', item.name); break; case 'regions': setWidget('cities'); - region.set(item.name); + setFilter('region', item.name); break; case 'cities': - city.set(item.name); + setFilter('city', item.name); break; } }} diff --git a/apps/web/src/components/overview/overview-top-pages.tsx b/apps/web/src/components/overview/overview-top-pages.tsx index 0ead0ccc..8784c940 100644 --- a/apps/web/src/components/overview/overview-top-pages.tsx +++ b/apps/web/src/components/overview/overview-top-pages.tsx @@ -1,10 +1,7 @@ 'use client'; import { Chart } from '@/components/report/chart'; -import { - useEventFilters, - useEventQueryFilters, -} from '@/hooks/useEventQueryFilters'; +import { useEventQueryFilters } from '@/hooks/useEventQueryFilters'; import { cn } from '@/utils/cn'; import { Widget, WidgetBody } from '../Widget'; @@ -17,8 +14,7 @@ interface OverviewTopPagesProps { } export default function OverviewTopPages({ projectId }: OverviewTopPagesProps) { const { interval, range, previous } = useOverviewOptions(); - const filters = useEventFilters(); - const { path } = useEventQueryFilters(); + const [filters, setFilter] = useEventQueryFilters(); const [widget, setWidget, widgets] = useOverviewWidget('pages', { top: { title: 'Top pages', @@ -129,7 +125,7 @@ export default function OverviewTopPages({ projectId }: OverviewTopPagesProps) { {...widget.chart} previous={false} onClick={(item) => { - path.set(item.name); + setFilter('path', item.name); }} /> diff --git a/apps/web/src/components/overview/overview-top-sources.tsx b/apps/web/src/components/overview/overview-top-sources.tsx index fa5f7cf2..07907e61 100644 --- a/apps/web/src/components/overview/overview-top-sources.tsx +++ b/apps/web/src/components/overview/overview-top-sources.tsx @@ -1,10 +1,7 @@ 'use client'; import { Chart } from '@/components/report/chart'; -import { - useEventFilters, - useEventQueryFilters, -} from '@/hooks/useEventQueryFilters'; +import { useEventQueryFilters } from '@/hooks/useEventQueryFilters'; import { cn } from '@/utils/cn'; import { Widget, WidgetBody } from '../Widget'; @@ -19,17 +16,7 @@ export default function OverviewTopSources({ projectId, }: OverviewTopSourcesProps) { const { interval, range, previous } = useOverviewOptions(); - const { - referrer, - referrerName, - referrerType, - utmCampaign, - utmContent, - utmMedium, - utmSource, - utmTerm, - } = useEventQueryFilters(); - const filters = useEventFilters(); + const [filters, setFilter] = useEventQueryFilters(); const [widget, setWidget, widgets] = useOverviewWidget('sources', { all: { title: 'Top sources', @@ -282,30 +269,30 @@ export default function OverviewTopSources({ onClick={(item) => { switch (widget.key) { case 'all': - referrerName.set(item.name); + setFilter('referrer_name', item.name); setWidget('domain'); break; case 'domain': - referrer.set(item.name); + setFilter('referrer', item.name); break; case 'type': - referrerType.set(item.name); + setFilter('referrer_type', item.name); setWidget('domain'); break; case 'utm_source': - utmSource.set(item.name); + setFilter('utm_source', item.name); break; case 'utm_medium': - utmMedium.set(item.name); + setFilter('utm_medium', item.name); break; case 'utm_campaign': - utmCampaign.set(item.name); + setFilter('utm_campaign', item.name); break; case 'utm_term': - utmTerm.set(item.name); + setFilter('utm_term', item.name); break; case 'utm_content': - utmContent.set(item.name); + setFilter('utm_content', item.name); break; } }} diff --git a/apps/web/src/components/overview/useOverviewOptions.ts b/apps/web/src/components/overview/useOverviewOptions.ts index 6cd076e9..704239e4 100644 --- a/apps/web/src/components/overview/useOverviewOptions.ts +++ b/apps/web/src/components/overview/useOverviewOptions.ts @@ -1,6 +1,3 @@ -import { useEventQueryFilters } from '@/hooks/useEventQueryFilters'; -import { getDefaultIntervalByRange, timeRanges } from '@/utils/constants'; -import { mapKeys } from '@/utils/validation'; import { parseAsBoolean, parseAsInteger, @@ -8,6 +5,9 @@ import { useQueryState, } from 'nuqs'; +import { getDefaultIntervalByRange, timeRanges } from '@mixan/constants'; +import { mapKeys } from '@mixan/validation'; + const nuqsOptions = { history: 'push' } as const; export function useOverviewOptions() { diff --git a/apps/web/src/components/overview/useOverviewWidget.tsx b/apps/web/src/components/overview/useOverviewWidget.tsx index 242a1240..62e0dd5e 100644 --- a/apps/web/src/components/overview/useOverviewWidget.tsx +++ b/apps/web/src/components/overview/useOverviewWidget.tsx @@ -1,7 +1,8 @@ -import type { IChartInput } from '@/types'; -import { mapKeys } from '@/utils/validation'; import { parseAsStringEnum, useQueryState } from 'nuqs'; +import { mapKeys } from '@mixan/validation'; +import type { IChartInput } from '@mixan/validation'; + export function useOverviewWidget( key: string, widgets: Record @@ -15,7 +16,7 @@ export function useOverviewWidget( ); return [ { - ...widgets[widget]!, + ...widgets[widget], key: widget, }, setWidget, diff --git a/apps/web/src/components/profiles/ProfileAvatar.tsx b/apps/web/src/components/profiles/ProfileAvatar.tsx index 385d841e..76820930 100644 --- a/apps/web/src/components/profiles/ProfileAvatar.tsx +++ b/apps/web/src/components/profiles/ProfileAvatar.tsx @@ -1,11 +1,12 @@ 'use client'; -import type { IServiceProfile } from '@/server/services/profile.service'; import { cn } from '@/utils/cn'; import { AvatarImage } from '@radix-ui/react-avatar'; import type { VariantProps } from 'class-variance-authority'; import { cva } from 'class-variance-authority'; +import type { IServiceProfile } from '@mixan/db'; + import { Avatar, AvatarFallback } from '../ui/avatar'; interface ProfileAvatarProps @@ -41,8 +42,8 @@ export function ProfileAvatar({ size === 'sm' ? 'text-xs' : size === 'xs' - ? 'text-[8px]' - : 'text-base', + ? 'text-[8px]' + : 'text-base', 'bg-slate-200 text-slate-800' )} > diff --git a/apps/web/src/components/projects/ProjectActions.tsx b/apps/web/src/components/projects/ProjectActions.tsx index 0bc3d8ef..e11ff397 100644 --- a/apps/web/src/components/projects/ProjectActions.tsx +++ b/apps/web/src/components/projects/ProjectActions.tsx @@ -2,12 +2,13 @@ import { api } from '@/app/_trpc/client'; import { pushModal, showConfirm } from '@/modals'; -import type { IProject } from '@/types'; import { clipboard } from '@/utils/clipboard'; import { MoreHorizontal } from 'lucide-react'; import { useRouter } from 'next/navigation'; import { toast } from 'sonner'; +import type { IServiceProject } from '@mixan/db'; + import { Button } from '../ui/button'; import { DropdownMenu, @@ -18,7 +19,7 @@ import { DropdownMenuTrigger, } from '../ui/dropdown-menu'; -export function ProjectActions(project: IProject) { +export function ProjectActions(project: Exclude) { const { id } = project; const router = useRouter(); const deletion = api.project.remove.useMutation({ diff --git a/apps/web/src/components/projects/table.tsx b/apps/web/src/components/projects/table.tsx index bff79da3..0367b7a5 100644 --- a/apps/web/src/components/projects/table.tsx +++ b/apps/web/src/components/projects/table.tsx @@ -1,7 +1,7 @@ -import { IServiceProject } from '@/server/services/project.service'; import { formatDate } from '@/utils/date'; import type { ColumnDef } from '@tanstack/react-table'; +import { IServiceProject } from '@mixan/db'; import type { Project as IProject } from '@mixan/db'; import { ProjectActions } from './ProjectActions'; diff --git a/apps/web/src/components/report/ReportChartType.tsx b/apps/web/src/components/report/ReportChartType.tsx index 33655e27..51c492a0 100644 --- a/apps/web/src/components/report/ReportChartType.tsx +++ b/apps/web/src/components/report/ReportChartType.tsx @@ -1,8 +1,9 @@ import { useDispatch, useSelector } from '@/redux'; -import { chartTypes } from '@/utils/constants'; -import { objectToZodEnums } from '@/utils/validation'; import { LineChartIcon } from 'lucide-react'; +import { chartTypes } from '@mixan/constants'; +import { objectToZodEnums } from '@mixan/validation'; + import { Combobox } from '../ui/combobox'; import { changeChartType } from './reportSlice'; diff --git a/apps/web/src/components/report/ReportInterval.tsx b/apps/web/src/components/report/ReportInterval.tsx index 8eed5dee..d55330d0 100644 --- a/apps/web/src/components/report/ReportInterval.tsx +++ b/apps/web/src/components/report/ReportInterval.tsx @@ -1,10 +1,11 @@ import { useDispatch, useSelector } from '@/redux'; -import type { IInterval } from '@/types'; +import { ClockIcon } from 'lucide-react'; + import { isHourIntervalEnabledByRange, isMinuteIntervalEnabledByRange, -} from '@/utils/constants'; -import { ClockIcon } from 'lucide-react'; +} from '@mixan/constants'; +import type { IInterval } from '@mixan/validation'; import { Combobox } from '../ui/combobox'; import { changeInterval } from './reportSlice'; @@ -32,7 +33,7 @@ export function ReportInterval({ className }: ReportIntervalProps) { className={className} placeholder="Interval" onChange={(value) => { - dispatch(changeInterval(value as IInterval)); + dispatch(changeInterval(value)); }} value={interval} items={[ diff --git a/apps/web/src/components/report/ReportLineType.tsx b/apps/web/src/components/report/ReportLineType.tsx index a829933b..272567ee 100644 --- a/apps/web/src/components/report/ReportLineType.tsx +++ b/apps/web/src/components/report/ReportLineType.tsx @@ -1,8 +1,9 @@ import { useDispatch, useSelector } from '@/redux'; -import { lineTypes } from '@/utils/constants'; -import { objectToZodEnums } from '@/utils/validation'; import { Tv2Icon } from 'lucide-react'; +import { lineTypes } from '@mixan/constants'; +import { objectToZodEnums } from '@mixan/validation'; + import { Combobox } from '../ui/combobox'; import { changeLineType } from './reportSlice'; diff --git a/apps/web/src/components/report/ReportRange.tsx b/apps/web/src/components/report/ReportRange.tsx index 51d51fdc..0a8df92a 100644 --- a/apps/web/src/components/report/ReportRange.tsx +++ b/apps/web/src/components/report/ReportRange.tsx @@ -1,7 +1,8 @@ -import type { IChartRange } from '@/types'; -import { timeRanges } from '@/utils/constants'; import { CalendarIcon } from 'lucide-react'; +import { timeRanges } from '@mixan/constants'; +import type { IChartRange } from '@mixan/validation'; + import type { ExtendedComboboxProps } from '../ui/combobox'; import { Combobox } from '../ui/combobox'; diff --git a/apps/web/src/components/report/chart/ChartProvider.tsx b/apps/web/src/components/report/chart/ChartProvider.tsx index e852f44a..38541245 100644 --- a/apps/web/src/components/report/chart/ChartProvider.tsx +++ b/apps/web/src/components/report/chart/ChartProvider.tsx @@ -10,7 +10,8 @@ import { useState, } from 'react'; import type { IChartSerie } from '@/server/api/routers/chart'; -import type { IChartInput } from '@/types'; + +import type { IChartInput } from '@mixan/validation'; import { ChartLoading } from './ChartLoading'; import { MetricCardLoading } from './MetricCard'; diff --git a/apps/web/src/components/report/chart/MetricCard.tsx b/apps/web/src/components/report/chart/MetricCard.tsx index 12745909..dc1034bb 100644 --- a/apps/web/src/components/report/chart/MetricCard.tsx +++ b/apps/web/src/components/report/chart/MetricCard.tsx @@ -3,11 +3,12 @@ import type { IChartData } from '@/app/_trpc/client'; import { ColorSquare } from '@/components/ColorSquare'; import { useNumber } from '@/hooks/useNumerFormatter'; -import type { IChartMetric } from '@/types'; import { theme } from '@/utils/theme'; import AutoSizer from 'react-virtualized-auto-sizer'; import { Area, AreaChart } from 'recharts'; +import type { IChartMetric } from '@mixan/validation'; + import { PreviousDiffIndicator } from '../PreviousDiffIndicator'; interface MetricCardProps { diff --git a/apps/web/src/components/report/chart/ReportAreaChart.tsx b/apps/web/src/components/report/chart/ReportAreaChart.tsx index 2f5ca221..64a6b188 100644 --- a/apps/web/src/components/report/chart/ReportAreaChart.tsx +++ b/apps/web/src/components/report/chart/ReportAreaChart.tsx @@ -4,7 +4,6 @@ import { AutoSizer } from '@/components/AutoSizer'; import { useFormatDateInterval } from '@/hooks/useFormatDateInterval'; import { useRechartDataModel } from '@/hooks/useRechartDataModel'; import { useVisibleSeries } from '@/hooks/useVisibleSeries'; -import type { IChartLineType, IInterval } from '@/types'; import { cn } from '@/utils/cn'; import { getChartColor } from '@/utils/theme'; import { @@ -16,6 +15,8 @@ import { YAxis, } from 'recharts'; +import type { IChartLineType, IInterval } from '@mixan/validation'; + import { getYAxisWidth } from './chart-utils'; import { useChartContext } from './ChartProvider'; import { ReportChartTooltip } from './ReportChartTooltip'; diff --git a/apps/web/src/components/report/chart/ReportBarChart.tsx b/apps/web/src/components/report/chart/ReportBarChart.tsx index 337982f9..24e1040b 100644 --- a/apps/web/src/components/report/chart/ReportBarChart.tsx +++ b/apps/web/src/components/report/chart/ReportBarChart.tsx @@ -5,9 +5,10 @@ import type { IChartData } from '@/app/_trpc/client'; import { Progress } from '@/components/ui/progress'; import { useNumber } from '@/hooks/useNumerFormatter'; import { cn } from '@/utils/cn'; -import { NOT_SET_VALUE } from '@/utils/constants'; import { getChartColor } from '@/utils/theme'; +import { NOT_SET_VALUE } from '@mixan/constants'; + import { PreviousDiffIndicator } from '../PreviousDiffIndicator'; import { useChartContext } from './ChartProvider'; import { SerieIcon } from './SerieIcon'; diff --git a/apps/web/src/components/report/chart/ReportChartTooltip.tsx b/apps/web/src/components/report/chart/ReportChartTooltip.tsx index b7eca33a..28c889f5 100644 --- a/apps/web/src/components/report/chart/ReportChartTooltip.tsx +++ b/apps/web/src/components/report/chart/ReportChartTooltip.tsx @@ -3,7 +3,6 @@ import { useFormatDateInterval } from '@/hooks/useFormatDateInterval'; import { useMappings } from '@/hooks/useMappings'; import { useNumber } from '@/hooks/useNumerFormatter'; import type { IRechartPayloadItem } from '@/hooks/useRechartDataModel'; -import { useSelector } from '@/redux'; import type { IToolTipProps } from '@/types'; import { PreviousDiffIndicator } from '../PreviousDiffIndicator'; diff --git a/apps/web/src/components/report/chart/ReportHistogramChart.tsx b/apps/web/src/components/report/chart/ReportHistogramChart.tsx index 50a63125..402a6bbb 100644 --- a/apps/web/src/components/report/chart/ReportHistogramChart.tsx +++ b/apps/web/src/components/report/chart/ReportHistogramChart.tsx @@ -4,11 +4,12 @@ import { AutoSizer } from '@/components/AutoSizer'; import { useFormatDateInterval } from '@/hooks/useFormatDateInterval'; import { useRechartDataModel } from '@/hooks/useRechartDataModel'; import { useVisibleSeries } from '@/hooks/useVisibleSeries'; -import type { IInterval } from '@/types'; import { cn } from '@/utils/cn'; import { getChartColor, theme } from '@/utils/theme'; import { Bar, BarChart, CartesianGrid, Tooltip, XAxis, YAxis } from 'recharts'; +import type { IInterval } from '@mixan/validation'; + import { getYAxisWidth } from './chart-utils'; import { useChartContext } from './ChartProvider'; import { ReportChartTooltip } from './ReportChartTooltip'; diff --git a/apps/web/src/components/report/chart/ReportLineChart.tsx b/apps/web/src/components/report/chart/ReportLineChart.tsx index 75b6e9e0..4284f983 100644 --- a/apps/web/src/components/report/chart/ReportLineChart.tsx +++ b/apps/web/src/components/report/chart/ReportLineChart.tsx @@ -6,7 +6,6 @@ import { AutoSizer } from '@/components/AutoSizer'; import { useFormatDateInterval } from '@/hooks/useFormatDateInterval'; import { useRechartDataModel } from '@/hooks/useRechartDataModel'; import { useVisibleSeries } from '@/hooks/useVisibleSeries'; -import type { IChartLineType, IInterval } from '@/types'; import { cn } from '@/utils/cn'; import { getChartColor } from '@/utils/theme'; import { @@ -18,6 +17,8 @@ import { YAxis, } from 'recharts'; +import type { IChartLineType, IInterval } from '@mixan/validation'; + import { getYAxisWidth } from './chart-utils'; import { useChartContext } from './ChartProvider'; import { ReportChartTooltip } from './ReportChartTooltip'; diff --git a/apps/web/src/components/report/chart/SerieIcon.tsx b/apps/web/src/components/report/chart/SerieIcon.tsx index 12652bb9..dacd14a0 100644 --- a/apps/web/src/components/report/chart/SerieIcon.tsx +++ b/apps/web/src/components/report/chart/SerieIcon.tsx @@ -1,5 +1,4 @@ import { useMemo } from 'react'; -import { NOT_SET_VALUE } from '@/utils/constants'; import type { LucideIcon, LucideProps } from 'lucide-react'; import { ActivityIcon, @@ -15,6 +14,8 @@ import { TabletIcon, } from 'lucide-react'; +import { NOT_SET_VALUE } from '@mixan/constants'; + interface SerieIconProps extends LucideProps { name: string; } diff --git a/apps/web/src/components/report/chart/index.tsx b/apps/web/src/components/report/chart/index.tsx index a8334c68..2923cf46 100644 --- a/apps/web/src/components/report/chart/index.tsx +++ b/apps/web/src/components/report/chart/index.tsx @@ -3,7 +3,8 @@ import { memo, useEffect, useState } from 'react'; import type { RouterOutputs } from '@/app/_trpc/client'; import { api } from '@/app/_trpc/client'; -import type { IChartInput } from '@/types'; + +import type { IChartInput } from '@mixan/validation'; import { ChartEmpty } from './ChartEmpty'; import { ChartLoading } from './ChartLoading'; diff --git a/apps/web/src/components/report/reportSlice.ts b/apps/web/src/components/report/reportSlice.ts index b8c3dec6..78d2d0fa 100644 --- a/apps/web/src/components/report/reportSlice.ts +++ b/apps/web/src/components/report/reportSlice.ts @@ -1,3 +1,12 @@ +import { createSlice } from '@reduxjs/toolkit'; +import type { PayloadAction } from '@reduxjs/toolkit'; + +import { + alphabetIds, + getDefaultIntervalByRange, + isHourIntervalEnabledByRange, + isMinuteIntervalEnabledByRange, +} from '@mixan/constants'; import type { IChartBreakdown, IChartEvent, @@ -6,15 +15,7 @@ import type { IChartRange, IChartType, IInterval, -} from '@/types'; -import { - alphabetIds, - getDefaultIntervalByRange, - isHourIntervalEnabledByRange, - isMinuteIntervalEnabledByRange, -} from '@/utils/constants'; -import { createSlice } from '@reduxjs/toolkit'; -import type { PayloadAction } from '@reduxjs/toolkit'; +} from '@mixan/validation'; type InitialState = IChartInput & { dirty: boolean; diff --git a/apps/web/src/components/report/sidebar/EventPropertiesCombobox.tsx b/apps/web/src/components/report/sidebar/EventPropertiesCombobox.tsx index ed21b2e6..e25a2261 100644 --- a/apps/web/src/components/report/sidebar/EventPropertiesCombobox.tsx +++ b/apps/web/src/components/report/sidebar/EventPropertiesCombobox.tsx @@ -2,10 +2,11 @@ import { api } from '@/app/_trpc/client'; import { Combobox } from '@/components/ui/combobox'; import { useAppParams } from '@/hooks/useAppParams'; import { useDispatch } from '@/redux'; -import type { IChartEvent } from '@/types'; import { cn } from '@/utils/cn'; import { DatabaseIcon } from 'lucide-react'; +import type { IChartEvent } from '@mixan/validation'; + import { changeEvent } from '../reportSlice'; interface EventPropertiesComboboxProps { diff --git a/apps/web/src/components/report/sidebar/ReportBreakdowns.tsx b/apps/web/src/components/report/sidebar/ReportBreakdowns.tsx index dcbb63fc..27e21b35 100644 --- a/apps/web/src/components/report/sidebar/ReportBreakdowns.tsx +++ b/apps/web/src/components/report/sidebar/ReportBreakdowns.tsx @@ -5,9 +5,10 @@ import { ColorSquare } from '@/components/ColorSquare'; import { Combobox } from '@/components/ui/combobox'; import { useAppParams } from '@/hooks/useAppParams'; import { useDispatch, useSelector } from '@/redux'; -import type { IChartBreakdown } from '@/types'; import { SplitIcon } from 'lucide-react'; +import type { IChartBreakdown } from '@mixan/validation'; + import { addBreakdown, changeBreakdown, removeBreakdown } from '../reportSlice'; import { ReportBreakdownMore } from './ReportBreakdownMore'; import type { ReportEventMoreProps } from './ReportEventMore'; diff --git a/apps/web/src/components/report/sidebar/ReportEvents.tsx b/apps/web/src/components/report/sidebar/ReportEvents.tsx index ea258b92..def2a7bd 100644 --- a/apps/web/src/components/report/sidebar/ReportEvents.tsx +++ b/apps/web/src/components/report/sidebar/ReportEvents.tsx @@ -1,6 +1,5 @@ 'use client'; -import { api } from '@/app/_trpc/client'; import { ColorSquare } from '@/components/ColorSquare'; import { Dropdown } from '@/components/Dropdown'; import { Checkbox } from '@/components/ui/checkbox'; @@ -8,10 +7,12 @@ import { Combobox } from '@/components/ui/combobox'; import { Input } from '@/components/ui/input'; import { useAppParams } from '@/hooks/useAppParams'; import { useDebounceFn } from '@/hooks/useDebounceFn'; +import { useEventNames } from '@/hooks/useEventNames'; import { useDispatch, useSelector } from '@/redux'; -import type { IChartEvent } from '@/types'; import { GanttChart, GanttChartIcon, Users } from 'lucide-react'; +import type { IChartEvent } from '@mixan/validation'; + import { addEvent, changeEvent, @@ -29,14 +30,8 @@ export function ReportEvents() { const selectedEvents = useSelector((state) => state.report.events); const dispatch = useDispatch(); const { projectId } = useAppParams(); + const eventNames = useEventNames(projectId); - const eventsQuery = api.chart.events.useQuery({ - projectId, - }); - const eventsCombobox = (eventsQuery.data ?? []).map((item) => ({ - value: item.name, - label: item.name, - })); const dispatchChangeEvent = useDebounceFn((event: IChartEvent) => { dispatch(changeEvent(event)); }); @@ -76,7 +71,10 @@ export function ReportEvents() { }) ); }} - items={eventsCombobox} + items={eventNames.map((item) => ({ + label: item.name, + value: item.name, + }))} placeholder="Select event" /> ({ + label: item.name, + value: item.name, + }))} placeholder="Select event" />
diff --git a/apps/web/src/components/report/sidebar/filters/FilterItem.tsx b/apps/web/src/components/report/sidebar/filters/FilterItem.tsx index 8e457836..75017222 100644 --- a/apps/web/src/components/report/sidebar/filters/FilterItem.tsx +++ b/apps/web/src/components/report/sidebar/filters/FilterItem.tsx @@ -7,13 +7,15 @@ import { RenderDots } from '@/components/ui/RenderDots'; import { useAppParams } from '@/hooks/useAppParams'; import { useMappings } from '@/hooks/useMappings'; import { useDispatch } from '@/redux'; +import { SlidersHorizontal, Trash } from 'lucide-react'; + +import { operators } from '@mixan/constants'; import type { IChartEvent, - IChartEventFilter, + IChartEventFilterOperator, IChartEventFilterValue, -} from '@/types'; -import { operators } from '@/utils/constants'; -import { SlidersHorizontal, Trash } from 'lucide-react'; +} from '@mixan/validation'; +import { mapKeys } from '@mixan/validation'; import { changeEvent } from '../../reportSlice'; @@ -67,7 +69,7 @@ export function FilterItem({ filter, event }: FilterProps) { ); }; - const changeFilterOperator = (operator: IChartEventFilter['operator']) => { + const changeFilterOperator = (operator: IChartEventFilterOperator) => { dispatch( changeEvent({ ...event, @@ -104,9 +106,9 @@ export function FilterItem({ filter, event }: FilterProps) {
({ - value: key as IChartEventFilter['operator'], - label: value, + items={mapKeys(operators).map((key) => ({ + value: key, + label: operators[key], }))} label="Operator" > diff --git a/apps/web/src/components/report/sidebar/filters/FiltersCombobox.tsx b/apps/web/src/components/report/sidebar/filters/FiltersCombobox.tsx index e639b61c..f244af63 100644 --- a/apps/web/src/components/report/sidebar/filters/FiltersCombobox.tsx +++ b/apps/web/src/components/report/sidebar/filters/FiltersCombobox.tsx @@ -2,9 +2,10 @@ import { api } from '@/app/_trpc/client'; import { Combobox } from '@/components/ui/combobox'; import { useAppParams } from '@/hooks/useAppParams'; import { useDispatch } from '@/redux'; -import type { IChartEvent } from '@/types'; import { FilterIcon } from 'lucide-react'; +import type { IChartEvent } from '@mixan/validation'; + import { changeEvent } from '../../reportSlice'; interface FiltersComboboxProps { diff --git a/apps/web/src/components/report/sidebar/filters/FiltersList.tsx b/apps/web/src/components/report/sidebar/filters/FiltersList.tsx index 03c4f920..cc9dc981 100644 --- a/apps/web/src/components/report/sidebar/filters/FiltersList.tsx +++ b/apps/web/src/components/report/sidebar/filters/FiltersList.tsx @@ -1,4 +1,4 @@ -import type { IChartEvent } from '@/types'; +import type { IChartEvent } from '@mixan/validation'; import { FilterItem } from './FilterItem'; diff --git a/apps/web/src/components/ui/combobox-advanced.tsx b/apps/web/src/components/ui/combobox-advanced.tsx index f8f6e9d5..35d8618f 100644 --- a/apps/web/src/components/ui/combobox-advanced.tsx +++ b/apps/web/src/components/ui/combobox-advanced.tsx @@ -25,6 +25,7 @@ interface ComboboxAdvancedProps { onChange: React.Dispatch>; items: IItem[]; placeholder: string; + className?: string; } export function ComboboxAdvanced({ @@ -32,6 +33,7 @@ export function ComboboxAdvanced({ value, onChange, placeholder, + className, }: ComboboxAdvancedProps) { const [open, setOpen] = React.useState(false); const [inputValue, setInputValue] = React.useState(''); @@ -81,8 +83,12 @@ export function ComboboxAdvanced({ return ( -