diff --git a/apps/page/package.json b/apps/page/package.json index 9a141a3..b6c7c37 100644 --- a/apps/page/package.json +++ b/apps/page/package.json @@ -18,7 +18,6 @@ "@sentry/nextjs": "^7.93.0", "@supabase/supabase-js": "^2.39.2", "@tailwindcss/typography": "^0.5.1", - "@vercel/analytics": "^1.1.1", "@vercel/og": "0.0.20", "classnames": "^2.3.1", "cors": "^2.8.5", diff --git a/apps/page/pages/_app.tsx b/apps/page/pages/_app.tsx index e0e105f..57e9f9c 100644 --- a/apps/page/pages/_app.tsx +++ b/apps/page/pages/_app.tsx @@ -1,4 +1,3 @@ -import { Analytics } from "@vercel/analytics/react"; import { ThemeProvider } from "next-themes"; import type { AppProps } from "next/app"; import Head from "next/head"; @@ -16,7 +15,6 @@ function MyApp({ Component, pageProps }: AppProps) { - ); } diff --git a/apps/web/components/dialogs/manage-team-dialog.component.tsx b/apps/web/components/dialogs/manage-team-dialog.component.tsx index a63c937..2f377fe 100644 --- a/apps/web/components/dialogs/manage-team-dialog.component.tsx +++ b/apps/web/components/dialogs/manage-team-dialog.component.tsx @@ -7,7 +7,6 @@ import { Fragment, useEffect, useRef } from "react"; import { InferType } from "yup"; import { ROUTES } from "../../data/routes.data"; import { NewTeamSchema } from "../../data/schema"; -import { track } from "../../utils/analytics"; import { useUserData } from "../../utils/useUser"; import { InlineErrorMessage } from "../forms/notification.component"; @@ -66,7 +65,6 @@ export default function ManageTeamDialog({ image: values.image, }) .match({ id: team.id }); - track("EditTeam"); onSuccess(); } else { await supabase @@ -79,7 +77,6 @@ export default function ManageTeamDialog({ }, ]) .select(); - track("CreateTeam"); onSuccess(); } diff --git a/apps/web/components/forms/post-form.component.tsx b/apps/web/components/forms/post-form.component.tsx index 4f2bf64..607efd7 100644 --- a/apps/web/components/forms/post-form.component.tsx +++ b/apps/web/components/forms/post-form.component.tsx @@ -21,7 +21,6 @@ import ReactMarkdown from "react-markdown"; import { v4 } from "uuid"; import { InferType } from "yup"; import { NewPostSchema } from "../../data/schema"; -import { track } from "../../utils/analytics"; import { useUserData } from "../../utils/useUser"; import { PrimaryButton } from "../core/buttons.component"; import MarkdownEditor from "../core/editor.component"; @@ -646,7 +645,6 @@ export default function PostFormComponent({ formik.setFieldValue("publication_date", publication_date); formik.setFieldValue("status", PostStatus.published); setCustomPublishDate(false); - track("AddCustomPublishDate"); }} /> @@ -667,7 +665,6 @@ export default function PostFormComponent({ } setPromptSchedule(false); - track("SchedulePost"); }} /> @@ -678,7 +675,6 @@ export default function PostFormComponent({ confirmCallback={(title: string) => { formik.setFieldValue("title", title); setPromptTitleSuggestions(false); - track("AiSuggestTitle"); }} /> @@ -687,11 +683,9 @@ export default function PostFormComponent({ open={promptExpandConcept} setOpen={(open: boolean) => { setPromptExpandConcept(open); - track("AiExpandConcept"); }} insertContentCallback={(content: string) => { setPromptExpandConcept(false); - track("AiExpandConceptCopyToPost"); formik.setFieldValue( "content", `${formik.values.content}\n\n${content}` @@ -704,7 +698,6 @@ export default function PostFormComponent({ open={promptProofRead} setOpen={(open: boolean) => { setPromptProofRead(open); - track("AiProofRead"); }} /> diff --git a/apps/web/next.config.js b/apps/web/next.config.js index 7b53dc7..5b5de94 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -69,6 +69,25 @@ const moduleExports = { typescript: { ignoreBuildErrors: true, }, + // PostHog rewrites for ingest and static assets + async rewrites() { + return [ + { + source: "/ingest/static/:path*", + destination: "https://us-assets.i.posthog.com/static/:path*", + }, + { + source: "/ingest/:path*", + destination: "https://us.i.posthog.com/:path*", + }, + { + source: "/ingest/decide", + destination: "https://us.i.posthog.com/decide", + }, + ]; + }, + // This is required to support PostHog trailing slash API requests + skipTrailingSlashRedirect: true, }; // ensure that your source maps include changes from all other Webpack plugins diff --git a/apps/web/package.json b/apps/web/package.json index 0e8a516..a9138ae 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -28,7 +28,6 @@ "@supabase/supabase-js": "^2.39.3", "@tailwindcss/typography": "^0.5.1", "@types/canvas-confetti": "^1.6.4", - "@vercel/analytics": "^1.0.1", "@vercel/og": "^0.0.20", "canvas-confetti": "^1.9.3", "chrono-node": "^2.7.6", @@ -46,6 +45,8 @@ "next-sitemap": "^4.2.3", "nprogress": "^0.2.0", "openai": "^3.2.1", + "posthog-js": "^1.242.2", + "posthog-node": "^4.17.1", "postmark": "^3.0.15", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/apps/web/pages/_app.tsx b/apps/web/pages/_app.tsx index e3265e3..5ce57f4 100644 --- a/apps/web/pages/_app.tsx +++ b/apps/web/pages/_app.tsx @@ -1,10 +1,12 @@ import { createPagesBrowserClient } from "@supabase/auth-helpers-nextjs"; import { SessionContextProvider } from "@supabase/auth-helpers-react"; -import { Analytics } from "@vercel/analytics/react"; import dynamic from "next/dynamic"; import localFont from "next/font/local"; import Head from "next/head"; -import { useState } from "react"; +import { Router } from "next/router"; +import posthog from "posthog-js"; +import { PostHogProvider } from "posthog-js/react"; +import { useEffect, useState } from "react"; import "../styles/global.css"; import { UserContextProvider } from "../utils/useUser"; @@ -32,8 +34,24 @@ export default function App({ Component, pageProps }) { const getLayout = Component.getLayout || ((page) => page); const [supabaseClient] = useState(() => createPagesBrowserClient()); + useEffect(() => { + posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, { + api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST, + loaded: (ph) => { + if (process.env.NODE_ENV === "development") ph.debug(); + }, + debug: process.env.NODE_ENV === "development", + }); + + const handleRouteChange = () => posthog.capture("$pageview"); + Router.events.on("routeChangeComplete", handleRouteChange); + return () => { + Router.events.off("routeChangeComplete", handleRouteChange); + }; + }, []); + return ( - <> + {getLayout()} - - + ); } diff --git a/apps/web/pages/account/billing.tsx b/apps/web/pages/account/billing.tsx index d10e0d4..a3d5d98 100644 --- a/apps/web/pages/account/billing.tsx +++ b/apps/web/pages/account/billing.tsx @@ -9,7 +9,6 @@ import { notifyError, notifyInfo } from "../../components/core/toast.component"; import AuthLayout from "../../components/layout/auth-layout.component"; import Page from "../../components/layout/page.component"; import { ROUTES } from "../../data/routes.data"; -import { track } from "../../utils/analytics"; import { httpPost } from "../../utils/http"; import { useUserData } from "../../utils/useUser"; @@ -20,8 +19,6 @@ export default function Billing() { try { notifyInfo("Redirecting to billing portal..."); - track("OpenBillingPortal"); - const session = await httpPost({ url: "/api/billing/create-billing-portal", data: { diff --git a/apps/web/pages/free-tools/ai-changelog-generator.tsx b/apps/web/pages/free-tools/ai-changelog-generator.tsx index 422385b..26645f6 100644 --- a/apps/web/pages/free-tools/ai-changelog-generator.tsx +++ b/apps/web/pages/free-tools/ai-changelog-generator.tsx @@ -11,7 +11,6 @@ import { } from "../../components/core/toast.component"; import FooterComponent from "../../components/layout/footer.component"; import MarketingHeaderComponent from "../../components/marketing/marketing-header.component"; -import { track } from "../../utils/analytics"; import usePrefersColorScheme from "../../utils/hooks/usePrefersColorScheme"; import { createSignedStreamingUrl } from "../../utils/manageprompt"; @@ -35,8 +34,6 @@ export default function AIChangelogGenerator({ return; } - track("FreeTools-AiChangelogGenerator-Generate"); - setLoading(true); const response = await fetch(modelStreamUrl, { diff --git a/apps/web/pages/pages/[page_id]/new.tsx b/apps/web/pages/pages/[page_id]/new.tsx index d2515f7..2f9b6bf 100644 --- a/apps/web/pages/pages/[page_id]/new.tsx +++ b/apps/web/pages/pages/[page_id]/new.tsx @@ -11,7 +11,6 @@ import AuthLayout from "../../../components/layout/auth-layout.component"; import Page from "../../../components/layout/page.component"; import { ROUTES } from "../../../data/routes.data"; import { NewPostSchema } from "../../../data/schema"; -import { track } from "../../../utils/analytics"; import { httpPost } from "../../../utils/http"; import { createOrRetrievePageSettings } from "../../../utils/useDatabase"; @@ -49,8 +48,6 @@ export default function NewPost({ }, }); - track("PostCreated"); - if (values.status !== PostStatus.draft) { return await router.replace(`${ROUTES.PAGES}/${page_id}?yay=true`); } diff --git a/apps/web/pages/pages/new.tsx b/apps/web/pages/pages/new.tsx index aac0e0c..3d3d383 100644 --- a/apps/web/pages/pages/new.tsx +++ b/apps/web/pages/pages/new.tsx @@ -9,7 +9,6 @@ import AuthLayout from "../../components/layout/auth-layout.component"; import Page from "../../components/layout/page.component"; import { ROUTES } from "../../data/routes.data"; import { NewPageSchema } from "../../data/schema"; -import { track } from "../../utils/analytics"; import { httpPost } from "../../utils/http"; export default function NewPage() { @@ -47,10 +46,6 @@ export default function NewPage() { }, }); - track("PageCreated", { - url_slug: values.url_slug, - }); - return await router.push(ROUTES.PAGES + "?yay=true"); } catch (e) { notifyError(); diff --git a/apps/web/pages/teams/index.tsx b/apps/web/pages/teams/index.tsx index 6239d47..d6363d9 100644 --- a/apps/web/pages/teams/index.tsx +++ b/apps/web/pages/teams/index.tsx @@ -22,7 +22,6 @@ import AuthLayout from "../../components/layout/auth-layout.component"; import Page from "../../components/layout/page.component"; import Changelog from "../../components/marketing/changelog"; import MemeberDetails from "../../components/teams/memeber-details"; -import { track } from "../../utils/analytics"; import { getAppBaseURL } from "../../utils/helpers"; import { httpPost } from "../../utils/http"; import { useUserData } from "../../utils/useUser"; @@ -117,7 +116,6 @@ export default function Teams() { return; } - track("DeleteTeam"); fetchData(); setTeamToDelete(null); setIsDeleting(false); @@ -133,7 +131,6 @@ export default function Teams() { }) .match({ id: pageId }); - track("AssignPageToTeam"); fetchData(); setAssigningPage(false); setShowAssignPage(null); @@ -148,7 +145,6 @@ export default function Teams() { }) .match({ id: pageId }); - track("RemovePageFromTeam"); fetchData(); }; @@ -158,14 +154,12 @@ export default function Teams() { user_id: userId, }); - track("RemoveTeamMember"); fetchData(); }; const handleRevokeInvite = async (inviteId: string) => { await supabase.from("team_invitations").delete().match({ id: inviteId }); - track("RevokeTeamInvitation"); fetchData(); }; @@ -184,7 +178,6 @@ export default function Teams() { } ); - track("AcceptTeamInvitation"); fetchData(); }; @@ -195,7 +188,6 @@ export default function Teams() { user_id: user?.id, }); - track("LeaveTeam"); fetchData(); } }; diff --git a/apps/web/utils/analytics.ts b/apps/web/utils/analytics.ts deleted file mode 100644 index de1007b..0000000 --- a/apps/web/utils/analytics.ts +++ /dev/null @@ -1,3 +0,0 @@ -import va from "@vercel/analytics"; - -export const track = va.track; diff --git a/apps/web/utils/hooks/usePageSettings.ts b/apps/web/utils/hooks/usePageSettings.ts index c759d7e..cd1a96d 100644 --- a/apps/web/utils/hooks/usePageSettings.ts +++ b/apps/web/utils/hooks/usePageSettings.ts @@ -4,7 +4,6 @@ import { notifyError, notifySuccess, } from "../../components/core/toast.component"; -import { track } from "../analytics"; import { httpGet } from "../http"; import { useUserData } from "../useUser"; @@ -33,8 +32,6 @@ export default function usePageSettings(pageId: string, prefetch = true) { setSettings(settings[0]); } - track("UpdatePageSettings"); - notifySuccess("Page updated!"); } catch (e) { console.error(e); diff --git a/apps/web/utils/posthog.ts b/apps/web/utils/posthog.ts new file mode 100644 index 0000000..cbe2bf8 --- /dev/null +++ b/apps/web/utils/posthog.ts @@ -0,0 +1,10 @@ +import { PostHog } from "posthog-node" + +export default function PostHogClient() { + const posthogClient = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY!, { + host: process.env.NEXT_PUBLIC_POSTHOG_HOST, + flushAt: 1, + flushInterval: 0, + }) + return posthogClient +} \ No newline at end of file diff --git a/apps/web/utils/useUser.tsx b/apps/web/utils/useUser.tsx index 1dbdeff..7d3762d 100644 --- a/apps/web/utils/useUser.tsx +++ b/apps/web/utils/useUser.tsx @@ -6,6 +6,7 @@ import { useUser, } from "@supabase/auth-helpers-react"; import { useRouter } from "next/router"; +import posthog from "posthog-js"; import { createContext, useCallback, @@ -65,6 +66,10 @@ export const UserContextProvider = (props: any) => { fetchBilling().then(() => { setLoading(false); }); + posthog.identify(user.id, { + email: user.email, + name: user.user_metadata?.full_name, + }); } else { setLoading(false); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0f9804b..31107d3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,9 +83,6 @@ importers: '@tailwindcss/typography': specifier: ^0.5.1 version: 0.5.16(tailwindcss@3.4.17) - '@vercel/analytics': - specifier: ^1.1.1 - version: 1.5.0(next@14.2.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) '@vercel/og': specifier: 0.0.20 version: 0.0.20 @@ -261,9 +258,6 @@ importers: '@types/canvas-confetti': specifier: ^1.6.4 version: 1.9.0 - '@vercel/analytics': - specifier: ^1.0.1 - version: 1.5.0(next@14.2.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) '@vercel/og': specifier: ^0.0.20 version: 0.0.20 @@ -315,6 +309,12 @@ importers: openai: specifier: ^3.2.1 version: 3.3.0 + posthog-js: + specifier: ^1.242.2 + version: 1.242.2 + posthog-node: + specifier: ^4.17.1 + version: 4.17.1 postmark: specifier: ^3.0.15 version: 3.11.0 @@ -2106,32 +2106,6 @@ packages: cpu: [x64] os: [win32] - '@vercel/analytics@1.5.0': - resolution: {integrity: sha512-MYsBzfPki4gthY5HnYN7jgInhAZ7Ac1cYDoRWFomwGHWEX7odTEzbtg9kf/QSo7XEsEAqlQugA6gJ2WS2DEa3g==} - peerDependencies: - '@remix-run/react': ^2 - '@sveltejs/kit': ^1 || ^2 - next: '>= 13' - react: ^18 || ^19 || ^19.0.0-rc - svelte: '>= 4' - vue: ^3 - vue-router: ^4 - peerDependenciesMeta: - '@remix-run/react': - optional: true - '@sveltejs/kit': - optional: true - next: - optional: true - react: - optional: true - svelte: - optional: true - vue: - optional: true - vue-router: - optional: true - '@vercel/og@0.0.20': resolution: {integrity: sha512-089P+TfqWz0xBxjOvOhkZIDDtfrLcye94H4IZ+SqxoGPWpNGXaBvRJER/z5SoJxJRcCAL8tPiK5zdjRskM6tLw==} engines: {node: '>=16'} @@ -2288,6 +2262,9 @@ packages: axios@0.26.1: resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==} + axios@1.9.0: + resolution: {integrity: sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==} + axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} @@ -2488,6 +2465,9 @@ packages: cookie-es@1.2.2: resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} + core-js@3.42.0: + resolution: {integrity: sha512-Sz4PP4ZA+Rq4II21qkNqOEDTDrCvcANId3xpIgB34NDkWc3UduWj2dqEtN9yZIq8Dk3HyPI33x9sqqU5C8sr0g==} + cors@2.8.5: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} @@ -2938,6 +2918,9 @@ packages: resolution: {integrity: sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==} engines: {node: '>=0.4.0'} + fflate@0.4.8: + resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==} + fflate@0.7.4: resolution: {integrity: sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==} @@ -4309,9 +4292,27 @@ packages: resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} engines: {node: ^10 || ^12 || >=14} + posthog-js@1.242.2: + resolution: {integrity: sha512-bj5Bq9Z/TE24k0fbt/VoD13xsqCybuoLp7KkWmfknEiO63+1Ln/PnX/1CjppikE9yM7toQIWY3vY7hT/RTT57Q==} + peerDependencies: + '@rrweb/types': 2.0.0-alpha.17 + rrweb-snapshot: 2.0.0-alpha.17 + peerDependenciesMeta: + '@rrweb/types': + optional: true + rrweb-snapshot: + optional: true + + posthog-node@4.17.1: + resolution: {integrity: sha512-cVlQPOwOPjakUnrueKRCQe1m2Ku+XzKaOos7Tn/zDZkkZFeBT/byP7tbNf7LiwhaBRWFBRowZZb/MsTtSRaorg==} + engines: {node: '>=15.0.0'} + postmark@3.11.0: resolution: {integrity: sha512-asguBQ9M/8ueQMJ1D45iPF+3+T641q8rAU8m8cQSfhDWePw4TVYql9wszjAwSCE93dUonyrF08D8Kvg6USBoFA==} + preact@10.26.6: + resolution: {integrity: sha512-5SRRBinwpwkaD+OqlBDeITlRgvd8I8QlxHJw9AxSdMNV6O+LodN9nUyYGpSF7sadHjs6RzeFShMexC6DbtWr9g==} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -5164,6 +5165,9 @@ packages: web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + web-vitals@4.2.4: + resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -6909,11 +6913,6 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.7.2': optional: true - '@vercel/analytics@1.5.0(next@14.2.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': - optionalDependencies: - next: 14.2.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - '@vercel/og@0.0.20': dependencies: '@resvg/resvg-wasm': 2.0.0-alpha.4 @@ -7104,6 +7103,14 @@ snapshots: transitivePeerDependencies: - debug + axios@1.9.0: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.2 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + axobject-query@4.1.0: {} bail@2.0.2: {} @@ -7293,6 +7300,8 @@ snapshots: cookie-es@1.2.2: {} + core-js@3.42.0: {} + cors@2.8.5: dependencies: object-assign: 4.1.1 @@ -7960,6 +7969,8 @@ snapshots: dependencies: xml-js: 1.6.11 + fflate@0.4.8: {} + fflate@0.7.4: {} file-entry-cache@6.0.1: @@ -9878,12 +9889,27 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + posthog-js@1.242.2: + dependencies: + core-js: 3.42.0 + fflate: 0.4.8 + preact: 10.26.6 + web-vitals: 4.2.4 + + posthog-node@4.17.1: + dependencies: + axios: 1.9.0 + transitivePeerDependencies: + - debug + postmark@3.11.0: dependencies: axios: 0.25.0 transitivePeerDependencies: - debug + preact@10.26.6: {} + prelude-ls@1.2.1: {} prettier@2.8.8: {} @@ -11006,6 +11032,8 @@ snapshots: web-namespaces@2.0.1: {} + web-vitals@4.2.4: {} + webidl-conversions@3.0.1: {} webpack-bundle-analyzer@4.7.0: