Skip to content
Merged
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
7 changes: 6 additions & 1 deletion apps/web/.env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
VITE_APP_URL="http://localhost:3000"
VITE_API_URL="http://localhost:3000"
VITE_API_URL="http://localhost:3000"

# PostHog analytics + session replay (public client-side key).
# Leave empty to disable PostHog. Host defaults to https://us.i.posthog.com.
VITE_PUBLIC_POSTHOG_KEY=""
VITE_PUBLIC_POSTHOG_HOST="https://us.i.posthog.com"
1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
"motion": "^12.23.6",
"partysocket": "1.1.4",
"postcss": "^8.5.6",
"posthog-js": "^1.407.2",
"react": "^19.2.1",
"react-cookie": "^8.0.1",
"react-day-picker": "^9.7.0",
Expand Down
34 changes: 34 additions & 0 deletions apps/web/src/lib/posthog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import posthog from 'posthog-js';

// PostHog is disabled in local dev (MODE === 'development') and whenever no
// project key is configured. The key/host are baked in at build time via
// `import.meta.env.VITE_PUBLIC_POSTHOG_KEY` / `VITE_PUBLIC_POSTHOG_HOST`.
const posthogKey = import.meta.env.VITE_PUBLIC_POSTHOG_KEY;
const posthogHost = import.meta.env.VITE_PUBLIC_POSTHOG_HOST;

export const isPostHogEnabled = import.meta.env.MODE !== 'development' && Boolean(posthogKey);

export function initPostHog() {
if (!isPostHogEnabled) {
return;
}

posthog.init(posthogKey, {
api_host: posthogHost,
// Only fire pageviews once we've bootstrapped; we let the router drive them.
capture_pageview: true,
capture_pageleave: true,
person_profiles: 'identified_only',
// Session replay.
disable_session_recording: false,
session_recording: {
// Mask user-entered text by default so we don't record sensitive input
// (candidate code, credentials, etc.). Loosen per-element with
// `ph-no-mask` if you want specific fields recorded verbatim.
maskAllInputs: true,
maskTextSelector: '[data-ph-mask]',
},
});
}

export { posthog };
20 changes: 20 additions & 0 deletions apps/web/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import './App.css';
import NotFound from '@/components/common/NotFound.tsx';
import { PendingView } from '@/components/common/PendingView.tsx';
import { AuthContext, AuthProvider, useAuth } from '@/contexts/AuthContext.tsx';
import { initPostHog, isPostHogEnabled, posthog } from '@/lib/posthog.ts';
import { TanstackQueryClient } from '@/query/client.ts';
import reportWebVitals from './reportWebVitals.ts';

Expand Down Expand Up @@ -48,6 +49,10 @@ Sentry.init({
sendDefaultPii: true,
});

// Initialize PostHog analytics + session replay. Disabled in local dev and when
// no project key is configured (see @/lib/posthog).
initPostHog();

const authClient = Promise.withResolvers<AuthContext>();

function InnerApp() {
Expand All @@ -61,6 +66,21 @@ function InnerApp() {
// Tie Sentry events to the current user (cleared on sign-out).
Sentry.setUser(auth.user ? { id: auth.user.id, email: auth.user.email } : null);

// Tie PostHog events + session replays to the current user (reset on sign-out).
if (isPostHogEnabled) {
if (auth.user) {
posthog.identify(auth.user.id, {
email: auth.user.email,
name: auth.user.name,
});
if (auth.session?.activeOrganizationId) {
posthog.group('organization', auth.session.activeOrganizationId);
}
} else {
posthog.reset();
}
}

authClient.resolve(auth);
}, [auth, auth.isInitalLoading]);

Expand Down
7 changes: 7 additions & 0 deletions apps/web/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ export default defineConfig({
process.env.VITE_SENTRY_DSN ??
'https://d3de88682d569cc42655cfd5bc148001@o4510473855959040.ingest.us.sentry.io/4511725503512576'
),
// Env-var only, no default: PostHog stays off unless the key is set.
'import.meta.env.VITE_PUBLIC_POSTHOG_KEY': JSON.stringify(
process.env.VITE_PUBLIC_POSTHOG_KEY ?? ''
),
'import.meta.env.VITE_PUBLIC_POSTHOG_HOST': JSON.stringify(
process.env.VITE_PUBLIC_POSTHOG_HOST ?? 'https://us.i.posthog.com'
),
},
resolve: {
alias: {
Expand Down
83 changes: 83 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading