From 127246623581bc0464f016341c4d44303be01eef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl-Gerhard=20Lindesva=CC=88rd?= Date: Fri, 27 Feb 2026 23:27:06 +0100 Subject: [PATCH] feat: add tracking code on project settings --- .../src/components/clients/table/columns.tsx | 13 +- .../src/components/clients/table/index.tsx | 13 +- .../src/components/onboarding/connect-web.tsx | 6 +- apps/start/src/routeTree.gen.ts | 23 ++++ ...nId.$projectId.settings._tabs.tracking.tsx | 116 ++++++++++++++++++ ...ganizationId.$projectId.settings._tabs.tsx | 19 +-- 6 files changed, 163 insertions(+), 27 deletions(-) create mode 100644 apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.tracking.tsx diff --git a/apps/start/src/components/clients/table/columns.tsx b/apps/start/src/components/clients/table/columns.tsx index adc707c3..5a7de0ba 100644 --- a/apps/start/src/components/clients/table/columns.tsx +++ b/apps/start/src/components/clients/table/columns.tsx @@ -1,7 +1,7 @@ -import { formatDateTime, formatTime } from '@/utils/date'; +import { DropdownMenuSeparator } from '@radix-ui/react-dropdown-menu'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; import type { ColumnDef } from '@tanstack/react-table'; -import { isToday } from 'date-fns'; - +import { toast } from 'sonner'; import { ColumnCreatedAt } from '@/components/column-created-at'; import CopyInput from '@/components/forms/copy-input'; import { createActionColumn } from '@/components/ui/data-table/data-table-helpers'; @@ -10,9 +10,6 @@ import { handleError, useTRPC } from '@/integrations/trpc/react'; import { pushModal, showConfirm } from '@/modals'; import type { RouterOutputs } from '@/trpc/client'; import { clipboard } from '@/utils/clipboard'; -import { DropdownMenuSeparator } from '@radix-ui/react-dropdown-menu'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { toast } from 'sonner'; export function useColumns() { const columns: ColumnDef[] = [ @@ -51,7 +48,7 @@ export function useColumns() { queryClient.invalidateQueries(trpc.client.list.pathFilter()); }, onError: handleError, - }), + }) ); return ( <> @@ -67,7 +64,6 @@ export function useColumns() { { showConfirm({ title: 'Revoke client', @@ -79,6 +75,7 @@ export function useColumns() { }, }); }} + variant="destructive" > Revoke diff --git a/apps/start/src/components/clients/table/index.tsx b/apps/start/src/components/clients/table/index.tsx index 5ef59cf7..5bb5a4a9 100644 --- a/apps/start/src/components/clients/table/index.tsx +++ b/apps/start/src/components/clients/table/index.tsx @@ -1,17 +1,16 @@ import type { UseQueryResult } from '@tanstack/react-query'; - +import { PlusIcon } from 'lucide-react'; +import { useColumns } from './columns'; import { Button } from '@/components/ui/button'; import { DataTable } from '@/components/ui/data-table/data-table'; import { DataTableToolbar } from '@/components/ui/data-table/data-table-toolbar'; import { useTable } from '@/components/ui/data-table/use-table'; import { pushModal } from '@/modals'; import type { RouterOutputs } from '@/trpc/client'; -import { PlusIcon } from 'lucide-react'; -import { useColumns } from './columns'; -type Props = { +interface Props { query: UseQueryResult; -}; +} export const ClientsTable = ({ query }: Props) => { const columns = useColumns(); @@ -30,13 +29,13 @@ export const ClientsTable = ({ query }: Props) => { - + ); }; diff --git a/apps/start/src/components/onboarding/connect-web.tsx b/apps/start/src/components/onboarding/connect-web.tsx index e41fc39c..a1b4a9de 100644 --- a/apps/start/src/components/onboarding/connect-web.tsx +++ b/apps/start/src/components/onboarding/connect-web.tsx @@ -27,7 +27,7 @@ const ConnectWeb = ({ client }: Props) => { `; return ( - <> +
@@ -44,7 +44,7 @@ const ConnectWeb = ({ client }: Props) => {
- +

@@ -80,7 +80,7 @@ const ConnectWeb = ({ client }: Props) => {

- +
); }; diff --git a/apps/start/src/routeTree.gen.ts b/apps/start/src/routeTree.gen.ts index 5b7eba9a..221cc39d 100644 --- a/apps/start/src/routeTree.gen.ts +++ b/apps/start/src/routeTree.gen.ts @@ -69,6 +69,7 @@ import { Route as AppOrganizationIdProjectIdProfilesTabsIndexRouteImport } from import { Route as AppOrganizationIdProjectIdNotificationsTabsIndexRouteImport } from './routes/_app.$organizationId.$projectId.notifications._tabs.index' import { Route as AppOrganizationIdProjectIdEventsTabsIndexRouteImport } from './routes/_app.$organizationId.$projectId.events._tabs.index' import { Route as AppOrganizationIdProjectIdSettingsTabsWidgetsRouteImport } from './routes/_app.$organizationId.$projectId.settings._tabs.widgets' +import { Route as AppOrganizationIdProjectIdSettingsTabsTrackingRouteImport } from './routes/_app.$organizationId.$projectId.settings._tabs.tracking' import { Route as AppOrganizationIdProjectIdSettingsTabsImportsRouteImport } from './routes/_app.$organizationId.$projectId.settings._tabs.imports' import { Route as AppOrganizationIdProjectIdSettingsTabsEventsRouteImport } from './routes/_app.$organizationId.$projectId.settings._tabs.events' import { Route as AppOrganizationIdProjectIdSettingsTabsDetailsRouteImport } from './routes/_app.$organizationId.$projectId.settings._tabs.details' @@ -475,6 +476,12 @@ const AppOrganizationIdProjectIdSettingsTabsWidgetsRoute = path: '/widgets', getParentRoute: () => AppOrganizationIdProjectIdSettingsTabsRoute, } as any) +const AppOrganizationIdProjectIdSettingsTabsTrackingRoute = + AppOrganizationIdProjectIdSettingsTabsTrackingRouteImport.update({ + id: '/tracking', + path: '/tracking', + getParentRoute: () => AppOrganizationIdProjectIdSettingsTabsRoute, + } as any) const AppOrganizationIdProjectIdSettingsTabsImportsRoute = AppOrganizationIdProjectIdSettingsTabsImportsRouteImport.update({ id: '/imports', @@ -634,6 +641,7 @@ export interface FileRoutesByFullPath { '/$organizationId/$projectId/settings/details': typeof AppOrganizationIdProjectIdSettingsTabsDetailsRoute '/$organizationId/$projectId/settings/events': typeof AppOrganizationIdProjectIdSettingsTabsEventsRoute '/$organizationId/$projectId/settings/imports': typeof AppOrganizationIdProjectIdSettingsTabsImportsRoute + '/$organizationId/$projectId/settings/tracking': typeof AppOrganizationIdProjectIdSettingsTabsTrackingRoute '/$organizationId/$projectId/settings/widgets': typeof AppOrganizationIdProjectIdSettingsTabsWidgetsRoute '/$organizationId/$projectId/events/': typeof AppOrganizationIdProjectIdEventsTabsIndexRoute '/$organizationId/$projectId/notifications/': typeof AppOrganizationIdProjectIdNotificationsTabsIndexRoute @@ -701,6 +709,7 @@ export interface FileRoutesByTo { '/$organizationId/$projectId/settings/details': typeof AppOrganizationIdProjectIdSettingsTabsDetailsRoute '/$organizationId/$projectId/settings/events': typeof AppOrganizationIdProjectIdSettingsTabsEventsRoute '/$organizationId/$projectId/settings/imports': typeof AppOrganizationIdProjectIdSettingsTabsImportsRoute + '/$organizationId/$projectId/settings/tracking': typeof AppOrganizationIdProjectIdSettingsTabsTrackingRoute '/$organizationId/$projectId/settings/widgets': typeof AppOrganizationIdProjectIdSettingsTabsWidgetsRoute '/$organizationId/$projectId/profiles/$profileId/events': typeof AppOrganizationIdProjectIdProfilesProfileIdTabsEventsRoute '/$organizationId/$projectId/profiles/$profileId/sessions': typeof AppOrganizationIdProjectIdProfilesProfileIdTabsSessionsRoute @@ -781,6 +790,7 @@ export interface FileRoutesById { '/_app/$organizationId/$projectId/settings/_tabs/details': typeof AppOrganizationIdProjectIdSettingsTabsDetailsRoute '/_app/$organizationId/$projectId/settings/_tabs/events': typeof AppOrganizationIdProjectIdSettingsTabsEventsRoute '/_app/$organizationId/$projectId/settings/_tabs/imports': typeof AppOrganizationIdProjectIdSettingsTabsImportsRoute + '/_app/$organizationId/$projectId/settings/_tabs/tracking': typeof AppOrganizationIdProjectIdSettingsTabsTrackingRoute '/_app/$organizationId/$projectId/settings/_tabs/widgets': typeof AppOrganizationIdProjectIdSettingsTabsWidgetsRoute '/_app/$organizationId/$projectId/events/_tabs/': typeof AppOrganizationIdProjectIdEventsTabsIndexRoute '/_app/$organizationId/$projectId/notifications/_tabs/': typeof AppOrganizationIdProjectIdNotificationsTabsIndexRoute @@ -855,6 +865,7 @@ export interface FileRouteTypes { | '/$organizationId/$projectId/settings/details' | '/$organizationId/$projectId/settings/events' | '/$organizationId/$projectId/settings/imports' + | '/$organizationId/$projectId/settings/tracking' | '/$organizationId/$projectId/settings/widgets' | '/$organizationId/$projectId/events/' | '/$organizationId/$projectId/notifications/' @@ -922,6 +933,7 @@ export interface FileRouteTypes { | '/$organizationId/$projectId/settings/details' | '/$organizationId/$projectId/settings/events' | '/$organizationId/$projectId/settings/imports' + | '/$organizationId/$projectId/settings/tracking' | '/$organizationId/$projectId/settings/widgets' | '/$organizationId/$projectId/profiles/$profileId/events' | '/$organizationId/$projectId/profiles/$profileId/sessions' @@ -1001,6 +1013,7 @@ export interface FileRouteTypes { | '/_app/$organizationId/$projectId/settings/_tabs/details' | '/_app/$organizationId/$projectId/settings/_tabs/events' | '/_app/$organizationId/$projectId/settings/_tabs/imports' + | '/_app/$organizationId/$projectId/settings/_tabs/tracking' | '/_app/$organizationId/$projectId/settings/_tabs/widgets' | '/_app/$organizationId/$projectId/events/_tabs/' | '/_app/$organizationId/$projectId/notifications/_tabs/' @@ -1493,6 +1506,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AppOrganizationIdProjectIdSettingsTabsWidgetsRouteImport parentRoute: typeof AppOrganizationIdProjectIdSettingsTabsRoute } + '/_app/$organizationId/$projectId/settings/_tabs/tracking': { + id: '/_app/$organizationId/$projectId/settings/_tabs/tracking' + path: '/tracking' + fullPath: '/$organizationId/$projectId/settings/tracking' + preLoaderRoute: typeof AppOrganizationIdProjectIdSettingsTabsTrackingRouteImport + parentRoute: typeof AppOrganizationIdProjectIdSettingsTabsRoute + } '/_app/$organizationId/$projectId/settings/_tabs/imports': { id: '/_app/$organizationId/$projectId/settings/_tabs/imports' path: '/imports' @@ -1766,6 +1786,7 @@ interface AppOrganizationIdProjectIdSettingsTabsRouteChildren { AppOrganizationIdProjectIdSettingsTabsDetailsRoute: typeof AppOrganizationIdProjectIdSettingsTabsDetailsRoute AppOrganizationIdProjectIdSettingsTabsEventsRoute: typeof AppOrganizationIdProjectIdSettingsTabsEventsRoute AppOrganizationIdProjectIdSettingsTabsImportsRoute: typeof AppOrganizationIdProjectIdSettingsTabsImportsRoute + AppOrganizationIdProjectIdSettingsTabsTrackingRoute: typeof AppOrganizationIdProjectIdSettingsTabsTrackingRoute AppOrganizationIdProjectIdSettingsTabsWidgetsRoute: typeof AppOrganizationIdProjectIdSettingsTabsWidgetsRoute AppOrganizationIdProjectIdSettingsTabsIndexRoute: typeof AppOrganizationIdProjectIdSettingsTabsIndexRoute } @@ -1780,6 +1801,8 @@ const AppOrganizationIdProjectIdSettingsTabsRouteChildren: AppOrganizationIdProj AppOrganizationIdProjectIdSettingsTabsEventsRoute, AppOrganizationIdProjectIdSettingsTabsImportsRoute: AppOrganizationIdProjectIdSettingsTabsImportsRoute, + AppOrganizationIdProjectIdSettingsTabsTrackingRoute: + AppOrganizationIdProjectIdSettingsTabsTrackingRoute, AppOrganizationIdProjectIdSettingsTabsWidgetsRoute: AppOrganizationIdProjectIdSettingsTabsWidgetsRoute, AppOrganizationIdProjectIdSettingsTabsIndexRoute: diff --git a/apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.tracking.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.tracking.tsx new file mode 100644 index 00000000..dbebd244 --- /dev/null +++ b/apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.tracking.tsx @@ -0,0 +1,116 @@ +import type { IServiceClient } from '@openpanel/db'; +import { frameworks } from '@openpanel/sdk-info'; +import { useQuery } from '@tanstack/react-query'; +import { createFileRoute } from '@tanstack/react-router'; +import { CopyIcon } from 'lucide-react'; +import { useEffect, useState } from 'react'; +import Syntax from '@/components/syntax'; +import { Button } from '@/components/ui/button'; +import { Combobox } from '@/components/ui/combobox'; +import { useAppContext } from '@/hooks/use-app-context'; +import { useAppParams } from '@/hooks/use-app-params'; +import { useTRPC } from '@/integrations/trpc/react'; +import { pushModal } from '@/modals'; +import { clipboard } from '@/utils/clipboard'; + +export const Route = createFileRoute( + '/_app/$organizationId/$projectId/settings/_tabs/tracking' +)({ + component: Component, +}); + +function Component() { + const { projectId } = useAppParams(); + const trpc = useTRPC(); + const query = useQuery(trpc.client.list.queryOptions({ projectId })); + return ; +} + +interface Props { + clients: IServiceClient[]; +} + +const ConnectWeb = ({ clients }: Props) => { + const [client, setClient] = useState(null); + useEffect(() => { + if (!client && clients && clients.length > 0) { + setClient(clients[0]); + } + }, [clients]); + const context = useAppContext(); + const code = ` +`; + return ( +
+
+
+ ({ + value: c.id, + label: c.name, + }))} + onChange={(id) => + setClient(clients.find((c) => c.id === id) ?? null) + } + placeholder="Select client" + searchable + value={client?.id ?? null} + /> + +
+ +
+
+

+ Or pick a framework below to get started. +

+
+ {frameworks.map((framework) => ( + + ))} +
+

+ Missing a framework?{' '} + + Let us know! + +

+
+
+ ); +}; diff --git a/apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.tsx b/apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.tsx index 2acfa01d..205fb7bc 100644 --- a/apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.tsx +++ b/apps/start/src/routes/_app.$organizationId.$projectId.settings._tabs.tsx @@ -1,16 +1,16 @@ -import FullPageLoadingState from '@/components/full-page-loading-state'; -import { PageHeader } from '@/components/page-header'; -import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; -import { PAGE_TITLES, createProjectTitle } from '@/utils/title'; import { - Outlet, createFileRoute, + Outlet, useLocation, useRouter, } from '@tanstack/react-router'; +import FullPageLoadingState from '@/components/full-page-loading-state'; +import { PageHeader } from '@/components/page-header'; +import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { createProjectTitle, PAGE_TITLES } from '@/utils/title'; export const Route = createFileRoute( - '/_app/$organizationId/$projectId/settings/_tabs', + '/_app/$organizationId/$projectId/settings/_tabs' )({ component: ProjectDashboard, head: () => { @@ -27,7 +27,7 @@ export const Route = createFileRoute( await queryClient.prefetchQuery( trpc.project.getProjectWithClients.queryOptions({ projectId: params.projectId, - }), + }) ); }, pendingComponent: FullPageLoadingState, @@ -42,6 +42,7 @@ function ProjectDashboard() { { id: 'details', label: 'Details' }, { id: 'events', label: 'Events' }, { id: 'clients', label: 'Clients' }, + { id: 'tracking', label: 'Tracking script' }, { id: 'widgets', label: 'Widgets' }, { id: 'imports', label: 'Imports' }, ]; @@ -56,11 +57,11 @@ function ProjectDashboard() { return (
- + {settingsTabs.map((tab) => (