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",