diff --git a/app/error.tsx b/app/error.tsx index bce1803..d408138 100644 --- a/app/error.tsx +++ b/app/error.tsx @@ -2,6 +2,7 @@ import { useEffect } from "react"; import Link from "next/link"; +import { captureException } from "@/lib/posthog/helpers"; export default function ErrorPage({ error, @@ -12,6 +13,7 @@ export default function ErrorPage({ }) { useEffect(() => { console.error(error); + captureException(error, { digest: error.digest }); }, [error]); return ( diff --git a/app/providers.tsx b/app/providers.tsx index 59e86e2..9e9810f 100644 --- a/app/providers.tsx +++ b/app/providers.tsx @@ -4,14 +4,8 @@ import { useEffect, Suspense } from "react"; import { usePathname, useSearchParams } from "next/navigation"; import posthog from "posthog-js"; import { PostHogProvider as PHProvider } from "posthog-js/react"; - -if (typeof window !== "undefined") { - posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, { - api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST, - person_profiles: "identified_only", - capture_pageview: false, - }); -} +import { captureEvent } from "@/lib/posthog/helpers"; +import { POSTHOG_EVENTS } from "@/lib/posthog/events"; function PostHogPageTracker() { const pathname = usePathname(); @@ -23,7 +17,7 @@ function PostHogPageTracker() { if (searchParams.toString()) { url += `?${searchParams.toString()}`; } - posthog.capture("$pageview", { $current_url: url }); + captureEvent(POSTHOG_EVENTS.PAGE_VIEW, { $current_url: url }); } }, [pathname, searchParams]); diff --git a/components/BookEvent.tsx b/components/BookEvent.tsx index ee5f8e2..7313482 100644 --- a/components/BookEvent.tsx +++ b/components/BookEvent.tsx @@ -2,7 +2,8 @@ import { useState } from "react"; import { createBooking } from "@/lib/actions/booking.actions"; -import posthog from "posthog-js"; +import { captureEvent } from "@/lib/posthog/helpers"; +import { POSTHOG_EVENTS } from "@/lib/posthog/events"; const BookEvent = ({ eventId, slug }: { eventId: string; slug: string }) => { const [email, setEmail] = useState(''); @@ -27,10 +28,10 @@ const BookEvent = ({ eventId, slug }: { eventId: string; slug: string }) => { if (response.success) { setSubmitted(true); // Do not send raw email (PII) to analytics. - posthog.capture('event_booked', { eventId, slug }); + captureEvent(POSTHOG_EVENTS.EVENT_BOOKED, { eventId, slug }); } else { setError(response.error || "An unexpected error occurred. Please try again."); - posthog.captureException('Booking creation failed'); + captureEvent(POSTHOG_EVENTS.BOOKING_FAILED, { eventId, slug, email }); } } catch { setError("A network error occurred. Please try again."); @@ -54,13 +55,11 @@ const BookEvent = ({ eventId, slug }: { eventId: string; slug: string }) => { id="email" placeholder="Enter your email address" required - disabled={isSubmitting} // 1. Freeze input when submitting + disabled={isSubmitting} /> - {/* 2. Show the red error message under the input if an error occurs */} {error &&

{error}

} - {/* 3. Disable the button and change text dynamically */} diff --git a/components/CreateNewEvent.tsx b/components/CreateNewEvent.tsx index b5395ab..28cfc88 100644 --- a/components/CreateNewEvent.tsx +++ b/components/CreateNewEvent.tsx @@ -5,6 +5,8 @@ import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; import { toast } from "sonner"; import { createEvent } from "@/lib/actions/create-event.actions"; +import { captureEvent, captureException } from "@/lib/posthog/helpers"; +import { POSTHOG_EVENTS } from "@/lib/posthog/events"; const eventSchema = z.object({ title: z.string().min(3, "Title must be at least 3 characters"), @@ -63,12 +65,22 @@ const CreateEventForm = () => { if (result.success) { toast.success("Event created successfully!"); + captureEvent(POSTHOG_EVENTS.EVENT_CREATED, { + type: data.type, + mode: data.mode, + location: data.location, + }); reset(); } else { toast.error(result.error || "Failed to create event"); + captureEvent(POSTHOG_EVENTS.EVENT_CREATION_FAILED, { + reason: result.error || "unknown", + type: data.type, + mode: data.mode, + }); } @@ -77,6 +89,7 @@ const CreateEventForm = () => { console.error(error); toast.error("Something went wrong"); + captureException(error instanceof Error ? error : new Error("event_creation_exception")); } }; diff --git a/components/EventCard.tsx b/components/EventCard.tsx index 46573ec..96aef17 100644 --- a/components/EventCard.tsx +++ b/components/EventCard.tsx @@ -3,6 +3,8 @@ import Image from "next/image"; import Link from "next/link"; import { useState } from "react"; +import { captureEvent } from "@/lib/posthog/helpers"; +import { POSTHOG_EVENTS } from "@/lib/posthog/events"; interface Props { title: string; @@ -48,9 +50,11 @@ const EventCard = ({ (item: string) => item !== slug ); setBookmarked(false); + captureEvent(POSTHOG_EVENTS.EVENT_UNBOOKMARKED, { slug, title }); } else { updated = [...saved, slug]; setBookmarked(true); + captureEvent(POSTHOG_EVENTS.EVENT_BOOKMARKED, { slug, title }); } localStorage.setItem( @@ -58,9 +62,11 @@ const EventCard = ({ JSON.stringify(updated) ); }; + return ( captureEvent(POSTHOG_EVENTS.EVENT_VIEWED, { slug, title })} className=" event-card group @@ -139,4 +145,4 @@ const EventCard = ({ ); }; -export default EventCard; \ No newline at end of file +export default EventCard; diff --git a/components/SearchFilters.tsx b/components/SearchFilters.tsx index 2d52c2a..525d849 100644 --- a/components/SearchFilters.tsx +++ b/components/SearchFilters.tsx @@ -2,6 +2,8 @@ import { useRouter, useSearchParams, usePathname } from 'next/navigation'; import { useState, useEffect } from 'react'; +import { captureEvent } from '@/lib/posthog/helpers'; +import { POSTHOG_EVENTS } from '@/lib/posthog/events'; const MODES = ['All', 'Online', 'Offline', 'Hybrid']; const POPULAR_TAGS = ['All', 'Hackathon', 'Meetup', 'Web3', 'React', 'DevOps', 'AI']; @@ -21,11 +23,15 @@ export default function SearchFilters() { params.delete(key); } router.push(`${pathname}?${params.toString()}`, { scroll: false }); + captureEvent(POSTHOG_EVENTS.EVENT_FILTER_CHANGED, { filter: key, value }); }; useEffect(() => { const delayDebounceFn = setTimeout(() => { handleFilterChange('query', search); + if (search.trim()) { + captureEvent(POSTHOG_EVENTS.EVENT_SEARCHED, { query: search }); + } }, 400); return () => clearTimeout(delayDebounceFn); }, [search]); diff --git a/instrumentation-client.ts b/instrumentation-client.ts new file mode 100644 index 0000000..099abf8 --- /dev/null +++ b/instrumentation-client.ts @@ -0,0 +1,5 @@ +import posthog from "posthog-js"; + +posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, { + api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST, +}); diff --git a/lib/posthog.js b/lib/posthog.js deleted file mode 100644 index 1f15c69..0000000 --- a/lib/posthog.js +++ /dev/null @@ -1,9 +0,0 @@ -import posthog from "posthog-js"; - -if (typeof window !== "undefined") { - posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, { - api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST, - }); -} - -export default posthog; diff --git a/lib/posthog/events.ts b/lib/posthog/events.ts new file mode 100644 index 0000000..6e84682 --- /dev/null +++ b/lib/posthog/events.ts @@ -0,0 +1,21 @@ +export const POSTHOG_EVENTS = { + // Navigation + PAGE_VIEW: "$pageview", + + // Event discovery + EVENT_VIEWED: "event_viewed", + EVENT_SEARCHED: "event_searched", + EVENT_FILTER_CHANGED: "event_filter_changed", + + // Bookmarks + EVENT_BOOKMARKED: "event_bookmarked", + EVENT_UNBOOKMARKED: "event_unbookmarked", + + // Booking + EVENT_BOOKED: "event_booked", + BOOKING_FAILED: "booking_failed", + + // Creation + EVENT_CREATED: "event_created", + EVENT_CREATION_FAILED: "event_creation_failed", +} as const; diff --git a/lib/posthog/helpers.ts b/lib/posthog/helpers.ts new file mode 100644 index 0000000..9120195 --- /dev/null +++ b/lib/posthog/helpers.ts @@ -0,0 +1,22 @@ +import posthog from "posthog-js"; + +export function identifyUser( + userId: string, + traits?: Record +) { + posthog.identify(userId, traits); +} + +export function captureEvent( + event: string, + properties?: Record +) { + posthog.capture(event, properties); +} + +export function captureException( + error: unknown, + properties?: Record +) { + posthog.captureException(error, properties); +}