Skip to content

Commit 7d4626b

Browse files
author
wizard-ci[bot]
committed
wizard-ci: react-router/shopper
1 parent ca0defc commit 7d4626b

File tree

13 files changed

+742
-4
lines changed

13 files changed

+742
-4
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { startTransition, StrictMode } from "react";
2+
import { hydrateRoot } from "react-dom/client";
3+
import { HydratedRouter } from "react-router/dom";
4+
5+
import posthog from 'posthog-js';
6+
import { PostHogProvider } from '@posthog/react'
7+
8+
posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_KEY, {
9+
api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST,
10+
defaults: '2025-11-30',
11+
__add_tracing_headers: [window.location.host, 'localhost'],
12+
});
13+
14+
startTransition(() => {
15+
hydrateRoot(
16+
document,
17+
<PostHogProvider client={posthog}>
18+
<StrictMode>
19+
<HydratedRouter />
20+
</StrictMode>
21+
</PostHogProvider>,
22+
);
23+
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { PostHog } from "posthog-node";
2+
import type { RouterContextProvider } from "react-router";
3+
import type { Route } from "../+types/root";
4+
5+
export interface PostHogContext extends RouterContextProvider {
6+
posthog?: PostHog;
7+
}
8+
9+
export const posthogMiddleware: Route.MiddlewareFunction = async ({ request, context }, next) => {
10+
const posthog = new PostHog(process.env.VITE_PUBLIC_POSTHOG_KEY!, {
11+
host: process.env.VITE_PUBLIC_POSTHOG_HOST!,
12+
flushAt: 1,
13+
flushInterval: 0,
14+
});
15+
16+
const sessionId = request.headers.get('X-POSTHOG-SESSION-ID');
17+
const distinctId = request.headers.get('X-POSTHOG-DISTINCT-ID');
18+
19+
(context as PostHogContext).posthog = posthog;
20+
21+
const response = await posthog.withContext(
22+
{ sessionId: sessionId ?? undefined, distinctId: distinctId ?? undefined },
23+
next
24+
);
25+
26+
await posthog.shutdown().catch(() => {});
27+
28+
return response;
29+
};

apps/react-router/shopper/app/root.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { usePostHog } from '@posthog/react';
12
import {
23
isRouteErrorResponse,
34
Links,
@@ -11,6 +12,11 @@ import type { Route } from "./+types/root";
1112
import "./app.css";
1213
import { CartProvider } from "./context/CartContext";
1314
import Navbar from "./components/Navbar";
15+
import { posthogMiddleware } from "./lib/posthog-middleware";
16+
17+
export const middleware: Route.MiddlewareFunction[] = [
18+
posthogMiddleware,
19+
];
1420

1521
export const links: Route.LinksFunction = () => [
1622
{ rel: "preconnect", href: "https://fonts.googleapis.com" },
@@ -59,6 +65,9 @@ export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
5965
let details = "An unexpected error occurred.";
6066
let stack: string | undefined;
6167

68+
const posthog = usePostHog();
69+
posthog?.captureException(error);
70+
6271
if (isRouteErrorResponse(error)) {
6372
message = error.status === 404 ? "404" : "Error";
6473
details =

apps/react-router/shopper/app/routes/cart.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,44 @@
11
import { Link } from "react-router";
22
import { useCart, type CartItem } from "../context/CartContext";
3+
import { usePostHog } from "@posthog/react";
34

45
export default function Cart() {
56
const { cart, removeFromCart, updateQuantity, getCartTotal } = useCart();
7+
const posthog = usePostHog();
68

79
const handleRemoveFromCart = (item: CartItem) => {
810
removeFromCart(item.id);
11+
posthog?.capture("product_removed_from_cart", {
12+
product_id: item.id,
13+
product_name: item.name,
14+
product_price: item.price,
15+
product_category: item.category,
16+
quantity: item.quantity,
17+
});
918
};
1019

1120
const handleUpdateQuantity = (item: CartItem, newQuantity: number) => {
1221
updateQuantity(item.id, newQuantity);
22+
posthog?.capture("cart_quantity_updated", {
23+
product_id: item.id,
24+
product_name: item.name,
25+
old_quantity: item.quantity,
26+
new_quantity: newQuantity,
27+
});
28+
};
29+
30+
const handleCheckoutClick = () => {
31+
posthog?.capture("checkout_started", {
32+
cart_total: getCartTotal(),
33+
cart_items_count: cart.length,
34+
});
35+
};
36+
37+
const handleContinueShoppingClick = () => {
38+
posthog?.capture("continue_shopping_clicked", {
39+
cart_total: getCartTotal(),
40+
cart_items_count: cart.length,
41+
});
1342
};
1443

1544
if (cart.length === 0) {
@@ -154,13 +183,15 @@ export default function Cart() {
154183

155184
<Link
156185
to="/checkout"
186+
onClick={handleCheckoutClick}
157187
className="block w-full bg-indigo-600 text-white py-3 rounded-lg text-center font-semibold hover:bg-indigo-700 transition"
158188
>
159189
Proceed to Checkout
160190
</Link>
161191

162192
<Link
163193
to="/products"
194+
onClick={handleContinueShoppingClick}
164195
className="block w-full mt-3 bg-gray-200 text-gray-900 py-3 rounded-lg text-center font-semibold hover:bg-gray-300 transition"
165196
>
166197
Continue Shopping

apps/react-router/shopper/app/routes/checkout.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { useState } from "react";
22
import { useNavigate } from "react-router";
33
import { useCart } from "../context/CartContext";
4+
import { usePostHog } from "@posthog/react";
45

56
export default function Checkout() {
67
const { cart, getCartTotal, clearCart } = useCart();
78
const navigate = useNavigate();
89
const [isProcessing, setIsProcessing] = useState(false);
10+
const posthog = usePostHog();
911
const [formData, setFormData] = useState({
1012
fullName: "",
1113
email: "",
@@ -43,6 +45,30 @@ export default function Checkout() {
4345
setIsProcessing(true);
4446

4547
setTimeout(() => {
48+
const orderTotal = getCartTotal() * 1.1;
49+
const itemCount = cart.reduce((sum, item) => sum + item.quantity, 0);
50+
51+
posthog?.capture("order_completed", {
52+
order_total: orderTotal,
53+
order_items_count: itemCount,
54+
order_subtotal: getCartTotal(),
55+
order_tax: getCartTotal() * 0.1,
56+
products: cart.map((item) => ({
57+
product_id: item.id,
58+
product_name: item.name,
59+
product_price: item.price,
60+
quantity: item.quantity,
61+
})),
62+
});
63+
64+
// Identify user with email from checkout form
65+
if (formData.email) {
66+
posthog?.identify(formData.email, {
67+
email: formData.email,
68+
name: formData.fullName,
69+
});
70+
}
71+
4672
clearCart();
4773
setIsProcessing(false);
4874
alert("Order placed successfully! Thank you for your purchase.");

apps/react-router/shopper/app/routes/home.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Link } from "react-router";
22
import type { Route } from "./+types/home";
3+
import { usePostHog } from "@posthog/react";
34

45
export function meta({}: Route.MetaArgs) {
56
return [
@@ -9,6 +10,12 @@ export function meta({}: Route.MetaArgs) {
910
}
1011

1112
export default function Home() {
13+
const posthog = usePostHog();
14+
15+
const handleStartShoppingClick = () => {
16+
posthog?.capture("start_shopping_clicked");
17+
};
18+
1219
return (
1320
<div className="container mx-auto px-4 py-16">
1421
<div className="text-center max-w-3xl mx-auto">
@@ -20,6 +27,7 @@ export default function Home() {
2027
</p>
2128
<Link
2229
to="/products"
30+
onClick={handleStartShoppingClick}
2331
className="inline-block bg-indigo-600 text-white px-8 py-4 rounded-lg text-lg font-semibold hover:bg-indigo-700 transition shadow-lg hover:shadow-xl"
2432
>
2533
Start Shopping

apps/react-router/shopper/app/routes/products.$productId.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { Route } from "./+types/products.$productId";
33
import { getProductById } from "../data/products";
44
import { useCart } from "../context/CartContext";
55
import { useState } from "react";
6+
import { usePostHog } from "@posthog/react";
67

78
export async function clientLoader({ params }: Route.LoaderArgs) {
89
const productId = parseInt(params.productId);
@@ -19,11 +20,19 @@ export default function ProductDetail({ loaderData }: Route.ComponentProps) {
1920
const { product } = loaderData;
2021
const { addToCart } = useCart();
2122
const [quantity, setQuantity] = useState(1);
23+
const posthog = usePostHog();
2224

2325
const handleAddToCart = () => {
2426
for (let i = 0; i < quantity; i++) {
2527
addToCart(product);
2628
}
29+
posthog?.capture("product_added_to_cart", {
30+
product_id: product.id,
31+
product_name: product.name,
32+
product_price: product.price,
33+
product_category: product.category,
34+
quantity: quantity,
35+
});
2736
};
2837

2938
return (

apps/react-router/shopper/app/routes/products.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { Route } from "./+types/products";
33
import { getProducts, getCategories, type Product } from "../data/products";
44
import { useState } from "react";
55
import { useCart } from "../context/CartContext";
6+
import { usePostHog } from "@posthog/react";
67

78
export async function clientLoader() {
89
return {
@@ -16,17 +17,32 @@ export default function Products({ loaderData }: Route.ComponentProps) {
1617
const { addToCart } = useCart();
1718
const [selectedCategory, setSelectedCategory] = useState<string>("");
1819
const [searchTerm, setSearchTerm] = useState<string>("");
20+
const posthog = usePostHog();
1921

2022
const handleAddToCart = (product: Product) => {
2123
addToCart(product);
24+
posthog?.capture("product_added_to_cart", {
25+
product_id: product.id,
26+
product_name: product.name,
27+
product_price: product.price,
28+
product_category: product.category,
29+
});
2230
};
2331

2432
const handleSearch = (term: string) => {
2533
setSearchTerm(term);
34+
if (term.trim()) {
35+
posthog?.capture("product_searched", {
36+
search_term: term,
37+
});
38+
}
2639
};
2740

2841
const handleCategoryChange = (category: string) => {
2942
setSelectedCategory(category);
43+
posthog?.capture("category_filtered", {
44+
category: category || "All Categories",
45+
});
3046
};
3147

3248
const filteredProducts = products.filter((product) => {

0 commit comments

Comments
 (0)