diff --git a/app/dashboard/README.md b/app/dashboard/README.md index d06894802..10c0c387f 100644 --- a/app/dashboard/README.md +++ b/app/dashboard/README.md @@ -234,6 +234,45 @@ This section outlines the revised architecture for handling authentication, API * Removed the `OperationalTokenProvider` and the concept of a separate operational token. * All authenticated data API calls use the Supabase JWT and go through `fetchAuthenticatedApi`, either directly or via helper functions in `lib/api/`. +## Troubleshooting Common Issues 🔧 + +### Console Errors in Local Development + +When running the dashboard locally, you might see console errors related to PostHog analytics and authentication. These have been addressed with the following fixes: + +#### PostHog Analytics Errors (401/404) +- **Issue**: PostHog tries to load resources from `us-assets.i.posthog.com` even when not configured +- **Solution**: PostHog is now automatically disabled in local development when no API key is provided +- **Configuration**: Leave `NEXT_PUBLIC_POSTHOG_KEY` empty in your `.env.local` file for local development + +#### API Authentication Errors (401) +- **Issue**: The `/opsboard/users/me` endpoint returns 401 when not logged in +- **Solution**: These errors are now suppressed in development mode as they're expected behavior +- **Note**: You'll still be redirected to the signin page when authentication is required + +#### MIME Type Errors +- **Issue**: Incorrect MIME type when loading PostHog resources like `array.json` +- **Solution**: PostHog rewrites in `next.config.js` are now conditional based on configuration + +#### Setting Up Local Development Without Errors + +1. Copy the example environment file: + ```bash + cp .env.local.example .env.local + ``` + +2. For local development, leave analytics services empty: + ```bash + # In .env.local - leave these commented out or empty + # NEXT_PUBLIC_POSTHOG_KEY= + # NEXT_PUBLIC_POSTHOG_HOST= + # NEXT_PUBLIC_SENTRY_DSN= + ``` + +3. Ensure your backend API is running at the configured URL (default: `http://localhost:8000`) + +These changes ensure a cleaner console output during local development while maintaining full analytics capabilities in production environments. + ## Cypress E2E Testing 🧪 For details on setting up and running End-to-End tests with Cypress, please refer to the dedicated README: diff --git a/app/dashboard/app/providers/posthog-provider.tsx b/app/dashboard/app/providers/posthog-provider.tsx index cff2eec39..c37dadcc4 100644 --- a/app/dashboard/app/providers/posthog-provider.tsx +++ b/app/dashboard/app/providers/posthog-provider.tsx @@ -11,14 +11,22 @@ import { PostHogProvider as PHProvider } from 'posthog-js/react'; export function PostHogProvider({ children }: { children: React.ReactNode }) { useEffect(() => { const posthogKey = process.env.NEXT_PUBLIC_POSTHOG_KEY; + const isDevelopment = process.env.NODE_ENV === 'development'; + const isLocalhost = typeof window !== 'undefined' && + (window.location.hostname === 'localhost' || + window.location.hostname === '127.0.0.1' || + window.location.hostname.startsWith('192.168.')); - // Only initialize PostHog if we have a valid key - if (posthogKey) { + // Only initialize PostHog if we have a valid key AND we're not in local development + // This prevents console errors when PostHog is not configured for local development + if (posthogKey && !isLocalhost) { posthog.init(posthogKey, { api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || 'https://us.i.posthog.com', person_profiles: 'identified_only', // or 'always' to create profiles for anonymous users as well capture_pageview: false, // Disable automatic pageview capture, as we capture manually }); + } else if (isDevelopment && !posthogKey) { + console.log('[PostHog] Analytics disabled in local development mode'); } }, []); @@ -37,7 +45,8 @@ function PostHogPageView() { // Track pageviews useEffect(() => { - if (pathname && posthog && posthog.__loaded) { + // Only track if PostHog is properly initialized and loaded + if (pathname && posthog && posthog.__loaded && posthog.isFeatureEnabled !== undefined) { let url = window.origin + pathname; if (searchParams.toString()) { url = url + '?' + searchParams.toString(); diff --git a/app/dashboard/components/posthog-user-identifier.tsx b/app/dashboard/components/posthog-user-identifier.tsx index 1f52fe21b..02fd11b5e 100644 --- a/app/dashboard/components/posthog-user-identifier.tsx +++ b/app/dashboard/components/posthog-user-identifier.tsx @@ -9,13 +9,16 @@ export function PostHogUserIdentifier() { const posthog = usePostHog(); useEffect(() => { - if (posthog && user?.id) { - posthog.identify(user.id, { - email: user.email || undefined, - name: user.full_name || undefined, - }); - } else if (posthog && !user) { - posthog.reset(); + // Only interact with PostHog if it's properly initialized + if (posthog && posthog.__loaded && posthog.isFeatureEnabled !== undefined) { + if (user?.id) { + posthog.identify(user.id, { + email: user.email || undefined, + name: user.full_name || undefined, + }); + } else { + posthog.reset(); + } } }, [posthog, user]); diff --git a/app/dashboard/lib/api-client.ts b/app/dashboard/lib/api-client.ts index 0b992281f..ff6fc981f 100644 --- a/app/dashboard/lib/api-client.ts +++ b/app/dashboard/lib/api-client.ts @@ -122,7 +122,11 @@ export async function fetchAuthenticatedApi( errorBody = 'Failed to read error response body'; } } - console.error(`[API Client] API Error ${response.status} for ${endpoint}:`, errorBody); + // In development, don't log 401 errors as they're expected when not authenticated + const isDevelopment = process.env.NODE_ENV === 'development'; + if (response.status !== 401 || !isDevelopment) { + console.error(`[API Client] API Error ${response.status} for ${endpoint}:`, errorBody); + } throw new ApiError( `API request failed with status ${response.status}`, response.status, @@ -149,7 +153,10 @@ export async function fetchAuthenticatedApi( if (error.status === 401) { // Ensure this runs only on the client side if (typeof window !== 'undefined') { - console.warn('[API Client] Received 401 Unauthorized for ${endpoint}.'); + const isDevelopment = process.env.NODE_ENV === 'development'; + if (!isDevelopment) { + console.warn(`[API Client] Received 401 Unauthorized for ${endpoint}.`); + } // Prevent infinite loops if signin page itself triggers a 401 somehow if (window.location.pathname !== '/signin') { window.location.href = '/signin'; diff --git a/app/dashboard/lib/api/user.ts b/app/dashboard/lib/api/user.ts index 6b689b79a..5faccccd6 100644 --- a/app/dashboard/lib/api/user.ts +++ b/app/dashboard/lib/api/user.ts @@ -17,10 +17,16 @@ export const fetchUserAPI = async (): Promise => { return user || null; } catch (error) { - console.error('[fetchUserAPI] Error fetching user:', error); + // In development, 401 errors are expected when not logged in + // Only log other errors or 401s in production + const isDevelopment = process.env.NODE_ENV === 'development'; if (error instanceof ApiError && (error.status === 401 || error.status === 403)) { + if (!isDevelopment) { + console.error('[fetchUserAPI] Authentication error:', error.status); + } return null; } + console.error('[fetchUserAPI] Error fetching user:', error); throw error; } }; diff --git a/app/dashboard/next.config.js b/app/dashboard/next.config.js index d780cb2d9..80e6ad82a 100644 --- a/app/dashboard/next.config.js +++ b/app/dashboard/next.config.js @@ -66,19 +66,29 @@ const nextConfig = { skipTrailingSlashRedirect: true, 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*', - }, - { - source: '/ingest/decide', - destination: 'https://us.i.posthog.com/decide', - }, + const rewrites = []; + + // Only add PostHog rewrites if PostHog is configured + // This prevents 401/404 errors in local development when PostHog is not set up + if (process.env.NEXT_PUBLIC_POSTHOG_KEY) { + rewrites.push( + { + source: '/ingest/static/:path*', + destination: 'https://us-assets.i.posthog.com/static/:path*', + }, + { + source: '/ingest/:path*', + destination: 'https://us.i.posthog.com/:path*', + }, + { + source: '/ingest/decide', + destination: 'https://us.i.posthog.com/decide', + } + ); + } + + // Add other rewrites that should always be present + rewrites.push( { source: '/functions/v1/:path*', destination: 'https://qjkcnuesiiqjpohzdjjm.supabase.co/functions/v1/:path*', @@ -102,8 +112,10 @@ const nextConfig = { { source: '/api-metrics/:path*', destination: 'http://0.0.0.0:8000/:path*', - }, - ]; + } + ); + + return rewrites; }, async redirects() {