diff --git a/Dockerfile b/Dockerfile index a9fdfa1865..d7904cc76e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -77,20 +77,12 @@ ARG NEXT_PUBLIC_PORTAL_URL ARG NEXT_PUBLIC_POSTHOG_KEY ARG NEXT_PUBLIC_POSTHOG_HOST ARG NEXT_PUBLIC_IS_DUB_ENABLED -ARG NEXT_PUBLIC_GTM_ID -ARG NEXT_PUBLIC_LINKEDIN_PARTNER_ID -ARG NEXT_PUBLIC_LINKEDIN_CONVERSION_ID -ARG NEXT_PUBLIC_GOOGLE_ADS_CONVERSION_LABEL ARG NEXT_PUBLIC_API_URL ENV NEXT_PUBLIC_BETTER_AUTH_URL=$NEXT_PUBLIC_BETTER_AUTH_URL \ NEXT_PUBLIC_PORTAL_URL=$NEXT_PUBLIC_PORTAL_URL \ NEXT_PUBLIC_POSTHOG_KEY=$NEXT_PUBLIC_POSTHOG_KEY \ NEXT_PUBLIC_POSTHOG_HOST=$NEXT_PUBLIC_POSTHOG_HOST \ NEXT_PUBLIC_IS_DUB_ENABLED=$NEXT_PUBLIC_IS_DUB_ENABLED \ - NEXT_PUBLIC_GTM_ID=$NEXT_PUBLIC_GTM_ID \ - NEXT_PUBLIC_LINKEDIN_PARTNER_ID=$NEXT_PUBLIC_LINKEDIN_PARTNER_ID \ - NEXT_PUBLIC_LINKEDIN_CONVERSION_ID=$NEXT_PUBLIC_LINKEDIN_CONVERSION_ID \ - NEXT_PUBLIC_GOOGLE_ADS_CONVERSION_LABEL=$NEXT_PUBLIC_GOOGLE_ADS_CONVERSION_LABEL \ NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL \ NEXT_TELEMETRY_DISABLED=1 NODE_ENV=production \ NEXT_OUTPUT_STANDALONE=true \ @@ -157,4 +149,4 @@ COPY --from=portal-builder /app/apps/portal/public ./apps/portal/public EXPOSE 3000 CMD ["node", "apps/portal/server.js"] -# (Trigger.dev hosted; no local runner stage) \ No newline at end of file +# (Trigger.dev hosted; no local runner stage) diff --git a/apps/app/package.json b/apps/app/package.json index 92b94ca95d..de6e57de53 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -31,7 +31,6 @@ "@mendable/firecrawl-js": "^1.24.0", "@monaco-editor/react": "^4.7.0", "@nangohq/frontend": "^0.53.2", - "@next/third-parties": "^15.3.1", "@novu/api": "^1.6.0", "@novu/nextjs": "^3.10.1", "@number-flow/react": "^0.5.9", diff --git a/apps/app/src/app/(app)/setup/hooks/useOnboardingForm.ts b/apps/app/src/app/(app)/setup/hooks/useOnboardingForm.ts index 9ac806e8b7..89f3375fcc 100644 --- a/apps/app/src/app/(app)/setup/hooks/useOnboardingForm.ts +++ b/apps/app/src/app/(app)/setup/hooks/useOnboardingForm.ts @@ -3,9 +3,8 @@ import { useApi } from '@/hooks/use-api'; import { trackEvent, trackOnboardingEvent } from '@/utils/tracking'; import { zodResolver } from '@hookform/resolvers/zod'; -import { sendGTMEvent } from '@next/third-parties/google'; import { useAction } from 'next-safe-action/hooks'; -import { useRouter, useSearchParams } from 'next/navigation'; +import { useSearchParams } from 'next/navigation'; import { useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; import { toast } from 'sonner'; @@ -30,7 +29,6 @@ export function useOnboardingForm({ initialData, currentStep, }: UseOnboardingFormProps = {}) { - const router = useRouter(); const searchParams = useSearchParams(); const api = useApi(); @@ -114,7 +112,6 @@ export function useOnboardingForm({ onSuccess: async ({ data }) => { if (data?.success && data?.organizationId) { setIsFinalizing(true); - sendGTMEvent({ event: 'conversion' }); // Track organization created trackEvent('organization_created', { @@ -231,14 +228,14 @@ export function useOnboardingForm({ const handlePrefillAll = async () => { try { // Fetch frameworks to get valid IDs - const response = await api.get<{ data: { id: string; visible: boolean }[] }>('/v1/frameworks/available'); + const response = await api.get<{ data: { id: string; visible: boolean }[] }>( + '/v1/frameworks/available', + ); const allFrameworks = Array.isArray(response.data?.data) ? response.data.data : []; const visibleFrameworks = allFrameworks.filter((f) => f.visible); - + // Use first two visible frameworks, or just the first one if only one exists - const frameworkIds = visibleFrameworks - .slice(0, 2) - .map((f: { id: string }) => f.id); + const frameworkIds = visibleFrameworks.slice(0, 2).map((f: { id: string }) => f.id); const prefilledAnswers: Partial = { frameworkIds: frameworkIds.length > 0 ? frameworkIds : [], diff --git a/apps/app/src/app/layout.tsx b/apps/app/src/app/layout.tsx index 267a5d03ff..d4c7ff01f0 100644 --- a/apps/app/src/app/layout.tsx +++ b/apps/app/src/app/layout.tsx @@ -1,10 +1,9 @@ import '@trycompai/design-system/globals.css'; -import { LinkedInInsight } from '@/components/tracking/LinkedInInsight'; import { env } from '@/env.mjs'; import { auth } from '@/utils/auth'; -import { cn } from '@trycompai/ui/cn'; import { Analytics as DubAnalytics } from '@dub/analytics/react'; +import { cn } from '@trycompai/ui/cn'; import { Analytics as VercelAnalytics } from '@vercel/analytics/next'; import { GeistMono } from 'geist/font/mono'; import type { Metadata } from 'next'; @@ -95,19 +94,9 @@ export default async function Layout({ children }: { children: React.ReactNode } /> )} - - {env.NEXT_PUBLIC_LINKEDIN_PARTNER_ID && ( - - )} + - - {children} - + {children} diff --git a/apps/app/src/app/providers.tsx b/apps/app/src/app/providers.tsx index d3ace1603e..bed840ffce 100644 --- a/apps/app/src/app/providers.tsx +++ b/apps/app/src/app/providers.tsx @@ -1,14 +1,12 @@ 'use client'; -import { env } from '@/env.mjs'; -import { AnalyticsProvider } from '@trycompai/analytics'; -import { Toaster } from '@trycompai/ui/sooner'; -import { GoogleTagManager } from '@next/third-parties/google'; import { defaultShouldDehydrateQuery, QueryClient, QueryClientProvider, } from '@tanstack/react-query'; +import { AnalyticsProvider } from '@trycompai/analytics'; +import { Toaster } from '@trycompai/ui/sooner'; import { Session, User } from 'better-auth'; import { ThemeProvider } from 'next-themes'; import type { ReactNode } from 'react'; @@ -72,15 +70,6 @@ export function Providers({ children, session }: ProviderProps) { disableTransitionOnChange scriptProps={{ 'data-cfasync': 'false' }} > - {env.NEXT_PUBLIC_GTM_ID && ( - - )} - {` - _linkedin_partner_id = "${partnerId}"; - window._linkedin_data_partner_ids = window._linkedin_data_partner_ids || []; - window._linkedin_data_partner_ids.push(_linkedin_partner_id); - (function(l) { - if (!l){window.lintrk = function(a,b){window.lintrk.q.push([a,b])}; - window.lintrk.q=[]} - var s = document.getElementsByTagName("script")[0]; - var b = document.createElement("script"); - b.type = "text/javascript";b.async = true; - b.src = "https://snap.licdn.com/li.lms-analytics/insight.min.js"; - s.parentNode.insertBefore(b, s);})(window.lintrk); - `} - - ); -} diff --git a/apps/app/src/components/tracking/README.md b/apps/app/src/components/tracking/README.md index e7fbe80de7..112bc7339e 100644 --- a/apps/app/src/components/tracking/README.md +++ b/apps/app/src/components/tracking/README.md @@ -1,36 +1,16 @@ -# Unified Tracking Implementation Guide +# Product Analytics Tracking -This implementation provides a unified tracking system that sends events to Google Tag Manager, LinkedIn, and PostHog on both client and server side. +The product app intentionally tracks product analytics only. Marketing pixels belong on the marketing site, not on `app.trycomp.ai`. -## Services Integrated +Revenue, customer, and signed-deal measurement for paid media lives in HubSpot lifecycle imports. App-side `purchase_completed` events are product analytics only and must not be wired to ad conversion pixels. -1. **Google Tag Manager (GTM)** - For Google Analytics and Google Ads conversion tracking -2. **LinkedIn Insight Tag** - For LinkedIn Ads tracking and conversions -3. **PostHog** - For product analytics and feature usage tracking +## Service Integrated -## Environment Variables +1. **PostHog** - Product analytics and feature usage tracking -Add these to your `.env.local` file: +## Environment Variables ```bash -# Google Tag Manager (Client-side) -NEXT_PUBLIC_GTM_ID=GTM-XXXXXXX - -# Google Ads Conversion Tracking (Client-side) -NEXT_PUBLIC_GOOGLE_ADS_CONVERSION_LABEL=AW-XXXXXXXXX/YYYYYYYYYYY - -# Google Analytics 4 (Server-side) -GA4_API_SECRET=your-ga4-api-secret -GA4_MEASUREMENT_ID=G-XXXXXXXXXX - -# LinkedIn Insight Tag (Client-side) -NEXT_PUBLIC_LINKEDIN_PARTNER_ID=1234567 - -# LinkedIn Conversions (Client & Server) -NEXT_PUBLIC_LINKEDIN_CONVERSION_ID=1234567 -LINKEDIN_CONVERSIONS_ACCESS_TOKEN=your-linkedin-access-token - -# PostHog (Client & Server) NEXT_PUBLIC_POSTHOG_KEY=phc_xxxxxxxxxxxxxxxxxxxxxxxxxxxx NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com ``` @@ -39,19 +19,11 @@ NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com ### Client-Side Tracking -The `trackEvent()` function in `utils/tracking.ts` automatically sends events to: - -- Google Tag Manager via `dataLayer.push()` -- PostHog via `Analytics.track()` -- LinkedIn for specific conversion events +The `trackEvent()` function in `utils/tracking.ts` sends events to PostHog via `Analytics.track()`. ### Server-Side Tracking -The `trackEventServer()` function in `utils/server-tracking.ts` sends events to: - -- PostHog via the Node.js SDK -- Google Analytics 4 via Measurement Protocol API -- LinkedIn Conversions API (for purchase events) +The `trackEventServer()` function in `utils/server-tracking.ts` sends events to PostHog via the Node.js SDK. ## Events Tracked @@ -67,9 +39,8 @@ The `trackEventServer()` function in `utils/server-tracking.ts` sends events to: - `upgrade_started` - When user views pricing page - `checkout_started` - When user clicks a pricing plan -- `purchase_completed` - When payment is successful +- `purchase_completed` - Product billing/checkout success in PostHog only - `upgrade_completed` - After successful upgrade -- `conversion` - Google Ads specific conversion event ### Additional Events @@ -77,38 +48,6 @@ The `trackEventServer()` function in `utils/server-tracking.ts` sends events to: - `signup_started` - When signup begins - `signup_completed` - When signup completes -## Conversion Tracking - -### Google Ads Conversion Tracking - -When a purchase is completed, the system automatically: - -1. Sends a `purchase_completed` event with full ecommerce data -2. Sends a `conversion` event specifically for Google Ads (if configured) - -The conversion event includes: - -- Transaction value -- Currency (USD) -- Unique transaction ID -- Product details (plan name, category, etc.) - -To get your Google Ads conversion label: - -1. Go to Google Ads > Tools & Settings > Conversions -2. Create or select your conversion action -3. Click "Tag setup" > "Use Google Tag Manager" -4. Copy the Conversion ID and Conversion Label -5. Format: `AW-CONVERSIONID/CONVERSIONLABEL` - -### Client-Side Purchase Tracking - -The `CheckoutCompleteTracking` component automatically tracks conversions when users complete a purchase. It reads parameters from the URL: - -- `checkoutComplete` - The plan type purchased -- `organizationId` - The organization ID -- `value` - The transaction value - ## Usage Examples ### Client-Side Tracking @@ -116,14 +55,12 @@ The `CheckoutCompleteTracking` component automatically tracks conversions when u ```typescript import { trackEvent, trackPurchaseEvent } from '@/utils/tracking'; -// Track a custom event trackEvent('feature_used', { event_category: 'engagement', event_label: 'export_data', feature_name: 'data_export', }); -// Track a purchase event trackPurchaseEvent('checkout_started', 99, 'USD'); ``` @@ -132,7 +69,6 @@ trackPurchaseEvent('checkout_started', 99, 'USD'); ```typescript import { trackEventServer, trackPurchaseCompletionServer } from '@/utils/server-tracking'; -// Track any event from server await trackEventServer( 'api_called', { @@ -143,57 +79,9 @@ await trackEventServer( userId, ); -// Track purchase completion await trackPurchaseCompletionServer(organizationId, 'starter', 99, userId); ``` -## Setup Instructions - -### 1. Google Tag Manager Setup - -1. Create a GTM container -2. Add Google Analytics 4 Configuration tag -3. Set up conversion triggers for tracked events -4. Enable cross-domain tracking if needed - -For Google Ads conversion tracking in GTM: - -1. Create a new tag: Google Ads Conversion Tracking -2. Set trigger to fire on `purchase_completed` or `conversion` events -3. Use dataLayer variables for dynamic values - -### 2. Google Analytics 4 Server-Side - -1. Go to GA4 Admin > Data Streams > your stream -2. Navigate to Measurement Protocol API secrets -3. Create a new API secret -4. Add the secret and measurement ID to your env - -### 3. LinkedIn Setup - -1. Add LinkedIn Insight Tag partner ID from Campaign Manager -2. Create conversion events in Campaign Manager -3. For Conversions API access, apply for LinkedIn Marketing API access - -### 4. PostHog Setup - -1. Get your project API key from PostHog -2. Set the host (US or EU cloud) -3. PostHog is automatically initialized via the Analytics provider - -## Benefits of Unified Tracking - -1. **Consistency** - All events are tracked across all platforms -2. **Server-Side Reliability** - Events are tracked even with ad blockers -3. **Better Attribution** - Cross-device and cross-domain tracking -4. **Privacy Compliant** - Server-side tracking respects user privacy settings -5. **Debugging** - All events logged in development mode - ## Testing -In development, all server-side events are logged to the console. Check the browser DevTools for client-side events: - -- GTM: Use Tag Assistant or Preview mode -- PostHog: Check the Network tab for `/e` requests -- LinkedIn: Use LinkedIn Insight Tag Helper extension -- Google Ads: Check for `conversion` events in dataLayer +In development, server-side events are logged to the console. In the browser, check the Network tab for PostHog `/e` requests. diff --git a/apps/app/src/env.mjs b/apps/app/src/env.mjs index efb8bb00d9..b4e338eab9 100644 --- a/apps/app/src/env.mjs +++ b/apps/app/src/env.mjs @@ -40,9 +40,6 @@ export const env = createEnv({ FLEET_TOKEN: z.string().optional(), DUB_API_KEY: z.string().optional(), DUB_REFER_URL: z.string().optional(), - GA4_API_SECRET: z.string().optional(), - GA4_MEASUREMENT_ID: z.string().optional(), - LINKEDIN_CONVERSIONS_ACCESS_TOKEN: z.string().optional(), NOVU_API_KEY: z.string().optional(), SERVICE_TOKEN_TRIGGER: z.string().optional(), STRIPE_SECRET_KEY: z.string().optional(), @@ -57,10 +54,6 @@ export const env = createEnv({ NEXT_PUBLIC_POSTHOG_KEY: z.string().optional(), NEXT_PUBLIC_POSTHOG_HOST: z.string().optional(), NEXT_PUBLIC_IS_DUB_ENABLED: z.string().optional(), - NEXT_PUBLIC_GTM_ID: z.string().optional(), - NEXT_PUBLIC_LINKEDIN_PARTNER_ID: z.string().optional(), - NEXT_PUBLIC_LINKEDIN_CONVERSION_ID: z.string().optional(), - NEXT_PUBLIC_GOOGLE_ADS_CONVERSION_LABEL: z.string().optional(), NEXT_PUBLIC_API_URL: z.string().optional(), NEXT_PUBLIC_BETTER_AUTH_URL: z.string().optional(), NEXT_PUBLIC_NOVU_APPLICATION_IDENTIFIER: z.string().optional(), @@ -109,13 +102,6 @@ export const env = createEnv({ DUB_API_KEY: process.env.DUB_API_KEY, DUB_REFER_URL: process.env.DUB_REFER_URL, NEXT_PUBLIC_IS_DUB_ENABLED: process.env.NEXT_PUBLIC_IS_DUB_ENABLED, - NEXT_PUBLIC_GTM_ID: process.env.NEXT_PUBLIC_GTM_ID, - NEXT_PUBLIC_LINKEDIN_PARTNER_ID: process.env.NEXT_PUBLIC_LINKEDIN_PARTNER_ID, - NEXT_PUBLIC_LINKEDIN_CONVERSION_ID: process.env.NEXT_PUBLIC_LINKEDIN_CONVERSION_ID, - GA4_API_SECRET: process.env.GA4_API_SECRET, - GA4_MEASUREMENT_ID: process.env.GA4_MEASUREMENT_ID, - LINKEDIN_CONVERSIONS_ACCESS_TOKEN: process.env.LINKEDIN_CONVERSIONS_ACCESS_TOKEN, - NEXT_PUBLIC_GOOGLE_ADS_CONVERSION_LABEL: process.env.NEXT_PUBLIC_GOOGLE_ADS_CONVERSION_LABEL, NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL, NEXT_PUBLIC_BETTER_AUTH_URL: process.env.NEXT_PUBLIC_BETTER_AUTH_URL, NOVU_API_KEY: process.env.NOVU_API_KEY, diff --git a/apps/app/src/hooks/use-unified-tracking.ts b/apps/app/src/hooks/use-unified-tracking.ts index 4f41772f93..b8a7d90507 100644 --- a/apps/app/src/hooks/use-unified-tracking.ts +++ b/apps/app/src/hooks/use-unified-tracking.ts @@ -8,7 +8,7 @@ import { import { useCallback } from 'react'; /** - * React hook for unified tracking across Google Tag Manager, LinkedIn, and PostHog + * React hook for product analytics tracking. */ export function useUnifiedTracking() { const track = useCallback( diff --git a/apps/app/src/utils/server-tracking.ts b/apps/app/src/utils/server-tracking.ts index d60b1f2550..f4fccace4b 100644 --- a/apps/app/src/utils/server-tracking.ts +++ b/apps/app/src/utils/server-tracking.ts @@ -17,12 +17,9 @@ async function getTrackingUserId(): Promise { return session.user.id; } - // Fallback to anonymous tracking ID from cookies + // Fallback to anonymous PostHog tracking ID from cookies const cookieStore = await cookies(); - const anonymousId = - cookieStore.get('_ga')?.value || // Google Analytics client ID - cookieStore.get('ph_distinct_id')?.value || // PostHog distinct ID - null; + const anonymousId = cookieStore.get('ph_distinct_id')?.value || null; return anonymousId; } catch (error) { @@ -32,7 +29,7 @@ async function getTrackingUserId(): Promise { } /** - * Server-side event tracking that sends to all services + * Server-side event tracking that sends to PostHog. */ export async function trackEventServer( eventName: TrackingEventName, @@ -45,112 +42,12 @@ export async function trackEventServer( // 1. PostHog Server-Side Tracking await trackPostHog(distinctId, eventName, parameters); - - // 2. Google Analytics Measurement Protocol (if you have GA4 API Secret) - // Note: You'll need to add GA4_API_SECRET and GA4_MEASUREMENT_ID to your env - if (process.env.GA4_API_SECRET && process.env.GA4_MEASUREMENT_ID) { - await sendToGoogleAnalytics(eventName, parameters, distinctId); - } - - // 3. LinkedIn Conversions API (if you have access token) - // Note: LinkedIn's Conversions API requires special access - if (eventName === 'purchase_completed' && process.env.LINKEDIN_CONVERSIONS_ACCESS_TOKEN) { - await sendToLinkedInConversions(parameters); - } - } catch (error) { console.error('Error tracking server event:', error); // Don't throw - tracking should not break the application } } -/** - * Send event to Google Analytics 4 via Measurement Protocol - */ -async function sendToGoogleAnalytics( - eventName: string, - parameters?: TrackingEventParameters, - clientId?: string, -) { - const apiSecret = process.env.GA4_API_SECRET; - const measurementId = process.env.GA4_MEASUREMENT_ID; - - if (!apiSecret || !measurementId) return; - - try { - const payload = { - client_id: clientId || 'server_' + Date.now(), - events: [ - { - name: eventName, - params: { - ...parameters, - engagement_time_msec: '100', - session_id: Date.now().toString(), - }, - }, - ], - }; - - const response = await fetch( - `https://www.google-analytics.com/mp/collect?measurement_id=${measurementId}&api_secret=${apiSecret}`, - { - method: 'POST', - body: JSON.stringify(payload), - headers: { - 'Content-Type': 'application/json', - }, - }, - ); - - if (!response.ok) { - console.error('GA4 Measurement Protocol error:', response.statusText); - } - } catch (error) { - console.error('Error sending to Google Analytics:', error); - } -} - -/** - * Send conversion event to LinkedIn Conversions API - */ -async function sendToLinkedInConversions(parameters?: TrackingEventParameters) { - const accessToken = process.env.LINKEDIN_CONVERSIONS_ACCESS_TOKEN; - const conversionId = process.env.NEXT_PUBLIC_LINKEDIN_CONVERSION_ID; - - if (!accessToken || !conversionId) return; - - try { - // LinkedIn Conversions API implementation - // Note: This is a simplified example - LinkedIn's API has specific requirements - const payload = { - conversion: conversionId, - conversionHappenedAt: Date.now(), - conversionValue: { - currencyCode: parameters?.currency || 'USD', - amount: parameters?.value?.toString() || '0', - }, - // Add user data if available (email hash, etc.) - }; - - const response = await fetch('https://api.linkedin.com/v2/conversionEvents', { - method: 'POST', - headers: { - Authorization: `Bearer ${accessToken}`, - 'Content-Type': 'application/json', - 'LinkedIn-Version': '202401', - }, - body: JSON.stringify(payload), - }); - - if (!response.ok) { - console.error('LinkedIn Conversions API error:', response.statusText); - } - } catch (error) { - console.error('Error sending to LinkedIn Conversions:', error); - } -} - /** * Track purchase completion on the server */ diff --git a/apps/app/src/utils/tracking.ts b/apps/app/src/utils/tracking.ts index ab6bab3432..8bbb5c3411 100644 --- a/apps/app/src/utils/tracking.ts +++ b/apps/app/src/utils/tracking.ts @@ -13,8 +13,7 @@ export type TrackingEventName = | 'upgrade_started' | 'upgrade_completed' | 'checkout_started' - | 'purchase_completed' - | 'conversion'; + | 'purchase_completed'; export interface TrackingEventParameters { event_category?: string; @@ -24,43 +23,13 @@ export interface TrackingEventParameters { [key: string]: any; } -declare global { - interface Window { - dataLayer?: Object[]; - lintrk?: (action: string, data: any) => void; - } -} - /** - * Unified tracking that sends events to all services + * Product tracking that sends events to PostHog. */ export function trackEvent(eventName: TrackingEventName, parameters?: TrackingEventParameters) { if (typeof window === 'undefined') return; - // 1. Google Tag Manager - if (window.dataLayer) { - window.dataLayer.push({ - event: eventName, - ...parameters, - }); - } - - // 2. PostHog Analytics.track(eventName, parameters); - - // 3. LinkedIn - for specific conversion events - if (eventName === 'purchase_completed' && process.env.NEXT_PUBLIC_LINKEDIN_CONVERSION_ID) { - trackLinkedInConversion(process.env.NEXT_PUBLIC_LINKEDIN_CONVERSION_ID); - } -} - -/** - * Track conversion with LinkedIn - */ -export function trackLinkedInConversion(conversionId: string) { - if (typeof window !== 'undefined' && window.lintrk) { - window.lintrk('track', { conversion_id: conversionId }); - } } /** diff --git a/bun.lock b/bun.lock index 414cd73af9..9acbc4bbd0 100644 --- a/bun.lock +++ b/bun.lock @@ -246,7 +246,6 @@ "@mendable/firecrawl-js": "^1.24.0", "@monaco-editor/react": "^4.7.0", "@nangohq/frontend": "^0.53.2", - "@next/third-parties": "^15.3.1", "@novu/api": "^1.6.0", "@novu/nextjs": "^3.10.1", "@number-flow/react": "^0.5.9", @@ -1790,8 +1789,6 @@ "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.2.0", "", { "os": "win32", "cpu": "x64" }, "sha512-DRrNJKW+/eimrZgdhVN1uvkN1OI4j6Lpefwr44jKQ0YQzztlmOBUUzHuV5GxOMPK3nmodAYElUVCY8ZXo/IWeA=="], - "@next/third-parties": ["@next/third-parties@15.5.9", "", { "dependencies": { "third-party-capital": "1.0.20" }, "peerDependencies": { "next": "^13.0.0 || ^14.0.0 || ^15.0.0", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0" } }, "sha512-kX8u/o+NMUwib5Rn+J9zhx47wZZzgxW3Q/OTuqG4gcZS80jARZCU/9bQ5hGL6V9XGDWZiR/Lycs7Dg8Y+xOfCw=="], - "@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="], "@noble/curves": ["@noble/curves@1.9.7", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw=="], @@ -6244,8 +6241,6 @@ "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], - "third-party-capital": ["third-party-capital@1.0.20", "", {}, "sha512-oB7yIimd8SuGptespDAZnNkzIz+NWaJCu2RMsbs4Wmp9zSDUM8Nhi3s2OOcqYuv3mN4hitXc8DVx+LyUmbUDiA=="], - "thread-stream": ["thread-stream@3.1.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A=="], "three": ["three@0.182.0", "", {}, "sha512-GbHabT+Irv+ihI1/f5kIIsZ+Ef9Sl5A1Y7imvS5RQjWgtTPfPnZ43JmlYI7NtCRDK9zir20lQpfg8/9Yd02OvQ=="], @@ -7604,8 +7599,6 @@ "@next/eslint-plugin-next/fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="], - "@next/third-parties/next": ["next@15.5.9", "", { "dependencies": { "@next/env": "15.5.9", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.7", "@next/swc-darwin-x64": "15.5.7", "@next/swc-linux-arm64-gnu": "15.5.7", "@next/swc-linux-arm64-musl": "15.5.7", "@next/swc-linux-x64-gnu": "15.5.7", "@next/swc-linux-x64-musl": "15.5.7", "@next/swc-win32-arm64-msvc": "15.5.7", "@next/swc-win32-x64-msvc": "15.5.7", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-agNLK89seZEtC5zUHwtut0+tNrc0Xw4FT/Dg+B/VLEo9pAcS9rtTKpek3V6kVcVwsB2YlqMaHdfZL4eLEVYuCg=="], - "@novu/api/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.25.1", "", { "dependencies": { "@hono/node-server": "^1.19.7", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "jose": "^6.1.1", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.0" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-yO28oVFFC7EBoiKdAn+VqRm+plcfv4v0xp6osG/VsCB0NlPZWi87ajbCZZ8f/RvOFLEu7//rSRmuZZ7lMoe3gQ=="], "@novu/api/zod": ["zod@4.2.1", "", {}, "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw=="], @@ -10056,26 +10049,6 @@ "@next/eslint-plugin-next/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - "@next/third-parties/next/@next/env": ["@next/env@15.5.9", "", {}, "sha512-4GlTZ+EJM7WaW2HEZcyU317tIQDjkQIyENDLxYJfSWlfqguN+dHkZgyQTV/7ykvobU7yEH5gKvreNrH4B6QgIg=="], - - "@next/third-parties/next/@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.5.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw=="], - - "@next/third-parties/next/@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.5.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg=="], - - "@next/third-parties/next/@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.5.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA=="], - - "@next/third-parties/next/@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.5.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw=="], - - "@next/third-parties/next/@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.5.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw=="], - - "@next/third-parties/next/@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.5.7", "", { "os": "linux", "cpu": "x64" }, "sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA=="], - - "@next/third-parties/next/@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.5.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ=="], - - "@next/third-parties/next/@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.5.7", "", { "os": "win32", "cpu": "x64" }, "sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw=="], - - "@next/third-parties/next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], - "@novu/api/@modelcontextprotocol/sdk/@hono/node-server": ["@hono/node-server@1.19.7", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw=="], "@novu/api/@modelcontextprotocol/sdk/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], @@ -11822,8 +11795,6 @@ "@nestjs/schematics/@angular-devkit/schematics/ora/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "@next/third-parties/next/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - "@novu/api/@modelcontextprotocol/sdk/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], "@novu/api/@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], diff --git a/packages/docs/self-hosting/docker.mdx b/packages/docs/self-hosting/docker.mdx index 03830306cb..99feac26f0 100644 --- a/packages/docs/self-hosting/docker.mdx +++ b/packages/docs/self-hosting/docker.mdx @@ -100,10 +100,6 @@ The Dockerfile defines these build args for the app: - `NEXT_PUBLIC_POSTHOG_KEY` - `NEXT_PUBLIC_POSTHOG_HOST` - `NEXT_PUBLIC_IS_DUB_ENABLED` -- `NEXT_PUBLIC_GTM_ID` -- `NEXT_PUBLIC_LINKEDIN_PARTNER_ID` -- `NEXT_PUBLIC_LINKEDIN_CONVERSION_ID` -- `NEXT_PUBLIC_GOOGLE_ADS_CONVERSION_LABEL` - `NEXT_PUBLIC_API_URL` For portal, only `NEXT_PUBLIC_BETTER_AUTH_URL` is used at build time. diff --git a/packages/docs/self-hosting/env-reference.mdx b/packages/docs/self-hosting/env-reference.mdx index 3ddf5d6e3c..218c010728 100644 --- a/packages/docs/self-hosting/env-reference.mdx +++ b/packages/docs/self-hosting/env-reference.mdx @@ -108,12 +108,6 @@ AWS S3 variables are required for file upload features (attachments, logos, ques |------|-----------|-------|----------|-------| | `NEXT_PUBLIC_POSTHOG_KEY` | app, portal | build | optional | PostHog project key | | `NEXT_PUBLIC_POSTHOG_HOST` | app, portal | build | optional | PostHog host (e.g., `/ingest`) | -| `NEXT_PUBLIC_GTM_ID` | app | build | optional | Google Tag Manager container ID | -| `NEXT_PUBLIC_LINKEDIN_PARTNER_ID` | app | build | optional | LinkedIn Insight Tag partner ID | -| `NEXT_PUBLIC_LINKEDIN_CONVERSION_ID` | app | build | optional | LinkedIn conversion ID | -| `NEXT_PUBLIC_GOOGLE_ADS_CONVERSION_LABEL` | app | build | optional | Google Ads conversion label | -| `GA4_API_SECRET` | app | runtime | optional | GA4 Measurement Protocol secret | -| `GA4_MEASUREMENT_ID` | app | runtime | optional | GA4 Measurement ID | ### Vercel (Trust Portal Domains)