Skip to content

Commit 90f653a

Browse files
author
wizard-ci[bot]
committed
wizard-ci: react-router/rrv7-starter
1 parent ca0defc commit 90f653a

File tree

19 files changed

+608
-143
lines changed

19 files changed

+608
-143
lines changed

apps/react-router/rrv7-starter/app/components/PostCard.tsx

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useState, useEffect } from 'react'
2+
import { usePostHog } from '@posthog/react'
23
import type { FakePost } from '@/lib/data/fake-data'
34
import cn from '@/lib/utils/cn'
45
import { getLikedPosts, toggleLikedPost } from '@/lib/utils/localStorage'
@@ -10,6 +11,7 @@ interface PostCardProps {
1011
export function PostCard({ post }: PostCardProps) {
1112
const [liked, setLiked] = useState(false)
1213
const [likes, setLikes] = useState(post.likes)
14+
const posthog = usePostHog()
1315

1416
useEffect(() => {
1517
const likedPosts = getLikedPosts()
@@ -19,17 +21,26 @@ export function PostCard({ post }: PostCardProps) {
1921
const handleLike = () => {
2022
const newLikedState = toggleLikedPost(post.id)
2123
setLiked(newLikedState)
22-
setLikes((prev) => (prev + (newLikedState ? 1 : -1)))
24+
setLikes((prev) => prev + (newLikedState ? 1 : -1))
25+
26+
if (newLikedState) {
27+
posthog?.capture('post_liked', {
28+
post_id: post.id,
29+
post_username: post.username,
30+
post_verified: post.verified,
31+
})
32+
} else {
33+
posthog?.capture('post_unliked', {
34+
post_id: post.id,
35+
post_username: post.username,
36+
})
37+
}
2338
}
2439

2540
return (
2641
<div className="bg-primary/5 border border-primary/20 rounded-lg p-4 mb-4">
2742
<div className="flex items-center gap-3 mb-3">
28-
<img
29-
src={post.avatar}
30-
alt={post.username}
31-
className="w-10 h-10 rounded-full"
32-
/>
43+
<img src={post.avatar} alt={post.username} className="w-10 h-10 rounded-full" />
3344
<div className="flex-1">
3445
<div className="flex items-center gap-2">
3546
<span className="font-bold text-primary">{post.username}</span>
@@ -47,21 +58,14 @@ export function PostCard({ post }: PostCardProps) {
4758

4859
{post.image && (
4960
<div className="mb-3 rounded-lg overflow-hidden">
50-
<img
51-
src={post.image}
52-
alt="Post"
53-
className="w-full h-auto"
54-
/>
61+
<img src={post.image} alt="Post" className="w-full h-auto" />
5562
</div>
5663
)}
5764

5865
<div className="flex items-center gap-6 text-primary/70">
5966
<button
6067
onClick={handleLike}
61-
className={cn(
62-
'flex items-center gap-2 hover:text-accent transition',
63-
liked && 'text-red-500'
64-
)}
68+
className={cn('flex items-center gap-2 hover:text-accent transition', liked && 'text-red-500')}
6569
>
6670
<span className="text-xl">{liked ? '❤️' : '🤍'}</span>
6771
<span className="text-sm">
@@ -84,4 +88,3 @@ export function PostCard({ post }: PostCardProps) {
8488
</div>
8589
)
8690
}
87-

apps/react-router/rrv7-starter/app/components/StatCard.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,7 @@ export function StatCard({ metric }: StatCardProps) {
1515
{metric.value.toLocaleString()}
1616
{metric.unit && <span className="text-lg ml-1">{metric.unit}</span>}
1717
</div>
18-
<div
19-
className={cn(
20-
'text-sm flex items-center gap-1',
21-
isPositive ? 'text-green-500' : 'text-red-500'
22-
)}
23-
>
18+
<div className={cn('text-sm flex items-center gap-1', isPositive ? 'text-green-500' : 'text-red-500')}>
2419
<span>{isPositive ? '↑' : '↓'}</span>
2520
<span>
2621
{Math.abs(metric.change).toLocaleString()}
@@ -31,4 +26,3 @@ export function StatCard({ metric }: StatCardProps) {
3126
</div>
3227
)
3328
}
34-

apps/react-router/rrv7-starter/app/components/header.tsx

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { useState, useEffect } from 'react'
22
import { Link } from 'react-router'
3+
import { usePostHog } from '@posthog/react'
34
import { fakeUser } from '@/lib/data/fake-data'
45
import { getFollowers } from '@/lib/utils/localStorage'
56

67
export const Header = () => {
8+
const posthog = usePostHog()
79
const [followers, setFollowers] = useState(fakeUser.followers)
810

911
useEffect(() => {
@@ -32,16 +34,24 @@ export const Header = () => {
3234
<nav className="flex items-center justify-center md:justify-self-center">
3335
<ul className="flex items-center gap-3 md:gap-6 font-mono uppercase text-sm">
3436
<li>
35-
<Link to="/" className="hover:text-accent transition">Home</Link>
37+
<Link to="/" className="hover:text-accent transition">
38+
Home
39+
</Link>
3640
</li>
3741
<li>
38-
<Link to="/feed" className="hover:text-accent transition">Feed</Link>
42+
<Link to="/feed" className="hover:text-accent transition">
43+
Feed
44+
</Link>
3945
</li>
4046
<li>
41-
<Link to="/profile" className="hover:text-accent transition">Profile</Link>
47+
<Link to="/profile" className="hover:text-accent transition">
48+
Profile
49+
</Link>
4250
</li>
4351
<li>
44-
<Link to="/analytics" className="hover:text-accent transition">Analytics</Link>
52+
<Link to="/analytics" className="hover:text-accent transition">
53+
Analytics
54+
</Link>
4555
</li>
4656
</ul>
4757
</nav>
@@ -54,16 +64,13 @@ export const Header = () => {
5464
</div>
5565
<Link
5666
to="/buy-followers"
67+
onClick={() => posthog?.capture('buy_followers_header_clicked')}
5768
className="bg-accent text-primary font-bold px-4 py-2 rounded-lg text-sm hover:opacity-80 transition"
5869
>
5970
Buy Followers
6071
</Link>
6172
<Link to="/profile" className="flex items-center gap-2">
62-
<img
63-
src={fakeUser.avatar}
64-
alt={fakeUser.username}
65-
className="w-8 h-8 rounded-full border-2 border-accent"
66-
/>
73+
<img src={fakeUser.avatar} alt={fakeUser.username} className="w-8 h-8 rounded-full border-2 border-accent" />
6774
</Link>
6875
</div>
6976
</header>
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: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
import { useState, useEffect } from 'react';
1+
import { useState, useEffect } from 'react'
22

33
let hydrated = false
44

55
export const useHydrated = () => {
6-
const [, setIsHydrated] = useState(false);
6+
const [, setIsHydrated] = useState(false)
77

88
useEffect(() => {
99
if (!hydrated) {
10-
hydrated = true;
11-
setIsHydrated(true);
10+
hydrated = true
11+
setIsHydrated(true)
1212
}
13-
}, []);
13+
}, [])
1414

15-
return hydrated;
16-
};
15+
return hydrated
16+
}

apps/react-router/rrv7-starter/app/lib/data/fake-data.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export const fakePosts: FakePost[] = [
8888
id: '4',
8989
username: 'travel_fake',
9090
avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=travel',
91-
content: 'Living my best life in Bali! (Stock photo, I\'m actually at home) 🌴 #travel #wanderlust #fake',
91+
content: "Living my best life in Bali! (Stock photo, I'm actually at home) 🌴 #travel #wanderlust #fake",
9292
image: 'https://picsum.photos/800/600?random=4',
9393
likes: 156000,
9494
comments: 5600,
@@ -145,4 +145,3 @@ export const followerPackages = [
145145
{ amount: 50000, price: 299.99, bonus: 15000 },
146146
{ amount: 100000, price: 499.99, bonus: 50000 },
147147
]
148-
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/rrv7-starter/app/lib/utils/localStorage.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ export function toggleLikedPost(postId: string): boolean {
1818
if (typeof window === 'undefined') return false
1919
const liked = getLikedPosts()
2020
const isLiked = liked.has(postId)
21-
21+
2222
if (isLiked) {
2323
liked.delete(postId)
2424
} else {
2525
liked.add(postId)
2626
}
27-
27+
2828
localStorage.setItem(STORAGE_KEYS.LIKED_POSTS, JSON.stringify(Array.from(liked)))
2929
return !isLiked
3030
}
@@ -82,4 +82,3 @@ export function setPosts(count: number): void {
8282
if (typeof window === 'undefined') return
8383
localStorage.setItem(STORAGE_KEYS.POSTS, count.toString())
8484
}
85-

apps/react-router/rrv7-starter/app/root.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import {
88
useOutlet,
99
type MetaFunction,
1010
} from 'react-router'
11+
import { usePostHog } from '@posthog/react'
1112
import gsap from 'gsap'
1213

1314
import type { Route } from './+types/root'
15+
import { posthogMiddleware } from '@/lib/posthog-middleware'
1416
import stylesheet from './app.css?url'
1517
import { RouteTransitionManager } from '@joycostudio/transitions'
1618
import routes from './routes'
@@ -21,6 +23,8 @@ import { SITE_URL, WATERMARK } from '@/lib/constants'
2123
import { generateMeta } from '@/lib/utils/meta'
2224
import { generateLinks } from '@/lib/utils/links'
2325

26+
export const middleware: Route.MiddlewareFunction[] = [posthogMiddleware]
27+
2428
export const links: Route.LinksFunction = () =>
2529
generateLinks({
2630
stylesheets: [stylesheet, 'https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@700&display=swap'],
@@ -136,6 +140,9 @@ export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
136140
let details = 'An unexpected error occurred.'
137141
let stack: string | undefined
138142

143+
const posthog = usePostHog()
144+
posthog.captureException(error)
145+
139146
if (isRouteErrorResponse(error)) {
140147
message = error.status === 404 ? '404' : 'Error'
141148
details = error.status === 404 ? 'The requested page could not be found.' : error.statusText || details

apps/react-router/rrv7-starter/app/routes/analytics.tsx

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,7 @@ export default function Analytics() {
4848
<div className="container mx-auto px-4 py-8 max-w-6xl">
4949
<div className="mb-6">
5050
<h1 className="text-4xl font-bold text-primary mb-2">Analytics Dashboard</h1>
51-
<p className="text-primary/50">
52-
All your metrics are fake, but they look real! 📊
53-
</p>
51+
<p className="text-primary/50">All your metrics are fake, but they look real! 📊</p>
5452
{purchasedFollowers > 0 && (
5553
<p className="text-accent mt-2">
5654
You've purchased {purchasedFollowers.toLocaleString()} fake followers! (Saved in localStorage)
@@ -64,9 +62,8 @@ export default function Analytics() {
6462
<div>
6563
<h2 className="text-xl font-bold text-primary">Disclaimer</h2>
6664
<p className="text-primary/70 text-sm">
67-
All metrics shown are completely fake and generated randomly.
68-
Any resemblance to real engagement is purely coincidental.
69-
(But your follower count is saved in localStorage!)
65+
All metrics shown are completely fake and generated randomly. Any resemblance to real engagement is
66+
purely coincidental. (But your follower count is saved in localStorage!)
7067
</p>
7168
</div>
7269
</div>
@@ -83,9 +80,7 @@ export default function Analytics() {
8380
<div className="bg-background border border-primary/10 rounded-lg p-8 h-64 flex items-center justify-center">
8481
<div className="text-center">
8582
<div className="text-6xl mb-4">📊</div>
86-
<p className="text-primary/50">
87-
This chart would show your fake engagement over time
88-
</p>
83+
<p className="text-primary/50">This chart would show your fake engagement over time</p>
8984
<p className="text-primary/30 text-sm mt-2">
9085
(If we had time to build it, which we don't, because it's fake)
9186
</p>
@@ -96,4 +91,3 @@ export default function Analytics() {
9691
</div>
9792
)
9893
}
99-

0 commit comments

Comments
 (0)