From e47094fbf4f92c5bdd92ab20dd4c65a12955ce87 Mon Sep 17 00:00:00 2001 From: Nevo David Date: Wed, 17 Jul 2024 18:05:44 +0700 Subject: [PATCH] feat: plausible to dashboard --- apps/frontend/src/app/layout.tsx | 13 +++-- .../frontend/src/components/auth/register.tsx | 4 ++ .../components/billing/billing.component.tsx | 12 ++++- .../src/components/billing/lifetime.deal.tsx | 3 ++ .../launches/launches.component.tsx | 5 ++ .../helpers/src/utils/use.fire.events.ts | 9 ++++ libraries/helpers/src/utils/utm.saver.tsx | 47 +++++++++++++++++++ package-lock.json | 14 ++++++ package.json | 1 + 9 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 libraries/helpers/src/utils/use.fire.events.ts create mode 100644 libraries/helpers/src/utils/utm.saver.tsx diff --git a/apps/frontend/src/app/layout.tsx b/apps/frontend/src/app/layout.tsx index c1c44d5a..09161dcd 100644 --- a/apps/frontend/src/app/layout.tsx +++ b/apps/frontend/src/app/layout.tsx @@ -3,12 +3,13 @@ import interClass from '@gitroom/react/helpers/inter.font'; export const dynamic = 'force-dynamic'; import './global.css'; import 'react-tooltip/dist/react-tooltip.css'; -import "@copilotkit/react-ui/styles.css"; +import '@copilotkit/react-ui/styles.css'; import LayoutContext from '@gitroom/frontend/components/layout/layout.context'; import { ReactNode } from 'react'; import { Chakra_Petch } from 'next/font/google'; import { isGeneral } from '@gitroom/react/helpers/is.general'; +import PlausibleProvider from 'next-plausible'; const chakra = Chakra_Petch({ weight: '400', subsets: ['latin'] }); @@ -16,10 +17,16 @@ export default async function AppLayout({ children }: { children: ReactNode }) { return ( - + - {children} + + {children} + ); diff --git a/apps/frontend/src/components/auth/register.tsx b/apps/frontend/src/components/auth/register.tsx index c20455c0..7c46eccc 100644 --- a/apps/frontend/src/components/auth/register.tsx +++ b/apps/frontend/src/components/auth/register.tsx @@ -15,6 +15,7 @@ import interClass from '@gitroom/react/helpers/inter.font'; import { isGeneral } from '@gitroom/react/helpers/is.general'; import clsx from 'clsx'; import { GoogleProvider } from '@gitroom/frontend/components/auth/providers/google.provider'; +import { useFireEvents } from '@gitroom/helpers/utils/use.fire.events'; type Inputs = { email: string; @@ -74,6 +75,7 @@ export function RegisterAfter({ const [loading, setLoading] = useState(false); const getQuery = useSearchParams(); const router = useRouter(); + const fireEvents = useFireEvents(); const isAfterProvider = useMemo(() => { return !!token && !!provider; @@ -107,6 +109,8 @@ export function RegisterAfter({ setLoading(false); } + fireEvents('register'); + if (register.headers.get('activate')) { router.push('/auth/activate'); } diff --git a/apps/frontend/src/components/billing/billing.component.tsx b/apps/frontend/src/components/billing/billing.component.tsx index 7150f142..b726bb4b 100644 --- a/apps/frontend/src/components/billing/billing.component.tsx +++ b/apps/frontend/src/components/billing/billing.component.tsx @@ -1,13 +1,23 @@ 'use client'; -import { useCallback } from 'react'; +import { useCallback, useEffect } from 'react'; import useSWR from 'swr'; import { LoadingComponent } from '@gitroom/frontend/components/layout/loading'; import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; import { MainBillingComponent } from './main.billing.component'; +import { useSearchParams } from 'next/navigation'; +import { useFireEvents } from '@gitroom/helpers/utils/use.fire.events'; export const BillingComponent = () => { const fetch = useFetch(); + const searchParams = useSearchParams(); + const fireEvents = useFireEvents(); + + useEffect(() => { + if (searchParams.get('check')) { + fireEvents('purchase'); + } + }, []); const load = useCallback(async (path: string) => { return await (await fetch(path)).json(); diff --git a/apps/frontend/src/components/billing/lifetime.deal.tsx b/apps/frontend/src/components/billing/lifetime.deal.tsx index 698c0fc2..81bf515d 100644 --- a/apps/frontend/src/components/billing/lifetime.deal.tsx +++ b/apps/frontend/src/components/billing/lifetime.deal.tsx @@ -9,6 +9,7 @@ import { Button } from '@gitroom/react/form/button'; import { useSWRConfig } from 'swr'; import { useToaster } from '@gitroom/react/toaster/toaster'; import { useRouter } from 'next/navigation'; +import { useFireEvents } from '@gitroom/helpers/utils/use.fire.events'; export const LifetimeDeal = () => { const fetch = useFetch(); @@ -17,6 +18,7 @@ export const LifetimeDeal = () => { const toast = useToaster(); const { mutate } = useSWRConfig(); const router = useRouter(); + const fireEvents = useFireEvents(); const claim = useCallback(async () => { const { success } = await ( @@ -32,6 +34,7 @@ export const LifetimeDeal = () => { if (success) { mutate('/user/self'); toast.show('Successfully claimed the code'); + fireEvents('lifetime_claimed'); } else { toast.show('Code already claimed or invalid code', 'warning'); } diff --git a/apps/frontend/src/components/launches/launches.component.tsx b/apps/frontend/src/components/launches/launches.component.tsx index d115536c..21bdc06d 100644 --- a/apps/frontend/src/components/launches/launches.component.tsx +++ b/apps/frontend/src/components/launches/launches.component.tsx @@ -18,12 +18,14 @@ import { useRouter, useSearchParams } from 'next/navigation'; import { Integration } from '@prisma/client'; import ImageWithFallback from '@gitroom/react/helpers/image.with.fallback'; import { useToaster } from '@gitroom/react/toaster/toaster'; +import { useFireEvents } from '@gitroom/helpers/utils/use.fire.events'; export const LaunchesComponent = () => { const fetch = useFetch(); const router = useRouter(); const search = useSearchParams(); const toast = useToaster(); + const fireEvents = useFireEvents(); const [reload, setReload] = useState(false); const load = useCallback(async (path: string) => { @@ -97,6 +99,9 @@ export const LaunchesComponent = () => { if (search.get('scope') === 'missing') { toast.show('You have to approve all the channel permissions', 'warning'); } + if (search.get('added')) { + fireEvents('channel_added'); + } if (window.opener) { window.close(); } diff --git a/libraries/helpers/src/utils/use.fire.events.ts b/libraries/helpers/src/utils/use.fire.events.ts new file mode 100644 index 00000000..71fcab2e --- /dev/null +++ b/libraries/helpers/src/utils/use.fire.events.ts @@ -0,0 +1,9 @@ +import {usePlausible} from 'next-plausible' +import {useCallback} from "react"; + +export const useFireEvents = () => { + const plausible = usePlausible(); + return useCallback((name: string, props?: object) => { + plausible(name, {props}); + }, []); +} \ No newline at end of file diff --git a/libraries/helpers/src/utils/utm.saver.tsx b/libraries/helpers/src/utils/utm.saver.tsx new file mode 100644 index 00000000..656ca9c6 --- /dev/null +++ b/libraries/helpers/src/utils/utm.saver.tsx @@ -0,0 +1,47 @@ +import {FC, useCallback, useEffect} from "react"; +import {useSearchParams} from "next/navigation"; + +const UtmSaver: FC = () => { + const query = useSearchParams(); + useEffect(() => { + const landingUrl = localStorage.getItem('landingUrl'); + if (landingUrl) { + return ; + } + + localStorage.setItem('landingUrl', window.location.href); + localStorage.setItem('referrer', document.referrer); + }, []); + + useEffect(() => { + const utm = query.get('utm_source') || query.get('utm'); + const utmMedium = query.get('utm_medium'); + const utmCampaign = query.get('utm_campaign'); + + if (utm) { + localStorage.setItem('utm', utm); + } + if (utmMedium) { + localStorage.setItem('utm_medium', utmMedium); + } + if (utmCampaign) { + localStorage.setItem('utm_campaign', utmCampaign); + } + }, [query]); + + return <>; +} + +export const useUtmSaver = () => { + return useCallback(() => { + return { + utm: localStorage.getItem('utm'), + utmMedium: localStorage.getItem('utm_medium'), + utmCampaign: localStorage.getItem('utm_campaign'), + landingUrl: localStorage.getItem('landingUrl'), + referrer: localStorage.getItem('referrer'), + } + }, []); +} + +export default UtmSaver; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index dbecb38a..002ae9a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -84,6 +84,7 @@ "multer": "^1.4.5-lts.1", "nestjs-command": "^3.1.4", "next": "^14.2.4", + "next-plausible": "^3.12.0", "openai": "^4.47.1", "prisma-paginate": "^5.2.1", "react": "18.2.0", @@ -29566,6 +29567,19 @@ "react-dom": ">=16.x <=18.x" } }, + "node_modules/next-plausible": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/next-plausible/-/next-plausible-3.12.0.tgz", + "integrity": "sha512-SSkEqKQ6PgR8fx3sYfIAT69k2xuCUXO5ngkSS19CjxY97lAoZxsfZpYednxB4zo0mHYv87JzhPynrdBPlCBVHg==", + "funding": { + "url": "https://github.com/4lejandrito/next-plausible?sponsor=1" + }, + "peerDependencies": { + "next": "^11.1.0 || ^12.0.0 || ^13.0.0 || ^14.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/next/node_modules/@swc/helpers": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", diff --git a/package.json b/package.json index 7a059f59..9d48549c 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "multer": "^1.4.5-lts.1", "nestjs-command": "^3.1.4", "next": "^14.2.4", + "next-plausible": "^3.12.0", "openai": "^4.47.1", "prisma-paginate": "^5.2.1", "react": "18.2.0",