Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/basic-integration/react-router/shopper/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
VITE_PUBLIC_POSTHOG_PROJECT_TOKEN=
VITE_PUBLIC_POSTHOG_HOST=
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { PostHogErrorBoundary, PostHogProvider } from "@posthog/react";
import { useEffect, useState, type ReactNode } from "react";
import type { PostHog } from "posthog-js";

let posthogClient: PostHog | null = null;
let initializationStarted = false;

export default function PostHogClientProvider({
children,
}: {
children: ReactNode;
}) {
const [client, setClient] = useState<PostHog | null>(posthogClient);

useEffect(() => {
const token = import.meta.env.VITE_PUBLIC_POSTHOG_PROJECT_TOKEN;
const host = import.meta.env.VITE_PUBLIC_POSTHOG_HOST;

if (!token || !host) {
if (import.meta.env.DEV) {
if (!token) {
console.error(
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"
)
);
}

if (!host) {
console.error(
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"
)
);
}
}

return;
}

if (posthogClient) {
setClient(posthogClient);
return;
}

if (initializationStarted) return;
initializationStarted = true;

void import("posthog-js").then(({ default: posthog }) => {
posthog.init(token, {
api_host: host,
defaults: "2026-08-30",
logs: {
serviceName: "shopper-web",
environment: import.meta.env.MODE,
},
});
posthogClient = posthog;
setClient(posthog);
});
}, []);

if (!client) return <>{children}</>;

return (
<PostHogProvider client={client}>
<PostHogErrorBoundary>{children}</PostHogErrorBoundary>
</PostHogProvider>
);
}
17 changes: 10 additions & 7 deletions apps/basic-integration/react-router/shopper/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import {

import type { Route } from "./+types/root";
import "./app.css";
import { CartProvider } from "./context/CartContext";
import Navbar from "./components/Navbar";
import PostHogClientProvider from "./components/PostHogClientProvider";
import { CartProvider } from "./context/CartContext";

export const links: Route.LinksFunction = () => [
{ rel: "preconnect", href: "https://fonts.googleapis.com" },
Expand Down Expand Up @@ -45,12 +46,14 @@ export function Layout({ children }: { children: React.ReactNode }) {

export default function App() {
return (
<CartProvider>
<div className="min-h-screen bg-gray-50">
<Navbar />
<Outlet />
</div>
</CartProvider>
<PostHogClientProvider>
<CartProvider>
<div className="min-h-screen bg-gray-50">
<Navbar />
<Outlet />
</div>
</CartProvider>
</PostHogClientProvider>
);
}

Expand Down
28 changes: 28 additions & 0 deletions apps/basic-integration/react-router/shopper/app/routes/cart.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,42 @@
import { Link } from "react-router";
import { useCart, type CartItem } from "../context/CartContext";
import { usePostHog } from "@posthog/react";

export default function Cart() {
const { cart, removeFromCart, updateQuantity, getCartTotal } = useCart();
const posthog = usePostHog();

const handleRemoveFromCart = (item: CartItem) => {
removeFromCart(item.id);
posthog?.capture("cart_item_removed", {
product_id: item.id,
category: item.category,
quantity: item.quantity,
price: item.price,
});
};

const handleUpdateQuantity = (item: CartItem, newQuantity: number) => {
if (newQuantity <= 0) {
handleRemoveFromCart(item);
return;
}

updateQuantity(item.id, newQuantity);
posthog?.capture("cart_quantity_updated", {
product_id: item.id,
category: item.category,
previous_quantity: item.quantity,
quantity: newQuantity,
});
};

const handleCheckoutStarted = () => {
posthog?.capture("checkout_started", {
cart_item_count: cart.reduce((count, item) => count + item.quantity, 0),
cart_value: getCartTotal(),
currency: "USD",
});
};

if (cart.length === 0) {
Expand Down Expand Up @@ -154,6 +181,7 @@ export default function Cart() {

<Link
to="/checkout"
onClick={handleCheckoutStarted}
className="block w-full bg-indigo-600 text-white py-3 rounded-lg text-center font-semibold hover:bg-indigo-700 transition"
>
Proceed to Checkout
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { useState } from "react";
import { useNavigate } from "react-router";
import { useCart } from "../context/CartContext";
import { usePostHog } from "@posthog/react";

export default function Checkout() {
const { cart, getCartTotal, clearCart } = useCart();
const posthog = usePostHog();
const posthogLogger = posthog?.logger;
const navigate = useNavigate();
const [isProcessing, setIsProcessing] = useState(false);
const [formData, setFormData] = useState({
Expand Down Expand Up @@ -40,9 +43,30 @@ export default function Checkout() {

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const subtotal = getCartTotal();
const itemCount = cart.reduce((count, item) => count + item.quantity, 0);
const orderTotal = subtotal * 1.1;
posthogLogger?.info("checkout order submitted", {
subtotal,
order_total: orderTotal,
item_count: itemCount,
currency: "USD",
});
setIsProcessing(true);

setTimeout(() => {
posthog?.capture("order_completed", {
subtotal,
order_total: orderTotal,
item_count: itemCount,
currency: "USD",
});
posthogLogger?.info("checkout order completed", {
subtotal,
order_total: orderTotal,
item_count: itemCount,
currency: "USD",
});
clearCart();
setIsProcessing(false);
alert("Order placed successfully! Thank you for your purchase.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { Route } from "./+types/products.$productId";
import { getProductById } from "../data/products";
import { useCart } from "../context/CartContext";
import { useState } from "react";
import { usePostHog } from "@posthog/react";

export async function clientLoader({ params }: Route.LoaderArgs) {
const productId = parseInt(params.productId);
Expand All @@ -18,12 +19,20 @@ export async function clientLoader({ params }: Route.LoaderArgs) {
export default function ProductDetail({ loaderData }: Route.ComponentProps) {
const { product } = loaderData;
const { addToCart } = useCart();
const posthog = usePostHog();
const [quantity, setQuantity] = useState(1);

const handleAddToCart = () => {
for (let i = 0; i < quantity; i++) {
addToCart(product);
}
posthog?.capture("product_added_to_cart", {
product_id: product.id,
category: product.category,
price: product.price,
quantity,
source: "product_detail",
});
};

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { Route } from "./+types/products";
import { getProducts, getCategories, type Product } from "../data/products";
import { useState } from "react";
import { useCart } from "../context/CartContext";
import { usePostHog } from "@posthog/react";

export async function clientLoader() {
return {
Expand All @@ -14,11 +15,19 @@ export async function clientLoader() {
export default function Products({ loaderData }: Route.ComponentProps) {
const { products, categories } = loaderData;
const { addToCart } = useCart();
const posthog = usePostHog();
const [selectedCategory, setSelectedCategory] = useState<string>("");
const [searchTerm, setSearchTerm] = useState<string>("");

const handleAddToCart = (product: Product) => {
addToCart(product);
posthog?.capture("product_added_to_cart", {
product_id: product.id,
category: product.category,
price: product.price,
quantity: 1,
source: "product_catalog",
});
};

const handleSearch = (term: string) => {
Expand Down
Loading
Loading