Skip to content

Commit 00fda4c

Browse files
authored
Setup PostHog and Integrate with Cookie Banner Component (#2557)
* feat: Setup posthog and integrate with cookie banner component * refactor: Improved error handling
1 parent beea49a commit 00fda4c

File tree

4 files changed

+176
-15
lines changed

4 files changed

+176
-15
lines changed

frontend/docs/components/ui/cookie-banner.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { useEffect, useState } from "react";
3+
import { useCallback, useEffect, useState } from "react";
44
import { cn } from "@/lib/utils";
55
import { Button } from "./button";
66
import { CookieIcon } from "@radix-ui/react-icons";
@@ -23,29 +23,37 @@ export default function CookieConsent({
2323
const [hide, setHide] = useState(false);
2424
const [consentGiven, setConsentGiven] = useState("");
2525

26-
const accept = () => {
26+
const accept = useCallback(() => {
2727
setIsOpen(false);
28+
// eslint-disable-next-line no-restricted-syntax
2829
document.cookie =
2930
"cookieConsent=true; expires=Fri, 31 Dec 9999 23:59:59 GMT";
3031

3132
localStorage.setItem("cookie_consent", "yes");
3233
setConsentGiven("yes");
3334

35+
// Dispatch custom event to notify other components
36+
window.dispatchEvent(new Event("cookie-consent-change"));
37+
3438
setTimeout(() => {
3539
setHide(true);
3640
}, 700);
3741
onAcceptCallback();
38-
};
42+
}, [onAcceptCallback]);
3943

40-
const decline = () => {
44+
const decline = useCallback(() => {
4145
setIsOpen(false);
4246
localStorage.setItem("cookie_consent", "no");
4347
setConsentGiven("no");
48+
49+
// Dispatch custom event to notify other components
50+
window.dispatchEvent(new Event("cookie-consent-change"));
51+
4452
setTimeout(() => {
4553
setHide(true);
4654
}, 700);
4755
onDeclineCallback();
48-
};
56+
}, [onDeclineCallback]);
4957

5058
useEffect(() => {
5159
try {
@@ -70,9 +78,9 @@ export default function CookieConsent({
7078
}
7179
}
7280
} catch (e) {
73-
// console.log("Error: ", e);
81+
console.error("Error checking cookie consent:", e);
7482
}
75-
}, []);
83+
}, [accept, demo]);
7684

7785
useEffect(() => {
7886
const consented = consentGiven === "yes";
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"use client";
2+
3+
import {
4+
createContext,
5+
useContext,
6+
useEffect,
7+
useState,
8+
type ReactNode,
9+
} from "react";
10+
11+
type ConsentStatus = "yes" | "no" | "undecided";
12+
13+
interface ConsentContextType {
14+
consentStatus: ConsentStatus;
15+
hasConsent: boolean;
16+
}
17+
18+
const ConsentContext = createContext<ConsentContextType | undefined>(undefined);
19+
20+
export function ConsentProvider({ children }: { children: ReactNode }) {
21+
const [consentStatus, setConsentStatus] =
22+
useState<ConsentStatus>("undecided");
23+
24+
useEffect(() => {
25+
const checkConsent = () => {
26+
const consent = localStorage.getItem("cookie_consent");
27+
if (consent === "yes" || consent === "no") {
28+
setConsentStatus(consent as ConsentStatus);
29+
} else {
30+
setConsentStatus("undecided");
31+
}
32+
};
33+
34+
checkConsent();
35+
36+
const handleStorageChange = (e: StorageEvent) => {
37+
if (e.key === "cookie_consent") {
38+
checkConsent();
39+
}
40+
};
41+
42+
const handleConsentChange = () => {
43+
checkConsent();
44+
};
45+
46+
window.addEventListener("storage", handleStorageChange);
47+
window.addEventListener("cookie-consent-change", handleConsentChange);
48+
49+
return () => {
50+
window.removeEventListener("storage", handleStorageChange);
51+
window.removeEventListener("cookie-consent-change", handleConsentChange);
52+
};
53+
}, []);
54+
55+
const hasConsent = consentStatus === "yes";
56+
57+
return (
58+
<ConsentContext.Provider value={{ consentStatus, hasConsent }}>
59+
{children}
60+
</ConsentContext.Provider>
61+
);
62+
}
63+
64+
export function useConsent() {
65+
const context = useContext(ConsentContext);
66+
if (context === undefined) {
67+
throw new Error("useConsent must be used within a ConsentProvider");
68+
}
69+
return context;
70+
}

frontend/docs/pages/_app.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
import type { AppProps } from "next/app";
2-
import posthog from "posthog-js";
3-
import { PostHogProvider } from "posthog-js/react";
42
import "../styles/global.css";
53
import { LanguageProvider } from "../context/LanguageContext";
4+
import { ConsentProvider } from "../context/ConsentContext";
65
import CookieConsent from "@/components/ui/cookie-banner";
6+
import { PostHogProvider } from "@/providers/posthog";
77

88
function MyApp({ Component, pageProps }: AppProps) {
99
return (
1010
<LanguageProvider>
11-
<PostHogProvider client={posthog}>
12-
<main>
13-
<CookieConsent />
14-
<Component {...pageProps} />
15-
</main>
16-
</PostHogProvider>
11+
<ConsentProvider>
12+
<PostHogProvider>
13+
<main>
14+
<CookieConsent />
15+
<Component {...pageProps} />
16+
</main>
17+
</PostHogProvider>
18+
</ConsentProvider>
1719
</LanguageProvider>
1820
);
1921
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"use client";
2+
3+
import { usePathname, useSearchParams } from "next/navigation";
4+
import posthog from "posthog-js";
5+
import { PostHogProvider as PhProvider, usePostHog } from "posthog-js/react";
6+
import { useEffect, useRef } from "react";
7+
import { useConsent } from "@/context/ConsentContext";
8+
9+
export function PostHogProvider({ children }: { children: React.ReactNode }) {
10+
const { hasConsent } = useConsent();
11+
const initializedRef = useRef(false);
12+
13+
useEffect(() => {
14+
const key = process.env.NEXT_PUBLIC_POSTHOG_KEY;
15+
16+
if (!key)
17+
return console.error("PostHog key is not set in environment variables");
18+
19+
if (!hasConsent) {
20+
posthog.stopSessionRecording();
21+
posthog.opt_out_capturing();
22+
posthog.reset();
23+
24+
return;
25+
}
26+
27+
// Initialize PostHog on first run
28+
if (!initializedRef.current) {
29+
posthog.init(key, {
30+
api_host:
31+
process.env.NEXT_PUBLIC_POSTHOG_HOST || "https://us.i.posthog.com",
32+
person_profiles: "identified_only",
33+
capture_pageleave: true,
34+
capture_exceptions: {
35+
capture_unhandled_errors: true,
36+
capture_unhandled_rejections: true,
37+
capture_console_errors: false, // handle these manually
38+
},
39+
session_recording: {
40+
maskAllInputs: false,
41+
maskInputOptions: { password: true },
42+
},
43+
persistence: "localStorage+cookie",
44+
before_send: (event) => {
45+
// You can customize exception events for better grouping
46+
return event;
47+
},
48+
});
49+
initializedRef.current = true;
50+
}
51+
52+
posthog.opt_in_capturing();
53+
posthog.startSessionRecording();
54+
}, [hasConsent]);
55+
56+
return (
57+
<PhProvider client={posthog}>
58+
<PostHogPageView />
59+
{children}
60+
</PhProvider>
61+
);
62+
}
63+
64+
function PostHogPageView() {
65+
const pathname = usePathname();
66+
const searchParams = useSearchParams();
67+
const posthog = usePostHog();
68+
69+
useEffect(() => {
70+
if (pathname && posthog) {
71+
let url = window.origin + pathname;
72+
if (searchParams.toString()) {
73+
url = `${url}?${searchParams.toString()}`;
74+
}
75+
76+
posthog.capture("$pageview", { $current_url: url });
77+
}
78+
}, [pathname, searchParams, posthog]);
79+
80+
return null;
81+
}

0 commit comments

Comments
 (0)