Skip to content
Draft
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
39 changes: 39 additions & 0 deletions app/dashboard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
15 changes: 12 additions & 3 deletions app/dashboard/app/providers/posthog-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
}, []);

Expand All @@ -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();
Expand Down
17 changes: 10 additions & 7 deletions app/dashboard/components/posthog-user-identifier.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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]);

Expand Down
11 changes: 9 additions & 2 deletions app/dashboard/lib/api-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,11 @@ export async function fetchAuthenticatedApi<T = any>(
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,
Expand All @@ -149,7 +153,10 @@ export async function fetchAuthenticatedApi<T = any>(
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';
Expand Down
8 changes: 7 additions & 1 deletion app/dashboard/lib/api/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,16 @@ export const fetchUserAPI = async (): Promise<IUser | null> => {

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;
}
};
42 changes: 27 additions & 15 deletions app/dashboard/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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*',
Expand All @@ -102,8 +112,10 @@ const nextConfig = {
{
source: '/api-metrics/:path*',
destination: 'http://0.0.0.0:8000/:path*',
},
];
}
);

return rewrites;
},

async redirects() {
Expand Down