From 411435c111f94edd91b3cba1e7a8706e6219a328 Mon Sep 17 00:00:00 2001 From: "wizard-ci-bot[bot]" <254716194+wizard-ci-bot[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 17:00:50 +0000 Subject: [PATCH] wizard-ci: react-router/react-router-v7-project --- .../react-router-v7-project/.env.example | 2 + .../app/context/AuthContext.tsx | 37 ++++- .../app/entry.client.tsx | 1 + .../app/lib/posthog-logger.client.ts | 21 +++ .../app/lib/posthog.client.ts | 35 +++++ .../app/routes/countries.tsx | 45 +++++- .../app/routes/login.tsx | 10 ++ .../app/routes/profile.tsx | 10 +- .../app/routes/signup.tsx | 9 ++ .../react-router-v7-project/package-lock.json | 134 +++++++++++++++++- .../react-router-v7-project/package.json | 1 + 11 files changed, 295 insertions(+), 10 deletions(-) create mode 100644 apps/basic-integration/react-router/react-router-v7-project/.env.example create mode 100644 apps/basic-integration/react-router/react-router-v7-project/app/lib/posthog-logger.client.ts create mode 100644 apps/basic-integration/react-router/react-router-v7-project/app/lib/posthog.client.ts diff --git a/apps/basic-integration/react-router/react-router-v7-project/.env.example b/apps/basic-integration/react-router/react-router-v7-project/.env.example new file mode 100644 index 000000000..486aab85d --- /dev/null +++ b/apps/basic-integration/react-router/react-router-v7-project/.env.example @@ -0,0 +1,2 @@ +VITE_PUBLIC_POSTHOG_PROJECT_TOKEN= +VITE_PUBLIC_POSTHOG_HOST= diff --git a/apps/basic-integration/react-router/react-router-v7-project/app/context/AuthContext.tsx b/apps/basic-integration/react-router/react-router-v7-project/app/context/AuthContext.tsx index d72a28651..48ddc18f5 100644 --- a/apps/basic-integration/react-router/react-router-v7-project/app/context/AuthContext.tsx +++ b/apps/basic-integration/react-router/react-router-v7-project/app/context/AuthContext.tsx @@ -1,4 +1,4 @@ -import { createContext, useContext, useState, useEffect, type ReactNode } from 'react' +import { createContext, useContext, useState, useEffect, useRef, type ReactNode } from 'react' import type { FakeUser } from '~/lib/utils/auth' import { getCurrentUser, setCurrentUser, fakeLogin, fakeSignup, fakeLogout } from '~/lib/utils/auth' @@ -14,12 +14,41 @@ const AuthContext = createContext(undefined) export function AuthProvider({ children }: { children: ReactNode }) { const [user, setUser] = useState(null) + const identifiedUserId = useRef(null) useEffect(() => { const currentUser = getCurrentUser() setUser(currentUser) }, []) + useEffect(() => { + let cancelled = false + + if (!user) { + return () => { + cancelled = true + } + } + + void import('~/lib/posthog.client').then(({ default: posthog, isPostHogEnabled }) => { + if (cancelled || !isPostHogEnabled) return + + if (identifiedUserId.current && identifiedUserId.current !== user.id) { + posthog.reset() + } + + posthog.identify(user.id, { + email: user.email, + username: user.username, + }) + identifiedUserId.current = user.id + }) + + return () => { + cancelled = true + } + }, [user?.id]) + const login = (username: string, password: string): boolean => { const loggedInUser = fakeLogin(username, password) if (loggedInUser) { @@ -41,6 +70,12 @@ export function AuthProvider({ children }: { children: ReactNode }) { } const logout = () => { + void import('~/lib/posthog.client').then(({ default: posthog, isPostHogEnabled }) => { + if (isPostHogEnabled) { + posthog.reset() + } + }) + identifiedUserId.current = null fakeLogout() setUser(null) } diff --git a/apps/basic-integration/react-router/react-router-v7-project/app/entry.client.tsx b/apps/basic-integration/react-router/react-router-v7-project/app/entry.client.tsx index 487c480b9..c0aac82d6 100644 --- a/apps/basic-integration/react-router/react-router-v7-project/app/entry.client.tsx +++ b/apps/basic-integration/react-router/react-router-v7-project/app/entry.client.tsx @@ -1,6 +1,7 @@ import { startTransition, StrictMode } from "react"; import { hydrateRoot } from "react-dom/client"; import { HydratedRouter } from "react-router/dom"; +import "./lib/posthog.client"; startTransition(() => { hydrateRoot( diff --git a/apps/basic-integration/react-router/react-router-v7-project/app/lib/posthog-logger.client.ts b/apps/basic-integration/react-router/react-router-v7-project/app/lib/posthog-logger.client.ts new file mode 100644 index 000000000..0507c5eb0 --- /dev/null +++ b/apps/basic-integration/react-router/react-router-v7-project/app/lib/posthog-logger.client.ts @@ -0,0 +1,21 @@ +import posthog, { isPostHogEnabled } from "~/lib/posthog.client"; + +type LogAttributes = Record; + +export const posthogLogger = { + info(message: string, attributes: LogAttributes) { + if (isPostHogEnabled) { + posthog.logger.info(message, attributes); + } + }, + warn(message: string, attributes: LogAttributes) { + if (isPostHogEnabled) { + posthog.logger.warn(message, attributes); + } + }, + error(message: string, attributes: LogAttributes) { + if (isPostHogEnabled) { + posthog.logger.error(message, attributes); + } + }, +}; diff --git a/apps/basic-integration/react-router/react-router-v7-project/app/lib/posthog.client.ts b/apps/basic-integration/react-router/react-router-v7-project/app/lib/posthog.client.ts new file mode 100644 index 000000000..2b341a1a0 --- /dev/null +++ b/apps/basic-integration/react-router/react-router-v7-project/app/lib/posthog.client.ts @@ -0,0 +1,35 @@ +import posthog from "posthog-js"; + +const projectToken = import.meta.env.VITE_PUBLIC_POSTHOG_PROJECT_TOKEN; +const host = import.meta.env.VITE_PUBLIC_POSTHOG_HOST; +export const isPostHogEnabled = Boolean(projectToken && host); + +if (!projectToken) { + if (import.meta.env.DEV) { + throw new Error( + "VITE_PUBLIC_POSTHOG_PROJECT_TOKEN variable required by PostHog is missing or un-configured, this causes events to be silently missed. This error stops appearing once VITE_PUBLIC_POSTHOG_PROJECT_TOKEN is configured", + ); + } +} else if (!host) { + if (import.meta.env.DEV) { + throw new Error( + "VITE_PUBLIC_POSTHOG_HOST variable required by PostHog is missing or un-configured, this causes events to be silently missed. This error stops appearing once VITE_PUBLIC_POSTHOG_HOST is configured", + ); + } +} else { + posthog.init(projectToken, { + api_host: host, + defaults: "2026-08-30", + logs: { + serviceName: "rest-explorer-web", + environment: import.meta.env.MODE, + }, + capture_exceptions: { + capture_unhandled_errors: true, + capture_unhandled_rejections: true, + capture_console_errors: false, + }, + }); +} + +export default posthog; diff --git a/apps/basic-integration/react-router/react-router-v7-project/app/routes/countries.tsx b/apps/basic-integration/react-router/react-router-v7-project/app/routes/countries.tsx index 7a7c23577..55f519eb0 100644 --- a/apps/basic-integration/react-router/react-router-v7-project/app/routes/countries.tsx +++ b/apps/basic-integration/react-router/react-router-v7-project/app/routes/countries.tsx @@ -19,15 +19,28 @@ export async function clientLoader() { // Check if API returned an error object if (data.status === 404 || (data.message && !Array.isArray(data))) { console.error("API Error:", data.message || data.status); + const { posthogLogger } = await import("~/lib/posthog-logger.client"); + posthogLogger.warn("countries_catalog_api_rejected", { + source: "rest_countries", + }); return []; } // Ensure we return an array const countries = Array.isArray(data) ? data : []; console.log(`Loaded ${countries.length} countries from API`); + const { posthogLogger } = await import("~/lib/posthog-logger.client"); + posthogLogger.info("countries_catalog_loaded", { + country_count: countries.length, + source: "rest_countries", + }); return countries; } catch (error) { console.error("Error loading countries:", error); + const { posthogLogger } = await import("~/lib/posthog-logger.client"); + posthogLogger.error("countries_catalog_fetch_failed", { + source: "rest_countries", + }); // Return empty array on error to prevent crashes return []; } @@ -50,6 +63,16 @@ export default function Countries({ loaderData }: Route.ComponentProps) { setRegion(newRegion); }; + const captureCountryAction = async ( + event: "country_claimed" | "country_liked" | "country_visited", + countryName: string, + ) => { + const { default: posthog, isPostHogEnabled } = await import("~/lib/posthog.client"); + if (isPostHogEnabled) { + posthog.capture(event, { country_name: countryName }); + } + }; + // Ensure loaderData is an array, fallback to empty array const countries = Array.isArray(loaderData) ? loaderData : []; @@ -110,6 +133,7 @@ export default function Countries({ loaderData }: Route.ComponentProps) { const countryName = country.name.common; const isClaimed = user?.claimedCountries.includes(countryName); const isLiked = user?.likedCountries.includes(countryName); + const isVisited = user?.visitedCountries.includes(countryName); return (