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
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { updateAccount } from '@/app/(login)/actions';
import { User } from '@/lib/db/schema';
import useSWR from 'swr';
import { Suspense } from 'react';
import posthog from 'posthog-js';

const fetcher = (url: string) => fetch(url).then((res) => res.json());

Expand Down Expand Up @@ -89,7 +90,13 @@ export default function GeneralPage() {
<CardTitle>Account Information</CardTitle>
</CardHeader>
<CardContent>
<form className="space-y-4" action={formAction}>
<form
className="space-y-4"
action={formAction}
onSubmit={() => {
posthog.capture('account_updated');
}}
>
<Suspense fallback={<AccountForm state={state} />}>
<AccountFormWithData state={state} />
</Suspense>
Expand Down
35 changes: 32 additions & 3 deletions apps/next-js/15-app-router-saas/app/(dashboard)/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { Input } from '@/components/ui/input';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import { Label } from '@/components/ui/label';
import { Loader2, PlusCircle } from 'lucide-react';
import posthog from 'posthog-js';

type ActionState = {
error?: string;
Expand Down Expand Up @@ -60,7 +61,15 @@ function ManageSubscription() {
: 'No active subscription'}
</p>
</div>
<form action={customerPortalAction}>
<form
action={customerPortalAction}
onSubmit={() => {
posthog.capture('subscription_managed', {
plan_name: teamData?.planName,
subscription_status: teamData?.subscriptionStatus,
});
}}
>
<Button type="submit" variant="outline">
Manage Subscription
</Button>
Expand Down Expand Up @@ -154,7 +163,15 @@ function TeamMembers() {
</div>
</div>
{index > 1 ? (
<form action={removeAction}>
<form
action={removeAction}
onSubmit={() => {
posthog.capture('team_member_removed', {
member_id: member.id,
member_role: member.role,
});
}}
>
<input type="hidden" name="memberId" value={member.id} />
<Button
type="submit"
Expand Down Expand Up @@ -201,7 +218,19 @@ function InviteTeamMember() {
<CardTitle>Invite Team Member</CardTitle>
</CardHeader>
<CardContent>
<form action={inviteAction} className="space-y-4">
<form
action={inviteAction}
className="space-y-4"
onSubmit={(e) => {
const formData = new FormData(e.currentTarget);
const email = formData.get('email') as string;
const role = formData.get('role') as string;
posthog.capture('team_member_invited', {
invited_email: email,
invited_role: role,
});
}}
>
<div>
<Label htmlFor="email" className="mb-2">
Email
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Label } from '@/components/ui/label';
import { Lock, Trash2, Loader2 } from 'lucide-react';
import { useActionState } from 'react';
import { updatePassword, deleteAccount } from '@/app/(login)/actions';
import posthog from 'posthog-js';

type PasswordState = {
currentPassword?: string;
Expand Down Expand Up @@ -43,7 +44,13 @@ export default function SecurityPage() {
<CardTitle>Password</CardTitle>
</CardHeader>
<CardContent>
<form className="space-y-4" action={passwordAction}>
<form
className="space-y-4"
action={passwordAction}
onSubmit={() => {
posthog.capture('password_update_submitted');
}}
>
<div>
<Label htmlFor="current-password" className="mb-2">
Current Password
Expand Down Expand Up @@ -123,7 +130,13 @@ export default function SecurityPage() {
<p className="text-sm text-gray-500 mb-4">
Account deletion is non-reversable. Please proceed with caution.
</p>
<form action={deleteAction} className="space-y-4">
<form
action={deleteAction}
className="space-y-4"
onSubmit={() => {
posthog.capture('account_deletion_submitted');
}}
>
<div>
<Label htmlFor="delete-password" className="mb-2">
Confirm Password
Expand Down
3 changes: 3 additions & 0 deletions apps/next-js/15-app-router-saas/app/(dashboard)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { signOut } from '@/app/(login)/actions';
import { useRouter } from 'next/navigation';
import { User } from '@/lib/db/schema';
import useSWR, { mutate } from 'swr';
import posthog from 'posthog-js';

const fetcher = (url: string) => fetch(url).then((res) => res.json());

Expand All @@ -24,6 +25,8 @@ function UserMenu() {
const router = useRouter();

async function handleSignOut() {
posthog.capture('sign_out_clicked');
posthog.reset();
await signOut();
mutate('/api/user');
router.push('/');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,22 @@
import { Button } from '@/components/ui/button';
import { ArrowRight, Loader2 } from 'lucide-react';
import { useFormStatus } from 'react-dom';
import posthog from 'posthog-js';

export function SubmitButton() {
const { pending } = useFormStatus();

const handleClick = () => {
posthog.capture('checkout_started');
};

return (
<Button
type="submit"
disabled={pending}
variant="outline"
className="w-full rounded-full"
onClick={handleClick}
>
{pending ? (
<>
Expand Down
17 changes: 16 additions & 1 deletion apps/next-js/15-app-router-saas/app/(login)/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Label } from '@/components/ui/label';
import { CircleIcon, Loader2 } from 'lucide-react';
import { signIn, signUp } from './actions';
import { ActionState } from '@/lib/auth/middleware';
import posthog from 'posthog-js';

export function Login({ mode = 'signin' }: { mode?: 'signin' | 'signup' }) {
const searchParams = useSearchParams();
Expand All @@ -34,7 +35,21 @@ export function Login({ mode = 'signin' }: { mode?: 'signin' | 'signup' }) {
</div>

<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<form className="space-y-6" action={formAction}>
<form
className="space-y-6"
action={formAction}
onSubmit={(e) => {
const formData = new FormData(e.currentTarget);
const email = formData.get('email') as string;
if (mode === 'signin') {
posthog.capture('sign_in_submitted', { email });
posthog.identify(email, { email });
} else {
posthog.capture('sign_up_submitted', { email });
posthog.identify(email, { email });
}
}}
>
<input type="hidden" name="redirect" value={redirect || ''} />
<input type="hidden" name="priceId" value={priceId || ''} />
<input type="hidden" name="inviteId" value={inviteId || ''} />
Expand Down
9 changes: 9 additions & 0 deletions apps/next-js/15-app-router-saas/instrumentation-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import posthog from 'posthog-js';

posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
api_host: '/ingest',
ui_host: 'https://us.posthog.com',
defaults: '2025-05-24',
capture_exceptions: true,
debug: process.env.NODE_ENV === 'development',
});
20 changes: 20 additions & 0 deletions apps/next-js/15-app-router-saas/lib/posthog-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { PostHog } from 'posthog-node';

let posthogClient: PostHog | null = null;

export function getPostHogClient() {
if (!posthogClient) {
posthogClient = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
flushAt: 1,
flushInterval: 0,
});
}
return posthogClient;
}

export async function shutdownPostHog() {
if (posthogClient) {
await posthogClient.shutdown();
}
}
13 changes: 13 additions & 0 deletions apps/next-js/15-app-router-saas/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ const nextConfig: NextConfig = {
// Configuration for stable Next.js 15
// To enable experimental features like PPR, upgrade to canary:
// pnpm add next@canary
async rewrites() {
return [
{
source: '/ingest/static/:path*',
destination: 'https://us-assets.i.posthog.com/static/:path*',
},
{
source: '/ingest/:path*',
destination: 'https://us.i.posthog.com/:path*',
},
];
},
skipTrailingSlashRedirect: true,
};

export default nextConfig;
2 changes: 2 additions & 0 deletions apps/next-js/15-app-router-saas/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
"next": "15.5.7",
"postcss": "^8.5.3",
"postgres": "^3.4.5",
"posthog-js": "^1.321.2",
"posthog-node": "^5.21.0",
"radix-ui": "^1.4.2",
"react": "19.1.2",
"react-dom": "19.1.2",
Expand Down
Loading