From b4bb162f4d62678fa33d8eac8c7600370fb82cc9 Mon Sep 17 00:00:00 2001 From: iza <59828082+izadoesdev@users.noreply.github.com> Date: Wed, 26 Aug 2026 00:39:45 +0300 Subject: [PATCH 01/22] chore: delete 109 unused files flagged by knip --- apps/api/src/middleware/website-auth.ts | 195 --- .../organizations/components/empty-state.tsx | 65 - .../settings/_components/chart-preview.tsx | 60 - .../[id]/_components/utils/tab-layout.tsx | 67 - .../[id]/flags/_components/flag-key.tsx | 40 - .../[id]/goals/_components/goal-analytics.tsx | 207 ---- .../[id]/pulse/_components/monitor-card.tsx | 151 --- .../[id]/pulse/_components/status-header.tsx | 194 --- .../components/ai-elements/canvas.tsx | 22 - .../ai-elements/chain-of-thought.tsx | 39 - .../components/ai-elements/connection.tsx | 28 - .../components/ai-elements/context.tsx | 422 ------- .../components/ai-elements/controls.tsx | 18 - .../dashboard/components/ai-elements/edge.tsx | 140 --- .../components/ai-elements/image.tsx | 30 - .../components/ai-elements/loader.tsx | 96 -- .../components/ai-elements/model-selector.tsx | 204 --- .../dashboard/components/ai-elements/node.tsx | 65 - .../components/ai-elements/panel.tsx | 15 - .../components/ai-elements/suggestion.tsx | 53 - .../components/ai-elements/toolbar.tsx | 16 - .../components/atomic/FormattedNumber.tsx | 22 - .../components/atomic/PageLinkCell.tsx | 72 -- apps/dashboard/components/bits/Aurora.tsx | 216 ---- .../components/dev-tools/dev-tools-drawer.tsx | 1092 ----------------- apps/dashboard/components/empty-state.tsx | 292 ----- .../components/layout/help-dialog.tsx | 158 --- .../monitors/collapsible-section.tsx | 66 - apps/dashboard/components/ui/aspect-ratio.tsx | 11 - apps/dashboard/components/ui/badge.tsx | 52 - apps/dashboard/components/ui/card.tsx | 92 -- apps/dashboard/components/ui/carousel.tsx | 249 ---- apps/dashboard/components/ui/command.tsx | 174 --- apps/dashboard/components/ui/dialog.tsx | 146 --- .../components/ui/elastic-slider.tsx | 224 ---- apps/dashboard/components/ui/form-dialog.tsx | 147 --- apps/dashboard/components/ui/form.tsx | 167 --- .../dashboard/components/ui/inline-toggle.tsx | 60 - apps/dashboard/components/ui/input-group.tsx | 169 --- apps/dashboard/components/ui/input.tsx | 132 -- .../components/ui/keyboard-shortcuts.tsx | 104 -- apps/dashboard/components/ui/label.tsx | 24 - .../components/ui/percentage-badge.tsx | 39 - apps/dashboard/components/ui/progress.tsx | 34 - .../components/ui/segmented-control.tsx | 76 -- apps/dashboard/components/ui/separator.tsx | 28 - apps/dashboard/components/ui/sheet.tsx | 153 --- apps/dashboard/components/ui/skeleton.tsx | 13 - apps/dashboard/components/ui/slider.tsx | 63 - apps/dashboard/components/ui/spinner.tsx | 12 - apps/dashboard/components/ui/switch.tsx | 31 - apps/dashboard/components/ui/tabs.tsx | 311 ----- apps/dashboard/components/ui/tags.tsx | 164 --- apps/dashboard/components/ui/textarea.tsx | 66 - apps/dashboard/components/ui/toggle-group.tsx | 72 -- apps/dashboard/components/ui/toggle.tsx | 47 - .../components/ui/tooltip-bubble.tsx | 23 - apps/dashboard/components/ui/tooltip.tsx | 66 - apps/dashboard/components/user-avatar.tsx | 88 -- apps/dashboard/error.tsx | 44 - .../lib/ai-components/renderers/config.ts | 7 - apps/dashboard/lib/autumn/attach-content.tsx | 15 - .../lib/flags/get-examples-strategy.ts | 83 -- apps/dashboard/lib/orpc-public.ts | 13 - apps/dashboard/types/invitations.ts | 24 - apps/dashboard/types/performance.ts | 35 - apps/dashboard/types/pulse-features.ts | 325 ----- apps/docs/components/bits/liquid.tsx | 199 --- apps/docs/components/bits/squares.tsx | 197 --- apps/docs/components/docs-navbar.tsx | 264 ---- apps/docs/components/features.tsx | 240 ---- apps/docs/components/icons/hipaa.tsx | 27 - apps/docs/components/icons/iso27001.tsx | 46 - apps/docs/components/icons/soc.tsx | 22 - apps/docs/components/icons/yc-logo.tsx | 29 - .../landing/home-insights-showcase.tsx | 106 -- .../components/navbar-github-desktop-link.tsx | 23 - .../components/navbar-github-mobile-link.tsx | 51 - apps/docs/components/theme-toggle.tsx | 38 - apps/docs/components/ui/alert-dialog.tsx | 156 --- apps/docs/components/ui/alert.tsx | 66 - apps/docs/components/ui/aspect-ratio.tsx | 11 - apps/docs/components/ui/breadcrumb.tsx | 110 -- apps/docs/components/ui/calendar.tsx | 211 ---- apps/docs/components/ui/carousel.tsx | 251 ---- apps/docs/components/ui/chart.tsx | 362 ------ apps/docs/components/ui/checkbox.tsx | 32 - apps/docs/components/ui/collapsible.tsx | 33 - apps/docs/components/ui/command.tsx | 183 --- apps/docs/components/ui/dialog.tsx | 142 --- apps/docs/components/ui/drawer.tsx | 135 -- apps/docs/components/ui/dropdown-menu.tsx | 257 ---- apps/docs/components/ui/form.tsx | 165 --- apps/docs/components/ui/hover-card.tsx | 44 - apps/docs/components/ui/input-otp.tsx | 77 -- apps/docs/components/ui/menubar.tsx | 276 ----- apps/docs/components/ui/navigation-menu.tsx | 168 --- apps/docs/components/ui/pagination.tsx | 122 -- apps/docs/components/ui/progress.tsx | 30 - apps/docs/components/ui/radio-group.tsx | 45 - apps/docs/components/ui/sidebar.tsx | 734 ----------- apps/docs/components/ui/tabs.tsx | 66 - apps/docs/components/ui/toggle-group.tsx | 70 -- apps/docs/components/ui/toggle.tsx | 47 - apps/docs/components/ui/tooltip.tsx | 61 - apps/docs/hooks/use-mobile.ts | 19 - apps/docs/lib/format-locale-number.ts | 5 - packages/rpc/src/utils/cache-invalidation.ts | 98 -- packages/rpc/src/utils/referrer.ts | 6 - 109 files changed, 12852 deletions(-) delete mode 100644 apps/api/src/middleware/website-auth.ts delete mode 100644 apps/dashboard/app/(main)/organizations/components/empty-state.tsx delete mode 100644 apps/dashboard/app/(main)/settings/_components/chart-preview.tsx delete mode 100644 apps/dashboard/app/(main)/websites/[id]/_components/utils/tab-layout.tsx delete mode 100644 apps/dashboard/app/(main)/websites/[id]/flags/_components/flag-key.tsx delete mode 100644 apps/dashboard/app/(main)/websites/[id]/goals/_components/goal-analytics.tsx delete mode 100644 apps/dashboard/app/(main)/websites/[id]/pulse/_components/monitor-card.tsx delete mode 100644 apps/dashboard/app/(main)/websites/[id]/pulse/_components/status-header.tsx delete mode 100644 apps/dashboard/components/ai-elements/canvas.tsx delete mode 100644 apps/dashboard/components/ai-elements/chain-of-thought.tsx delete mode 100644 apps/dashboard/components/ai-elements/connection.tsx delete mode 100644 apps/dashboard/components/ai-elements/context.tsx delete mode 100644 apps/dashboard/components/ai-elements/controls.tsx delete mode 100644 apps/dashboard/components/ai-elements/edge.tsx delete mode 100644 apps/dashboard/components/ai-elements/image.tsx delete mode 100644 apps/dashboard/components/ai-elements/loader.tsx delete mode 100644 apps/dashboard/components/ai-elements/model-selector.tsx delete mode 100644 apps/dashboard/components/ai-elements/node.tsx delete mode 100644 apps/dashboard/components/ai-elements/panel.tsx delete mode 100644 apps/dashboard/components/ai-elements/suggestion.tsx delete mode 100644 apps/dashboard/components/ai-elements/toolbar.tsx delete mode 100644 apps/dashboard/components/atomic/FormattedNumber.tsx delete mode 100644 apps/dashboard/components/atomic/PageLinkCell.tsx delete mode 100644 apps/dashboard/components/bits/Aurora.tsx delete mode 100644 apps/dashboard/components/dev-tools/dev-tools-drawer.tsx delete mode 100644 apps/dashboard/components/empty-state.tsx delete mode 100644 apps/dashboard/components/layout/help-dialog.tsx delete mode 100644 apps/dashboard/components/monitors/collapsible-section.tsx delete mode 100644 apps/dashboard/components/ui/aspect-ratio.tsx delete mode 100644 apps/dashboard/components/ui/badge.tsx delete mode 100644 apps/dashboard/components/ui/card.tsx delete mode 100644 apps/dashboard/components/ui/carousel.tsx delete mode 100644 apps/dashboard/components/ui/command.tsx delete mode 100644 apps/dashboard/components/ui/dialog.tsx delete mode 100644 apps/dashboard/components/ui/elastic-slider.tsx delete mode 100644 apps/dashboard/components/ui/form-dialog.tsx delete mode 100644 apps/dashboard/components/ui/form.tsx delete mode 100644 apps/dashboard/components/ui/inline-toggle.tsx delete mode 100644 apps/dashboard/components/ui/input-group.tsx delete mode 100644 apps/dashboard/components/ui/input.tsx delete mode 100644 apps/dashboard/components/ui/keyboard-shortcuts.tsx delete mode 100644 apps/dashboard/components/ui/label.tsx delete mode 100644 apps/dashboard/components/ui/percentage-badge.tsx delete mode 100644 apps/dashboard/components/ui/progress.tsx delete mode 100644 apps/dashboard/components/ui/segmented-control.tsx delete mode 100644 apps/dashboard/components/ui/separator.tsx delete mode 100644 apps/dashboard/components/ui/sheet.tsx delete mode 100644 apps/dashboard/components/ui/skeleton.tsx delete mode 100644 apps/dashboard/components/ui/slider.tsx delete mode 100644 apps/dashboard/components/ui/spinner.tsx delete mode 100644 apps/dashboard/components/ui/switch.tsx delete mode 100644 apps/dashboard/components/ui/tabs.tsx delete mode 100644 apps/dashboard/components/ui/tags.tsx delete mode 100644 apps/dashboard/components/ui/textarea.tsx delete mode 100644 apps/dashboard/components/ui/toggle-group.tsx delete mode 100644 apps/dashboard/components/ui/toggle.tsx delete mode 100644 apps/dashboard/components/ui/tooltip-bubble.tsx delete mode 100644 apps/dashboard/components/ui/tooltip.tsx delete mode 100644 apps/dashboard/components/user-avatar.tsx delete mode 100644 apps/dashboard/error.tsx delete mode 100644 apps/dashboard/lib/ai-components/renderers/config.ts delete mode 100644 apps/dashboard/lib/autumn/attach-content.tsx delete mode 100644 apps/dashboard/lib/flags/get-examples-strategy.ts delete mode 100644 apps/dashboard/lib/orpc-public.ts delete mode 100644 apps/dashboard/types/invitations.ts delete mode 100644 apps/dashboard/types/performance.ts delete mode 100644 apps/dashboard/types/pulse-features.ts delete mode 100644 apps/docs/components/bits/liquid.tsx delete mode 100644 apps/docs/components/bits/squares.tsx delete mode 100644 apps/docs/components/docs-navbar.tsx delete mode 100644 apps/docs/components/features.tsx delete mode 100644 apps/docs/components/icons/hipaa.tsx delete mode 100644 apps/docs/components/icons/iso27001.tsx delete mode 100644 apps/docs/components/icons/soc.tsx delete mode 100644 apps/docs/components/icons/yc-logo.tsx delete mode 100644 apps/docs/components/landing/home-insights-showcase.tsx delete mode 100644 apps/docs/components/navbar-github-desktop-link.tsx delete mode 100644 apps/docs/components/navbar-github-mobile-link.tsx delete mode 100644 apps/docs/components/theme-toggle.tsx delete mode 100644 apps/docs/components/ui/alert-dialog.tsx delete mode 100644 apps/docs/components/ui/alert.tsx delete mode 100644 apps/docs/components/ui/aspect-ratio.tsx delete mode 100644 apps/docs/components/ui/breadcrumb.tsx delete mode 100644 apps/docs/components/ui/calendar.tsx delete mode 100644 apps/docs/components/ui/carousel.tsx delete mode 100644 apps/docs/components/ui/chart.tsx delete mode 100644 apps/docs/components/ui/checkbox.tsx delete mode 100644 apps/docs/components/ui/collapsible.tsx delete mode 100644 apps/docs/components/ui/command.tsx delete mode 100644 apps/docs/components/ui/dialog.tsx delete mode 100644 apps/docs/components/ui/drawer.tsx delete mode 100644 apps/docs/components/ui/dropdown-menu.tsx delete mode 100644 apps/docs/components/ui/form.tsx delete mode 100644 apps/docs/components/ui/hover-card.tsx delete mode 100644 apps/docs/components/ui/input-otp.tsx delete mode 100644 apps/docs/components/ui/menubar.tsx delete mode 100644 apps/docs/components/ui/navigation-menu.tsx delete mode 100644 apps/docs/components/ui/pagination.tsx delete mode 100644 apps/docs/components/ui/progress.tsx delete mode 100644 apps/docs/components/ui/radio-group.tsx delete mode 100644 apps/docs/components/ui/sidebar.tsx delete mode 100644 apps/docs/components/ui/tabs.tsx delete mode 100644 apps/docs/components/ui/toggle-group.tsx delete mode 100644 apps/docs/components/ui/toggle.tsx delete mode 100644 apps/docs/components/ui/tooltip.tsx delete mode 100644 apps/docs/hooks/use-mobile.ts delete mode 100644 apps/docs/lib/format-locale-number.ts delete mode 100644 packages/rpc/src/utils/cache-invalidation.ts delete mode 100644 packages/rpc/src/utils/referrer.ts diff --git a/apps/api/src/middleware/website-auth.ts b/apps/api/src/middleware/website-auth.ts deleted file mode 100644 index 0199fcbbd3..0000000000 --- a/apps/api/src/middleware/website-auth.ts +++ /dev/null @@ -1,195 +0,0 @@ -import { - getApiKeyFromHeader, - hasWebsiteScopeForOrganization, - isApiKeyPresent, -} from "@databuddy/api-keys/resolve"; -import { auth } from "@databuddy/auth"; -import { db } from "@databuddy/db"; -import { Elysia } from "elysia"; -import { getResolvedAuth } from "../lib/auth-wide-event"; -import { getCachedWebsite, getTimezone } from "@databuddy/ai/lib/website-utils"; - -interface SessionUser { - email: string; - id: string; - name: string; -} - -function json(status: number, body: unknown) { - return new Response(JSON.stringify(body), { - status, - headers: { "Content-Type": "application/json" }, - }); -} - -function getSessionUser( - session: Awaited> | null -): SessionUser | null { - if (!session?.user) { - return null; - } - - return { - email: session.user.email, - id: session.user.id, - name: session.user.name, - }; -} - -export function websiteAuth() { - return new Elysia() - .derive(async ({ request }) => { - if (isPreflight(request)) { - return { - user: null, - session: null, - website: undefined, - timezone: "UTC", - _apiKey: null, - _apiKeyPresent: false, - _authChecked: true, - } as const; - } - - const url = new URL(request.url); - const websiteId = url.searchParams.get("website_id"); - - const preResolved = getResolvedAuth(request.headers); - let sessionUser: SessionUser | null = null; - let session: Awaited> | null = - null; - let apiKey: Awaited> | null = null; - const apiKeyPresent = isApiKeyPresent(request.headers); - - if (preResolved) { - session = preResolved.session; - sessionUser = getSessionUser(session); - apiKey = preResolved.apiKeyResult?.key ?? null; - } else { - const [resolvedApiKey, resolvedSession] = await Promise.all([ - apiKeyPresent ? getApiKeyFromHeader(request.headers) : null, - auth.api.getSession({ headers: request.headers }), - ]); - session = resolvedSession; - sessionUser = getSessionUser(session); - apiKey = resolvedApiKey; - } - - const website = websiteId ? await getCachedWebsite(websiteId) : undefined; - - const timezone = session?.user - ? await getTimezone(request, session) - : await getTimezone(request, null); - - return { - user: sessionUser, - session, - website, - timezone, - _apiKey: apiKey, - _apiKeyPresent: apiKeyPresent, - _authChecked: true, - } as const; - }) - .onBeforeHandle(({ user, website, _apiKey, _apiKeyPresent, request }) => { - if (isPreflight(request)) { - return; - } - - const url = new URL(request.url); - const websiteId = url.searchParams.get("website_id"); - - if (!websiteId) { - if (user || _apiKey) { - return null; - } - return json(401, { - success: false, - error: "Authentication required", - code: "AUTH_REQUIRED", - }); - } - - return checkWebsiteAuth( - websiteId, - user, - website ?? null, - _apiKey, - _apiKeyPresent - ); - }); -} - -function isPreflight(request: Request): boolean { - return request.method === "OPTIONS" || request.method === "HEAD"; -} - -async function checkWebsiteAuth( - _websiteId: string, - sessionUser: SessionUser | null, - website: Awaited> | null, - apiKey: Awaited> | null, - apiKeyPresent: boolean -): Promise { - if (!website) { - return json(404, { - success: false, - error: "Website not found", - code: "NOT_FOUND", - }); - } - if (website.isPublic) { - return null; - } - - if (sessionUser) { - if (!website.organizationId) { - return json(403, { - success: false, - error: "Website must belong to a workspace", - code: "FORBIDDEN", - }); - } - - const membership = await db.query.member.findFirst({ - where: { userId: sessionUser.id, organizationId: website.organizationId }, - columns: { - id: true, - }, - }); - - if (membership) { - return null; - } - - return json(403, { - success: false, - error: "Access denied to this website", - code: "FORBIDDEN", - }); - } - - if (!apiKeyPresent) { - return json(401, { - success: false, - error: "Authentication required", - code: "AUTH_REQUIRED", - }); - } - if (!apiKey) { - return json(401, { - success: false, - error: "Invalid or expired API key", - code: "AUTH_REQUIRED", - }); - } - const ok = hasWebsiteScopeForOrganization(apiKey, website, "read:data"); - if (!ok) { - return json(403, { - success: false, - error: "Insufficient permissions", - code: "FORBIDDEN", - }); - } - return null; -} diff --git a/apps/dashboard/app/(main)/organizations/components/empty-state.tsx b/apps/dashboard/app/(main)/organizations/components/empty-state.tsx deleted file mode 100644 index 512de8da79..0000000000 --- a/apps/dashboard/app/(main)/organizations/components/empty-state.tsx +++ /dev/null @@ -1,65 +0,0 @@ -"use client"; - -import type { ComponentType, ReactNode, SVGProps } from "react"; - -type IconComponent = ComponentType< - SVGProps & { size?: number | string; weight?: string } ->; - -interface EmptyStateProps { - action?: ReactNode; - description: string; - features?: Array<{ - label: string; - }>; - icon: IconComponent; - title: string; - variant?: "default" | "success" | "warning" | "destructive"; -} - -export function EmptyState({ - icon: Icon, - title, - description, - features, - action, - variant = "default", -}: EmptyStateProps) { - const variantStyles = { - default: "border-accent bg-accent/50 text-primary", - success: "border-green-200 bg-green-100 text-green-600", - warning: "border-orange-200 bg-orange-100 text-orange-600", - destructive: "border-destructive/20 bg-destructive/10 text-destructive", - }; - - return ( -
-
- -
-

{title}

-

- {description} -

- {features && ( -
-
- {features.map((feature, index) => ( -
-
- {feature.label} -
- ))} -
-
- )} - {action &&
{action}
} -
- ); -} diff --git a/apps/dashboard/app/(main)/settings/_components/chart-preview.tsx b/apps/dashboard/app/(main)/settings/_components/chart-preview.tsx deleted file mode 100644 index 7ce3533de2..0000000000 --- a/apps/dashboard/app/(main)/settings/_components/chart-preview.tsx +++ /dev/null @@ -1,60 +0,0 @@ -"use client"; - -import { Chart } from "@/components/ui/composables/chart"; -import { cn } from "@/lib/utils"; -import { Card } from "@databuddy/ui"; - -const previewData = [ - { date: "Mon", value: 186 }, - { date: "Tue", value: 305 }, - { date: "Wed", value: 237 }, - { date: "Thu", value: 73 }, - { date: "Fri", value: 209 }, - { date: "Sat", value: 214 }, -]; - -const ChartPreview = ({ - chartType, - className, - size = 150, -}: { - chartType: "bar" | "line" | "area" | "composed"; - className?: string; - size?: number; -}) => { - const chartId = `chart-preview-${chartType}`; - const chartHeight = size - 16; - - const seriesKind = - chartType === "bar" || chartType === "composed" - ? "bar" - : chartType === "line" - ? "line" - : "area"; - - return ( - - -
- -
-
-
- ); -}; - -export default ChartPreview; diff --git a/apps/dashboard/app/(main)/websites/[id]/_components/utils/tab-layout.tsx b/apps/dashboard/app/(main)/websites/[id]/_components/utils/tab-layout.tsx deleted file mode 100644 index 7ea43999db..0000000000 --- a/apps/dashboard/app/(main)/websites/[id]/_components/utils/tab-layout.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import type React from "react"; -import { Skeleton } from "@databuddy/ui"; -import { BORDER_RADIUS } from "./ui-components"; - -interface TabLayoutProps { - actions?: React.ReactNode; - children: React.ReactNode; - className?: string; - description?: string; - isLoading?: boolean; - title?: string; -} - -export function TabLayout({ - title, - description, - isLoading = false, - children, - className = "", - actions, -}: TabLayoutProps) { - if (isLoading) { - return ; - } - - return ( -
- {(title || description || actions) && ( -
- {(title || description) && ( -
- {title &&

{title}

} - {description && ( -

{description}

- )} -
- )} - {actions &&
{actions}
} -
- )} - {children} -
- ); -} - -export function TabLoadingSkeleton() { - const loadingSkeletonIds = [ - "loading-skeleton-1", - "loading-skeleton-2", - "loading-skeleton-3", - "loading-skeleton-4", - ]; - return ( -
-
- {loadingSkeletonIds.map((id) => ( - - ))} -
- -
- - -
-
- ); -} diff --git a/apps/dashboard/app/(main)/websites/[id]/flags/_components/flag-key.tsx b/apps/dashboard/app/(main)/websites/[id]/flags/_components/flag-key.tsx deleted file mode 100644 index d86c812ee9..0000000000 --- a/apps/dashboard/app/(main)/websites/[id]/flags/_components/flag-key.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"; -import { cn } from "@/lib/utils"; -import type { Flag } from "./types"; -import { CheckIcon, CopyIcon } from "@databuddy/ui/icons"; -import { Button, Tooltip } from "@databuddy/ui"; - -export function FlagKey({ - flag, - className, - ...props -}: { flag: Flag } & React.ComponentProps<"button">) { - const { isCopied, copyToClipboard } = useCopyToClipboard(); - - return ( - - - - ); -} diff --git a/apps/dashboard/app/(main)/websites/[id]/goals/_components/goal-analytics.tsx b/apps/dashboard/app/(main)/websites/[id]/goals/_components/goal-analytics.tsx deleted file mode 100644 index 3940dbb308..0000000000 --- a/apps/dashboard/app/(main)/websites/[id]/goals/_components/goal-analytics.tsx +++ /dev/null @@ -1,207 +0,0 @@ -"use client"; - -import { formatNumber } from "@/lib/formatters"; -import { - ArrowClockwiseIcon as ArrowClockwise, - TargetIcon as Target, - TrendUpIcon as TrendUp, - UsersIcon as Users, -} from "@databuddy/ui/icons"; -import { Button, Card } from "@databuddy/ui"; - -interface GoalAnalyticsProps { - data: any; - error: Error | null; - isLoading: boolean; - onRetry: () => void; - summaryStats: { - totalUsers: number; - conversionRate: number; - completions: number; - }; -} - -export function GoalAnalytics({ - isLoading, - error, - data, - summaryStats, - onRetry, -}: GoalAnalyticsProps) { - if (isLoading) { - return ( -
-
- {[...new Array(3)].map((_, i) => ( - - -
-
- - - ))} -
-
- ); - } - - if (error) { - return ( - - -
-
-

- Failed to load goal analytics -

-

{error.message}

-
- -
-
-
- ); - } - - if (!(data?.success && data.data)) { - return ( - - -

- No analytics data available -

-
-
- ); - } - - const formatPercentage = (num: number) => `${num.toFixed(1)}%`; - - return ( -
-
- - -
-
- -
-
-

- Total Users -

-

- {formatNumber(summaryStats.totalUsers)} -

-
-
-
-
- - - -
-
- -
-
-

- Completions -

-

- {formatNumber(summaryStats.completions)} -

-
-
-
-
- - - -
-
- -
-
-

- Conversion Rate -

-

- {formatPercentage(summaryStats.conversionRate)} -

-
-
-
-
-
- - - - Goal Performance - - -
-
-
- Performance Summary - - {data.date_range?.start_date} - {data.date_range?.end_date} - -
- -
-
-

- Users who reached goal -

-

- {formatNumber(summaryStats.completions)} /{" "} - {formatNumber(summaryStats.totalUsers)} -

-
-
-

- Success rate -

-

- {formatPercentage(summaryStats.conversionRate)} -

-
-
-
- - {data.data.avg_completion_time > 0 && ( -
-
- - Average Time to Complete - - - {data.data.avg_completion_time_formatted || - `${Math.round(data.data.avg_completion_time)}s`} - -
-
- )} -
-
-
-
- ); -} diff --git a/apps/dashboard/app/(main)/websites/[id]/pulse/_components/monitor-card.tsx b/apps/dashboard/app/(main)/websites/[id]/pulse/_components/monitor-card.tsx deleted file mode 100644 index fd74d7a15c..0000000000 --- a/apps/dashboard/app/(main)/websites/[id]/pulse/_components/monitor-card.tsx +++ /dev/null @@ -1,151 +0,0 @@ -"use client"; - -import { useMutation } from "@tanstack/react-query"; -import { useState } from "react"; -import { toast } from "sonner"; -import { orpc } from "@/lib/orpc"; -import { - DotsThreeIcon, - HeartbeatIcon, - PencilIcon, - TrashIcon, -} from "@databuddy/ui/icons"; -import { Card, GhostTriggerButton } from "@databuddy/ui"; -import { DropdownMenu } from "@databuddy/ui/client"; - -const granularityLabels: Record = { - minute: "1m", - ten_minutes: "10m", - thirty_minutes: "30m", - hour: "1h", - six_hours: "6h", - twelve_hours: "12h", - day: "Daily", -}; - -interface MonitorCardProps { - onDeleteAction: () => void; - onEditAction: () => void; - onRefetchAction: () => void; - schedule: { - id: string; - granularity: string; - cron: string; - isPaused: boolean; - createdAt: Date | string; - updatedAt: Date | string; - }; -} - -export function MonitorCard({ - schedule, - onEditAction, - onDeleteAction, - onRefetchAction, -}: MonitorCardProps) { - const [isPausing, setIsPausing] = useState(false); - - const pauseMutation = useMutation({ - ...orpc.uptime.pauseSchedule.mutationOptions(), - }); - const resumeMutation = useMutation({ - ...orpc.uptime.resumeSchedule.mutationOptions(), - }); - - const handleTogglePause = async () => { - setIsPausing(true); - try { - if (schedule.isPaused) { - await resumeMutation.mutateAsync({ scheduleId: schedule.id }); - toast.success("Monitor resumed"); - } else { - await pauseMutation.mutateAsync({ scheduleId: schedule.id }); - toast.success("Monitor paused"); - } - onRefetchAction(); - } catch (error) { - const errorMessage = - error instanceof Error ? error.message : "Failed to update monitor"; - toast.error(errorMessage); - } finally { - setIsPausing(false); - } - }; - - return ( - - -
-
-
- -
-
-

- Uptime Monitor -

-
-
- Check Frequency: - - {granularityLabels[schedule.granularity] || - schedule.granularity} - -
-
- Status: - - {schedule.isPaused ? "Paused" : "Active"} - -
-
-
-
- - - - - - } - /> - - - - Edit - - - - {schedule.isPaused ? "Resume" : "Pause"} - - - - Delete - - - -
-
-
- ); -} diff --git a/apps/dashboard/app/(main)/websites/[id]/pulse/_components/status-header.tsx b/apps/dashboard/app/(main)/websites/[id]/pulse/_components/status-header.tsx deleted file mode 100644 index 556c55917e..0000000000 --- a/apps/dashboard/app/(main)/websites/[id]/pulse/_components/status-header.tsx +++ /dev/null @@ -1,194 +0,0 @@ -"use client"; - -import { useMutation } from "@tanstack/react-query"; -import { useState } from "react"; -import { toast } from "sonner"; -import { orpc } from "@/lib/orpc"; -import { cn } from "@/lib/utils"; -import { - CircleIcon, - PauseIcon, - PencilIcon, - PlayIcon, - TrashIcon, -} from "@databuddy/ui/icons"; -import { DropdownMenu } from "@databuddy/ui/client"; -import { Button, Card, GhostTriggerButton, fromNow } from "@databuddy/ui"; - -const granularityLabels: Record = { - minute: "Every minute", - ten_minutes: "Every 10 minutes", - thirty_minutes: "Every 30 minutes", - hour: "Hourly", - six_hours: "Every 6 hours", - twelve_hours: "Every 12 hours", - day: "Daily", -}; - -interface StatusHeaderProps { - currentStatus?: "up" | "down" | "unknown"; - lastCheck?: { - timestamp: string; - status: number; - probe_region?: string; - }; - onDeleteAction: () => void; - onEditAction: () => void; - onRefetchAction: () => void; - schedule: { - id: string; - granularity: string; - cron: string; - isPaused: boolean; - createdAt: Date | string; - updatedAt: Date | string; - }; -} - -export function StatusHeader({ - schedule, - currentStatus = "unknown", - lastCheck, - onEditAction, - onDeleteAction, - onRefetchAction, -}: StatusHeaderProps) { - const [isPausing, setIsPausing] = useState(false); - - const pauseMutation = useMutation({ - ...orpc.uptime.pauseSchedule.mutationOptions(), - }); - const resumeMutation = useMutation({ - ...orpc.uptime.resumeSchedule.mutationOptions(), - }); - - const handleTogglePause = async () => { - setIsPausing(true); - try { - if (schedule.isPaused) { - await resumeMutation.mutateAsync({ scheduleId: schedule.id }); - toast.success("Monitor resumed"); - } else { - await pauseMutation.mutateAsync({ scheduleId: schedule.id }); - toast.success("Monitor paused"); - } - onRefetchAction(); - } catch (error) { - const errorMessage = - error instanceof Error ? error.message : "Failed to update monitor"; - toast.error(errorMessage); - } finally { - setIsPausing(false); - } - }; - - const isOperational = - !schedule.isPaused && - (currentStatus === "up" || currentStatus === "unknown"); - const isDown = !schedule.isPaused && currentStatus === "down"; - const isPaused = schedule.isPaused; - - return ( - -
-
-
-
-

- {isPaused - ? "Monitoring Paused" - : isDown - ? "System Outage" - : "All Systems Operational"} -

-
-
- - {granularityLabels[schedule.granularity] || schedule.granularity} - - {lastCheck ? ( - <> - - Last checked {fromNow(lastCheck.timestamp)} - {lastCheck.probe_region ? ( - <> - - from {lastCheck.probe_region} - - ) : null} - - ) : null} -
-
- -
- - - - - - - } - /> - - - - Delete Monitor - - - -
-
- - ); -} diff --git a/apps/dashboard/components/ai-elements/canvas.tsx b/apps/dashboard/components/ai-elements/canvas.tsx deleted file mode 100644 index 05e66d648a..0000000000 --- a/apps/dashboard/components/ai-elements/canvas.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Background, ReactFlow, type ReactFlowProps } from "@xyflow/react"; -import type { ReactNode } from "react"; -import "@xyflow/react/dist/style.css"; - -type CanvasProps = ReactFlowProps & { - children?: ReactNode; -}; - -export const Canvas = ({ children, ...props }: CanvasProps) => ( - - - {children} - -); diff --git a/apps/dashboard/components/ai-elements/chain-of-thought.tsx b/apps/dashboard/components/ai-elements/chain-of-thought.tsx deleted file mode 100644 index 23a68f6065..0000000000 --- a/apps/dashboard/components/ai-elements/chain-of-thought.tsx +++ /dev/null @@ -1,39 +0,0 @@ -"use client"; - -import type { ComponentProps, ReactNode } from "react"; -import { memo } from "react"; -import { cn } from "@/lib/utils"; -import { CheckCircleIcon, CircleNotchIcon } from "@databuddy/ui/icons"; - -export type ToolStepProps = ComponentProps<"div"> & { - label: ReactNode; - status?: "complete" | "active"; -}; - -export const ToolStep = memo( - ({ className, label, status = "complete", ...props }: ToolStepProps) => ( -
- {status === "complete" ? ( - - ) : ( - - )} - {label} -
- ) -); - -ToolStep.displayName = "ToolStep"; diff --git a/apps/dashboard/components/ai-elements/connection.tsx b/apps/dashboard/components/ai-elements/connection.tsx deleted file mode 100644 index 99a58bc512..0000000000 --- a/apps/dashboard/components/ai-elements/connection.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import type { ConnectionLineComponent } from "@xyflow/react"; - -const HALF = 0.5; - -export const Connection: ConnectionLineComponent = ({ - fromX, - fromY, - toX, - toY, -}) => ( - - - - -); diff --git a/apps/dashboard/components/ai-elements/context.tsx b/apps/dashboard/components/ai-elements/context.tsx deleted file mode 100644 index eeaf3d7c30..0000000000 --- a/apps/dashboard/components/ai-elements/context.tsx +++ /dev/null @@ -1,422 +0,0 @@ -"use client"; - -import { - HoverCard, - HoverCardContent, - HoverCardTrigger, -} from "@/components/ui/hover-card"; -import { cn } from "@/lib/utils"; -import type { LanguageModelUsage } from "ai"; -import { type ComponentProps, createContext, useContext } from "react"; -import type { SourceModel } from "tokenlens"; -import { computeTokenCostsForModel } from "tokenlens/helpers"; -import { vercelModels } from "tokenlens/providers/vercel"; -import { Button, Progress } from "@databuddy/ui"; - -const lookupModel = (modelId: string): SourceModel | undefined => { - const model = - vercelModels.models[modelId as keyof typeof vercelModels.models]; - return model - ? ({ canonical_id: model.id, ...model } satisfies SourceModel) - : undefined; -}; - -const PERCENT_MAX = 100; -const ICON_RADIUS = 10; -const ICON_VIEWBOX = 24; -const ICON_CENTER = 12; -const ICON_STROKE_WIDTH = 2; - -type ModelId = string; - -interface ContextSchema { - maxTokens: number; - modelId?: ModelId; - usage?: LanguageModelUsage; - usedTokens: number; -} - -const ContextContext = createContext(null); - -const useContextValue = () => { - const context = useContext(ContextContext); - - if (!context) { - throw new Error("Context components must be used within Context"); - } - - return context; -}; - -export type ContextProps = ComponentProps & ContextSchema; - -export const Context = ({ - usedTokens, - maxTokens, - usage, - modelId, - ...props -}: ContextProps) => ( - - - -); - -const ContextIcon = () => { - const { usedTokens, maxTokens } = useContextValue(); - const circumference = 2 * Math.PI * ICON_RADIUS; - const usedPercent = usedTokens / maxTokens; - const dashOffset = circumference * (1 - usedPercent); - - return ( - - - - - ); -}; - -export type ContextTriggerProps = ComponentProps; - -export const ContextTrigger = ({ children, ...props }: ContextTriggerProps) => { - const { usedTokens, maxTokens } = useContextValue(); - const usedPercent = usedTokens / maxTokens; - const renderedPercent = new Intl.NumberFormat("en-US", { - style: "percent", - maximumFractionDigits: 1, - }).format(usedPercent); - - return ( - - {children ?? ( - - )} - - ); -}; - -export type ContextContentProps = ComponentProps; - -export const ContextContent = ({ - className, - ...props -}: ContextContentProps) => ( - -); - -export type ContextContentHeaderProps = ComponentProps<"div">; - -export const ContextContentHeader = ({ - children, - className, - ...props -}: ContextContentHeaderProps) => { - const { usedTokens, maxTokens } = useContextValue(); - const usedPercent = usedTokens / maxTokens; - const displayPct = new Intl.NumberFormat("en-US", { - style: "percent", - maximumFractionDigits: 1, - }).format(usedPercent); - const used = new Intl.NumberFormat("en-US", { - notation: "compact", - }).format(usedTokens); - const total = new Intl.NumberFormat("en-US", { - notation: "compact", - }).format(maxTokens); - - return ( -
- {children ?? ( - <> -
-

{displayPct}

-

- {used} / {total} -

-
-
- -
- - )} -
- ); -}; - -export type ContextContentBodyProps = ComponentProps<"div">; - -export const ContextContentBody = ({ - children, - className, - ...props -}: ContextContentBodyProps) => ( -
- {children} -
-); - -export type ContextContentFooterProps = ComponentProps<"div">; - -export const ContextContentFooter = ({ - children, - className, - ...props -}: ContextContentFooterProps) => { - const { modelId, usage } = useContextValue(); - const model = modelId ? lookupModel(modelId) : undefined; - const costUSD = model - ? computeTokenCostsForModel({ - model, - usage: { - input_tokens: usage?.inputTokens ?? 0, - output_tokens: usage?.outputTokens ?? 0, - }, - }).totalTokenCostUSD - : undefined; - const totalCost = new Intl.NumberFormat("en-US", { - style: "currency", - currency: "USD", - }).format(costUSD ?? 0); - - return ( -
- {children ?? ( - <> - Total cost - {totalCost} - - )} -
- ); -}; - -export type ContextInputUsageProps = ComponentProps<"div">; - -export const ContextInputUsage = ({ - className, - children, - ...props -}: ContextInputUsageProps) => { - const { usage, modelId } = useContextValue(); - const inputTokens = usage?.inputTokens ?? 0; - - if (children) { - return children; - } - - if (!inputTokens) { - return null; - } - - const inputModel = modelId ? lookupModel(modelId) : undefined; - const inputCost = inputModel - ? computeTokenCostsForModel({ - model: inputModel, - usage: { input_tokens: inputTokens, output_tokens: 0 }, - }).totalTokenCostUSD - : undefined; - const inputCostText = new Intl.NumberFormat("en-US", { - style: "currency", - currency: "USD", - }).format(inputCost ?? 0); - - return ( -
- Input - -
- ); -}; - -export type ContextOutputUsageProps = ComponentProps<"div">; - -export const ContextOutputUsage = ({ - className, - children, - ...props -}: ContextOutputUsageProps) => { - const { usage, modelId } = useContextValue(); - const outputTokens = usage?.outputTokens ?? 0; - - if (children) { - return children; - } - - if (!outputTokens) { - return null; - } - - const outputModel = modelId ? lookupModel(modelId) : undefined; - const outputCost = outputModel - ? computeTokenCostsForModel({ - model: outputModel, - usage: { input_tokens: 0, output_tokens: outputTokens }, - }).totalTokenCostUSD - : undefined; - const outputCostText = new Intl.NumberFormat("en-US", { - style: "currency", - currency: "USD", - }).format(outputCost ?? 0); - - return ( -
- Output - -
- ); -}; - -export type ContextReasoningUsageProps = ComponentProps<"div">; - -export const ContextReasoningUsage = ({ - className, - children, - ...props -}: ContextReasoningUsageProps) => { - const { usage, modelId } = useContextValue(); - const reasoningTokens = usage?.reasoningTokens ?? 0; - - if (children) { - return children; - } - - if (!reasoningTokens) { - return null; - } - - const reasoningModel = modelId ? lookupModel(modelId) : undefined; - const reasoningCost = reasoningModel - ? computeTokenCostsForModel({ - model: reasoningModel, - usage: { reasoning_tokens: reasoningTokens }, - }).totalTokenCostUSD - : undefined; - const reasoningCostText = new Intl.NumberFormat("en-US", { - style: "currency", - currency: "USD", - }).format(reasoningCost ?? 0); - - return ( -
- Reasoning - -
- ); -}; - -export type ContextCacheUsageProps = ComponentProps<"div">; - -export const ContextCacheUsage = ({ - className, - children, - ...props -}: ContextCacheUsageProps) => { - const { usage, modelId } = useContextValue(); - const cacheTokens = usage?.cachedInputTokens ?? 0; - - if (children) { - return children; - } - - if (!cacheTokens) { - return null; - } - - const cacheModel = modelId ? lookupModel(modelId) : undefined; - const cacheCost = cacheModel - ? computeTokenCostsForModel({ - model: cacheModel, - usage: { cache_read_tokens: cacheTokens }, - }).totalTokenCostUSD - : undefined; - const cacheCostText = new Intl.NumberFormat("en-US", { - style: "currency", - currency: "USD", - }).format(cacheCost ?? 0); - - return ( -
- Cache - -
- ); -}; - -const TokensWithCost = ({ - tokens, - costText, -}: { - tokens?: number; - costText?: string; -}) => ( - - {tokens === undefined - ? "—" - : new Intl.NumberFormat("en-US", { - notation: "compact", - }).format(tokens)} - {costText ? ( - • {costText} - ) : null} - -); diff --git a/apps/dashboard/components/ai-elements/controls.tsx b/apps/dashboard/components/ai-elements/controls.tsx deleted file mode 100644 index dec68eb229..0000000000 --- a/apps/dashboard/components/ai-elements/controls.tsx +++ /dev/null @@ -1,18 +0,0 @@ -"use client"; - -import { Controls as ControlsPrimitive } from "@xyflow/react"; -import type { ComponentProps } from "react"; -import { cn } from "@/lib/utils"; - -export type ControlsProps = ComponentProps; - -export const Controls = ({ className, ...props }: ControlsProps) => ( - button]:rounded-md [&>button]:border-none! [&>button]:bg-transparent! [&>button]:hover:bg-secondary!", - className - )} - {...props} - /> -); diff --git a/apps/dashboard/components/ai-elements/edge.tsx b/apps/dashboard/components/ai-elements/edge.tsx deleted file mode 100644 index 15fa15108e..0000000000 --- a/apps/dashboard/components/ai-elements/edge.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { - BaseEdge, - type EdgeProps, - getBezierPath, - getSimpleBezierPath, - type InternalNode, - type Node, - Position, - useInternalNode, -} from "@xyflow/react"; - -const Temporary = ({ - id, - sourceX, - sourceY, - targetX, - targetY, - sourcePosition, - targetPosition, -}: EdgeProps) => { - const [edgePath] = getSimpleBezierPath({ - sourceX, - sourceY, - sourcePosition, - targetX, - targetY, - targetPosition, - }); - - return ( - - ); -}; - -const getHandleCoordsByPosition = ( - node: InternalNode, - handlePosition: Position -) => { - // Choose the handle type based on position - Left is for target, Right is for source - const handleType = handlePosition === Position.Left ? "target" : "source"; - - const handle = node.internals.handleBounds?.[handleType]?.find( - (h) => h.position === handlePosition - ); - - if (!handle) { - return [0, 0] as const; - } - - let offsetX = handle.width / 2; - let offsetY = handle.height / 2; - - // this is a tiny detail to make the markerEnd of an edge visible. - // The handle position that gets calculated has the origin top-left, so depending which side we are using, we add a little offset - // when the handlePosition is Position.Right for example, we need to add an offset as big as the handle itself in order to get the correct position - switch (handlePosition) { - case Position.Left: - offsetX = 0; - break; - case Position.Right: - offsetX = handle.width; - break; - case Position.Top: - offsetY = 0; - break; - case Position.Bottom: - offsetY = handle.height; - break; - default: - throw new Error(`Invalid handle position: ${handlePosition}`); - } - - const x = node.internals.positionAbsolute.x + handle.x + offsetX; - const y = node.internals.positionAbsolute.y + handle.y + offsetY; - - return [x, y] as const; -}; - -const getEdgeParams = ( - source: InternalNode, - target: InternalNode -) => { - const sourcePos = Position.Right; - const [sx, sy] = getHandleCoordsByPosition(source, sourcePos); - const targetPos = Position.Left; - const [tx, ty] = getHandleCoordsByPosition(target, targetPos); - - return { - sx, - sy, - tx, - ty, - sourcePos, - targetPos, - }; -}; - -const Animated = ({ id, source, target, markerEnd, style }: EdgeProps) => { - const sourceNode = useInternalNode(source); - const targetNode = useInternalNode(target); - - if (!(sourceNode && targetNode)) { - return null; - } - - const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams( - sourceNode, - targetNode - ); - - const [edgePath] = getBezierPath({ - sourceX: sx, - sourceY: sy, - sourcePosition: sourcePos, - targetX: tx, - targetY: ty, - targetPosition: targetPos, - }); - - return ( - <> - - - - - - ); -}; - -export const Edge = { - Temporary, - Animated, -}; diff --git a/apps/dashboard/components/ai-elements/image.tsx b/apps/dashboard/components/ai-elements/image.tsx deleted file mode 100644 index 6c5850c791..0000000000 --- a/apps/dashboard/components/ai-elements/image.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import type { Experimental_GeneratedImage } from "ai"; -import { cn } from "@/lib/utils"; - -export type ImageProps = Experimental_GeneratedImage & { - alt?: string; - className?: string; - height?: number; - width?: number; -}; - -export const Image = ({ - base64, - mediaType, - uint8Array, - height = 256, - width = 256, - ...props -}: ImageProps) => ( - {props.alt} -); diff --git a/apps/dashboard/components/ai-elements/loader.tsx b/apps/dashboard/components/ai-elements/loader.tsx deleted file mode 100644 index 8f872b2d57..0000000000 --- a/apps/dashboard/components/ai-elements/loader.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import type { HTMLAttributes } from "react"; -import { cn } from "@/lib/utils"; - -interface LoaderIconProps { - size?: number; -} - -const LoaderIcon = ({ size = 16 }: LoaderIconProps) => ( - - Loader - - - - - - - - - - - - - - - - - - -); - -export type LoaderProps = HTMLAttributes & { - size?: number; -}; - -export const Loader = ({ className, size = 16, ...props }: LoaderProps) => ( -
- -
-); diff --git a/apps/dashboard/components/ai-elements/model-selector.tsx b/apps/dashboard/components/ai-elements/model-selector.tsx deleted file mode 100644 index 9d38947318..0000000000 --- a/apps/dashboard/components/ai-elements/model-selector.tsx +++ /dev/null @@ -1,204 +0,0 @@ -import type { ComponentProps, ReactNode } from "react"; -import { - Command, - CommandDialog, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, - CommandSeparator, - CommandShortcut, -} from "@/components/ui/command"; -import { cn } from "@/lib/utils"; -import { Dialog } from "@databuddy/ui/client"; - -export type ModelSelectorProps = ComponentProps; - -export const ModelSelector = (props: ModelSelectorProps) => ( - -); - -export type ModelSelectorTriggerProps = ComponentProps; - -export const ModelSelectorTrigger = (props: ModelSelectorTriggerProps) => ( - -); - -export type ModelSelectorContentProps = ComponentProps< - typeof Dialog.Content -> & { - title?: ReactNode; -}; - -export const ModelSelectorContent = ({ - className, - children, - title = "Model Selector", - ...props -}: ModelSelectorContentProps) => ( - - {title} - - - {children} - - - -); - -export type ModelSelectorDialogProps = ComponentProps; - -export const ModelSelectorDialog = (props: ModelSelectorDialogProps) => ( - -); - -export type ModelSelectorInputProps = ComponentProps; - -export const ModelSelectorInput = ({ - className, - ...props -}: ModelSelectorInputProps) => ( - -); - -export type ModelSelectorListProps = ComponentProps; - -export const ModelSelectorList = (props: ModelSelectorListProps) => ( - -); - -export type ModelSelectorEmptyProps = ComponentProps; - -export const ModelSelectorEmpty = (props: ModelSelectorEmptyProps) => ( - -); - -export type ModelSelectorGroupProps = ComponentProps; - -export const ModelSelectorGroup = (props: ModelSelectorGroupProps) => ( - -); - -export type ModelSelectorItemProps = ComponentProps; - -export const ModelSelectorItem = (props: ModelSelectorItemProps) => ( - -); - -export type ModelSelectorShortcutProps = ComponentProps; - -export const ModelSelectorShortcut = (props: ModelSelectorShortcutProps) => ( - -); - -export type ModelSelectorSeparatorProps = ComponentProps< - typeof CommandSeparator ->; - -export const ModelSelectorSeparator = (props: ModelSelectorSeparatorProps) => ( - -); - -export type ModelSelectorLogoProps = Omit< - ComponentProps<"img">, - "src" | "alt" -> & { - provider: - | "moonshotai-cn" - | "lucidquery" - | "moonshotai" - | "zai-coding-plan" - | "alibaba" - | "xai" - | "vultr" - | "nvidia" - | "upstage" - | "groq" - | "github-copilot" - | "mistral" - | "vercel" - | "nebius" - | "deepseek" - | "alibaba-cn" - | "google-vertex-anthropic" - | "venice" - | "chutes" - | "cortecs" - | "github-models" - | "togetherai" - | "azure" - | "baseten" - | "huggingface" - | "opencode" - | "fastrouter" - | "google" - | "google-vertex" - | "cloudflare-workers-ai" - | "inception" - | "wandb" - | "openai" - | "zhipuai-coding-plan" - | "perplexity" - | "openrouter" - | "zenmux" - | "v0" - | "iflowcn" - | "synthetic" - | "deepinfra" - | "zhipuai" - | "submodel" - | "zai" - | "inference" - | "requesty" - | "morph" - | "lmstudio" - | "anthropic" - | "aihubmix" - | "fireworks-ai" - | "modelscope" - | "llama" - | "scaleway" - | "amazon-bedrock" - | "cerebras" - | (string & {}); -}; - -export const ModelSelectorLogo = ({ - provider, - className, - ...props -}: ModelSelectorLogoProps) => ( - {`${provider} -); - -export type ModelSelectorLogoGroupProps = ComponentProps<"div">; - -export const ModelSelectorLogoGroup = ({ - className, - ...props -}: ModelSelectorLogoGroupProps) => ( -
img]:rounded-full [&>img]:bg-background [&>img]:p-px [&>img]:ring-1 dark:[&>img]:bg-foreground", - className - )} - {...props} - /> -); - -export type ModelSelectorNameProps = ComponentProps<"span">; - -export const ModelSelectorName = ({ - className, - ...props -}: ModelSelectorNameProps) => ( - -); diff --git a/apps/dashboard/components/ai-elements/node.tsx b/apps/dashboard/components/ai-elements/node.tsx deleted file mode 100644 index f1c467e3a9..0000000000 --- a/apps/dashboard/components/ai-elements/node.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { Handle, Position } from "@xyflow/react"; -import type { ComponentProps } from "react"; -import { cn } from "@/lib/utils"; -import { Card } from "@databuddy/ui"; - -export type NodeProps = ComponentProps & { - handles: { - target: boolean; - source: boolean; - }; -}; - -export const Node = ({ handles, className, ...props }: NodeProps) => ( - - {handles.target && } - {handles.source && } - {props.children} - -); - -export type NodeHeaderProps = ComponentProps; - -export const NodeHeader = ({ className, ...props }: NodeHeaderProps) => ( - -); - -export type NodeTitleProps = ComponentProps; - -export const NodeTitle = (props: NodeTitleProps) => ; - -export type NodeDescriptionProps = ComponentProps; - -export const NodeDescription = (props: NodeDescriptionProps) => ( - -); - -export type NodeActionProps = ComponentProps; - -export const NodeAction = (props: NodeActionProps) => ( - -); - -export type NodeContentProps = ComponentProps; - -export const NodeContent = ({ className, ...props }: NodeContentProps) => ( - -); - -export type NodeFooterProps = ComponentProps; - -export const NodeFooter = ({ className, ...props }: NodeFooterProps) => ( - -); diff --git a/apps/dashboard/components/ai-elements/panel.tsx b/apps/dashboard/components/ai-elements/panel.tsx deleted file mode 100644 index 5c769a5465..0000000000 --- a/apps/dashboard/components/ai-elements/panel.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { Panel as PanelPrimitive } from "@xyflow/react"; -import type { ComponentProps } from "react"; -import { cn } from "@/lib/utils"; - -type PanelProps = ComponentProps; - -export const Panel = ({ className, ...props }: PanelProps) => ( - -); diff --git a/apps/dashboard/components/ai-elements/suggestion.tsx b/apps/dashboard/components/ai-elements/suggestion.tsx deleted file mode 100644 index e8b82fc9ad..0000000000 --- a/apps/dashboard/components/ai-elements/suggestion.tsx +++ /dev/null @@ -1,53 +0,0 @@ -"use client"; - -import type { ComponentProps } from "react"; -import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; -import { cn } from "@/lib/utils"; -import { Button } from "@databuddy/ui"; - -export type SuggestionsProps = ComponentProps; - -export const Suggestions = ({ - className, - children, - ...props -}: SuggestionsProps) => ( - -
- {children} -
- -
-); - -export type SuggestionProps = Omit, "onClick"> & { - suggestion: string; - onClick?: (suggestion: string) => void; -}; - -export const Suggestion = ({ - suggestion, - onClick, - className, - variant = "secondary", - size = "sm", - children, - ...props -}: SuggestionProps) => { - const handleClick = () => { - onClick?.(suggestion); - }; - - return ( - - ); -}; diff --git a/apps/dashboard/components/ai-elements/toolbar.tsx b/apps/dashboard/components/ai-elements/toolbar.tsx deleted file mode 100644 index 3b4fbf0aea..0000000000 --- a/apps/dashboard/components/ai-elements/toolbar.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { NodeToolbar, Position } from "@xyflow/react"; -import type { ComponentProps } from "react"; -import { cn } from "@/lib/utils"; - -type ToolbarProps = ComponentProps; - -export const Toolbar = ({ className, ...props }: ToolbarProps) => ( - -); diff --git a/apps/dashboard/components/atomic/FormattedNumber.tsx b/apps/dashboard/components/atomic/FormattedNumber.tsx deleted file mode 100644 index 26c167a458..0000000000 --- a/apps/dashboard/components/atomic/FormattedNumber.tsx +++ /dev/null @@ -1,22 +0,0 @@ -"use client"; - -import type React from "react"; -import { formatNumber } from "@/lib/formatters"; - -interface FormattedNumberProps { - className?: string; - id?: string; - value: number; -} - -export const FormattedNumber: React.FC = ({ - id, - value, - className, -}) => ( - - {formatNumber(value)} - -); - -export default FormattedNumber; diff --git a/apps/dashboard/components/atomic/PageLinkCell.tsx b/apps/dashboard/components/atomic/PageLinkCell.tsx deleted file mode 100644 index 4cc10acba5..0000000000 --- a/apps/dashboard/components/atomic/PageLinkCell.tsx +++ /dev/null @@ -1,72 +0,0 @@ -"use client"; - -import type React from "react"; -import { formatDomainLink } from "@/app/(main)/websites/[id]/_components/utils/analytics-helpers"; -import { cn } from "@/lib/utils"; -import { ArrowSquareOutIcon, FileTextIcon } from "@databuddy/ui/icons"; - -export interface PageLinkCellData { - id?: string; - path: string; - websiteDomain?: string; -} - -type PageLinkCellProps = PageLinkCellData & { - className?: string; - iconClassName?: string; - textClassName?: string; - maxLength?: number; -}; - -export const PageLinkCell: React.FC = ({ - id, - path, - websiteDomain, - className, - iconClassName = "size-4 text-muted-foreground", - textClassName = "text-sm", - maxLength = 35, -}) => { - if (!path) { - return ( - - (not set) - - ); - } - - const { href, display } = formatDomainLink(path, websiteDomain, maxLength); - const isExternal = href.startsWith("http"); - - return ( - - - - {display} - - {isExternal && ( - - )} - - ); -}; - -export default PageLinkCell; diff --git a/apps/dashboard/components/bits/Aurora.tsx b/apps/dashboard/components/bits/Aurora.tsx deleted file mode 100644 index 428a502505..0000000000 --- a/apps/dashboard/components/bits/Aurora.tsx +++ /dev/null @@ -1,216 +0,0 @@ -import { Color, Mesh, Program, Renderer, Triangle } from "ogl"; -import { useEffect, useRef } from "react"; - -export interface CommonProps { - onReady?: () => void; -} - -export interface TimeProps { - speed?: number; - time?: number; -} - -export interface ControlProps { - paused?: boolean; -} - -export interface AuroraProps extends CommonProps, TimeProps, ControlProps { - amplitude?: number; - colorStops?: [string, string, string]; -} - -const VERT = `#version 300 es -in vec2 uv; -in vec2 position; - -out vec2 vUv; - -void main() { - vUv = uv; - gl_Position = vec4(position, 0.0, 1.0); -} -`; - -const FRAG = `#version 300 es -precision highp float; - -uniform float uTime; -uniform float uAmplitude; -uniform vec3 uColorStops[3]; - -in vec2 vUv; -out vec4 fragColor; - -vec3 permute(vec3 x) { - return mod(((x * 34.0) + 1.0) * x, 289.0); -} - -float snoise(vec2 v){ - const vec4 C = vec4( - 0.211324865405187, 0.366025403784439, - -0.577350269189626, 0.024390243902439 - ); - vec2 i = floor(v + dot(v, C.yy)); - vec2 x0 = v - i + dot(i, C.xx); - vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); - vec4 x12 = x0.xyxy + C.xxzz; - x12.xy -= i1; - i = mod(i, 289.0); - - vec3 p = permute( - permute(i.y + vec3(0.0, i1.y, 1.0)) - + i.x + vec3(0.0, i1.x, 1.0) - ); - - vec3 m = max( - 0.5 - vec3( - dot(x0, x0), - dot(x12.xy, x12.xy), - dot(x12.zw, x12.zw) - ), - 0.0 - ); - m = m * m; - m = m * m; - - vec3 x = 2.0 * fract(p * C.www) - 1.0; - vec3 h = abs(x) - 0.5; - vec3 ox = floor(x + 0.5); - vec3 a0 = x - ox; - m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h); - - vec3 g; - g.x = a0.x * x0.x + h.x * x0.y; - g.yz = a0.yz * x12.xz + h.yz * x12.yw; - return 130.0 * dot(m, g); -} - -struct ColorStop { - vec3 color; - float position; -}; - -/** - * Helper macro to blend between consecutive ColorStops - * based on vUv.x - */ -#define COLOR_RAMP(colors, factor, finalColor) { \ - int index = 0; \ - for (int i = 0; i < colors.length() - 1; i++) { \ - ColorStop currentColor = colors[i]; \ - bool isInBetween = currentColor.position <= factor; \ - index = int(mix(float(index), float(i), float(isInBetween))); \ - } \ - ColorStop currentColor = colors[index]; \ - ColorStop nextColor = colors[index + 1]; \ - float range = nextColor.position - currentColor.position; \ - float lerpFactor = (factor - currentColor.position) / range; \ - finalColor = mix(currentColor.color, nextColor.color, lerpFactor); \ -} - -void main() { - // Build our three color stops from uniform array uColorStops - ColorStop colors[3]; - colors[0] = ColorStop(uColorStops[0], 0.0); - colors[1] = ColorStop(uColorStops[1], 0.5); - colors[2] = ColorStop(uColorStops[2], 1.0); - - // Interpolate color along vUv.x - vec3 rampColor; - COLOR_RAMP(colors, vUv.x, rampColor); - - // Noise-based "height," scaled by amplitude - float height = snoise(vec2(vUv.x * 2.0 + uTime * 0.1, uTime * 0.25)) - * 0.5 - * uAmplitude; - height = exp(height); - height = (vUv.y * 2.0 - height + 0.2); - - fragColor.rgb = 0.6 * height * rampColor; - fragColor.a = 1.0; -} -`; - -export default function Aurora(props: AuroraProps) { - const { colorStops = ["#00d8ff", "#7cff67", "#00d8ff"], amplitude = 1.0 } = - props; - - const propsRef = useRef(props); - propsRef.current = props; - - const ctnDom = useRef(null); - - useEffect(() => { - const ctn = ctnDom.current; - if (!ctn) { - return; - } - - const renderer = new Renderer(); - const gl = renderer.gl; - gl.clearColor(1, 1, 1, 1); - - function resize() { - if (!ctn) { - return; - } - renderer.setSize(ctn.offsetWidth, ctn.offsetHeight); - } - window.addEventListener("resize", resize); - resize(); - - const geometry = new Triangle(gl); - geometry.addAttribute("uv", { - size: 2, - data: new Float32Array([0, 0, 2, 0, 0, 2]), - }); - - const colorStopsArray = colorStops.map((hex) => { - const c = new Color(hex); - return [c.r, c.g, c.b] as [number, number, number]; - }); - - const program = new Program(gl, { - vertex: VERT, - fragment: FRAG, - uniforms: { - uTime: { value: 0 }, - uAmplitude: { value: amplitude }, - uColorStops: { value: colorStopsArray }, - }, - }); - - const mesh = new Mesh(gl, { geometry, program }); - ctn.appendChild(gl.canvas); - - let animateId = 0; - - const update = (t: number) => { - animateId = requestAnimationFrame(update); - - const { time = t * 0.01, speed = 1.0 } = propsRef.current; - program.uniforms.uTime.value = time * speed * 0.1; - - program.uniforms.uAmplitude.value = propsRef.current.amplitude ?? 1.0; - const stops = propsRef.current.colorStops ?? colorStops; - program.uniforms.uColorStops.value = stops.map((hex) => { - const c = new Color(hex); - return [c.r, c.g, c.b] as [number, number, number]; - }); - - renderer.render({ scene: mesh }); - }; - animateId = requestAnimationFrame(update); - - return () => { - cancelAnimationFrame(animateId); - window.removeEventListener("resize", resize); - - ctn?.removeChild(gl.canvas); - - gl.getExtension("WEBGL_lose_context")?.loseContext(); - }; - }, [amplitude, colorStops]); - - return
; -} diff --git a/apps/dashboard/components/dev-tools/dev-tools-drawer.tsx b/apps/dashboard/components/dev-tools/dev-tools-drawer.tsx deleted file mode 100644 index 95473ddc93..0000000000 --- a/apps/dashboard/components/dev-tools/dev-tools-drawer.tsx +++ /dev/null @@ -1,1092 +0,0 @@ -"use client"; - -import { publicConfig } from "@databuddy/env/public"; -import { useQueryClient } from "@tanstack/react-query"; -import { useTheme } from "next-themes"; -import { useCallback, useEffect, useState } from "react"; -import { toast } from "sonner"; -import type { - ChartCurveType, - ChartSeriesKind, -} from "@/components/ui/composables/chart"; -import { - CHART_LOCATION_LABELS, - CHART_LOCATIONS, - type ChartLocation, - useAllChartPreferences, -} from "@/hooks/use-chart-preferences"; -import { cn } from "@/lib/utils"; -import { Button } from "../ui/button"; -import { - Drawer, - DrawerClose, - DrawerContent, - DrawerDescription, - DrawerHeader, - DrawerTitle, -} from "../ui/drawer"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "../ui/select"; -import { Separator } from "../ui/separator"; -import { - BugIcon, - CaretDownIcon, - ChartBarIcon, - ChartLineIcon, - CheckCircleIcon, - ClipboardIcon, - CopyIcon, - DatabaseIcon, - DesktopIcon, - FunnelIcon, - GearIcon, - InfoIcon, - LightningIcon, - MonitorIcon, - MoonIcon, - PresentationChartIcon, - SpinnerIcon, - SquaresFourIcon, - StackIcon, - SunIcon, - TrashIcon, - WarningCircleIcon, - XMarkIcon as XIcon, -} from "@databuddy/ui/icons"; -import type { NavIcon } from "@/components/layout/navigation/types"; - -function InfoSection({ - title, - children, -}: { - title: string; - children: React.ReactNode; -}) { - return ( -
-

- - {title} -

-
{children}
-
- ); -} - -function ActionButton({ - icon: Icon, - label, - onClick, - variant = "outline", -}: { - icon: NavIcon; - label: string; - onClick: () => void; - variant?: "outline" | "destructive"; -}) { - return ( - - ); -} - -function EnvironmentInfo() { - const apiUrl = publicConfig.urls.api; - const env = process.env.NODE_ENV || "development"; - - return ( - -
-
- API URL: - {apiUrl} -
-
- Environment: - {env} -
-
- Hostname: - {window.location.hostname} -
-
-
- ); -} - -function ToastPreview() { - return ( - -

- Trigger Sonner to preview dashboard styling. -

-
- toast.success("Success")} - /> - toast.error("Error")} - variant="destructive" - /> - toast.warning("Warning")} - /> - toast.info("Info")} - /> - toast.loading("Loading")} - /> - toast("Default")} - /> -
-
- ); -} - -function ReactQueryCache() { - const queryClient = useQueryClient(); - const [cacheStats, setCacheStats] = useState({ - queries: 0, - mutations: 0, - }); - - const updateStats = useCallback(() => { - const cache = queryClient.getQueryCache(); - const mutationCache = queryClient.getMutationCache(); - setCacheStats({ - queries: cache.getAll().length, - mutations: mutationCache.getAll().length, - }); - }, [queryClient]); - - useEffect(() => { - updateStats(); - const interval = setInterval(updateStats, 1000); - return () => clearInterval(interval); - }, [updateStats]); - - const handleClearCache = () => { - queryClient.clear(); - updateStats(); - toast.success("React Query cache cleared"); - }; - - const handleInvalidateAll = () => { - queryClient.invalidateQueries(); - toast.success("All queries invalidated"); - }; - - return ( - -
-
- Active Queries: - {cacheStats.queries} -
-
- Active Mutations: - {cacheStats.mutations} -
-
- - -
-
-
- ); -} - -function StorageManagement() { - const [storageStats, setStorageStats] = useState({ - localStorage: 0, - sessionStorage: 0, - }); - - const updateStats = useCallback(() => { - let localStorageSize = 0; - let sessionStorageSize = 0; - - for (const key in localStorage) { - if (Object.hasOwn(localStorage, key)) { - localStorageSize += localStorage[key].length + key.length; - } - } - - for (const key in sessionStorage) { - if (Object.hasOwn(sessionStorage, key)) { - sessionStorageSize += sessionStorage[key].length + key.length; - } - } - - setStorageStats({ - localStorage: localStorageSize, - sessionStorage: sessionStorageSize, - }); - }, []); - - useEffect(() => { - updateStats(); - const interval = setInterval(updateStats, 2000); - return () => clearInterval(interval); - }, [updateStats]); - - const formatBytes = (bytes: number) => { - if (bytes === 0) { - return "0 B"; - } - const k = 1024; - const sizes = ["B", "KB", "MB"]; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return `${(bytes / k ** i).toFixed(2)} ${sizes[i]}`; - }; - - const handleClearLocalStorage = () => { - localStorage.clear(); - updateStats(); - toast.success("LocalStorage cleared"); - }; - - const handleClearSessionStorage = () => { - sessionStorage.clear(); - updateStats(); - toast.success("SessionStorage cleared"); - }; - - return ( - -
-
- LocalStorage: - - {formatBytes(storageStats.localStorage)} - -
-
- SessionStorage: - - {formatBytes(storageStats.sessionStorage)} - -
-
- - -
-
-
- ); -} - -function PerformanceInfo() { - const queryClient = useQueryClient(); - const [memoryInfo, setMemoryInfo] = useState<{ - usedJSHeapSize?: number; - totalJSHeapSize?: number; - jsHeapSizeLimit?: number; - }>({}); - const [advancedMemory, setAdvancedMemory] = useState<{ - bytes?: number; - breakdown?: Array<{ - bytes: number; - attribution: Array<{ - url: string; - scope: string; - }>; - types: string[]; - }>; - } | null>(null); - const [isMeasuring, setIsMeasuring] = useState(false); - const [breakdown, setBreakdown] = useState<{ - domNodes: number; - eventListeners: number; - reactQueryCache: number; - storage: number; - images: number; - scripts: number; - timers: number; - intervals: number; - websockets: number; - workers: number; - }>({ - domNodes: 0, - eventListeners: 0, - reactQueryCache: 0, - storage: 0, - images: 0, - scripts: 0, - timers: 0, - intervals: 0, - websockets: 0, - workers: 0, - }); - - const calculateBreakdown = useCallback(() => { - const domNodes = document.querySelectorAll("*").length; - - let eventListeners = 0; - const allElements = document.querySelectorAll("*"); - for (const el of allElements) { - if ( - el instanceof HTMLElement && - (el.onclick || - el.onmouseover || - el.onfocus || - el.getAttribute("onclick")) - ) { - eventListeners++; - } - } - - const cache = queryClient.getQueryCache(); - const queries = cache.getAll(); - let reactQueryCache = 0; - for (const query of queries) { - const state = query.state; - if (state.data) { - try { - reactQueryCache += JSON.stringify(state.data).length; - } catch { - reactQueryCache += Object.keys(state.data).length * 100; - } - } - } - - let storage = 0; - for (const key in localStorage) { - if (Object.hasOwn(localStorage, key)) { - storage += localStorage[key].length + key.length; - } - } - for (const key in sessionStorage) { - if (Object.hasOwn(sessionStorage, key)) { - storage += sessionStorage[key].length + key.length; - } - } - - const images = document.querySelectorAll("img"); - let imagesSize = 0; - for (const img of images) { - if (img.complete && img.naturalWidth && img.naturalHeight) { - imagesSize += img.naturalWidth * img.naturalHeight * 4; - } - } - - const scripts = document.querySelectorAll("script").length; - - const timers = 0; - const intervals = 0; - - const websockets = 0; - - const workers = 0; - - setBreakdown({ - domNodes, - eventListeners, - reactQueryCache, - storage, - images: imagesSize, - scripts, - timers, - intervals, - websockets, - workers, - }); - }, [queryClient]); - - const measureAdvancedMemory = useCallback(async () => { - if ( - typeof performance !== "undefined" && - "measureUserAgentSpecificMemory" in performance && - typeof performance.measureUserAgentSpecificMemory === "function" - ) { - setIsMeasuring(true); - try { - const result = await performance.measureUserAgentSpecificMemory(); - setAdvancedMemory(result); - toast.success("Advanced memory measurement completed"); - } catch (error) { - if (error instanceof DOMException) { - if (error.name === "SecurityError") { - toast.error( - "Memory measurement requires cross-origin isolation. Enable COOP/COEP headers." - ); - } else { - toast.error(`Memory measurement failed: ${error.message}`); - } - } else { - toast.error("Failed to measure memory"); - } - console.error("Memory measurement error:", error); - } finally { - setIsMeasuring(false); - } - } else { - toast.error( - "Advanced memory API not available. Use Chrome 89+ with cross-origin isolation." - ); - } - }, []); - - const openDevToolsMemory = useCallback(() => { - toast.info( - "Open Chrome DevTools → Memory tab → Take heap snapshot for detailed analysis" - ); - console.log( - "💡 Tip: Open Chrome DevTools (F12) → Memory tab → Take heap snapshot to see detailed JavaScript object memory usage" - ); - }, []); - - useEffect(() => { - let animationFrameId: number; - let lastUpdate = 0; - const throttleMs = 500; - - const updateMemory = (timestamp: number) => { - if (timestamp - lastUpdate >= throttleMs) { - if ("memory" in performance) { - const mem = (performance as { memory?: typeof memoryInfo }).memory; - if (mem) { - setMemoryInfo({ - usedJSHeapSize: mem.usedJSHeapSize, - totalJSHeapSize: mem.totalJSHeapSize, - jsHeapSizeLimit: mem.jsHeapSizeLimit, - }); - } - } - calculateBreakdown(); - lastUpdate = timestamp; - } - - animationFrameId = requestAnimationFrame(updateMemory); - }; - - if ("memory" in performance) { - const mem = (performance as { memory?: typeof memoryInfo }).memory; - if (mem) { - setMemoryInfo({ - usedJSHeapSize: mem.usedJSHeapSize, - totalJSHeapSize: mem.totalJSHeapSize, - jsHeapSizeLimit: mem.jsHeapSizeLimit, - }); - } - } - calculateBreakdown(); - - animationFrameId = requestAnimationFrame(updateMemory); - - return () => { - cancelAnimationFrame(animationFrameId); - }; - }, [calculateBreakdown]); - - const formatBytes = (bytes?: number) => { - if (!bytes) { - return "N/A"; - } - const k = 1024; - const sizes = ["B", "KB", "MB"]; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return `${(bytes / k ** i).toFixed(2)} ${sizes[i]}`; - }; - - const formatNumber = (num: number) => { - if (num >= 1_000_000) { - return `${(num / 1_000_000).toFixed(2)}M`; - } - if (num >= 1000) { - return `${(num / 1000).toFixed(2)}K`; - } - return num.toString(); - }; - - const canMeasureAdvanced = - typeof performance !== "undefined" && - "measureUserAgentSpecificMemory" in performance && - typeof performance.measureUserAgentSpecificMemory === "function" && - typeof crossOriginIsolated !== "undefined" && - crossOriginIsolated; - - if (Object.keys(memoryInfo).length === 0) { - return null; - } - - return ( - -
-
-
- Used Heap: - - {formatBytes(memoryInfo.usedJSHeapSize)} - -
-
- Total Heap: - - {formatBytes(memoryInfo.totalJSHeapSize)} - -
-
- Heap Limit: - - {formatBytes(memoryInfo.jsHeapSizeLimit)} - -
-
- - {canMeasureAdvanced && ( - <> - -
-
-

- Advanced Measurement -

- -
- {advancedMemory && ( -
-
- Total Memory: - - {formatBytes(advancedMemory.bytes)} - -
- {advancedMemory.breakdown && ( -
-
- Breakdown: -
- {advancedMemory.breakdown.map((item, idx) => ( -
- - {item.attribution[0]?.url || "Unknown"}: - - - {formatBytes(item.bytes)} - -
- ))} -
- )} -
- )} -
- - )} - - - -
-
-

- Tracked Memory Usage -

- -
-
- These are measurable contributions. For detailed JavaScript object - analysis, use Chrome DevTools Memory profiler (click icon above). -
-
-
- React Query Cache: - - {formatBytes(breakdown.reactQueryCache)} - -
-
- Storage (LS/SS): - - {formatBytes(breakdown.storage)} - -
-
- Images (estimated): - - {formatBytes(breakdown.images)} - -
-
- DOM Nodes: - - {formatNumber(breakdown.domNodes)} nodes - -
-
- Scripts: - - {breakdown.scripts} loaded - -
-
-
-
-
- ); -} - -const THEME_OPTIONS = [ - { - id: "light", - name: "Light", - icon: SunIcon, - }, - { - id: "dark", - name: "Dark", - icon: MoonIcon, - }, - { - id: "system", - name: "System", - icon: DesktopIcon, - }, -] as const; - -const CHART_TYPE_OPTIONS: { - id: ChartSeriesKind; - name: string; - icon: NavIcon; -}[] = [ - { id: "bar", name: "Bar", icon: ChartBarIcon }, - { id: "line", name: "Line", icon: ChartLineIcon }, - { id: "area", name: "Area", icon: StackIcon }, -]; - -const STEP_TYPE_OPTIONS: { id: ChartCurveType; name: string }[] = [ - { id: "monotone", name: "Smooth" }, - { id: "linear", name: "Linear" }, - { id: "step", name: "Step" }, - { id: "stepBefore", name: "Step Before" }, - { id: "stepAfter", name: "Step After" }, -]; - -const LOCATION_ICONS: Record = { - "overview-stats": SquaresFourIcon, - "overview-main": PresentationChartIcon, - funnels: FunnelIcon, - "website-list": ChartLineIcon, - events: ChartBarIcon, -}; - -function AppearanceSettings() { - const { theme, setTheme } = useTheme(); - const { preferences, updateLocationPreferences, updateAllPreferences } = - useAllChartPreferences(); - const [showGranular, setShowGranular] = useState(false); - const [mounted, setMounted] = useState(false); - - useEffect(() => { - setMounted(true); - }, []); - - if (!mounted) { - return null; - } - - const currentTheme = theme ?? "system"; - const globalPrefs = preferences["overview-stats"] ?? { - chartType: "area" as ChartSeriesKind, - chartStepType: "monotone" as ChartCurveType, - }; - const isGlobalBar = globalPrefs.chartType === "bar"; - - return ( -
-
-

- - Theme -

-
- {THEME_OPTIONS.map(({ id, name, icon: Icon }) => ( - - ))} -
-
- - - -
-

- - Charts -

- -
-
- All Charts -
- - -
-
- - - - {showGranular ? ( -
- {CHART_LOCATIONS.map((location) => { - const prefs = preferences[location] ?? { - chartType: "area" as ChartSeriesKind, - chartStepType: "monotone" as ChartCurveType, - }; - const isBar = prefs.chartType === "bar"; - const Icon = LOCATION_ICONS[location]; - - return ( -
-
- - - {CHART_LOCATION_LABELS[location]} - -
-
- - -
-
- ); - })} -
- ) : null} -
-
-
- ); -} - -function QuickActions() { - const handleCopyUrl = () => { - navigator.clipboard.writeText(window.location.href); - toast.success("URL copied to clipboard"); - }; - - const handleCopyState = () => { - const state = { - url: window.location.href, - timestamp: new Date().toISOString(), - userAgent: navigator.userAgent, - viewport: { - width: window.innerWidth, - height: window.innerHeight, - }, - }; - console.table(state); - navigator.clipboard.writeText(JSON.stringify(state, null, 2)); - toast.success("State copied to clipboard and logged to console"); - }; - - const handleClearConsole = () => { - console.clear(); - toast.success("Console cleared"); - }; - - const handleReload = () => { - window.location.reload(); - }; - - return ( -
-

- - Quick Actions -

-
- - - - -
-
- ); -} - -export function DevToolsDrawer() { - const [mounted, setMounted] = useState(false); - const [open, setOpen] = useState(false); - const [isLocalhost, setIsLocalhost] = useState(false); - - useEffect(() => { - setMounted(true); - const hostname = window.location.hostname; - setIsLocalhost(hostname === "localhost" || hostname === "127.0.0.1"); - }, []); - - useEffect(() => { - if (!isLocalhost) { - return; - } - - const handleKeyDown = (e: KeyboardEvent) => { - if ((e.metaKey || e.ctrlKey) && e.key === ".") { - e.preventDefault(); - setOpen((prev) => !prev); - } - }; - - window.addEventListener("keydown", handleKeyDown); - return () => window.removeEventListener("keydown", handleKeyDown); - }, [isLocalhost]); - - if (!(mounted && isLocalhost)) { - return null; - } - - return ( - <> - - - - - -
-
- - Dev Tools -
- - - -
- - Development tools and debugging utilities - -
- -
-
- - - - - - - - - - - - - - - -
-

- Tip: Press{" "} - - ⌘ - {" "} - - . - {" "} - to toggle this drawer -

-
-
-
-
-
- - ); -} diff --git a/apps/dashboard/components/empty-state.tsx b/apps/dashboard/components/empty-state.tsx deleted file mode 100644 index b7d74bc240..0000000000 --- a/apps/dashboard/components/empty-state.tsx +++ /dev/null @@ -1,292 +0,0 @@ -"use client"; - -import { - cloneElement, - memo, - type ReactElement, - type ReactNode, - type SVGProps, -} from "react"; -import { cn } from "@/lib/utils"; -import { PlusIcon } from "@databuddy/ui/icons"; -import { Button, Card } from "@databuddy/ui"; - -export interface EmptyStateAction { - label: string; - onClick: () => void; - size?: "sm" | "md" | "lg"; - tone?: "destructive"; - variant?: "primary" | "secondary" | "ghost"; -} - -export interface EmptyStateProps { - /** Primary action button */ - action?: EmptyStateAction; - /** Custom aria-label for screen readers */ - "aria-label"?: string; - /** Custom className */ - className?: string; - /** Description text */ - description?: string | ReactNode; - /** Main icon to display */ - icon: ReactElement< - SVGProps & { size?: number | string; weight?: string } - >; - /** Whether this is the main content area */ - isMainContent?: boolean; - /** Custom padding */ - padding?: "sm" | "md" | "lg"; - /** Custom role for accessibility (defaults to 'region') */ - role?: "region" | "complementary" | "main"; - /** Secondary action button */ - secondaryAction?: EmptyStateAction; - /** Whether to show the plus badge on the icon */ - showPlusBadge?: boolean; - /** Main heading */ - title: string; - /** Custom styling variants */ - variant?: "default" | "simple" | "minimal" | "error"; -} - -export const EmptyState = memo(function EmptyState({ - icon, - title, - description, - action, - secondaryAction, - variant = "minimal", - className, - showPlusBadge = true, - padding = "lg", - role = "region", - "aria-label": ariaLabel, - isMainContent = false, -}: EmptyStateProps) { - const getPadding = () => { - switch (padding) { - case "sm": - return "px-6 py-12"; - case "md": - return "px-8 py-14"; - case "lg": - return "px-8"; - default: - return "px-8"; - } - }; - - const renderIcon = () => { - if (!icon || typeof icon !== "object" || !("type" in icon)) { - return null; - } - - const iconProps = icon.props || {}; - - if (variant === "simple" || variant === "minimal" || variant === "error") { - return ( - - ); - } - - return ( -
- - {showPlusBadge && ( - - )} -
- ); - }; - - const renderCard = () => { - const cardClasses = cn( - variant === "default" && - "rounded-xl border-2 border-dashed bg-gradient-to-br from-background to-muted/10", - variant === "simple" && "rounded border-dashed bg-muted/10", - variant === "minimal" && - "flex flex-1 rounded border-none bg-transparent shadow-none", - variant === "error" && - "flex flex-1 rounded border-none bg-transparent shadow-none", - "safe-area-inset-4 sm:safe-area-inset-6 lg:safe-area-inset-8", - className - ); - - const contentClasses = cn( - "flex flex-1 flex-col items-center justify-center text-center", - getPadding(), - "px-6 sm:px-8 lg:px-12" - ); - - return ( - - - {renderIcon()} -
- {isMainContent ? ( -

- {title} -

- ) : ( -
-

- {title} -

-

{description}

-
- )} - {(action || secondaryAction) && ( -
- {action && ( - - )} - {secondaryAction && ( - - )} -
- )} -
-
-
- ); - }; - - return renderCard(); -}); - -EmptyState.displayName = "EmptyState"; - -export function FeatureEmptyState({ - icon, - title, - description, - actionLabel, - onAction, -}: { - icon: ReactElement< - SVGProps & { size?: number | string; weight?: string } - >; - title: string; - description: string; - actionLabel: string; - onAction: () => void; -}) { - return ( - - ); -} diff --git a/apps/dashboard/components/layout/help-dialog.tsx b/apps/dashboard/components/layout/help-dialog.tsx deleted file mode 100644 index 80ed58a37b..0000000000 --- a/apps/dashboard/components/layout/help-dialog.tsx +++ /dev/null @@ -1,158 +0,0 @@ -"use client"; - -import Link from "next/link"; -import { useState } from "react"; -import { KeyboardShortcuts } from "@/components/ui/keyboard-shortcuts"; -import { cn } from "@/lib/utils"; -import { - BookOpenIcon, - ChatTextIcon as ChatCircleIcon, - CommandIcon as KeyboardIcon, - PlayIcon, -} from "@databuddy/ui/icons"; -import { Button, Text } from "@databuddy/ui"; -import { Dialog } from "@databuddy/ui/client"; - -interface HelpDialogProps { - onOpenChangeAction: (open: boolean) => void; - open: boolean; -} - -const HELP_ITEMS = [ - { - href: "https://www.databuddy.cc/docs", - icon: BookOpenIcon, - title: "Documentation", - description: "Read guides and API references", - external: true, - }, - { - href: "mailto:support@databuddy.cc", - icon: ChatCircleIcon, - title: "Contact Support", - description: "Get help from our support team", - external: false, - }, - { - href: "https://www.youtube.com/@trydatabuddy", - icon: PlayIcon, - title: "Tutorials", - description: "Learn Databuddy step by step", - external: true, - }, -] as const; - -function HelpRow({ - children, - className, - ...rest -}: React.ButtonHTMLAttributes) { - return ( - - ); -} - -export function HelpDialog({ open, onOpenChangeAction }: HelpDialogProps) { - const [showShortcuts, setShowShortcuts] = useState(false); - - return ( - { - if (!o) { - setShowShortcuts(false); - } - onOpenChangeAction(o); - }} - open={open} - > - - - Help & Resources - - Get assistance and learn more about Databuddy - - - - - {showShortcuts ? ( -
-
- Keyboard Shortcuts - -
- -
- ) : ( -
- setShowShortcuts(true)}> -
- -
-
- Keyboard Shortcuts - - View all available keyboard shortcuts - -
-
- - {HELP_ITEMS.map((item) => { - const Icon = item.icon; - return ( - -
- -
-
- {item.title} - - {item.description} - -
- - ); - })} -
- )} -
-
-
- ); -} diff --git a/apps/dashboard/components/monitors/collapsible-section.tsx b/apps/dashboard/components/monitors/collapsible-section.tsx deleted file mode 100644 index a012d0302c..0000000000 --- a/apps/dashboard/components/monitors/collapsible-section.tsx +++ /dev/null @@ -1,66 +0,0 @@ -"use client"; - -import { AnimatePresence, motion } from "motion/react"; -import { cn } from "@/lib/utils"; -import { CaretDownIcon } from "@databuddy/ui/icons"; -import { Button } from "@databuddy/ui"; - -interface CollapsibleSectionProps { - badge?: number; - children: React.ReactNode; - icon: React.ComponentType<{ size?: number; weight?: "duotone" | "fill" }>; - isExpanded: boolean; - onToggleAction: () => void; - title: string; -} - -export function CollapsibleSection({ - icon: Icon, - title, - badge, - isExpanded, - onToggleAction, - children, -}: CollapsibleSectionProps) { - return ( -
- - - - {isExpanded && ( - -
{children}
-
- )} -
-
- ); -} diff --git a/apps/dashboard/components/ui/aspect-ratio.tsx b/apps/dashboard/components/ui/aspect-ratio.tsx deleted file mode 100644 index 956e8bb05d..0000000000 --- a/apps/dashboard/components/ui/aspect-ratio.tsx +++ /dev/null @@ -1,11 +0,0 @@ -"use client"; - -import { AspectRatio as AspectRatioPrimitive } from "radix-ui"; - -function AspectRatio({ - ...props -}: React.ComponentProps) { - return ; -} - -export { AspectRatio }; diff --git a/apps/dashboard/components/ui/badge.tsx b/apps/dashboard/components/ui/badge.tsx deleted file mode 100644 index 3c6f47bfdd..0000000000 --- a/apps/dashboard/components/ui/badge.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { Slot } from "@radix-ui/react-slot"; -import { cva, type VariantProps } from "class-variance-authority"; -import type * as React from "react"; - -import { cn } from "@/lib/utils"; - -const badgeVariants = cva( - "inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden whitespace-nowrap rounded border px-2 py-0.5 font-medium text-xs transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3", - { - variants: { - variant: { - default: - "border border-brand-purple/35 bg-brand-purple text-white dark:border-brand-purple/55 dark:bg-brand-purple dark:text-white [a&]:hover:bg-brand-purple/90", - gray: "border border-border bg-muted text-muted-foreground dark:border-border dark:bg-secondary dark:text-muted-foreground [a&]:hover:bg-muted/90", - blue: "border border-brand-purple/25 bg-brand-purple/10 text-brand-purple dark:border-brand-purple/40 dark:bg-brand-purple/18 dark:text-[#C9BFE8] [a&]:hover:bg-brand-purple/15", - green: - "border border-emerald-600/25 bg-emerald-50 text-emerald-800 dark:border-emerald-500/35 dark:bg-emerald-950/50 dark:text-emerald-300 [a&]:hover:bg-emerald-100/90", - amber: - "border border-brand-amber/30 bg-brand-amber/12 text-amber-950 dark:border-brand-amber/40 dark:bg-brand-amber/14 dark:text-amber-300 [a&]:hover:bg-brand-amber/18", - secondary: - "border border-foreground/15 bg-foreground text-background dark:border-foreground/25 dark:bg-foreground dark:text-background [a&]:hover:bg-foreground/90", - destructive: - "border border-brand-coral/30 bg-brand-coral/12 text-brand-coral focus-visible:ring-brand-coral/20 dark:border-brand-coral/45 dark:bg-brand-coral/22 dark:text-[#E8A8BE] [a&]:hover:bg-brand-coral/18", - outline: - "border border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", - }, - }, - defaultVariants: { - variant: "default", - }, - } -); - -function Badge({ - className, - variant, - asChild = false, - ...props -}: React.ComponentProps<"span"> & - VariantProps & { asChild?: boolean }) { - const Comp = asChild ? Slot : "span"; - - return ( - - ); -} - -export { Badge, badgeVariants }; diff --git a/apps/dashboard/components/ui/card.tsx b/apps/dashboard/components/ui/card.tsx deleted file mode 100644 index aa9c6f8a63..0000000000 --- a/apps/dashboard/components/ui/card.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import type * as React from "react"; - -import { cn } from "@/lib/utils"; - -function Card({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ); -} - -function CardHeader({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ); -} - -function CardTitle({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ); -} - -function CardDescription({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ); -} - -function CardAction({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ); -} - -function CardContent({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ); -} - -function CardFooter({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ); -} - -export { - Card, - CardAction, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -}; diff --git a/apps/dashboard/components/ui/carousel.tsx b/apps/dashboard/components/ui/carousel.tsx deleted file mode 100644 index b4134c2143..0000000000 --- a/apps/dashboard/components/ui/carousel.tsx +++ /dev/null @@ -1,249 +0,0 @@ -"use client"; - -import useEmblaCarousel, { - type UseEmblaCarouselType, -} from "embla-carousel-react"; -import * as React from "react"; -import { cn } from "@/lib/utils"; -import { - ArrowLeftIcon, - ArrowRightIcon, -} from "@databuddy/ui/icons"; -import { Button } from "@databuddy/ui"; - -type CarouselApi = UseEmblaCarouselType[1]; -type UseCarouselParameters = Parameters; -type CarouselOptions = UseCarouselParameters[0]; -type CarouselPlugin = UseCarouselParameters[1]; - -type CarouselProps = { - opts?: CarouselOptions; - plugins?: CarouselPlugin; - orientation?: "horizontal" | "vertical"; - setApi?: (api: CarouselApi) => void; -}; - -type CarouselContextProps = { - carouselRef: ReturnType[0]; - api: ReturnType[1]; - scrollPrev: () => void; - scrollNext: () => void; - canScrollPrev: boolean; - canScrollNext: boolean; -} & CarouselProps; - -const CarouselContext = React.createContext(null); - -function useCarousel() { - const context = React.useContext(CarouselContext); - - if (!context) { - throw new Error("useCarousel must be used within a "); - } - - return context; -} - -function Carousel({ - orientation = "horizontal", - opts, - setApi, - plugins, - className, - children, - ...props -}: React.ComponentProps<"div"> & CarouselProps) { - const [carouselRef, api] = useEmblaCarousel( - { - ...opts, - axis: orientation === "horizontal" ? "x" : "y", - }, - plugins - ); - const [canScrollPrev, setCanScrollPrev] = React.useState(false); - const [canScrollNext, setCanScrollNext] = React.useState(false); - - const onSelect = React.useCallback((api: CarouselApi) => { - if (!api) { - return; - } - setCanScrollPrev(api.canScrollPrev()); - setCanScrollNext(api.canScrollNext()); - }, []); - - const scrollPrev = React.useCallback(() => { - api?.scrollPrev(); - }, [api]); - - const scrollNext = React.useCallback(() => { - api?.scrollNext(); - }, [api]); - - const handleKeyDown = React.useCallback( - (event: React.KeyboardEvent) => { - if (event.key === "ArrowLeft") { - event.preventDefault(); - scrollPrev(); - } else if (event.key === "ArrowRight") { - event.preventDefault(); - scrollNext(); - } - }, - [scrollPrev, scrollNext] - ); - - React.useEffect(() => { - if (!(api && setApi)) { - return; - } - setApi(api); - }, [api, setApi]); - - React.useEffect(() => { - if (!api) { - return; - } - onSelect(api); - api.on("reInit", onSelect); - api.on("select", onSelect); - - return () => { - api?.off("select", onSelect); - }; - }, [api, onSelect]); - - return ( - -
- {children} -
-
- ); -} - -function CarouselContent({ className, ...props }: React.ComponentProps<"div">) { - const { carouselRef, orientation } = useCarousel(); - - return ( -
-
-
- ); -} - -function CarouselItem({ className, ...props }: React.ComponentProps<"div">) { - const { orientation } = useCarousel(); - - return ( -
- ); -} - -function CarouselPrevious({ - className, - variant = "secondary", - size = "sm", - ...props -}: React.ComponentProps) { - const { orientation, scrollPrev, canScrollPrev } = useCarousel(); - - return ( - - ); -} - -function CarouselNext({ - className, - variant = "secondary", - size = "sm", - ...props -}: React.ComponentProps) { - const { orientation, scrollNext, canScrollNext } = useCarousel(); - - return ( - - ); -} - -export { - Carousel, - type CarouselApi, - CarouselContent, - CarouselItem, - CarouselNext, - CarouselPrevious, -}; diff --git a/apps/dashboard/components/ui/command.tsx b/apps/dashboard/components/ui/command.tsx deleted file mode 100644 index ab33153220..0000000000 --- a/apps/dashboard/components/ui/command.tsx +++ /dev/null @@ -1,174 +0,0 @@ -"use client"; - -import { Command as CommandPrimitive } from "cmdk"; -import type * as React from "react"; -import { cn } from "@/lib/utils"; -import { - MagnifyingGlassIcon, -} from "@databuddy/ui/icons"; -import { Dialog } from "@databuddy/ui/client"; - -function Command({ - className, - ...props -}: React.ComponentProps) { - return ( - - ); -} - -function CommandDialog({ - title = "Command Palette", - description = "Search for a command to run...", - children, - ...props -}: Omit, "children"> & { - title?: string; - description?: string; - children?: React.ReactNode; -}) { - return ( - - - - {title} - {description} - - - - {children} - - - - - ); -} - -function CommandInput({ - className, - ...props -}: React.ComponentProps) { - return ( -
- - -
- ); -} - -function CommandList({ - className, - ...props -}: React.ComponentProps) { - return ( - e.stopPropagation()} - onWheel={(e) => e.stopPropagation()} - {...props} - /> - ); -} - -function CommandEmpty({ - ...props -}: React.ComponentProps) { - return ( - - ); -} - -function CommandGroup({ - className, - ...props -}: React.ComponentProps) { - return ( - - ); -} - -function CommandSeparator({ - className, - ...props -}: React.ComponentProps) { - return ( - - ); -} - -function CommandItem({ - className, - ...props -}: React.ComponentProps) { - return ( - - ); -} - -function CommandShortcut({ - className, - ...props -}: React.ComponentProps<"span">) { - return ( - - ); -} - -export { - Command, - CommandDialog, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, - CommandSeparator, - CommandShortcut, -}; diff --git a/apps/dashboard/components/ui/dialog.tsx b/apps/dashboard/components/ui/dialog.tsx deleted file mode 100644 index 485ee171d9..0000000000 --- a/apps/dashboard/components/ui/dialog.tsx +++ /dev/null @@ -1,146 +0,0 @@ -"use client"; - -import { XMarkIcon as XIcon } from "@databuddy/ui/icons"; -import { Dialog as DialogPrimitive } from "radix-ui"; -import type * as React from "react"; - -import { cn } from "@/lib/utils"; - -function Dialog({ - ...props -}: React.ComponentProps) { - return ; -} - -function DialogTrigger({ - ...props -}: React.ComponentProps) { - return ; -} - -function DialogPortal({ - ...props -}: React.ComponentProps) { - return ; -} - -function DialogClose({ - ...props -}: React.ComponentProps) { - return ; -} - -function DialogOverlay({ - className, - ...props -}: React.ComponentProps) { - return ( - - ); -} - -function DialogContent({ - className, - children, - showCloseButton = true, - ...props -}: React.ComponentProps & { - showCloseButton?: boolean; -}) { - return ( - - - - {children} - {showCloseButton && ( - - - Close - - )} - - - ); -} - -function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ); -} - -function DialogFooter({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ); -} - -function DialogTitle({ - className, - ...props -}: React.ComponentProps) { - return ( - - ); -} - -function DialogDescription({ - className, - ...props -}: React.ComponentProps) { - return ( - - ); -} - -export { - Dialog, - DialogClose, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogOverlay, - DialogPortal, - DialogTitle, - DialogTrigger, -}; diff --git a/apps/dashboard/components/ui/elastic-slider.tsx b/apps/dashboard/components/ui/elastic-slider.tsx deleted file mode 100644 index 0e06f221b4..0000000000 --- a/apps/dashboard/components/ui/elastic-slider.tsx +++ /dev/null @@ -1,224 +0,0 @@ -"use client"; - -import { - motion, - useMotionValue, - useMotionValueEvent, - useTransform, -} from "motion/react"; -import { useCallback, useRef, useState } from "react"; -import { cn } from "@/lib/utils"; -import { - MinusIcon, - PlusIcon, -} from "@databuddy/ui/icons"; - -const MAX_OVERFLOW = 30; - -interface SliderProps { - className?: string; - disabled?: boolean; - leftIcon?: React.ReactNode; - max?: number; - min?: number; - onValueChange?: (value: number) => void; - rightIcon?: React.ReactNode; - showValue?: boolean; - step?: number; - value?: number; -} - -function decay(value: number, maxValue: number): number { - if (maxValue === 0) { - return 0; - } - const entry = value / maxValue; - const sigmoid = 2 * (1 / (1 + Math.exp(-entry)) - 0.5); - return sigmoid * maxValue; -} - -export function Slider({ - value = 0, - onValueChange, - min = 0, - max = 100, - step = 1, - className, - leftIcon = , - rightIcon = , - showValue = true, - disabled = false, -}: SliderProps) { - const [internalValue, setInternalValue] = useState(value); - const sliderRef = useRef(null); - const [region, setRegion] = useState<"left" | "middle" | "right">("middle"); - const [isDragging, setIsDragging] = useState(false); - - const clientX = useMotionValue(0); - const overflow = useMotionValue(0); - - const percentage = ((internalValue - min) / (max - min || 1)) * 100; - - useMotionValueEvent(clientX, "change", (latest: number) => { - if (!(sliderRef.current && isDragging)) { - return; - } - - const { left, right } = sliderRef.current.getBoundingClientRect(); - let newOverflow = 0; - - if (latest < left) { - setRegion("left"); - newOverflow = left - latest; - } else if (latest > right) { - setRegion("right"); - newOverflow = latest - right; - } else { - setRegion("middle"); - } - - overflow.jump(decay(newOverflow, MAX_OVERFLOW)); - }); - - const updateValue = useCallback( - (clientXPos: number) => { - if (!sliderRef.current) { - return; - } - - const { left, width } = sliderRef.current.getBoundingClientRect(); - let newValue = min + ((clientXPos - left) / width) * (max - min); - - if (step > 0) { - newValue = Math.round(newValue / step) * step; - } - - newValue = Math.min(Math.max(newValue, min), max); - setInternalValue(newValue); - onValueChange?.(newValue); - clientX.jump(clientXPos); - }, - [min, max, step, onValueChange, clientX] - ); - - const handlePointerDown = (e: React.PointerEvent) => { - if (disabled) { - return; - } - - setIsDragging(true); - updateValue(e.clientX); - e.currentTarget.setPointerCapture(e.pointerId); - document.body.style.cursor = "grabbing"; - }; - - const handlePointerMove = (e: React.PointerEvent) => { - if (!isDragging || disabled) { - return; - } - updateValue(e.clientX); - }; - - const handlePointerUp = () => { - setIsDragging(false); - setRegion("middle"); - overflow.jump(0); - document.body.style.cursor = ""; - }; - - return ( -
-
- - region === "left" ? -overflow.get() / 2 : 0 - ), - scale: region === "left" ? 1.3 : 1, - }} - > - {leftIcon} - - -
- { - if (!sliderRef.current) { - return 1; - } - const { width } = sliderRef.current.getBoundingClientRect(); - return 1 + overflow.get() / width; - }), - scaleY: useTransform(overflow, [0, MAX_OVERFLOW], [1, 0.7]), - transformOrigin: useTransform(() => { - if (!sliderRef.current) { - return "center"; - } - const { left, width } = - sliderRef.current.getBoundingClientRect(); - return clientX.get() < left + width / 2 ? "right" : "left"; - }), - }} - > -
-
-
- - - -
- - - region === "right" ? overflow.get() / 2 : 0 - ), - scale: region === "right" ? 1.3 : 1, - }} - > - {rightIcon} - -
- - {showValue && ( -
- - {Math.round(internalValue)} - {max === 100 && "%"} - -
- )} -
- ); -} diff --git a/apps/dashboard/components/ui/form-dialog.tsx b/apps/dashboard/components/ui/form-dialog.tsx deleted file mode 100644 index b60a7fd759..0000000000 --- a/apps/dashboard/components/ui/form-dialog.tsx +++ /dev/null @@ -1,147 +0,0 @@ -"use client"; - -import { - Drawer, - DrawerContent, - DrawerDescription, - DrawerFooter, - DrawerHeader, - DrawerTitle, -} from "@/components/ui/drawer"; -import { useIsMobile } from "@/hooks/use-mobile"; -import { Button } from "@databuddy/ui"; -import { Dialog } from "@databuddy/ui/client"; - -interface FormDialogProps { - cancelLabel?: string; - children: React.ReactNode; - description?: string; - icon?: React.ReactNode; - isSubmitting?: boolean; - onOpenChange: (open: boolean) => void; - onSubmit: () => void; - open: boolean; - size?: "sm" | "md" | "lg"; - submitDisabled?: boolean; - submitLabel?: string; - title: string; -} - -export function FormDialog({ - open, - onOpenChange, - title, - description, - children, - onSubmit, - submitLabel = "Save", - cancelLabel = "Cancel", - isSubmitting = false, - submitDisabled = false, - icon, - size = "md", -}: FormDialogProps) { - const isMobile = useIsMobile(); - - const sizeClasses = { - sm: "w-[95vw] max-w-sm sm:w-full", - md: "w-[95vw] max-w-md sm:w-full", - lg: "w-[95vw] max-w-lg sm:w-full", - }; - - const drawerHeaderContent = icon ? ( -
-
- {icon} -
-
- {title} - {description && ( - {description} - )} -
-
- ) : null; - - const formContent = ( -
- {children} -
- ); - - const footerContent = ( - <> - - - - ); - - if (isMobile) { - return ( - - - {icon ? ( - {drawerHeaderContent} - ) : ( - - {title} - {description && ( - {description} - )} - - )} -
{formContent}
- - {footerContent} - -
-
- ); - } - - return ( - - - - {icon ? ( -
-
- {icon} -
-
- {title} - {description && ( - {description} - )} -
-
- ) : ( - <> - {title} - {description && ( - {description} - )} - - )} -
- {formContent} - {footerContent} - -
-
- ); -} diff --git a/apps/dashboard/components/ui/form.tsx b/apps/dashboard/components/ui/form.tsx deleted file mode 100644 index f1dd097369..0000000000 --- a/apps/dashboard/components/ui/form.tsx +++ /dev/null @@ -1,167 +0,0 @@ -"use client"; - -import { type Label as LabelPrimitive, Slot as SlotPrimitive } from "radix-ui"; -import * as React from "react"; - -import { - Controller, - type ControllerProps, - type FieldPath, - type FieldValues, - FormProvider, - useFormContext, - useFormState, -} from "react-hook-form"; -import { cn } from "@/lib/utils"; -import { Field } from "@databuddy/ui"; - -const Form = FormProvider; - -type FormFieldContextValue< - TFieldValues extends FieldValues = FieldValues, - TName extends FieldPath = FieldPath, -> = { - name: TName; -}; - -const FormFieldContext = React.createContext( - {} as FormFieldContextValue -); - -const FormField = < - TFieldValues extends FieldValues = FieldValues, - TName extends FieldPath = FieldPath, ->({ - ...props -}: ControllerProps) => { - return ( - - - - ); -}; - -const useFormField = () => { - const fieldContext = React.useContext(FormFieldContext); - const itemContext = React.useContext(FormItemContext); - const { getFieldState } = useFormContext(); - const formState = useFormState({ name: fieldContext.name }); - const fieldState = getFieldState(fieldContext.name, formState); - - if (!fieldContext) { - throw new Error("useFormField should be used within "); - } - - const { id } = itemContext; - - return { - id, - name: fieldContext.name, - formItemId: `${id}-form-item`, - formDescriptionId: `${id}-form-item-description`, - formMessageId: `${id}-form-item-message`, - ...fieldState, - }; -}; - -type FormItemContextValue = { - id: string; -}; - -const FormItemContext = React.createContext( - {} as FormItemContextValue -); - -function FormItem({ className, ...props }: React.ComponentProps<"div">) { - const id = React.useId(); - - return ( - -
- - ); -} - -function FormLabel({ - className, - ...props -}: React.ComponentProps) { - const { error, formItemId } = useFormField(); - - return ( - - ); -} - -function FormControl({ - ...props -}: React.ComponentProps) { - const { error, formItemId, formDescriptionId, formMessageId } = - useFormField(); - - return ( - - ); -} - -function FormDescription({ className, ...props }: React.ComponentProps<"p">) { - const { formDescriptionId } = useFormField(); - - return ( -

- ); -} - -function FormMessage({ className, ...props }: React.ComponentProps<"p">) { - const { error, formMessageId } = useFormField(); - const body = error ? String(error?.message ?? "") : props.children; - - if (!body) { - return null; - } - - return ( -

- {body} -

- ); -} - -export { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, - useFormField, -}; diff --git a/apps/dashboard/components/ui/inline-toggle.tsx b/apps/dashboard/components/ui/inline-toggle.tsx deleted file mode 100644 index b8941f6eaa..0000000000 --- a/apps/dashboard/components/ui/inline-toggle.tsx +++ /dev/null @@ -1,60 +0,0 @@ -"use client"; - -import type { ReactNode } from "react"; -import { cn } from "@/lib/utils"; - -type InlineToggleOption = { - value: T; - label: ReactNode; - ariaLabel?: string; -}; - -type InlineToggleProps = { - options: InlineToggleOption[]; - value: T; - onValueChangeAction: (value: T) => void; - className?: string; - disabled?: boolean; -}; - -export function InlineToggle({ - options, - value, - onValueChangeAction, - className, - disabled = false, -}: InlineToggleProps) { - return ( -
- {options.map((option) => { - const isSelected = option.value === value; - return ( - - ); - })} -
- ); -} diff --git a/apps/dashboard/components/ui/input-group.tsx b/apps/dashboard/components/ui/input-group.tsx deleted file mode 100644 index 8ee6478bd6..0000000000 --- a/apps/dashboard/components/ui/input-group.tsx +++ /dev/null @@ -1,169 +0,0 @@ -"use client"; - -import { cva, type VariantProps } from "class-variance-authority"; -import type * as React from "react"; -import { Input } from "@/components/ui/input"; -import { Textarea, type TextareaProps } from "@/components/ui/textarea"; -import { cn } from "@/lib/utils"; -import { Button } from "@databuddy/ui"; - -function InputGroup({ className, ...props }: React.ComponentProps<"div">) { - return ( -
textarea]:h-auto", - - // Variants based on alignment. - "has-[>[data-align=inline-start]]:[&>input]:pl-2", - "has-[>[data-align=inline-end]]:[&>input]:pr-2", - "has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3", - "has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3", - - // Focus state. - "has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50", - - // Error state. - "has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40", - - className - )} - data-slot="input-group" - role="group" - {...props} - /> - ); -} - -const inputGroupAddonVariants = cva( - "flex h-auto cursor-text select-none items-center justify-center gap-2 py-1.5 font-medium text-muted-foreground text-sm group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4", - { - variants: { - align: { - "inline-start": - "order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]", - "inline-end": - "order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]", - "block-start": - "order-first w-full justify-start px-3 pt-3 group-has-[>input]/input-group:pt-2.5 [.border-b]:pb-3", - "block-end": - "order-last w-full justify-start px-3 pb-3 group-has-[>input]/input-group:pb-2.5 [.border-t]:pt-3", - }, - }, - defaultVariants: { - align: "inline-start", - }, - } -); - -function InputGroupAddon({ - className, - align = "inline-start", - ...props -}: React.ComponentProps<"div"> & VariantProps) { - return ( -
{ - if ((e.target as HTMLElement).closest("button")) { - return; - } - e.currentTarget.parentElement?.querySelector("input")?.focus(); - }} - role="group" - {...props} - /> - ); -} - -const inputGroupButtonVariants = cva( - "flex items-center gap-2 text-sm shadow-none", - { - variants: { - size: { - xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-2 has-[>svg]:px-2 [&>svg:not([class*='size-'])]:size-3.5", - sm: "h-8 gap-1.5 rounded-md px-2.5 has-[>svg]:px-2.5", - "icon-xs": - "size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0", - "icon-sm": "size-8 p-0 has-[>svg]:p-0", - }, - }, - defaultVariants: { - size: "xs", - }, - } -); - -function InputGroupButton({ - className, - type = "button", - variant = "ghost", - size = "xs", - ...props -}: Omit, "size"> & - VariantProps) { - return ( -