From b0aa9db26d72ad9ce3d0a377ccd8c7cfd5ccfa19 Mon Sep 17 00:00:00 2001 From: Luke Vella Date: Tue, 24 Jan 2023 14:04:13 +0000 Subject: [PATCH] Update analytics (#432) --- declarations/environment.d.ts | 3 +- package.json | 2 +- src/components/create-poll.tsx | 12 ++---- src/components/discussion/discussion.tsx | 10 ++--- src/components/dropdown.tsx | 19 +++++++--- .../month-calendar/month-calendar.tsx | 4 -- src/components/login-form.tsx | 5 +-- src/components/poll.tsx | 9 ++--- .../poll/manage-poll/delete-poll-form.tsx | 6 +-- src/components/poll/mutations.ts | 27 ++++++------- src/components/poll/notifications-toggle.tsx | 9 +---- src/components/preferences.tsx | 22 ----------- src/components/profile/user-details.tsx | 4 +- src/components/user-provider.tsx | 38 ++++++++++++++++++- src/pages/_app.tsx | 11 +----- src/pages/demo.tsx | 13 ++++--- src/pages/login.tsx | 5 +-- yarn.lock | 29 +++++++++++--- 18 files changed, 122 insertions(+), 106 deletions(-) diff --git a/declarations/environment.d.ts b/declarations/environment.d.ts index 7d98e8fae..3a55989da 100644 --- a/declarations/environment.d.ts +++ b/declarations/environment.d.ts @@ -6,7 +6,8 @@ declare global { SECRET_PASSWORD: string; NEXT_PUBLIC_LEGACY_POLLS?: string; NEXT_PUBLIC_MAINTENANCE_MODE?: string; - PLAUSIBLE_DOMAIN?: string; + NEXT_PUBLIC_POSTHOG_API_KEY?: string; + NEXT_PUBLIC_POSTHOG_API_HOST?: string; NEXT_PUBLIC_CRISP_WEBSITE_ID?: string; LEGACY_MONGODB_URI?: string; SUPPORT_EMAIL: string; diff --git a/package.json b/package.json index d6bba7171..d68e2ad1d 100644 --- a/package.json +++ b/package.json @@ -49,10 +49,10 @@ "nanoid": "^3.1.30", "next": "^13.1.3", "next-i18next": "^12.1.0", - "next-plausible": "^3.6.4", "next-seo": "^5.15.0", "nodemailer": "^6.7.2", "postcss": "^8.4.21", + "posthog-js": "^1.40.2", "prisma": "^4.9.0", "react": "^18.2.0", "react-big-calendar": "^1.5.0", diff --git a/src/components/create-poll.tsx b/src/components/create-poll.tsx index ede7a3be8..1f972d46a 100644 --- a/src/components/create-poll.tsx +++ b/src/components/create-poll.tsx @@ -2,7 +2,7 @@ import { NextPage } from "next"; import Head from "next/head"; import { useRouter } from "next/router"; import { useTranslation } from "next-i18next"; -import { usePlausible } from "next-plausible"; +import posthog from "posthog-js"; import React from "react"; import { useSessionStorage } from "react-use"; @@ -92,16 +92,12 @@ const Page: NextPage = ({ const [isRedirecting, setIsRedirecting] = React.useState(false); - const plausible = usePlausible(); - const createPoll = trpc.useMutation(["polls.create"], { onSuccess: (res) => { setIsRedirecting(true); - plausible("Created poll", { - props: { - numberOfOptions: formData.options?.options?.length, - optionsView: formData?.options?.view, - }, + posthog.capture("created poll", { + numberOfOptions: formData.options?.options?.length, + optionsView: formData?.options?.view, }); setPersistedFormData(initialNewEventData); router.replace(`/admin/${res.urlId}?sharing=true`); diff --git a/src/components/discussion/discussion.tsx b/src/components/discussion/discussion.tsx index b61220196..81563b8ff 100644 --- a/src/components/discussion/discussion.tsx +++ b/src/components/discussion/discussion.tsx @@ -1,7 +1,7 @@ import clsx from "clsx"; import { AnimatePresence, motion } from "framer-motion"; import { useTranslation } from "next-i18next"; -import { usePlausible } from "next-plausible"; +import posthog from "posthog-js"; import * as React from "react"; import { Controller, useForm } from "react-hook-form"; @@ -39,18 +39,16 @@ const Discussion: React.VoidFunctionComponent = () => { }, ); - const plausible = usePlausible(); - const addComment = trpc.useMutation("polls.comments.add", { onSuccess: (newComment) => { - session.refresh(); + posthog.capture("created comment"); + queryClient.setQueryData( ["polls.comments.list", { pollId }], (existingComments = []) => { return [...existingComments, newComment]; }, ); - plausible("Created comment"); }, }); @@ -64,7 +62,7 @@ const Discussion: React.VoidFunctionComponent = () => { ); }, onSuccess: () => { - plausible("Deleted comment"); + posthog.capture("deleted comment"); }, }); diff --git a/src/components/dropdown.tsx b/src/components/dropdown.tsx index 9fc0c7daa..64fd2ad53 100644 --- a/src/components/dropdown.tsx +++ b/src/components/dropdown.tsx @@ -83,13 +83,20 @@ const Dropdown: React.VoidFunctionComponent = ({ ); }; -const AnchorLink: React.VoidFunctionComponent<{ - href?: string; - children?: React.ReactNode; - className?: string; -}> = ({ href = "", className, children, ...forwardProps }) => { +const AnchorLink = React.forwardRef< + HTMLAnchorElement, + { + href?: string; + children?: React.ReactNode; + className?: string; + } +>(function AnchorLink( + { href = "", className, children, ...forwardProps }, + ref, +) { return ( ); -}; +}); export const DropdownItem: React.VoidFunctionComponent<{ icon?: React.ComponentType<{ className?: string }>; diff --git a/src/components/forms/poll-options-form/month-calendar/month-calendar.tsx b/src/components/forms/poll-options-form/month-calendar/month-calendar.tsx index bda8d3c9c..f6bc1f73f 100644 --- a/src/components/forms/poll-options-form/month-calendar/month-calendar.tsx +++ b/src/components/forms/poll-options-form/month-calendar/month-calendar.tsx @@ -1,6 +1,5 @@ import clsx from "clsx"; import { useTranslation } from "next-i18next"; -import { usePlausible } from "next-plausible"; import * as React from "react"; import { @@ -40,8 +39,6 @@ const MonthCalendar: React.VoidFunctionComponent = ({ const { t } = useTranslation("app"); const isTimedEvent = options.some((option) => option.type === "timeSlot"); - const plausible = usePlausible(); - const optionsByDay = React.useMemo(() => { const res: Record< string, @@ -355,7 +352,6 @@ const MonthCalendar: React.VoidFunctionComponent = ({ disabled={datepicker.selection.length < 2} label={t("applyToAllDates")} onClick={() => { - plausible("Applied options to all dates"); const times = optionsForDay.map( ({ option }) => { if (option.type === "date") { diff --git a/src/components/login-form.tsx b/src/components/login-form.tsx index 72991206f..828e77bc3 100644 --- a/src/components/login-form.tsx +++ b/src/components/login-form.tsx @@ -1,7 +1,7 @@ import clsx from "clsx"; import { useRouter } from "next/router"; import { useTranslation } from "next-i18next"; -import { usePlausible } from "next-plausible"; +import posthog from "posthog-js"; import * as React from "react"; import { useForm } from "react-hook-form"; @@ -18,7 +18,6 @@ const LoginForm: React.VoidFunctionComponent = () => { const login = trpc.useMutation(["login"]); - const plausible = usePlausible(); const router = useRouter(); return (
@@ -32,7 +31,7 @@ const LoginForm: React.VoidFunctionComponent = () => { {!formState.isSubmitSuccessful ? (
{ - plausible("Login requested"); + posthog.capture("login requested", { email }); await login.mutateAsync({ email, path: router.asPath }); })} > diff --git a/src/components/poll.tsx b/src/components/poll.tsx index a50e0eec4..ab5d71893 100644 --- a/src/components/poll.tsx +++ b/src/components/poll.tsx @@ -3,7 +3,7 @@ import { NextPage } from "next"; import Head from "next/head"; import { useRouter } from "next/router"; import { useTranslation } from "next-i18next"; -import { usePlausible } from "next-plausible"; +import posthog from "posthog-js"; import React from "react"; import toast from "react-hot-toast"; import { useMount } from "react-use"; @@ -43,7 +43,6 @@ const PollPage: NextPage = () => { const session = useUser(); const queryClient = trpc.useContext(); - const plausible = usePlausible(); const { mutate: updatePollMutation } = useUpdatePollMutation(); @@ -55,7 +54,7 @@ const PollPage: NextPage = () => { verified: true, }); session.refresh(); - plausible("Verified email"); + posthog.capture("verified email"); }, onError: () => { toast.error(t("linkHasExpired")); @@ -81,7 +80,7 @@ const PollPage: NextPage = () => { { onSuccess: () => { toast.success(t("notificationsDisabled")); - plausible("Unsubscribed from notifications"); + posthog.capture("unsubscribed from notifications"); }, }, ); @@ -89,7 +88,7 @@ const PollPage: NextPage = () => { shallow: true, }); } - }, [plausible, urlId, router, updatePollMutation, t]); + }, [urlId, router, updatePollMutation, t]); const checkIfWideScreen = () => window.innerWidth > 640; diff --git a/src/components/poll/manage-poll/delete-poll-form.tsx b/src/components/poll/manage-poll/delete-poll-form.tsx index 060e53706..8402a8538 100644 --- a/src/components/poll/manage-poll/delete-poll-form.tsx +++ b/src/components/poll/manage-poll/delete-poll-form.tsx @@ -1,6 +1,6 @@ import clsx from "clsx"; import { Trans, useTranslation } from "next-i18next"; -import { usePlausible } from "next-plausible"; +import posthog from "posthog-js"; import * as React from "react"; import { useForm } from "react-hook-form"; @@ -19,13 +19,11 @@ export const DeletePollForm: React.VoidFunctionComponent<{ const { register, handleSubmit, formState, watch } = useForm<{ confirmation: string }>(); - const plausible = usePlausible(); - const confirmationText = watch("confirmation"); const canDelete = confirmationText === confirmText; const deletePoll = trpc.useMutation("polls.delete", { onSuccess: () => { - plausible("Deleted poll"); + posthog.capture("deleted poll"); }, }); diff --git a/src/components/poll/mutations.ts b/src/components/poll/mutations.ts index 48409153e..19e8c86bd 100644 --- a/src/components/poll/mutations.ts +++ b/src/components/poll/mutations.ts @@ -1,8 +1,7 @@ -import { usePlausible } from "next-plausible"; +import posthog from "posthog-js"; import { trpc } from "../../utils/trpc"; import { usePoll } from "../poll-context"; -import { useUser } from "../user-provider"; import { ParticipantForm } from "./types"; export const normalizeVotes = ( @@ -17,12 +16,12 @@ export const normalizeVotes = ( export const useAddParticipantMutation = () => { const queryClient = trpc.useContext(); - const session = useUser(); - const plausible = usePlausible(); return trpc.useMutation(["polls.participants.add"], { onSuccess: (participant) => { - plausible("Add participant"); + posthog.capture("add participant", { + name: participant.name, + }); queryClient.setQueryData( ["polls.participants.list", { pollId: participant.pollId }], (existingParticipants = []) => { @@ -33,17 +32,17 @@ export const useAddParticipantMutation = () => { "polls.participants.list", { pollId: participant.pollId }, ]); - session.refresh(); }, }); }; export const useUpdateParticipantMutation = () => { const queryClient = trpc.useContext(); - const plausible = usePlausible(); return trpc.useMutation("polls.participants.update", { onSuccess: (participant) => { - plausible("Update participant"); + posthog.capture("update participant", { + name: participant.name, + }); queryClient.setQueryData( ["polls.participants.list", { pollId: participant.pollId }], (existingParticipants = []) => { @@ -66,7 +65,6 @@ export const useUpdateParticipantMutation = () => { export const useDeleteParticipantMutation = () => { const queryClient = trpc.useContext(); - const plausible = usePlausible(); return trpc.useMutation("polls.participants.delete", { onMutate: ({ participantId, pollId }) => { queryClient.setQueryData( @@ -76,20 +74,23 @@ export const useDeleteParticipantMutation = () => { }, ); }, - onSuccess: () => { - plausible("Remove participant"); + onSuccess: (_, { participantId }) => { + posthog.capture("remove participant", { + participantId, + }); }, }); }; export const useUpdatePollMutation = () => { const { urlId, admin } = usePoll(); - const plausible = usePlausible(); const queryClient = trpc.useContext(); return trpc.useMutation(["polls.update"], { onSuccess: (data) => { queryClient.setQueryData(["polls.get", { urlId, admin }], data); - plausible("Updated poll"); + posthog.capture("updated poll", { + id: data.id, + }); }, }); }; diff --git a/src/components/poll/notifications-toggle.tsx b/src/components/poll/notifications-toggle.tsx index dc6faa70c..71359d2aa 100644 --- a/src/components/poll/notifications-toggle.tsx +++ b/src/components/poll/notifications-toggle.tsx @@ -1,5 +1,4 @@ import { Trans, useTranslation } from "next-i18next"; -import { usePlausible } from "next-plausible"; import * as React from "react"; import { Button } from "@/components/button"; @@ -18,7 +17,6 @@ const NotificationsToggle: React.VoidFunctionComponent = () => { const { mutate: updatePollMutation } = useUpdatePollMutation(); - const plausible = usePlausible(); return ( { notifications: !poll.notifications, }, { - onSuccess: ({ notifications }) => { - plausible( - notifications - ? "Turned notifications on" - : "Turned notifications off", - ); + onSuccess: () => { setIsUpdatingNotifications(false); }, }, diff --git a/src/components/preferences.tsx b/src/components/preferences.tsx index 1907a03ab..73a639e81 100644 --- a/src/components/preferences.tsx +++ b/src/components/preferences.tsx @@ -1,7 +1,6 @@ import clsx from "clsx"; import { useRouter } from "next/router"; import { useTranslation } from "next-i18next"; -import { usePlausible } from "next-plausible"; import React from "react"; import { useDayjs } from "../utils/dayjs"; @@ -14,7 +13,6 @@ const Preferences: React.VoidFunctionComponent = () => { useDayjs(); const router = useRouter(); - const plausible = usePlausible(); return (
@@ -36,11 +34,6 @@ const Preferences: React.VoidFunctionComponent = () => { })} onClick={() => { setWeekStartsOn("monday"); - plausible("Change week start", { - props: { - timeFormat: "monday", - }, - }); }} type="button" > @@ -52,11 +45,6 @@ const Preferences: React.VoidFunctionComponent = () => { })} onClick={() => { setWeekStartsOn("sunday"); - plausible("Change week start", { - props: { - timeFormat: "sunday", - }, - }); }} type="button" > @@ -76,11 +64,6 @@ const Preferences: React.VoidFunctionComponent = () => { })} onClick={() => { setTimeFormat("12h"); - plausible("Change time format", { - props: { - timeFormat: "12h", - }, - }); }} type="button" > @@ -92,11 +75,6 @@ const Preferences: React.VoidFunctionComponent = () => { })} onClick={() => { setTimeFormat("24h"); - plausible("Change time format", { - props: { - timeFormat: "24h", - }, - }); }} type="button" > diff --git a/src/components/profile/user-details.tsx b/src/components/profile/user-details.tsx index fdad067b5..90b295293 100644 --- a/src/components/profile/user-details.tsx +++ b/src/components/profile/user-details.tsx @@ -1,5 +1,6 @@ import { motion } from "framer-motion"; import { useTranslation } from "next-i18next"; +import posthog from "posthog-js"; import * as React from "react"; import { useForm } from "react-hook-form"; @@ -33,7 +34,8 @@ export const UserDetails: React.VoidFunctionComponent = ({ const { refresh } = useUser(); const changeName = trpc.useMutation("user.changeName", { - onSuccess: () => { + onSuccess: (_, { name }) => { + posthog.people.set({ name }); refresh(); }, }); diff --git a/src/components/user-provider.tsx b/src/components/user-provider.tsx index 024f65bf6..0f8e8fca2 100644 --- a/src/components/user-provider.tsx +++ b/src/components/user-provider.tsx @@ -1,5 +1,7 @@ import { useTranslation } from "next-i18next"; +import posthog from "posthog-js"; import React from "react"; +import { useMount } from "react-use"; import { UserSession } from "@/utils/auth"; @@ -49,12 +51,44 @@ export const UserProvider = (props: { children?: React.ReactNode }) => { const { t } = useTranslation("app"); const { data: user, refetch } = trpcNext.whoami.get.useQuery(); - const logout = trpcNext.whoami.destroy.useMutation(); + + const logout = trpcNext.whoami.destroy.useMutation({ + onSuccess: () => { + posthog.reset(); + }, + }); + + useMount(() => { + if (!process.env.NEXT_PUBLIC_POSTHOG_API_KEY) { + return; + } + + posthog.init(process.env.NEXT_PUBLIC_POSTHOG_API_KEY, { + api_host: process.env.NEXT_PUBLIC_POSTHOG_API_HOST, + opt_out_capturing_by_default: false, + capture_pageview: false, + capture_pageleave: false, + autocapture: false, + loaded: (posthog) => { + if (process.env.NODE_ENV === "development") { + posthog.opt_out_capturing(); + } + if (user && posthog.get_distinct_id() !== user.id) { + posthog.identify( + user.id, + !user.isGuest + ? { email: user.email, name: user.name } + : { name: user.id }, + ); + } + }, + }); + }); const shortName = user ? user.isGuest === false ? user.name - : `${t("guest")}-${user.id.substring(user.id.length - 4)}` + : user.id.substring(0, 10) : t("guest"); if (!user) { diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index f615b947c..e52a5f275 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -8,7 +8,6 @@ import { NextPage } from "next"; import { AppProps } from "next/app"; import Head from "next/head"; import { appWithTranslation } from "next-i18next"; -import PlausibleProvider from "next-plausible"; import { DefaultSeo } from "next-seo"; import React from "react"; import { Toaster } from "react-hot-toast"; @@ -44,13 +43,7 @@ const MyApp: NextPage = ({ Component, pageProps }) => { } return ( - + <> = ({ Component, pageProps }) => { } `} - + ); }; diff --git a/src/pages/demo.tsx b/src/pages/demo.tsx index 83cec3266..e1f84d94c 100644 --- a/src/pages/demo.tsx +++ b/src/pages/demo.tsx @@ -1,11 +1,13 @@ import { NextPage } from "next"; import { useRouter } from "next/router"; import { useTranslation } from "next-i18next"; -import { usePlausible } from "next-plausible"; +import posthog from "posthog-js"; import React from "react"; import { useMount } from "react-use"; import FullPageLoader from "../components/full-page-loader"; +import { withSession } from "../components/user-provider"; +import { withSessionSsr } from "../utils/auth"; import { trpc } from "../utils/trpc"; import { withPageTranslations } from "../utils/with-page-translations"; @@ -13,18 +15,19 @@ const Demo: NextPage = () => { const { t } = useTranslation("app"); const router = useRouter(); - const plausible = usePlausible(); const createDemo = trpc.useMutation(["polls.demo.create"]); useMount(async () => { const urlId = await createDemo.mutateAsync(); - plausible("Create demo poll"); + posthog.capture("create demo poll"); router.replace(`/admin/${urlId}`); }); return {t("creatingDemo")}; }; -export const getServerSideProps = withPageTranslations(["common", "app"]); +export const getServerSideProps = withSessionSsr( + withPageTranslations(["common", "app"]), +); -export default Demo; +export default withSession(Demo); diff --git a/src/pages/login.tsx b/src/pages/login.tsx index c8532b301..931ff9a58 100644 --- a/src/pages/login.tsx +++ b/src/pages/login.tsx @@ -1,7 +1,7 @@ import { GetServerSideProps, NextPage } from "next"; import Head from "next/head"; import { useRouter } from "next/router"; -import { usePlausible } from "next-plausible"; +import posthog from "posthog-js"; import React from "react"; import toast from "react-hot-toast"; import { useTimeoutFn } from "react-use"; @@ -20,14 +20,13 @@ const Page: NextPage<{ success: boolean; redirectTo: string }> = ({ redirectTo, }) => { const router = useRouter(); - const pluasible = usePlausible(); if (!success) { toast.error("Login failed! Link is expired or invalid"); } useTimeoutFn(() => { if (success) { - pluasible("Login completed"); + posthog.capture("login completed"); } router.replace(redirectTo); }, 100); diff --git a/yarn.lock b/yarn.lock index 5418b68ad..a06b14d4a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1677,6 +1677,11 @@ "@sentry/utils" "7.32.1" tslib "^1.9.3" +"@sentry/types@7.22.0": + version "7.22.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.22.0.tgz#58e4ce77b523048e0f31e2ea4b597946d76f6079" + integrity sha512-LhCL+wb1Jch+OesB2CIt6xpfO1Ab6CRvoNYRRzVumWPLns1T3ZJkarYfhbLaOEIb38EIbPgREdxn2AJT560U4Q== + "@sentry/types@7.32.1": version "7.32.1" resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.32.1.tgz#24728cf098694d31ceb4f556164674477c6433d6" @@ -3482,6 +3487,11 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +fflate@^0.4.1: + version "0.4.8" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae" + integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA== + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" @@ -4504,11 +4514,6 @@ next-i18next@^12.1.0: i18next-fs-backend "^1.1.5" react-i18next "^11.18.4" -next-plausible@^3.6.4: - version "3.6.4" - resolved "https://registry.yarnpkg.com/next-plausible/-/next-plausible-3.6.4.tgz#15684340736e47d3ffa49f9c8fae598e3cfefca6" - integrity sha512-aHL4IL+gkkjs5ScB18LZ3LMEWXKR5VTvnMDs/fHNFHadOh23i37fD+VP5oAQLALR8Mde63l9JJljrw9DkvjWjQ== - next-seo@^5.15.0: version "5.15.0" resolved "https://registry.yarnpkg.com/next-seo/-/next-seo-5.15.0.tgz#b1a90508599774982909ea44803323c6fb7b50f4" @@ -4925,6 +4930,15 @@ postcss@^8.4.21: picocolors "^1.0.0" source-map-js "^1.0.2" +posthog-js@^1.40.2: + version "1.40.2" + resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.40.2.tgz#f9658f532653789287015f7df9f61988949e08e9" + integrity sha512-jVq/g5J/339u2uHulfpTR57iyG8xmbSKpB9xtbEqp5gQpr/eu0P4OAdfye12tsjm8IB3+7RR/SDArQ7PI/m6Yg== + dependencies: + "@sentry/types" "7.22.0" + fflate "^0.4.1" + rrweb-snapshot "^1.1.14" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" @@ -5330,6 +5344,11 @@ rollup@2.78.0: optionalDependencies: fsevents "~2.3.2" +rrweb-snapshot@^1.1.14: + version "1.1.14" + resolved "https://registry.yarnpkg.com/rrweb-snapshot/-/rrweb-snapshot-1.1.14.tgz#9d4d9be54a28a893373428ee4393ec7e5bd83fcc" + integrity sha512-eP5pirNjP5+GewQfcOQY4uBiDnpqxNRc65yKPW0eSoU1XamDfc4M8oqpXGMyUyvLyxFDB0q0+DChuxxiU2FXBQ== + rtl-css-js@^1.14.0: version "1.15.0" resolved "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.15.0.tgz"