@aacigroup/aaci_shared
v5.3.0
Published
Shared tracking utilities for AACI Group projects with React Context support
Readme
AACI Shared Library
React Context-based tracking and magic-link library for frontend and backend projects.
| Client: Frontend Side – React | Client: Backend Side | API Server: Backend Side | | --- | --- | --- |
Quick Links
- Client: Frontend (React) - React Context, hooks, and components
- Client: Backend (Supabase Edge Functions) - Magic Links, 2FA, and notifications in edge functions
- API Server (Backend Services) - Type-safe API handlers with validation
Features
🔐 Magic Links & Authentication
- V2 Magic Links with built-in 2FA support (light/strict modes)
- Email & SMS 2FA delivery
- Token validation with URL pattern matching
- Admin tokens for privileged access
- Full TypeScript support with comprehensive types
📧 External Notifications
- Email & SMS notification sending
- Template-based messaging
- HTML content support
- Tracking and logging
📊 Lead Tracking & Analytics
- PostHog integration for analytics
- Google Tag Manager (GTM) support
- Lead capture with address validation
- Session data tracking
- Environment detection (prod/dev)
⚛️ React Integration
- Context Providers for easy setup
- Custom hooks for all features
- Feature flags support
- Type-safe throughout
Client: Frontend Side – React
Installation
npm install @aacigroup/aaci_shared react1. Install the package
npm install @aacigroup/aaci_shared react2. Set up environment variables
# .env file
VITE_LEAD_CAPTURE_API_URL=https://your-api.com/leads
VITE_LEAD_CAPTURE_API_KEY=your-lead-api-key
VITE_PROJECT_NAME=MyProject
VITE_POSTHOG_KEY=phc_your_production_key
VITE_POSTHOG_DEV_KEY=phc_your_development_key
VITE_PRODUCTION_DOMAINS=myproject.com,www.myproject.com3. Wrap your app with TrackingProvider
// App.tsx
import { TrackingProvider } from '@aacigroup/aaci_shared/react';
function App() {
const trackingConfig = {
apiUrl: import.meta.env.VITE_LEAD_CAPTURE_API_URL,
apiKey: import.meta.env.VITE_LEAD_CAPTURE_API_KEY,
projectName: import.meta.env.VITE_PROJECT_NAME,
posthogKey: import.meta.env.VITE_POSTHOG_KEY,
posthogDevKey: import.meta.env.VITE_POSTHOG_DEV_KEY,
productionDomains: import.meta.env.VITE_PRODUCTION_DOMAINS.split(',').map(d => d.trim())
};
return (
<TrackingProvider config={trackingConfig}>
<YourApp />
</TrackingProvider>
);
}4. Use tracking in components
// ContactForm.tsx
import { useLeadTracker, usePostHog } from '@aacigroup/aaci_shared/react';
function ContactForm() {
const tracker = useLeadTracker();
const analytics = usePostHog();
const handleSubmit = async (formData) => {
// Track lead
await tracker.trackLeadAndAddress({
lead_type: 'contact',
email: formData.email,
first_name: formData.firstName
});
// Track analytics
analytics.trackEvent('contact_form_submitted');
};
return <form onSubmit={handleSubmit}>...</form>;
}React Context Setup
App Setup
// App.tsx
import { TrackingProvider } from '@aacigroup/aaci_shared/react';
function App() {
// Create config from environment variables
const trackingConfig = {
// Lead Capture API settings
apiUrl: import.meta.env.VITE_LEAD_CAPTURE_API_URL,
apiKey: import.meta.env.VITE_LEAD_CAPTURE_API_KEY,
projectName: import.meta.env.VITE_PROJECT_NAME,
// PostHog Analytics settings
posthogKey: import.meta.env.VITE_POSTHOG_KEY,
posthogDevKey: import.meta.env.VITE_POSTHOG_DEV_KEY, // Optional: for development
posthogApiHost: import.meta.env.VITE_POSTHOG_API_HOST, // Optional: for self-hosted PostHog
// Environment detection
productionDomains: import.meta.env.VITE_PRODUCTION_DOMAINS.split(',').map(d => d.trim())
};
return (
<TrackingProvider config={trackingConfig}>
<YourApp />
</TrackingProvider>
);
}PostHog Dual Environment Setup
The library automatically uses different PostHog instances based on your environment:
- Production Environment: Uses
VITE_POSTHOG_KEY(only on domains listed inVITE_PRODUCTION_DOMAINS) - Development/Staging: Uses
VITE_POSTHOG_DEV_KEY(on all other domains/localhost)
This allows you to:
- Keep production analytics clean and separate from development data
- Track development/staging events in a separate PostHog project
- Debug analytics issues without affecting production data
Configuration Options:
# Required: Production PostHog key
VITE_POSTHOG_KEY=phc_your_production_key
# Optional: Development PostHog key (if not provided, PostHog won't initialize in dev)
VITE_POSTHOG_DEV_KEY=phc_your_development_keyInternal Traffic Filtering
Add ?internal=1 to any URL to mark traffic as internal and exclude it from analytics:
https://yoursite.com?internal=1 // Marks as internal
https://yoursite.com?internal=true // Marks as internal
https://yoursite.com?internal=0 // Marks as external
https://yoursite.com?internal=false // Marks as externalSetting persists across page visits until browser storage is cleared.
Component Usage
// ContactForm.tsx
import {
useLeadTracker,
usePostHog,
useStoredLead,
useStoredAddress
} from '@aacigroup/aaci_shared/react';
function ContactForm() {
const tracker = useLeadTracker();
const analytics = usePostHog();
const storedLead = useStoredLead(); // TrackLeadParams | null
const storedAddress = useStoredAddress(); // AddressInput | null
const handleSubmit = async (formData) => {
// Track lead submission
await tracker.trackLeadAndAddress({
lead_type: 'contact',
email: formData.email,
first_name: formData.firstName
});
// Track analytics event
analytics.trackEvent('contact', { has_email: !!email });
};
// Check if user has previously stored data
const hasStoredData = storedLead || storedAddress;
return (
<form onSubmit={handleSubmit}>
{hasStoredData && <p>We found your previous information!</p>}
{/* form fields */}
</form>
);
}Feature Flags (Optional)
The library includes optional PostHog-powered feature flags that integrate seamlessly with the tracking system.
Setup
Feature flags are optional and must be explicitly added by wrapping your app with FeatureFlagsProvider inside the TrackingProvider:
// App.tsx
import {
TrackingProvider,
FeatureFlagsProvider
} from '@aacigroup/aaci_shared/react';
function App() {
const trackingConfig = {
// ... your tracking config
};
return (
<TrackingProvider config={trackingConfig}>
<FeatureFlagsProvider>
<YourApp />
</FeatureFlagsProvider>
</TrackingProvider>
);
}Important: FeatureFlagsProvider must be inside TrackingProvider - it uses the same PostHog instance for consistency.
Component Usage
import { useFeatureFlags } from '@aacigroup/aaci_shared/react';
function MyComponent() {
const { isFeatureFlagEnabled, getFeatureFlag } = useFeatureFlags();
// Simple boolean flag
const showNewDashboard = isFeatureFlagEnabled('new-dashboard');
// Multi-variant flag (string/boolean values)
const buttonColor = getFeatureFlag('button-color'); // 'red', 'blue', true, false, etc.
return (
<div>
{showNewDashboard && <NewDashboard />}
<button className={`btn-${buttonColor}`}>
Click me
</button>
</div>
);
}PostHog Configuration
To create and manage feature flags in PostHog:
Access PostHog Dashboard
- Go to PostHog
- Navigate to the project matching your API key
Create Feature Flag
- Go to "Feature Flags" in the left sidebar
- Click "New Feature Flag"
- Enter a flag key (e.g.,
new-dashboard,button-color)
Configure Release
- For simple on/off flags: Set release condition to "100% of users"
- For A/B testing: Configure percentage splits
- For gradual rollouts: Start with lower percentages and increase over time
Save and Deploy
- Click "Save" - flags are immediately available
- No code deployment needed for flag value changes
Example Flag Types
// Boolean flags (most common)
const showNewFeature = isFeatureFlagEnabled('new-feature');
const enableBetaMode = isFeatureFlagEnabled('beta-mode');
// Multi-variant flags
const theme = getFeatureFlag('ui-theme'); // 'dark', 'light', 'auto'
const buttonStyle = getFeatureFlag('btn-style'); // 'rounded', 'square', 'pill'
const pricing = getFeatureFlag('pricing-tier'); // 'basic', 'premium', 'enterprise'
// Use in conditional rendering
return (
<div className={theme === 'dark' ? 'dark-theme' : 'light-theme'}>
{showNewFeature && <NewFeature />}
<button className={`btn-${buttonStyle}`}>
{pricing === 'premium' ? 'Get Premium' : 'Get Basic'}
</button>
</div>
);Error Handling
If FeatureFlagsProvider is used outside TrackingProvider, you'll see helpful error messages:
// ❌ Wrong - will show console.error
<FeatureFlagsProvider>
<App />
</FeatureFlagsProvider>
// Console error: "FeatureFlagsProvider must be used within TrackingProvider..."Advanced Usage
Access the PostHog instance directly if needed:
import { usePostHogFeatureFlags, usePostHog } from '@aacigroup/aaci_shared/react';
function AdvancedComponent() {
const posthog = usePostHogFeatureFlags();
const analytics = usePostHog();
// Access PostHog methods directly
const flagValue = posthog.getInstance().getFeatureFlag('complex-flag');
// Check which PostHog environment is being used
const envInfo = analytics.getEnvironmentInfo();
console.log(`Using ${envInfo.environment} PostHog instance`, envInfo);
return <div>Advanced usage</div>;
}Data Types
Question Types
The library includes a portable question types system for managing dynamic forms and questionnaires.
// Import all question types
import * as QuestionTypes from '@aacigroup/aaci_shared/questionTypes';
// Import specific types
import {
TemplateSource,
QuestionTemplateData,
EntityQuestionsMap,
TemplateQuestion
} from '@aacigroup/aaci_shared/questionTypes';Available exports:
- All type definitions from
types.ts - Helper functions from
helpers.ts - Validators from
validators.ts - Defaults from
defaults.ts - Writers from
writers.ts - Session types from
session.ts - Template types from
template.ts(QuestionTemplate, EntityQuestionsMap, TemplateQuestion, etc.) - Answer validators from
answerValidators/ - Conditions from
conditions.ts - Condition helpers from
conditionHelpers.ts
Lead Data
interface TrackLeadParams {
lead_type: string; // Required: Type of lead
first_name?: string; // Optional: First name
last_name?: string; // Optional: Last name
email?: string; // Optional: Email address
phone?: string; // Optional: Phone number
extra_data?: Record<string, any>; // Optional: Additional custom data
}Common lead_type examples (free string):
'policy_review'- Insurance policy review requests'address_check'- Property address verification'contact'- General contact form submissions'signup'- User registration/signup'consultation'- Service consultation requests'quote'- Price quote requests
Note: lead_type is a free string - you can use any value that makes sense for your application.
Validation Rules:
lead_typeis always required- Either
emailORphoneis required when tracking a lead
Address Data
// Base address interface - only address-related fields
interface Address {
full_address: string; // Required: Complete address string
place_id: string; // Required: Google Places API place ID
street_address: string; // Required: Street address component
street_address_2?: string; // Optional: Street address line 2 (apt, suite, etc.)
city: string; // Required: City
state: string; // Required: State/Province
zip_code: string; // Required: ZIP/Postal code
county?: string; // Optional: County
country?: string; // Optional: Country
}
// Address input for tracking - includes source and extra_data
interface AddressInput extends Address {
source: string; // Required: Lead type that generated this address
extra_data?: Record<string, any>; // Optional: Additional address data
}Usage:
- Use
Addressfor pure address data (display, validation) - Use
AddressInputfor tracking operations that requiresourcefield
Session Data
interface SessionData {
ip?: string; // Optional: User's IP address
user_agent?: string; // Optional: Browser user agent
browser?: string; // Optional: Browser name
browser_version?: string; // Optional: Browser version
os?: string; // Optional: Operating system
device?: string; // Optional: Device type
referrer?: string; // Optional: Referring page URL
utm_source?: string; // Optional: UTM source parameter
utm_medium?: string; // Optional: UTM medium parameter
utm_campaign?: string; // Optional: UTM campaign parameter
landing_page?: string; // Optional: First page visited
current_page?: string; // Optional: Current page URL
session_id?: string; // Optional: Session identifier
timestamp?: string; // Optional: Timestamp of action
distinct_id?: string; // Optional: User identifier
gclid?: string; // Optional: Google Ads click ID
fbclid?: string; // Optional: Facebook click ID
fbc?: string; // Optional: Facebook browser cookie
fbp?: string; // Optional: Facebook pixel cookie
is_internal?: boolean; // Optional: Internal traffic flag (auto-detected from URL)
}Common source examples (free string):
'policy_review'- When tracking address from policy review forms'address_check'- For standalone address verification'contact'- From general contact forms'signup'- During user registration'consultation'- From consultation request forms'quote'- From quote request forms
Note: source is a free string - you can use any value to identify where the address came from. It may match lead_type by design but doesn't have to.
Validation Rules:
full_addressis always requiredplace_idis always requiredstreet_addressis always requiredcityis always requiredstateis always requiredzip_codeis always requiredsourceis always required
Usage Examples
import { useLeadTracker, usePostHog } from '@aacigroup/aaci_shared/react';
function MyComponent() {
const tracker = useLeadTracker();
const analytics = usePostHog();
// 1. Track lead only - Basic contact form
const handleContactForm = async (formData) => {
await tracker.trackLeadAndAddress({
lead_type: 'contact',
email: formData.email,
first_name: formData.firstName
});
analytics.trackEvent('contact_form_submitted');
};
// 2. Track address only - Address verification
const handleAddressCheck = async (address) => {
await tracker.trackLeadAndAddress(undefined, {
full_address: address,
place_id: 'ChIJd8BlQ2BZwokRAFUEcm_qrcA', // Google Places API ID
street_address: '123 Main St',
city: 'New York',
state: 'NY',
zip_code: '10001',
source: 'address_check'
});
analytics.trackEvent('address_checked');
};
// 3. Track both together - Policy review with address
const handlePolicyReview = async (leadData, addressData) => {
await tracker.trackLeadAndAddress({
lead_type: 'policy_review',
email: leadData.email,
phone: leadData.phone,
extra_data: { policy_number: leadData.policyNumber }
}, {
full_address: addressData.fullAddress,
place_id: addressData.placeId,
street_address: addressData.streetAddress,
city: addressData.city,
zip_code: addressData.zipCode,
source: 'policy_review'
});
analytics.trackEvent('policy_review_started', {
has_phone: !!leadData.phone
});
};
// 4. Multi-step forms
const handleStepOne = async (leadData) => {
// Step 1: Collect lead info
await tracker.trackLeadAndAddress({
lead_type: 'quote',
email: leadData.email
});
};
const handleStepTwo = async (addressData) => {
// Step 2: Add address (automatically merges with saved lead)
await tracker.trackLeadAndAddress(undefined, {
full_address: addressData.address,
place_id: addressData.placeId,
street_address: addressData.street,
city: addressData.city,
zip_code: addressData.zipCode,
source: 'quote'
});
analytics.trackEvent('quote_completed');
};
return <div>...</div>;
}Data Persistence
Lead and address data are automatically saved to browser localStorage when successfully tracked:
- Lead data - Saved to
aaci_saved_leadafter successful API response - Address data - Saved to
aaci_saved_addressafter successful API response - Smart merging - If you track only address later, it automatically attaches to saved lead
- Session persistence - Data persists across page reloads within the same browser
Access Stored Data
import { useStoredLead, useStoredAddress } from '@aacigroup/aaci_shared/react';
function MyComponent() {
const storedLead = useStoredLead(); // Returns TrackLeadParams | null
const storedAddress = useStoredAddress(); // Returns Address | null
// Use stored data to pre-fill forms or show personalized content
if (storedLead) {
console.log('Previous lead:', storedLead.email);
console.log('Lead type:', storedLead.lead_type);
}
if (storedAddress) {
console.log('Previous address:', storedAddress.full_address);
console.log('Address source:', storedAddress.source);
}
}localStorage Keys
aaci_saved_lead- Contains the last successfully tracked lead dataaaci_saved_address- Contains the last successfully tracked address data
// Example: Multi-step form automatically merges data
// Step 1: Track lead (saves to localStorage)
await tracker.trackLeadAndAddress({ lead_type: 'quote', email: '[email protected]' });
// Step 2: Track address (merges with saved lead automatically)
await tracker.trackLeadAndAddress(undefined, {
full_address: '123 Main St, New York, NY 10001',
place_id: 'ChIJd8BlQ2BZwokRAFUEcm_qrcA',
street_address: '123 Main St',
city: 'New York',
zip_code: '10001',
source: 'quote'
});Magic Links
The library includes Magic Link functionality for secure, token-based, login-free access to personalized pages. Magic links are created and validated centrally in your backend and tied to a user profile.
Note: Magic Link is a separate hook from TrackingContext. It requires its own configuration.
Overview
Magic links provide:
- Customer access tokens - For public URLs to personalized content
- Admin tokens (optional) - For elevated access/override links
- URL pattern binding - Tokens are only valid within their expected URL structure
- Automatic user resolution - Links to user profile by email/phone
React Hook Usage
import { useMagicLink, MagicLinkConfig } from '@aacigroup/aaci_shared/react';
function MagicLinkExample() {
// Magic Link requires its own config (separate from TrackingConfig)
const magicLinkConfig: MagicLinkConfig = {
apiUrl: import.meta.env.VITE_LEAD_CAPTURE_API_URL,
apiKey: import.meta.env.VITE_LEAD_CAPTURE_API_KEY,
projectName: import.meta.env.VITE_PROJECT_NAME
};
const magicLink = useMagicLink(magicLinkConfig);
// Create a magic link for a customer
const handleCreateLink = async () => {
const result = await magicLink.createMagicLink({
email: '[email protected]',
url_pattern: 'https://myapp.com/session/{token}',
admin_url_pattern: 'https://myapp.com/session/{token}/admin/{admin_token}', // optional
expires_at: '2025-12-31T23:59:59.000Z' // optional
});
if (result.status === 'ok') {
console.log('Customer URL:', result.url);
console.log('Admin URL:', result.admin_url); // if admin_url_pattern was provided
console.log('Token ID:', result.magic_link_token_id);
}
};
// Validate a magic link token from URL (extract token from router params)
// Example: if your route is /session/:sessionId/:token
import { useParams } from 'react-router-dom';
function SessionPage() {
const { token } = useParams(); // token from route: /session/:sessionId/:token
const handleValidateLink = async () => {
const currentUrl = window.location.href;
const result = await magicLink.validateMagicLink({
token,
current_url: currentUrl,
mode: 'customer' // or 'admin' for admin access
});
if (result.valid) {
console.log('Valid! User ID:', result.person_profile_id);
console.log('Link data:', result.data); // Data stored in magic link data
// Render personalized content
} else {
console.log('Invalid:', result.reason); // 'expired', 'revoked', 'url_mismatch', 'not_found'
}
};
// Call handleValidateLink on mount or button click
// useEffect(() => { handleValidateLink(); }, []);
// or <button onClick={handleValidateLink}>Validate</button>
return null;
}
return <div>...</div>;
}Create Magic Link
interface CreateMagicLinkParams {
project_name?: string; // Optional – auto-populated from config
email?: string; // Optional – used to resolve user
phone?: string; // Optional – alternative user lookup
url_pattern: string; // Required – must contain "{token}"
admin_url_pattern?: string; // Optional – must contain "{admin_token}"
expires_at?: string; // Optional ISO timestamp
extra_data?: Record<string, any>; // Optional – stored in magic link data
session_data?: SessionData; // Optional – auto-populated if not provided
}Rules:
- At least one of:
emailorphoneis required url_patternis required and must contain{token}- If
admin_url_patternis provided, it must contain{admin_token}(and may also include{token}) session_datais automatically collected from the browser if not explicitly provided
Response:
interface CreateMagicLinkResponse {
status: 'ok' | 'error';
project_name?: string; // Project identifier
magic_link_token_id?: string; // ID of the created magic link
url?: string; // Resolved customer URL
admin_url?: string; // Resolved admin URL (if admin_url_pattern provided)
expires_at?: string; // Expiration timestamp (if set)
message?: string;
errors?: Array<{ field: string; message: string }>;
}Validate Magic Link
interface ValidateMagicLinkParams {
project_name?: string; // Optional – auto-populated from config
token: string; // The token from URL
current_url: string; // Full URL being accessed
mode?: 'customer' | 'admin'; // Default: 'customer'
session_data?: SessionData; // Optional – auto-populated if not provided
}Rules:
mode = 'customer'→ validates againsttoken+url_patternmode = 'admin'→ validates againstadmin_token+admin_url_pattern- Link must: exist, not be expired, not be revoked, match the expected URL pattern
session_datais automatically collected from the browser if not explicitly provided
Response:
interface ValidateMagicLinkResponse {
status: 'ok' | 'error';
valid: boolean;
project_name?: string; // Project identifier
magic_link_token_id?: string;
person_profile_id?: string;
data?: Record<string, any>; // Data stored in magic link data
message?: string;
reason?: 'expired' | 'revoked' | 'url_mismatch' | 'not_found'; // if invalid
}Full Page Access Example
import { useMagicLink } from '@aacigroup/aaci_shared/react';
import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom'; // or your router of choice
function ProtectedSessionPage() {
const magicLink = useMagicLink();
const { token, adminToken } = useParams(); // Get tokens from React Router
const [accessState, setAccessState] = useState<'loading' | 'valid' | 'invalid'>('loading');
const [personId, setPersonId] = useState<string | null>(null);
const [linkData, setLinkData] = useState<Record<string, any> | null>(null);
const [isAdmin, setIsAdmin] = useState(false);
useEffect(() => {
const validateAccess = async () => {
const currentUrl = window.location.href;
// Try admin token first if available
if (adminToken) {
const result = await magicLink.validateMagicLink({
token: adminToken,
current_url: currentUrl,
mode: 'admin'
});
if (result.valid) {
setAccessState('valid');
setPersonId(result.person_profile_id || null);
setLinkData(result.data || null);
setIsAdmin(true);
return;
}
}
// Fall back to customer token
if (token) {
const result = await magicLink.validateMagicLink({
token,
current_url: currentUrl,
mode: 'customer'
});
if (result.valid) {
setAccessState('valid');
setPersonId(result.person_profile_id || null);
setLinkData(result.data || null);
return;
}
}
setAccessState('invalid');
};
validateAccess();
}, [token, adminToken]);
if (accessState === 'loading') return <div>Validating access...</div>;
if (accessState === 'invalid') return <div>Access denied. Invalid or expired link.</div>;
return (
<div>
{isAdmin && <div className="admin-banner">Admin Mode</div>}
<h1>Welcome to your personalized session</h1>
{/* Render personalized content using personId and linkData */}
</div>
);
}Security Notes
- Projects must NOT store tokens - Only store
magic_link_token_idin your database entities - Tokens are URL-bound - A token is only valid when accessed via its designated URL pattern
- Tokens are strong - 32-64 character alphanumeric strings
- Your backend should be the single source of truth for token storage and validation
Non-React and Backend Usage
For Client: Backend Side and API Server: Backend Side usage patterns (including MagicLink usage in edge/API functions and server-only validation), see:
CLIENT_BACKEND.mdAPI_SERVER.md
Troubleshooting
PostHog 401 Unauthorized Errors
If you see console errors like:
GET https://your-api.com/functions/v1/capture-lead-with-address/array/... 401 (Unauthorized)
POST https://your-api.com/functions/v1/capture-lead-with-address/flags/... 401 (Unauthorized)
[PostHog.js] Bad HTTP status: 401 {"status":"error","message":"Unauthorized"}Cause: PostHog is trying to make requests to your lead capture API instead of PostHog's servers.
Solutions:
Don't set
posthogApiHostunless you have a self-hosted PostHog instance:// ❌ Wrong - this will cause 401 errors const trackingConfig = { apiUrl: 'https://your-api.com/leads', posthogApiHost: 'https://your-api.com/leads', // Don't do this! posthogKey: 'phc_...' }; // ✅ Correct - let PostHog use its default servers const trackingConfig = { apiUrl: 'https://your-api.com/leads', // posthogApiHost: not needed for PostHog Cloud posthogKey: 'phc_...' };For PostHog Cloud users (most common):
- Remove or don't set
posthogApiHostin your config - PostHog will automatically use
https://us.i.posthog.com
- Remove or don't set
For self-hosted PostHog users:
const trackingConfig = { apiUrl: 'https://your-api.com/leads', // Your API posthogApiHost: 'https://your-posthog.com', // Your PostHog instance posthogKey: 'phc_...' };
Other Common Issues
Environment Variables Not Loading:
- Make sure your
.envfile is in the project root - Restart your development server after adding new environment variables
- Verify variable names start with
VITE_for Vite projects
PostHog Not Initializing:
- Check browser console for initialization messages
- Verify your PostHog API key format:
phc_... - Make sure
productionDomainsmatches your actual domain
Lead Tracking API Errors:
- Verify
VITE_LEAD_CAPTURE_API_URLpoints to your actual API - Check that
VITE_LEAD_CAPTURE_API_KEYis correctly set - Ensure your API supports the expected request format
License
ISC
