diff --git a/docs/campaign-cart/analytics/best-practices.md b/docs/campaign-cart/analytics/best-practices.md new file mode 100644 index 00000000..7606eab6 --- /dev/null +++ b/docs/campaign-cart/analytics/best-practices.md @@ -0,0 +1,1149 @@ +--- +title: Best Practices +description: Patterns and recommendations for implementing Campaign Cart analytics including configuration strategies, event naming conventions, tracking patterns, and optimization techniques. +sidebar_position: 12 +--- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + +This guide covers patterns and recommendations for implementing Campaign Cart analytics across your application. + +## Configuration Best Practices + +### 1. Use Auto Mode + +Auto mode handles most tracking automatically: + +```javascript +window.nextConfig = { + apiKey: 'your-api-key', + analytics: { + enabled: true, + mode: 'auto' // Automatic tracking + } +}; +``` + +**Auto Mode Tracks:** +- `dl_user_data` - Every page load +- `dl_view_item_list` - Collection/category pages +- `dl_add_to_cart` - Cart additions +- `dl_begin_checkout` - Checkout initiation + +**Manual Tracking Required:** +- Purchase events (order confirmation page) +- Login/signup events +- Custom business events + +### 2. Set storeName for Facebook Pixel + +The `storeName` parameter is required for Facebook Pixel purchase deduplication. Without it, duplicate purchases may be reported: + +```javascript +window.nextConfig = { + apiKey: 'your-api-key', + storeName: 'my-store', // Required for Facebook deduplication + analytics: { + enabled: true, + providers: { + facebook: { + enabled: true, + settings: { pixelId: 'YOUR_PIXEL_ID' } + } + } + } +}; +``` + +Use a consistent, unique store identifier across all environments to prevent duplicate purchase attribution in Facebook Ads Manager. + +### 3. Use Debug Mode in Development + +Enable debug mode only during development to see detailed console logs without polluting production data: + +```javascript +window.nextConfig = { + apiKey: 'your-api-key', + analytics: { + enabled: true, + debug: process.env.NODE_ENV === 'development' // Enable only in dev + } +}; +``` + +**Debug Mode Shows:** +- Event names and data +- Provider routing decisions +- Validation errors +- Transform function results + +### 4. Environment-Specific Configuration + +Use environment variables to manage different configurations across environments: + +```javascript +window.nextConfig = { + apiKey: process.env.REACT_APP_API_KEY, + analytics: { + enabled: process.env.REACT_APP_ANALYTICS_ENABLED === 'true', + debug: process.env.NODE_ENV === 'development', + mode: 'auto', + providers: { + gtm: { enabled: true }, + facebook: { + enabled: true, + settings: { pixelId: process.env.REACT_APP_FACEBOOK_PIXEL_ID } + } + } + } +}; +``` + +## Event Naming Conventions + +### Use Consistent snake_case Format + +Adopt a consistent naming convention across all custom events. Use snake_case (lowercase with underscores): + +```javascript +// Good - Consistent snake_case +'newsletter_subscribe' +'video_completed' +'form_submitted' +'feature_enabled' +'product_reviewed' +'wishlist_added' + +// Avoid - Inconsistent formats +'subscribeNewsletter' // camelCase +'VideoCompleted' // PascalCase +'form-submitted' // kebab-case +'FEATURE_ENABLED' // UPPER_CASE +``` + +### Naming Patterns by Category + +Follow these naming patterns to make event names self-documenting: + + + + +```javascript +// Format: [verb]_[noun] +'newsletter_subscribe' +'video_play' +'form_submit' +'product_add' +'account_create' +'payment_process' +``` + + + + +```javascript +// Format: [noun]_[status] +'video_started' +'video_completed' +'download_finished' +'upload_error' +'sync_failed' +``` + + + + +```javascript +// Format: [noun]_[verb]_[context] +'product_view_search' +'product_add_recommendation' +'video_complete_onboarding' +'form_submit_footer' +'checkout_error_payment' +``` + + + + +## Tracking Patterns + +### 1. Track Purchase Events ASAP + +Track purchase events on the order confirmation page immediately when the page loads, before users navigate away: + +```javascript +// On order confirmation page +window.addEventListener('DOMContentLoaded', () => { + // Get order data from page or API + const orderData = window.__ORDER_DATA__ || getOrderFromAPI(); + + if (orderData) { + // Track immediately + next.trackPurchase({ + id: orderData.orderId, + total: orderData.total, + currency: orderData.currency, + items: orderData.items + }); + } +}); +``` + +Users often navigate away quickly. Track immediately to capture the event before they leave. + +### 2. Track Before Navigation + +For events that might cause navigation, track before the navigation occurs: + +```javascript +// Bad - Event might not send before navigation +document.getElementById('checkout-btn').addEventListener('click', () => { + next.trackBeginCheckout(); + window.location.href = '/checkout'; // May interrupt tracking +}); + +// Good - Track with callback +document.getElementById('checkout-btn').addEventListener('click', (e) => { + e.preventDefault(); + next.trackBeginCheckout(); + // Give the event time to send + setTimeout(() => { + window.location.href = '/checkout'; + }, 100); +}); + +// Better - Use async tracking +document.getElementById('checkout-btn').addEventListener('click', async (e) => { + e.preventDefault(); + await window.NextAnalytics.trackAsync({ + event: 'checkout_initiated', + from_page: window.location.pathname + }); + window.location.href = '/checkout'; +}); +``` + +### 3. Include Context in Events + +Provide context data with custom events: + +```javascript +// Good - With context +window.NextAnalytics.track({ + event: 'video_completed', + video_id: 'intro-video', + video_title: 'Product Introduction', + video_duration: 120, + video_category: 'onboarding', + user_watched_percent: 100, + completion_time_seconds: 118, + playback_quality: '720p' +}); + +// Avoid - Minimal context +window.NextAnalytics.track({ + event: 'video_completed' + // Missing critical context +}); +``` + +### 4. Track User Journey Flows + +For multi-step flows, track key checkpoints using consistent flow IDs: + +```javascript +// Generate a unique flow ID +const flowId = generateUUID(); + +// Track flow start +window.NextAnalytics.track({ + event: 'checkout_flow_started', + flow_id: flowId, + entry_point: 'cart_page', + cart_value: cartTotal, + item_count: cartItems.length +}); + +// Track each step +window.NextAnalytics.track({ + event: 'checkout_step_completed', + flow_id: flowId, + step: 'shipping_info', + step_number: 1, + duration_ms: Date.now() - stepStartTime +}); + +// Track completion or abandonment +window.NextAnalytics.track({ + event: 'checkout_flow_completed', + flow_id: flowId, + steps_completed: 4, + total_duration_ms: Date.now() - flowStartTime, + conversion: true +}); +``` + +### 5. Track Errors and Edge Cases + +Capture error events for debugging and monitoring: + +```javascript +// Track checkout errors +window.NextAnalytics.track({ + event: 'checkout_error', + error_code: 'PAYMENT_DECLINED', + error_message: 'Your card was declined', + step: 'payment_processing', + attempted_amount: 99.99 +}); + +// Track form validation failures +window.NextAnalytics.track({ + event: 'form_validation_error', + form_id: 'contact_form', + field_errors: ['email', 'phone'], + error_count: 2 +}); + +// Track API failures +window.NextAnalytics.track({ + event: 'api_call_failed', + endpoint: '/api/orders', + status_code: 500, + retry_attempt: 1, + error_type: 'server_error' +}); +``` + +## Error Handling + +### Wrap Tracking in Try-Catch Blocks + +Never let analytics errors break your application functionality: + +```javascript +function trackAnalyticsEvent(eventName, eventData) { + try { + window.NextAnalytics.track({ + event: eventName, + ...eventData + }); + } catch (error) { + // Log the error for monitoring + console.error('Analytics tracking failed:', { + event: eventName, + error: error.message, + stack: error.stack + }); + + // Don't re-throw - continue with app functionality + // Consider sending to error tracking service + if (window.errorReporter) { + window.errorReporter.captureException(error); + } + } +} + +// Usage +trackAnalyticsEvent('form_submitted', { + form_id: 'contact', + fields_count: 5 +}); +``` + +### Implement Graceful Degradation + +Ensure the app works even if analytics fails: + +```javascript +// Wrap the entire initialization +try { + window.nextConfig = { + apiKey: 'your-api-key', + analytics: { enabled: true } + }; +} catch (error) { + console.warn('Analytics initialization failed:', error); + // App continues to work without analytics +} + +// For critical tracking, use a wrapper +async function trackPurchaseWithFallback(orderData) { + try { + await window.NextAnalytics.trackAsync({ + event: 'purchase', + ...orderData + }); + } catch (error) { + console.error('Failed to track purchase:', error); + + // Fallback: Send to error service + if (window.errorReporter) { + window.errorReporter.captureException(error, { + tags: { event: 'purchase_tracking_failed' }, + extra: { orderData } + }); + } + } +} +``` + +## Performance Optimization + +### 1. Batch Events + +For multiple events in quick succession, batch them together: + +```javascript +// Avoid - Multiple individual calls +items.forEach(item => { + window.NextAnalytics.track({ + event: 'item_viewed', + item_id: item.id + }); +}); + +// Better - Batch in a single event +window.NextAnalytics.track({ + event: 'items_batch_viewed', + items: items.map(item => ({ + item_id: item.id, + item_title: item.title + })), + batch_size: items.length, + timestamp: Date.now() +}); +``` + +### 2. Lazy Load Analytics + +For non-critical tracking, defer analytics initialization: + +```javascript +// Defer analytics to idle time +if ('requestIdleCallback' in window) { + requestIdleCallback(() => { + initializeAnalytics(); + }); +} else { + // Fallback for browsers without requestIdleCallback + setTimeout(initializeAnalytics, 2000); +} + +function initializeAnalytics() { + window.nextConfig = { + apiKey: 'your-api-key', + analytics: { enabled: true } + }; +} +``` + +### 3. Use Appropriate API Based on Timing + +Choose the API that matches your timing needs: + +```javascript +// Immediate tracking (synchronous) +next.trackAddToCart('item-123', 1); + +// For critical events that must complete +await window.NextAnalytics.trackAsync({ + event: 'purchase_confirmation', + order_id: 'ORD-12345' +}); + +// For background tracking +window.NextAnalytics.track({ + event: 'page_interaction', + interaction_type: 'scroll' + // Fires in background, doesn't block +}); +``` + +### 4. Debounce Frequent Events + +For high-frequency events, debounce to reduce overhead: + +```javascript +// Debounce scroll tracking +let scrollTimeout; +window.addEventListener('scroll', () => { + clearTimeout(scrollTimeout); + scrollTimeout = setTimeout(() => { + window.NextAnalytics.track({ + event: 'page_scrolled', + scroll_depth: (window.scrollY / document.body.scrollHeight) * 100 + }); + }, 1000); // Track once per second max +}); + +// Debounce resize events +let resizeTimeout; +window.addEventListener('resize', () => { + clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(() => { + window.NextAnalytics.track({ + event: 'viewport_changed', + width: window.innerWidth, + height: window.innerHeight + }); + }, 500); +}); +``` + +## Custom Events Best Practices + +### 1. Use EventBuilder for E-commerce Events + +Use EventBuilder for e-commerce events to ensure GA4 compliance: + +```javascript +import { EventBuilder, dataLayer } from '@/utils/analytics'; + +// Track wishlist addition +const item = { + id: 'pkg-123', + title: 'Premium Package', + price: 99.99, + category: 'packages' +}; + +const event = EventBuilder.createEvent('wishlist_add', { + ecommerce: { + currency: EventBuilder.getCurrency(), + items: [EventBuilder.formatEcommerceItem(item, 0)] + } +}); + +dataLayer.push(event); +``` + +### 2. Use Transform Functions for Data Enrichment + +Enrich events globally without modifying every tracking call: + +```javascript +import { dataLayer } from '@/utils/analytics'; + +// Add context to all events +dataLayer.setTransformFunction((event) => { + // Add app metadata + event.app_version = window.APP_VERSION; + event.environment = window.ENV; + + // Add user context + if (window.currentUser) { + event.user_id = window.currentUser.id; + event.user_tier = window.currentUser.tier; + } + + // Add session data + event.session_duration = Date.now() - window.sessionStartTime; + + return event; +}); +``` + +### 3. Filter Events Strategically + +Use transform functions to filter out unwanted events: + +```javascript +dataLayer.setTransformFunction((event) => { + // Filter out internal events in production + const internalEvents = ['internal_test', 'dev_event']; + if (internalEvents.includes(event.event) && window.ENV === 'production') { + return null; // Event won't be sent + } + + // Filter test users + if (event.user_properties?.customer_email?.includes('@test.com')) { + return null; + } + + return event; +}); +``` + +### 4. Validate Events in Development + +Enable event validation to catch issues early: + +```javascript +if (process.env.NODE_ENV === 'development') { + import { EventValidator } from '@/utils/analytics'; + + const validator = new EventValidator(true); + + // Create a wrapper function + const trackWithValidation = (eventName, eventData) => { + const event = { + event: eventName, + ...eventData + }; + + // Validate before tracking + const result = validator.validateEvent(event); + if (!result.valid) { + console.error('Invalid event:', { + event: eventName, + errors: result.errors, + data: eventData + }); + return; // Don't send invalid events + } + + window.NextAnalytics.track(event); + }; + + // Use throughout app + window.trackWithValidation = trackWithValidation; +} +``` + +## Provider-Specific Best Practices + +### Google Tag Manager + + + + +```javascript +window.nextConfig = { + analytics: { + providers: { + gtm: { + enabled: true + // Optional: custom settings + } + } + } +}; +``` + + + + +1. **Test in GTM Preview Mode**: Always test events in GTM's preview mode before publishing containers. + +```javascript +// Verify GTM is loading +console.log('GTM ready:', typeof dataLayer !== 'undefined'); + +// Check event in Preview +window.NextAnalytics.track({ + event: 'test_event', + timestamp: new Date().toISOString() +}); +``` + +2. **Use GTM's Debug View**: Enable GTM's Real-time Debug View in Google Analytics for verification. + +3. **Version Control Containers**: Use GTM's version history feature to manage changes. + + + + +### Facebook Pixel + + + + +```javascript +window.nextConfig = { + storeName: 'my-store', // Critical for deduplication + analytics: { + providers: { + facebook: { + enabled: true, + settings: { + pixelId: 'YOUR_PIXEL_ID' + } + } + } + } +}; +``` + + + + +1. **Set storeName**: Required for purchase deduplication across web and app. + +```javascript +window.nextConfig = { + storeName: 'consistent-store-name', // Use same name in app SDK + analytics: { ... } +}; +``` + +2. **Track Purchase Immediately**: Track purchases on confirmation page before navigation. + +```javascript +window.addEventListener('DOMContentLoaded', () => { + next.trackPurchase(orderData); +}); +``` + +3. **Use Correct Currency**: Ensure currency matches your Facebook Ads account settings. + +```javascript +window.nextConfig = { + currency: 'USD', // Match your ad account currency + analytics: { ... } +}; +``` + +4. **Verify in Pixel Helper**: Use Facebook's Pixel Helper browser extension to verify events. + + + + +### RudderStack + + + + +```javascript +window.nextConfig = { + analytics: { + providers: { + rudderstack: { + enabled: true, + settings: { + writeKey: 'YOUR_WRITE_KEY', + dataPlaneURL: 'https://your-dataplane.rudderstack.com' + } + } + } + } +}; +``` + + + + +1. **Identify Users**: Set user context for better tracking. + +```javascript +// After login +window.NextAnalytics.setUserProperties({ + user_id: user.id, + email: user.email, + name: user.name, + plan: user.plan +}); +``` + +2. **Use Traits**: Attach user traits for segmentation. + +```javascript +window.NextAnalytics.track({ + event: 'feature_used', + feature: 'advanced_search', + traits: { + plan: 'premium', + company: 'acme-corp' + } +}); +``` + +3. **Monitor Transformations**: Use RudderStack's transformations for event routing. + + + + +### Custom Webhooks + + + + +```javascript +window.nextConfig = { + analytics: { + providers: { + custom: { + enabled: true, + settings: { + endpoint: 'https://api.yourapp.com/events', + batchSize: 10, + flushInterval: 5000 + } + } + } + } +}; +``` + + + + +1. **Implement Retry Logic**: Handle failed requests gracefully. + +```javascript +// Server-side webhook handler +app.post('/events', async (req, res) => { + const events = req.body.events; + + try { + await processEvents(events); + res.json({ success: true, processedCount: events.length }); + } catch (error) { + // Client will retry + res.status(500).json({ error: 'Processing failed' }); + } +}); +``` + +2. **Validate Signatures**: Verify webhook authenticity if sensitive. + +```javascript +// Client-side: Add signature to webhook +const crypto = require('crypto'); +const signature = crypto + .createHmac('sha256', SECRET_KEY) + .update(JSON.stringify(events)) + .digest('hex'); + +// Include in request headers +``` + +3. **Handle Batch Events**: Custom webhooks receive batched events for efficiency. + +```javascript +// Webhook receives multiple events +{ + "events": [ + { "event": "view_item", ... }, + { "event": "add_to_cart", ... }, + { "event": "begin_checkout", ... } + ] +} +``` + + + + +## Testing Strategies + +### 1. Development Testing Checklist + +Use this checklist when testing analytics implementation: + +```javascript +// 1. Verify initialization +console.assert( + typeof window.NextAnalytics !== 'undefined', + 'NextAnalytics not initialized' +); + +// 2. Check debug mode +window.NextAnalytics.setDebugMode(true); + +// 3. Test simple event +window.NextAnalytics.track({ + event: 'test_event', + timestamp: new Date().toISOString() +}); + +// 4. Test add to cart +next.trackAddToCart('test-item', 1); + +// 5. Test begin checkout +next.trackBeginCheckout(); + +// 6. Verify in console +const status = window.NextAnalytics.getStatus(); +console.log('Analytics status:', status); +``` + +### 2. Browser Console Testing + +Monitor events in real-time using the browser console: + +```javascript +// Enable debug mode +window.NextAnalytics.setDebugMode(true); + +// Monitor all events +window.addEventListener('NextAnalyticsEvent', (event) => { + console.log('Event tracked:', event.detail); +}); + +// Check data layer +console.log('Data layer:', window.NextDataLayer); + +// Verify provider routing +const status = window.NextAnalytics.getStatus(); +console.table(status.providers); +``` + +### 3. Testing Purchase Flow + +Test the complete purchase flow: + +```javascript +// Simulate checkout initiation +window.addEventListener('DOMContentLoaded', () => { + // Step 1: View items + next.trackViewItem('item-1'); + next.trackViewItem('item-2'); + + // Step 2: Add to cart + next.trackAddToCart('item-1', 1); + next.trackAddToCart('item-2', 2); + + // Step 3: Begin checkout + next.trackBeginCheckout(); + + // Step 4: Track purchase + next.trackPurchase({ + id: 'ORDER_' + Date.now(), + total: 199.99, + currency: 'USD', + items: [ + { id: 'item-1', title: 'Item 1', price: 99.99 }, + { id: 'item-2', title: 'Item 2', price: 100.00 } + ] + }); +}); +``` + +### 4. Error Scenario Testing + +Test error handling: + +```javascript +// Test error tracking +window.NextAnalytics.track({ + event: 'payment_error', + error_code: 'CARD_DECLINED', + error_message: 'Your card was declined', + attempted_amount: 99.99 +}); + +// Test recovery +window.NextAnalytics.track({ + event: 'payment_retry', + retry_attempt: 2, + original_error: 'CARD_DECLINED' +}); + +// Verify error is tracked but doesn't break app +console.assert( + document.body !== null, + 'App broken after tracking error' +); +``` + +## Production Checklist + +Before deploying analytics to production, verify: + + + + +- [ ] Analytics enabled in production config +- [ ] Debug mode disabled (set based on environment) +- [ ] storeName set for Facebook Pixel +- [ ] All required API keys configured via environment variables +- [ ] Mode set to 'auto' (unless specific reason for manual) +- [ ] All required providers enabled +- [ ] Custom webhook endpoints verified if using custom provider + + + + +- [ ] All purchase tracking implemented on confirmation page +- [ ] All custom events use consistent snake_case naming +- [ ] Error handling wrapped around all tracking calls +- [ ] Critical events (purchase, signup) have error handling +- [ ] Transform functions added for data enrichment +- [ ] Event validation enabled in development +- [ ] No PII tracked (emails, phone numbers) in non-critical events + + + + +- [ ] Tested with GTM Debug View (if using GTM) +- [ ] Tested with Facebook Pixel Helper (if using Facebook) +- [ ] Verified purchase events on test transaction +- [ ] Verified custom events in GA4 Real-time Report +- [ ] Tested error scenarios (failed payment, form errors) +- [ ] Verified browser console has no analytics errors +- [ ] Cross-browser testing completed +- [ ] Mobile testing completed + + + + +- [ ] Set up alerting for tracking failures +- [ ] Monitor event delivery rates +- [ ] Check for unexpected event spikes +- [ ] Review GA4 real-time dashboard for first 24 hours +- [ ] Verify revenue data matches internal records +- [ ] Monitor error event rates +- [ ] Set up weekly data quality checks + + + + +- [ ] Document all custom events and their parameters +- [ ] Document any event transform functions +- [ ] Document custom webhook endpoint schema +- [ ] Document any environment-specific configuration +- [ ] Create runbook for disabling analytics if needed +- [ ] Document provider credentials location +- [ ] Keep change log of analytics modifications + + + + +## Common Patterns and Examples + +### User Journey Tracking + +Track complete user flows with contextual data: + +```javascript +class UserJourneyTracker { + constructor() { + this.journeyId = this.generateId(); + this.events = []; + this.startTime = Date.now(); + } + + generateId() { + return `journey_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; + } + + trackStep(stepName, stepData = {}) { + const eventData = { + event: `journey_${stepName}`, + journey_id: this.journeyId, + step_timestamp: Date.now(), + elapsed_time: Date.now() - this.startTime, + ...stepData + }; + + window.NextAnalytics.track(eventData); + this.events.push(eventData); + } + + complete(metadata = {}) { + this.trackStep('completed', { + total_duration: Date.now() - this.startTime, + total_events: this.events.length, + ...metadata + }); + } + + abandon(reason, metadata = {}) { + this.trackStep('abandoned', { + abandon_reason: reason, + last_step: this.events[this.events.length - 1]?.event, + ...metadata + }); + } +} + +// Usage +const journey = new UserJourneyTracker(); +journey.trackStep('initiated', { entry_point: 'homepage' }); +journey.trackStep('browsing', { category_viewed: 'premium-packages' }); +journey.trackStep('added_to_cart', { product_id: 'pkg-123' }); +journey.complete({ conversion: true }); +``` + +### Feature Flag Analytics + +Track feature flag usage: + +```javascript +function trackFeatureUsage(featureName, enabled, metadata = {}) { + window.NextAnalytics.track({ + event: 'feature_flag_evaluated', + feature_name: featureName, + enabled: enabled, + timestamp: Date.now(), + ...metadata + }); +} + +// Usage +if (shouldEnableNewCheckout()) { + trackFeatureUsage('new_checkout_ui', true, { + variant: 'experimental', + user_segment: 'premium' + }); + renderNewCheckout(); +} else { + trackFeatureUsage('new_checkout_ui', false, { + reason: 'user_segment_not_matched' + }); + renderLegacyCheckout(); +} +``` + +### Conversion Funnel Tracking + +Track conversion funnel steps: + +```javascript +class FunnelTracker { + constructor(funnelName) { + this.funnelName = funnelName; + this.funnelId = `funnel_${Date.now()}`; + this.steps = []; + } + + trackStep(stepName, stepNumber, metadata = {}) { + const event = { + event: `funnel_step`, + funnel_name: this.funnelName, + funnel_id: this.funnelId, + step_name: stepName, + step_number: stepNumber, + steps_completed: stepNumber, + ...metadata + }; + + window.NextAnalytics.track(event); + this.steps.push(event); + } + + trackDropoff(stepName, stepNumber, reason, metadata = {}) { + window.NextAnalytics.track({ + event: `funnel_dropoff`, + funnel_name: this.funnelName, + funnel_id: this.funnelId, + dropped_at_step: stepName, + step_number: stepNumber, + dropoff_reason: reason, + steps_completed: stepNumber - 1, + ...metadata + }); + } +} + +// Usage +const checkoutFunnel = new FunnelTracker('checkout'); +checkoutFunnel.trackStep('shipping', 1, { state_entered: 'CA' }); +checkoutFunnel.trackStep('payment', 2, { payment_method: 'credit_card' }); +checkoutFunnel.trackDropoff('confirmation', 3, 'payment_failed', { + error_code: 'CARD_DECLINED' +}); +``` + +## Summary + +Following these practices: +- Events reach their destinations consistently +- Events contain context for analysis +- Analytics doesn't slow down your application +- Consistent patterns make code easier to understand +- Error handling and configuration provide stable systems + +Review this guide regularly and update your implementation as needed. diff --git a/docs/campaign-cart/analytics/configuration.md b/docs/campaign-cart/analytics/configuration.md new file mode 100644 index 00000000..c6831142 --- /dev/null +++ b/docs/campaign-cart/analytics/configuration.md @@ -0,0 +1,510 @@ +--- +title: Configuration & Modes +description: Complete analytics configuration reference including tracking modes, provider settings, and runtime options. +sidebar_position: 2 +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Configure the analytics system through `window.nextConfig` to control tracking behavior, enable providers, and set operational modes. + +## Basic Configuration + +```javascript +window.nextConfig = { + apiKey: 'your-api-key', + storeName: 'my-store', // Important for Facebook deduplication + analytics: { + enabled: true, + debug: false, + mode: 'auto', + providers: { + gtm: { enabled: true }, + facebook: { + enabled: true, + settings: { pixelId: 'YOUR_PIXEL_ID' } + }, + rudderstack: { enabled: true }, + custom: { + enabled: true, + settings: { endpoint: 'https://api.yourapp.com/analytics' } + } + } + } +}; +``` + +## Configuration Options + +### Top-Level Options + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `enabled` | boolean | `false` | Master switch for all analytics tracking | +| `debug` | boolean | `false` | Enable detailed console logging for troubleshooting | +| `mode` | string | `'auto'` | Tracking mode: `'auto'` or `'manual'` | +| `providers` | object | `{}` | Provider configurations (GTM, Facebook, RudderStack, custom) | + +### Store-Level Options + +| Option | Type | Description | +|--------|------|-------------| +| `storeName` | string | **Required for Facebook Pixel** - Used for purchase deduplication | + +## Tracking Modes + +### Auto Mode (Recommended) + +Auto mode automatically tracks common e-commerce events without manual intervention. + +```javascript +analytics: { + mode: 'auto' +} +``` + +**Automatically tracked events:** + +| Event | When | Trigger | +|------|------|---------| +| `dl_user_data` | First event on page load | Analytics initialization in auto mode | +| `dl_view_item_list` | Page load or when products are detected | ViewItemListTracker scans for elements with `data-next-package-id` | +| `dl_view_item` | Single product view | When only one product is detected on page (via ViewItemListTracker) | +| `dl_add_to_cart` | Item added to cart | `cart:item-added` SDK event (auto-tracked via AutoEventListener) | +| `dl_remove_from_cart` | Item removed from cart | `cart:item-removed` SDK event (auto-tracked via AutoEventListener) | +| `dl_begin_checkout` | Checkout form initializes | CheckoutFormEnhancer detects checkout form and cart has items | +| `dl_add_shipping_info` | Shipping form submission | Shipping information entered | +| `dl_add_payment_info` | Payment form submission | After all credit card fields are valid and complete or when the express checkout button is clicked | +| `dl_purchase` | Order completed | `order:completed` SDK event (auto-tracked via AutoEventListener) | +| `dl_viewed_upsell` | Upsell offer displayed | `upsell:viewed` SDK event (auto-tracked) | +| `dl_accepted_upsell` | User accepts upsell | `upsell:accepted` or `upsell:added` SDK events (auto-tracked) | + +**Events requiring manual tracking:** +- `dl_sign_up` - User registration +- `dl_login` - User login +- Custom business events + +**Event Queue:** + +Events with redirects (like `dl_purchase`) are queued to `sessionStorage` and automatically fired after navigation completes. + +**When to use auto mode:** +- Standard e-commerce implementations +- Minimal custom tracking requirements +- Want to minimize code complexity +- Need consistent tracking across pages + +### Manual Mode + +Full control over when and how events are tracked. + +```javascript +analytics: { + mode: 'manual' +} +``` + +**All events must be manually tracked** using the tracking API. + +**When to use manual mode:** +- Need complete control over event timing +- Custom e-commerce flows that don't match standard patterns +- Want to track only specific events +- Implementing custom event logic + +**Example manual tracking:** + +```javascript +// View item +window.NextAnalytics.trackViewItem({ id: '123', title: 'Product', price: 99.99 }); + +// Add to cart +window.NextAnalytics.trackAddToCart({ id: '123', quantity: 1 }); + +// Begin checkout +window.NextAnalytics.trackBeginCheckout(); + +// Purchase +window.NextAnalytics.trackPurchase(orderData); +``` + +## Provider Configuration + +### Google Tag Manager + +```javascript +providers: { + gtm: { + enabled: true, + blockedEvents: ['dl_test_event', 'internal_event'] // Optional + } +} +``` + +**Options:** + +| Option | Type | Description | +|--------|------|-------------| +| `enabled` | boolean | Enable GTM integration | +| `blockedEvents` | string[] | Events to exclude from GTM | + +Events are pushed to `window.dataLayer` and `window.ElevarDataLayer`. + +See [Google Tag Manager Setup](/docs/campaign-cart/analytics/examples/google-tag-manager/) for detailed configuration. + +### Facebook Pixel + +```javascript +storeName: 'my-store', // CRITICAL for deduplication +analytics: { + providers: { + facebook: { + enabled: true, + settings: { + pixelId: 'YOUR_PIXEL_ID' + }, + blockedEvents: [] // Optional + } + } +} +``` + +**Options:** + +| Option | Type | Description | +|--------|------|-------------| +| `enabled` | boolean | Enable Facebook Pixel integration | +| `settings.pixelId` | string | **Required** - Facebook Pixel ID | +| `blockedEvents` | string[] | Events to exclude from Facebook | + +:::caution +Always set `storeName` in your config when using Facebook Pixel to ensure proper purchase deduplication with server-side events. +::: + +See [Facebook Pixel Setup](/docs/campaign-cart/analytics/examples/facebook-pixel/) for event mapping and deduplication details. + +### RudderStack + +```javascript +providers: { + rudderstack: { + enabled: true, + blockedEvents: [] // Optional + } +} +``` + +**Options:** + +| Option | Type | Description | +|--------|------|-------------| +| `enabled` | boolean | Enable RudderStack integration | +| `blockedEvents` | string[] | Events to exclude from RudderStack | + +Events are mapped to Segment specification format. + +See [RudderStack Setup](/docs/campaign-cart/analytics/examples/rudderstack/) for event mapping details. + +### Custom Webhook + +```javascript +providers: { + custom: { + enabled: true, + settings: { + // Required + endpoint: 'https://api.yourapp.com/analytics', + + // Optional headers + headers: { + 'Authorization': 'Bearer YOUR_TOKEN', + 'Content-Type': 'application/json', + 'X-Store-ID': 'store-123' + }, + + // Batching settings + batchSize: 10, // Events per batch (default: 10) + batchIntervalMs: 5000, // Max time before sending (default: 5000ms) + + // Retry configuration + maxRetries: 3, // Retry attempts (default: 3) + retryDelayMs: 1000, // Initial retry delay (default: 1000ms) + + // Transform function + transformFunction: (event) => { + event.app_version = '1.0.0'; + return event; + } + }, + blockedEvents: [] // Optional + } +} +``` + +**Options:** + +| Option | Type | Description | +|--------|------|-------------| +| `enabled` | boolean | Enable custom webhook integration | +| `settings.endpoint` | string | **Required** - Your webhook URL | +| `settings.headers` | object | Custom HTTP headers | +| `settings.batchSize` | number | Events per batch (default: 10) | +| `settings.batchIntervalMs` | number | Max batch wait time (default: 5000ms) | +| `settings.maxRetries` | number | Retry attempts on failure (default: 3) | +| `settings.retryDelayMs` | number | Initial retry delay with exponential backoff (default: 1000ms) | +| `settings.transformFunction` | function | Transform events before sending | +| `blockedEvents` | string[] | Events to exclude from webhook | + +See [Custom Webhook Setup](/docs/campaign-cart/analytics/examples/custom-webhook/) for batching, retry logic, and implementation details. + +## Multiple Providers + +Enable all providers simultaneously - each operates independently: + +```javascript +providers: { + gtm: { enabled: true }, + facebook: { enabled: true, settings: { pixelId: 'xxx' } }, + rudderstack: { enabled: true }, + custom: { enabled: true, settings: { endpoint: 'https://...' } } +} +``` + +**How it works:** +- Each event sent to ALL enabled providers +- Providers operate independently (one failure doesn't affect others) +- Events always stored in `NextDataLayer` regardless of provider status +- Debug mode shows which providers received each event + +## Runtime Configuration + +### Enable Debug Mode + + + + +```javascript +analytics: { + debug: true +} +``` + + + + +```javascript +// Enable at runtime +window.NextAnalytics.setDebugMode(true); + +// Disable +window.NextAnalytics.setDebugMode(false); +``` + + + + +### Check Analytics Status + +```javascript +const status = window.NextAnalytics.getStatus(); +console.log(status); + +// Output: +// { +// enabled: true, +// mode: 'auto', +// providers: ['GTM', 'Facebook', 'RudderStack'], +// eventsTracked: 15, +// debugMode: false +// } +``` + +### Disable Tracking Temporarily + +Add `?ignore=true` to URL: + +``` +https://yoursite.com?ignore=true +``` + +This disables ALL tracking for the entire session. + +To clear ignore flag: +```javascript +window.NextAnalyticsClearIgnore(); +``` + +## Configuration Examples + +### Minimal Configuration (GTM Only) + +```javascript +window.nextConfig = { + apiKey: 'your-api-key', + analytics: { + enabled: true, + mode: 'auto', + providers: { + gtm: { enabled: true } + } + } +}; +``` + +### E-commerce with Facebook Ads + +```javascript +window.nextConfig = { + apiKey: 'your-api-key', + storeName: 'my-store', + analytics: { + enabled: true, + mode: 'auto', + providers: { + gtm: { enabled: true }, + facebook: { + enabled: true, + settings: { pixelId: 'YOUR_PIXEL_ID' } + } + } + } +}; +``` + +### Development Configuration + +```javascript +window.nextConfig = { + apiKey: 'your-api-key', + analytics: { + enabled: true, + debug: true, // Enable debug logging + mode: 'auto', + providers: { + gtm: { enabled: false }, // Disable GTM in dev + custom: { + enabled: true, + settings: { + endpoint: 'http://localhost:3000/analytics', + headers: { 'X-Dev-Mode': 'true' } + } + } + } + } +}; +``` + +### Enterprise Multi-Platform Setup + +```javascript +window.nextConfig = { + apiKey: 'your-api-key', + storeName: 'enterprise-store', + analytics: { + enabled: true, + mode: 'auto', + providers: { + gtm: { + enabled: true, + blockedEvents: ['internal_test', 'dev_event'] + }, + facebook: { + enabled: true, + settings: { pixelId: 'PROD_PIXEL_ID' } + }, + rudderstack: { enabled: true }, + custom: { + enabled: true, + settings: { + endpoint: 'https://api.yourapp.com/analytics', + headers: { + 'Authorization': 'Bearer YOUR_PRODUCTION_TOKEN', + 'X-Environment': 'production', + 'X-Region': 'US-WEST' + }, + batchSize: 20, + batchIntervalMs: 3000, + maxRetries: 5, + transformFunction: (event) => { + // Add custom metadata + event.app_version = window.APP_VERSION; + event.environment = 'production'; + event.region = 'us-west-1'; + + // Filter sensitive data + if (event.user_properties?.customer_phone) { + delete event.user_properties.customer_phone; + } + + return event; + } + } + } + } + } +}; +``` + +## Configuration Best Practices + +### 1. Always Set storeName for Facebook + +```javascript +{ + storeName: 'my-store', // Required for purchase deduplication + analytics: { + providers: { + facebook: { enabled: true, settings: { pixelId: 'xxx' } } + } + } +} +``` + +### 2. Use Auto Mode for Standard Implementations + +```javascript +analytics: { + mode: 'auto' // Handles 90% of tracking automatically +} +``` + +### 3. Enable Debug in Development + +```javascript +analytics: { + debug: process.env.NODE_ENV === 'development' +} +``` + +### 4. Block Internal Events + +```javascript +providers: { + gtm: { + enabled: true, + blockedEvents: ['internal_test', 'dev_event', 'debug_event'] + } +} +``` + +### 5. Use Transform Functions for Custom Data + +```javascript +custom: { + settings: { + transformFunction: (event) => { + event.custom_field = getCustomData(); + return event; + } + } +} +``` + +## Related Documentation + +- [Tracking API Reference](/docs/campaign-cart/analytics/tracking-api/) - Complete API documentation +- [Examples Overview](/docs/campaign-cart/analytics/examples/) - Provider-specific setup guides +- [Event Reference](/docs/campaign-cart/analytics/events/) - Complete event schemas +- [Debugging Guide](/docs/campaign-cart/analytics/debugging/) - Troubleshooting and testing + diff --git a/docs/campaign-cart/analytics/custom-events.md b/docs/campaign-cart/analytics/custom-events.md new file mode 100644 index 00000000..fe2ba053 --- /dev/null +++ b/docs/campaign-cart/analytics/custom-events.md @@ -0,0 +1,610 @@ +--- +title: Custom Events & Advanced Tracking +description: Create custom analytics events and implement advanced tracking patterns with transform functions. +sidebar_position: 10 +--- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + +Track custom business events and transform events before they're sent to providers. + +## Simple Custom Events + +Track custom events with arbitrary data: + +```javascript +window.NextAnalytics.track({ + event: 'newsletter_subscribe', + email: 'user@example.com', + list_name: 'Weekly Newsletter', + source: 'footer_form' +}); +``` + +Custom events are sent to **all enabled providers** and stored in `window.NextDataLayer`. + +### Automatic Enrichment + +Every event is automatically enriched with: +- **event_id** - Unique event identifier +- **event_time** - ISO timestamp +- **user_properties** - Current user data (visitor_type, customer_email, etc.) +- **attribution** - UTM parameters, funnel, affiliate data +- **session_id** - Current session identifier +- **Context** - page_location, page_title, user_agent, screen_resolution, viewport_size + +You don't need to add these manually - they're included automatically. + +### Common Custom Event Examples + + + + +```javascript +// Newsletter subscription +window.NextAnalytics.track({ + event: 'newsletter_subscribe', + email: 'user@example.com', + list: 'weekly_digest', + source: 'footer' +}); +``` + + + + +```javascript +// Video engagement +window.NextAnalytics.track({ + event: 'video_played', + video_id: 'intro-demo', + video_title: 'Product Introduction', + duration: 120 +}); + +window.NextAnalytics.track({ + event: 'video_completed', + video_id: 'intro-demo', + percent_watched: 100 +}); +``` + + + + +```javascript +// Form submission +window.NextAnalytics.track({ + event: 'form_submitted', + form_id: 'contact', + form_type: 'contact_us', + fields_filled: 5 +}); +``` + + + + +```javascript +// Feature usage +window.NextAnalytics.track({ + event: 'feature_used', + feature_name: 'product_comparison', + items_compared: 3, + user_id: currentUserId +}); +``` + + + + +## E-commerce Custom Events + +For custom e-commerce events, include product data using the GA4 ecommerce format: + +```javascript +window.NextAnalytics.track({ + event: 'wishlist_add', + ecommerce: { + currency: 'USD', + value: 99.99, + items: [{ + item_id: 'SKU-123', + item_name: 'Product Name', + item_category: 'Electronics', + price: 99.99, + quantity: 1 + }] + } +}); +``` + +### Custom E-commerce Examples + + + + +```javascript +function trackWishlistAdd(item) { + window.NextAnalytics.track({ + event: 'wishlist_add', + ecommerce: { + currency: 'USD', + value: item.price, + items: [{ + item_id: item.sku, + item_name: item.name, + item_category: item.category, + price: item.price, + quantity: 1 + }] + } + }); +} +``` + + + + +```javascript +function trackProductComparison(products) { + window.NextAnalytics.track({ + event: 'product_compare', + comparison_count: products.length, + ecommerce: { + currency: 'USD', + items: products.map((product, index) => ({ + item_id: product.sku, + item_name: product.name, + item_category: product.category, + price: product.price, + quantity: 1, + index: index + })) + } + }); +} +``` + + + + +```javascript +function trackQuickView(item) { + window.NextAnalytics.track({ + event: 'quick_view', + view_type: 'modal', + ecommerce: { + currency: 'USD', + value: item.price, + items: [{ + item_id: item.sku, + item_name: item.name, + item_category: item.category, + price: item.price, + quantity: 1 + }] + } + }); +} +``` + + + + +## Transform Functions + +Modify ALL events before they're sent to providers using transform functions. + +### Using DataLayerManager + +```javascript +// Access the data layer manager +const dataLayer = window.NextDataLayerManager; + +dataLayer.setTransformFunction((event) => { + // Add custom fields to every event + event.app_version = '1.0.0'; + event.environment = 'production'; + event.custom_user_id = getCurrentUserId(); + + // Filter out events + if (event.event === 'internal_test') { + return null; // Event won't be sent + } + + // Modify specific events + if (event.event === 'dl_purchase') { + event.ecommerce.custom_order_type = getOrderType(); + event.fulfillment_center = 'US-WEST'; + } + + return event; +}); +``` + +### Global Transform Function + +You can also set a transform function globally in the window: + +```javascript +window.NextDataLayerTransformFn = function(event) { + event.custom_property = 'value'; + return event; +}; +``` + +This runs before the configured transform function. + +### Transform Function Use Cases + + + + +```javascript +window.NextDataLayerTransformFn = function(event) { + // Add app context to all events + event.app_version = window.APP_VERSION; + event.environment = window.ENV; + event.build_number = window.BUILD_NUM; + + // Add user context + if (window.userSession) { + event.session_duration = Date.now() - window.userSession.startTime; + event.page_views = window.userSession.pageViews; + } + + return event; +}; +``` + + + + +```javascript +window.NextDataLayerTransformFn = function(event) { + // Filter out internal events + const internalEvents = ['internal_test', 'dev_event', 'debug']; + if (internalEvents.includes(event.event)) { + return null; // Don't send + } + + // Filter test users + if (event.user_properties?.customer_email?.includes('@test.com')) { + return null; + } + + return event; +}; +``` + + + + +```javascript +window.NextDataLayerTransformFn = function(event) { + // Remove PII in certain environments + if (window.ENV === 'development') { + if (event.user_properties) { + event.user_properties.customer_email = 'redacted@example.com'; + event.user_properties.customer_phone = 'REDACTED'; + delete event.user_properties.customer_address_1; + } + } + + return event; +}; +``` + + + + +```javascript +window.NextDataLayerTransformFn = function(event) { + // Add region-specific data + const region = getUserRegion(); + event.region = region; + event.currency_override = getRegionalCurrency(region); + + // Route high-value purchases + if (event.event === 'dl_purchase' && event.ecommerce.value > 1000) { + event.priority = 'high'; + event.fraud_check_required = true; + } + + return event; +}; +``` + + + + +## Advanced Event Patterns + +### Event Chaining + +Track sequences of related events: + +```javascript +// Start checkout flow +window.NextAnalytics.track({ + event: 'checkout_flow_started', + flow_id: generateFlowId(), + entry_point: 'cart_page' +}); + +// Track each step +window.NextAnalytics.track({ + event: 'checkout_step_completed', + flow_id: currentFlowId, + step: 'shipping_info', + duration_ms: stepDuration +}); + +// Track completion +window.NextAnalytics.track({ + event: 'checkout_flow_completed', + flow_id: currentFlowId, + total_duration_ms: totalDuration, + steps_completed: 4 +}); +``` + +### Conditional Event Tracking + +Track events based on business logic: + +```javascript +function trackCartMilestone(cartValue) { + const milestones = [ + { threshold: 50, name: 'free_shipping_eligible' }, + { threshold: 100, name: 'discount_eligible' }, + { threshold: 200, name: 'premium_tier_reached' } + ]; + + milestones.forEach(milestone => { + if (cartValue >= milestone.threshold && !hasMilestone(milestone.name)) { + window.NextAnalytics.track({ + event: 'cart_milestone', + milestone: milestone.name, + cart_value: cartValue, + threshold: milestone.threshold + }); + + saveMilestone(milestone.name); + } + }); +} +``` + +### Time-based Event Tracking + +Track engagement duration: + +```javascript +class VideoTracker { + constructor(videoId) { + this.videoId = videoId; + this.startTime = Date.now(); + this.milestones = [25, 50, 75, 100]; + this.tracked = new Set(); + } + + trackProgress(percentComplete) { + this.milestones.forEach(milestone => { + if (percentComplete >= milestone && !this.tracked.has(milestone)) { + window.NextAnalytics.track({ + event: 'video_progress', + video_id: this.videoId, + milestone: milestone, + duration_watched: Date.now() - this.startTime + }); + + this.tracked.add(milestone); + } + }); + } + + trackComplete() { + window.NextAnalytics.track({ + event: 'video_completed', + video_id: this.videoId, + total_duration: Date.now() - this.startTime + }); + } +} +``` + +## Best Practices + +### 1. Consistent Event Naming + +Use snake_case for all custom events: + +```javascript +// Good +'newsletter_subscribe' +'video_completed' +'form_submitted' +'feature_enabled' + +// Avoid +'subscribeNewsletter' +'VideoCompleted' +'form-submitted' +'FEATURE_ENABLED' +``` + +### 2. Include Context + +Provide event context: + +```javascript +// Good - With context +window.NextAnalytics.track({ + event: 'video_completed', + video_id: 'intro-video', + video_title: 'Product Introduction', + video_duration: 120, + video_category: 'onboarding', + user_watched_percent: 100, + completion_time_seconds: 118 +}); + +// Avoid - Minimal context +window.NextAnalytics.track({ + event: 'video_completed' +}); +``` + +### 3. Use GA4 Ecommerce Format + +For e-commerce events, use the GA4 standard format: + +```javascript +// Good - GA4 format +window.NextAnalytics.track({ + event: 'wishlist_add', + ecommerce: { + currency: 'USD', + value: 99.99, + items: [{ + item_id: 'SKU-123', + item_name: 'Product Name', + price: 99.99, + quantity: 1 + }] + } +}); + +// Avoid - Non-standard format +window.NextAnalytics.track({ + event: 'wishlist_add', + product: item // Wrong structure +}); +``` + +### 4. Handle Errors Gracefully + +Never let analytics errors break your app: + +```javascript +try { + window.NextAnalytics.track(customEvent); +} catch (error) { + console.error('Analytics tracking failed:', error); + // Don't throw - continue with app functionality +} +``` + +### 5. Debug in Development + +Enable debug mode to see detailed logs: + +```javascript +// In browser console +window.NextAnalytics.setDebugMode(true); + +// Or via config +window.nextConfig = { + analytics: { + debug: true + } +}; +``` + +## Examples + +### Product Recommendation Tracking + +```javascript +function trackRecommendationClick(item, recommendationType) { + window.NextAnalytics.track({ + event: 'recommendation_clicked', + item_id: item.id, + item_name: item.title, + recommendation_type: recommendationType, // 'similar', 'upsell', 'cross-sell' + recommendation_position: item.position, + algorithm: 'collaborative_filtering' + }); +} +``` + +### A/B Test Tracking + +```javascript +function trackExperiment(experimentId, variantId) { + window.NextAnalytics.track({ + event: 'experiment_viewed', + experiment_id: experimentId, + variant_id: variantId, + user_id: getCurrentUserId() + }); +} + +function trackExperimentConversion(experimentId, variantId) { + window.NextAnalytics.track({ + event: 'experiment_converted', + experiment_id: experimentId, + variant_id: variantId, + conversion_value: getCartValue() + }); +} +``` + +### Search Tracking + +```javascript +function trackSearch(query, resultsCount) { + window.NextAnalytics.track({ + event: 'search_performed', + search_query: query, + results_count: resultsCount, + search_filters: getActiveFilters(), + search_sort: getCurrentSort() + }); +} + +function trackSearchResultClick(query, item, position) { + window.NextAnalytics.track({ + event: 'search_result_clicked', + search_query: query, + item_id: item.id, + item_position: position, + results_count: getTotalResults() + }); +} +``` + +## Accessing the Data Layer + +View all tracked events: + +```javascript +// View all events +console.log(window.NextDataLayer); + +// Get event count +const count = window.NextDataLayerManager.getEventCount(); +console.log(`Tracked ${count} events`); + +// Get analytics status +const status = window.NextAnalytics.getStatus(); +console.log(status); +// { +// initialized: true, +// debugMode: false, +// providers: ['GTM', 'Facebook'], +// eventsTracked: 15, +// ignored: false +// } +``` + +## Related Documentation + +- [Analytics Overview](/docs/campaign-cart/analytics/) - Main analytics documentation +- [Tracking API](/docs/campaign-cart/analytics/tracking-api/) - Core tracking methods and events +- [Configuration](/docs/campaign-cart/analytics/configuration/) - SDK configuration options +- [Examples](/docs/campaign-cart/analytics/examples/) - Provider setup guides diff --git a/docs/campaign-cart/analytics/debugging.md b/docs/campaign-cart/analytics/debugging.md new file mode 100644 index 00000000..f5a9a208 --- /dev/null +++ b/docs/campaign-cart/analytics/debugging.md @@ -0,0 +1,421 @@ +--- +title: "Debugging & Testing" +description: "Test analytics implementation, troubleshoot issues, and verify event tracking across all providers." +--- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + +## Overview + +This guide provides debugging tools and troubleshooting strategies for testing your analytics implementation. Use these techniques to verify events are being tracked correctly across all providers and identify issues. + +## Enable Debug Mode + +Debug mode provides console logging of analytics events and provider interactions. + + + + +Enable debug mode in your analytics configuration: + +```javascript +analytics: { + debug: true // Enable in config +} +``` + + + + +Enable or check debug mode at runtime via the browser console: + +```javascript +// Enable at runtime +window.NextAnalytics.setDebugMode(true); + +// Check current status +const status = window.NextAnalytics.getStatus(); +console.log(status); +``` + + + + +When debug mode is enabled, you'll see detailed console output for every event tracked, including which providers received the event and any transformations applied. + +## Disable Tracking for Testing + +Temporarily disable all tracking while testing your site without affecting session data. + +### Enable Ignore Mode + +Add the `?ignore=true` query parameter to your URL: + +``` +https://yoursite.com?ignore=true +``` + +This disables **ALL** tracking for the entire session. All analytics events will be silently ignored. + +### Clear Ignore Mode + +When you're finished testing, clear the ignore flag: + +```javascript +window.NextAnalyticsClearIgnore(); +``` + +After calling this function, tracking will resume normally for new events. + +## Verify Events in Data Layers + +Check that events are properly pushed to the data layer and available for your analytics platforms to consume. + +### Check All Data Layers + +View the raw event data in each data layer: + +```javascript +// NextAnalytics data layer +console.log(window.NextDataLayer); + +// Google Tag Manager +console.log(window.dataLayer); + +// Elevar (if enabled) +console.log(window.ElevarDataLayer); +``` + +### Example Output + +When you track an event with debug mode enabled: + +```javascript +// Check data layers +console.log(window.NextDataLayer); +// Output: [{ +// event: 'dl_add_to_cart', +// ecommerce: { items: [...], value: 99.99 }, +// timestamp: 1234567890 +// }] + +console.log(window.dataLayer); +// Output: [{ +// event: 'add_to_cart', +// ecommerce: { items: [...], value: 99.99 } +// }] +``` + +## Check Analytics Status + +Use the status API to verify providers are loaded and events are being tracked. + +### Get Current Status + +```javascript +const status = window.NextAnalytics.getStatus(); +console.log('Events tracked:', status.eventsTracked); +console.log('Providers:', status.providers); +``` + +### Example Output + +```javascript +{ + eventsTracked: 42, + providers: ['GTM', 'Facebook', 'RudderStack', 'Custom'], + debugMode: true, + ignoreMode: false +} +``` + +This tells you: +- **eventsTracked**: Total number of events processed in this session +- **providers**: List of active providers receiving events +- **debugMode**: Whether debug logging is enabled +- **ignoreMode**: Whether tracking is temporarily disabled + +## Provider-Specific Debugging + +Each provider has different debugging tools and verification methods. Use the appropriate tab for your provider. + +### Verify Provider Connections + +First, verify that all expected providers are connected and receiving events: + +```javascript +// Enable debug mode to see detailed logs +window.NextAnalytics.setDebugMode(true); + +// Track a test event +next.trackAddToCart('123', 1); + +// Console will show detailed output: +// [NextDataLayer] Event pushed: dl_add_to_cart +// [GTM] Pushing to dataLayer +// [Facebook] Tracking AddToCart +// [RudderStack] Tracking Product Added +// [Custom] Batching event (1/10) +``` + +### Test Each Provider + + + + +**Google Tag Manager Debugging** + +```javascript +// Check if GTM container loaded +console.log('GTM loaded:', typeof window.dataLayer !== 'undefined'); + +// View all events in dataLayer +console.log(window.dataLayer); + +// Check Elevar data layer (if using Elevar) +console.log(window.ElevarDataLayer); +``` + +**Verification in GTM:** +1. Go to your GTM container in preview mode +2. Load your website and perform actions +3. The GTM preview panel should show events being fired +4. Check that events match your expected naming convention + +**Common GTM Issues:** +- Events not appearing in preview mode +- Incorrect event names or properties +- Missing custom variables in GTM +- Events firing after GTM container loads + + + + +**Facebook Pixel Debugging** + +```javascript +// Check if Pixel loaded +console.log('Facebook Pixel loaded:', typeof window.fbq !== 'undefined'); + +// Track a test event +if (window.fbq) { + fbq('trackCustom', 'TestEvent', { test: true }); +} +``` + +**Using Facebook Pixel Helper:** +1. Install the [Facebook Pixel Helper browser extension](https://www.facebook.com/business/tools/pixel-helper) +2. Open the extension to see all pixel events firing in real-time +3. Verify event names and data parameters match your Facebook Catalog + +**Common Facebook Issues:** +- Duplicate purchase events from tracking same conversion twice +- Missing `storeName` in configuration causing pixel conflicts +- Events not matching Facebook's standard event names +- Pixel events firing but not converting in Ads Manager + + + + +**RudderStack Debugging** + +```javascript +// Check if RudderStack SDK loaded +console.log('RudderStack loaded:', typeof window.rudderanalytics !== 'undefined'); + +// Check ready state +if (window.rudderanalytics) { + rudderanalytics.ready(() => { + console.log('RudderStack is ready'); + console.log('User ID:', rudderanalytics.getUserId()); + }); +} +``` + +**Using RudderStack Debugger:** +1. Enable the RudderStack debugger in your browser dev tools +2. Open the Console and look for RudderStack logs +3. Check the Network tab for POST requests to your RudderStack endpoint +4. Verify the event payloads match your destination schema + +**Common RudderStack Issues:** +- RudderStack SDK doesn't load within 5-second timeout +- Events queued but not sent to destination +- Invalid schema causing events to be rejected +- Missing user identification for conversion tracking + + + + +**Custom Endpoint Debugging** + +```javascript +// Check network requests to your endpoint +// 1. Open DevTools > Network tab +// 2. Filter by your endpoint URL +// 3. Look for POST requests with analytics events + +// Or add debug logging in your transform function: +transformFunction: (event) => { + console.log('Sending to webhook:', { + event: event.event, + timestamp: new Date().toISOString(), + payload: event + }); + return event; +} +``` + +**Verification Steps:** +1. Open DevTools > Network tab +2. Filter requests by your webhook endpoint +3. Look for POST requests containing your events +4. Check the request payload matches your expected format +5. Verify response status is 200-299 (success) +6. Check your server logs for incoming requests + +**Common Custom Issues:** +- 429 Rate Limit errors: Increase `batchIntervalMs` or reduce `batchSize` +- Webhook not receiving batched events: Check endpoint URL and network connectivity +- Events arriving in wrong order: Adjust batching configuration +- Server returning 400/500 errors: Validate event schema against your API spec + + + + +## Common Issues and Solutions + +Common analytics problems and solutions: + +| Issue | Cause | Solution | +|-------|-------|----------| +| No events in any provider | Analytics not initialized | Verify SDK loads before tracking events; check `debug` config | +| GTM not receiving events | Container loads after tracking events | Ensure GTM tag loads before SDK initialization | +| Facebook showing duplicate purchases | Same event tracked twice or storeName conflict | Remove duplicate tracking calls; configure `storeName` if using multiple pixels | +| RudderStack events not sent | SDK not ready when events tracked | SDK waits 5 seconds for RudderStack to load; verify endpoint configuration | +| Custom webhook 429 errors | Sending too many requests per second | Increase `batchIntervalMs` (default 3000ms) or reduce `batchSize` (default 10) | +| Events in dataLayer but not in provider | Provider script not loaded | Verify provider scripts load and initialize before analytics events | +| Debug logs not showing | Debug mode disabled | Enable with `window.NextAnalytics.setDebugMode(true)` | +| ?ignore=true not working | Session already processing events | Clear ignore with `window.NextAnalyticsClearIgnore()` and refresh page | +| Missing event properties | Insufficient context data | Verify event context (user, page, cart) populated before tracking | +| Provider not in status list | Provider configuration disabled | Check analytics config for provider `enabled: true` setting | + +## Provider Not Receiving Events + +If a specific provider isn't receiving events, follow this debugging process: + +### Step 1: Verify Provider is Enabled + +```javascript +const status = window.NextAnalytics.getStatus(); +console.log('Active providers:', status.providers); +``` + +If your provider isn't in the list, check your configuration to ensure it's enabled. + +### Step 2: Enable Debug Mode + +```javascript +window.NextAnalytics.setDebugMode(true); +``` + +Now track an event and watch the console for debug output related to your provider. + +### Step 3: Check for Blocked Events + +Some events may be intentionally blocked from certain providers: + +```javascript +// Check GTM blocked events as an example +const config = window.nextConfig.analytics.providers.gtm; +console.log('Blocked events:', config.blockedEvents); +``` + +Verify the event you're testing isn't in the blocked list. + +### Step 4: Verify Provider Script Loaded + +Check that the provider's JavaScript library loaded successfully: + +```javascript +// Google Tag Manager +console.log('GTM loaded:', typeof window.dataLayer !== 'undefined'); + +// Facebook Pixel +console.log('Facebook loaded:', typeof window.fbq !== 'undefined'); + +// RudderStack +console.log('RudderStack loaded:', typeof window.rudderanalytics !== 'undefined'); +``` + +If any of these return `false` or `undefined`, the provider script didn't load. Check your configuration and network requests. + +## Events Not Showing in Analytics Platform + +Events sent from your site may not appear in your analytics platform's dashboard. Use provider-specific debugging tools: + +### GTM: Preview Mode + +1. Open your GTM container +2. Click **Preview** to enter preview mode +3. Visit your website in a new tab +4. The GTM preview panel will show all tags and events firing in real-time +5. Click on events to see their properties and which tags they trigger + +### Facebook: Pixel Helper + +1. Install the [Facebook Pixel Helper extension](https://www.facebook.com/business/tools/pixel-helper) +2. Click the extension icon to view all pixel events +3. Verify event names match your Facebook Catalog +4. Check event properties like `value` and `currency` are correct +5. Use the Diagnostics tab to identify validation issues + +### RudderStack: Debugger + +1. Check RudderStack's web console for errors +2. Open DevTools Network tab and filter for RudderStack API calls +3. Verify the destination is receiving events in your RudderStack workspace +4. Check that your destination transformation rules are correct + +### Custom: Server Logs and Network Monitoring + +1. Check your server logs for incoming webhook requests +2. Verify request headers and payload structure match your spec +3. Check response codes (200-299 = success, 4xx/5xx = error) +4. Monitor Network tab in DevTools for request timing and failures + +## Testing Checklist + +Verify your analytics implementation before going live: + +- [ ] **Debug mode enabled** - `window.NextAnalytics.setDebugMode(true)` shows detailed logs +- [ ] **All providers active** - `window.NextAnalytics.getStatus()` lists all expected providers +- [ ] **Events in data layer** - `console.log(window.NextDataLayer)` shows events +- [ ] **GTM receiving events** - `console.log(window.dataLayer)` contains your events +- [ ] **GTM preview mode** - Events appear in GTM container preview panel +- [ ] **Facebook Pixel Helper** - Extension shows purchase and add-to-cart events +- [ ] **RudderStack events** - Network tab shows POST requests to RudderStack +- [ ] **Custom webhook** - Server receives POST requests with event payloads +- [ ] **Event properties complete** - All required fields present (value, currency, items) +- [ ] **No blocked events** - Verify events aren't in provider `blockedEvents` list +- [ ] **No duplicate events** - Each action tracked once per provider +- [ ] **Ignore mode works** - `?ignore=true` prevents events from being tracked +- [ ] **Ignore mode clears** - `window.NextAnalyticsClearIgnore()` resumes tracking +- [ ] **Status API responsive** - `getStatus()` returns current state +- [ ] **Multiple providers tested** - Events work across all enabled providers +- [ ] **Error handling** - Failed provider requests don't break site functionality +- [ ] **Production ready** - Debug mode disabled in production config + +## Next Steps + +After testing and debugging your analytics: + +1. **Review Configuration** - Check your provider settings match your platform setup +2. **Enable Tracking** - Remove `?ignore=true` and deploy to production +3. **Monitor Events** - Use platform dashboards to monitor event flow +4. **Set Up Alerts** - Configure monitoring for provider failures or dropped events +5. **Document Setup** - Record your analytics configuration for your team + +See the [Configuration Guide](/docs/campaign-cart/analytics/configuration/) for detailed provider setup instructions. diff --git a/docs/campaign-cart/analytics/events.md b/docs/campaign-cart/analytics/events.md new file mode 100644 index 00000000..af287791 --- /dev/null +++ b/docs/campaign-cart/analytics/events.md @@ -0,0 +1,660 @@ +--- +title: Event Reference +description: Complete reference of all analytics events with schemas, examples, and when they fire. +sidebar_position: 3 +--- + + +Complete reference of all standard analytics events tracked by the SDK, including event structure, when they fire, and whether they're automatically tracked. + +## Event Naming Convention + +All standard events follow the `dl_*` naming pattern for consistency with data layer conventions. + +## Event Categories + +- **User Events** - User identification and authentication +- **E-commerce Events** - Product views, cart, checkout, purchase +- **Custom Events** - Your custom business events + +--- + +## User Events + +### dl_user_data + +Base user event with cart contents. Automatically fired on every page load in auto mode. + +**When it fires:** +- Page load +- Route changes +- User login/logout +- Browser back/forward navigation + +**Auto-tracked:** Yes (in auto mode) + +**Example:** + +```javascript +{ + event: 'dl_user_data', + event_id: 'sess_123_1_1234567890', + event_time: '2025-01-12T10:30:00Z', + user_properties: { + visitor_type: 'guest', + customer_email: 'user@example.com', + customer_id: 'user_123' + }, + ecommerce: { + currency: 'USD', + value: 199.99, + items: [...] // Current cart items + } +} +``` + +--- + +### dl_sign_up + +User registration event. + +**When it fires:** When user creates an account + +**Auto-tracked:** No (manual tracking required) + +**Tracking:** + +```javascript +// Simple +next.trackSignUp('user@example.com'); + +// Advanced +window.NextAnalytics.trackSignUp('user@example.com'); +``` + +**Example:** + +```javascript +{ + event: 'dl_sign_up', + event_id: 'sess_123_2_1234567890', + method: 'email', + user_properties: { + visitor_type: 'logged_in', + customer_email: 'user@example.com', + customer_first_name: 'John', + customer_last_name: 'Doe' + } +} +``` + +--- + +### dl_login + +User login event. + +**When it fires:** When user logs in + +**Auto-tracked:** No (manual tracking required) + +**Tracking:** + +```javascript +// Simple +next.trackLogin('user@example.com'); + +// Advanced +window.NextAnalytics.trackLogin('user@example.com'); +``` + +--- + +## E-commerce Events + +### dl_view_item_list + +Product list view (collection, category, search results). + +**When it fires:** When a product list is displayed + +**Auto-tracked:** Yes (when URL matches collection/category patterns) + +**Tracking:** + +```javascript +// Simple +next.trackViewItemList(['123', '456'], 'summer-sale', 'Summer Sale'); + +// Advanced +window.NextAnalytics.trackViewItemList(items, listId, listName); +``` + +**Example:** + +```javascript +{ + event: 'dl_view_item_list', + event_id: 'sess_123_3_1234567890', + user_properties: { ... }, + ecommerce: { + item_list_id: 'summer-sale', + item_list_name: 'Summer Sale Collection', + currency: 'USD', + items: [ + { + item_id: 'SKU-123', + item_name: 'Product Name', + price: 99.99, + quantity: 1, + index: 0 + } + ] + } +} +``` + +**List Attribution:** Stores list ID and name in session storage for 30 minutes. + +--- + +### dl_view_item + +Product detail view. + +**When it fires:** When a product page is viewed + +**Auto-tracked:** Yes (when page has exactly 1 product with `data-next-package-id`) + +**Manual tracking:** + +```javascript +// Simple +next.trackViewItem('123'); + +// Advanced +window.NextAnalytics.trackViewItem({ + id: 'pkg-123', + packageId: '123', + title: 'Product Name', + price: 99.99 +}); +``` + +**Example:** + +```javascript +{ + event: 'dl_view_item', + ecommerce: { + currency: 'USD', + value: 99.99, + items: [{ + item_id: 'SKU-123', + item_name: 'Product Name', + price: 99.99, + quantity: 1, + item_list_id: 'summer-sale', // If coming from a list + item_list_name: 'Summer Sale' + }] + } +} +``` + +--- + +### dl_add_to_cart + +Item added to cart. + +**When it fires:** When user adds item to cart + +**Auto-tracked:** Yes (when using data attributes or cart methods) + +**Tracking:** + +```javascript +// Simple +next.trackAddToCart('123', 1); + +// Advanced with list attribution +window.NextAnalytics.trackAddToCart({ + id: 'pkg-123', + packageId: '123', + title: 'Product Name', + price: 99.99, + quantity: 1 +}, 'summer-sale', 'Summer Sale'); +``` + +**Example:** + +```javascript +{ + event: 'dl_add_to_cart', + ecommerce: { + currency: 'USD', + value: 99.99, + items: [{ + item_id: 'SKU-123', + item_name: 'Product Name', + price: 99.99, + quantity: 1, + item_list_id: 'summer-sale', + item_list_name: 'Summer Sale' + }] + } +} +``` + +--- + +### dl_remove_from_cart + +Item removed from cart. + +**When it fires:** When user removes item from cart + +**Auto-tracked:** Yes (when using cart methods) + +**Tracking:** + +```javascript +// Simple +next.trackRemoveFromCart('123', 1); + +// Advanced +import { EcommerceEvents } from '@/utils/analytics'; +import { dataLayer } from '@/utils/analytics'; + +const event = EcommerceEvents.createRemoveFromCartEvent(item); +dataLayer.push(event); +``` + +--- + +### dl_view_cart + +Cart page view. + +**When it fires:** When cart page is viewed + +**Auto-tracked:** No (manual tracking required) + +**Example:** + +```javascript +{ + event: 'dl_view_cart', + ecommerce: { + currency: 'USD', + value: 199.99, + items: [...] // All cart items + } +} +``` + +--- + +### dl_begin_checkout + +Checkout process initiation. + +**When it fires:** When checkout starts + +**Auto-tracked:** Yes (when checkout page loads) + +**Tracking:** + +```javascript +// Simple +next.trackBeginCheckout(); + +// Advanced +window.NextAnalytics.trackBeginCheckout(); +``` + +**Example:** + +```javascript +{ + event: 'dl_begin_checkout', + ecommerce: { + currency: 'USD', + value: 199.99, + items: [...] // All cart items + } +} +``` + +--- + +### dl_add_shipping_info + +Shipping information entered. + +**When it fires:** When shipping details are completed + +**Auto-tracked:** Yes (when shipping form is submitted) + +**Example:** + +```javascript +{ + event: 'dl_add_shipping_info', + ecommerce: { + currency: 'USD', + value: 199.99, + shipping_tier: 'Standard Shipping', + items: [...] + } +} +``` + +--- + +### dl_add_payment_info + +Payment information entered. + +**When it fires:** When payment method is selected + +**Auto-tracked:** Yes (when payment form is submitted) + +**Example:** + +```javascript +{ + event: 'dl_add_payment_info', + ecommerce: { + currency: 'USD', + value: 199.99, + payment_type: 'Credit Card', + items: [...] + } +} +``` + +--- + +### dl_purchase + +Order completion. + +**When it fires:** When order is completed + +**Auto-tracked:** Yes + +**Event Queue:** + +Purchase events are queued to `sessionStorage` when the order completes, then automatically fired on the confirmation page after redirect. This prevents event loss during navigation. + +**Manual tracking (optional):** + +```javascript +// Optional: Manually trigger for testing or special integrations +// Order data automatically available in sessionStorage['next-order'] +const orderData = JSON.parse(sessionStorage.getItem('next-order'))?.state?.order; +if (orderData) { + next.trackPurchase({ order: orderData }); +} + +// Or provide custom order data +next.trackPurchase({ + id: 'ORDER_123', + total: 159.99, + currency: 'USD', + tax: 9.99, + shipping: 10.00 +}); +``` + +**Example:** + +```javascript +{ + event: 'dl_purchase', + transaction_id: 'ORD-12345', + ecommerce: { + transaction_id: 'ORD-12345', + affiliation: 'Online Store', + currency: 'USD', + value: 159.99, + tax: 9.99, + shipping: 10.00, + coupon: 'SUMMER20', + items: [ + { + item_id: 'SKU-123', + item_name: 'Product Name', + price: 149.99, + quantity: 1 + } + ] + } +} +``` + +--- + +### dl_package_swapped + +Package swap (replace one item with another). + +**When it fires:** When user swaps one package for another + +**Auto-tracked:** No (manual tracking required) + +**Example:** + +```javascript +{ + event: 'dl_package_swapped', + ecommerce: { + currency: 'USD', + value: 20.00, // Price difference + items_removed: [{ + item_id: 'SKU-123', + item_name: 'Basic Package', + price: 99.99, + quantity: 1 + }], + items_added: [{ + item_id: 'SKU-456', + item_name: 'Premium Package', + price: 119.99, + quantity: 1 + }] + } +} +``` + +--- + +### dl_upsell_purchase + +Upsell accepted (post-purchase). + +**When it fires:** When user accepts an upsell offer + +**Auto-tracked:** No (manual tracking required) + +**Example:** + +```javascript +{ + event: 'dl_upsell_purchase', + transaction_id: 'ORD-12345-US1', // Format: {orderId}-US{number} + ecommerce: { + transaction_id: 'ORD-12345-US1', + currency: 'USD', + value: 29.99, + items: [{ + item_id: 'UPSELL-789', + item_name: 'Upsell Package', + price: 29.99, + quantity: 1 + }] + }, + _willRedirect: true // Queued for post-redirect processing +} +``` + +**Note:** Has `_willRedirect: true` flag - automatically queued and processed after page redirect. + +--- + +### dl_view_search_results + +Search results page view. + +**When it fires:** When search results are displayed + +**Auto-tracked:** No (manual tracking required) + +**Example:** + +```javascript +{ + event: 'dl_view_search_results', + search_term: 'drone', + ecommerce: { + item_list_id: 'search-results', + item_list_name: 'Search Results', + currency: 'USD', + items: [...] + } +} +``` + +--- + +## Event Structure + +All events follow this GA4-compliant structure: + +```javascript +{ + // Event identification + event: string, // Event name (e.g., 'dl_add_to_cart') + event_id: string, // Unique ID: sessionId_sequence_timestamp + event_time: string, // ISO timestamp + + // User properties (Elevar format) + user_properties: { + visitor_type: 'guest' | 'logged_in', + customer_id?: string, + customer_email?: string, + customer_phone?: string, + customer_first_name?: string, + customer_last_name?: string, + customer_address_1?: string, + customer_city?: string, + customer_province?: string, + customer_zip?: string, + customer_country?: string + }, + + // Ecommerce data (GA4 format) + ecommerce?: { + currency: string, + value?: number, + transaction_id?: string, + affiliation?: string, + tax?: number, + shipping?: number, + coupon?: string, + discount?: number, + item_list_id?: string, + item_list_name?: string, + shipping_tier?: string, + payment_type?: string, + items: [{ + item_id: string, + item_name: string, + item_brand?: string, + item_category?: string, + item_variant?: string, + price: number, + quantity: number, + currency: string, + index?: number, + item_list_id?: string, + item_list_name?: string + }] + }, + + // Attribution (auto-added) + attribution?: { + utm_source?: string, + utm_medium?: string, + utm_campaign?: string, + utm_term?: string, + utm_content?: string, + gclid?: string, + funnel?: string, + affiliate?: string + }, + + // Context (auto-added) + page_location?: string, + page_title?: string, + page_referrer?: string, + + // Internal metadata + _metadata: { + pushed_at: number, + session_id: string, + sequence_number: number, + source: 'next-campaign-cart', + version: '0.2.0' + } +} +``` + +## Quick Reference Table + +| Event | When it Fires | Auto-Tracked | Manual Method | +|-------|--------------|--------------|---------------| +| `dl_user_data` | Every page load | Yes | - | +| `dl_sign_up` | User registration | No | `next.trackSignUp()` | +| `dl_login` | User login | No | `next.trackLogin()` | +| `dl_view_item_list` | List page view | Yes | `next.trackViewItemList()` | +| `dl_view_item` | Product page view | Yes | `next.trackViewItem()` | +| `dl_add_to_cart` | Add to cart | Yes | `next.trackAddToCart()` | +| `dl_remove_from_cart` | Remove from cart | Yes | `next.trackRemoveFromCart()` | +| `dl_view_cart` | Cart page view | No | Manual | +| `dl_begin_checkout` | Checkout start | Yes | `next.trackBeginCheckout()` | +| `dl_add_shipping_info` | Shipping entered | Yes | Manual | +| `dl_add_payment_info` | Payment entered | Yes | Manual | +| `dl_purchase` | Order complete | Yes | `next.trackPurchase()` (optional) | +| `dl_package_swapped` | Package swap | No | Manual | +| `dl_upsell_purchase` | Upsell accepted | No | Manual | +| `dl_view_search_results` | Search results | No | Manual | + +## Event Validation + +All events are automatically validated against schemas: + +```javascript +import { EventValidator } from '@/utils/analytics'; + +const validator = new EventValidator(true); + +// Validate event +const result = validator.validateEvent(event); +if (!result.valid) { + console.error('Invalid event:', result.errors); +} + +// Get available schemas +const schemas = validator.getAvailableSchemas(); +console.log(schemas); +``` + +## Related Documentation + +- [Analytics Overview](/docs/campaign-cart/analytics/) - Main analytics documentation +- [Custom Events](/docs/campaign-cart/analytics/custom-events/) - Creating custom events +- [Examples](/docs/campaign-cart/analytics/examples/) - Provider integration guides diff --git a/docs/campaign-cart/analytics/examples/custom-analytics-triggers.md b/docs/campaign-cart/analytics/examples/custom-analytics-triggers.md new file mode 100644 index 00000000..d74bb2b4 --- /dev/null +++ b/docs/campaign-cart/analytics/examples/custom-analytics-triggers.md @@ -0,0 +1,878 @@ +# Custom Analytics Triggers + +This external script modifies when `add_to_cart` and `begin_checkout` events fire for GTM and Facebook Pixel, allowing you to customize the timing of these critical ecommerce events. + +## Overview + +This script changes **when** events fire, but uses the **same event format and data structure** as the SDK's default events: + +- **`dl_add_to_cart`**: Fires when the checkout page loads (instead of when items are added to cart) +- **`dl_begin_checkout`**: Fires when: + - Prospect cart is created (when name/email input triggers `carts.create`) + - OR when express checkout option is clicked (PayPal, Apple Pay, Google Pay) + +## Changes Made + +- **`add_to_cart`**: Now fires when the checkout page loads (instead of when items are added to cart) +- **`begin_checkout`**: Now fires when: + - Prospect cart is created (when name/email input triggers `carts.create`) + - OR when express checkout option is clicked (PayPal, Apple Pay, Google Pay) + +## Requirements + +- SDK must be loaded and initialized +- GTM `dataLayer` must be available (for GTM events) +- Facebook Pixel `fbq` must be available (for Facebook events) +- Default SDK events must be blocked in configuration (see Setup below) + +## Setup + +### 1. Configuration Setup + +Add `blockedEvents` to both GTM and Facebook providers in your `config.js`: + +```javascript +window.nextConfig = { + apiKey: 'your-api-key', + analytics: { + enabled: true, + providers: { + gtm: { + enabled: true, + settings: { ... }, + blockedEvents: ['dl_add_to_cart', 'dl_begin_checkout'] + }, + facebook: { + enabled: true, + settings: { pixelId: 'xxx' }, + blockedEvents: ['dl_add_to_cart', 'dl_begin_checkout'] + } + } + } +}; +``` + +### 2. Include Script in HTML + +Include this script **after** the SDK loader but **before** the SDK initializes: + +```html + + + + + + + + + + + + + + + + + + +``` + +### 3. Ensure Cart Has Items + +- Add items to cart before testing +- Cart must not be empty for events to fire + +## How It Works + +### Checkout Page Detection + +The script detects checkout pages by: +- Looking for checkout form elements (`form[data-next-checkout]` or `form[os-checkout-form]`) +- Checking for `meta[name="next-page-type"]` with value `checkout` +- Checking if URL contains "checkout" or "cart" + +### add_to_cart Trigger + +- Fires when checkout page loads +- Also listens for `checkout:form-initialized` event +- Only fires once per page load (duplicate prevention) + +### begin_checkout Triggers + +- Listens for `next:prospect-cart-created` DOM event (fired when name/email input creates prospect cart) +- Listens for express checkout button clicks (PayPal, Apple Pay, Google Pay buttons) +- Listens for `express-checkout:started` SDK event +- Only fires once per checkout session (duplicate prevention) + +## Event Format + +The script formats events according to: +- **GTM**: GA4 ecommerce event format with `ecommerce` object, using `dl_` prefix (Campaign Cart SDK's standard naming convention) +- **Facebook Pixel**: Standard Facebook event parameters +- Events are pushed to both `ElevarDataLayer` (for Elevar compatibility) and `dataLayer` (standard GTM) + +:::note About Event Naming +The `dl_` prefix is Campaign Cart SDK's standard event naming convention (e.g., `dl_add_to_cart`, `dl_begin_checkout`). This follows data layer conventions and is used by the SDK for all events, not specific to Elevar. + +`ElevarDataLayer` is a separate data layer specifically for Elevar compatibility. If you're not using Elevar, you can remove the `ElevarDataLayer` references from the script - events will still work with standard GTM via `window.dataLayer`. +::: + +## Complete Script + +Here's the complete JavaScript code for the custom analytics triggers: + +```javascript +/** + * Custom Analytics Triggers for GTM and Facebook Pixel + * + * This script modifies WHEN add_to_cart and begin_checkout events fire, but uses + * the SAME event format and data structure as the SDK's default events. + * + * Changes: + * - dl_add_to_cart: Fires when checkout page loads (instead of when items are added) + * - dl_begin_checkout: Fires when carts.create fires (name/email input) OR when express checkout is clicked + * + * The events use the same format as SDK defaults: + * - dl_ prefix (Campaign Cart SDK's standard naming convention) + * - Same ecommerce data structure + * - Pushed to both ElevarDataLayer (for Elevar compatibility) and dataLayer (standard GTM) + * + * Usage: Include this script after the SDK loader, and block the default events + * in your config.js: blockedEvents: ['dl_add_to_cart', 'dl_begin_checkout'] + */ + +(function() { + 'use strict'; + + // Flag to allow our script's InitiateCheckout events through + let isFiringFromOurScript = false; + + // Wrap fbq immediately to block SDK's InitiateCheckout on page load + (function() { + const originalFbq = window.fbq; + + // Create wrapper function + window.fbq = function() { + const args = Array.from(arguments); + const command = args[0]; + const eventName = args[1]; + + // Block InitiateCheckout unless it's from our script + if (command === 'track' && eventName === 'InitiateCheckout' && !isFiringFromOurScript) { + console.log('🚫 Blocked SDK InitiateCheckout event (backup blocker)'); + return; + } + + // Allow our script's events through + if (command === 'track' && eventName === 'InitiateCheckout' && isFiringFromOurScript) { + isFiringFromOurScript = false; // Reset flag after allowing + } + + // Call original fbq + if (typeof originalFbq === 'function') { + return originalFbq.apply(this, arguments); + } + + // If originalFbq wasn't available, queue to _fbq + window._fbq = window._fbq || []; + window._fbq.push(args); + }; + + // Copy properties from original fbq if it exists + if (originalFbq) { + Object.keys(originalFbq).forEach(function(key) { + window.fbq[key] = originalFbq[key]; + }); + } + })(); + + // Track if events have been fired to prevent duplicates + let hasFiredAddToCart = false; + let hasFiredBeginCheckout = false; + + /** + * Get cart data from SDK + */ + function getCartData() { + try { + if (typeof window.next !== 'undefined' && window.next.getCartData) { + return window.next.getCartData(); + } + + // Fallback: try to access stores via debug API if available + if (typeof window.nextDebug !== 'undefined' && window.nextDebug.stores) { + const cartStore = window.nextDebug.stores.cart; + const campaignStore = window.nextDebug.stores.campaign; + if (cartStore && campaignStore) { + return { + cartLines: cartStore.getState().enrichedItems || cartStore.getState().items, + cartTotals: cartStore.getState().totals, + campaignData: campaignStore.getState().data, + appliedCoupons: cartStore.getState().appliedCoupons || [] + }; + } + } + + return null; + } catch (error) { + console.warn('Could not get cart data:', error); + return null; + } + } + + /** + * Get campaign data for currency and package info + */ + function getCampaignData() { + try { + if (typeof window.next !== 'undefined' && window.next.getCampaignData) { + return window.next.getCampaignData(); + } + + // Fallback: try to access stores via debug API if available + if (typeof window.nextDebug !== 'undefined' && window.nextDebug.stores) { + const campaignStore = window.nextDebug.stores.campaign; + if (campaignStore) { + return campaignStore.getState().data; + } + } + + return null; + } catch (error) { + console.warn('Could not get campaign data:', error); + return null; + } + } + + /** + * Get package data by ID + */ + function getPackage(packageId) { + try { + if (typeof window.next !== 'undefined' && window.next.getPackage) { + return window.next.getPackage(packageId); + } + + // Fallback: try to access stores via debug API if available + if (typeof window.nextDebug !== 'undefined' && window.nextDebug.stores) { + const campaignStore = window.nextDebug.stores.campaign; + if (campaignStore && campaignStore.getState().getPackage) { + return campaignStore.getState().getPackage(packageId); + } + } + + return null; + } catch (error) { + console.warn('Could not get package data:', error); + return null; + } + } + + /** + * Format cart items for analytics + */ + function formatCartItems(cartData, campaignData) { + if (!cartData || !cartData.cartLines || cartData.cartLines.length === 0) { + return []; + } + + const currency = campaignData?.currency || cartData.campaignData?.currency || 'USD'; + + return cartData.cartLines.map((item, index) => { + // Extract packageId from enriched item (could be in different places) + const packageId = item.packageId || item.package_id || item.id; + + // Try to get package data + let packageData = null; + if (packageId) { + packageData = getPackage(packageId); + } + + return { + item_id: packageData?.external_id?.toString() || item.external_id?.toString() || packageId?.toString() || String(packageId), + item_name: packageData?.name || item.name || item.title || `Package ${packageId}`, + price: parseFloat(packageData?.price_total || item.price_total || item.price || '0'), + quantity: item.quantity || 1, + item_category: campaignData?.name || cartData.campaignData?.name || 'Campaign', + item_variant: packageData?.product_variant_name || item.variant_name || packageData?.product?.variant?.name, + item_brand: packageData?.product_name || item.product_name || packageData?.product?.name, + item_sku: packageData?.product_sku || item.sku || packageData?.product?.variant?.sku, + ...(packageData?.image && { item_image: packageData.image }), + ...(item.image && { item_image: item.image }), + index: index + }; + }); + } + + /** + * Fire add_to_cart event to GTM and Facebook + */ + function fireAddToCart() { + if (hasFiredAddToCart) { + return; + } + + const cartData = getCartData(); + const campaignData = getCampaignData(); + + if (!cartData || !cartData.cartLines || cartData.cartLines.length === 0) { + console.warn('Cannot fire add_to_cart: cart is empty'); + return; + } + + const items = formatCartItems(cartData, campaignData); + const currency = campaignData?.currency || cartData.campaignData?.currency || 'USD'; + const totalValue = cartData.cartTotals?.total?.value || cartData.cartTotals?.total || 0; + + // Calculate total value from items if not available + const calculatedValue = items.reduce((sum, item) => sum + (item.price * item.quantity), 0); + const value = totalValue || calculatedValue; + + // Fire to GTM (dataLayer) - using dl_ prefix (Campaign Cart SDK's standard naming convention) + if (typeof window.dataLayer !== 'undefined') { + // Ensure ElevarDataLayer exists (for Elevar compatibility - optional if not using Elevar) + window.ElevarDataLayer = window.ElevarDataLayer || []; + + // Create event in SDK format (dl_ prefix is Campaign Cart SDK's standard) + const event = { + event: 'dl_add_to_cart', + ecommerce: { + currency: currency, + value: value, + items: items + }, + timestamp: new Date().toISOString() + }; + + // Push to ElevarDataLayer first (for Elevar processing - optional if not using Elevar) + window.ElevarDataLayer.push(event); + + // Clear previous ecommerce data and push to standard dataLayer + window.dataLayer.push({ ecommerce: null }); + window.dataLayer.push(event); + + console.log('✅ Fired dl_add_to_cart to GTM'); + } + + // Fire to Facebook Pixel - simple direct call + if (typeof window.fbq !== 'undefined') { + const fbParams = { + content_type: 'product', + currency: currency, + value: value, + content_ids: items.map(item => item.item_id), + contents: items.map(item => ({ + id: item.item_id, + quantity: item.quantity, + item_price: item.price + })), + content_name: items.map(item => item.item_name).join(', '), + num_items: items.reduce((sum, item) => sum + item.quantity, 0) + }; + + window.fbq('track', 'AddToCart', fbParams); + console.log('✅ Fired add_to_cart to Facebook Pixel'); + } + + hasFiredAddToCart = true; + } + + /** + * Fire begin_checkout event to GTM and Facebook + */ + function fireBeginCheckout() { + if (hasFiredBeginCheckout) { + return; + } + + const cartData = getCartData(); + const campaignData = getCampaignData(); + + if (!cartData || !cartData.cartLines || cartData.cartLines.length === 0) { + console.warn('Cannot fire begin_checkout: cart is empty'); + return; + } + + const items = formatCartItems(cartData, campaignData); + const currency = campaignData?.currency || cartData.campaignData?.currency || 'USD'; + const totalValue = cartData.cartTotals?.total?.value || cartData.cartTotals?.total || 0; + + // Calculate total value from items if not available + const calculatedValue = items.reduce((sum, item) => sum + (item.price * item.quantity), 0); + const value = totalValue || calculatedValue; + + // Get coupon if available + const coupon = cartData.appliedCoupons?.[0]?.code || (Array.isArray(cartData.appliedCoupons) && cartData.appliedCoupons.length > 0 ? cartData.appliedCoupons[0] : null) || null; + + // Fire to GTM (dataLayer) - using dl_ prefix (Campaign Cart SDK's standard naming convention) + if (typeof window.dataLayer !== 'undefined') { + // Ensure ElevarDataLayer exists (for Elevar compatibility - optional if not using Elevar) + window.ElevarDataLayer = window.ElevarDataLayer || []; + + // Create event in SDK format (dl_ prefix is Campaign Cart SDK's standard) + const event = { + event: 'dl_begin_checkout', + ecommerce: { + currency: currency, + value: value, + items: items + }, + cart_total: String(value), + timestamp: new Date().toISOString() + }; + + if (coupon) { + event.ecommerce.coupon = coupon; + } + + // Push to ElevarDataLayer first (for Elevar processing - optional if not using Elevar) + window.ElevarDataLayer.push(event); + + // Clear previous ecommerce data and push to standard dataLayer + window.dataLayer.push({ ecommerce: null }); + window.dataLayer.push(event); + + console.log('✅ Fired dl_begin_checkout to GTM'); + } + + // Fire to Facebook Pixel - set flag to allow through wrapper + if (typeof window.fbq !== 'undefined') { + const fbParams = { + content_type: 'product', + currency: currency, + value: value, + content_ids: items.map(item => item.item_id), + contents: items.map(item => ({ + id: item.item_id, + quantity: item.quantity, + item_price: item.price + })), + num_items: items.reduce((sum, item) => sum + item.quantity, 0) + }; + + if (coupon) { + fbParams.coupon = coupon; + } + + // Set flag to allow our event through the wrapper + isFiringFromOurScript = true; + window.fbq('track', 'InitiateCheckout', fbParams); + console.log('✅ Fired begin_checkout to Facebook Pixel'); + } + + hasFiredBeginCheckout = true; + } + + /** + * Check if we're on a checkout page + */ + function isCheckoutPage() { + // Check for checkout form + const checkoutForm = document.querySelector('form[data-next-checkout], form[os-checkout-form]'); + if (checkoutForm) { + return true; + } + + // Check for meta tag + const pageType = document.querySelector('meta[name="next-page-type"]'); + if (pageType && pageType.getAttribute('content') === 'checkout') { + return true; + } + + // Check URL + const url = window.location.pathname.toLowerCase(); + if (url.includes('checkout') || url.includes('cart')) { + return true; + } + + return false; + } + + /** + * Initialize the custom triggers + */ + function initialize() { + // Wait for SDK to be ready + if (typeof window.nextReady !== 'undefined') { + window.nextReady.push(function() { + setupTriggers(); + }); + } else if (typeof window.next !== 'undefined') { + // SDK might already be loaded + setupTriggers(); + } else { + // Wait for SDK initialization event + document.addEventListener('next:initialized', function() { + setupTriggers(); + }); + } + } + + /** + * Setup event listeners for triggers + */ + function setupTriggers() { + console.log('🔧 Setting up custom analytics triggers'); + + // 1. Fire add_to_cart when checkout page loads + if (isCheckoutPage()) { + // Wait a bit for cart to be populated + setTimeout(function() { + fireAddToCart(); + }, 500); + } + + // Also listen for checkout form initialization + document.addEventListener('checkout:form-initialized', function() { + if (isCheckoutPage()) { + setTimeout(function() { + fireAddToCart(); + }, 100); + } + }); + + // 2. Fire begin_checkout when prospect cart is created (name/email input) + document.addEventListener('next:prospect-cart-created', function(event) { + // Check if form has user input (not page load) + const emailField = document.querySelector('[data-next-checkout-field="email"], [os-checkout-field="email"], input[type="email"]'); + + if (emailField && emailField.value && emailField.value.trim().length > 0) { + console.log('✅ Prospect cart created (user input detected), firing begin_checkout'); + setTimeout(function() { + fireBeginCheckout(); + }, 100); + } else { + console.log('⚠️ Prospect cart created but no user input - ignoring (likely page load)'); + } + }); + + // 3. Fire begin_checkout when express checkout is clicked + document.addEventListener('click', function(event) { + const target = event.target; + if (!target) return; + + // Check if it's an express checkout button + const button = target.closest('[data-next-express-checkout], [os-express-checkout], button[data-next-payment="paypal"], button[data-next-payment="apple_pay"], button[data-next-payment="google_pay"]'); + + if (button) { + console.log('✅ Express checkout clicked, firing begin_checkout'); + setTimeout(function() { + fireBeginCheckout(); + }, 100); + } + }, true); // Use capture phase to catch early + + // Also listen for express checkout started event (if SDK emits it) + if (typeof window.next !== 'undefined' && window.next.on) { + window.next.on('express-checkout:started', function() { + console.log('✅ Express checkout started event, firing begin_checkout'); + setTimeout(function() { + fireBeginCheckout(); + }, 100); + }); + } + } + + // Initialize when DOM is ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initialize); + } else { + initialize(); + } + +})(); +``` + +## Testing + +### Setup Checklist + +1. ✅ **Config.js Setup** - Add `blockedEvents` to both GTM and Facebook providers +2. ✅ **Include Script in HTML** - Script included after SDK loader +3. ✅ **Ensure Cart Has Items** - Cart must not be empty for events to fire + +### Test `add_to_cart` Event (Checkout Page Load) + +**What to test:** Event fires when checkout page loads + +**Steps:** +1. Add items to cart on product/selection page +2. Navigate to checkout page +3. Open browser console (F12) +4. Look for console log: `"✅ Fired dl_add_to_cart to GTM"` and `"✅ Fired add_to_cart to Facebook Pixel"` + +**Verify in Browser Console:** +```javascript +// Check dataLayer +console.log(window.dataLayer.filter(e => e.event === 'dl_add_to_cart')); + +// Check ElevarDataLayer +console.log(window.ElevarDataLayer.filter(e => e.event === 'dl_add_to_cart')); + +// Should see event with: +// - event: 'dl_add_to_cart' +// - ecommerce.currency +// - ecommerce.value +// - ecommerce.items[] (array of cart items) +``` + +**Verify in GTM Preview Mode:** +1. Open GTM Preview mode +2. Navigate to checkout page +3. Look for `dl_add_to_cart` event +4. Check that event has: + - `ecommerce.currency` + - `ecommerce.value` + - `ecommerce.items[]` with item details + +**Verify in Facebook Events Manager:** +1. Go to Facebook Events Manager +2. Navigate to checkout page +3. Check for `AddToCart` event +4. Verify event parameters (value, currency, content_ids, etc.) + +**Expected Behavior:** +- ✅ Event fires ONCE when checkout page loads +- ✅ Event does NOT fire when items are added to cart (that's blocked) +- ✅ Event includes all cart items +- ✅ Event has correct currency and value + +### Test `begin_checkout` Event (Prospect Cart Created) + +**What to test:** Event fires when name/email input triggers cart creation + +**Steps:** +1. Navigate to checkout page (with items in cart) +2. Open browser console (F12) +3. Enter email address in checkout form +4. Enter first name +5. Enter last name +6. Look for console log: `"✅ Prospect cart created (user input detected), firing begin_checkout"` +7. Then: `"✅ Fired dl_begin_checkout to GTM"` and `"✅ Fired begin_checkout to Facebook Pixel"` + +**Verify in Browser Console:** +```javascript +// Check dataLayer +console.log(window.dataLayer.filter(e => e.event === 'dl_begin_checkout')); + +// Check ElevarDataLayer +console.log(window.ElevarDataLayer.filter(e => e.event === 'dl_begin_checkout')); + +// Should see event with: +// - event: 'dl_begin_checkout' +// - ecommerce.currency +// - ecommerce.value +// - ecommerce.items[] +// - cart_total (string) +// - ecommerce.coupon (if coupon applied) +``` + +**Expected Behavior:** +- ✅ Event fires when prospect cart is created (after email/name entry) +- ✅ Event fires only ONCE per checkout session +- ✅ Event includes all cart items +- ✅ Event has correct currency, value, and coupon (if applicable) + +### Test `begin_checkout` Event (Express Checkout Click) + +**What to test:** Event fires when express checkout button is clicked + +**Steps:** +1. Navigate to checkout page (with items in cart) +2. Open browser console (F12) +3. Click PayPal, Apple Pay, or Google Pay button +4. Look for console log: `"✅ Express checkout clicked, firing begin_checkout"` +5. Then: `"✅ Fired dl_begin_checkout to GTM"` and `"✅ Fired begin_checkout to Facebook Pixel"` + +**Verify:** +- Same verification steps as above +- Event should fire immediately when express checkout button is clicked +- Event should NOT fire again if already fired (duplicate prevention) + +## Console Debugging + +### Check if Script Loaded +```javascript +// Should see in console when script initializes: +"🔧 Setting up custom analytics triggers" +``` + +### Check Event Firing +```javascript +// Monitor all events +window.dataLayer.forEach(e => { + if (e.event === 'dl_add_to_cart' || e.event === 'dl_begin_checkout') { + console.log('Event fired:', e); + } +}); +``` + +### Check if SDK Events are Blocked +```javascript +// If SDK events are properly blocked, you should NOT see: +// - dl_add_to_cart when items are added to cart +// - dl_begin_checkout when checkout form initializes + +// Only see: +// - dl_add_to_cart when checkout page loads +// - dl_begin_checkout when prospect cart created OR express checkout clicked +``` + +### Quick Test Script + +Add this to browser console for quick testing: + +```javascript +// Test if script is working +function testCustomTriggers() { + console.log('=== Custom Triggers Test ==='); + + // Check if script loaded + console.log('Script loaded:', typeof window.dataLayer !== 'undefined'); + + // Check SDK + console.log('SDK ready:', typeof window.next !== 'undefined'); + + // Check cart + if (window.next) { + const cart = window.next.getCartData(); + console.log('Cart items:', cart?.cartLines?.length || 0); + console.log('Cart total:', cart?.cartTotals?.total?.value || 0); + } + + // Check recent events + const recentEvents = window.dataLayer.slice(-10); + const customEvents = recentEvents.filter(e => + e.event === 'dl_add_to_cart' || e.event === 'dl_begin_checkout' + ); + console.log('Recent custom events:', customEvents); + + // Check if on checkout page + const isCheckout = document.querySelector('form[data-next-checkout], form[os-checkout-form]') !== null; + console.log('On checkout page:', isCheckout); +} + +testCustomTriggers(); +``` + +## GTM Preview Mode Testing + +1. **Open GTM Preview Mode** + - Go to GTM → Preview + - Enter your site URL + +2. **Test add_to_cart** + - Navigate to checkout page + - In GTM Preview, look for `dl_add_to_cart` event + - Click on event to see details + - Verify `ecommerce` object has correct data + +3. **Test begin_checkout** + - Enter email/name OR click express checkout + - In GTM Preview, look for `dl_begin_checkout` event + - Verify event data + +4. **Verify Events are Blocked** + - Add item to cart (should NOT see `dl_add_to_cart`) + - Load checkout page (SHOULD see `dl_add_to_cart`) + - Initialize checkout form (should NOT see `dl_begin_checkout` from SDK) + +## Facebook Events Manager Testing + +1. **Open Facebook Events Manager** + - Go to Events Manager → Test Events + - Enter your site URL + +2. **Test Events** + - Navigate checkout page → Should see `AddToCart` + - Enter email/name OR click express checkout → Should see `InitiateCheckout` + +3. **Verify Event Parameters** + - Check `value` matches cart total + - Check `currency` is correct + - Check `content_ids` has all product IDs + - Check `num_items` matches cart quantity + +## Troubleshooting + +### Issue: Events Not Firing + +**Check:** +1. ✅ Script is included after SDK loader +2. ✅ Cart has items (not empty) +3. ✅ Console shows "🔧 Setting up custom analytics triggers" +4. ✅ No JavaScript errors in console +5. ✅ SDK is initialized (`window.next` exists) + +**Debug:** +```javascript +// Check if SDK is ready +console.log('SDK ready:', typeof window.next !== 'undefined'); + +// Check cart data +if (window.next) { + const cart = window.next.getCartData(); + console.log('Cart data:', cart); + console.log('Cart has items:', cart?.cartLines?.length > 0); +} +``` + +### Issue: Events Firing Multiple Times + +**Check:** +- Script should prevent duplicates with `hasFiredAddToCart` and `hasFiredBeginCheckout` flags +- If seeing duplicates, check if script is included multiple times in HTML + +### Issue: Wrong Event Format + +**Check:** +- Events should use `dl_` prefix (not `add_to_cart`, but `dl_add_to_cart`) - this is Campaign Cart SDK's standard naming convention +- Events should be pushed to `dataLayer` (required for GTM) +- Events can optionally be pushed to `ElevarDataLayer` if using Elevar +- Events should have `ecommerce` object with `currency`, `value`, and `items` + +### Issue: Missing Data in Events + +**Check:** +- Cart must have items before events fire +- Campaign data must be loaded (for currency, package info) +- Check console for warnings: `"Cannot fire add_to_cart: cart is empty"` + +## Success Criteria + +✅ **add_to_cart:** +- Fires when checkout page loads +- Does NOT fire when items added to cart +- Includes all cart items +- Has correct currency and value + +✅ **begin_checkout:** +- Fires when prospect cart created (email/name entry) +- Fires when express checkout clicked +- Does NOT fire when checkout form initializes +- Includes all cart items +- Has correct currency, value, and coupon (if applicable) + +✅ **No Duplicates:** +- Each event fires only once per session +- SDK's default events are blocked +- Only custom script events fire + +## Debugging + +The script logs to console when events are fired: +- `✅ Fired dl_add_to_cart to GTM` +- `✅ Fired add_to_cart to Facebook Pixel` +- `✅ Fired dl_begin_checkout to GTM` +- `✅ Fired begin_checkout to Facebook Pixel` + +Open browser console to see these messages and verify events are firing correctly. + +## Notes + +- Events are only fired once per page load (duplicate prevention) +- Script waits for SDK to be ready before setting up listeners +- Cart must have items for events to fire +- Script gracefully handles missing SDK or analytics tools +- Events use the same format as SDK defaults for consistency + diff --git a/docs/campaign-cart/analytics/examples/custom-webhook.md b/docs/campaign-cart/analytics/examples/custom-webhook.md new file mode 100644 index 00000000..4f287684 --- /dev/null +++ b/docs/campaign-cart/analytics/examples/custom-webhook.md @@ -0,0 +1,814 @@ +--- +title: "Custom Webhook Integration" +description: "Send analytics events to your own backend with batching, retry logic, and custom transformations." +--- + +Send events to your own backend or third-party APIs with control over data transformation, batching, and delivery. + +## Configuration + +Configure the custom webhook provider with your endpoint and optional settings: + +```javascript +providers: { + custom: { + enabled: true, + settings: { + // Required + endpoint: 'https://api.yourapp.com/analytics', + + // Optional headers + headers: { + 'Authorization': 'Bearer YOUR_TOKEN', + 'Content-Type': 'application/json', + 'X-Store-ID': 'store-123' + }, + + // Batching settings + batchSize: 10, // Events per batch + batchIntervalMs: 5000, // Max time before sending + + // Retry configuration + maxRetries: 3, + retryDelayMs: 1000, // Exponential backoff: 1s, 2s, 4s + + // Transform events before sending + transformFunction: (event) => { + return { + ...event, + app_version: '1.0.0', + environment: 'production', + timestamp: new Date().toISOString() + }; + } + } + } +} +``` + +### Configuration Options + +| Option | Type | Required | Default | Description | +|--------|------|----------|---------|-------------| +| `endpoint` | string | Yes | - | The URL of your backend endpoint that receives analytics events | +| `headers` | object | No | `{}` | Custom HTTP headers to include in each request (e.g., auth tokens) | +| `batchSize` | number | No | 10 | Number of events to accumulate before sending a batch | +| `batchIntervalMs` | number | No | 5000 | Maximum milliseconds to wait before sending a batch, regardless of size | +| `maxRetries` | number | No | 3 | Number of times to retry failed requests | +| `retryDelayMs` | number | No | 1000 | Initial delay in milliseconds for retry attempts (exponential backoff) | +| `transformFunction` | function | No | - | Function to transform each event before sending (receives event, returns modified event) | + +## Request Format + +Events are sent as POST requests in batches to your endpoint: + +``` +POST https://api.yourapp.com/analytics +Content-Type: application/json +Authorization: Bearer YOUR_TOKEN + +{ + "events": [ + { + "event": "dl_add_to_cart", + "event_id": "sess_123_1_1234567890", + "event_time": "2025-01-12T10:30:00Z", + "ecommerce": { + "items": [ + { + "item_id": "SKU123", + "item_name": "Product Name", + "price": 29.99, + "quantity": 1 + } + ], + "value": 29.99, + "currency": "USD" + }, + "user_properties": { + "user_id": "user_123", + "session_id": "sess_123", + "country": "US" + }, + "attribution": { + "source": "google", + "medium": "cpc", + "campaign": "summer_sale" + } + }, + { + "event": "dl_view_item", + "event_id": "sess_123_2_1234567891", + "event_time": "2025-01-12T10:30:02Z", + ... + } + ], + "batch_info": { + "size": 2, + "timestamp": "2025-01-12T10:30:05Z", + "source": "next-campaign-cart" + } +} +``` + +### Request Body Fields + +| Field | Type | Description | +|-------|------|-------------| +| `events` | array | Array of event objects (max 100 per batch) | +| `batch_info.size` | number | Number of events in this batch | +| `batch_info.timestamp` | string | ISO 8601 timestamp when batch was sent | +| `batch_info.source` | string | Always "next-campaign-cart" | + +## Response Expected + +Your endpoint should return appropriate HTTP status codes: + +- **`200 OK`** - Events processed successfully +- **`201 Created`** - Events created successfully +- **`202 Accepted`** - Events accepted for processing (recommended) +- **`4xx`** - Client error (SDK will retry based on specific status) +- **`5xx`** - Server error (SDK will retry with exponential backoff) + +### Response Body (Optional) + +While not required, you can return a response body: + +```json +{ + "success": true, + "message": "Analytics events received", + "batch_id": "batch_abc123xyz", + "events_processed": 2 +} +``` + +## Batching Behavior + +Events are batched to reduce network requests and server load. Batches are sent when: + +1. **Size Threshold**: `batchSize` events have been queued +2. **Time Threshold**: `batchIntervalMs` milliseconds have elapsed since the first event in the batch +3. **Page Unload**: Immediately before navigation or window close to ensure data is not lost +4. **Manual Flush**: When explicitly requested through the SDK API (if available) + +### Example Batching Timeline + +With `batchSize: 10` and `batchIntervalMs: 5000`: + +``` +Time Event Batch Status +0ms Event 1 arrives Queue: [1] → Start 5s timer +100ms Event 2 arrives Queue: [1, 2] +500ms Event 3 arrives Queue: [1, 2, 3] +1000ms Event 4 arrives Queue: [1, 2, 3, 4] +1500ms Event 5 arrives Queue: [1, 2, 3, 4, 5] +2000ms Event 6 arrives Queue: [1, 2, 3, 4, 5, 6] +2500ms Event 7 arrives Queue: [1, 2, 3, 4, 5, 6, 7] +3000ms Event 8 arrives Queue: [1, 2, 3, 4, 5, 6, 7, 8] +3500ms Event 9 arrives Queue: [1, 2, 3, 4, 5, 6, 7, 8, 9] +4000ms Event 10 arrives Queue: [1-10] → SEND BATCH (size threshold met) +4050ms Event 11 arrives Queue: [11] → Start new 5s timer +5000ms (5s elapsed) Queue: [11-15] → SEND BATCH (time threshold met) +``` + +## Retry Logic + +Failed requests retry with exponential backoff to prevent overwhelming your server during temporary outages. + +### Retry Schedule + +- **Attempt 1**: Immediate, no delay +- **Attempt 2**: Wait `retryDelayMs` (default: 1s) +- **Attempt 3**: Wait `retryDelayMs * 2` (default: 2s) +- **Attempt 4**: Wait `retryDelayMs * 4` (default: 4s) + +After `maxRetries` attempts, events are dropped and logged. + +### Example with Default Settings + +``` +Time Event Status +0ms Send batch Fails (500 error) +0ms Schedule retry 1 +1000ms Retry attempt 1 Fails (500 error) +1000ms Schedule retry 2 +3000ms Retry attempt 2 Fails (503 timeout) +3000ms Schedule retry 3 +7000ms Retry attempt 3 Succeeds (200 OK) + Batch sent! +``` + +### Retry Configuration Examples + +**Aggressive Retries** (for critical analytics): +```javascript +maxRetries: 5, +retryDelayMs: 500, // 0.5s, 1s, 2s, 4s, 8s = 15.5s total +``` + +**Conservative Retries** (for high-volume events): +```javascript +maxRetries: 2, +retryDelayMs: 2000, // 2s, 4s = 6s total +``` + +**No Retries** (if endpoint is highly available): +```javascript +maxRetries: 0, // Send once, drop on failure +``` + +## Transform Function + +The `transformFunction` option allows you to modify, filter, or enrich events before they're sent to your backend. This function is called once for each event. + +### Basic Transform Function + +```javascript +transformFunction: (event) => { + // Add custom fields + event.app_version = '1.0.0'; + event.environment = 'production'; + event.server_timestamp = Date.now(); + + // Filter sensitive data + if (event.user_properties) { + delete event.user_properties.customer_phone; + } + + // Add business context + if (event.event === 'dl_purchase') { + event.custom_order_type = 'online'; + event.fulfillment_center = 'US-WEST'; + } + + return event; +} +``` + +### Advanced Transform Examples + +#### Conditional Field Addition + +```javascript +transformFunction: (event) => { + // Add fields only for specific events + if (event.event === 'dl_purchase') { + event.revenue_category = event.ecommerce?.value > 100 ? 'high' : 'normal'; + } + + // Add user tier information + if (event.user_properties?.user_id) { + event.user_tier = calculateUserTier(event.user_properties.user_id); + } + + return event; +} +``` + +#### Data Validation and Sanitization + +```javascript +transformFunction: (event) => { + // Ensure required fields + if (!event.event_time) { + event.event_time = new Date().toISOString(); + } + + // Sanitize strings + if (event.user_properties?.session_id) { + event.user_properties.session_id = + event.user_properties.session_id.replace(/[^a-zA-Z0-9_-]/g, ''); + } + + // Convert currencies + if (event.ecommerce?.currency === 'EUR') { + event.ecommerce.value_usd = event.ecommerce.value * 0.92; + } + + return event; +} +``` + +#### PII Redaction + +```javascript +transformFunction: (event) => { + const sensitiveFields = ['email', 'phone', 'ssn', 'credit_card']; + + if (event.user_properties) { + sensitiveFields.forEach(field => { + if (event.user_properties[field]) { + delete event.user_properties[field]; + } + }); + } + + return event; +} +``` + +#### Event Sampling + +```javascript +transformFunction: (event) => { + // Sample non-purchase events at 10% to reduce volume + if (event.event !== 'dl_purchase') { + if (Math.random() > 0.1) { + return null; // Drop this event + } + } + + return event; +} +``` + +#### Regional Routing + +```javascript +transformFunction: (event) => { + const region = getUserRegion(); + event.endpoint_region = region; + + // Set processing priority + event.processing_priority = event.event === 'dl_purchase' ? 'high' : 'normal'; + + // Add regional context + event.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + + return event; +} +``` + +## Use Cases + +### Data Warehouse Integration + +Forward events to your data warehouse for long-term analysis and reporting: + +```javascript +endpoint: 'https://api.yourdata.com/ingest/analytics', +headers: { + 'Authorization': 'Bearer warehouse-token', + 'X-Dataset-ID': 'campaign-cart-events' +}, +batchSize: 50, // Larger batches for warehouse efficiency +batchIntervalMs: 10000, +transformFunction: (event) => { + return { + ...event, + warehouse_partition: new Date().toISOString().split('T')[0], + ingestion_source: 'campaign-cart' + }; +} +``` + +**Supported**: Snowflake, BigQuery, Redshift, Databricks, etc. + +### Custom Analytics Platform + +Send events to your own analytics infrastructure: + +```javascript +endpoint: 'https://analytics.yourcompany.com/api/v1/events', +headers: { + 'Authorization': 'Bearer api-key', + 'X-App-ID': 'campaign-cart' +}, +maxRetries: 3, +transformFunction: (event) => { + return { + ...event, + event_version: '2.0', + company_id: 'comp_123', + received_at: new Date().toISOString() + }; +} +``` + +### Marketing Automation + +Trigger marketing workflows based on analytics events: + +```javascript +endpoint: 'https://api.marketing-platform.com/webhooks/events', +headers: { + 'Authorization': 'Bearer webhook-key', + 'X-Integration-ID': 'cart-system' +}, +batchSize: 1, // Send events immediately +batchIntervalMs: 100, +transformFunction: (event) => { + // Only send customer conversion events + if (!['dl_purchase', 'dl_view_item'].includes(event.event)) { + return null; + } + + return { + ...event, + trigger_type: event.event === 'dl_purchase' ? 'purchase' : 'view', + automation_enabled: true + }; +} +``` + +**Platforms**: HubSpot, Klaviyo, Braze, Marketo, etc. + +### Real-time Processing + +Process events in real-time systems for immediate action: + +```javascript +endpoint: 'https://streaming.yourcompany.com/events', +headers: { + 'Authorization': 'Bearer stream-key' +}, +batchSize: 5, +batchIntervalMs: 1000, +transformFunction: (event) => { + return { + ...event, + processing_timestamp: Date.now(), + priority: ['dl_purchase', 'dl_error'].includes(event.event) ? 'high' : 'normal' + }; +} +``` + +**Platforms**: Kafka, RabbitMQ, Apache Pulsar, AWS Kinesis, etc. + +### Multi-region Distribution + +Send different events to different regional endpoints: + +```javascript +endpoint: 'https://api.yourapp.com/analytics', +transformFunction: (event) => { + // Determine user region + const region = getUserRegion(); // 'us', 'eu', 'apac' + + return { + ...event, + target_region: region, + regional_endpoint: `https://api-${region}.yourapp.com/analytics`, + processing_priority: event.event === 'dl_purchase' ? 'high' : 'normal', + is_critical_event: ['dl_purchase', 'dl_error'].includes(event.event) + }; +} +``` + +## Example: Multi-endpoint Setup + +For advanced scenarios where you need to send events to multiple endpoints or route them based on event type: + +```javascript +import { useAnalytics } from '@campaign-cart/analytics'; + +const analytics = useAnalytics({ + providers: { + custom: { + enabled: true, + settings: { + endpoint: 'https://api.yourapp.com/analytics', + headers: { + 'Authorization': 'Bearer YOUR_TOKEN', + 'Content-Type': 'application/json' + }, + batchSize: 10, + batchIntervalMs: 5000, + maxRetries: 3, + retryDelayMs: 1000, + + // Advanced routing based on event type and attributes + transformFunction: (event) => { + const transformed = { + ...event, + app_version: '1.0.0', + environment: process.env.NODE_ENV + }; + + // Route purchase events to high-priority endpoint + if (event.event === 'dl_purchase') { + transformed.endpoint_priority = 'high'; + transformed.endpoint_type = 'purchases'; + transformed.requires_acknowledgment = true; + } + + // Route behavioral events normally + else if (['dl_view_item', 'dl_add_to_cart', 'dl_remove_from_cart'].includes(event.event)) { + transformed.endpoint_priority = 'normal'; + transformed.endpoint_type = 'behaviors'; + } + + // Route errors to special endpoint + else if (event.event === 'dl_error') { + transformed.endpoint_priority = 'critical'; + transformed.endpoint_type = 'errors'; + transformed.alert_on_receipt = true; + } + + // Add regional information + const userRegion = detectUserRegion(); + transformed.region = userRegion; + transformed.regional_endpoint = `https://api-${userRegion}.yourapp.com/analytics`; + + return transformed; + } + } + } + } +}); + +// Helper function to detect user region +function detectUserRegion() { + if (typeof window === 'undefined') return 'default'; + + const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + + if (timezone.includes('America')) return 'us'; + if (timezone.includes('Europe')) return 'eu'; + if (timezone.includes('Asia') || timezone.includes('Australia')) return 'apac'; + + return 'default'; +} + +// In your component +analytics.track('dl_purchase', { + ecommerce: { + value: 99.99, + currency: 'USD', + items: [ + { item_id: 'SKU123', item_name: 'Product', quantity: 1, price: 99.99 } + ] + } +}); +``` + +## Troubleshooting + +### Events Not Being Sent + +**Problem**: Analytics events are not reaching your endpoint. + +**Solutions**: +1. **Check endpoint URL**: Ensure the `endpoint` is correct and accessible + ```javascript + // Test in browser console + fetch('https://api.yourapp.com/analytics', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ test: true }) + }) + ``` + +2. **Verify CORS headers**: Ensure your endpoint allows requests from your domain + ``` + Access-Control-Allow-Origin: * + Access-Control-Allow-Methods: POST, OPTIONS + Access-Control-Allow-Headers: Content-Type, Authorization + ``` + +3. **Check browser console**: Look for network errors (404, 403, CORS errors) + - Open DevTools → Network tab → Filter for your endpoint + - Check Request/Response headers and body + +4. **Verify configuration**: Ensure custom provider is enabled + ```javascript + providers: { + custom: { + enabled: true, // Must be true + settings: { ... } + } + } + ``` + +### 401 Unauthorized Errors + +**Problem**: Requests are rejected with 401 status. + +**Solutions**: +1. **Check authorization header**: + ```javascript + headers: { + 'Authorization': 'Bearer YOUR_VALID_TOKEN' // Use actual token + } + ``` + +2. **Verify token expiration**: Ensure tokens are refreshed periodically + ```javascript + headers: { + 'Authorization': `Bearer ${getValidToken()}` + } + ``` + +3. **Test endpoint authentication**: + ```bash + curl -X POST https://api.yourapp.com/analytics \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"events": [], "batch_info": {}}' + ``` + +### Retries Not Working + +**Problem**: Failed requests are not being retried. + +**Solutions**: +1. **Check retry configuration**: + ```javascript + maxRetries: 3, // Must be > 0 + retryDelayMs: 1000 // Must be positive + ``` + +2. **Check status codes**: SDK only retries on 5xx, timeouts, and network errors + - 4xx errors (except some specific cases) are not retried + - 2xx responses are considered success + +3. **Monitor retry logs**: Enable debug logging (if available) + ```javascript + // Check browser DevTools console for messages like: + // "Analytics: Retrying batch (attempt 2 of 3)..." + ``` + +### High Event Loss + +**Problem**: Events are being dropped before reaching your endpoint. + +**Solutions**: +1. **Increase batch time limits**: + ```javascript + // Give more time before dropping + batchIntervalMs: 10000, // Increased from 5000 + maxRetries: 5 // More retry attempts + ``` + +2. **Implement page unload handler** (SDK should handle this automatically) + +3. **Check transform function**: Ensure it's not filtering out events + ```javascript + transformFunction: (event) => { + console.log('Event:', event); // Debug what's being sent + return event; + } + ``` + +### Transform Function Issues + +**Problem**: Events are being modified incorrectly or transform function has errors. + +**Solutions**: +1. **Add error handling**: + ```javascript + transformFunction: (event) => { + try { + // Your transform logic + return { + ...event, + custom_field: calculateValue(event) + }; + } catch (error) { + console.error('Transform error:', error); + return event; // Return original if transform fails + } + } + ``` + +2. **Validate return value**: Always return an event object or null + ```javascript + transformFunction: (event) => { + // WRONG: return undefined + // CORRECT: return event or null + + if (shouldDropEvent(event)) { + return null; + } + + return modifiedEvent; + } + ``` + +3. **Test transform function independently**: + ```javascript + const testEvent = { event: 'dl_view_item', ... }; + const result = transformFunction(testEvent); + console.log('Transform result:', result); + ``` + +### Endpoint Timeouts + +**Problem**: Requests are timing out and being retried excessively. + +**Solutions**: +1. **Reduce batch size**: + ```javascript + batchSize: 5, // Smaller payloads = faster processing + ``` + +2. **Increase request timeout** (if configurable): + ```javascript + requestTimeoutMs: 10000 // 10 second timeout + ``` + +3. **Check backend performance**: + ```bash + # Monitor endpoint response times + curl -w "@curl-format.txt" -o /dev/null -s \ + https://api.yourapp.com/analytics + ``` + +4. **Verify network connectivity**: Check if your server can reach the endpoint + +### Duplicate Events at Endpoint + +**Problem**: Events appear to be received multiple times. + +**Solutions**: +1. **Implement idempotency**: + - Use `event_id` field (already unique per event) + - Store processed event IDs to detect duplicates + ```javascript + // In your backend + const processedEventIds = new Set(); + + if (processedEventIds.has(event.event_id)) { + return { success: true }; // Already processed + } + + processedEventIds.add(event.event_id); + // Process event... + ``` + +2. **Check batch_info**: Events in same batch have unique IDs + ```javascript + const eventIds = batch.events.map(e => e.event_id); + const uniqueIds = new Set(eventIds); + + if (uniqueIds.size !== eventIds.length) { + // Handle duplicate detection + } + ``` + +3. **Verify retry handling**: Ensure retried batches aren't duplicated + - Use batch IDs if implementing retry deduplication + +### Backend Not Receiving Headers + +**Problem**: Custom headers aren't appearing in requests. + +**Solutions**: +1. **Check header configuration**: + ```javascript + headers: { + 'Authorization': 'Bearer token', + 'X-Custom-Header': 'value' + } + ``` + +2. **Verify CORS headers** (for browser requests): + - `Access-Control-Allow-Headers` must include custom header names + ``` + Access-Control-Allow-Headers: Authorization, Content-Type, X-Custom-Header + ``` + +3. **Check header case sensitivity**: Some servers normalize headers + ```javascript + // Both work, but be consistent + 'Authorization': 'Bearer token' + 'authorization': 'Bearer token' + ``` + +### Memory Leaks with Events + +**Problem**: Memory usage increases continuously. + +**Solutions**: +1. **Limit batch size**: + ```javascript + batchSize: 20, // Don't queue too many + batchIntervalMs: 3000 // Send more frequently + ``` + +2. **Implement event sampling** in transform function: + ```javascript + transformFunction: (event) => { + // Sample down high-volume events + if (event.event === 'dl_page_view' && Math.random() > 0.1) { + return null; // Drop 90% of page views + } + return event; + } + ``` + +3. **Check for event listener leaks**: Ensure analytics instance is properly cleaned up + +## Best Practices + +1. **Always return a 2xx status** from your endpoint when events are received +2. **Make endpoints idempotent** using event IDs to handle retries safely +3. **Log all received events** for debugging and auditing +4. **Monitor endpoint performance** to catch issues early +5. **Use appropriate batch sizes** based on your network and server capacity +6. **Implement rate limiting** on your endpoint to prevent abuse +7. **Validate incoming data** to ensure it matches your schema +8. **Archive analytics data** for historical analysis +9. **Alert on errors** (via email, Slack, etc.) for critical endpoints +10. **Version your endpoint** (e.g., `/api/v1/analytics`) for backward compatibility diff --git a/docs/campaign-cart/analytics/examples/direct-ga4.md b/docs/campaign-cart/analytics/examples/direct-ga4.md new file mode 100644 index 00000000..fa8e2964 --- /dev/null +++ b/docs/campaign-cart/analytics/examples/direct-ga4.md @@ -0,0 +1,465 @@ +--- +title: "Direct GA4 Integration (No GTM)" +description: "Send events directly to Google Analytics 4 without using Google Tag Manager using an event transformer." +--- + + +## Overview + +This guide shows how to send Campaign Cart SDK events directly to Google Analytics 4 **without using Google Tag Manager** by using an event transformer script. + +:::tip +This is a specific implementation of the general [Event Transformers](/docs/campaign-cart/analytics/examples/event-transformers/) pattern. You can use the same approach for TikTok, Snapchat, Pinterest, or any other platform. +::: + +## When to Use This + +Use the GA4 Bridge when: +- You want to send events directly to GA4 without GTM +- You're already using GA4 and don't want to set up GTM +- You need simpler setup with fewer moving parts +- You want to avoid GTM's additional layer + +:::tip +If you're using Google Tag Manager, you don't need this script - the SDK already pushes events to `window.dataLayer` that GTM can consume. +::: + +## Setup + +1. **Add Google Analytics 4** + + Add the GA4 tracking code to your page: + + ```html + + + + ``` + + Replace `G-XXXXXXXXXX` with your GA4 Measurement ID. + +2. **Add the Bridge Script** + + Add the NextDataLayer GA4 Bridge script **after** the Campaign Cart SDK: + + ```html + + + + + + ``` + +3. **Enable Analytics in SDK Config** + + ```javascript + window.nextConfig = { + apiKey: 'your-api-key', + analytics: { + enabled: true, + mode: 'auto' + } + }; + ``` + + + +That's it! Events will automatically flow from Campaign Cart SDK → NextDataLayer → GA4. + +## How It Works + +The bridge script: + +1. **Waits for NextDataLayer** to be available +2. **Intercepts events** pushed to `window.NextDataLayer` +3. **Converts event names** from `dl_*` format to standard GA4 format +4. **Formats ecommerce data** according to GA4 specification +5. **Pushes to window.dataLayer** for GA4 to consume +6. **Prevents duplicates** using event deduplication +7. **Handles upsells** by converting them to purchase events + +### Event Conversion Flow + +``` +Campaign Cart SDK Event + ↓ +NextDataLayer (dl_add_to_cart) + ↓ +GA4 Bridge Script (converts) + ↓ +window.dataLayer (add_to_cart) + ↓ +Google Analytics 4 +``` + +## Event Mapping + +The bridge automatically converts SDK events to GA4 standard events: + +### E-commerce Events + +| SDK Event | GA4 Event | Description | +|-----------|-----------|-------------| +| `dl_view_item` | `view_item` | Product viewed | +| `dl_view_item_list` | `view_item_list` | Product list viewed | +| `dl_add_to_cart` | `add_to_cart` | Item added to cart | +| `dl_remove_from_cart` | `remove_from_cart` | Item removed from cart | +| `dl_view_cart` | `view_cart` | Cart page viewed | +| `dl_begin_checkout` | `begin_checkout` | Checkout started | +| `dl_add_shipping_info` | `add_shipping_info` | Shipping info added | +| `dl_add_payment_info` | `add_payment_info` | Payment info added | +| `dl_purchase` | `purchase` | Order completed | + +### Upsell Events + +| SDK Event | GA4 Event | Special Handling | +|-----------|-----------|------------------| +| `dl_viewed_upsell` | `view_item` | Upsell viewed as product view | +| `dl_accepted_upsell` | `purchase` | **Upsell converted to purchase with same transaction_id** | + +:::caution +Upsell events are converted to `purchase` events using the **same transaction_id** as the original purchase. This allows GA4 to track upsells as additional revenue on the same order. +::: + +### User Events + +| SDK Event | GA4 Event | +|-----------|-----------| +| `dl_sign_up` | `sign_up` | +| `dl_login` | `login` | + +## Features + +### 1. Duplicate Prevention + +The bridge tracks processed events and prevents duplicates: + +```javascript +// Events are deduplicated using: +// - Event name +// - Sequence number +// - Item ID +// - Order ID +``` + +### 2. Upsell Tracking + +Upsells are automatically converted to purchase events with the original transaction ID: + +```javascript +// Original purchase +{ + event: 'purchase', + transaction_id: 'ORD-12345', + value: 99.99 +} + +// Upsell (automatically uses same transaction_id) +{ + event: 'purchase', + transaction_id: 'ORD-12345', // Same as original! + value: 29.99, + items: [{ item_category: 'Upsell', ... }] +} +``` + +This allows GA4 to track total order value including upsells. + +### 3. Ecommerce Object Clearing + +The bridge follows GA4 best practices by clearing the ecommerce object before each event: + +```javascript +// Clear ecommerce first +window.dataLayer.push({ ecommerce: null }); + +// Then push new event +window.dataLayer.push({ + event: 'add_to_cart', + ecommerce: { ... } +}); +``` + +### 4. Memory Management + +Automatically cleans up old data to prevent memory leaks: +- Keeps last 500 processed events +- Keeps last 50 transaction IDs +- Runs cleanup every 60 seconds + +### 5. Debug Mode + +Debug logging is automatically enabled on: +- `localhost` +- URLs with `?debug=true` + +View logs in browser console: + +``` +[GA4 Bridge] Initializing dataLayer bridge +[GA4 Bridge] Converted dl_add_to_cart → add_to_cart +[GA4 Bridge] Upsell converted to purchase with transaction_id: ORD-12345 +``` + +## Bridge API + +The bridge exposes debugging utilities on `window.GA4Bridge`: + +### Get Processed Event Count + +```javascript +const count = window.GA4Bridge.getProcessedCount(); +console.log(`Processed ${count} events`); +``` + +### View Event Mapping + +```javascript +const mapping = window.GA4Bridge.getEventMap(); +console.log(mapping); +// { +// 'dl_add_to_cart': 'add_to_cart', +// 'dl_purchase': 'purchase', +// ... +// } +``` + +### View Transaction Map + +```javascript +const transactions = window.GA4Bridge.getTransactionMap(); +console.log(transactions); +// { +// '123': { transaction_id: 'ORD-12345', order_number: '12345' }, +// ... +// } +``` + +### Check Bridge Status + +```javascript +const isActive = window.GA4Bridge.isActive(); +console.log(`Bridge active: ${isActive}`); +``` + +## Example Event Flow + +### Cart Event + +SDK fires: +```javascript +window.NextDataLayer.push({ + event: 'dl_add_to_cart', + event_id: '1234567890_abc123', + ecommerce: { + currency: 'USD', + value: 99.99, + items: [{ + item_id: 'SKU-123', + item_name: 'Product Name', + price: 99.99, + quantity: 1 + }] + } +}); +``` + +Bridge converts to: +```javascript +window.dataLayer.push({ ecommerce: null }); // Clear first +window.dataLayer.push({ + event: 'add_to_cart', + event_id: '1234567890_abc123', + ecommerce: { + currency: 'USD', + value: 99.99, + items: [{ + item_id: 'SKU-123', + item_name: 'Product Name', + price: 99.99, + quantity: 1 + }] + } +}); +``` + +### Purchase Event + +SDK fires: +```javascript +window.NextDataLayer.push({ + event: 'dl_purchase', + ecommerce: { + transaction_id: 'ORD-12345', + order_number: '12345', + value: 159.99, + currency: 'USD', + tax: 9.99, + shipping: 10.00, + items: [...] + } +}); +``` + +Bridge stores transaction ID and converts to: +```javascript +window.dataLayer.push({ ecommerce: null }); +window.dataLayer.push({ + event: 'purchase', + ecommerce: { + transaction_id: 'ORD-12345', + order_number: '12345', + value: 159.99, + currency: 'USD', + tax: 9.99, + shipping: 10.00, + items: [...] + } +}); +``` + +### Upsell Event + +SDK fires: +```javascript +window.NextDataLayer.push({ + event: 'dl_accepted_upsell', + order_id: '123', + upsell: { + package_id: 'warranty-extended', + package_name: 'Extended Warranty', + price: 29.99, + quantity: 1 + } +}); +``` + +Bridge converts to: +```javascript +window.dataLayer.push({ ecommerce: null }); +window.dataLayer.push({ + event: 'purchase', // Converted to purchase! + ecommerce: { + transaction_id: 'ORD-12345', // Same as original purchase + order_number: '12345', + value: 29.99, + currency: 'USD', + items: [{ + item_id: 'warranty-extended', + item_name: 'Extended Warranty', + price: 29.99, + quantity: 1, + item_category: 'Upsell' + }] + } +}); +``` + +## Troubleshooting + +### Events Not Appearing in GA4 + +1. **Check GA4 is loaded** + ```javascript + console.log(typeof gtag); // Should be 'function' + ``` + +2. **Check bridge is active** + ```javascript + console.log(window.GA4Bridge.isActive()); // Should be true + ``` + +3. **Check events are being converted** + - Open browser console + - Add `?debug=true` to URL + - Look for `[GA4 Bridge]` log messages + +4. **Verify NextDataLayer exists** + ```javascript + console.log(window.NextDataLayer); // Should be an array + ``` + +### Duplicate Events + +The bridge automatically prevents duplicates, but if you see duplicates: + +1. Make sure you're only loading the bridge script once +2. Check that you're not also pushing events to `window.dataLayer` manually +3. Verify the bridge is loaded after the SDK + +### Upsells Not Tracking + +If upsells aren't showing up as purchases: + +1. **Check transaction map** + ```javascript + console.log(window.GA4Bridge.getTransactionMap()); + ``` + +2. **Verify purchase event fired first** + - The initial purchase must fire before upsells + - Transaction ID is stored from the purchase event + +3. **Check order_id is present** + - Upsell events need `order_id` to look up transaction_id + +### Missing Transaction IDs + +If upsells show `undefined` transaction_id: + +- The initial `dl_purchase` event must include `ecommerce.transaction_id` +- The upsell event must include `order_id` matching the purchase +- Check the transaction map: `window.GA4Bridge.getTransactionMap()` + +## Performance + +The bridge is lightweight and efficient: +- **~5KB** minified +- Processes events in **<1ms** +- Memory-safe with automatic cleanup +- No external dependencies + +## Comparison: Bridge vs GTM + +| Feature | GA4 Bridge | Google Tag Manager | +|---------|------------|-------------------| +| Setup complexity | Simple (1 script) | Complex (container setup) | +| Event conversion | Automatic | Manual triggers/tags | +| Upsell handling | Built-in | Manual configuration | +| Deduplication | Automatic | Must configure | +| Additional tracking | Limited | Unlimited | +| Tag management | None | Full tag management | +| Best for | Direct GA4 | Multiple platforms | + +## When NOT to Use This + +Don't use the GA4 Bridge if: +- You're already using GTM successfully +- You need to send data to multiple platforms (Facebook, TikTok, etc.) +- You need complex tag management and triggering +- You want to manage tags without code deployments + +In these cases, use Google Tag Manager instead. See [Google Tag Manager Setup](/docs/campaign-cart/analytics/examples/google-tag-manager/). + +## Build Your Own Transformer + +Want to adapt this pattern for other platforms (TikTok, Snapchat, Pinterest)? + +See **[Event Transformers](/docs/campaign-cart/analytics/examples/event-transformers/)** for: +- Generic transformer template +- Examples for TikTok, Snapchat, Pinterest +- Multi-platform routing +- Best practices and patterns + +## Related Documentation + +- **[Event Transformers](/docs/campaign-cart/analytics/examples/event-transformers/)** - General pattern for any platform +- [Google Tag Manager Setup](/docs/campaign-cart/analytics/examples/google-tag-manager/) - Alternative using GTM +- [Analytics Overview](/docs/campaign-cart/analytics/) - Main analytics documentation +- [Event Reference](/docs/campaign-cart/analytics/events/) - All available events +- [Configuration](/docs/campaign-cart/analytics/configuration/) - SDK configuration options diff --git a/docs/campaign-cart/analytics/examples/event-transformers.md b/docs/campaign-cart/analytics/examples/event-transformers.md new file mode 100644 index 00000000..575b81c8 --- /dev/null +++ b/docs/campaign-cart/analytics/examples/event-transformers.md @@ -0,0 +1,681 @@ +--- +title: "Event Transformers" +description: "Build custom event transformers to convert NextDataLayer events to any platform's format." +sidebar_position: 13 +--- + +## Overview + +Event transformers are scripts that intercept events from `window.NextDataLayer` and convert them to formats required by different analytics platforms. This pattern allows you to: + +- Send events to platforms not natively supported by the SDK +- Convert SDK events to platform-specific formats +- Add custom logic for event processing +- Route events to multiple destinations with different formats + +## How It Works + +The transformer pattern: + +1. **Waits** for `window.NextDataLayer` to be available +2. **Intercepts** events by overriding the `push()` method +3. **Transforms** events to platform-specific format +4. **Routes** transformed events to the destination +5. **Prevents duplicates** using event deduplication + +### Basic Flow + +``` +Campaign Cart SDK + ↓ +window.NextDataLayer.push() + ↓ +Transformer (intercepts) + ↓ +Convert format + ↓ +window.platformLayer.push() + ↓ +Platform (GA4, TikTok, etc.) +``` + +## Basic Transformer Template + +Here's a generic template you can adapt for any platform: + +```javascript +(function() { + 'use strict'; + + // Track processed events to avoid duplicates + const processedEvents = new Set(); + + // Your event mapping + const EVENT_MAP = { + 'dl_view_item': 'ViewContent', // Platform-specific name + 'dl_add_to_cart': 'AddToCart', + 'dl_purchase': 'Purchase' + // Add more mappings... + }; + + // Wait for NextDataLayer + const initTransformer = () => { + if (!window.NextDataLayer) { + setTimeout(initTransformer, 100); + return; + } + + console.log('[Transformer] Initializing...'); + + // Override the push method + const originalPush = window.NextDataLayer.push; + + window.NextDataLayer.push = function(...args) { + // Call original push first + const result = originalPush.apply(window.NextDataLayer, args); + + // Process each pushed item + args.forEach(item => { + if (item && typeof item === 'object' && item.event) { + processEvent(item); + } + }); + + return result; + }; + + // Process existing events + window.NextDataLayer.forEach(item => { + if (item && typeof item === 'object' && item.event) { + processEvent(item); + } + }); + + console.log('[Transformer] Initialized successfully'); + }; + + // Process individual events + const processEvent = (event) => { + // Skip if not mapped + if (!EVENT_MAP[event.event]) { + return; + } + + // Create unique ID to prevent duplicates + const eventId = `${event.event}_${event._metadata?.sequence_number || Date.now()}`; + + if (processedEvents.has(eventId)) { + return; + } + + processedEvents.add(eventId); + + // Get platform-specific event name + const platformEventName = EVENT_MAP[event.event]; + + // Build platform-specific event + const platformEvent = { + event: platformEventName, + // Add your platform-specific fields... + }; + + // Send to platform (customize this!) + window.yourPlatformLayer = window.yourPlatformLayer || []; + window.yourPlatformLayer.push(platformEvent); + + console.log(`[Transformer] Converted ${event.event} → ${platformEventName}`, platformEvent); + }; + + // Clean up old events periodically (prevent memory leak) + setInterval(() => { + if (processedEvents.size > 1000) { + const entriesToKeep = Array.from(processedEvents).slice(-500); + processedEvents.clear(); + entriesToKeep.forEach(entry => processedEvents.add(entry)); + } + }, 60000); + + // Start the transformer + initTransformer(); +})(); +``` + +## Platform-Specific Examples + +### GA4 Transformer + +Convert events to Google Analytics 4 format and send to `window.dataLayer`: + +```javascript +const EVENT_MAP = { + 'dl_add_to_cart': 'add_to_cart', + 'dl_remove_from_cart': 'remove_from_cart', + 'dl_view_item': 'view_item', + 'dl_view_item_list': 'view_item_list', + 'dl_begin_checkout': 'begin_checkout', + 'dl_purchase': 'purchase', + 'dl_add_payment_info': 'add_payment_info', + 'dl_add_shipping_info': 'add_shipping_info', + 'dl_login': 'login', + 'dl_sign_up': 'sign_up' +}; + +const processEvent = (event) => { + if (!EVENT_MAP[event.event]) return; + + const ga4Event = { + event: EVENT_MAP[event.event], + event_id: event.event_id + }; + + // Handle ecommerce events + if (event.ecommerce) { + // Clear ecommerce first (GTM best practice) + window.dataLayer.push({ ecommerce: null }); + + ga4Event.ecommerce = { + ...event.ecommerce, + items: event.ecommerce.items?.map(item => ({ + item_id: item.item_id || item.id, + item_name: item.item_name || item.name, + price: item.price, + quantity: item.quantity, + currency: item.currency + })) + }; + } + + window.dataLayer.push(ga4Event); +}; +``` + +### TikTok Pixel Transformer + +Convert events to TikTok Pixel format: + +```javascript +const EVENT_MAP = { + 'dl_view_item': 'ViewContent', + 'dl_add_to_cart': 'AddToCart', + 'dl_begin_checkout': 'InitiateCheckout', + 'dl_purchase': 'CompletePayment', + 'dl_view_item_list': 'ViewContent' +}; + +const processEvent = (event) => { + if (!EVENT_MAP[event.event]) return; + + const tiktokEventName = EVENT_MAP[event.event]; + + // Build TikTok event data + const tiktokData = { + content_type: 'product' + }; + + if (event.ecommerce) { + tiktokData.currency = event.ecommerce.currency; + tiktokData.value = event.ecommerce.value; + + if (event.ecommerce.items?.[0]) { + tiktokData.content_id = event.ecommerce.items[0].item_id; + tiktokData.content_name = event.ecommerce.items[0].item_name; + } + + // For purchase events + if (event.event === 'dl_purchase') { + tiktokData.content_ids = event.ecommerce.items?.map(i => i.item_id); + tiktokData.contents = event.ecommerce.items?.map(i => ({ + content_id: i.item_id, + content_name: i.item_name, + quantity: i.quantity, + price: i.price + })); + } + } + + // Send to TikTok Pixel + if (window.ttq) { + window.ttq.track(tiktokEventName, tiktokData); + console.log(`[TikTok] ${event.event} → ${tiktokEventName}`, tiktokData); + } +}; +``` + +### Snapchat Pixel Transformer + +Convert events to Snapchat Pixel format: + +```javascript +const EVENT_MAP = { + 'dl_view_item': 'VIEW_CONTENT', + 'dl_add_to_cart': 'ADD_CART', + 'dl_begin_checkout': 'START_CHECKOUT', + 'dl_purchase': 'PURCHASE', + 'dl_sign_up': 'SIGN_UP' +}; + +const processEvent = (event) => { + if (!EVENT_MAP[event.event]) return; + + const snapEventName = EVENT_MAP[event.event]; + + const snapData = {}; + + if (event.ecommerce) { + snapData.currency = event.ecommerce.currency; + snapData.price = event.ecommerce.value; + + if (event.ecommerce.transaction_id) { + snapData.transaction_id = event.ecommerce.transaction_id; + } + + if (event.ecommerce.items?.length > 0) { + snapData.item_ids = event.ecommerce.items.map(i => i.item_id); + snapData.item_category = event.ecommerce.items[0].item_category; + snapData.number_items = event.ecommerce.items.length; + } + } + + // Send to Snapchat Pixel + if (window.snaptr) { + window.snaptr('track', snapEventName, snapData); + console.log(`[Snapchat] ${event.event} → ${snapEventName}`, snapData); + } +}; +``` + +### Pinterest Tag Transformer + +Convert events to Pinterest Tag format: + +```javascript +const EVENT_MAP = { + 'dl_view_item': 'pagevisit', + 'dl_add_to_cart': 'addtocart', + 'dl_begin_checkout': 'checkout', + 'dl_purchase': 'checkout', + 'dl_sign_up': 'signup', + 'dl_view_item_list': 'viewcategory' +}; + +const processEvent = (event) => { + if (!EVENT_MAP[event.event]) return; + + const pinterestEventName = EVENT_MAP[event.event]; + + const pinterestData = {}; + + if (event.ecommerce) { + pinterestData.currency = event.ecommerce.currency; + pinterestData.value = event.ecommerce.value; + + if (event.ecommerce.items?.length > 0) { + pinterestData.line_items = event.ecommerce.items.map(item => ({ + product_name: item.item_name, + product_id: item.item_id, + product_price: item.price, + product_quantity: item.quantity + })); + } + + if (event.event === 'dl_purchase' && event.ecommerce.transaction_id) { + pinterestData.order_id = event.ecommerce.transaction_id; + pinterestData.order_quantity = event.ecommerce.items?.reduce((sum, i) => sum + i.quantity, 0); + } + } + + // Send to Pinterest Tag + if (window.pintrk) { + window.pintrk('track', pinterestEventName, pinterestData); + console.log(`[Pinterest] ${event.event} → ${pinterestEventName}`, pinterestData); + } +}; +``` + +## Advanced Patterns + +### Multi-Platform Transformer + +Send events to multiple platforms from one transformer: + +```javascript +const processEvent = (event) => { + // Send to GA4 + if (window.dataLayer && GA4_EVENT_MAP[event.event]) { + const ga4Event = buildGA4Event(event); + window.dataLayer.push(ga4Event); + } + + // Send to TikTok + if (window.ttq && TIKTOK_EVENT_MAP[event.event]) { + const tiktokData = buildTikTokEvent(event); + window.ttq.track(TIKTOK_EVENT_MAP[event.event], tiktokData); + } + + // Send to Snapchat + if (window.snaptr && SNAP_EVENT_MAP[event.event]) { + const snapData = buildSnapEvent(event); + window.snaptr('track', SNAP_EVENT_MAP[event.event], snapData); + } +}; +``` + +### Conditional Routing + +Route events based on conditions: + +```javascript +const processEvent = (event) => { + // Only send high-value purchases to premium platforms + if (event.event === 'dl_purchase' && event.ecommerce?.value > 1000) { + sendToPremiumPlatform(event); + } + + // Send all events to GA4 + sendToGA4(event); + + // Filter test users + if (event.user_properties?.customer_email?.includes('@test.com')) { + return; // Don't send to paid platforms + } + + sendToFacebookPixel(event); +}; +``` + +### Event Enrichment + +Add additional data before sending: + +```javascript +const processEvent = (event) => { + // Enrich with custom data + const enrichedEvent = { + ...event, + app_version: window.APP_VERSION, + environment: window.ENV, + user_segment: getUserSegment(), + ab_test_variant: getActiveVariant() + }; + + // Send enriched event + sendToPlatform(enrichedEvent); +}; +``` + +## Installation + +1. **Create transformer script** + + Save your transformer as a `.js` file (e.g., `transformer-tiktok.js`) + +2. **Add to page after SDK** + + ```html + + + + + + ``` + +3. **Load platform script** + + Ensure the target platform's script loads before the transformer: + + ```html + + + + + + + + + ``` + +## Best Practices + +### 1. Deduplicate Events + +Always track processed events to prevent duplicates: + +```javascript +const processedEvents = new Set(); + +const processEvent = (event) => { + const eventId = `${event.event}_${event._metadata?.sequence_number}`; + + if (processedEvents.has(eventId)) { + return; // Skip duplicate + } + + processedEvents.add(eventId); + // Process event... +}; +``` + +### 2. Clean Up Memory + +Prevent memory leaks by periodically cleaning old data: + +```javascript +setInterval(() => { + if (processedEvents.size > 1000) { + const entriesToKeep = Array.from(processedEvents).slice(-500); + processedEvents.clear(); + entriesToKeep.forEach(entry => processedEvents.add(entry)); + } +}, 60000); // Every minute +``` + +### 3. Handle Missing Data + +Gracefully handle missing or malformed data: + +```javascript +const processEvent = (event) => { + if (!event || !event.event) { + console.warn('[Transformer] Invalid event', event); + return; + } + + const value = event.ecommerce?.value ?? 0; + const items = event.ecommerce?.items || []; + + // Use defaults... +}; +``` + +### 4. Debug Logging + +Add conditional debug logging: + +```javascript +const DEBUG = window.location.hostname === 'localhost' || + window.location.search.includes('debug=true'); + +const processEvent = (event) => { + // Process event... + + if (DEBUG) { + console.log(`[Transformer] Converted ${event.event}`, platformEvent); + } +}; +``` + +### 5. Error Handling + +Wrap platform calls in try-catch: + +```javascript +const processEvent = (event) => { + try { + const platformEvent = buildEvent(event); + window.platform.track(platformEvent); + } catch (error) { + console.error('[Transformer] Error processing event:', error, event); + // Don't throw - let other code continue + } +}; +``` + +## Debugging + +### Check Transformer Status + +```javascript +// Check if NextDataLayer exists +console.log(window.NextDataLayer); + +// Check if transformer modified push +console.log(window.NextDataLayer.push.toString()); + +// View all events +console.log(window.NextDataLayer); +``` + +### Enable Debug Mode + +Add `?debug=true` to your URL to see console logs: + +``` +https://yoursite.com?debug=true +``` + +### Verify Platform Script Loaded + +```javascript +// TikTok +console.log(typeof window.ttq); // Should be 'object' + +// Snapchat +console.log(typeof window.snaptr); // Should be 'function' + +// Pinterest +console.log(typeof window.pintrk); // Should be 'function' + +// GA4 +console.log(typeof window.gtag); // Should be 'function' +``` + +## Full Example: TikTok Transformer + +Complete working example for TikTok Pixel: + +```javascript +(function() { + 'use strict'; + + const processedEvents = new Set(); + const DEBUG = window.location.search.includes('debug=true'); + + const EVENT_MAP = { + 'dl_view_item': 'ViewContent', + 'dl_add_to_cart': 'AddToCart', + 'dl_begin_checkout': 'InitiateCheckout', + 'dl_purchase': 'CompletePayment' + }; + + const initTransformer = () => { + if (!window.NextDataLayer) { + setTimeout(initTransformer, 100); + return; + } + + if (DEBUG) console.log('[TikTok Transformer] Initializing...'); + + const originalPush = window.NextDataLayer.push; + + window.NextDataLayer.push = function(...args) { + const result = originalPush.apply(window.NextDataLayer, args); + + args.forEach(item => { + if (item && typeof item === 'object' && item.event) { + processEvent(item); + } + }); + + return result; + }; + + window.NextDataLayer.forEach(item => { + if (item && typeof item === 'object' && item.event) { + processEvent(item); + } + }); + + if (DEBUG) console.log('[TikTok Transformer] Initialized'); + }; + + const processEvent = (event) => { + if (!EVENT_MAP[event.event]) return; + + const eventId = `${event.event}_${event._metadata?.sequence_number || Date.now()}`; + if (processedEvents.has(eventId)) return; + processedEvents.add(eventId); + + const tiktokEventName = EVENT_MAP[event.event]; + const tiktokData = { + content_type: 'product' + }; + + if (event.ecommerce) { + tiktokData.currency = event.ecommerce.currency || 'USD'; + tiktokData.value = event.ecommerce.value || 0; + + if (event.ecommerce.items?.length > 0) { + tiktokData.content_id = event.ecommerce.items[0].item_id; + tiktokData.content_name = event.ecommerce.items[0].item_name; + + if (event.event === 'dl_purchase') { + tiktokData.content_ids = event.ecommerce.items.map(i => i.item_id); + tiktokData.contents = event.ecommerce.items.map(i => ({ + content_id: i.item_id, + content_name: i.item_name, + quantity: i.quantity, + price: i.price + })); + } + } + } + + try { + if (window.ttq) { + window.ttq.track(tiktokEventName, tiktokData); + if (DEBUG) { + console.log(`[TikTok] ${event.event} → ${tiktokEventName}`, tiktokData); + } + } + } catch (error) { + console.error('[TikTok Transformer] Error:', error); + } + }; + + setInterval(() => { + if (processedEvents.size > 1000) { + const entriesToKeep = Array.from(processedEvents).slice(-500); + processedEvents.clear(); + entriesToKeep.forEach(entry => processedEvents.add(entry)); + } + }, 60000); + + initTransformer(); +})(); +``` + +## Download Example Scripts + +Get ready-to-use transformer scripts: +- **[Analytics Overview](/docs/campaign-cart/analytics/)** - Analytics setup and configuration +- **TikTok Transformer** - Copy from example above +- **Snapchat Transformer** - Copy from example above +- **Pinterest Transformer** - Copy from example above + +## Related Documentation + +- [Analytics Overview](/docs/campaign-cart/analytics/) - Main analytics documentation +- [Custom Events](/docs/campaign-cart/analytics/custom-events/) - Creating custom events +- [Examples](/docs/campaign-cart/analytics/examples/) - Built-in provider integrations +- [Configuration](/docs/campaign-cart/analytics/configuration/) - SDK configuration options + diff --git a/docs/campaign-cart/analytics/examples/facebook-pixel.md b/docs/campaign-cart/analytics/examples/facebook-pixel.md new file mode 100644 index 00000000..7d732729 --- /dev/null +++ b/docs/campaign-cart/analytics/examples/facebook-pixel.md @@ -0,0 +1,170 @@ +--- +title: "Facebook Pixel Setup" +description: "Integrate Facebook Pixel with automatic event mapping and purchase deduplication." +--- + + +## Setup + +1. **Add Facebook Pixel Base Code** + + ```html + + + ``` + +2. **Configure SDK** + + ```javascript + window.nextConfig = { + storeName: 'my-store', // IMPORTANT for deduplication! + analytics: { + providers: { + facebook: { + enabled: true, + settings: { + pixelId: 'YOUR_PIXEL_ID' + }, + blockedEvents: [] // Optional + } + } + } + }; + ``` + + + +:::tip +Make sure to replace `YOUR_PIXEL_ID` with your actual Facebook Pixel ID from your Meta Business Manager account. +::: + +## Event Mapping + +SDK events are automatically mapped to Facebook standard events: + +| SDK Event | Facebook Event | Type | +|-----------|----------------|------| +| `dl_view_item` | `ViewContent` | Standard | +| `dl_add_to_cart` | `AddToCart` | Standard | +| `dl_begin_checkout` | `InitiateCheckout` | Standard | +| `dl_add_shipping_info` | `AddShippingInfo` | Custom | +| `dl_add_payment_info` | `AddPaymentInfo` | Standard | +| `dl_purchase` | `Purchase` | Standard | +| `dl_sign_up` | `CompleteRegistration` | Standard | + +This mapping tracks store events in Facebook Analytics without additional configuration. + +## Purchase Deduplication + +The SDK uses `eventID` to prevent duplicate purchase tracking: + +```javascript +// SDK automatically generates eventID +fbq('track', 'Purchase', { + value: 159.99, + currency: 'USD' +}, { + eventID: 'my-store-12345' // Format: {storeName}-{orderNumber} +}); +``` + +**Why storeName is required:** +- Creates unique eventIDs across different stores +- Prevents Facebook from counting the same purchase twice +- Required for server-side API deduplication +- Without it, deduplication may fail + +:::caution +The `storeName` configuration is required for Facebook Pixel purchase deduplication. Without it, Facebook may count the same purchase multiple times across your sales channels (web, mobile, server-side events). Set `storeName` to a unique identifier for your store before deploying to production. +::: + +## Event Format Example + +Facebook Pixel events are formatted with these properties: + +```javascript +fbq('track', 'AddToCart', { + content_type: 'product', + content_ids: ['SKU-123'], + content_name: 'Product Name', + value: 99.99, + currency: 'USD', + contents: [{ + id: 'SKU-123', + quantity: 1, + item_price: 99.99 + }] +}); +``` + +### Event Property Details + +- **content_type**: Type of content (e.g., 'product', 'product_group') +- **content_ids**: Array of product SKUs or IDs +- **content_name**: Name of the product or content +- **value**: Total value of the event +- **currency**: Currency code (e.g., 'USD', 'EUR') +- **contents**: Array of items with quantity and pricing details + +## Blocked Events + +Prevent specific events from being sent to Facebook Pixel using the `blockedEvents` configuration: + +```javascript +facebook: { + enabled: true, + settings: { pixelId: 'xxx' }, + blockedEvents: ['dl_test_event', 'internal_event'] +} +``` + +This is useful for: +- Preventing test events from affecting analytics +- Blocking internal tracking events not relevant to Facebook +- Reducing event volume and optimizing tracking + +## Troubleshooting + +### Events Not Appearing in Facebook Analytics + +1. **Verify Pixel ID**: Ensure your `pixelId` is correct in the configuration +2. **Check storeName**: Confirm that `storeName` is set in your config - missing this can cause events to be blocked or deduplicated unexpectedly +3. **Test with Facebook Pixel Helper**: Use the [Facebook Pixel Helper Chrome extension](https://chrome.google.com/webstore/detail/facebook-pixel-helper/fdgodlnavgvnoonakpplpknkeae6764d) to verify events are firing +4. **Review Browser Console**: Check for JavaScript errors that might prevent events from sending + +### Purchase Deduplication Not Working + +- **Ensure storeName is configured**: This is the primary reason deduplication fails +- **Verify eventID format**: Should be `{storeName}-{orderNumber}` (e.g., `my-store-12345`) +- **Check server-side events**: If you're also sending events server-side, ensure consistent storeName and eventID +- **Allow time for deduplication**: Facebook's deduplication can take up to 24 hours to process + +### Low Purchase Conversion Events + +- **Verify currency code**: Ensure the currency matches your Facebook Pixel settings +- **Check content details**: Make sure `contents` array includes all required item information +- **Review blocked events**: Ensure `dl_purchase` is not in your `blockedEvents` list +- **Validate event values**: Confirm that transaction values are being passed correctly + +### Multiple Events Firing for Same Action + +1. Check that you're not initializing the Facebook Pixel multiple times +2. Verify that duplicate SDK instances aren't running +3. Review your `blockedEvents` configuration to filter unwanted duplicates + +### Contact Support + +If you continue experiencing issues: +- Check your Meta Business Manager logs for event validation errors +- Consult the [Facebook Pixel documentation](https://developers.facebook.com/docs/facebook-pixel) +- Reach out to your analytics support team with event payloads for investigation diff --git a/docs/campaign-cart/analytics/examples/google-tag-manager.md b/docs/campaign-cart/analytics/examples/google-tag-manager.md new file mode 100644 index 00000000..0d42d460 --- /dev/null +++ b/docs/campaign-cart/analytics/examples/google-tag-manager.md @@ -0,0 +1,325 @@ +--- +title: "Google Tag Manager Setup" +description: "Complete guide to integrating Google Tag Manager with Campaign Cart SDK analytics." +--- + + +## Overview + +Google Tag Manager (GTM) is a tag management system that manages marketing tags without modifying code directly. Campaign Cart SDK integrates with GTM by pushing analytics events to the standard data layer. + +## Setup + +1. **Add GTM Container** + + Add the GTM container snippet to your page **before** the Campaign Cart SDK: + + ```html + + + + ``` + + Replace `GTM-XXXXXX` with your actual GTM container ID. + +2. **Enable GTM in SDK Config** + + ```javascript + analytics: { + providers: { + gtm: { + enabled: true, + blockedEvents: ['dl_test_event'] // Optional + } + } + } + ``` + +3. **Create GTM Triggers** + + In Google Tag Manager: + - Trigger Type: **Custom Event** + - Event Name: `dl_*` (matches all events) or specific events like `dl_add_to_cart` + + + +## Where Events Are Pushed + +Events are automatically pushed to **two data layers**: + +1. **window.dataLayer** - Standard GTM data layer +2. **window.ElevarDataLayer** - Elevar-compatible format + +This dual-layer approach provides compatibility with both standard GTM implementations and ecommerce tracking platforms. + +## Event Format + +All events follow this standardized format in the data layer: + +```javascript +{ + event: 'dl_add_to_cart', + ecommerce: { + currency: 'USD', + value: 99.99, + items: [...] + }, + user_properties: { + visitor_type: 'guest', + customer_email: 'user@example.com' + }, + // Attribution fields at root level for easy GTM access + utm_source: 'google', + utm_medium: 'cpc', + funnel: 'main', + affiliate: 'partner-123' +} +``` + +### Common Events + +The SDK pushes the following event types: + +- `dl_view_item` - User views a product +- `dl_add_to_cart` - Item added to shopping cart +- `dl_remove_from_cart` - Item removed from cart +- `dl_view_cart` - User views the cart +- `dl_begin_checkout` - Checkout process initiated +- `dl_add_shipping_info` - Shipping information provided +- `dl_add_payment_info` - Payment information provided +- `dl_purchase` - Order completed + +## GTM Variables + +Create these variables in GTM for easy access to event data: + +| Variable Name | Type | Value | +|--------------|------|-------| +| DL - Event | Data Layer Variable | `event` | +| DL - Ecommerce | Data Layer Variable | `ecommerce` | +| DL - User Properties | Data Layer Variable | `user_properties` | +| DL - UTM Source | Data Layer Variable | `utm_source` | +| DL - UTM Medium | Data Layer Variable | `utm_medium` | +| DL - Funnel | Data Layer Variable | `funnel` | +| DL - Currency | Data Layer Variable | `ecommerce.currency` | +| DL - Value | Data Layer Variable | `ecommerce.value` | +| DL - Items | Data Layer Variable | `ecommerce.items` | + +### Creating Variables in GTM + +1. Open your GTM container +2. Navigate to **Variables** → **User-Defined Variables** +3. Click **New** for each variable +4. Select **Data Layer Variable** as the type +5. Enter the variable name and path +6. Save and publish your changes + +## GA4 Tag Setup + +To send Campaign Cart analytics events to Google Analytics 4: + +1. Create GA4 Event Tag +2. Event Name: `{{DL - Event}}` +3. Event Parameters: + - `currency`: `{{ecommerce.currency}}` + - `value`: `{{ecommerce.value}}` + - `items`: `{{ecommerce.items}}` +4. Trigger: Custom Event `dl_*` + +### Complete GA4 Configuration Example + +``` +Tag Type: Google Analytics: GA4 Event +Measurement ID: G-XXXXXXXXXX +Event Name: {{DL - Event}} +Event Parameters: + - currency: {{ecommerce.currency}} + - value: {{ecommerce.value}} + - items: {{ecommerce.items}} + - user_id: {{user_properties.user_id}} +Trigger: Custom Event matching dl_* +``` + +## Blocked Events + +Block specific events from being sent to GTM to reduce noise and focus on important conversions: + +```javascript +gtm: { + enabled: true, + blockedEvents: [ + 'dl_test_event', + 'internal_debug', + 'dev_event' + ] +} +``` + +### Why Block Events? + +- Prevent low-priority events from cluttering your GTM logs +- Reduce GTM traffic (GTM charges based on page views) +- Focus on events that matter for your business +- Block test or debug events from production + +## Troubleshooting + +### Events Not Appearing in GTM + +**Problem**: You've configured GTM but aren't seeing events in the preview/debug mode. + +**Solutions**: +1. Verify the GTM container snippet is loaded **before** the Campaign Cart SDK +2. Check that `analytics.providers.gtm.enabled` is set to `true` +3. Verify the GTM container ID is correct (GTM-XXXXXX) +4. Open GTM Preview mode and reload the page to see real-time events +5. Check browser console for any JavaScript errors + +### Events Not Triggering GA4 Tags + +**Problem**: Events appear in GTM but aren't reaching Google Analytics 4. + +**Solutions**: +1. Verify your GA4 Event Tag is created and enabled +2. Check the trigger configuration - ensure it matches `dl_*` or the specific event name +3. Verify the Measurement ID in your GA4 tag is correct +4. Check GA4 real-time report to confirm events are arriving +5. Ensure Event Parameters are correctly mapped to data layer variables + +### Incorrect Event Data + +**Problem**: Events are reaching GTM but with missing or incorrect data. + +**Solutions**: +1. Verify data layer variables point to the correct data layer paths +2. Use GTM's Preview mode to inspect the raw event payload +3. Check the `ecommerce` object structure matches GA4 requirements +4. Verify `user_properties` are being populated correctly +5. Ensure custom dimensions are correctly named and configured + +### Performance Issues + +**Problem**: Site performance degrades after implementing GTM. + +**Solutions**: +1. Load the GTM container asynchronously (it should load async by default) +2. Review the number of tags firing on each page +3. Consider delaying non-critical tag execution +4. Use GTM's tag sequencing to optimize load order +5. Monitor tag firing frequency and optimize trigger conditions + +## Best Practices + +### 1. Use Consistent Event Names + +Keep event names consistent: + +```javascript +// Good +'dl_add_to_cart' +'dl_purchase' +'dl_begin_checkout' + +// Avoid +'dl_event1' +'dl_tracking' +'dl_data' +``` + +### 2. Structure Data Consistently + +The ecommerce object should follow the GA4 schema: + +```javascript +ecommerce: { + currency: 'USD', + value: 99.99, + items: [ + { + item_id: 'SKU123', + item_name: 'Product Name', + price: 99.99, + quantity: 1 + } + ] +} +``` + +### 3. Test Before Deploying + +1. Use GTM's Preview mode during development +2. Test each event type on staging +3. Verify data flows to GA4 before production +4. Monitor real-time reports after deploying + +### 4. Document Your Implementation + +Maintain documentation of: +- Which events you're tracking +- What GTM variables you created +- Which tags use which triggers +- Any custom event modifications + +### 5. Monitor and Iterate + +- Regularly review GTM reports for data quality +- Check GA4 for anomalies or missing events +- Adjust blockedEvents list based on actual needs +- Update documentation as your implementation evolves + +### 6. Use Version Control for GTM + +- Export your GTM container configuration +- Store it in your project repository +- Document significant changes +- Maintain a changelog of GTM updates + +### 7. Use Attribution Tracking + +Use the root-level attribution fields: + +```javascript +{ + event: 'dl_purchase', + utm_source: 'google', + utm_medium: 'cpc', + utm_campaign: 'summer_sale', + funnel: 'main', + affiliate: 'partner-123' +} +``` + +Create GTM variables for each attribution field to enable advanced segmentation and attribution analysis. + +### 8. Handle Blocked Events Wisely + +Only block events that: +- Are used for internal testing +- Generate excessive noise +- Aren't needed for business analysis +- Can be filtered elsewhere + +Document which events are blocked and why. + +## Migration Guide + +If migrating from another analytics setup to GTM with Campaign Cart SDK: + +1. **Inventory existing events** - Document all events you're currently tracking +2. **Map to Campaign Cart events** - Match existing events to SDK event types +3. **Set up GTM container** - Follow the Setup section above +4. **Test in parallel** - Run both old and new tracking simultaneously +5. **Validate data quality** - Compare reports between old and new setup +6. **Monitor initial period** - Watch for discrepancies in first 2-4 weeks +7. **Phase out old tracking** - Gradually disable legacy tracking when confident + +## Additional Resources + +- [Google Tag Manager Documentation](https://support.google.com/tagmanager) +- [GA4 Event Schema](https://support.google.com/analytics/answer/11137820) +- [Campaign Cart Analytics Documentation](/docs/campaign-cart/analytics/) +- [Event Reference](/docs/campaign-cart/analytics/events/) diff --git a/docs/campaign-cart/analytics/examples/index.md b/docs/campaign-cart/analytics/examples/index.md new file mode 100644 index 00000000..2f37041a --- /dev/null +++ b/docs/campaign-cart/analytics/examples/index.md @@ -0,0 +1,112 @@ +--- +sidebar_label: Examples +sidebar_position: 14 +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Analytics Providers Overview + +Configure multiple analytics providers to send events simultaneously to GTM, Facebook Pixel, RudderStack, and custom platforms. + +Configure analytics providers to send events to multiple platforms. The SDK supports Google Tag Manager, Direct GA4 integration, Facebook Pixel, RudderStack, and custom webhooks. + +## Provider Configuration + +Enable multiple providers in your configuration: + +```javascript +window.nextConfig = { + apiKey: 'your-api-key', + storeName: 'my-store', // Important for Facebook deduplication + analytics: { + enabled: true, + mode: 'auto', + providers: { + gtm: { enabled: true }, + facebook: { enabled: true, settings: { pixelId: 'xxx' } }, + rudderstack: { enabled: true }, + custom: { enabled: true, settings: { endpoint: 'https://...' } } + } + } +}; +``` + +## Providers Comparison + +| Provider | Best For | Setup Complexity | Auto-Deduplication | +|----------|----------|------------------|--------------------| +| **Google Tag Manager** | Event tracking & conversion measurement | Low | Yes (via GTM) | +| **Direct GA4 (No GTM)** | Simple GA4 tracking without GTM | Very Low | Yes (automatic) | +| **Facebook Pixel** | Conversion tracking & audience building | Medium | Yes (with storeName) | +| **RudderStack** | Data warehouse integration & CDP | High | Yes (via RudderStack) | +| **Custom Webhook** | Third-party integrations & APIs | Medium | Manual | + +## Provider Independence + +Each provider operates independently. If one provider fails or is temporarily unavailable: + +- Other providers continue to receive and process events normally +- Failed requests do not block the event pipeline +- Your analytics data is safely distributed across multiple platforms +- No single point of failure exists + +## Using Multiple Providers + +You can enable any combination of providers simultaneously: + +### All Providers +Enable all providers for data collection across multiple platforms: + +```javascript +analytics: { + providers: { + gtm: { enabled: true }, + facebook: { enabled: true, settings: { pixelId: 'YOUR_PIXEL_ID' } }, + rudderstack: { enabled: true }, + custom: { enabled: true, settings: { endpoint: 'https://your-api.com/events' } } + } +} +``` + +### Selective Providers +Choose only the providers you need: + +```javascript +analytics: { + providers: { + gtm: { enabled: true }, + facebook: { enabled: true, settings: { pixelId: 'YOUR_PIXEL_ID' } }, + rudderstack: { enabled: false }, + custom: { enabled: false } + } +} +``` + +:::tip +Enable multiple providers to send analytics data to all required platforms. If one service fails, others continue operating. +::: + +## Provider Setup Guides + +### Built-in Providers + +- **[Google Tag Manager](/docs/campaign-cart/analytics/examples/google-tag-manager/)** - Container setup and variable configuration +- **[Facebook Pixel](/docs/campaign-cart/analytics/examples/facebook-pixel/)** - Conversion tracking and audience building +- **[RudderStack](/docs/campaign-cart/analytics/examples/rudderstack/)** - Data warehouse and CDP integration +- **[Custom Webhook](/docs/campaign-cart/analytics/examples/custom-webhook/)** - Third-party API integration + +### Alternative Integrations + +- **[Direct GA4 (No GTM)](/docs/campaign-cart/analytics/examples/direct-ga4/)** - Send events directly to Google Analytics 4 without GTM +- **[Event Transformers](/docs/campaign-cart/analytics/examples/event-transformers/)** - Build custom transformers for any platform (TikTok, Snapchat, Pinterest, etc.) +- **[Custom Analytics Triggers](/docs/campaign-cart/analytics/examples/custom-analytics-triggers/)** - Modify when `add_to_cart` and `begin_checkout` events fire + +## Next Steps + +1. Choose which providers you need +2. Follow the provider-specific setup guide +3. Test event delivery in your development environment +4. Deploy to production + diff --git a/docs/campaign-cart/analytics/examples/rudderstack.md b/docs/campaign-cart/analytics/examples/rudderstack.md new file mode 100644 index 00000000..379c7a16 --- /dev/null +++ b/docs/campaign-cart/analytics/examples/rudderstack.md @@ -0,0 +1,223 @@ +--- +title: RudderStack Setup +description: Integrate RudderStack with Segment-compatible event tracking. +--- + + +RudderStack is a Segment-compatible analytics platform that tracks customer events and collects product data. This integration maps SDK events to RudderStack's Segment specification. + +## Setup + +1. **Add RudderStack Snippet** + + Add the RudderStack tracking snippet to your HTML `` section. You'll need to replace `YOUR_WRITE_KEY` and `YOUR_DATA_PLANE_URL` with your actual RudderStack credentials: + + ```html + + + ``` + + **Getting Your Credentials:** + - Log in to your RudderStack dashboard + - Navigate to Workspace Settings → Data Plane URLs + - Copy your Write Key and Data Plane URL + - Replace the placeholders in the snippet above + +2. **Enable in SDK Config** + + Configure the RudderStack provider in your analytics configuration: + + ```javascript + analytics: { + providers: { + rudderstack: { + enabled: true, + blockedEvents: [] // Optional: array of event names to exclude + } + } + } + ``` + + **Configuration Options:** + - `enabled` (boolean): Enable or disable RudderStack tracking + - `blockedEvents` (array): List of SDK event names to exclude from RudderStack (useful for filtering sensitive events) + + + +## Event Mapping + +RudderStack events follow the Segment specification. The SDK automatically maps your e-commerce events to their RudderStack equivalents: + +| SDK Event | RudderStack Event | Description | +|-----------|-------------------|-------------| +| `dl_add_to_cart` | `Product Added` | Fired when a product is added to the shopping cart | +| `dl_remove_from_cart` | `Product Removed` | Fired when a product is removed from the shopping cart | +| `dl_view_item` | `Product Viewed` | Fired when a product detail page is viewed | +| `dl_view_item_list` | `Product List Viewed` | Fired when a product list or category page is viewed | +| `dl_view_cart` | `Cart Viewed` | Fired when the shopping cart is viewed | +| `dl_begin_checkout` | `Checkout Started` | Fired when the checkout process is initiated | +| `dl_add_shipping_info` | `Checkout Step Completed` | Fired when shipping information is entered | +| `dl_add_payment_info` | `Payment Info Entered` | Fired when payment information is provided | +| `dl_purchase` | `Order Completed` | Fired when an order is successfully completed | + +## Product Format + +Products are automatically converted to the Segment specification format. Each product in your events will be transformed to include these standardized properties: + +```javascript +{ + product_id: 'SKU-123', + sku: 'SKU-123', + name: 'Product Name', + price: 99.99, + quantity: 1, + category: 'Category', + brand: 'Brand', + variant: 'Variant', + position: 0, + url: 'https://example.com/product/sku-123' +} +``` + +**Field Descriptions:** +- `product_id`: Unique identifier for the product (same as SKU) +- `sku`: Stock Keeping Unit +- `name`: Product display name +- `price`: Product price in decimal format +- `quantity`: Number of units +- `category`: Product category +- `brand`: Product brand +- `variant`: Product variant or size +- `position`: Position in product list (0-indexed) +- `url`: Product page URL + +## Features + +### Identify Calls + +The SDK sends `identify()` calls with user data when user information is available. This includes: +- User profile tracking +- Cross-device identification +- Demographic data collection +- User trait enrichment + +### Page Calls + +The SDK triggers `page()` calls on route changes to: +- Track page views and navigation patterns +- Segment user journeys +- Analyze user flow through your application + +### Additional Capabilities + +- Product metadata included in tracking calls +- Campaign source and attribution tracking +- Session tracking across user interactions +- Revenue and conversion metrics + +## Configuration Options + +### Basic Configuration + +```javascript +analytics: { + providers: { + rudderstack: { + enabled: true + } + } +} +``` + +### Advanced Configuration + +```javascript +analytics: { + providers: { + rudderstack: { + enabled: true, + blockedEvents: [ + 'dl_custom_internal_event', + 'dl_debug_event' + ] + } + } +} +``` + +### Environment-Based Configuration + +```javascript +analytics: { + providers: { + rudderstack: { + enabled: process.env.NODE_ENV === 'production', + blockedEvents: process.env.NODE_ENV === 'development' ? ['dl_debug_event'] : [] + } + } +} +``` + +## Troubleshooting + +### Events Not Appearing in RudderStack + +**Check Your Setup:** +1. Verify the RudderStack snippet is loaded in your `` section +2. Ensure your Write Key and Data Plane URL are correct +3. Check browser console for JavaScript errors (open Developer Tools) +4. Confirm RudderStack is enabled in your SDK configuration + +**Debug with Browser Console:** +```javascript +// Check if RudderStack is loaded +console.log(window.rudderanalytics); + +// Manually trigger a test event +rudderanalytics.track('Test Event', { test: true }); +``` + +### Write Key or Data Plane URL Issues + +**Symptoms:** Events fail silently or "401 Unauthorized" errors appear + +**Solution:** +1. Verify credentials in your RudderStack dashboard +2. Ensure your Data Plane URL is complete (include protocol, e.g., `https://`) +3. Check that your workspace is active +4. Generate a new Write Key if the current one seems compromised + +### Blocked Events Not Working + +**Symptoms:** Events you want to exclude still appear in RudderStack + +**Solution:** +1. Verify the event name exactly matches your blockedEvents array +2. Check that the configuration is properly loaded before events fire +3. Event names are case-sensitive: `dl_add_to_cart` ≠ `dl_AddToCart` + +### Product Data Missing + +**Symptoms:** Product events reach RudderStack but lack properties + +**Solution:** +1. Ensure your product objects include required fields: `product_id`, `name`, `price` +2. Check that product data is properly formatted in your event payloads +3. Verify no custom product field mapping is conflicting + +### Performance Issues + +**Optimization Tips:** +1. Use `blockedEvents` to exclude high-frequency internal events +2. Load the RudderStack script asynchronously +3. Consider batching events if sending large volumes +4. Review RudderStack's destination list and disable unused integrations + +### Contact Support + +For additional help: +- Check the [RudderStack Documentation](https://www.rudderstack.com/docs/) +- Review [Segment Event Specification](https://segment.com/docs/protocols/tracking-plan/spec/) +- Contact your RudderStack support team diff --git a/docs/campaign-cart/analytics/index.md b/docs/campaign-cart/analytics/index.md new file mode 100644 index 00000000..f469ddf6 --- /dev/null +++ b/docs/campaign-cart/analytics/index.md @@ -0,0 +1,250 @@ +--- +sidebar_label: Analytics +sidebar_position: 6 +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Analytics Overview + +Track e-commerce events across Google Analytics 4, Facebook Pixel, RudderStack, and custom platforms. + +The SDK tracks e-commerce events and sends them to analytics providers. + +## Supported Providers + +- Google Tag Manager +- Facebook Pixel +- RudderStack +- Custom webhooks + +Events follow GA4 specification and can be tracked automatically or manually using two APIs: `next.*` methods or `window.NextAnalytics`. + +## How It Works + +### 1. Events Are Tracked + +The SDK captures e-commerce events using two APIs: + + + + +```javascript +// Simple tracking methods +next.trackAddToCart(packageId, quantity); +next.trackViewItem(packageId); +next.trackBeginCheckout(); +next.trackPurchase(orderData); +``` + +Works immediately, handles async loading automatically. + + + + +```javascript +// Advanced tracking with full control +window.NextAnalytics.trackAddToCart(item, listId, listName); +window.NextAnalytics.trackViewItem(item); +window.NextAnalytics.track({ event: 'custom_event', data: '...' }); +``` + +Direct access to analytics engine with advanced features. + + + + +### 2. Events Are Stored + +All events are stored in three data layers: + +- **`window.NextDataLayer`** - SDK's primary analytics store +- **`window.dataLayer`** - Standard Google Tag Manager layer +- **`window.ElevarDataLayer`** - Elevar-compatible format + +### 3. Events Are Sent + +Events are automatically sent to all enabled providers: + +```javascript +providers: { + gtm: { enabled: true }, // → Google Tag Manager + facebook: { enabled: true, settings: {} }, // → Facebook Pixel + rudderstack: { enabled: true }, // → RudderStack + custom: { enabled: true, settings: {} } // → Your backend +} +``` + +Each provider operates independently - if one fails, others continue working. + +## Event Data Structure + +All events follow GA4-compliant format: + +```javascript +{ + event: 'dl_add_to_cart', + event_id: 'sess_123_2_1234567890', + event_time: '2025-01-12T10:30:00Z', + + user_properties: { + visitor_type: 'guest', + customer_email: 'user@example.com', + customer_id: 'user_123' + }, + + ecommerce: { + currency: 'USD', + value: 99.99, + items: [{ + item_id: 'SKU-123', + item_name: 'Product Name', + price: 99.99, + quantity: 1 + }] + }, + + attribution: { + utm_source: 'google', + utm_medium: 'cpc', + funnel: 'main' + }, + + _metadata: { + session_id: 'sess_abc123', + sequence_number: 2, + source: 'next-campaign-cart', + version: '0.2.0' + } +} +``` + +## Testing & Verification + +### Enable Debug Mode + + + + +```javascript +analytics: { + debug: true +} +``` + + + + +```javascript +// Enable at runtime +window.NextAnalytics.setDebugMode(true); + +// Check status +const status = window.NextAnalytics.getStatus(); +console.log(status); +``` + + + + +### Disable Tracking Temporarily + +``` +https://yoursite.com?ignore=true +``` + +This disables ALL tracking for the entire session. + +To clear: +```javascript +window.NextAnalyticsClearIgnore(); +``` + +### Verify Events + +```javascript +// Check all data layers +console.log(window.NextDataLayer); +console.log(window.dataLayer); // GTM +console.log(window.ElevarDataLayer); // Elevar + +// Get analytics status +const status = window.NextAnalytics.getStatus(); +console.log('Events tracked:', status.eventsTracked); +console.log('Providers:', status.providers); +``` + +## Next Steps + +1. **Configure providers** - Set up GTM, Facebook Pixel, or other platforms + + See [Examples Overview](/docs/campaign-cart/analytics/examples/) + +2. **Learn tracking methods** - Understand the tracking API + + See [Tracking API Reference](/docs/campaign-cart/analytics/tracking-api/) + +3. **Review events** - See all standard e-commerce events + + See [Event Reference](/docs/campaign-cart/analytics/events/) + +4. **Advanced tracking** - Create custom events and use transform functions + + See [Custom Events](/docs/campaign-cart/analytics/custom-events/) + +## Documentation Structure + +- **[Meta Tags](/docs/campaign-cart/analytics/meta-tags/)** - Declarative analytics configuration via HTML meta tags +- **[Configuration & Modes](/docs/campaign-cart/analytics/configuration/)** - Detailed configuration options +- **[Tracking API Reference](/docs/campaign-cart/analytics/tracking-api/)** - Complete API documentation +- **[Examples](/docs/campaign-cart/analytics/examples/)** - Provider-specific setup guides +- **[Event Reference](/docs/campaign-cart/analytics/events/)** - Complete event schemas and examples +- **[Custom Events](/docs/campaign-cart/analytics/custom-events/)** - Advanced tracking patterns +- **[Debugging](/docs/campaign-cart/analytics/debugging/)** - Troubleshooting and testing +- **[Best Practices](/docs/campaign-cart/analytics/best-practices/)** - Implementation patterns and tips + +## FAQ + +### Do I need to call next.init()? + +No! Analytics initializes automatically when the SDK loads. Just configure `window.nextConfig`. + +### What's the difference between the three data layers? + +- `window.dataLayer` - Standard GTM data layer +- `window.NextDataLayer` - SDK's primary analytics store (all events) +- `window.ElevarDataLayer` - Elevar-compatible format for GTM integrations + +### Can I track events before SDK loads? + +Yes, use the nextReady queue: + +```javascript +window.nextReady = window.nextReady || []; +window.nextReady.push(function() { + next.trackAddToCart('123', 1); +}); +``` + +### What happens if a provider fails? + +The SDK handles failures gracefully: +- Events still track to NextDataLayer +- Other providers continue working +- Warnings logged in debug mode +- SDK functionality unaffected + +### How do I check if analytics is working? + +```javascript +// Check status +window.NextAnalytics.getStatus(); + +// Check events +window.NextDataLayer; + +// Enable debug +window.NextAnalytics.setDebugMode(true); +``` + diff --git a/docs/campaign-cart/analytics/meta-tags.md b/docs/campaign-cart/analytics/meta-tags.md new file mode 100644 index 00000000..e7194204 --- /dev/null +++ b/docs/campaign-cart/analytics/meta-tags.md @@ -0,0 +1,236 @@ +--- +title: Analytics Meta Tags +description: Control analytics events declaratively through HTML meta tags - no JavaScript required. +sidebar_position: 4 +--- + +Control analytics events declaratively through HTML meta tags - no JavaScript required. + +--- + +## Quick Reference + +| Meta Tag | Purpose | +|----------|---------| +| `next-analytics-view-item` | Fire `dl_view_item` for a package (replaces auto-detection) | +| `next-analytics-view-item-list` | Fire `dl_view_item_list` for packages (replaces auto-detection) | +| `next-analytics-list-id` | Set page-level list ID for all events | +| `next-analytics-list-name` | Set page-level list name for all events | +| `next-analytics-scroll-tracking` | Track scroll depth at specified thresholds | +| `next-analytics-disable` | Block specific events globally (all providers) | +| `next-analytics-enable-only` | Whitelist mode - only fire these events | + +--- + +## `dl_view_item` Examples + +### Basic - Fire immediately on page load +```html + +``` +Replace `123` with an actual package `ref_id` from your campaign. + +### With Time Delay (engagement signal) +```html + +``` +The event fires 3 seconds (3000ms) after the page loads. + +### With Scroll Trigger +```html + +``` +The event fires when the element `#product-details` scrolls into view (50% visible). + +### From URL Parameter +```html + +``` +Reads the package ID from the URL query string: `?pid=123` + +**Trigger Format**: `type:value` +- `time:3000` = wait 3000ms (3 seconds) after page load +- `view:#selector` = fire when CSS selector scrolls into view (50% threshold) + +--- + +## `dl_view_item_list` Examples + +### Multiple packages (comma-separated) +```html + +``` +Fires `dl_view_item_list` with all specified packages. + +### From URL Parameter +```html + +``` +Reads comma-separated IDs from URL: `?products=123,456,789` + +--- + +## List Context (Attribution) + +Set page-level list context that will be included in all add-to-cart events: + +```html + + +``` + +This ensures proper attribution tracking when users add items to cart. + +--- + +## Scroll Depth Tracking + +Track when users scroll to specific percentages of the page: + +```html + +``` + +Fires `dl_scroll_depth` events at 25%, 50%, 75%, and 90% scroll depth. Each threshold fires only once. + +--- + +## Event Blocking + +### Disable Specific Events (Global Block) +```html + +``` +Blocks these events from firing for ALL providers. + +### Whitelist Mode (Only Allow These Events) +```html + +``` +Only these events will fire - everything else is blocked globally. + +--- + +## Complete Examples + +### Product Detail Page +```html + + + + + + + + + + + +``` + +### Landing Page with URL Parameters +```html + + + + + + + + +``` + +### Upsell Page (Block Unwanted Events) +```html + + + + + + + + +``` + +--- + +## How It Works + +### Priority: Meta Tags Override Auto-Detection + +When a meta tag like `` is present: +- It **REPLACES** auto-detected `dl_view_item` entirely +- **No need to manually block it first** in the config +- The meta tag always takes priority over auto-detection + +### Event Control Hierarchy + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ LEVEL 1: Provider-Specific blockedEvents (Most Granular) │ +│ Blocks event for THAT PROVIDER ONLY │ +│ gtm: { blockedEvents: ['dl_view_item_list'] } → GTM won't get it │ +└─────────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────────┐ +│ LEVEL 2: Meta Tag disable/enable-only (Global Block) │ +│ Blocks event for ALL PROVIDERS │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────────┐ +│ LEVEL 3: Meta Tag view-item/view-item-list (Auto-Event Override) │ +│ REPLACES auto-detection entirely (not additive) │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Debugging + +After the page loads, you can check the status in the browser console: + +```javascript +// Check MetaTagController status +NextMetaTagController.getStatus() + +// Check if events were fired +NextMetaTagController.wasViewItemFired() +NextMetaTagController.wasViewItemListFired() + +// Check the parsed config +NextMetaTagController.getStatus().config + +// Check disabled events +NextMetaTagController.getStatus().config.disabledEvents + +// Check list context +NextMetaTagController.getListContext() +``` + +--- + +## Events Reference + +| Event Name | Description | +|------------|-------------| +| `dl_view_item` | Single product view | +| `dl_view_item_list` | Multiple products viewed (list/collection) | +| `dl_add_to_cart` | Item added to cart | +| `dl_remove_from_cart` | Item removed from cart | +| `dl_begin_checkout` | Checkout started | +| `dl_purchase` | Order completed | +| `dl_scroll_depth` | User scrolled to threshold | + +--- + +## Browser Support + +- Chrome 90+ +- Firefox 88+ +- Safari 14+ +- Edge 90+ + +No polyfills required - uses standard DOM APIs. + diff --git a/docs/campaign-cart/analytics/tracking-api.md b/docs/campaign-cart/analytics/tracking-api.md new file mode 100644 index 00000000..b802becd --- /dev/null +++ b/docs/campaign-cart/analytics/tracking-api.md @@ -0,0 +1,679 @@ +--- +title: Tracking API Reference +description: Complete reference for tracking e-commerce events using the simple next.* API or advanced window.NextAnalytics methods. +sidebar_position: 11 +--- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + +The SDK provides two complementary APIs for tracking analytics events. Choose the simple API for quick implementations or the advanced API for full control. + +## API Overview + + + + +**Best for:** Quick implementations, minimal code + +```javascript +// Simple, clean method calls +next.trackAddToCart(packageId, quantity); +next.trackViewItem(packageId); +next.trackBeginCheckout(); +next.trackPurchase(orderData); +``` + +**Features:** +- Handles async loading automatically +- Simple parameter signatures +- Works before SDK fully loads (queued) +- Ideal for most use cases + + + + +**Best for:** Advanced tracking, custom events, full control + +```javascript +// Direct access to analytics engine +window.NextAnalytics.trackAddToCart(item, listId, listName); +window.NextAnalytics.track({ event: 'custom', data: '...' }); +window.NextAnalytics.setDebugMode(true); +``` + +**Features:** +- Full item data objects +- List attribution support +- Custom event tracking +- Debug and status methods +- Direct analytics engine access + + + + +--- + +## E-commerce Events + +### Add to Cart + +Track when items are added to the cart. + + + + +```javascript +// Track with package ID and quantity +next.trackAddToCart('123', 1); + +// With quantity +next.trackAddToCart('456', 2); +``` + +**Parameters:** +- `packageId` (string|number) - Product/package identifier +- `quantity` (number, optional) - Quantity added (default: 1) + +**Auto-tracked when:** +- Using `data-next-action="add-to-cart"` attributes +- Calling cart methods like `next.addToCart()` + + + + +```javascript +// With full item data +window.NextAnalytics.trackAddToCart({ + id: 'pkg-123', + packageId: '123', + title: 'Premium Package', + price: 99.99, + quantity: 1 +}); + +// With list attribution +window.NextAnalytics.trackAddToCart({ + id: 'pkg-123', + packageId: '123', + title: 'Premium Package', + price: 99.99, + quantity: 1 +}, 'summer-collection', 'Summer Sale 2025'); +``` + +**Parameters:** +- `item` (object) - Full item data + - `id` (string) - Unique item identifier + - `packageId` (string|number) - Package ID + - `title` (string) - Product name + - `price` (number) - Unit price + - `quantity` (number) - Quantity +- `listId` (string, optional) - Product list ID for attribution +- `listName` (string, optional) - Product list name for attribution + + + + +### Remove from Cart + +Track when items are removed from the cart. + + + + +```javascript +// Track removal +next.trackRemoveFromCart('123', 1); +``` + +**Parameters:** +- `packageId` (string|number) - Product identifier +- `quantity` (number, optional) - Quantity removed + + + + +```javascript +// Using EcommerceEvents helper +import { EcommerceEvents } from '@/utils/analytics'; +import { dataLayer } from '@/utils/analytics'; + +const event = EcommerceEvents.createRemoveFromCartEvent(item); +dataLayer.push(event); +``` + +**Auto-tracked when:** +- Using cart removal methods + + + + +### View Item + +Track product detail page views. + + + + +```javascript +// Track product view +next.trackViewItem('123'); +``` + +**Parameters:** +- `packageId` (string|number) - Product identifier + +**Auto-tracked when:** +- Page has exactly 1 product with `data-next-package-id` +- Selected items in `data-next-selection-mode="select"` selectors +- Selected items in `data-next-selection-mode="swap"` selectors + + + + +```javascript +// With full product data +window.NextAnalytics.trackViewItem({ + id: 'pkg-123', + packageId: '123', + title: 'Premium Package', + price: 99.99 +}); +``` + +**Parameters:** +- `item` (object) - Product data + - `id` (string) - Unique identifier + - `packageId` (string|number) - Package ID + - `title` (string) - Product name + - `price` (number) - Price + + + + +### View Item List + +Track product list/collection views. + + + + +```javascript +// Track list view +next.trackViewItemList( + ['123', '456', '789'], + 'summer-sale', + 'Summer Sale Collection' +); +``` + +**Parameters:** +- `packageIds` (array) - Array of product IDs +- `listId` (string) - List identifier +- `listName` (string) - List display name + +**Auto-tracked when:** +- URL matches collection/category patterns +- Page type detected as product list + + + + +```javascript +window.NextAnalytics.trackViewItemList(items, listId, listName); +``` + +**Parameters:** +- `items` (array) - Array of item objects +- `listId` (string) - List identifier +- `listName` (string) - List display name + + + + +### Begin Checkout + +Track when checkout process starts. + + + + +```javascript +// Track checkout start +next.trackBeginCheckout(); +``` + +**No parameters required** - automatically includes cart items + +**Auto-tracked when:** +- Checkout page loads (in auto mode) + + + + +```javascript +window.NextAnalytics.trackBeginCheckout(); +``` + +Automatically includes: +- All current cart items +- Cart total value +- Currency +- User properties + + + + +### Purchase + +Track completed orders. + + + + +```javascript +// Minimal purchase tracking +next.trackPurchase({ + id: 'ORDER_123', + total: 159.99, + currency: 'USD', + tax: 9.99, + shipping: 10.00 +}); + +// With items +next.trackPurchase({ + id: 'ORDER_123', + total: 159.99, + currency: 'USD', + items: [ + { + id: 'SKU-123', + name: 'Product Name', + price: 149.99, + quantity: 1 + } + ] +}); +``` + +**Parameters:** +- `orderData` (object) + - `id` (string) - **Required** - Order ID + - `total` (number) - **Required** - Order total + - `currency` (string) - Currency code (default: 'USD') + - `tax` (number, optional) - Tax amount + - `shipping` (number, optional) - Shipping cost + - `items` (array, optional) - Order line items + + + + +```javascript +// Full order data from backend +window.NextAnalytics.trackPurchase({ + order: { + ref_id: 'ORD-12345', + number: '12345', + total_incl_tax: '159.99', + total_tax: '9.99', + shipping_incl_tax: '10.00', + currency: 'USD', + user: { + email: 'user@example.com', + first_name: 'John', + last_name: 'Doe' + }, + lines: [ + { + product_sku: 'SKU-123', + product_title: 'Product Name', + price_incl_tax: '149.99', + quantity: 1 + } + ], + billing_address: { + first_name: 'John', + last_name: 'Doe', + city: 'San Francisco', + state: 'CA', + postcode: '94102', + country: 'US' + } + } +}); +``` + +**Parameters:** +- `orderData` (object) + - `order` (object) - Complete order object from backend + - Automatically transforms to GA4 format + - Includes user properties, items, transaction details + + + + +**Auto-tracked:** Yes (queued and fired on confirmation page) + +**Event Queue:** Purchase events are queued to `sessionStorage` when order completes, then automatically fired on the confirmation page after redirect. + +**Manual tracking (optional):** + +You can manually trigger purchase events if needed (e.g., for testing or special integrations): + +```javascript +// Manually track with order data from sessionStorage['next-order'] +const orderData = JSON.parse(sessionStorage.getItem('next-order'))?.state?.order; +if (orderData) { + next.trackPurchase({ order: orderData }); +} + +// Or provide custom order data +next.trackPurchase({ + id: 'ORDER_123', + total: 159.99, + currency: 'USD' +}); +``` + +--- + +## User Events + +### Sign Up + +Track user registration. + + + + +```javascript +// Track registration +next.trackSignUp('user@example.com'); +``` + +**Parameters:** +- `email` (string) - User email address + + + + +```javascript +window.NextAnalytics.trackSignUp('user@example.com'); +``` + +Automatically includes: +- User email +- Timestamp +- Session data +- Attribution + + + + +### Login + +Track user login. + + + + +```javascript +// Track login +next.trackLogin('user@example.com'); +``` + +**Parameters:** +- `email` (string) - User email address + + + + +```javascript +window.NextAnalytics.trackLogin('user@example.com'); +``` + + + + +--- + +## Custom Events + +Track custom business events. + +```javascript +// Only available via advanced API +window.NextAnalytics.track({ + event: 'newsletter_subscribe', + email: 'user@example.com', + list_name: 'Weekly Newsletter', + source: 'footer_form' +}); + +// Video engagement +window.NextAnalytics.track({ + event: 'video_played', + video_id: 'intro-demo', + video_title: 'Product Introduction', + duration: 120 +}); + +// Feature usage +window.NextAnalytics.track({ + event: 'feature_used', + feature_name: 'product_comparison', + items_compared: 3 +}); +``` + +**Parameters:** +- `eventData` (object) - Custom event object + - `event` (string) - **Required** - Event name (use snake_case) + - Any additional properties as needed + +Custom events are sent to **all enabled providers**. + +See [Custom Events Guide](/docs/campaign-cart/analytics/custom-events/) for advanced patterns and EventBuilder usage. + +--- + +## Utility Methods + +### setDebugMode + +Enable or disable debug logging. + +```javascript +// Enable debug mode +window.NextAnalytics.setDebugMode(true); + +// Disable +window.NextAnalytics.setDebugMode(false); +``` + +**Parameters:** +- `enabled` (boolean) - Enable/disable debug logs + +**Debug output includes:** +- Event names and data +- Provider dispatch confirmations +- Validation warnings +- Error messages + +### getStatus + +Get current analytics status and configuration. + +```javascript +const status = window.NextAnalytics.getStatus(); +console.log(status); +``` + +**Returns:** +```javascript +{ + enabled: true, + mode: 'auto', + providers: ['GTM', 'Facebook', 'RudderStack', 'Custom'], + eventsTracked: 15, + debugMode: false, + sessionId: 'sess_abc123', + version: '0.2.0' +} +``` + +--- + +## Method Reference Tables + +### Simple API (next.*) + +| Method | Parameters | Description | Auto-Tracked | +|--------|------------|-------------|--------------| +| `trackViewItem()` | packageId | Product detail view | Yes* | +| `trackAddToCart()` | packageId, quantity? | Add item to cart | Yes* | +| `trackRemoveFromCart()` | packageId, quantity? | Remove from cart | Yes* | +| `trackViewItemList()` | packageIds[], listId, listName | Product list view | Yes* | +| `trackBeginCheckout()` | - | Checkout initiation | Yes* | +| `trackPurchase()` | orderData | Order completion | Yes* | +| `trackSignUp()` | email | User registration | No | +| `trackLogin()` | email | User login | No | + +*Auto-tracked only in auto mode + +### Advanced API (window.NextAnalytics) + +| Method | Parameters | Description | +|--------|------------|-------------| +| `trackAddToCart()` | item, listId?, listName? | Add to cart with list attribution | +| `trackRemoveFromCart()` | item | Remove from cart | +| `trackViewItem()` | item | Product detail view | +| `trackViewItemList()` | items[], listId, listName | Product list view | +| `trackBeginCheckout()` | - | Checkout initiation | +| `trackPurchase()` | orderData | Order completion | +| `trackSignUp()` | email | User registration | +| `trackLogin()` | email | User login | +| `track()` | eventData | Custom event | +| `setDebugMode()` | enabled | Enable/disable debug logs | +| `getStatus()` | - | Get analytics status | + +--- + +## Usage Patterns + +### Track on Page Load + +```javascript +window.addEventListener('DOMContentLoaded', () => { + // Product page + if (productData) { + next.trackViewItem(productData.id); + } + + // Order confirmation + if (orderData) { + next.trackPurchase(orderData); + } +}); +``` + +### Track Before SDK Loads + +Use the nextReady queue: + +```javascript +window.nextReady = window.nextReady || []; +window.nextReady.push(function() { + next.trackAddToCart('123', 1); + next.trackBeginCheckout(); +}); +``` + +### Track from Event Handlers + +```javascript +// Button click +document.getElementById('subscribe-btn').addEventListener('click', () => { + window.NextAnalytics.track({ + event: 'newsletter_subscribe', + source: 'hero_section' + }); +}); + +// Form submission +form.addEventListener('submit', (e) => { + window.NextAnalytics.track({ + event: 'form_submitted', + form_id: 'contact' + }); +}); +``` + +### Track with List Attribution + +```javascript +// Set list attribution when viewing a collection +window.NextAnalytics.trackViewItemList( + items, + 'summer-sale-2025', + 'Summer Sale Collection' +); + +// Attribution automatically included when user adds to cart +next.trackAddToCart('123', 1); +// Event includes: item_list_id: 'summer-sale-2025', item_list_name: 'Summer Sale Collection' +``` + +--- + +## Error Handling + +The SDK handles analytics errors gracefully: + +```javascript +// Analytics errors never break your app +try { + next.trackPurchase(orderData); +} catch (error) { + // Error automatically logged in debug mode + // App continues functioning normally +} +``` + +**Error behavior:** +- Errors logged to console in debug mode +- Events still stored in NextDataLayer +- Failed provider doesn't affect others +- SDK functionality unaffected + +--- + +## TypeScript Support + +Type definitions available for TypeScript projects: + +```typescript +// Import types +import type { OrderData, ItemData } from 'next-campaign-cart'; + +// Typed parameters +const orderData: OrderData = { + id: 'ORDER_123', + total: 159.99, + currency: 'USD' +}; + +next.trackPurchase(orderData); +``` + +--- + +## Related Documentation + +- [Configuration & Modes](/docs/campaign-cart/analytics/configuration/) - Configure tracking modes and providers +- [Event Reference](/docs/campaign-cart/analytics/events/) - Complete event schemas and examples +- [Custom Events](/docs/campaign-cart/analytics/custom-events/) - Advanced tracking patterns with EventBuilder +- [Debugging Guide](/docs/campaign-cart/analytics/debugging/) - Troubleshooting and testing diff --git a/docs/campaign-cart/cart-system/index.md b/docs/campaign-cart/cart-system/index.md new file mode 100644 index 00000000..14dc7ad3 --- /dev/null +++ b/docs/campaign-cart/cart-system/index.md @@ -0,0 +1,452 @@ +--- +sidebar_label: Cart System +sidebar_position: 1 +--- + +# Cart System + +Learn how to manage shopping carts with Campaign Cart JS SDK using HTML attributes. + +## Cart Selectors + +Cart selectors allow users to choose products before adding them to cart. The SDK supports multiple selection patterns. + +### Swap Mode Selector + +In swap mode, clicking a card immediately replaces the current item in the cart. No button needed. + +```html +
+
+

Basic Package

+ $99 +
+
+

Premium Package

+ $199 +
+
+``` + +**Key Attributes:** +- `data-next-cart-selector`: Defines the selector container +- `data-next-selection-mode="swap"`: Auto-adds to cart on selection +- `data-next-selector-card`: Individual selectable cards +- `data-next-package-id`: Package ID to add +- `data-next-selected="true"`: Default selection + +### Select Mode with Button + +Select first, then click button to add. Button is disabled until selection is made. + +```html +
+
+

Package Option 1

+
+
+

Package Option 2

+
+
+ + +``` + +**Key Attributes:** +- `data-next-selection-mode="select"`: Requires button to add +- `data-next-selector-id`: Links selector to button +- `data-next-action="add-to-cart"`: Add to cart action + +### Displaying Selection Data + +Show data about the currently selected package: + +```html +
+ +
+ + +
+ Selected: None + Price: $0 +
+``` + +### CSS Classes + +Selectors automatically get CSS classes: +- `.next-selected` - Applied to selected card +- `.next-selector-active` - Applied when selector has selection + +### Multiple Selectors + +You can have multiple selectors on the same page with different IDs: + +```html + +
+ +
+ + +
+ +
+``` + +### Conditional Display + +Show/hide elements based on selection: + +```html +
+ You've selected a package! +
+``` + +## Cart Buttons + +Various button types for managing cart items. + +### Direct Add to Cart Buttons + +Buttons that add specific packages directly without selection. + +**Basic Add to Cart:** + +```html + +``` + +**Add and Redirect:** + +```html + +``` + +**Clear Cart, Add, and Redirect:** + +```html + +``` + +### Cart Toggle Buttons + +Toggle items in/out of cart with dynamic states. + +**Basic Toggle:** + +```html + +``` + +**Toggle with Quantity:** + +```html + +``` + +**Dynamic Text Toggle:** + +```html + +``` + +### State Container Pattern + +Container element gets state classes when item is in cart: + +```html +
+ + +
+``` + +### Quantity Sync Feature + +Perfect for warranties and accessories that should match main product quantity. + +```html + + +``` + +### Button Attributes + +**Add to Cart Attributes:** +- `data-next-action="add-to-cart"` - Action type +- `data-next-package-id` - Package to add +- `data-next-quantity` - Quantity to add +- `data-next-url` - Redirect after add +- `data-next-clear-cart` - Clear cart before adding + +**Toggle Attributes:** +- `data-next-toggle` - Enable toggle functionality +- `data-next-package-id` - Package to toggle +- `data-next-quantity` - Quantity when toggled on +- `data-add-text` - Text when item not in cart +- `data-remove-text` - Text when item in cart +- `data-next-is-upsell` - Mark as upsell item +- `data-next-package-sync` - Sync quantity with other packages + +**CSS Classes:** +- `.next-in-cart` - Item is in cart +- `.next-active` - Toggle is active +- `.next-disabled` - Button is disabled + +## Quantity Controls + +Manage product quantities in the cart with various control patterns. + +### Direct Quantity in Buttons + +Set quantity directly in add to cart buttons: + +```html + + +``` + +### Toggle with Quantity + +Toggle buttons can specify quantity: + +```html + +``` + +### Container-Based Quantity + +Specify quantity at the container level: + +```html +
+ +
+``` + +### Quantity Sync Feature + +Sync quantities between related products (e.g., warranties that match product quantity): + +```html + + + + + + + +``` + +### Quantity Display + +Show current quantity in cart: + +```html + +0 + + +0 +``` + +### Quantity-Based Conditionals + +Show/hide elements based on quantity: + +```html + +
+ You qualify for bulk discount! +
+ + +
Single item in cart
+
Multiple items in cart
+``` + +### Best Practices + +1. **Clear Labels**: Always indicate quantity in button text +2. **Visual Feedback**: Update button text when quantity changes +3. **Sync Related Items**: Use quantity sync for accessories/warranties +4. **Display Current Quantity**: Show users current cart quantities + +## State Management + +The Next Commerce JS SDK automatically manages cart state and synchronizes it across all elements. + +### Automatic State Sync + +All elements displaying cart data automatically update when the cart changes: + +```html + +$0.00 +0 +
Cart has items!
+``` + +### CSS State Classes + +Elements automatically receive CSS classes based on state: + +**Toggle Button States:** + +```html + +``` + +**Container States:** + +```html +
+ + + + Product Info +
+``` + +**Selector States:** + +```html +
+ + Package Option +
+``` + +### State-Based Styling + +Use CSS to style based on state: + +```css +/* Style selected cards */ +.next-selected { + border: 2px solid blue; + background: #f0f0ff; +} + +/* Style items in cart */ +.next-in-cart { + opacity: 0.6; +} + +/* Style active toggle buttons */ +button.next-active { + background: green; + color: white; +} +``` + +### Conditional Display + +Show/hide elements based on cart state: + +```html + +
Your cart is empty
+
+ You have 0 items +
+ + +
Package 2 is in your cart
+ + +
Free shipping unlocked!
+``` + +### Events + +Listen to state changes: + +```javascript +// Cart updated +next.on('cart:updated', (data) => { + console.log('Cart updated:', data); +}); + +// Item added +next.on('cart:item-added', (data) => { + console.log('Item added:', data); +}); + +// Item removed +next.on('cart:item-removed', (data) => { + console.log('Item removed:', data); +}); +``` + +### Best Practices + +1. **Use CSS Classes**: Style based on state classes for better UX +2. **Conditional Content**: Show relevant messages based on cart state +3. **Listen to Events**: Trigger analytics or custom logic on state changes +4. **Loading States**: Use data-next-await for initial loading + +## Related Documentation + +- [Cart Attributes](/docs/campaign-cart/data-attributes/display/#cart-summary) - Display cart data +- [Upsells](/docs/campaign-cart/upsells/) - Post-purchase flows diff --git a/docs/campaign-cart/data-attributes/actions.md b/docs/campaign-cart/data-attributes/actions.md new file mode 100644 index 00000000..af133822 --- /dev/null +++ b/docs/campaign-cart/data-attributes/actions.md @@ -0,0 +1,750 @@ +--- +title: Action Attributes +description: Turn any HTML element into an interactive cart control +sidebar_position: 2 +--- + +**Action attributes transform static HTML elements into powerful e-commerce controls without writing JavaScript.** + +## Core Principles + +- **Any Element Can Be Interactive:** Not just buttons - images, divs, links all work +- **Automatic State Management:** Loading, disabled, and success states handled for you +- **Error Recovery Built-in:** Network failures and conflicts handled gracefully +- **Composable Actions:** Combine multiple actions on a single element + +## Primary Actions + +### Add to Cart + +The most fundamental e-commerce action. Any element can become an add-to-cart control. + +```html + +``` + +**Advanced Options:** + +```html + +``` + +| Attribute | Purpose | Default | +|-----------|---------|---------| +| `data-next-package-id` | Package to add | Required | +| `data-next-quantity` | Quantity to add | 1 | +| `data-next-clear-cart` | Clear cart first | false | +| `data-next-url` | Redirect after adding | none | +| `data-add-text` | Text when not in cart | Element's text | +| `data-remove-text` | Text when in cart | Element's text | +| `data-loading-text` | Text while processing | "Loading..." | + +### Toggle Cart Item + +Toggle between adding and removing an item - perfect for wishlist-style interfaces. + +```html +
+ Product + +
+``` + +The SDK automatically: +- Adds `next-in-cart` class when item is in cart +- Toggles between add and remove operations +- Updates ARIA states for screen readers + +### Remove from Cart + +Explicitly remove an item from the cart. + +```html + +``` + +### Clear Cart + +Empty the entire cart - useful for "Start Over" functionality. + +```html + +``` + +### Swap Package + +Replace one package with another - perfect for variant selection. + +```html + +``` + +## Quantity Actions + +### Quantity Controls + +Create increment/decrement controls without JavaScript. + +```html +
+ + + + + +
+``` + +| Value | Behavior | +|-------|----------| +| `increase` | Increment by step (default 1) | +| `decrease` | Decrement by step (default 1) | +| `input` | Direct quantity input | +| `set` | Set to specific value | + +**With Custom Steps:** + +```html + +``` + +## Selector Actions + +### Package Selector + +Create product variant selectors that update the cart automatically. + +```html +
+ + + + + +
+``` + +**Button-Based Selector:** + +```html +
+ + + +
+``` + +## Checkout Actions + +### Direct Checkout + +Skip the cart page and go straight to checkout. + +```html + +``` + +### Express Checkout + +Trigger express checkout methods (Apple Pay, Google Pay, etc.). + +```html +
+ +
+ + + +``` + +## Upsell Actions + +### Accept Upsell + +For post-purchase upsell flows. + +```html + +``` + +### Skip Upsell + +Track upsell rejections for analytics. + +```html + +``` + +## Conditional Actions + +### Profile-Based Actions + +Show different actions based on customer profiles. + +```html + + + + +``` + +### State-Based Actions + +Actions that only work in certain states. + +```html + + + + + +``` + +## Action Composition + +### Multiple Actions + +Execute multiple actions in sequence. + +```html + +``` + +**Execution order:** +1. Clear existing cart +2. Add package 123 with quantity 1 +3. Redirect to /checkout + +### Conditional Chains + +```html + +``` + +## Loading States + +The SDK automatically manages loading states during actions. + +```html + + + + +``` + +**Custom Loading Behavior:** + +```html + +``` + +## Error Handling + +Actions automatically handle common errors. + +```html + + + +
+ Could not add item. Please try again. +
+``` + +**Custom Error Handling:** + +```javascript +// Listen for action errors +window.addEventListener('action:failed', (event) => { + console.error('Action failed:', event.detail); + // Custom error UI +}); +``` + +## Accessibility + +All actions are automatically accessible. + +```html + + + + +``` + +## Performance Considerations + +### Debouncing + +Actions are automatically debounced to prevent double-submissions. + +```html + + +``` + +### Optimistic Updates + +The UI updates immediately while the action processes. + +```html + +``` + +## Common Patterns + +### Product Card with Actions + +```html +
+ Product +

Premium Package

+

$99

+ +
+ + + +
+
+``` + +### Quick Add Form + +```html +
+ + + + + +
+``` + +## Specialized Attributes + +### Timer Controls + +Create countdown timers and expiration behaviors: + +```html + +
+ + 60:00 +
+ + +
+

Offer has expired!

+
+``` + +**Timer Attributes:** +- `data-next-timer` - Timer duration in seconds +- `data-persistence-id` - Unique ID for timer persistence +- `data-next-timer-display` - Element to show countdown +- `data-next-timer-expired` - Elements to show when expired + +### Checkout Form Fields + +Mark form fields for automatic checkout processing: + +```html +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+``` + +**Checkout Field Types:** +- `email` - Customer email address +- `fname`, `lname` - Customer name +- `phone` - Customer phone number +- `billing-address1`, `billing-city`, `billing-zip` - Billing address +- `cc-number`, `cc-month`, `cc-year`, `cvv` - Credit card details +- `accepts_marketing` - Marketing consent checkbox + +### Express Checkout + +Enable express checkout buttons (Apple Pay, Google Pay, etc.): + +```html + +
+ +
+ + +
+

Express Checkout

+
+ +
+
+ + + + + + + +``` + +**Express Checkout Values:** +- `auto` - Show all available methods +- `container` - Container for button placement +- `buttons` - Target element for button injection +- `apple-pay` - Apple Pay button +- `google-pay` - Google Pay button +- `paypal` - PayPal Express button + +### Remove Item Controls + +Remove specific items from cart: + +```html + + + + + + + + +``` + +**Remove Item Attributes:** +- `data-package-id` - Package ID to remove +- `data-confirm` - Show confirmation dialog +- `data-confirm-message` - Custom confirmation text +- `data-remove-all` - Remove all instances vs. single item + +### Checkout Review Fields + +Display checkout information for review: + +```html +
+ +
customer@example.com
+
John
+
Doe
+ + +
123 Main St
+
Anytown
+ + +
Credit Card
+
+``` + +## Advanced Action Patterns + +### Multi-Step Checkout + +```html + +
+ + + + +
+ + +
+ + + +
+ + +
+ + + +
+``` + +### Timer-Based Offers + +```html +
+

Flash Sale - 30:00 Left!

+ +
+ +
+ +
+

Sorry, this offer has expired.

+ +
+
+``` + +### Conditional Express Checkout + +```html + +
+

Quick Checkout Options

+
+
+ + +
+ +
+ +
+ +
+``` + +## Related Documentation + +- [Display Attributes](/docs/data-attributes/display/) - Show dynamic data +- [State Attributes](/docs/data-attributes/state/) - Conditional visibility +- [Events](/docs/campaign-cart/javascript-api/events/) - Respond to action events +- [JavaScript API](/docs/campaign-cart/javascript-api/methods/) - Programmatic cart control \ No newline at end of file diff --git a/docs/campaign-cart/data-attributes/campaign.md b/docs/campaign-cart/data-attributes/campaign.md new file mode 100644 index 00000000..f488ee7a --- /dev/null +++ b/docs/campaign-cart/data-attributes/campaign.md @@ -0,0 +1,73 @@ +--- +title: Campaign Attributes +description: Display campaign-level information like name, currency, and language +sidebar_position: 11 +--- + +# Campaign Attributes + +Display campaign-level information like name, currency, and language. + +## Available Attributes + +```html +{Campaign Name} +{Campaign Currency} +{Campaign Language} +``` + +## Examples + +### Basic Campaign Info + +```html +
+

Campaign Name

+

Currency: USD

+

Language: en

+
+``` + +### Currency Display + +```html + +$ + + +
+ $ + 99.99 +
+``` + +### Language-Based Content + +```html + +
+ Welcome to our store! +
+
+ ¡Bienvenido a nuestra tienda! +
+``` + +## Use Cases + +1. **Page Headers**: Display campaign name +2. **Currency Formatting**: Show appropriate currency +3. **Localization**: Conditional content by language +4. **Analytics**: Track campaign performance + +## Notes + +- Campaign data is loaded on SDK initialization +- Values are read-only +- Use for display purposes only + +## Related Documentation + +- [Display Attributes](/docs/campaign-cart/data-attributes/display/) - Display campaign data +- [State Attributes](/docs/campaign-cart/data-attributes/state/) - Conditional display based on campaign + diff --git a/docs/campaign-cart/data-attributes/checkout-review.md b/docs/campaign-cart/data-attributes/checkout-review.md new file mode 100644 index 00000000..80a2451d --- /dev/null +++ b/docs/campaign-cart/data-attributes/checkout-review.md @@ -0,0 +1,487 @@ +--- +title: Checkout Review +description: Display checkout information for customer review before order completion +sidebar_position: 8 +--- + +**The Checkout Review enhancer automatically displays stored checkout data from previous steps, allowing customers to review their information before completing their order.** + +The enhancer reads from the checkout store and supports multiple format types, auto-updates when data changes, and works with any container element. + +## Key Features + +- **Automatic data loading** - Reads from checkout store automatically +- **Multiple format types** - Text, address, name, phone, currency +- **Real-time updates** - Auto-updates when checkout data changes +- **Fallback support** - Custom text for empty fields +- **Flexible containers** - Works with any HTML element + +## Basic Usage + +### Container Setup + +Mark a container element with the checkout-review enhancer: + +```html +
+ +
+``` + +All checkout review fields within this container will be automatically populated with data from the checkout store. + +### Simple Text Fields + +Display individual checkout fields: + +```html +
+ +
+ Contact: + +
+ + +
+ Phone: + +
+
+``` + +### Formatted Fields + +Use format types to display formatted data: + +```html +
+ +
+ Name: + +
+ + +
+ Ship to: +
+
+
+``` + +## Attributes + +### Required Attributes + +| Attribute | Value | Description | +|-----------|-------|-------------| +| `data-next-enhancer` | `"checkout-review"` | Marks container element as checkout review enhancer | +| `data-next-checkout-review` | Field name | Specifies which checkout field to display | + +### Optional Attributes + +| Attribute | Values | Description | +|-----------|--------|-------------| +| `data-next-format` | `text`, `address`, `name`, `phone`, `currency` | Format type for the field value | +| `data-next-fallback` | Any text | Text to display when field is empty | + +## Available Fields + +All fields from the checkout store's form data: + +| Field | Description | Example Value | +|-------|-------------|---------------| +| `email` | Customer email | john@example.com | +| `fname` | First name | John | +| `lname` | Last name | Doe | +| `phone` | Phone number | 5551234567 | +| `address1` | Street address | 1949 East Camelback Road | +| `address2` | Apartment, suite, etc. | Suite 100 | +| `city` | City | Phoenix | +| `province` | State/Province | AZ | +| `postal` | ZIP/Postal code | 85016 | +| `country` | Country code | US | + +## Format Types + +### text (Default) + +Displays the raw field value as text: + +```html + + + + + + + +``` + +Use for: Email, individual name fields, individual address components + +### address + +Combines multiple address fields into a single formatted address: + +```html +
+ +``` + +**Includes:** +- address1 (required) +- address2 (if provided) +- city (required) +- province (required) +- postal (required) +- country (required - converts code to full name) + +**Example output formats:** +- With address2: `123 Main St, Apt 4B, Springfield IL 62701, United States` +- Without address2: `123 Main St, Springfield IL 62701, United States` + +**Notes:** +- Use `address1` as the field name +- Country codes automatically convert to full names (US → United States) +- address2 only included if it has a value + +### name + +Combines first and last name into a full name: + +```html + + +``` + +**Includes:** +- fname (first name) +- lname (last name) + +**Notes:** +- Use `fname` as the field name +- Automatically combines both names with a space + +### phone + +Formats phone numbers into standard display format: + +```html + + +``` + +**Input:** Raw digits (e.g., 5551234567) +**Output:** Formatted phone number (e.g., (555) 123-4567) + +### currency + +Formats numeric values as currency: + +```html + + +``` + +Use for: Prices, shipping costs, or any monetary value + +## Nested Fields + +Access nested checkout data using dot notation: + +```html + + + + + + +``` + +## Fallback Text + +Provide default text for empty or undefined fields: + +```html + +``` + +When the field is empty: +- Displays the fallback text +- Adds `next-review-empty` CSS class for styling + +## Auto-Updates + +The enhancer automatically subscribes to checkout store changes: + +```html +
+ + + +
+``` + +**How it works:** +1. Customer enters information on checkout step 1 +2. Customer proceeds to review page +3. Review fields automatically populate with stored data +4. If customer goes back and updates information +5. Review fields automatically update when they return + +No manual refresh or JavaScript required. + +## CSS Classes + +### next-review-empty + +Applied to elements when field is empty or using fallback text: + +```css +.next-review-empty { + color: #999; + font-style: italic; +} +``` + +Use this class to visually distinguish empty states from actual customer data. + +## Complete Example + +Shopify-style checkout review section: + +```html +
+

Review your information

+ +
+ +
+
+
Contact
+
+
+
+ Change +
+
+ + +
+
+
Ship to
+
+
+
+ Change +
+
+ + +
+
+
Phone
+
+
+
+ Change +
+
+
+
+``` + +### Example Styling + +```css +.checkout-review { + margin: 2rem 0; + padding: 1.5rem; + border: 1px solid #ddd; + border-radius: 8px; +} + +.review-table { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.review-row { + display: flex; + justify-content: space-between; + align-items: flex-start; + padding-bottom: 1rem; + border-bottom: 1px solid #eee; +} + +.review-row:last-child { + border-bottom: none; + padding-bottom: 0; +} + +.review-cell { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.review-label { + font-weight: 600; + color: #333; +} + +.review-value { + color: #666; +} + +.review-change { + color: #0066cc; + text-decoration: none; + font-size: 0.9em; +} + +.review-change:hover { + text-decoration: underline; +} + +/* Empty state styling */ +.next-review-empty { + color: #999; + font-style: italic; +} +``` + +## Advanced Usage + +### Manual Registration + +Register the enhancer programmatically: + +```javascript +const reviewContainer = document.querySelector('.checkout-review'); +const enhancer = await sdk.enhancers.register('checkout-review', reviewContainer); +``` + +### Mixed Format Types + +Combine different format types in one review section: + +```html +
+ +
Email:
+ + +
Name:
+ + +
Phone:
+ + +
Address:
+
+``` + +### Conditional Display with Fallbacks + +```html +
+ +
+ Email: + +
+ + +
+ Apartment: + +
+
+``` + +## Format Types Reference + +| Format | Field Used | Combines | Output Example | +|--------|-----------|----------|----------------| +| `text` | Any field | Single field | john@example.com | +| `address` | `address1` | address1, address2, city, province, postal, country | 123 Main St, Suite 1, Phoenix AZ 85016, United States | +| `name` | `fname` | fname + lname | John Doe | +| `phone` | `phone` | Single field (formatted) | (555) 123-4567 | +| `currency` | Any numeric | Single field (formatted) | $9.99 | + +## Common Patterns + +### Customer Information Summary + +```html +
+

Customer Information

+
+
Name:
+
-
+ +
Email:
+
-
+ +
Phone:
+
-
+
+
+``` + +### Address Review + +```html +
+

Shipping Address

+
+
+``` + +### Multi-Section Review + +```html +
+ +
+

Contact Information

+
+
+
+ + +
+

Shipping Address

+
+
+
+``` + +## Important Notes + +- The enhancer **must** be placed inside a container with `data-next-enhancer="checkout-review"` +- All review fields within the container will be **automatically populated** +- The enhancer **subscribes to checkout store updates** for real-time changes +- **Country codes are automatically converted** to full country names (US → United States) +- Fallback text triggers the `next-review-empty` CSS class for styling + +## Related Documentation + +- [Action Attributes](/data-attributes/actions/) - Checkout form actions +- [Display Attributes](/data-attributes/display/) - Display dynamic data +- [State Attributes](/data-attributes/state/) - Conditional display logic diff --git a/docs/campaign-cart/data-attributes/configuration.md b/docs/campaign-cart/data-attributes/configuration.md new file mode 100644 index 00000000..ac12c344 --- /dev/null +++ b/docs/campaign-cart/data-attributes/configuration.md @@ -0,0 +1,1038 @@ +--- +title: Configuration Attributes +description: Configure SDK behavior and appearance through HTML attributes +sidebar_position: 5 +--- + +**Configuration attributes control formatting, behavior, and appearance without writing JavaScript.** + +## Formatting Attributes + +Control how values are displayed and formatted. + +### data-format + +Specify how numeric values should be formatted. + +```html + + + $99.99 + + + + + 99.99 + + + + + 5 + + + + + 30% + + + + + $8.999 + +``` + +| Format | Description | Example Output | +|--------|-------------|----------------| +| `currency` | With currency symbol | $99.99 | +| `number` | Numeric only | 99.99 | +| `integer` | No decimals | 100 | +| `percentage` | Percentage format | 30% | +| `auto` | Auto-detect format | Varies | + +### data-hide-zero-cents + +Hide decimal places when value has no cents. + +```html + + + $100 + + + + + $99.99 + +``` + +### data-hide-if-zero + +Hide element completely when value is zero. + +```html + +
+ Discount: + $0.00 +
+ + +
+ Shipping: $0.00 +
+``` + +### data-hide-if-false + +Hide element when boolean value is false. + +```html +
+ Discount applied! +
+ +
+ In stock and ready to ship +
+``` + +## Mathematical Operations + +Perform calculations on displayed values. + +### data-multiply-by + +Multiply the value before displaying. + +```html + + + $9.99 + + + + + $1,188/year + + + + + 10% + +``` + +### data-divide-by + +Divide the value before displaying. + +```html + + + $19.99/item + + + + + 30% off + +``` + +### data-add + +Add a value to the displayed number. + +```html + + + $109.99 + +``` + +### data-subtract + +Subtract from the displayed value. + +```html + + + Save $20.00 + + + + + $25.00 away from free shipping + +``` + +## Timer Configuration + +Configure countdown timers and urgency elements. + +### Timer Attributes + +```html + +
+ 05:00 +
+ + +
+ 01:00:00 +
+ + +
+ 1d 00:00:00 +
+ + +
+ 10:00 +
+``` + +| Attribute | Purpose | Values | +|-----------|---------|--------| +| `data-duration` | Timer duration in seconds | Number | +| `data-format` | Display format | `mm:ss`, `hh:mm:ss`, `dd:hh:mm:ss`, `auto` | +| `data-persistence-id` | Persist across refreshes | String ID | +| `data-on-expire` | Action when timer ends | `hide`, `show-message`, `redirect` | +| `data-expire-message` | Message to show | String | +| `data-expire-url` | URL to redirect to | URL | + +## Selector Configuration + +Configure package selector behavior. + +### Selection Modes + +```html + +
+ + +
+ + +
+ + + +
+ + +
+ + + +
+``` + +| Mode | Description | Use Case | +|------|-------------|----------| +| `swap` | Immediate cart replacement | Single product selection | +| `select` | Selection then action | Complex product configuration | +| `multi` | Multiple selections | Add-ons and bundles | + +## Loading States + +Configure loading behavior during actions. + +```html + + + + + + + + +``` + +## Action Configuration + +Configure how actions behave. + +### Debouncing + +Prevent rapid repeated actions. + +```html + + + + + +``` + +### Optimistic Updates + +Update UI immediately while action processes. + +```html + + + + + +``` + +### Success/Error Handling + +```html + + + + + + + + +``` + +## Profile Configuration + +Configure profile-based behavior. + +```html + + + + + + + + +``` + +## Quantity Configuration + +Configure quantity controls and limits. + +```html + + + + + + + +
+ + 1 + +
+``` + +## Upsell Configuration + +Configure upsell behavior and display. + +```html + +
+

Special Offer!

+ + +
+ + +
+ + 1 + +
+ + +
+ + +
+``` + +## Meta Tag Configuration + +Page-level configuration via meta tags. + +```html + + + + + + + + + + + + + + + + + + + +``` + +## Common Configuration Patterns + +### Product Card with Full Configuration + +```html +
+ + + $99 + + + +
+ $129 + 23% off +
+ + + +
+``` + +### Configured Checkout Flow + +```html +
+ +
+ Add $ + 0 + more for checkout +
+ + + +
+``` + +## Utility Attributes + +General utility attributes for common scenarios. + +### Text Transformation + +```html + + + PRODUCT NAME + + + + + user@example.com + + + + + Product Category + + + + + Product description here. + +``` + +### String Manipulation + +```html + +

+ Long description text will be cut off at 150 characters... +

+ + + + John + + + + + Smith + + + + + J.S. + + + + + In Stock + +``` + +### Date and Time Utilities + +```html + + + 12/25/2023 + + + + + 2 hours ago + + + + + 14:30 + + + + + 01/01/2024 + + + + + 3d 12:45:30 + +``` + +### URL and Link Utilities + +```html + + + Awesome Product + + + + + Share on Facebook + + + + + user@example.com + + + + + (123) 456-7890 + +``` + +### CSS Class Utilities + +```html + +
+ Stock Level: 10 +
+ + + + + +
+ Cart content +
+ + +
+ +
+``` + +### Animation and Transition Utilities + +```html + + + $99.99 + + + +
+ Cart items +
+ + + + 5 + + + + +``` + +### Validation and Input Utilities + +```html + + + + + + + + + + + +``` + +### Performance Utilities + +```html + +Product image + + +
+ Critical content +
+ + + + + +
+
+``` + +### Accessibility Utilities + +```html + + + 5 items in cart + + + + + + +
+ 5 +
+ + + +``` + +### Storage Utilities + +```html + + + + + + + + +``` + +### Development Utilities + +```html + +
+ +
+ + +
+ Content to monitor +
+ + +
+ New checkout flow content +
+ + +
+ Variant A content +
+``` + +## Advanced Configuration Patterns + +### Multi-Step Form Configuration + +```html +
+ + +
+ + +
+ + +
+ + + +
+ + +
+ +
+
+``` + +### Dynamic Pricing Configuration + +```html +
+ +
+ $99.99 +
+ + +
+ $129.99 +
+ + +
+ Bulk discount applied +
+ + +
+ $156.78 +
+
+``` + +### Smart Recommendations + +```html +
+ + +
+``` + +## Related Documentation + +- [Display Attributes](/docs/data-attributes/display/) - Dynamic data display +- [Action Attributes](/docs/data-attributes/actions/) - Interactive elements +- [State Attributes](/docs/data-attributes/state/) - Conditional logic +- [Meta Tags](/docs/configuration/meta-tags/) - Page-level configuration diff --git a/docs/campaign-cart/data-attributes/css-classes.md b/docs/campaign-cart/data-attributes/css-classes.md new file mode 100644 index 00000000..dd155d76 --- /dev/null +++ b/docs/campaign-cart/data-attributes/css-classes.md @@ -0,0 +1,298 @@ +--- +title: CSS Classes +description: Automatic CSS classes applied by the SDK based on element state +sidebar_position: 12 +--- + +# CSS Classes + +The Next Commerce JS SDK automatically applies CSS classes to elements based on their state. + +## State Classes + +### Cart Item States + +#### .next-in-cart +Applied to elements when their package is in the cart: + +```html + + + + +
+ +
+``` + +#### .next-active +Applied to toggle buttons when active: + +```html + +``` + +### Selection States + +#### .next-selected +Applied to selected selector cards: + +```html +
+ Selected Option +
+``` + +#### .next-selector-active +Applied to selector container when it has a selection: + +```html +
+ +
+``` + +### Button States + +#### .next-disabled +Applied to disabled action buttons: + +```html + + +``` + +## Page States + +### .next-display-ready +Applied to `` when SDK is initialized: + +```html + + + +``` + +Usage: +```css +/* Hide content until ready */ +html:not(.next-display-ready) [data-next-display] { + visibility: hidden; +} +``` + +### .next-loading +Applied during data loading operations: + +```html + + + +``` + +## Loading State Classes + +Elements with `data-next-await` get special treatment: + +```css +/* Before SDK ready */ +html:not(.next-display-ready) [data-next-await] { + /* Shows loading skeleton */ +} + +/* After SDK ready */ +html.next-display-ready [data-next-await] { + /* Normal display */ +} +``` + +## Styling Examples + +### Toggle Button States + +```css +/* Default state */ +button[data-next-toggle] { + background: #007bff; + color: white; +} + +/* When item is in cart */ +button[data-next-toggle].next-in-cart { + background: #28a745; +} + +/* Active state */ +button[data-next-toggle].next-active { + background: #dc3545; +} + +/* Disabled state */ +button.next-disabled { + opacity: 0.5; + cursor: not-allowed; +} +``` + +### Product Card States + +```css +/* Product card container */ +.product-card { + border: 1px solid #ddd; + transition: all 0.3s ease; +} + +/* When product is in cart */ +.product-card.next-in-cart { + border-color: #28a745; + background: #f8fff9; +} + +/* Style children based on parent state */ +.product-card.next-in-cart .add-button { + display: none; +} + +.product-card.next-in-cart .remove-button { + display: block; +} +``` + +### Selector Card States + +```css +/* Selector cards */ +[data-next-selector-card] { + border: 2px solid #ddd; + cursor: pointer; + transition: all 0.2s ease; +} + +/* Hover state */ +[data-next-selector-card]:hover { + border-color: #007bff; +} + +/* Selected state */ +[data-next-selector-card].next-selected { + border-color: #007bff; + background: #e7f3ff; +} + +/* Selected indicator */ +[data-next-selector-card].next-selected::after { + content: '✓'; + position: absolute; + top: 10px; + right: 10px; + color: #007bff; +} +``` + +### Loading States + +```css +/* Loading skeleton animation */ +[data-next-await]::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); + background-size: 200% 100%; + animation: loading 1.5s infinite; +} + +@keyframes loading { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } +} + +/* Hide skeleton when ready */ +html.next-display-ready [data-next-await]::before { + display: none; +} +``` + +## Advanced Patterns + +### Multi-State Styling + +```css +/* Combine multiple states */ +.product-card.next-in-cart [data-next-toggle].next-active { + background: #dc3545; + color: white; +} + +/* Different styles for different pages */ +html[data-page-type="checkout"] .next-in-cart { + /* Checkout-specific styles */ +} +``` + +### Animation on State Change + +```css +/* Animate when item added to cart */ +@keyframes addedToCart { + 0% { transform: scale(1); } + 50% { transform: scale(1.1); } + 100% { transform: scale(1); } +} + +.product-card:not(.next-in-cart) { + animation: none; +} + +.product-card.next-in-cart { + animation: addedToCart 0.3s ease; +} +``` + +### Responsive State Styling + +```css +/* Mobile adjustments */ +@media (max-width: 768px) { + [data-next-selector-card].next-selected { + border-width: 3px; + } + + button[data-next-toggle].next-active { + font-size: 14px; + padding: 10px; + } +} +``` + +## Best Practices + +1. **Use CSS Classes**: Style based on SDK classes, not attributes +2. **Smooth Transitions**: Add transitions for state changes +3. **Clear Visual Feedback**: Make states obvious to users +4. **Accessibility**: Ensure state changes are perceivable +5. **Performance**: Use CSS transforms over layout changes + +## Class Reference Table + +| Class | Applied To | When | +|-------|------------|------| +| `.next-in-cart` | Elements with package ID | Package is in cart | +| `.next-active` | Toggle buttons | Toggle is active | +| `.next-selected` | Selector cards | Card is selected | +| `.next-selector-active` | Selector container | Has selection | +| `.next-disabled` | Action buttons | Action unavailable | +| `.next-display-ready` | `` | SDK initialized | +| `.next-loading` | `` | Loading data | \ No newline at end of file diff --git a/docs/campaign-cart/data-attributes/display.md b/docs/campaign-cart/data-attributes/display.md new file mode 100644 index 00000000..17d71788 --- /dev/null +++ b/docs/campaign-cart/data-attributes/display.md @@ -0,0 +1,744 @@ +--- +title: Display Attributes +description: Show dynamic data that updates automatically +sidebar_position: 3 +--- + +**Display attributes bind HTML elements to live data that updates automatically when state changes.** + +## data-next-display + +The primary attribute for showing dynamic values from cart, campaign, and order data. + +### Basic Usage + +```html + +$0.00 +$0.00 +$0.00 +$0.00 +$0.00 + + +0 +0 + + +$0.00 +Loading... +Loading... +``` + +### Display Paths + +| Path | Description | Example Output | +|------|-------------|----------------| +| `cart.total` | Total cart value | "$99.99" | +| `cart.subtotal` | Subtotal before shipping/tax | "$89.99" | +| `cart.shipping` | Shipping cost | "$10.00" | +| `cart.tax` | Tax amount | "$8.00" | +| `cart.discount` | Discount amount | "-$10.00" | +| `cart.totalQuantity` | Total items in cart | "5" | +| `cart.itemCount` | Unique items in cart | "3" | +| `cart.isEmpty` | Cart empty state | "true" or "false" | +| `package.[id].price` | Package price | "$29.99" | +| `package.[id].name` | Package display name | "Premium Bundle" | +| `package.[id].description` | Package description | "Includes everything..." | +| `package.[id].savings` | Discount amount | "$10.00" | +| `package.[id].quantity` | Quantity in cart | "2" | +| `campaign.name` | Campaign name | "Summer Sale" | +| `campaign.currency` | Active currency | "USD" | +| `order.total` | Order total | "$99.99" | +| `order.refId` | Order reference | "ORD-12345" | +| `profile.active` | Active profile | "premium" | + +### Formatting Options + +Use `data-format` to control how values are displayed: + +```html + + + $0.00 + + + + + 0.00 + + + + + 0 + + + + + 0% + + + + + $100 + +``` + +### Mathematical Operations + +```html + + + 0.99 + + + + + 30 + + + + + $20.00 + +``` + +## Conditional Display + +### data-next-show + +Show element when condition is true: + +```html + +
+ Your cart has items! +
+ + +
+ Premium package selected +
+ + +
+ Free shipping unlocked! +
+ + +
+ Bulk discount applied! +
+``` + +### data-next-hide + +Hide element when condition is true (inverse of show): + +```html + + + + + + + +
+ Checkout available +
+``` + +### data-hide-if-zero + +Hide element when displayed value is zero: + +```html + +
+ Discount: $0.00 +
+ + +
+ Shipping: $0.00 +
+``` + +### data-hide-if-false + +Hide element when value is false: + +```html +
+ Discount applied! +
+``` + +## Profile-Based Display + +Show/hide content based on active profile: + +```html + +
+ Premium member pricing +
+ + +
+ Upgrade to premium for better prices +
+ + +
+ Member exclusive content +
+``` + +## Timer Display + +### data-next-timer + +Display countdown timers: + +```html + +
+ 05:00 +
+ + +
+ 01:00:00 +
+ + +
+ 1d 00:00:00 +
+``` + +Timer format options: +- `mm:ss` - Minutes and seconds +- `hh:mm:ss` - Hours, minutes, seconds +- `dd:hh:mm:ss` - Days, hours, minutes, seconds +- `auto` - Automatic formatting based on duration + +## Complex Display Patterns + +### Cart Summary + +```html +
+ + 0 + + +
+ Your cart is empty +
+ + +
+
Items: 0
+
Subtotal: $0.00
+ + +
+ Shipping: $0.00 +
+ + +
+ Discount: $0.00 +
+ +
Total: $0.00
+
+
+``` + +### Product Card with Dynamic Pricing + +```html +
+ +
+ $0 +
+ + +
+ $0.00 + Save $0 +
+ + +
+ Out of Stock +
+ + +
+ 0 in cart +
+
+``` + +### Order Confirmation + +```html +
+

Order Confirmed!

+

Order ID: -

+

Total: $0.00

+ + +
+ +
+
+``` + +## Cart Items Template Variables + +The `data-next-cart-items` renderer uses template variables to display cart item data. All variables use the `{item.*}` syntax. + +### Basic Item Properties + +```html +
+
+

{item.name}

+

SKU: {item.sku}

+

Quantity: {item.quantity}

+ {item.name} +
+
+``` + +| Variable | Description | Example | +|----------|-------------|---------| +| `{item.id}` | Unique cart item ID | Auto-generated | +| `{item.packageId}` | Package ID | "123" | +| `{item.name}` | Product name | "Grounded Sheets" | +| `{item.title}` | Product title (alias) | "Grounded Sheets" | +| `{item.quantity}` | Quantity in cart | "2" | +| `{item.image}` | Product image URL | "https://..." | +| `{item.sku}` | Product SKU | "GRS-001" | +| `{item.frequency}` | Purchase frequency | "One time" or "Every month" | + +### Variant Properties + +Display product variant information: + +```html +
+
+

{item.productName}

+

{item.variantName}

+

{item.variantAttributesFormatted}

+ + + Color: {item.variantColor} + Size: {item.variantSize} +
+
+``` + +| Variable | Description | Example | +|----------|-------------|---------| +| `{item.productId}` | Base product ID | Auto-generated | +| `{item.productName}` | Base product name | "Grounded Sheets" | +| `{item.variantId}` | Variant ID | Auto-generated | +| `{item.variantName}` | Full variant name | "Grounded Sheets - Obsidian Grey / Twin" | +| `{item.variantSku}` | Variant SKU | "BE-GRS-TWN-GR" | +| `{item.variantAttributes}` | Raw array of variant attributes | For JSON output | +| `{item.variantAttributesFormatted}` | Formatted attributes | "Color: Obsidian Grey, Size: Twin" | +| `{item.variantAttributesList}` | HTML formatted attributes | `Color: Grey` | +| `{item.variantColor}` | Color value (dynamic) | "Obsidian Grey" | +| `{item.variantSize}` | Size value (dynamic) | "Twin" | +| `{item.variant.color}` | Color (dot notation) | "Obsidian Grey" | +| `{item.variant.size}` | Size (dot notation) | "Twin" | +| `{item.variant_color}` | Color (underscore) | "Obsidian Grey" | +| `{item.variant_size}` | Size (underscore) | "Twin" | + +Individual variant attributes are dynamically generated for all variant attribute codes. + +### Pricing Variables + +```html +
+
+

{item.price}

+

{item.unitPrice} each

+

Total: {item.lineTotal}

+ + + {item.comparePrice} +
+
+``` + +| Variable | Description | Type | +|----------|-------------|------| +| `{item.price}` | Package price | Currency | +| `{item.unitPrice}` | Price per unit | Currency | +| `{item.comparePrice}` | Original price before discount | Currency | +| `{item.unitComparePrice}` | Original unit price | Currency | +| `{item.lineTotal}` | Line total (price × quantity) | Currency | +| `{item.lineCompare}` | Original line total | Currency | +| `{item.recurringPrice}` | Subscription price | Currency | + +### Savings Variables + +```html +
+
+

+ Save {item.savingsAmount} ({item.savingsPct}%) +

+
+
+``` + +| Variable | Description | Type | +|----------|-------------|------| +| `{item.savingsAmount}` | Total savings | Currency | +| `{item.unitSavings}` | Per-unit savings | Currency | +| `{item.savingsPct}` | Savings percentage | Percentage | +| `{item.packageSavings}` | Package-level savings | Currency | +| `{item.packageSavingsPct}` | Package savings % | Percentage | + +### Package Details + +```html +
+
+

Package Price: {item.packagePrice}

+

Package Total: {item.packagePriceTotal}

+

Package Retail: {item.packageRetailPrice}

+

Units in Package: {item.packageQty}

+
+
+``` + +| Variable | Description | Type | +|----------|-------------|------| +| `{item.packagePrice}` | Base package price | Currency | +| `{item.packagePriceTotal}` | Package total price | Currency | +| `{item.packageRetailPrice}` | Package retail price | Currency | +| `{item.packageRetailTotal}` | Package retail total | Currency | +| `{item.packageQty}` | Quantity in package | Number | + +### Coupon Discount Variables + +```html +
+
+

Price: {item.price} {item.discountedPrice}

+

Coupon: -{item.discountAmount}

+

Final: {item.finalPrice}

+
+
+``` + +| Variable | Description | Type | +|----------|-------------|------| +| `{item.discountAmount}` | Coupon discount amount | Currency | +| `{item.discountedPrice}` | Price after coupon | Currency | +| `{item.discountedLineTotal}` | Line total after coupon | Currency | +| `{item.hasDiscount}` | Has coupon discount | "true" or "false" | +| `{item.finalPrice}` | Final display price | Currency | +| `{item.finalLineTotal}` | Final line total | Currency | + +### Conditional Display Classes + +Use these to show/hide elements conditionally: + +```html +
+
+ + {item.comparePrice} + Save {item.savingsAmount} + Coupon applied! + {item.frequency} +
+
+``` + +| Variable | Returns | Description | +|----------|---------|-------------| +| `{item.showCompare}` | "show" or "hide" | Show compare price if has savings | +| `{item.showSavings}` | "show" or "hide" | Show savings badge | +| `{item.showRecurring}` | "show" or "hide" | Show subscription info | +| `{item.showUnitPrice}` | "show" or "hide" | Show unit price for multi-packs | +| `{item.showDiscount}` | "show" or "hide" | Show coupon discount | +| `{item.showOriginalPrice}` | "show" or "hide" | Show strikethrough price | +| `{item.hasSavings}` | "true" or "false" | Boolean for conditionals | +| `{item.isRecurring}` | "true" or "false" | Is subscription product | + +### Cart Template Configuration + +Configure cart items renderer with these attributes: + +```html + + +
+
+ + +
+
+ + +
+
+ + +
+
+``` + +| Attribute | Description | Example | +|-----------|-------------|---------| +| `data-item-template-id` | Reference template by ID | `"cart-item-template"` | +| `data-item-template-selector` | Reference by CSS selector | `"#custom-template"` | +| `data-item-template` | Inline template string | `'
{item.name}
'` | +| `data-empty-template` | Template when cart is empty | `'

No items

'` | +| `data-title-map` | Map package IDs to titles (JSON) | `'{"2": "Premium"}'` | +| `data-group-items` | Group identical items together | Flag attribute | + +### Raw Values for Calculations + +Use `.raw` suffix for numeric calculations: + +```html +
+
+ +

Tax: ${item.lineTotal.raw * 0.1}

+
+
+``` + +| Variable | Description | +|----------|-------------| +| `{item.price.raw}` | Numeric price value | +| `{item.unitPrice.raw}` | Numeric unit price | +| `{item.lineTotal.raw}` | Numeric line total | +| `{item.savingsAmount.raw}` | Numeric savings | +| `{item.savingsPct.raw}` | Numeric savings percentage | +| `{item.discountAmount.raw}` | Numeric discount | +| `{item.discountedPrice.raw}` | Numeric discounted price | +| `{item.discountedLineTotal.raw}` | Numeric discounted line total | +| `{item.finalPrice.raw}` | Numeric final price | +| `{item.finalLineTotal.raw}` | Numeric final line total | + +## Package Subscription Fields + +Display subscription and recurring billing information for packages. + +### Package ID Fields + +```html + +Internal ID + + +External ID +``` + +| Property | Description | Type | +|----------|-------------|------| +| `package.ref_id` | Internal reference ID | Number | +| `package.external_id` | External system ID | Number | + +### Recurring Properties + +```html + +
+

Subscription Product

+

Delivered every month

+
+ + +
+

One-time purchase

+
+``` + +| Property | Description | Type | Values | +|----------|-------------|------|--------| +| `package.is_recurring` | Is subscription product | Boolean | true/false | +| `package.interval` | Billing interval | String | "day", "month", null | +| `package.interval_count` | Interval count | Number | 1, 2, 3, etc. | +| `package.isRecurring` | Is subscription (alias) | Boolean | true/false | +| `package.isOneTime` | Is one-time purchase | Boolean | true/false | + +### Recurring Pricing + +```html + +$29.99/month + + +$89.97 +``` + +| Property | Description | Type | +|----------|-------------|------| +| `package.price_recurring` | Recurring price per billing cycle | Currency | +| `package.price_recurring_total` | Total recurring price for all units | Currency | + +### Subscription Display Example + +```html +
+

Product Name

+ + +
+

$99.00

+

One-time purchase

+
+ + +
+

$29.99

+

+ Billed every + 1 + month +

+
+
+``` + +## Performance Tips + +### Efficient Selectors + +```html + +$0 + + +$0 +``` + +### Reduce Recalculations + +```html + +
+ 0 items - $0.00 +
+ + +0 items - +$0.00 +``` + +### Conditional Rendering + +```html + +
+

Discounts Applied

+ $0.00 +
+ + +
+

Discounts Applied

+ $0.00 +
+``` + +## Fallback Content + +Always provide fallback content for progressive enhancement: + +```html + + + Starting at $29.99 + + + + + Check availability + + + + + Loading campaign... + +``` + +## Debugging Display + +Enable debug mode to see: +- Which elements are bound to data +- Update frequency and performance +- Data path resolution +- Formatting applied + +```html + + + $99.99 + +``` + +## Related Documentation + +- [Actions](/docs/data-attributes/actions/) - Interactive elements +- [State Management](/docs/data-attributes/state/) - Conditional logic +- [Events](/docs/campaign-cart/javascript-api/events/) - React to data changes +- [Cart Display](/docs/building-blocks/cart-display/) - Complete cart UI patterns \ No newline at end of file diff --git a/docs/campaign-cart/data-attributes/index.md b/docs/campaign-cart/data-attributes/index.md new file mode 100644 index 00000000..cd0fe01c --- /dev/null +++ b/docs/campaign-cart/data-attributes/index.md @@ -0,0 +1,311 @@ +--- +title: Data Attributes Reference +description: Complete catalog of every data-next-* attribute that controls SDK behavior +sidebar_position: 3 +--- + +**Data attributes are the API through which your HTML communicates with the Campaign Cart SDK.** + +## Core Principles + +- **HTML as the Source of Truth:** Behavior is defined in markup, not JavaScript files +- **Progressive Enhancement:** Elements work without JavaScript, transform with it +- **Composable Patterns:** Combine attributes to create complex behaviors +- **State Management:** The SDK automatically tracks and updates element states + +## Attribute Categories + +### Display Attributes + +These attributes bind elements to dynamic data that updates automatically when state changes. + +| Attribute | Purpose | Example | +|-----------|---------|---------| +| `data-next-display` | Display dynamic data | `data-next-display="cart.total"` | +| `data-next-show` | Conditionally show element | `data-next-show="cart.isEmpty === false"` | +| `data-next-hide` | Conditionally hide element | `data-next-hide="cart.total < 100"` | +| `data-next-timer` | Countdown timer display | `data-next-timer="true" data-duration="300"` | + +This means: Your UI stays synchronized with cart state without writing any JavaScript. + +### Action Attributes + +These attributes turn any element into an interactive component. + +| Attribute | Purpose | Example | +|-----------|---------|---------| +| `data-next-action` | Define element action | `data-next-action="add-to-cart"` | +| `data-next-package-id` | Target package for action | `data-next-package-id="123"` | +| `data-next-quantity` | Quantity for action | `data-next-quantity="2"` | +| `data-next-toggle` | Toggle cart state | `data-next-toggle="cart"` | +| `data-next-clear-cart` | Clear cart before action | `data-next-clear-cart="true"` | +| `data-next-url` | Redirect after action | `data-next-url="/checkout"` | + +This means: Any element can trigger cart operations - buttons, images, links, or custom components. + +### Configuration Attributes + +These attributes configure component behavior and appearance. + +| Attribute | Purpose | Example | +|-----------|---------|---------| +| `data-format` | Number/price formatting | `data-format="currency"` | +| `data-hide-if-zero` | Hide when value is zero | `data-hide-if-zero="true"` | +| `data-hide-zero-cents` | Hide .00 in prices | `data-hide-zero-cents="true"` | +| `data-divide-by` | Mathematical division | `data-divide-by="100"` | +| `data-multiply-by` | Mathematical multiplication | `data-multiply-by="1.2"` | + +### State Attributes + +These attributes reflect current state and enable conditional logic. + +| Attribute | Purpose | Example | +|-----------|---------|---------| +| `data-next-selected` | Pre-selected state | `data-next-selected="true"` | +| `data-next-in-cart` | Item is in cart | Automatically managed | +| `data-next-loading` | Loading state | Automatically managed | +| `data-next-disabled` | Disabled state | Automatically managed | + +## Common Patterns + +### Add to Cart Button + +```html + +``` + +The SDK automatically: +- Toggles button text based on cart state +- Adds loading states during operations +- Updates aria-labels for accessibility +- Prevents double-clicks + +### Dynamic Price Display + +```html + + $0.00 + +``` + +The SDK automatically: +- Fetches current price from campaign data +- Formats according to user's locale +- Updates when currency changes +- Falls back to default content if data unavailable + +### Conditional Content + +```html +
+ Your cart has 0 items +
+ +
+ Your cart is empty +
+``` + +The SDK automatically: +- Evaluates conditions on every state change +- Uses CSS classes for performance (no DOM manipulation) +- Maintains layout stability (no content shifting) + +### Package Selector + +```html +
+ + + +
+``` + +The SDK automatically: +- Manages selection state across options +- Updates cart when selection changes +- Applies active/selected classes +- Handles keyboard navigation + +## Advanced Patterns + +### Quantity Controls with Sync + +```html +
+
+ + + +
+
+``` + +This creates a complete quantity control that: +- Syncs with cart state +- Respects min/max limits +- Updates on external changes +- Handles keyboard input + +### Timer with Persistence + +```html +
+ 05:00 +
+``` + +This creates a countdown timer that: +- Persists across page refreshes +- Formats time as minutes:seconds +- Emits events at milestones +- Auto-hides when complete + +### Profile-Based Display + +```html +
+ Premium member exclusive content +
+ +
+ +
+``` + +This enables: +- A/B testing without code changes +- Customer segment targeting +- Dynamic content based on user profiles + +## Performance Implications + +### Efficient Patterns + +✅ **Use specific display paths** +```html +$0 +``` + +✅ **Combine conditions in single attribute** +```html +
+``` + +✅ **Let SDK manage state classes** +```html + + +``` + +### Patterns to Avoid + +❌ **Don't nest conditional displays unnecessarily** +```html + +
+
+
+``` + +❌ **Don't duplicate package IDs in nested elements** +```html + +
+
+``` + +## Debugging Attributes + +Enable debug mode with `?debugger=true` to see: +- Which elements have been enhanced +- Current attribute values +- State changes in real-time +- Performance metrics + +```html + +
+ $99.00 +
+``` + +## Edge Cases + +### Multiple Actions on Single Element + +```html + + +``` + +Execution order: +1. Clear cart +2. Add package to cart +3. Redirect to checkout + +### Dynamic Attribute Updates + +The SDK observes attribute changes: + +```javascript +// This will trigger SDK re-enhancement +element.setAttribute('data-next-package-id', '456'); +``` + +### Fallback Content + +Always provide fallback content for progressive enhancement: + +```html + +$0.00 + + +
+ Limited time offer! +
+``` + +## Related Documentation + +- [JavaScript API](/docs/campaign-cart/javascript-api/) - JavaScript methods reference +- [Events](/docs/campaign-cart/javascript-api/events/) - Responding to SDK events +- [CSS Classes](/docs/campaign-cart/data-attributes/css-classes/) - Styling enhanced elements \ No newline at end of file diff --git a/docs/campaign-cart/data-attributes/order-data.md b/docs/campaign-cart/data-attributes/order-data.md new file mode 100644 index 00000000..a1efd0ba --- /dev/null +++ b/docs/campaign-cart/data-attributes/order-data.md @@ -0,0 +1,790 @@ +--- +title: Order Data Attributes +description: Display order confirmation data on thank you pages with order.* attributes +sidebar_position: 6 +--- + +**Order attributes display order confirmation data on thank you pages and post-purchase flows.** + +The order data is automatically loaded when the URL contains `?ref_id=ORDER_ID` and provides access to complete order information including customer details, line items, pricing, addresses, and attribution data. + +## Auto-Loading from URL + +Orders automatically load when the URL contains the `ref_id` parameter: + +``` +https://example.com/confirmation?ref_id=ORDER_123 +``` + +The SDK detects this parameter and loads the complete order data, making all `order.*` attributes available for display. + +## Loading States + +Order data has three states that should be handled in your confirmation page: + +```html + +
+

Loading your order...

+
+ + +
+

Unable to load order

+

Error details

+
+ + +
+ +
+``` + +The SDK automatically adds CSS classes to help with styling: +- `.next-loading` - Applied when order is loading +- `.next-loaded` - Applied when order loaded successfully +- `.next-error` - Applied when error occurred + +## Basic Order Properties + +Display order identification, status, and metadata. + +```html + +12345 +ORD-001 +abc123xyz +abc123xyz + + +Processing +2024-01-15 +2024-01-15 +USD + + +View Order Status +View Order Status +``` + +### Test Order Badge + +Display a badge when the order is a test transaction: + +```html + +🧪 TEST ORDER + + +
+

This is a test order

+
+``` + +| Property | Description | Type | +|----------|-------------|------| +| `order.id` | Order ID number | String/Number | +| `order.number` | Human-readable order number | String/Number | +| `order.ref_id` | Reference ID (snake_case) | String | +| `order.refId` | Reference ID (camelCase) | String | +| `order.status` | Order status | String | +| `order.is_test` | Test order flag (snake_case) | Boolean | +| `order.isTest` | Test order flag (camelCase) | Boolean | +| `order.testBadge` | "🧪 TEST ORDER" text or empty | String | +| `order.order_status_url` | Order status URL (snake_case) | String (URL) | +| `order.statusUrl` | Order status URL (camelCase) | String (URL) | +| `order.created_at` | Order creation date (snake_case) | String | +| `order.createdAt` | Order creation date (camelCase) | String | +| `order.currency` | Currency code | String | +| `order.supports_upsells` | Supports upsells (snake_case) | Boolean | +| `order.supportsUpsells` | Supports upsells (camelCase) | Boolean | + +## Order Financial Data + +Display order totals, taxes, shipping, and discounts. + +### Understanding Order Totals + +Order totals have specific meanings: + +- **`order.subtotal`** - Line items only (excludes shipping and tax) +- **`order.total_excl_tax`** - Total excluding tax but INCLUDING shipping +- **`order.total`** - Grand total including everything (products + shipping + tax) + +```html + +
Subtotal: $0.00
+
Shipping: $0.00
+
Tax: $0.00
+
Total: $0.00
+``` + +### Complete Order Summary Example + +```html +
+ +
+ Subtotal: + $0.00 +
+ + +
+ Shipping: + $0.00 +
+ + +
+ Tax: + $0.00 +
+ + +
+ Discount: + -$0.00 +
+ + +
+ You saved: + + $0.00 + (0%) + +
+ + +
+ Total: + $0.00 +
+
+``` + +### Financial Attributes Reference + +| Property | Description | Type | +|----------|-------------|------| +| `order.total_incl_tax` | Grand total including tax and shipping | Currency | +| `order.total` | Grand total (alias) | Currency | +| `order.subtotal` | Line items only | Currency | +| `order.subtotalExclShipping` | Line items only (alias) | Currency | +| `order.total_excl_tax` | Total excluding tax (includes shipping) | Currency | +| `order.total_tax` | Tax amount (snake_case) | Currency | +| `order.tax` | Tax amount (alias) | Currency | +| `order.shipping_incl_tax` | Shipping including tax (snake_case) | Currency | +| `order.shipping` | Shipping including tax (alias) | Currency | +| `order.shipping_excl_tax` | Shipping excluding tax (snake_case) | Currency | +| `order.shippingExclTax` | Shipping excluding tax (camelCase) | Currency | +| `order.shipping_tax` | Shipping tax amount (snake_case) | Currency | +| `order.shippingTax` | Shipping tax amount (camelCase) | Currency | +| `order.total_discounts` | Total discounts (snake_case) | Currency | +| `order.discounts` | Total discounts (alias) | Currency | +| `order.savingsAmount` | Calculated savings amount | Currency | +| `order.savingsPercentage` | Savings percentage | Number | +| `order.payment_method` | Payment method (snake_case) | String | +| `order.paymentMethod` | Payment method (camelCase) | String | +| `order.shipping_method` | Shipping method (snake_case) | String | +| `order.shippingMethod` | Shipping method (camelCase) | String | + +### Raw Values for Calculations + +Use the `.raw` suffix to get numeric values for mathematical operations: + +```html + +0% +``` + +| Property | Description | Type | +|----------|-------------|------| +| `order.total.raw` | Total as number | Number | +| `order.subtotal.raw` | Subtotal as number | Number | +| `order.tax.raw` | Tax as number | Number | +| `order.shipping.raw` | Shipping as number | Number | +| `order.discounts.raw` | Discounts as number | Number | + +## Customer Information + +Display customer contact details and preferences. + +```html + +
+

John Doe

+

john@example.com

+

+ Phone: 555-123-4567 +

+
+ + +
+ ✓ Subscribed to marketing emails +
+``` + +### Customer Attributes Reference + +| Property | Description | Type | +|----------|-------------|------| +| `order.customer.name` | Full customer name | String | +| `order.customer.firstName` | First name only | String | +| `order.customer.lastName` | Last name only | String | +| `order.customer.email` | Customer email | String | +| `order.customer.phone` | Customer phone | String | +| `order.customer.acceptsMarketing` | Marketing opt-in | Boolean | +| `order.customer.language` | Customer language | String | +| `order.customer.ip` | Customer IP address | String | + +### User Aliases + +`order.user.*` is an alias for `order.customer.*` and provides the same properties: + +```html + +John Doe +John Doe +``` + +| Property | Description | Type | +|----------|-------------|------| +| `order.user.name` | Full name (alias) | String | +| `order.user.firstName` | First name (alias) | String | +| `order.user.lastName` | Last name (alias) | String | +| `order.user.email` | Email (alias) | String | +| `order.user.phone` | Phone (alias) | String | + +## Address Information + +Display shipping and billing addresses. + +### Shipping Address + +```html +
+

Shipping Address

+
+ -
+ -
+ + -
+
+ -, + - + -
+ - +
+
+``` + +### Full Formatted Address + +Use the `.full` property to get a pre-formatted address string: + +```html +
+ 123 Main St, Springfield, IL 62701, USA +
+``` + +### Shipping Address Attributes + +| Property | Description | Type | +|----------|-------------|------| +| `order.shippingAddress.full` | Full formatted address | String | +| `order.shippingAddress.name` | Recipient name | String | +| `order.shippingAddress.line1` | Address line 1 | String | +| `order.shippingAddress.line2` | Address line 2 | String | +| `order.shippingAddress.city` | City | String | +| `order.shippingAddress.state` | State/Province | String | +| `order.shippingAddress.zip` | ZIP code | String | +| `order.shippingAddress.postcode` | Postcode (alias) | String | +| `order.shippingAddress.country` | Country | String | +| `order.shippingAddress.phone` | Phone number | String | + +### Billing Address Attributes + +Billing address has identical properties to shipping address: + +```html +
+

Billing Address

+
-
+
+``` + +| Property | Description | Type | +|----------|-------------|------| +| `order.billingAddress.full` | Full formatted address | String | +| `order.billingAddress.name` | Recipient name | String | +| `order.billingAddress.line1` | Address line 1 | String | +| `order.billingAddress.line2` | Address line 2 | String | +| `order.billingAddress.city` | City | String | +| `order.billingAddress.state` | State/Province | String | +| `order.billingAddress.zip` | ZIP code | String | +| `order.billingAddress.postcode` | Postcode (alias) | String | +| `order.billingAddress.country` | Country | String | +| `order.billingAddress.phone` | Phone number | String | + +## Line Items + +Display order items and product details. + +### Aggregate Line Item Data + +```html + +

Items: 0

+

Quantity: 0

+ + +

Product Name

+

SKU: -

+ + +

+ Includes 0 bonus item(s) +

+``` + +| Property | Description | Type | +|----------|-------------|------| +| `order.lines.count` | Number of line items | Number | +| `order.lines.totalQuantity` | Total quantity of all items | Number | +| `order.lines.upsellCount` | Number of upsell items | Number | +| `order.lines.mainProduct` | Main product title (first item) | String | +| `order.lines.mainProductSku` | Main product SKU (first item) | String | +| `order.items.count` | Line item count (alias) | Number | +| `order.items.totalQuantity` | Total quantity (alias) | Number | + +### Accessing Individual Line Items + +Access specific line items using array index notation: + +```html + +
+

Product Name

+

SKU: -

+

Quantity: 1

+

Price: $0.00

+

Total: $0.00

+
+ + +
+

Product Name

+
+``` + +### Line Item Attributes + +| Property | Description | Type | +|----------|-------------|------| +| `order.lines[n].title` | Product title | String | +| `order.lines[n].product_title` | Product title (snake_case) | String | +| `order.lines[n].sku` | Product SKU | String | +| `order.lines[n].product_sku` | Product SKU (snake_case) | String | +| `order.lines[n].quantity` | Quantity ordered | Number | +| `order.lines[n].image` | Product image URL | String (URL) | +| `order.lines[n].price` | Unit price including tax | Currency | +| `order.lines[n].priceExclTax` | Unit price excluding tax | Currency | +| `order.lines[n].priceExclTaxExclDiscounts` | Original price before discounts | Currency | +| `order.lines[n].priceInclTaxExclDiscounts` | Original price with tax before discounts | Currency | +| `order.lines[n].total` | Line total including tax | Currency | +| `order.lines[n].totalExclTax` | Line total excluding tax | Currency | +| `order.lines[n].isUpsell` | Is upsell item | Boolean | + +### Line Item Raw Values + +Use `.raw` for numeric calculations: + +| Property | Description | Type | +|----------|-------------|------| +| `order.lines[n].price.raw` | Unit price as number | Number | +| `order.lines[n].priceExclTax.raw` | Unit price excl tax as number | Number | +| `order.lines[n].priceExclTaxExclDiscounts.raw` | Original price as number | Number | +| `order.lines[n].priceInclTaxExclDiscounts.raw` | Original price with tax as number | Number | +| `order.lines[n].total.raw` | Line total as number | Number | +| `order.lines[n].totalExclTax.raw` | Line total excl tax as number | Number | + +## Order Items Renderer + +The `data-next-order-items` attribute automatically renders all line items using customizable templates. + +### Basic Usage + +```html +
+
+

{item.name}

+ Qty: {item.quantity} + {item.lineTotal} +
+
+``` + +### Template Configuration + +Templates can be defined in multiple ways: + +#### Template by ID + +```html + + + + +
+
+``` + +#### Inline Template + +```html +
+
+``` + +### Template Variables + +#### Basic Properties + +| Variable | Description | Type | +|----------|-------------|------| +| `{item.id}` | Order line ID | String | +| `{item.name}` | Product name | String | +| `{item.title}` | Product title (alias) | String | +| `{item.sku}` | Product SKU | String | +| `{item.quantity}` | Quantity ordered | Number | +| `{item.description}` | Product description | String | +| `{item.variant}` | Variant title | String | +| `{item.image}` | Product image URL | String | + +#### Pricing Variables + +| Variable | Description | Type | +|----------|-------------|------| +| `{item.price}` | Unit price including tax | Currency | +| `{item.priceExclTax}` | Unit price excluding tax | Currency | +| `{item.unitTax}` | Tax per unit | Currency | +| `{item.lineTotal}` | Line total including tax | Currency | +| `{item.lineTotalExclTax}` | Line total excluding tax | Currency | +| `{item.lineTax}` | Total tax for line | Currency | + +#### Original Pricing (Before Discounts) + +| Variable | Description | Type | +|----------|-------------|------| +| `{item.priceExclDiscounts}` | Original unit price (incl tax) | Currency | +| `{item.priceExclTaxExclDiscounts}` | Original unit price (excl tax) | Currency | +| `{item.lineTotalExclDiscounts}` | Original line total (incl tax) | Currency | +| `{item.lineTotalExclTaxExclDiscounts}` | Original line total (excl tax) | Currency | +| `{item.unitDiscount}` | Discount per unit | Currency | +| `{item.lineDiscount}` | Total line discount | Currency | + +#### Status Flags + +| Variable | Description | Value | +|----------|-------------|-------| +| `{item.isUpsell}` | Is upsell item | "true" or "false" | +| `{item.upsellBadge}` | Upsell badge text | "UPSELL" or "" | +| `{item.hasImage}` | Has image | "true" or "false" | +| `{item.hasDescription}` | Has description | "true" or "false" | +| `{item.hasVariant}` | Has variant | "true" or "false" | +| `{item.hasTax}` | Has tax | "true" or "false" | +| `{item.hasDiscount}` | Has discount | "true" or "false" | + +#### Conditional Display Classes + +| Variable | Description | Value | +|----------|-------------|-------| +| `{item.showUpsell}` | Show upsell badge | "show" or "hide" | +| `{item.showImage}` | Show image | "show" or "hide" | +| `{item.showDescription}` | Show description | "show" or "hide" | +| `{item.showVariant}` | Show variant | "show" or "hide" | +| `{item.showTax}` | Show tax | "show" or "hide" | +| `{item.showDiscount}` | Show discount | "show" or "hide" | + +### Complete Renderer Example + +```html + + + + +
+
+``` + +### Empty State + +Display a message when the order has no items: + +```html +
+
+``` + +## Attribution & UTM Tracking + +Display marketing attribution and campaign tracking data. + +```html + +
+

Campaign Information

+ +

+ Source: - +

+ +

+ Medium: - +

+ +

+ Campaign: - +

+ +

+ Affiliate: - +

+
+``` + +### Attribution Attributes + +| Property | Description | Type | +|----------|-------------|------| +| `order.attribution.utm_source` | UTM source (snake_case) | String | +| `order.attribution.source` | UTM source (alias) | String | +| `order.attribution.utm_medium` | UTM medium (snake_case) | String | +| `order.attribution.medium` | UTM medium (alias) | String | +| `order.attribution.utm_campaign` | UTM campaign (snake_case) | String | +| `order.attribution.campaign` | UTM campaign (alias) | String | +| `order.attribution.utm_term` | UTM term (snake_case) | String | +| `order.attribution.term` | UTM term (alias) | String | +| `order.attribution.utm_content` | UTM content (snake_case) | String | +| `order.attribution.content` | UTM content (alias) | String | +| `order.attribution.gclid` | Google Click ID | String | +| `order.attribution.funnel` | Funnel name | String | +| `order.attribution.affiliate` | Affiliate code | String | +| `order.attribution.hasTracking` | Has any tracking data | Boolean | + +## Boolean Properties + +Use boolean properties with `data-next-show` and `data-next-hide` for conditional display. + +### State Properties + +```html + +
Order loaded successfully
+
Loading order...
+
Error loading order
+``` + +### Content Properties + +```html + +
Order has items
+
Order is empty
+
Shipping was charged
+
Tax was charged
+
Discounts applied
+
Customer saved money
+
Order includes upsells
+``` + +### Timing Properties + +```html + +
Order placed recently
+
New order (< 15 min)
+
Can add more items
+``` + +### Boolean Properties Reference + +| Property | Description | Type | +|----------|-------------|------| +| `order.exists` | Order loaded successfully | Boolean | +| `order.isLoading` | Order is loading | Boolean | +| `order.hasError` | Error occurred | Boolean | +| `order.isTest` | Is test order | Boolean | +| `order.hasItems` | Order has line items | Boolean | +| `order.isEmpty` | Order is empty | Boolean | +| `order.hasShipping` | Shipping charged | Boolean | +| `order.hasTax` | Tax charged | Boolean | +| `order.hasDiscounts` | Discounts applied | Boolean | +| `order.hasSavings` | Has savings amount | Boolean | +| `order.hasUpsells` | Has upsell items | Boolean | +| `order.supportsUpsells` | Can add more items | Boolean | +| `order.isRecent` | Placed < 15 minutes ago | Boolean | +| `order.isExpired` | Placed > 15 minutes ago | Boolean | +| `order.isNewOrder` | New order (< 15 min) | Boolean | + +## Error Handling + +Display error messages when orders fail to load: + +```html +
+

Unable to Load Order

+

Error details

+

Please check your email for order confirmation.

+
+``` + +## Complete Confirmation Page Example + +```html +
+ +
+

Loading your order...

+
+ + +
+

Order Not Found

+

-

+
+ + +
+

Thank You for Your Order!

+ + +
+

+
+ + +
+

Order #-

+

Reference: -

+

Placed on -

+
+ + +
+

Customer Information

+

-

+

-

+
+ + +
+
+

Shipping Address

+
-
+
+ +
+

Billing Address

+
-
+
+
+ + +
+

Order Items

+
+
+

{item.name}

+ Qty: {item.quantity} + {item.lineTotal} +
+
+
+ + +
+

Order Summary

+
+ Subtotal: + $0.00 +
+
+ Shipping: + $0.00 +
+
+ Tax: + $0.00 +
+
+ Total: + $0.00 +
+
+ + + + View Order Status + +
+
+``` + +## Related Documentation + +- [Display Attributes](/data-attributes/display/) - Display dynamic data +- [State Attributes](/data-attributes/state/) - Conditional display logic +- [Configuration Attributes](/data-attributes/configuration/) - Formatting options diff --git a/docs/campaign-cart/data-attributes/profile.md b/docs/campaign-cart/data-attributes/profile.md new file mode 100644 index 00000000..9c029440 --- /dev/null +++ b/docs/campaign-cart/data-attributes/profile.md @@ -0,0 +1,286 @@ +--- +title: Profile Attributes +description: Display and control profile-based behavior and pricing +sidebar_position: 10 +--- + +# Profile Attributes + +Complete reference for profile-related HTML attributes and display properties. + +## Profile Display Properties + +Display current profile information and state. + +### Basic Profile Properties +```html + +exit_10 +exit_10 + + +Exit 10% Discount + + +true +``` + +## Profile Conditional Display + +Show or hide elements based on active profile. + +### Simple Profile Conditionals +```html + +
+ Black Friday prices are active! Save up to 50%! +
+ + +
+ Special promotional content +
+ + +
+ Welcome VIP member! Enjoy exclusive pricing. +
+ +
+ Don't leave! Here's your 10% discount. +
+``` + +### Complex Profile Conditionals +```html + +
+ Black Friday Sale Active +
+ + +
+ A special profile is currently active +
+ + +
+ VIP pricing applied +
+ + +
+ Seasonal sale profile available +
+``` + +## Profile Switcher Attributes + +Create interactive profile switching elements. + +### Profile Switch Button +```html + + + + + + + +
+ + + +
+``` + +### Profile Selector Dropdown +```html + + + + + +``` + +## Profile Switcher Options + +### data-next-clear-cart +Clears the cart before applying the new profile. +- **Values**: `true` | `false` +- **Default**: `false` + +```html + +``` + +### data-next-preserve-quantities +Maintains item quantities when swapping packages. +- **Values**: `true` | `false` +- **Default**: `true` + +```html + +``` + +### data-next-active-text / data-next-inactive-text +Dynamic button text based on profile state. + +```html + +``` + +## CSS Classes + +Automatically applied classes for styling profile elements. + +### Profile State Classes +- `.next-profile-active` - Applied when the profile is active +- `.next-profile-switcher` - Applied to profile switcher buttons +- `.next-profile-selector` - Applied to profile selector dropdowns +- `.next-profile-loading` - Applied during profile switching + +### Conditional Display Classes +- `.next-condition-met` - Applied when profile condition is true +- `.next-condition-not-met` - Applied when profile condition is false +- `.next-visible` - Applied when element should be shown +- `.next-hidden` - Applied when element should be hidden + +## Real-World Examples + +### Exit Intent Profile +```html + +
+

Wait! Don't Leave Yet!

+

We've activated a special 10% discount just for you!

+ + +
+``` + +### VIP Member Dashboard +```html + +
+

Welcome VIP Member!

+
+

Your Benefits:

+
    +
  • Exclusive VIP pricing
  • +
  • Free shipping on all orders
  • +
  • Priority customer support
  • +
+
+ +
+``` + +### Dynamic Pricing Display +```html + +
+ +
+

Regular Price

+ $99.99 +
+ + +
+

BLACK FRIDAY DEAL!

+ $99.99 + $49.99 + Save 50%! +
+
+``` + +### Profile-Based Navigation +```html + + +``` + +## JavaScript Events + +Listen for profile-related events: + +```javascript +// Profile applied +window.next.on('profile:applied', (data) => { + console.log(`Profile ${data.profileId} applied`); + console.log(`${data.itemsSwapped} items updated`); +}); + +// Profile reverted +window.next.on('profile:reverted', (data) => { + console.log(`Reverted from ${data.previousProfileId}`); +}); + +// Profile switched +window.next.on('profile:switched', (data) => { + console.log(`Switched from ${data.fromProfileId} to ${data.toProfileId}`); +}); +``` + +## Notes + +1. **Profile Priority**: Only one profile can be active at a time +2. **Package Mapping**: Profiles map package IDs to different packages (e.g., regular → sale version) +3. **Cart Persistence**: By default, cart items are swapped to mapped packages when profiles change +4. **URL Parameters**: Profiles can be activated via URL: `?profile=black_friday` or `?forceProfile=vip` +5. **Session Storage**: Active profile persists across page refreshes in the same session + +## Related Documentation + +- [State Attributes](/docs/campaign-cart/data-attributes/state/) - Profile conditionals +- [Configuration Attributes](/docs/campaign-cart/data-attributes/configuration/) - Profile switcher configuration +- [Display Attributes](/docs/campaign-cart/data-attributes/display/) - Display profile data + diff --git a/docs/campaign-cart/data-attributes/selection.md b/docs/campaign-cart/data-attributes/selection.md new file mode 100644 index 00000000..d9bea905 --- /dev/null +++ b/docs/campaign-cart/data-attributes/selection.md @@ -0,0 +1,448 @@ +--- +title: Selection Attributes +description: Display data based on currently selected package in product selectors +sidebar_position: 9 +--- + +**Selection attributes display data based on the currently selected package in a selector and update automatically when the selection changes.** + +Use selection attributes to show real-time pricing, savings, and product information as customers interact with your product selectors. + +## Selection Data Path + +Selection data is accessed using the pattern: + +``` +selection.{selectorId}.{property} +``` + +Where `{selectorId}` is the unique identifier specified in the `data-next-selector-id` attribute on the selector container. + +## Basic Selection Properties + +Display information about the currently selected package: + +```html + +Product Name + + +123 + + +1 + + +
+ Something is selected +
+``` + +| Property | Description | Type | +|----------|-------------|------| +| `selection.{selectorId}.name` | Selected package name | String | +| `selection.{selectorId}.packageId` | Selected package ID | Number | +| `selection.{selectorId}.quantity` | Selected quantity | Number | +| `selection.{selectorId}.hasSelection` | Whether something is selected | Boolean | + +## Selection Pricing + +Display pricing information for the selected package: + +```html + +$99.00 + + +$99.00 + + +$149.00 + + +$99.00 +``` + +| Property | Description | Type | +|----------|-------------|------| +| `selection.{selectorId}.price` | Unit price of selection | Currency | +| `selection.{selectorId}.total` | Total price (price × quantity) | Currency | +| `selection.{selectorId}.compareTotal` | Compare at price total | Currency | +| `selection.{selectorId}.unitPrice` | Price per unit | Currency | + +## Selection Calculated Fields + +Display savings and bundle information: + +```html + +$50.00 + + +33% + + +
+ You save money with this selection! +
+ + +
+ Multi-pack deal +
+ + +3 units +``` + +| Property | Description | Type | +|----------|-------------|------| +| `selection.{selectorId}.savingsAmount` | Total savings amount | Currency | +| `selection.{selectorId}.savingsPercentage` | Savings percentage | Number | +| `selection.{selectorId}.hasSavings` | Has savings | Boolean | +| `selection.{selectorId}.isBundle` | Is multi-pack bundle | Boolean | +| `selection.{selectorId}.totalUnits` | Total units in selection | Number | + +## Mathematical Expressions + +Perform calculations with selection data: + +```html + +$9.90 + + +$104.00 + + +$89.00 + + +$49.50 +``` + +Supported operators: `*` (multiply), `/` (divide), `+` (add), `-` (subtract) + +## Selector Container + +Create a product selector using the `data-next-cart-selector` attribute: + +```html +
+ +
+``` + +### Selector Container Attributes + +| Attribute | Required | Description | Example | +|-----------|----------|-------------|---------| +| `data-next-cart-selector` | Yes | Marks element as a selector container | `data-next-cart-selector` | +| `data-next-selector-id` | Yes | Unique identifier for the selector | `data-next-selector-id="main-product"` | +| `data-next-selection-mode` | No | Selection mode (swap, select, multi) | `data-next-selection-mode="swap"` | + +## Selector Cards + +Create selectable options within the selector: + +```html +
+ +
+ 1 Pack - $99 +
+ + +
+ 3 Pack - $249 +
+ + +
+ 5 Pack - $399 +
+
+``` + +### Selector Card Attributes + +| Attribute | Required | Description | +|-----------|----------|-------------| +| `data-next-selector-card` | Yes | Marks element as a selectable card | +| `data-next-package-id` | Yes | Package ID to select when clicked | +| `data-next-selected` | No | Pre-select this card (true/false) | +| `data-next-quantity` | No | Initial quantity for this card | +| `data-next-min-quantity` | No | Minimum quantity allowed (default: 1) | +| `data-next-max-quantity` | No | Maximum quantity allowed (default: 999) | + +## Quantity Controls + +Add quantity adjustment controls to selector cards: + +```html +
+ +

Premium Package

+

$19.99 each

+ + +
+ + 1 + +
+ + +

+ Total: $19.99 +

+
+``` + +### Quantity Control Attributes + +| Attribute | Description | Behavior | +|-----------|-------------|----------| +| `data-next-quantity-increase` | Increase quantity button | Auto-disables at max quantity | +| `data-next-quantity-decrease` | Decrease quantity button | Auto-disables at min quantity | +| `data-next-quantity-display` | Display current quantity | Updates automatically | +| `data-next-min-quantity` | Minimum allowed quantity | Default: 1 | +| `data-next-max-quantity` | Maximum allowed quantity | Default: 999 | + +### Automatic Behavior + +- **Auto-disable**: Decrease button gets `disabled` attribute and `next-disabled` class at minimum +- **Auto-disable**: Increase button gets `disabled` attribute and `next-disabled` class at maximum +- **Cart sync**: In `swap` mode, quantity changes update the cart automatically +- **Display updates**: All `selection.{selectorId}.total` elements update with quantity +- **Event prevention**: Quantity button clicks don't trigger card selection + +## Complete Example + +Product selector with selection display and quantity controls: + +```html + +
+ +
+ +
+

1 Pack

+

$99.00 each

+ + +
+ + 1 + +
+ + +

+ Total: $99.00 +

+
+
+ +
+

3 Pack

+

$249.00

+
+ +
+

5 Pack

+

$399.00

+
+
+ + +
+

Your Selection

+ + +

+ Please select a package +

+ + +
+

Selected: -

+

Quantity: 0 units

+

Price: $0

+ + +
+

You save: $0 + (0%) +

+
+
+ + + +
+``` + +## Dynamic Pricing Display + +Show different messages based on selection price: + +```html +
+ +
+ +
+

+ Basic protection for under $20 +

+

+ Premium protection +

+

+ Ultimate protection package +

+
+``` + +## Multiple Selectors + +Use multiple selectors on one page and combine their totals: + +```html + +
+
+ Main Product - $99 +
+
+ Premium Product - $149 +
+
+ + +
+
+ Basic Accessory - $19 +
+
+ Premium Accessory - $39 +
+
+ + +
+

Product: $0

+

Accessory: $0

+
+

Total: $0

+
+``` + +## Conditional Display + +Show/hide content based on selection state: + +```html + +
+

Choose a package to see pricing

+
+ + +
+

You selected:

+
+ + +
+ SAVE % +
+ + +
+ BUNDLE DEAL +
+``` + +## Selection Properties Reference + +### Basic Properties + +| Property | Description | Type | Example Value | +|----------|-------------|------|---------------| +| `name` | Package name | String | "Premium Package" | +| `packageId` | Package ID | Number | 123 | +| `quantity` | Selected quantity | Number | 2 | +| `hasSelection` | Selection exists | Boolean | true | + +### Pricing Properties + +| Property | Description | Type | Example Value | +|----------|-------------|------|---------------| +| `price` | Unit price | Currency | "$99.00" | +| `total` | Total price | Currency | "$198.00" | +| `compareTotal` | Compare total | Currency | "$298.00" | +| `unitPrice` | Price per unit | Currency | "$99.00" | + +### Calculated Properties + +| Property | Description | Type | Example Value | +|----------|-------------|------|---------------| +| `savingsAmount` | Total savings | Currency | "$100.00" | +| `savingsPercentage` | Savings % | Number | 33 | +| `hasSavings` | Has savings | Boolean | true | +| `isBundle` | Is bundle | Boolean | true | +| `totalUnits` | Total units | Number | 6 | + +## Best Practices + +1. **Use unique selector IDs** + - Choose descriptive, unique IDs for each selector + - Example: `main-product`, `warranty`, `accessory` + +2. **Show selection feedback** + - Display selected package details + - Show pricing updates in real-time + +3. **Use conditional display** + - Hide/show elements based on `hasSelection` + - Guide users through the selection process + +4. **Display price updates** + - Show real-time pricing as quantity changes + - Highlight savings when applicable + +5. **Validate before actions** + - Hide "Add to Cart" button until selection is made + - Use `data-next-hide="!selection.{selectorId}.hasSelection"` + +6. **Set appropriate quantity limits** + - Use `data-next-min-quantity` and `data-next-max-quantity` + - Match limits to your inventory/business rules + +## Related Documentation + +- [Display Attributes](/data-attributes/display/) - Display dynamic data +- [Action Attributes](/data-attributes/actions/) - Add to cart actions +- [State Attributes](/data-attributes/state/) - Conditional display diff --git a/docs/campaign-cart/data-attributes/state.md b/docs/campaign-cart/data-attributes/state.md new file mode 100644 index 00000000..dce3529b --- /dev/null +++ b/docs/campaign-cart/data-attributes/state.md @@ -0,0 +1,552 @@ +--- +title: State & Conditional Attributes +description: Control element visibility and behavior based on cart and application state +sidebar_position: 4 +--- + +**State attributes enable conditional logic and visibility control without JavaScript.** + +## Core Principles + +- **Declarative Conditions:** Express logic directly in HTML +- **CSS-Based Performance:** Uses classes for visibility, not DOM manipulation +- **Real-Time Evaluation:** Conditions re-evaluate on every state change +- **Layout Stability:** No content shifting or reflow + +## Conditional Display + +### data-next-show + +Show elements when conditions are true. + +```html + +
+ Cart has items +
+ +
+ Cart is empty +
+ + +
+ Free shipping unlocked! +
+ +
+ Bulk discount applied +
+ + +
+ Eligible for express checkout +
+ + +
+ Premium package selected +
+``` + +### data-next-hide + +Hide elements when conditions are true (inverse of show). + +```html + + + + + + + +
+ Checkout available +
+``` + +## Profile Conditionals + +Show/hide content based on active customer profiles. + +### data-next-show-if-profile + +```html + +
+ Premium member exclusive pricing +
+ + +
+ Member benefits apply +
+ + +
+ Standard pricing: $99 +
+ +
+ Special offer: $89 +
+``` + +### data-next-hide-if-profile + +```html + +
+ Upgrade to premium for better prices +
+ + +
+ +
+``` + +## State Classes + +The SDK automatically applies CSS classes based on element state. + +### Cart State Classes + +| Class | Applied When | Used On | +|-------|-------------|---------| +| `.next-in-cart` | Item is in cart | Action buttons | +| `.next-cart-empty` | Cart has no items | Body element | +| `.next-cart-has-items` | Cart has items | Body element | + +```css +/* Style based on cart state */ +.next-in-cart { + background: green; + color: white; +} + +.next-cart-empty .checkout-button { + opacity: 0.5; + pointer-events: none; +} +``` + +### Action State Classes + +| Class | Applied When | Used On | +|-------|-------------|---------| +| `.next-loading` | Action in progress | Action elements | +| `.next-disabled` | Element disabled | Interactive elements | +| `.next-active` | Element is active | Toggle elements | + +```css +/* Loading state */ +.next-loading { + opacity: 0.6; + cursor: wait; +} + +/* Disabled state */ +.next-disabled { + opacity: 0.4; + pointer-events: none; +} +``` + +### Selection State Classes + +| Class | Applied When | Used On | +|-------|-------------|---------| +| `.next-selected` | Option selected | Selector options | +| `.next-selector-active` | Has active selection | Selector container | + +```css +/* Selected option */ +.next-selected { + border: 2px solid blue; + background: #f0f8ff; +} + +/* Active selector */ +.next-selector-active .default-message { + display: none; +} +``` + +### Profile State Classes + +| Class | Applied When | Used On | +|-------|-------------|---------| +| `.next-profile-[id]` | Profile is active | Body element | +| `.next-profile-active` | Any profile active | Profile elements | + +```css +/* Profile-specific styles */ +.next-profile-premium .price { + color: gold; +} + +.next-profile-test .cta-button { + background: orange; +} +``` + +## Conditional Logic Syntax + +### Supported Operators + +| Operator | Description | Example | +|----------|-------------|---------| +| `===` | Strict equality | `cart.total === 100` | +| `!==` | Not equal | `profile.active !== 'premium'` | +| `>` | Greater than | `cart.totalQuantity > 5` | +| `<` | Less than | `cart.total < 50` | +| `>=` | Greater or equal | `cart.items.length >= 3` | +| `<=` | Less or equal | `cart.discount <= 10` | +| `&&` | Logical AND | `cart.hasItems && cart.total > 100` | +| `||` | Logical OR | `cart.isEmpty || cart.total < 25` | +| `!` | Logical NOT | `!cart.hasItem(123)` | + +### Compound Conditionals + +Combine multiple conditions using logical operators for powerful conditional logic. + +#### OR Operator (`||`) + +Show elements when ANY condition is true: + +```html + +
+ You have selected an eligible package! +
+ + +
+ Premium package detected +
+ + +
+ Eligible for bulk discount +
+``` + +#### AND Operator (`&&`) + +Show elements when ALL conditions are true: + +```html + +
+ Premium package with minimum order met +
+ + +
+ Bulk order discount applied +
+``` + +#### NOT Operator (`!`) + +Negate conditions: + +```html + +
+ +
+ + +
+ Complete your order with our premium package +
+``` + +#### Parentheses for Grouping + +Control operator precedence with parentheses: + +```html + +
+ Special offer unlocked! +
+ + +
+ VIP checkout available +
+ + +
+ Protect your purchase with extended warranty +
+``` + +#### Real-World Examples + +**Conditional Upsells** +```html + +
+
+

Protect Your Purchase

+

Add 3-year extended warranty for peace of mind

+ +
+
+``` + +**Smart Cross-Sells** +```html + +
+ Complete your setup with our accessory bundle! +
+``` + +**Eligibility Gates** +```html + +
+ +
+``` + +### Available Properties + +#### Cart Properties + +```html + +
...
+
...
+
...
+
...
+ + +
...
+
...
+
...
+
...
+
...
+
...
+ + +
...
+
...
+``` + +#### Package Properties + +```html + +
...
+
...
+
...
+
...
+``` + +#### Order Properties + +```html + +
...
+
...
+
...
+
...
+``` + +#### Profile Properties + +```html + +
...
+
...
+
...
+``` + +## Common Patterns + +### Progressive Checkout Button + +```html + + + + +
+ Add $0 more to checkout +
+``` + +### Tiered Messaging + +```html + +
+ Your cart is empty +
+ +
+ Add $0 for free shipping +
+ +
+ Free shipping unlocked! Add $0 for a gift +
+ +
+ Free shipping + gift included! +
+``` + +### Smart Product Recommendations + +```html + +
+

Complete your set

+ +
+ + +
+ +
+``` + +### Quantity-Based Offers + +```html + +
+ 10% bulk discount applied +
+ +
+ 15% bulk discount applied +
+ +
+ 20% bulk discount applied - Maximum savings! +
+``` + +## Performance Considerations + +### Efficient Conditions + +✅ **Use simple conditions when possible** +```html +
+``` + +✅ **Combine related conditions** +```html +
+``` + +✅ **Use built-in properties** +```html +
+``` + +### Avoid Complex Nesting + +❌ **Don't nest conditions unnecessarily** +```html + +
+
+
+``` + +✅ **Combine into single condition** +```html + +
+``` + +## Debugging State + +With `?debugger=true`, the SDK shows: +- Current evaluated values +- Condition results +- Applied state classes +- Re-evaluation frequency + +```html + +
+ Free shipping +
+``` + +## Edge Cases + +### Null Safety + +The SDK safely handles null/undefined values: + +```html + +
+ +
+``` + +### Race Conditions + +Conditions are evaluated after data loads: + +```html + +
+ Ready to checkout +
+``` + +### Dynamic Updates + +Conditions re-evaluate when data changes: + +```javascript +// Changing cart will update all conditions +await next.addItem({ packageId: 123 }); +// All data-next-show/hide automatically update +``` + +## Related Documentation + +- [Display Attributes](/docs/data-attributes/display/) - Showing dynamic data +- [Action Attributes](/docs/data-attributes/actions/) - Interactive elements +- [CSS Classes](/docs/reference/css-classes/) - Styling state classes +- [Events](/docs/campaign-cart/javascript-api/events/) - Responding to state changes diff --git a/docs/campaign-cart/data-attributes/url-parameters.md b/docs/campaign-cart/data-attributes/url-parameters.md new file mode 100644 index 00000000..c38590ee --- /dev/null +++ b/docs/campaign-cart/data-attributes/url-parameters.md @@ -0,0 +1,449 @@ +--- +title: URL Parameters +description: Control element visibility and behavior using URL query parameters +sidebar_position: 7 +--- + +**URL parameters provide a powerful way to control element visibility and behavior based on query string values.** + +Parameters are automatically captured when the SDK initializes and persist throughout the user's session, even when navigating between pages. This makes them perfect for feature flags, A/B testing, marketing campaigns, and development/testing scenarios. + +## How It Works + +The URL parameter system follows this lifecycle: + +1. **Parameter Capture** - When the SDK initializes, all URL parameters are automatically captured and stored in sessionStorage +2. **Session Persistence** - Parameters remain available throughout the user's session, even when navigating to pages without those parameters +3. **Parameter Override** - If a user visits a new page with different parameter values, the new values override the stored ones +4. **Conditional Display** - Use `data-next-show` and `data-next-hide` attributes to control element visibility based on parameter values + +## Basic Usage + +### Hiding Elements + +Hide elements when specific URL parameters are present: + +```html + +
+ As seen on TV section +
+ + +
+
00:00:00
+
+ + +
+ Customer testimonials... +
+``` + +### Showing Elements + +Show elements only when specific URL parameters match: + +```html + +
+ Debug mode is enabled +
+ + +
+ Simple header without promotional banner +
+``` + +## Parameter Syntax + +Access URL parameters using the `param.` prefix: + +```html + +
+ Advanced options +
+``` + +The parameter name after `param.` must match the URL query parameter name exactly (case-sensitive). + +## Supported Conditions + +### Equality Checks + +```html + +
+ Advanced options +
+ + +
+ Dark theme styles +
+``` + +### Existence Checks + +Check if a parameter exists regardless of its value: + +```html + +
+ Preview mode active +
+ + +
+ Login required message +
+``` + +### Numeric Comparisons + +Compare parameter values numerically: + +```html + +
+ Bulk discount available! +
+ + +
+ Age-restricted content +
+``` + +### Combined Conditions + +Combine URL parameters with other data properties using logical operators: + +```html + +
+ Special upsell offer for existing customers! +
+ + +
+ Gold + Premium member exclusive content +
+``` + +## Common Use Cases + +### Feature Flags + +Control which features are visible to users: + +```html + +
+
+ Special offer before you leave! +
+
+ + +
+
Loading...
+
+ + +
+ Promotional banner content +
+``` + +Common feature flag parameters: +- `?exit=n` - Disable exit intent popups +- `?timer=n` - Disable countdown timers +- `?reviews=n` - Hide reviews/testimonials +- `?loading=n` - Hide loading animations +- `?banner=n` - Remove promotional banners +- `?seen=n` - Hide "As seen on" sections + +### A/B Testing + +Show different variations based on URL parameters: + +```html + +
+ +
+ + +
+ +
+``` + +Access different variants using `?variant=a` or `?variant=b` in the URL. + +### Development & Testing + +Show debug information or test indicators: + +```html + +
+
Cart State: 
+
+ + +
+ TEST MODE - Orders will not be processed +
+``` + +Use `?debug=true` or `?test=true` to enable development features. + +### Marketing Campaigns + +Customize content based on campaign parameters: + +```html + +
+ Summer Sale - 50% Off Everything! +
+ + +
+ Regular pricing section +
+
+ VIP exclusive pricing! +
+``` + +Track campaigns with URLs like: +- `?campaign=summer2024` +- `?special=vip` +- `?member=gold` + +## Session Persistence + +Parameters persist throughout the entire session: + +``` +Page 1: ?banner=n&theme=dark + → Stores: banner=n, theme=dark + +Page 2: ?theme=light + → Updates: theme=light + → Keeps: banner=n + +Page 3: (no parameters) + → Uses stored: banner=n, theme=light +``` + +### Parameter Priority + +When the same parameter appears multiple times: + +1. **URL parameters on current page** - Highest priority +2. **Previously stored session parameters** - Used as fallback +3. **New values override old values** - Most recent wins + +## JavaScript API + +Manage URL parameters programmatically without modifying the URL. + +### Setting Parameters + +```javascript +// Set a single parameter +next.setParam('banner', 'n'); + +// Set multiple parameters at once +next.setParams({ + timer: 'n', + reviews: 'n' +}); +``` + +### Getting Parameters + +```javascript +// Check if parameter exists +if (next.hasParam('debug')) { + // Parameter exists +} + +// Get parameter value +const debugValue = next.getParam('debug'); +console.log('Debug mode:', debugValue); + +// Get all parameters +const params = next.getAllParams(); +console.log('Current parameters:', params); +``` + +### Clearing Parameters + +```javascript +// Clear a specific parameter +next.clearParam('timer'); +``` + +### JavaScript API Reference + +| Method | Description | Example | +|--------|-------------|---------| +| `next.setParam(name, value)` | Set single parameter | `next.setParam('banner', 'n')` | +| `next.setParams(object)` | Set multiple parameters | `next.setParams({ timer: 'n' })` | +| `next.hasParam(name)` | Check if parameter exists | `next.hasParam('debug')` | +| `next.getParam(name)` | Get parameter value | `next.getParam('debug')` | +| `next.clearParam(name)` | Clear parameter | `next.clearParam('timer')` | +| `next.getAllParams()` | Get all parameters | `next.getAllParams()` | + +## Debugging + +### View Current Parameters + +Open the browser console and use these commands: + +```javascript +// View all stored parameters (debug method) +nextDebug.stores.parameter().debug() + +// Get all parameters +const params = next.getAllParams(); +console.log('Current parameters:', params); + +// Check specific parameter +nextDebug.stores.parameter().getParam('seen') + +// Check if parameter exists +nextDebug.stores.parameter().hasParam('timer') +``` + +### Common Issues + +**Parameters not persisting** +- Check that sessionStorage is not being cleared +- Verify parameters are being set correctly + +**Wrong values showing** +- Remember that URL parameters override stored values +- Check parameter priority order + +**Case sensitivity** +- Parameter names are case-sensitive +- `param.Test` is NOT the same as `param.test` + +**Webflow quote stripping** +- Webflow may strip quotes from attribute values +- Both formats work: + - `data-next-hide="param.timer == 'n'"` (with quotes) + - `data-next-hide="param.timer==n"` (without quotes) +- The SDK automatically handles both formats + +## Complete Examples + +### Feature Flag System + +```html + +
+
+ Special offer popup +
+
+ + +
+
00:00:00
+
+ + +
+ Customer reviews section +
+ + +
+
Loading...
+
+ + +
+ Promotional banner content +
+ + +
+ As seen on TV/Media logos +
+``` + +### A/B Test Implementation + +```html + +
+

Limited Time Offer!

+ +

Only 3 items left in stock

+
+ + +
+

Premium Quality Products

+ +

Free shipping on all orders

+
+ + +
+

Welcome to Our Store

+ +
+``` + +## Best Practices + +1. **Use descriptive parameter names** + - Good: `?hideReviews=y` + - Bad: `?hr=y` + +2. **Be consistent with values** + - Choose a convention: `true`/`false` or `y`/`n` + - Use the same convention throughout your site + +3. **Document your parameters** + - Keep a list of all parameters your site uses + - Document what each parameter does + +4. **Consider security** + - Don't use parameters for sensitive features without validation + - Parameters are visible in URLs and can be modified + +5. **Test thoroughly** + - Test parameter behavior across page navigation + - Verify session persistence works as expected + - Check both with and without parameters in URL + +## Conditional Operators Reference + +| Operator | Description | Example | +|----------|-------------|---------| +| `==` | Equal to | `param.mode == 'advanced'` | +| `!=` | Not equal to | `param.theme != 'dark'` | +| `>` | Greater than | `param.quantity > 5` | +| `<` | Less than | `param.age < 18` | +| `param.name` | Exists (truthy) | `param.preview` | +| `!param.name` | Does not exist | `!param.authenticated` | +| `&&` | Logical AND | `param.a == 'x' && param.b == 'y'` | + +## Related Documentation + +- [State Attributes](/data-attributes/state/) - Conditional display logic +- [Display Attributes](/data-attributes/display/) - Display dynamic data +- [Configuration Attributes](/data-attributes/configuration/) - Advanced options diff --git a/docs/campaign-cart/guides/advanced-customization.md b/docs/campaign-cart/guides/advanced-customization.md new file mode 100644 index 00000000..ade0df21 --- /dev/null +++ b/docs/campaign-cart/guides/advanced-customization.md @@ -0,0 +1,625 @@ +# Advanced Customization Examples + +Complex scenarios and edge cases with the Next Commerce JS SDK. + +## Dynamic Bundle Builder + +Build custom bundles with real-time pricing updates: + +```html + + + + + Build Your Custom Drone Bundle + + + + + + + + +
+
+ +
+

1. Choose Your Drone

+
+
+ +
+

Drone Pro X Basic

+

Entry-level drone with HD camera

+
+
$599
+
+ +
+ +
+

Drone Pro X Advanced

+

4K camera with gimbal stabilization

+
+
$899
+
+ +
+ +
+

Drone Pro X Ultimate

+

8K camera, obstacle avoidance, 45min flight

+
+
$1299
+
+
+
+ + +
+

2. Extra Batteries

+
+
+
+

No Extra Batteries

+

Just the one that comes with drone

+
+
$0
+
+ +
+
+

+1 Extra Battery

+

Double your flight time

+
+
$79
+
+ +
+
+

+2 Extra Batteries

+

Triple your flight time

+ + Save 10% + +
+
$149
+
+
+
+ + +
+

3. Select Accessories

+
+
+ +
+

Carrying Case

+

Professional hard-shell case

+
+
$49
+
+ +
+ +
+

ND Filter Set

+

6 filters for perfect exposure

+
+
$39
+
+ +
+ +
+

Extra Propellers

+

2 complete sets

+
+
$29
+
+ +
+ +
+

Landing Pad

+

Professional takeoff/landing surface

+
+
$19
+
+
+
+ + +
+

4. Protection Plan

+
+
+
+

No Protection

+

Standard manufacturer warranty only

+
+
$0
+
+ +
+
+

2-Year Protection

+

Covers accidents and defects

+
+
$99
+
+ +
+
+

3-Year Protection + Care

+

Full coverage + annual maintenance

+
+
$179
+
+
+
+
+ + +
+

Your Bundle

+ + +
+
+ Drone: + $0 +
+ +
+ Batteries: + $0 +
+ +
+ Carrying Case: $49 +
+ +
+ ND Filter Set: $39 +
+ +
+ Extra Propellers: $29 +
+ +
+ Landing Pad: $19 +
+ +
+ Protection: + $0 +
+
+ +
+ + +
+ Total: $0 +
+ + +
+ You're saving $0! + That's 0% off retail. +
+ + +
+

Bundle Discounts

+
+ 3+ items: 5% off + ✓ Active +
+
+ 5+ items: 10% off + ✓ Active +
+
+ $1500+: Extra 5% off + ✓ Active +
+
+ + + + + +
+
+ + + + +``` + +## Multi-Currency Support + +Handle different currencies and regional pricing: + +```html + +
+ +
+ + +
+ $ + 99.99 +
+ + +``` + +## A/B Testing Implementation + +Test different layouts and messaging: + +```html + +``` + +## Dynamic Pricing Rules + +Implement complex pricing logic: + +```html + +``` + +## Custom Validation + +Add custom validation before checkout: + +```html + +``` + +## Performance Optimization + +Optimize for large catalogs: + +```html + +Product + + +
+ +
+ + +``` + +These advanced examples demonstrate: +1. **Dynamic Bundle Building** - Complex multi-step configuration +2. **Multi-Currency Support** - Regional pricing +3. **A/B Testing** - Conversion optimization +4. **Dynamic Pricing** - Time and quantity-based rules +5. **Custom Validation** - Business logic enforcement +6. **Performance Optimization** - Large catalog handling \ No newline at end of file diff --git a/docs/campaign-cart/guides/index.md b/docs/campaign-cart/guides/index.md new file mode 100644 index 00000000..0bb394b3 --- /dev/null +++ b/docs/campaign-cart/guides/index.md @@ -0,0 +1,21 @@ +--- +sidebar_label: Guides +sidebar_position: 7 +--- + +# Guides + +Complete implementations and code examples for common use cases. + +## Advanced Flows + +- [Advanced Customization](/docs/campaign-cart/guides/advanced-customization) - Complex scenarios +- [Tier Selector Implementation](/docs/campaign-cart/guides/tier-selector-implementation) - Tier selection patterns +- [Quantity Package Swapper](/docs/campaign-cart/guides/quantity-package-swapper) - Quantity-based swapping + +## Related + +- [Analytics Overview](/docs/campaign-cart/analytics/) - Analytics setup +- [Cart System](/docs/campaign-cart/cart-system/) - Cart management +- [Upsells](/docs/campaign-cart/upsells/) - Upsell flows + diff --git a/docs/campaign-cart/guides/profiles.md b/docs/campaign-cart/guides/profiles.md new file mode 100644 index 00000000..865c548d --- /dev/null +++ b/docs/campaign-cart/guides/profiles.md @@ -0,0 +1,702 @@ +# Profile-Based Package Mapping System + +The Profile System enables dynamic package ID swapping across your entire e-commerce implementation. This powerful feature allows you to maintain multiple pricing tiers, seasonal promotions, and customer-specific offers without duplicating your frontend code. + +## Table of Contents + +- [Overview](#overview) +- [Core Concepts](#core-concepts) +- [Configuration](#configuration) +- [Implementation Methods](#implementation-methods) +- [JavaScript API](#javascript-api) +- [HTML Data Attributes](#html-data-attributes) +- [URL Parameters](#url-parameters) +- [Real-World Examples](#real-world-examples) +- [Advanced Usage](#advanced-usage) +- [Performance Considerations](#performance-considerations) +- [Troubleshooting](#troubleshooting) + +## Overview + +The Profile System solves a common e-commerce challenge: how to efficiently manage different product variants (regular price, discounted, bundles) without complex conditional logic throughout your codebase. Instead of hardcoding package IDs, you define mapping profiles that automatically swap packages based on the active profile. + +### Key Benefits + +- **Dynamic Pricing**: Switch between pricing tiers instantly +- **Seasonal Promotions**: Activate holiday sales with a single profile change +- **A/B Testing**: Test different pricing strategies seamlessly +- **Membership Tiers**: Offer VIP pricing to premium customers +- **Bundle Management**: Convert single items to multi-packs automatically + +## Core Concepts + +### What is a Profile? + +A profile is a named configuration that defines how package IDs should be mapped. When a profile is active, any reference to an original package ID is automatically translated to its mapped counterpart. + +```javascript +// Profile Definition +{ + id: "black_friday", + name: "Black Friday Sale", + packageMappings: { + 1: 101, // Package 1 → Package 101 (BF variant) + 2: 102, // Package 2 → Package 102 (BF variant) + // ... more mappings + } +} +``` + +### How Mapping Works + +1. **Original State**: Your site uses standard package IDs (1, 2, 3, etc.) +2. **Profile Activation**: User or system activates a profile +3. **Automatic Mapping**: All package operations use mapped IDs +4. **Transparent Integration**: Cart, checkout, and display components update automatically + +## Configuration + +### Basic Setup + +Add profile configuration to your `window.nextConfig`: + +```html + +``` + +### Grounded Sheets Example + +For the Grounded Sheets product line with multiple variants: + +```javascript +profiles: { + // Exit intent discount + "exit_10": { + name: "Exit 10% Discount", + packageMappings: { + // Single quantity mappings + 1: 78, // Twin Obsidian Grey → Exit 10% variant + 2: 79, // Twin Chateau Ivory → Exit 10% variant + 3: 82, // Double Obsidian Grey → Exit 10% variant + 4: 83, // Double Chateau Ivory → Exit 10% variant + 5: 86, // Queen Obsidian Grey → Exit 10% variant + 6: 90, // King Obsidian Grey → Exit 10% variant + 7: 87, // Queen Chateau Ivory → Exit 10% variant + 8: 91, // King Chateau Ivory → Exit 10% variant + // ... continue for all colors/sizes + } + }, + + // Bundle profiles + "2_pack": { + name: "2-Pack Bundle", + packageMappings: { + 1: 29, // Twin Obsidian Grey → 2-pack + 2: 30, // Twin Chateau Ivory → 2-pack + 3: 33, // Double Obsidian Grey → 2-pack + // ... all 2-pack mappings + } + }, + + "3_pack": { + name: "3-Pack Bundle", + packageMappings: { + 1: 53, // Twin Obsidian Grey → 3-pack + 2: 54, // Twin Chateau Ivory → 3-pack + 3: 58, // Double Obsidian Grey → 3-pack + // ... all 3-pack mappings + } + } +} +``` + +## Implementation Methods + +### Method 1: URL Parameters + +The simplest way to activate profiles is through URL parameters: + +``` +// Apply profile (preserves cart) +https://example.com/checkout?profile=black_friday + +// Force profile (clears cart first) +https://example.com/checkout?forceProfile=vip + +// Alternative parameter name +https://example.com/checkout?packageProfile=sale_20 +``` + +### Method 2: JavaScript API + +Programmatic control via the `window.next` API: + +```javascript +// Apply a profile +await window.next.setProfile('black_friday'); + +// Apply with options +await window.next.setProfile('vip', { + clearCart: false, // Keep existing cart items + preserveQuantities: true // Maintain item quantities +}); + +// Revert to no profile +await window.next.revertProfile(); + +// Check active profile +const currentProfile = window.next.getActiveProfile(); +console.log(currentProfile); // "black_friday" + +// Get mapped package ID +const mappedId = window.next.getMappedPackageId(1); +console.log(mappedId); // 101 (if black_friday is active) + +// Get original package ID from mapped +const originalId = window.next.getOriginalPackageId(101); +console.log(originalId); // 1 + +// List all available profiles +const profiles = window.next.listProfiles(); +console.log(profiles); // ["regular", "black_friday", "vip"] + +// Check if profile exists +if (window.next.hasProfile('black_friday')) { + // Profile is configured +} + +// Get profile information +const profileInfo = window.next.getProfileInfo('black_friday'); +console.log(profileInfo.name); // "Black Friday Sale" +``` + +### Method 3: HTML Data Attributes + +#### Profile Switcher Button + +```html + + + + + + + + + + + +``` + +#### Profile Selector Dropdown + +```html + + + + + +``` + +#### Profile-Aware Add to Cart + +```html + + +``` + +#### Conditional Display + +```html + +
+ 🎉 Black Friday prices are active! Save up to 40%! +
+ + +

Current Pricing: Regular

+

Profile ID: none

+``` + +## Real-World Examples + +### Example 1: Exit Intent Discount + +```javascript +// In your exit intent handler +window.next.exitIntent({ + image: '/images/special-offer.jpg', + action: async () => { + // Apply exit discount profile + await window.next.setProfile('exit_10'); + + // Optionally apply a coupon too + await window.next.applyCoupon('SAVE10'); + + // Show success message + alert('10% discount applied to your cart!'); + } +}); +``` + +### Example 2: Membership Tier System + +```javascript +// After user login +async function applyMembershipPricing(user) { + if (user.membership === 'vip_gold') { + await window.next.setProfile('vip_gold'); + } else if (user.membership === 'vip_silver') { + await window.next.setProfile('vip_silver'); + } + // Regular members use default pricing +} + +// Check if user qualifies for special pricing +document.addEventListener('DOMContentLoaded', async () => { + const user = await fetchUserData(); + if (user && user.membership) { + applyMembershipPricing(user); + } +}); +``` + +### Example 3: Time-Based Promotions + +```javascript +// Automatic profile activation based on time +function checkAndApplyPromotions() { + const now = new Date(); + const hour = now.getHours(); + + // Happy hour: 3-6 PM + if (hour >= 15 && hour < 18) { + window.next.setProfile('happy_hour'); + } + // Flash sale: Midnight to 2 AM + else if (hour >= 0 && hour < 2) { + window.next.setProfile('flash_sale'); + } + // Weekend special + else if (now.getDay() === 0 || now.getDay() === 6) { + window.next.setProfile('weekend_special'); + } +} + +// Check every hour +setInterval(checkAndApplyPromotions, 3600000); +checkAndApplyPromotions(); // Initial check +``` + +### Example 4: Quantity-Based Bundle Conversion + +```html + +
+

Select Bundle Size:

+ + + + + + +
+ + +``` + +### Example 5: A/B Testing + +```javascript +// A/B test different pricing strategies +function initPricingExperiment() { + // Get or generate user variant + let variant = localStorage.getItem('pricing_variant'); + + if (!variant) { + // Randomly assign variant + variant = Math.random() < 0.5 ? 'control' : 'test'; + localStorage.setItem('pricing_variant', variant); + } + + // Apply corresponding profile + if (variant === 'test') { + window.next.setProfile('test_pricing'); + + // Track with analytics + window.next.trackCustomEvent('experiment_variant', { + experiment: 'pricing_test', + variant: 'test' + }); + } +} + +initPricingExperiment(); +``` + +## Advanced Usage + +### Profile Events + +Listen to profile change events: + +```javascript +// Listen for profile changes +window.next.on('profile:applied', (data) => { + console.log(`Profile changed to: ${data.profileId}`); + console.log(`Items swapped: ${data.itemsSwapped}`); + + // Update UI elements + updatePricingBadges(); + showProfileNotification(data.profileId); +}); + +// Listen for profile revert +window.next.on('profile:reverted', (data) => { + console.log(`Profile reverted, restored ${data.itemsRestored} items`); +}); +``` + +### Custom Profile Registration + +Register profiles dynamically at runtime: + +```javascript +// Register a new profile programmatically +window.next.registerProfile({ + id: 'custom_sale', + name: 'Custom Sale', + description: 'Dynamically created sale', + packageMappings: { + 1: 501, + 2: 502, + 3: 503 + } +}); + +// Now you can use it +await window.next.setProfile('custom_sale'); +``` + +### Profile Validation + +Check if mappings are valid before applying: + +```javascript +async function validateAndApplyProfile(profileId) { + const profile = window.next.getProfileInfo(profileId); + + if (!profile) { + console.error('Profile not found'); + return false; + } + + // Check if all mapped packages exist in campaign + const campaign = window.next.getCampaignData(); + const invalidMappings = []; + + for (const [original, mapped] of Object.entries(profile.packageMappings)) { + const packageExists = campaign.packages.some(p => p.ref_id === mapped); + if (!packageExists) { + invalidMappings.push({ original, mapped }); + } + } + + if (invalidMappings.length > 0) { + console.warn('Invalid mappings found:', invalidMappings); + // Decide whether to proceed + } + + await window.next.setProfile(profileId); + return true; +} +``` + +### Combining with Coupons + +Apply profiles and coupons together: + +```javascript +async function applySpecialOffer(offerCode) { + switch (offerCode) { + case 'BLACKFRIDAY': + await window.next.setProfile('black_friday'); + await window.next.applyCoupon('BF2024'); + break; + + case 'VIP_WELCOME': + await window.next.setProfile('vip'); + await window.next.applyCoupon('WELCOME10'); + break; + + case 'BUNDLE_DEAL': + await window.next.setProfile('3_pack'); + // No coupon needed, profile handles discount + break; + } +} +``` + +## Performance Considerations + +### Mapping Efficiency + +- **O(1) Lookups**: Profile mappings use hash maps for instant lookups +- **Reverse Mappings**: Automatically generated for bidirectional lookups +- **Caching**: Active profile cached in session storage +- **Lazy Loading**: Profile system only loads when needed + +### Best Practices + +1. **Predefine Profiles**: Configure all profiles at initialization +2. **Minimize Switches**: Avoid frequent profile changes +3. **Batch Operations**: Apply profiles before adding multiple items +4. **Use Default Profile**: Set a default to avoid null checks + +### Memory Management + +```javascript +// Profile data structure is optimized +{ + profiles: Map, // Efficient storage + activeProfileId: string | null, // Single reference + reverseMapping: Map, // Pre-computed + originalCartSnapshot: CartItem[] // Only when needed +} +``` + +## Troubleshooting + +### Common Issues + +#### Profile Not Applying + +```javascript +// Debug profile application +console.log('Active profile:', window.next.getActiveProfile()); +console.log('Available profiles:', window.next.listProfiles()); + +// Check if profile exists +if (!window.next.hasProfile('my_profile')) { + console.error('Profile not configured'); +} + +// Verify mappings +const profile = window.next.getProfileInfo('my_profile'); +console.log('Mappings:', profile.packageMappings); +``` + +#### Cart Items Not Updating + +```javascript +// Force cart recalculation after profile change +window.next.on('profile:applied', async () => { + // Trigger cart UI update + const cartStore = window.next.stores.cart.getState(); + await cartStore.calculateTotals(); + + // Emit update event + window.dispatchEvent(new CustomEvent('cart:updated')); +}); +``` + +#### Package Not Found After Mapping + +```javascript +// Validate mapped packages exist +async function checkMappedPackages(profileId) { + const profile = window.next.getProfileInfo(profileId); + const campaign = window.next.getCampaignData(); + + for (const [original, mapped] of Object.entries(profile.packageMappings)) { + const pkg = campaign.packages.find(p => p.ref_id === mapped); + if (!pkg) { + console.error(`Mapped package ${mapped} not found for original ${original}`); + } + } +} +``` + +### Debug Mode + +Enable debug mode to see profile operations: + +```javascript +// Enable debug mode +window.nextConfig.debug = true; + +// Or via URL +?debugger=true&profile=black_friday + +// Access debug utilities +window.nextDebug.attribution.debug(); +window.nextDebug.getStats(); +``` + +### Testing Profiles + +```javascript +// Test profile switching +async function testProfiles() { + const testCart = [ + { packageId: 1, quantity: 2 }, + { packageId: 2, quantity: 1 } + ]; + + // Clear and set up test cart + await window.next.clearCart(); + for (const item of testCart) { + await window.next.addItem(item); + } + + console.log('Original cart:', window.next.getCartData()); + + // Test each profile + for (const profileId of window.next.listProfiles()) { + await window.next.setProfile(profileId); + console.log(`Profile ${profileId}:`, window.next.getCartData()); + } + + // Revert to original + await window.next.revertProfile(); + console.log('Reverted cart:', window.next.getCartData()); +} +``` + +## Migration Guide + +### From Hardcoded Package IDs + +Before: +```javascript +// Old approach - hardcoded variants +if (isBlackFriday) { + addToCart(101); // Black Friday variant +} else { + addToCart(1); // Regular variant +} +``` + +After: +```javascript +// New approach - profile-based +if (isBlackFriday) { + await window.next.setProfile('black_friday'); +} +addToCart(1); // Always use base ID, profile handles mapping +``` + +### From Multiple Product Pages + +Before: +```html + + + + + +``` + +After: +```html + + + +``` + +## Summary + +The Profile System provides a powerful, flexible way to manage package variations without code duplication. By defining simple mappings, you can instantly switch between different pricing strategies, seasonal promotions, and customer tiers while maintaining a clean, maintainable codebase. + +Key takeaways: +- **Define once, use everywhere**: Profiles work across all SDK components +- **Zero frontend changes**: Existing package IDs automatically map +- **Performance optimized**: O(1) lookups with intelligent caching +- **Fully integrated**: Works with cart, checkout, analytics, and display systems +- **Developer friendly**: Simple API with comprehensive debugging tools \ No newline at end of file diff --git a/docs/campaign-cart/guides/quantity-package-swapper.md b/docs/campaign-cart/guides/quantity-package-swapper.md new file mode 100644 index 00000000..e6384b9e --- /dev/null +++ b/docs/campaign-cart/guides/quantity-package-swapper.md @@ -0,0 +1,348 @@ +# Quantity-Based Package Swapper (Standalone) + +A fully standalone script that handles quantity controls and package swapping. **No SDK selector enhancer needed** - this script manages everything directly. + +## Features + +- ✅ **Fully Standalone**: Handles its own quantity controls, no SDK selector enhancer +- ✅ **Zero Conflicts**: Doesn't interfere with SDK's PackageSelectorEnhancer +- ✅ **Automatic Sync**: Syncs selector from cart on page load +- ✅ **Flexible Config**: Easy quantity → package mapping +- ✅ **Multiple Selectors**: Support for multiple selectors on one page +- ✅ **Debug Logging**: Built-in debugging support +- ✅ **Zero Flicker**: Single cart update per action using `next.swapCart()` + +## Installation + +### Option 1: Inline Script + +```html + +``` + +### Option 2: Inline + +Copy the script content and paste it in a ` + + + + + + +``` + +## Notes + +- **Fully Standalone**: Uses custom `data-qty-*` attributes, zero conflict with SDK +- **No SDK Selector Enhancer**: No `data-next-cart-selector` needed +- **Display Attributes Work**: Keep `data-next-display` and `data-next-package-id` for SDK display system +- Requires SDK with `next.swapCart()` and `next.clearCart()` (for cart operations only) +- Handles its own quantity button click events +- Package swapping is atomic (single cart update via `swapCart`) +- Clears cart and resets to quantity 1 on page load +- No SDK modifications required diff --git a/docs/campaign-cart/guides/tier-selector-implementation.md b/docs/campaign-cart/guides/tier-selector-implementation.md new file mode 100644 index 00000000..64770d1a --- /dev/null +++ b/docs/campaign-cart/guides/tier-selector-implementation.md @@ -0,0 +1,450 @@ +# Tier Selector Implementation with Profiles + +This guide shows how to implement a pricing tier selector (Buy 1, Buy 2, Buy 3) using profiles combined with the variant system. + +## How It Works + +1. **Profiles** handle switching between pricing tiers (Buy 1 → Buy 2 → Buy 3) +2. **Variants** handle product options (Color, Size) +3. **Combined** they create a seamless tier + variant selection experience + +## Step 1: Configure Profiles for Each Tier + +```javascript +window.nextConfig = { + profiles: { + "buy_1": { + name: "Buy 1 - Regular Price", + description: "Single item purchase", + packageMappings: { + // Map default package IDs to Buy 1 tier packages + // Using a placeholder mapping approach + } + }, + "buy_2": { + name: "Buy 2 - Save 10%", + description: "Buy 2 and save", + packageMappings: { + // Chateau Ivory / Single + 19: 26, // Buy 1 (ref_id: 19) → Buy 2 (ref_id: 26) + // Obsidian Grey / Single + 17: 25, // Buy 1 (ref_id: 17) → Buy 2 (ref_id: 25) + // Add all other variant mappings... + } + }, + "buy_3": { + name: "Buy 3 - Save 20%", + description: "Best value - Buy 3", + packageMappings: { + // Map to Buy 3 tier packages + // (if they exist in your campaign) + } + } + }, + defaultProfile: "buy_1" // Start with Buy 1 tier +}; +``` + +## Step 2: Create Dynamic Profile Mappings + +Since you have many variants, generate the mappings dynamically: + +```javascript +// Helper function to build profile mappings dynamically +async function buildTierProfiles() { + const campaign = window.next.getCampaignData(); + if (!campaign) return; + + const profiles = { + buy_1: { name: "Buy 1", packageMappings: {} }, + buy_2: { name: "Buy 2", packageMappings: {} }, + buy_3: { name: "Buy 3", packageMappings: {} } + }; + + // Group packages by variant and tier + const variantGroups = {}; + + campaign.packages.forEach(pkg => { + // Create variant key from attributes + const variantKey = window.next.createVariantKey( + pkg.product_variant_attribute_values?.reduce((acc, attr) => { + acc[attr.code] = attr.value; + return acc; + }, {}) || {} + ); + + // Extract tier from package name + const tierMatch = pkg.name.match(/Buy (\d+)/i); + const tier = tierMatch ? `buy_${tierMatch[1]}` : 'buy_1'; + + // Store package by variant and tier + if (!variantGroups[variantKey]) { + variantGroups[variantKey] = {}; + } + variantGroups[variantKey][tier] = pkg.ref_id; + }); + + // Build mappings for each profile + Object.values(variantGroups).forEach(variant => { + if (variant.buy_1 && variant.buy_2) { + profiles.buy_2.packageMappings[variant.buy_1] = variant.buy_2; + } + if (variant.buy_1 && variant.buy_3) { + profiles.buy_3.packageMappings[variant.buy_1] = variant.buy_3; + } + }); + + // Register the profiles + Object.entries(profiles).forEach(([id, profile]) => { + window.next.registerProfile({ + id, + ...profile + }); + }); +} + +// Initialize on SDK ready +window.nextReady.push(buildTierProfiles); +``` + +## Step 3: HTML Implementation + +### Tier Selector UI + +```html + +
+

Select Quantity Tier:

+ + + + + + +
+ + + +``` + +### Variant Selector with Tier-Aware Pricing + +```html +
+ +
+ + + + + +
+ + +
+ +
+ Price: + $0.00 +
+ + +
+ Price (Buy 2): + $0.00 + Save 10%! +
+ + +
+ Price (Buy 3): + $0.00 + Save 20%! +
+
+ + +
+``` + +## Step 4: JavaScript Implementation + +```javascript +class TierVariantSelector { + constructor() { + this.productId = 3; // Grounded Sheets + this.selectedVariant = {}; + this.init(); + } + + async init() { + // Wait for SDK + await new Promise(resolve => window.nextReady.push(resolve)); + + // Build tier profiles + await this.buildTierProfiles(); + + // Set up event listeners + this.setupEventListeners(); + + // Initialize display + this.updateDisplay(); + } + + setupEventListeners() { + // Variant selectors + document.getElementById('color-select').addEventListener('change', (e) => { + this.selectedVariant.color = e.target.value; + this.updateDisplay(); + }); + + document.getElementById('size-select').addEventListener('change', (e) => { + this.selectedVariant.size = e.target.value; + this.updateDisplay(); + }); + + // Add to cart button + document.getElementById('add-to-cart').addEventListener('click', () => { + this.addToCart(); + }); + + // Listen for profile changes + window.next.on('profile:applied', () => { + this.updateDisplay(); + }); + } + + updateDisplay() { + // Check if variant is fully selected + if (!this.selectedVariant.color || !this.selectedVariant.size) { + document.getElementById('add-to-cart').disabled = true; + return; + } + + // Get current profile (tier) + const currentProfile = window.next.getActiveProfile() || 'buy_1'; + + // Get package for selected variant + const pkg = window.next.getPackageByVariantSelection( + this.productId, + this.selectedVariant + ); + + if (pkg) { + // The profile system will have already mapped to the correct tier package + // So pkg will be the correct package for the active tier + this.updatePriceDisplay(currentProfile, pkg.price); + document.getElementById('add-to-cart').disabled = false; + } + } + + updatePriceDisplay(tier, price) { + const priceElement = document.getElementById(`${tier.replace('_', '-')}-price`); + if (priceElement) { + priceElement.textContent = `$${price}`; + } + } + + async addToCart() { + const pkg = window.next.getPackageByVariantSelection( + this.productId, + this.selectedVariant + ); + + if (pkg) { + await window.next.addItem({ + packageId: pkg.ref_id, + quantity: 1 + }); + + // Show success message + this.showNotification('Added to cart!'); + } + } + + showNotification(message) { + // Implementation for showing notifications + console.log(message); + } + + async buildTierProfiles() { + // Use the implementation from Step 2 + // This builds profiles dynamically based on campaign data + } +} + +// Initialize on page load +document.addEventListener('DOMContentLoaded', () => { + new TierVariantSelector(); +}); +``` + +## Step 5: Advanced Implementation with Tier Comparison + +```html + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
QuantityPrice EachTotalSavings
Buy 1$0.00$0.00- + +
Buy 3$0.00$0.00Save 20% + +
+
+ + +``` + +## Key Benefits + +1. **Clean Separation**: Profiles handle tier switching, variants handle product options +2. **Automatic Mapping**: When user switches tiers, all cart items automatically update to new tier packages +3. **Session Persistence**: Selected tier persists across page refreshes +4. **URL Parameters**: Share tier-specific links: `?profile=buy_2` +5. **Easy Testing**: Switch between tiers without complex logic +6. **Cart Intelligence**: Items stay in cart when switching tiers + +## Best Practices + +1. **Default Tier**: Always set a default profile (usually "buy_1") +2. **Clear Naming**: Use consistent tier naming (buy_1, buy_2, etc.) +3. **Visual Feedback**: Show active tier clearly +4. **Price Comparison**: Display savings to encourage bulk purchases +5. **Mobile Friendly**: Ensure tier selector works on mobile + +## URL-Based Tier Selection + +Users can link directly to specific tiers: + +``` +https://yoursite.com/product?profile=buy_2 +https://yoursite.com/product?forceProfile=buy_3 // Clears cart first +``` + +## CSS Styling + +```css +.tier-selector { + display: flex; + gap: 1rem; + margin-bottom: 2rem; +} + +.tier-button { + flex: 1; + padding: 1rem; + border: 2px solid #ddd; + background: white; + cursor: pointer; + transition: all 0.3s; +} + +.tier-button:hover { + border-color: #007bff; +} + +.tier-button.next-profile-active { + background: #007bff; + color: white; + border-color: #007bff; +} + +.tier-comparison .recommended { + background: #f0f8ff; +} + +.tier-comparison .best-value { + background: #f0fff0; + font-weight: bold; +} +``` + +This approach leverages the profile system perfectly for tier-based pricing while maintaining clean, maintainable code. \ No newline at end of file diff --git a/docs/campaign-cart/index.md b/docs/campaign-cart/index.md new file mode 100644 index 00000000..ce801dd7 --- /dev/null +++ b/docs/campaign-cart/index.md @@ -0,0 +1,331 @@ +--- +sidebar_label: Campaign Cart +sidebar_position: 1 +--- + +# Campaign Cart JS SDK + +Welcome to the Next Commerce JS SDK documentation. This SDK enables developers to create e-commerce storefront experiences using HTML attributes, JavaScript, and CSS. + +## Quick Start + +### Option 1: Use Starter Template (Recommended) + +Get started quickly with our pre-configured starter template: + +**[🚀 Download Starter Template](https://github.com/29next/campaign-cart-example)** - Clone or download a ready-to-use campaign flow (landing --> checkout --> upsell --> receipt) with Campaign Cart integrated. + +### Option 2: Manual Setup + +1. **Get Your API Key** + + Go to Next Commerce Dashboard, open the Campaigns app, select your campaign, click on Integration, and copy your API key. + +2. **Add SDK Script** + + Add these two lines to your HTML `` section: + + ```html + + + + + + + ``` + + :::tip Latest Version + Check the [GitHub releases](https://github.com/NextCommerceCo/campaign-cart/releases) for the latest stable version. For development, you can use `@latest`, but we recommend using a specific version (e.g., `@v0.3.10`) in production for stability. + ::: + +3. **Start Building** + + You can now use Campaign Cart attributes in your HTML! + +## Configuration + +:::tip +Replace `your-api-key-here` with your actual Campaign API key from the dashboard. +::: + +### JavaScript Configuration + +For more advanced configuration: + +#### Minimal Setup (Getting Started) + +```javascript +// Configure before SDK loads +window.dataLayer = window.dataLayer || []; +window.nextReady = window.nextReady || []; + +window.nextConfig = { + apiKey: "your-api-key-here" +}; +``` + +#### Complete Example with Common Options + +```javascript +// Configure before SDK loads +window.dataLayer = window.dataLayer || []; +window.nextReady = window.nextReady || []; + +window.nextConfig = { + // Required: Your Campaign Cart API key + apiKey: "your-api-key-here", + + // Currency behavior when country changes + currencyBehavior: 'auto', // 'auto' | 'manual' + + // Payment and checkout configuration + paymentConfig: { + expressCheckout: { + enabled: true, // Enable/disable express checkout methods + requireValidation: false, // Require form validation before express checkout if radio option - not express buttons + requiredFields: ['email', 'fname', 'lname'], // Fields required for express checkout radio option + methodOrder: ['paypal', 'apple_pay', 'google_pay'] // Display order of express payment method buttons + } + }, + + // Address and country configuration + addressConfig: { + defaultCountry: "US", + showCountries: ["US", "CA", "GB", "AU", "DE", "FR"], + dontShowStates: ["AS", "GU", "PR", "VI"] + }, + + // Discount codes configuration + discounts: { + // Example discount code + // SAVE10: { + // code: "SAVE10", + // type: "percentage", // 'percentage' | 'fixed' + // value: 10, + // scope: "order", // 'package' | 'order' + // description: "10% off entire order", + // combinable: true, // Can be combined with other discounts + // // Optional: packageIds: [1, 2], // For package-specific discounts + // // Optional: minOrderValue: 50, // Minimum order value + // // Optional: maxDiscount: 20 // Maximum discount amount + // } + }, + + profiles: { + // "regular": { + // name: "Regular Pricing", + // // No mappings needed - uses original package IDs + // }, + + // Example: Exit intent save profile + // "SAVE_5": { + // name: "Exit Save 5", + // packageMappings: { + // // Original ID -> EXIT PACKAGE ID + // 1: 9, + // 2: 10, + // 3: 11, + // 4: 12, + // 5: 13, + // } + // }, + }, + + // Default profile to use (if not specified, uses "regular") + defaultProfile: "regular", + + // Google Maps integration (for address autocomplete) + googleMaps: { + apiKey: "your-google-maps-api-key", + region: "US", + enableAutocomplete: true + }, + + // Analytics providers configuration + analytics: { + enabled: true, + mode: 'auto', // 'auto' | 'manual' | 'disabled' + providers: { + // Next Campaign analytics (always enabled if analytics.enabled is true) + nextCampaign: { + enabled: true + }, + // Google Tag Manager + gtm: { + enabled: false, + settings: { + containerId: "GTM-XXXXXX", + dataLayerName: "dataLayer" + }, + // Optional: blockedEvents: ["PageView"] + }, + // Facebook Pixel + facebook: { + enabled: false, + settings: { + pixelId: "YOUR_PIXEL_ID" + }, + // Optional: blockedEvents: ["PageView"] + }, + // RudderStack + rudderstack: { + enabled: false, + settings: { + // RudderStack configuration is handled by the RudderStack SDK itself + // This just enables the adapter + }, + // Optional: blockedEvents: ["PageView"] + }, + // Custom analytics endpoint + custom: { + enabled: false, + settings: { + endpoint: "https://your-analytics.com/track", + apiKey: "your-api-key" + } + } + } + }, + + // UTM parameter transfer (preserve tracking params) + utmTransfer: { + enabled: true, + applyToExternalLinks: false, // Add UTM params to external links + debug: false, // Enable debug logging for UTM transfer + // Optional: excludedDomains: ['example.com', 'test.org'], // Domains to exclude + // Optional: paramsToCopy: ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term', 'gclid', 'fbclid'] + } +}; +``` + +### Meta Tag Configuration + +Configure the SDK using meta tags in your HTML head: + +```html + + + + + + + + + + + + + + + + + + +``` + +## HTML Setup Example +```html + + + + + + + + + + + + + + + + + + + + + + +``` + +## Features + +- **Attribute-driven architecture** - Build cart functionality with HTML attributes +- **Cart management** - Add to cart, selectors, quantity controls +- **Profile-based pricing** - Dynamic package mapping for different pricing tiers +- **Post-purchase upsells** - Maximize order value with upsell flows +- **Dynamic content** - Display prices, totals, and product data +- **Conversion tools** - FOMO notifications and exit intent popups + +## Quick Examples + +### Add to Cart Button +```html + +``` + +### Product Selector +```html +
+
Option 1
+
Option 2
+
+``` + +### Display Cart Total +```html +$0.00 +``` + +### Conditional Display +```html +
+ +
+``` + +## Documentation Sections + +### Core Features + +- **[Cart System](/docs/campaign-cart/cart-system/)** - Cart management and controls +- **[Upsells](/docs/campaign-cart/upsells/)** - Post-purchase upsell flows +- **[JavaScript API](/docs/campaign-cart/javascript-api/)** - Complete JavaScript methods reference +- **[Data Attributes](/docs/campaign-cart/data-attributes/)** - Complete attribute reference +- **[Utilities](/docs/campaign-cart/utilities/)** - FOMO, exit intent, and debugging tools + +### Reference + +- **[Events](/docs/campaign-cart/javascript-api/events)** - SDK event system +- **[Analytics Configuration](/docs/campaign-cart/analytics/configuration/)** - Analytics configuration options + +## Verification + +Verify the SDK loaded correctly by opening your browser console: + +```javascript +// Check if SDK is loaded +console.log(window.next ? 'SDK Loaded' : 'SDK Not Found'); + +// Check SDK version +if (window.next) { + console.log('SDK Version:', next.version); + console.log('Config:', next.getConfig()); +} +``` + +## Browser Support + +The SDK supports all modern browsers including Chrome, Firefox, Safari, and Edge. diff --git a/docs/campaign-cart/javascript-api/attribution.md b/docs/campaign-cart/javascript-api/attribution.md new file mode 100644 index 00000000..402f2212 --- /dev/null +++ b/docs/campaign-cart/javascript-api/attribution.md @@ -0,0 +1,784 @@ +--- +title: Attribution API +sidebar_position: 4 +--- + +# Attribution API + +The SDK provides comprehensive attribution tracking capabilities, including support for custom metadata that can be passed with orders. + +## Simple API via window.next + +The easiest way to manage attribution metadata is through the `window.next` object: + +```javascript +// Add or update a single metadata field +window.next.addMetadata('campaign_id', 'SUMMER2025'); +window.next.addMetadata('influencer_code', 'JANE123'); + +// Set multiple metadata fields at once +window.next.setMetadata({ + campaign_id: 'SUMMER2025', + influencer_code: 'JANE123', + test_variant: 'hero_b', + source_platform: 'instagram' +}); + +// Get current metadata +const metadata = window.next.getMetadata(); +console.log(metadata); + +// Clear all custom metadata (preserves automatic fields) +window.next.clearMetadata(); + +// Set complete attribution data (including standard fields) +window.next.setAttribution({ + utm_source: 'instagram', + utm_campaign: 'summer2025', + affiliate: 'partner123', + metadata: { + custom_field: 'value' + } +}); + +// Get current attribution that will be sent with orders +const attribution = window.next.getAttribution(); +console.log(attribution); + +// Debug attribution data in console +window.next.debugAttribution(); +``` + +## Attribution Object Structure + +When orders are created, the SDK sends attribution data in the following format: + +```typescript +interface Attribution { + affiliate?: string; + funnel?: string; + gclid?: string; + metadata?: Record; // Custom metadata object + subaffiliate1?: string; + subaffiliate2?: string; + subaffiliate3?: string; + subaffiliate4?: string; + subaffiliate5?: string; + utm_campaign?: string; + utm_content?: string; + utm_medium?: string; + utm_source?: string; + utm_term?: string; + everflow_transaction_id?: string; +} +``` + +## Accessing Attribution Store + +The attribution store is available globally after SDK initialization: + +```javascript +// Access via window object +window.NextAttributionStore + +// Or via NextStores +const { useAttributionStore } = window.NextStores; +const attributionStore = useAttributionStore.getState(); +``` + +## Methods + +### updateAttribution(data) + +Updates attribution data including custom metadata. + +```javascript +window.NextAttributionStore.updateAttribution({ + affiliate: 'partner_123', + utm_source: 'instagram', + metadata: { + custom_field: 'value', + tracking_id: 'abc123' + } +}); +``` + +**Parameters:** +- `data` (Partial<AttributionState>): Object containing attribution fields to update + +### getAttributionForApi() + +Returns the current attribution data formatted for API submission. + +```javascript +const attribution = window.NextAttributionStore.getAttributionForApi(); +console.log(attribution); +// Output: { affiliate: '...', metadata: {...}, utm_source: '...', ... } +``` + +**Returns:** Attribution object ready for API + +### setFunnelName(funnel) + +Sets the funnel name for attribution tracking. Once set, it persists and cannot be overwritten. + +```javascript +window.NextAttributionStore.setFunnelName('summer_sale_2025'); +``` + +**Parameters:** +- `funnel` (string): The funnel identifier + +### setEverflowClickId(evclid) + +Sets the Everflow click ID for tracking. + +```javascript +window.NextAttributionStore.setEverflowClickId('ef_click_12345'); +``` + +**Parameters:** +- `evclid` (string): Everflow click identifier + +### debug() + +Outputs detailed attribution information to the console for debugging. + +```javascript +window.NextAttributionStore.debug(); +// Logs comprehensive attribution data to console +``` + +### clearPersistedFunnel() + +Clears the persisted funnel name from storage. + +```javascript +window.NextAttributionStore.clearPersistedFunnel(); +``` + +## Custom Metadata + +The `metadata` field accepts any custom properties you need to track with orders. + +### Automatic Metadata Collection + +The SDK automatically collects these metadata fields: + +```javascript +{ + landing_page: string; // Entry page URL + referrer: string; // Referring URL + device: string; // Device info + device_type: 'mobile' | 'desktop'; + domain: string; // Current domain + timestamp: number; // Visit timestamp + conversion_timestamp?: number; + + // Facebook tracking (when available) + fb_fbp?: string; + fb_fbc?: string; + fb_pixel_id?: string; + fbclid?: string; + + // Everflow tracking (when available) + everflow_transaction_id?: string; + sg_evclid?: string; +} +``` + +### Adding Custom Metadata + +Add any custom tracking data your business requires: + +```javascript +window.NextAttributionStore.updateAttribution({ + metadata: { + // Marketing campaign data + campaign_id: 'SUMMER2025', + campaign_version: 'v2', + creative_id: 'hero_banner_b', + + // Partner/Influencer tracking + influencer_code: 'JANE123', + partner_id: 'partner_456', + commission_tier: 'gold', + + // Platform-specific IDs + tiktok_click_id: 'ttc_abc123', + pinterest_id: 'pin_xyz789', + snapchat_id: 'snap_456', + reddit_campaign: 'sponsored_post_789', + + // A/B testing + test_group: 'variant_b', + test_id: 'homepage_hero_test', + + // Geographic/Store data + store_location: 'NYC_flagship', + region: 'northeast', + + // Custom business logic + customer_segment: 'vip', + loyalty_tier: 'platinum', + referral_program_id: 'friend_2025', + + // Nested objects are supported + advanced_tracking: { + session_id: 'sess_xyz', + interaction_count: 5, + time_on_site: 300 + } + } +}); +``` + +## Examples + +### Example 1: E-commerce Platform Integration + +```javascript +// Track marketplace attribution +function trackMarketplaceSource(marketplace, sellerId, listingId) { + window.NextAttributionStore.updateAttribution({ + utm_source: marketplace, + utm_medium: 'marketplace', + affiliate: sellerId, + metadata: { + marketplace_name: marketplace, + seller_id: sellerId, + listing_id: listingId, + marketplace_category: 'electronics', + marketplace_rank: 5, + fulfilled_by: 'merchant', + prime_eligible: true, + promotion_type: 'lightning_deal' + } + }); +} + +// Usage +trackMarketplaceSource('amazon', 'SELLER123', 'B08XYZ123'); +``` + +### Example 2: Multi-Touch Attribution + +```javascript +// Track multiple touchpoints in customer journey +function trackTouchpoint(touchpointData) { + const currentMetadata = window.NextAttributionStore.getAttributionForApi().metadata || {}; + + // Append to touchpoint history + const touchpoints = currentMetadata.touchpoints || []; + touchpoints.push({ + timestamp: Date.now(), + channel: touchpointData.channel, + action: touchpointData.action, + value: touchpointData.value + }); + + window.NextAttributionStore.updateAttribution({ + metadata: { + ...currentMetadata, + touchpoints: touchpoints, + last_touchpoint: touchpointData.channel, + touchpoint_count: touchpoints.length, + attribution_model: 'linear' // or 'first_touch', 'last_touch', etc. + } + }); +} + +// Track various touchpoints +trackTouchpoint({ channel: 'email', action: 'opened', value: 'welcome_series' }); +trackTouchpoint({ channel: 'social', action: 'clicked', value: 'instagram_story' }); +trackTouchpoint({ channel: 'search', action: 'clicked', value: 'brand_term' }); +``` + +### Example 3: Retail & Offline Attribution + +```javascript +// Bridge offline and online attribution +function trackOfflineAttribution(storeData) { + window.NextAttributionStore.updateAttribution({ + utm_source: 'retail', + utm_medium: 'in_store', + utm_campaign: storeData.campaign || 'store_visit', + metadata: { + // Store information + store_id: storeData.storeId, + store_name: storeData.storeName, + store_region: storeData.region, + store_type: storeData.type, // 'flagship', 'outlet', 'pop_up' + + // Staff attribution + associate_id: storeData.associateId, + associate_name: storeData.associateName, + department: storeData.department, + + // In-store journey + kiosk_used: storeData.kioskUsed, + qr_scanned: storeData.qrScanned, + fitting_room_tech: storeData.fittingRoomTech, + + // Transaction bridging + in_store_cart_id: storeData.cartId, + pos_system: 'square', + store_promo_code: storeData.promoCode, + + // Customer experience + appointment_booked: storeData.appointmentBooked, + personal_shopper: storeData.personalShopper, + curbside_pickup: false, + + // Timing + store_visit_timestamp: storeData.visitTime, + offline_to_online_hours: storeData.hoursSinceVisit + } + }); +} +``` + +### Example 4: Content Attribution + +```javascript +// Track content-driven conversions +function trackContentAttribution(contentData) { + window.NextAttributionStore.updateAttribution({ + utm_source: contentData.platform, + utm_medium: 'content', + utm_campaign: contentData.campaign, + utm_content: contentData.contentId, + metadata: { + // Content details + content_type: contentData.type, // 'blog', 'video', 'podcast', 'webinar' + content_id: contentData.contentId, + content_title: contentData.title, + content_author: contentData.author, + content_category: contentData.category, + publish_date: contentData.publishDate, + + // Engagement metrics + read_time_seconds: contentData.readTime, + scroll_depth: contentData.scrollDepth, + video_watch_percentage: contentData.watchPercentage, + + // Content journey + content_path: contentData.contentPath, // ['blog_1', 'video_2', 'product_page'] + content_touches: contentData.touches, + + // SEO/Discovery + entry_keyword: contentData.keyword, + discovery_method: contentData.discovery, // 'search', 'recommendation', 'social' + + // Monetization + paywall_shown: contentData.paywallShown, + subscription_prompt: contentData.subscriptionPrompt, + native_ad_clicked: contentData.nativeAdClicked + } + }); +} +``` + +## Attribution URL Parameters + +The SDK automatically captures attribution data from URL parameters to track marketing campaigns, affiliate referrals, and conversion sources. This data is persisted throughout the user's session and included with all orders. + +### Overview + +Attribution parameters are: +- **Automatically captured** from URL query strings when the SDK initializes +- **Persisted** in sessionStorage throughout the user's session +- **URL parameters always override** any existing persisted values +- **Included automatically** in checkout and order API calls +- **Cross-page persistent** - navigate anywhere and attribution is preserved + +### Core Attribution Parameters + +#### Funnel + +Identifies the marketing funnel or campaign flow. + +**Parameter:** `funnel` + +**Example:** +``` +https://yoursite.com/checkout?funnel=summer-sale-2024 +``` + +**Usage:** +- Track different marketing funnels +- Identify which landing page or campaign the user came from +- Segment orders by funnel for reporting + +**Behavior:** +- Can also be set via meta tags (URL parameter takes priority) +- Once set, persists across all pages in the session +- URL parameter will override any existing funnel value +- Also accepts `next.setAttribution({ funnel: 'funnel-name' })` programmatically + +#### Affiliate ID + +Tracks affiliate or partner referrals. + +**Parameters:** `affid` or `aff` (both accepted) + +**Example:** +``` +https://yoursite.com/product?affid=partner123 +https://yoursite.com/product?aff=affiliate-xyz +``` + +**Usage:** +- Track affiliate commissions +- Attribute sales to specific partners +- Segment traffic by referral source + +#### Google Click ID (GCLID) + +Google Ads click identifier for conversion tracking. + +**Parameter:** `gclid` + +**Example:** +``` +https://yoursite.com/landing?gclid=EAIaIQobChMI7... +``` + +**Usage:** +- Google Ads conversion tracking +- Automatically captured from Google Ads campaigns +- Required for Google Ads conversion API + +### UTM Parameters + +Standard UTM tracking parameters for campaign analytics. + +| Parameter | Description | Example | +|-----------|-------------|---------| +| `utm_source` | Identifies the source of traffic | `?utm_source=facebook` | +| `utm_medium` | Identifies the marketing medium | `?utm_medium=cpc` | +| `utm_campaign` | Identifies the specific campaign | `?utm_campaign=summer-sale-2024` | +| `utm_content` | Identifies specific ad or link content | `?utm_content=banner-ad-blue` | +| `utm_term` | Identifies paid search keywords | `?utm_term=weight+loss+supplement` | + +**Complete UTM Example:** +``` +https://yoursite.com/product?utm_source=facebook&utm_medium=cpc&utm_campaign=summer-2024&utm_content=carousel-ad-1&utm_term=fitness +``` + +### Subaffiliate Tracking + +Track up to 5 levels of sub-affiliate data for multi-tier affiliate programs. + +**Parameters:** `subaffiliate1-5` or `sub1-5` (both formats accepted) + +**Character Limit:** Each subaffiliate value is limited to 225 characters (automatically truncated with a warning) + +**Examples:** +``` +https://yoursite.com/product?subaffiliate1=region-west&subaffiliate2=agent-123 +https://yoursite.com/product?sub1=tier1&sub2=tier2&sub3=tier3 +``` + +**Usage:** +- Multi-level marketing (MLM) tracking +- Regional affiliate tracking +- Sales team commission tracking +- Hierarchical partner structures + +**Subaffiliate Levels:** +- `subaffiliate1` / `sub1` - Primary sub-affiliate +- `subaffiliate2` / `sub2` - Secondary level +- `subaffiliate3` / `sub3` - Tertiary level +- `subaffiliate4` / `sub4` - Fourth level +- `subaffiliate5` / `sub5` - Fifth level + +### Click Tracking Parameters + +#### Facebook Click ID (FBCLID) + +Facebook Ads click identifier for conversion tracking. + +**Parameter:** `fbclid` + +**Example:** +``` +https://yoursite.com/landing?fbclid=IwAR1X... +``` + +**Usage:** +- Facebook Ads conversion tracking +- Automatically appended by Facebook to ad links +- Stored in attribution metadata + +#### Everflow Click ID (EVCLID) + +Everflow affiliate network tracking identifier. + +**Parameter:** `evclid` + +**Example:** +``` +https://yoursite.com/offer?evclid=123456789 +``` + +**Usage:** +- Everflow affiliate network tracking +- Performance marketing attribution +- Stored as `everflow_transaction_id` in metadata + +#### SG Everflow Click ID + +Secondary Everflow tracking identifier (SG variant). + +**Parameter:** `sg_evclid` + +**Example:** +``` +https://yoursite.com/offer?sg_evclid=987654321 +``` + +**Usage:** +- Alternate Everflow tracking +- Multi-channel Everflow campaigns + +#### Generic Click ID + +Generic click tracking for various platforms. + +**Parameter:** `clickid` + +**Example:** +``` +https://yoursite.com/product?clickid=abc123xyz +``` + +**Usage:** +- Custom tracking platforms +- Generic click tracking systems +- Platform-agnostic click attribution + +### How Attribution Works + +#### 1. Automatic Capture + +When a user visits any page with attribution parameters: + +``` +https://yoursite.com/landing?funnel=summer-sale&affid=partner123&utm_source=facebook +``` + +The SDK automatically: +- Captures all attribution parameters +- Stores them in sessionStorage +- Logs the capture event (visible with debug mode) + +#### 2. Session Persistence + +Once captured, attribution data persists throughout the session: + +``` +Page 1: /landing?funnel=summer-sale&affid=partner123 +Page 2: /products (no params) +Page 3: /checkout (no params) +``` + +All attribution data from Page 1 remains available on Pages 2 and 3. + +#### 3. Parameter Override + +URL parameters **always override** existing persisted values: + +``` +Visit 1: ?funnel=spring-sale + → Sets funnel to "spring-sale" + +Visit 2: ?funnel=summer-sale + → Overrides funnel to "summer-sale" + → Logs: "🔄 Funnel override: 'spring-sale' -> 'summer-sale'" +``` + +#### 4. Order Inclusion + +All captured attribution data is automatically included in: +- Cart creation API calls +- Checkout submission +- Order completion +- Upsell additions + +### Common Use Cases + +#### Affiliate Marketing Campaign +``` +https://yoursite.com/offer?affid=partner123&subaffiliate1=region-west&subaffiliate2=agent-456 +``` + +Tracks: +- Affiliate partner +- Region/territory +- Individual sales agent + +#### Facebook Ad Campaign +``` +https://yoursite.com/product?funnel=fb-summer-campaign&utm_source=facebook&utm_medium=cpc&utm_campaign=summer-2024&fbclid=IwAR1X... +``` + +Tracks: +- Campaign funnel +- UTM parameters for analytics +- Facebook click ID for conversion tracking + +#### Google Ads Campaign +``` +https://yoursite.com/landing?funnel=google-search&utm_source=google&utm_medium=cpc&utm_campaign=brand-terms&gclid=EAIaIQobChMI7... +``` + +Tracks: +- Search campaign funnel +- UTM parameters +- Google click ID for conversion API + +#### Email Marketing +``` +https://yoursite.com/exclusive?funnel=vip-email&utm_source=newsletter&utm_medium=email&utm_campaign=monthly-2024-06&utm_content=hero-cta +``` + +Tracks: +- Email funnel +- Newsletter source +- Specific email campaign +- Which CTA was clicked + +#### Multi-Tier Affiliate Program +``` +https://yoursite.com/product?affid=network1&sub1=master-affiliate&sub2=sub-affiliate&sub3=sales-rep&sub4=region&sub5=channel +``` + +Tracks complete affiliate hierarchy for commission distribution. + +### Meta Tag Support + +Attribution can also be set via meta tags (URL parameters take priority): + +```html + + + + + +``` + +**Priority Order:** +1. URL parameters (highest priority, always override) +2. Persisted sessionStorage values +3. Meta tags (lowest priority) + +### Attribution URL Parameters Best Practices + +1. **Use Consistent Naming** + ```javascript + // Good - descriptive and consistent + ?funnel=summer-sale-2024&affid=partner-network-west + + // Avoid - unclear abbreviations + ?f=ss24&a=pnw + ``` + +2. **Combine Parameters Logically** + ```javascript + // Track complete campaign attribution + ?funnel=fb-campaign&affid=social-partner&utm_source=facebook&utm_medium=cpc&utm_campaign=summer-2024 + ``` + +3. **Document Your Funnels** + Maintain a list of active funnels and their purposes: + - `summer-sale-2024` - Summer promotional campaign + - `vip-exclusive` - VIP member offers + - `affiliate-special` - Affiliate partner promotions + +4. **Test Attribution Capture** + ```javascript + // In console, verify attribution was captured + next.debugAttribution(); + + // Check specific fields + const attribution = next.getAttribution(); + console.log('Funnel:', attribution.funnel); + console.log('Affiliate:', attribution.affiliate); + ``` + +5. **Monitor Attribution Data** + Use your backend analytics to: + - Track conversion by funnel + - Calculate affiliate ROI + - Optimize campaign performance + - Identify top-performing sources + +### Important Notes + +**Character Limits:** +- **Subaffiliate values**: Limited to 225 characters each +- Values exceeding this limit are automatically truncated with a warning + +**Persistence:** +- Attribution data persists in **sessionStorage** (cleared when browser closes) +- Some values (funnel, evclid) also persist in **localStorage** (survives browser restart) + +**Security:** +- Attribution parameters are visible in URLs +- Do not use attribution parameters for sensitive data +- Validate all attribution data server-side + +**API Integration:** +- Attribution data is automatically included in all order-related API calls +- No manual intervention required +- Data appears in order records for reporting + +## Session Persistence + +Attribution data persists throughout the user's session: + +- **Session Storage**: Primary attribution data +- **Local Storage**: Funnel names and Everflow IDs +- **Duration**: Cleared when browser session ends + +## Best Practices + +1. **Initialize Early**: Set attribution data as soon as possible in the user journey +2. **Avoid PII**: Never include personally identifiable information (emails, names, etc.) +3. **Use Consistent Keys**: Establish naming conventions for metadata fields +4. **Validate Data**: Ensure metadata values are valid JSON types +5. **Document Fields**: Maintain documentation of custom metadata fields +6. **Test Integration**: Verify attribution data appears correctly in orders + +## Debugging + +```javascript +// Check current attribution state +window.NextAttributionStore.debug(); + +// Monitor attribution changes +const store = window.NextStores.useAttributionStore.getState(); +const unsubscribe = window.NextStores.useAttributionStore.subscribe( + (state) => console.log('Attribution updated:', state) +); + +// View attribution that will be sent with order +console.log('API Attribution:', window.NextAttributionStore.getAttributionForApi()); +``` + +## Common Issues + +### Issue: Metadata not appearing in orders +**Solution**: Ensure you're calling `updateAttribution()` before order creation and that the metadata object contains valid JSON values. + +### Issue: Funnel name not updating +**Solution**: Funnel names are write-once. Use `clearPersistedFunnel()` to reset if needed during development. + +### Issue: Attribution data lost on page refresh +**Solution**: Attribution persists in session storage. If you need longer persistence, implement your own storage solution and restore on page load. + +## See Also + +- [URL Parameters API](/docs/campaign-cart/javascript-api/url-parameters/) - General URL parameter management +- [Data Attributes - URL Parameters](/docs/campaign-cart/data-attributes/url-parameters/) - Using parameters with HTML attributes \ No newline at end of file diff --git a/docs/campaign-cart/javascript-api/events.md b/docs/campaign-cart/javascript-api/events.md new file mode 100644 index 00000000..fa3d734c --- /dev/null +++ b/docs/campaign-cart/javascript-api/events.md @@ -0,0 +1,769 @@ +--- +title: Events +sidebar_position: 3 +--- + +# Events + +The Next Commerce JS SDK emits events that you can listen to for tracking and custom functionality. The SDK has 34 internal events that can be listened to via `next.on()`. + +## Event Categories + +- **Cart Events** - Track cart state changes +- **Checkout Events** - Monitor checkout process +- **Payment Events** - Handle payment states +- **Order Events** - Track order completion +- **Campaign Events** - Campaign data loading +- **Coupon Events** - Discount code usage +- **Upsell Events** - Post-purchase upsell tracking +- **User Events** - User authentication +- **Behavioral Events** - FOMO, exit intent +- **Profile Events** - Profile management and switching +- **System Events** - SDK state, errors, routing + +## SDK Initialization + +### Initialization Callbacks (window.nextReady) + +The `window.nextReady` queue ensures your code runs after the SDK is fully initialized. + +```javascript +// Queue a callback before SDK loads +window.nextReady = window.nextReady || []; +window.nextReady.push(function() { + // SDK is ready - window.next is available + console.log('Cart count:', next.getCartCount()); + next.addItem({ packageId: 123 }); +}); +``` + +**Callback Patterns:** + +```javascript +// Pattern 1: Without SDK Parameter (Recommended) +window.nextReady.push(function() { + // Access SDK via global window.next + next.addItem({ packageId: 123 }); +}); + +// Pattern 2: With SDK Parameter +window.nextReady.push(function(sdk) { + // SDK passed as parameter + sdk.addItem({ packageId: 123 }); +}); +``` + +**Multiple Callbacks:** + +```javascript +// Queue multiple callbacks +window.nextReady.push(function() { + next.trackViewItemList(['1', '2', '3']); +}); + +window.nextReady.push(function() { + next.on('cart:updated', function(data) { + console.log('Cart updated:', data); + }); +}); +``` + +### `next:initialized` (DOM Event) + +Fired when SDK is fully loaded and ready (DOM event): + +```javascript +// Listen on document for initialization +document.addEventListener('next:initialized', function(event) { + console.log('SDK is ready at:', event.detail.timestamp); + console.log('Version:', event.detail.version); + // Safe to use SDK methods here +}); +``` + +## Cart Events + +### `cart:updated` + +Fired whenever cart contents change (most common event): + +```javascript +next.on('cart:updated', (cartState) => { + console.log('Cart updated:', cartState); + // cartState includes: items, totals, enrichedItems, etc. + console.log('Total items:', cartState.totalQuantity); + console.log('Cart total:', cartState.totals.total.formatted); +}); +``` + +### `cart:item-added` + +Fired when item is added to cart: + +```javascript +next.on('cart:item-added', (data) => { + console.log('Item added:', data); + // data includes: packageId, quantity, item details +}); +``` + +### `cart:item-removed` + +Fired when item is removed from cart: + +```javascript +next.on('cart:item-removed', (data) => { + console.log('Item removed:', data); + // data includes: packageId, item details that were removed +}); +``` + +### `cart:quantity-changed` + +Fired when item quantity is updated: + +```javascript +next.on('cart:quantity-changed', (data) => { + console.log('Quantity changed:', data); + // data includes: packageId, oldQuantity, newQuantity +}); +``` + +### `cart:package-swapped` + +Fired when a package is swapped for another: + +```javascript +next.on('cart:package-swapped', (data) => { + console.log('Package swapped:', data); + // data includes: previousPackageId, newPackageId, previousItem, newItem, priceDifference, source +}); +``` + +## Checkout & Order Events + +### `checkout:started` + +Fired when checkout process begins: + +```javascript +next.on('checkout:started', (data) => { + console.log('Checkout started:', data); + // data includes: cart items, totals +}); +``` + +### `checkout:form-initialized` + +Fired when checkout form is ready: + +```javascript +next.on('checkout:form-initialized', () => { + console.log('Checkout form ready'); + // Form is now initialized and ready for input +}); +``` + +### `order:completed` + +Fired when order is successfully completed: + +```javascript +next.on('order:completed', (order) => { + console.log('Order completed:', order); + // order includes: ref_id, total, lines, customer info +}); +``` + +## Payment Events + +### `payment:tokenized` + +Fired when payment is successfully tokenized: + +```javascript +next.on('payment:tokenized', (data) => { + console.log('Payment tokenized:', data); + // data includes: token info, payment method +}); +``` + +### `payment:error` + +Fired when payment processing fails: + +```javascript +next.on('payment:error', (error) => { + console.error('Payment failed:', error); + // error includes: message, code, details +}); +``` + +### `express-checkout:started` + +Fired when express checkout (PayPal, Apple Pay, etc.) begins: + +```javascript +next.on('express-checkout:started', (data) => { + console.log('Express checkout started:', data); + // data includes: method type (paypal, apple, google) +}); +``` + +## Campaign & Configuration Events + +### `campaign:loaded` + +Fired when campaign data is loaded: + +```javascript +next.on('campaign:loaded', (campaign) => { + console.log('Campaign loaded:', campaign); + // campaign includes: packages, settings, currency, etc. +}); +``` + +### `config:updated` + +Fired when SDK configuration changes: + +```javascript +next.on('config:updated', (config) => { + console.log('Config updated:', config); + // config includes: all SDK settings +}); +``` + +## Coupon Events + +### `coupon:applied` + +Fired when coupon is successfully applied: + +```javascript +next.on('coupon:applied', (coupon) => { + console.log('Coupon applied:', coupon); + // coupon includes: code, discount amount, type +}); +``` + +### `coupon:removed` + +Fired when coupon is removed: + +```javascript +next.on('coupon:removed', (code) => { + console.log('Coupon removed:', code); + // code is the removed coupon code string +}); +``` + +## Upsell Events + +### `upsell:viewed` + +Fired when upsell offer is displayed: + +```javascript +next.on('upsell:viewed', (data) => { + console.log('Upsell viewed:', data); + // data includes: packageId, offer details +}); +``` + +### `upsell:accepted` + +Fired when user accepts an upsell: + +```javascript +next.on('upsell:accepted', (data) => { + console.log('Upsell accepted:', data); + // data includes: packageId, quantity, value +}); +``` + +### `upsell:added` + +Fired when upsell is successfully added to order: + +```javascript +next.on('upsell:added', (data) => { + console.log('Upsell added:', data); + // data includes: packageId, order, value +}); +``` + +### `upsell:skipped` + +Fired when user skips/declines an upsell: + +```javascript +next.on('upsell:skipped', (data) => { + console.log('Upsell skipped:', data); + // data includes: packageId, reason +}); +``` + +## User Events + +### `user:logged-in` + +Fired when user logs in: + +```javascript +next.on('user:logged-in', (data) => { + console.log('User logged in:', data); + // data includes: user info, email +}); +``` + +### `user:logged-out` + +Fired when user logs out: + +```javascript +next.on('user:logged-out', (data) => { + console.log('User logged out:', data); +}); +``` + +## Behavioral Events + +### `fomo:shown` + +Fired when FOMO notification appears: + +```javascript +next.on('fomo:shown', (data) => { + console.log('FOMO shown:', data); + // data includes: customer name, product, location +}); +``` + +### `exit-intent:clicked` + +Fired when user clicks on exit intent popup: + +```javascript +next.on('exit-intent:clicked', (data) => { + console.log('Exit intent clicked:', data); + // data includes: imageUrl, template +}); +``` + +### `exit-intent:dismissed` + +Fired when exit intent popup is dismissed: + +```javascript +next.on('exit-intent:dismissed', (data) => { + console.log('Exit intent dismissed:', data); + // data includes: imageUrl, template +}); +``` + +### `exit-intent:closed` + +Fired when exit intent popup is closed: + +```javascript +next.on('exit-intent:closed', (data) => { + console.log('Exit intent closed:', data); + // data includes: imageUrl, template +}); +``` + +### `exit-intent:action` + +Fired when user takes an action on exit intent popup: + +```javascript +next.on('exit-intent:action', (data) => { + console.log('Exit intent action:', data); + // data includes: action, couponCode +}); +``` + +## Profile Events + +### `profile:applied` + +Fired when a profile is applied to the cart: + +```javascript +next.on('profile:applied', (data) => { + console.log('Profile applied:', data); + // data includes: profileId, previousProfileId, itemsSwapped, originalItems, cleared, profile +}); +``` + +### `profile:reverted` + +Fired when profile changes are reverted: + +```javascript +next.on('profile:reverted', (data) => { + console.log('Profile reverted:', data); + // data includes: previousProfileId, itemsRestored +}); +``` + +### `profile:switched` + +Fired when switching between profiles: + +```javascript +next.on('profile:switched', (data) => { + console.log('Profile switched:', data); + // data includes: fromProfileId, toProfileId, itemsAffected +}); +``` + +### `profile:registered` + +Fired when a new profile is registered: + +```javascript +next.on('profile:registered', (data) => { + console.log('Profile registered:', data); + // data includes: profileId, mappingsCount +}); +``` + +## System Events + +### `route:changed` + +Fired when SDK detects route/page change: + +```javascript +next.on('route:changed', (route) => { + console.log('Route changed:', route); + // route includes: path, params +}); +``` + +### `sdk:route-invalidated` + +Fired when SDK route context is invalidated: + +```javascript +next.on('sdk:route-invalidated', (data) => { + console.log('Route invalidated:', data); +}); +``` + +### `page:viewed` + +Fired when page view is tracked: + +```javascript +next.on('page:viewed', (data) => { + console.log('Page viewed:', data); + // data includes: page info, timestamp +}); +``` + +### `error:occurred` + +Fired when SDK encounters an error: + +```javascript +next.on('error:occurred', (error) => { + console.error('SDK error:', error); + // error includes: message, stack, context +}); +``` + +## Complete Event List + +Here are all 34 available events organized by category: + +### Cart (5 events) +- `cart:updated` - Cart state changed +- `cart:item-added` - Item added to cart +- `cart:item-removed` - Item removed from cart +- `cart:quantity-changed` - Item quantity changed +- `cart:package-swapped` - Package swapped for another + +### Checkout & Order (3 events) +- `checkout:started` - Checkout process began +- `checkout:form-initialized` - Checkout form ready +- `order:completed` - Order successfully completed + +### Payment (3 events) +- `payment:tokenized` - Payment token created +- `payment:error` - Payment processing failed +- `express-checkout:started` - Express checkout initiated + +### Campaign & Config (2 events) +- `campaign:loaded` - Campaign data loaded +- `config:updated` - Configuration changed + +### Coupons (2 events) +- `coupon:applied` - Discount code applied +- `coupon:removed` - Discount code removed + +### Upsells (4 events) +- `upsell:viewed` - Upsell offer shown +- `upsell:accepted` - Upsell accepted by user +- `upsell:added` - Upsell added to order +- `upsell:skipped` - Upsell declined/skipped + +### User (2 events) +- `user:logged-in` - User authenticated +- `user:logged-out` - User logged out + +### Behavioral (5 events) +- `fomo:shown` - FOMO popup displayed +- `exit-intent:clicked` - Exit intent popup clicked +- `exit-intent:dismissed` - Exit intent popup dismissed +- `exit-intent:closed` - Exit intent popup closed +- `exit-intent:action` - Exit intent action taken + +### Profile (4 events) +- `profile:applied` - Profile applied to cart +- `profile:reverted` - Profile changes reverted +- `profile:switched` - Switched between profiles +- `profile:registered` - New profile registered + +### System (5 events) +- `route:changed` - Page/route changed +- `sdk:route-invalidated` - Route context reset +- `page:viewed` - Page view tracked +- `error:occurred` - Error encountered + +## Event Handling Patterns + +### Basic Event Listening + +```javascript +// Subscribe to events +next.on('cart:updated', (data) => { + console.log('Cart updated:', data); +}); + +// Multiple events +['cart:item-added', 'cart:item-removed'].forEach(event => { + next.on(event, (data) => { + console.log(`Event ${event}:`, data); + }); +}); +``` + +### Removing Listeners + +```javascript +const handler = (data) => console.log(data); + +// Add listener +next.on('cart:updated', handler); + +// Remove listener +next.off('cart:updated', handler); +``` + +### Error Handling in Events + +```javascript +next.on('cart:updated', (data) => { + try { + // Your code here + updateUI(data); + } catch (error) { + console.error('Error in event handler:', error); + } +}); +``` + +### DOM Events vs SDK Events + +```javascript +// DOM events (for SDK lifecycle) +document.addEventListener('next:initialized', (event) => { + console.log('SDK ready via DOM event'); +}); + +// SDK events (for cart, checkout, etc.) +next.on('cart:updated', (data) => { + console.log('Cart updated via SDK event'); +}); +``` + +## Custom Event Integration + +### Google Analytics + +```javascript +next.on('cart:item-added', (data) => { + if (typeof gtag !== 'undefined') { + gtag('event', 'add_to_cart', { + value: data.item.price, + currency: 'USD', + items: [data.item] + }); + } +}); +``` + +### Custom Analytics + +```javascript +// Track all cart changes +next.on('cart:updated', (data) => { + fetch('/api/track', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + event: 'cart_updated', + properties: data + }) + }); +}); +``` + +## Best Practices + +1. **Always check data**: Validate event data before using +2. **Handle errors**: Wrap handlers in try-catch +3. **Clean up**: Remove listeners when no longer needed +4. **Don't block**: Keep handlers fast and async +5. **Test events**: Verify events fire as expected + +## Debugging Events + +### List All Available Events + +```javascript +// Get all registered events from the EventBus +if (window.next && window.next.eventBus) { + const listeners = window.next.eventBus.listeners; + const events = Array.from(listeners.keys()).sort(); + + console.log('Available events:', events.length); + events.forEach((event, i) => { + const count = listeners.get(event).size; + console.log(`${i + 1}. ${event} (${count} listeners)`); + }); +} +``` + +### Monitor All Events + +```javascript +// Log all events for debugging +if (window.location.search.includes('debug=true')) { + const allEvents = [ + // Cart + 'cart:updated', 'cart:item-added', 'cart:item-removed', 'cart:quantity-changed', 'cart:package-swapped', + // Checkout & Order + 'checkout:started', 'checkout:form-initialized', 'order:completed', + // Payment + 'payment:tokenized', 'payment:error', 'express-checkout:started', + // Campaign & Config + 'campaign:loaded', 'config:updated', + // Coupons + 'coupon:applied', 'coupon:removed', + // Upsells + 'upsell:viewed', 'upsell:accepted', 'upsell:added', 'upsell:skipped', + // User + 'user:logged-in', 'user:logged-out', + // Behavioral + 'fomo:shown', 'exit-intent:clicked', 'exit-intent:dismissed', 'exit-intent:closed', 'exit-intent:action', + // Profile + 'profile:applied', 'profile:reverted', 'profile:switched', 'profile:registered', + // System + 'route:changed', 'sdk:route-invalidated', 'page:viewed', 'error:occurred' + ]; + + allEvents.forEach(event => { + next.on(event, (data) => { + console.log(`[Event] ${event}:`, data); + }); + }); +} +``` + +## Legacy Lifecycle Callbacks + +:::warning Legacy Feature +Lifecycle callbacks (`next.registerCallback`) are a legacy feature primarily for backwards compatibility. For new code, use the [event system](#cart-events) instead. +::: + +The SDK supports lifecycle callbacks for specific operations. These are less flexible than events but may be needed for legacy integrations. + +### Available Callback Types + +```javascript +// CallbackType values: +'beforeRender' // Before cart UI renders +'afterRender' // After cart UI renders +'beforeCheckout' // Before checkout starts +'afterCheckout' // After checkout completes +'beforeRedirect' // Before order redirect +'itemAdded' // After item added to cart +'itemRemoved' // After item removed from cart +'cartCleared' // After cart cleared +``` + +### Registering Callbacks + +```javascript +window.nextReady.push(function() { + // Register a callback + next.registerCallback('itemAdded', function(data) { + console.log('Item added to cart:', data); + // data contains: cartLines, cartTotals, campaignData, appliedCoupons + }); + + // Register multiple callbacks + next.registerCallback('beforeCheckout', function(data) { + console.log('Checkout starting with:', data.cartTotals); + }); +}); +``` + +### Callback Data Structure + +All lifecycle callbacks receive a `CallbackData` object: + +```typescript +{ + cartLines: EnrichedCartLine[]; // Cart items with full details + cartTotals: CartTotals; // Pricing information + campaignData: Campaign | null; // Campaign configuration + appliedCoupons: AppliedCoupon[]; // Active discounts +} +``` + +### Unregistering Callbacks + +```javascript +// Store reference to callback function +const myCallback = function(data) { + console.log('Cart data:', data); +}; + +// Register callback +next.registerCallback('cartCleared', myCallback); + +// Later, unregister it +next.unregisterCallback('cartCleared', myCallback); +``` + +### Migration to Events + +For new code, prefer events over lifecycle callbacks: + +```javascript +// Preferred: Use events +next.on('cart:item-added', function(data) { + console.log('Item added:', data); +}); + +// Legacy: Lifecycle callbacks +next.registerCallback('itemAdded', function(data) { + console.log('Item added:', data); +}); +``` +``` \ No newline at end of file diff --git a/docs/campaign-cart/javascript-api/index.md b/docs/campaign-cart/javascript-api/index.md new file mode 100644 index 00000000..8473bf6b --- /dev/null +++ b/docs/campaign-cart/javascript-api/index.md @@ -0,0 +1,36 @@ +--- +sidebar_label: JavaScript API +sidebar_position: 4 +--- + +# JavaScript API + +Complete reference for all JavaScript methods available in the Campaign Cart SDK. + +## Quick Navigation + +- **[Complete Methods Reference](/docs/campaign-cart/javascript-api/methods)** - All JavaScript methods in one place + +## Method Categories + +The JavaScript API is organized into the following categories: + +- **Cart Methods** - Add, remove, update cart items +- **Campaign & Package Methods** - Get campaign and package data +- **Coupon Methods** - Apply and manage discount codes +- **Shipping Methods** - Manage shipping options +- **Tracking & Analytics Methods** - Track e-commerce events +- **Upsell Methods** - Post-purchase upsell management +- **Event Methods** - Subscribe to SDK events +- **Utility Methods** - Formatting and validation +- **Debug API** - Development and troubleshooting tools + +## Documentation + +- **[Events](/docs/campaign-cart/javascript-api/events)** - Complete event system documentation +- **[Profiles API](/docs/campaign-cart/javascript-api/profiles)** - Profile-based package mapping +- **[Attribution API](/docs/campaign-cart/javascript-api/attribution)** - Attribution tracking +- **[URL Parameters API](/docs/campaign-cart/javascript-api/url-parameters)** - URL parameter management +- **[Data Attributes](/docs/campaign-cart/data-attributes/)** - HTML attribute reference +- **[Utilities](/docs/campaign-cart/utilities/)** - FOMO, exit intent, and debugging tools + diff --git a/docs/campaign-cart/javascript-api/methods.md b/docs/campaign-cart/javascript-api/methods.md new file mode 100644 index 00000000..e905ee71 --- /dev/null +++ b/docs/campaign-cart/javascript-api/methods.md @@ -0,0 +1,870 @@ +--- +sidebar_label: Methods +sidebar_position: 2 +--- + +# JavaScript API Methods + +Complete reference for all JavaScript methods available in the Campaign Cart SDK. + +## Getting Started + +The SDK exposes methods through the `window.next` object after initialization. + +### Initialization Detection + +```javascript +// Check if SDK is ready +if (window.next) { + // SDK is ready, use it directly + next.addItem({ packageId: 1 }); +} else { + // Queue for later execution + window.nextReady = window.nextReady || []; + window.nextReady.push(function() { + next.addItem({ packageId: 1 }); + }); +} + +// Or listen for initialization event +document.addEventListener('next:initialized', function(event) { + // SDK is ready + console.log('SDK initialized at', new Date(event.detail.timestamp)); + next.addItem({ packageId: 1 }); +}); +``` + +## Table of Contents + +- [Cart Methods](#cart-methods) +- [Campaign & Package Methods](#campaign--package-methods) +- [Coupon Methods](#coupon-methods) +- [Shipping Methods](#shipping-methods) +- [Tracking & Analytics Methods](#tracking--analytics-methods) +- [Upsell Methods](#upsell-methods) +- [Event Methods](#event-methods) +- [Utility Methods](#utility-methods) +- [Debug API](#debug-api) + +## Cart Methods + +### addItem + +Adds an item to the cart. **Note: This method requires an options object, not a direct package ID.** + +```javascript +// ✅ CORRECT - Pass an object with packageId property +await next.addItem({ + packageId: 123, + quantity: 2 +}); + +// ✅ CORRECT - Quantity is optional (defaults to 1) +await next.addItem({ packageId: 123 }); + +// ❌ WRONG - Don't pass packageId directly +// next.addItem(123); // This won't work! +``` + +**Parameters:** +- `options` (object): Required options object + - `options.packageId` (number): Package ID to add + - `options.quantity` (number, optional): Quantity to add (default: 1) + +**Returns:** `Promise` + +### removeItem + +Removes an item from the cart. **Requires an options object.** + +```javascript +// ✅ CORRECT +await next.removeItem({ packageId: 123 }); + +// ❌ WRONG +// next.removeItem(123); // This won't work! +``` + +**Parameters:** +- `options` (object): Required options object + - `options.packageId` (number): Package ID to remove + +**Returns:** `Promise` + +### updateQuantity + +Updates the quantity of an item in the cart. **Requires an options object.** + +```javascript +// ✅ CORRECT +await next.updateQuantity({ + packageId: 123, + quantity: 5 +}); + +// ❌ WRONG +// next.updateQuantity(123, 5); // This won't work! +``` + +**Parameters:** +- `options` (object): Required options object + - `options.packageId` (number): Package ID to update + - `options.quantity` (number): New quantity + +**Returns:** `Promise` + +### clearCart + +Removes all items from the cart. + +```javascript +await next.clearCart(); +``` + +**Returns:** `Promise` + +### hasItemInCart + +Checks if an item is in the cart. **Requires an options object.** + +```javascript +// ✅ CORRECT +const hasItem = next.hasItemInCart({ packageId: 123 }); +console.log('Item in cart:', hasItem); // true or false + +// ❌ WRONG +// next.hasItemInCart(123); // This won't work! +``` + +**Parameters:** +- `options` (object): Required options object + - `options.packageId` (number): Package ID to check + +**Returns:** `boolean` + +### getCartData + +Returns comprehensive cart data including enriched items, totals, campaign data, and applied coupons. + +```javascript +const cartData = next.getCartData(); +console.log(cartData); +// { +// cartLines: [...], +// cartTotals: { subtotal: {...}, total: {...}, ... }, +// campaignData: {...}, +// appliedCoupons: [...] +// } +``` + +**Returns:** +```typescript +{ + cartLines: EnrichedCartLine[]; + cartTotals: CartTotals; + campaignData: Campaign | null; + appliedCoupons: AppliedCoupon[]; +} +``` + +### getCartTotals + +Returns cart totals and pricing information. + +```javascript +const totals = next.getCartTotals(); +console.log('Total:', totals.total.formatted); // "$99.99" +console.log('Subtotal:', totals.subtotal.formatted); // "$89.99" +console.log('Shipping:', totals.shipping.formatted); // "$10.00" +``` + +**Returns:** `CartTotals` object with subtotal, shipping, tax, discounts, and total + +### getCartCount + +Returns the total number of items in the cart. + +```javascript +const count = next.getCartCount(); +console.log('Items in cart:', count); // 3 +``` + +**Returns:** `number` + +## Campaign & Package Methods + +### getCampaignData + +Returns the loaded campaign data. + +```javascript +const campaign = next.getCampaignData(); +if (campaign) { + console.log('Campaign:', campaign.name); + console.log('Currency:', campaign.currency); + console.log('Packages:', campaign.packages); +} +``` + +**Returns:** `Campaign | null` + +### getPackage + +Gets detailed information about a specific package. + +```javascript +const package = next.getPackage(123); +if (package) { + console.log('Package name:', package.display_name); + console.log('Price:', next.formatPrice(package.price)); +} +``` + +**Parameters:** +- `id` (number): Package ID + +**Returns:** `Package | null` + +## Coupon Methods + +### applyCoupon + +Applies a coupon code to the cart. + +```javascript +const result = await next.applyCoupon('SAVE20'); +if (result.success) { + console.log('Coupon applied:', result.message); + console.log('Discount amount:', result.data.amount); +} else { + console.error('Coupon error:', result.message); +} +``` + +**Parameters:** +- `code` (string): Coupon code to apply + +**Returns:** +```typescript +Promise<{ + success: boolean; + message: string; + data?: { amount: number; formatted: string; } +}> +``` + +### removeCoupon + +Removes a coupon from the cart. + +```javascript +next.removeCoupon('SAVE20'); +``` + +**Parameters:** +- `code` (string): Coupon code to remove + +**Returns:** `void` + +### getCoupons + +Returns all applied coupons. + +```javascript +const coupons = next.getCoupons(); +coupons.forEach(coupon => { + console.log(`${coupon.code}: ${coupon.amount.formatted} off`); +}); +``` + +**Returns:** `AppliedCoupon[]` + +### validateCoupon + +Validates a coupon without applying it. + +```javascript +const validation = next.validateCoupon('TESTCODE'); +if (validation.valid) { + console.log('Coupon is valid'); +} else { + console.log('Invalid:', validation.message); +} +``` + +**Parameters:** +- `code` (string): Coupon code to validate + +**Returns:** +```typescript +{ + valid: boolean; + message?: string; +} +``` + +### calculateDiscountAmount + +Calculates the discount amount for a given coupon definition. + +```javascript +const amount = next.calculateDiscountAmount(couponDefinition); +console.log('Discount amount:', amount); +``` + +**Parameters:** +- `coupon` (DiscountDefinition): Coupon definition object + +**Returns:** `number` - Calculated discount amount + +## Shipping Methods + +### getShippingMethods + +Returns all available shipping methods from the campaign. + +```javascript +const methods = next.getShippingMethods(); +console.log(methods); +// [ +// {ref_id: 1, code: "standard", price: "0.00"}, +// {ref_id: 2, code: "express", price: "12.99"} +// ] +``` + +**Returns:** `Array<{ref_id: number; code: string; price: string}>` + +### getSelectedShippingMethod + +Returns the currently selected shipping method. + +```javascript +const selected = next.getSelectedShippingMethod(); +if (selected) { + console.log('Shipping:', selected.name, selected.price); +} +``` + +**Returns:** `{id: number; name: string; price: number; code: string} | null` + +### setShippingMethod + +Sets the shipping method by ID. + +```javascript +// Set standard shipping (ID 1) +await next.setShippingMethod(1); + +// Set express shipping (ID 2) +await next.setShippingMethod(2); +``` + +**Parameters:** +- `methodId` (number): The ref_id of the shipping method from campaign data + +**Returns:** `Promise` + +**Throws:** Error if shipping method ID is not found in campaign data + +## Tracking & Analytics Methods + +### trackViewItemList + +Tracks when users view a list of products. + +```javascript +// Basic tracking +await next.trackViewItemList(['1', '2', '3']); + +// With list context +await next.trackViewItemList( + ['1', '2', '3'], + 'homepage', + 'Featured Products' +); +``` + +**Parameters:** +- `packageIds` (Array<string\|number>): Array of package IDs +- `listId` (string, optional): Unique list identifier +- `listName` (string, optional): Human-readable list name + +**Returns:** `Promise` + +### trackViewItem + +Tracks when a single item is viewed. + +```javascript +await next.trackViewItem('1'); +``` + +**Parameters:** +- `packageId` (string\|number): Package ID viewed + +**Returns:** `Promise` + +### trackAddToCart + +Tracks when an item is added to cart. + +```javascript +await next.trackAddToCart('1', 2); +``` + +**Parameters:** +- `packageId` (string\|number): Package ID added +- `quantity` (number, optional): Quantity added (default: 1) + +**Returns:** `Promise` + +### trackRemoveFromCart + +Tracks when an item is removed from cart. + +```javascript +await next.trackRemoveFromCart('1', 1); +``` + +**Parameters:** +- `packageId` (string\|number): Package ID removed +- `quantity` (number, optional): Quantity removed (default: 1) + +**Returns:** `Promise` + +### trackBeginCheckout + +Tracks when checkout process begins. + +```javascript +await next.trackBeginCheckout(); +``` + +**Returns:** `Promise` + +### trackPurchase + +Tracks completed purchases. + +```javascript +// Track with order data +await next.trackPurchase(orderData); +``` + +**Parameters:** +- `orderData` (any): Order data object + +**Returns:** `Promise` + +### trackCustomEvent + +Tracks custom events with optional data. + +```javascript +// Simple custom event +await next.trackCustomEvent('video_played'); + +// Custom event with data +await next.trackCustomEvent('user_engagement', { + section: 'hero', + action: 'video_play', + duration: 30 +}); +``` + +**Parameters:** +- `eventName` (string): Custom event name +- `data` (Record<string, any>, optional): Additional event data + +**Returns:** `Promise` + +### trackSignUp + +Tracks user sign-up events. + +```javascript +await next.trackSignUp('user@example.com'); +``` + +**Parameters:** +- `email` (string): User's email address + +**Returns:** `Promise` + +### trackLogin + +Tracks user login events. + +```javascript +await next.trackLogin('user@example.com'); +``` + +**Parameters:** +- `email` (string): User's email address + +**Returns:** `Promise` + +### setDebugMode (Analytics) + +Enables or disables analytics debug mode. + +```javascript +await next.setDebugMode(true); // Enable debug logging +``` + +**Parameters:** +- `enabled` (boolean): Enable or disable debug mode + +**Returns:** `Promise` + +### invalidateAnalyticsContext + +Invalidates the analytics context, useful when switching between pages. + +```javascript +await next.invalidateAnalyticsContext(); +``` + +**Returns:** `Promise` + +## Upsell Methods + +### addUpsell + +Adds upsell items to a completed order. Only available after order completion. + +```javascript +// Add single upsell +const result = await next.addUpsell({ + packageId: 123, + quantity: 1 +}); + +// Add multiple upsells at once +const result = await next.addUpsell({ + items: [ + { packageId: 123, quantity: 1 }, + { packageId: 456, quantity: 2 } + ] +}); + +console.log('Upsells added:', result.addedLines); +console.log('Total upsell value:', result.totalValue); +``` + +**Parameters:** +- `options.packageId` (number, optional): Single package ID to add +- `options.quantity` (number, optional): Quantity for single item (default: 1) +- `options.items` (Array, optional): Multiple items to add + - `items[].packageId` (number): Package ID + - `items[].quantity` (number, optional): Quantity (default: 1) + +**Returns:** +```typescript +Promise<{ + order: Order; + addedLines: OrderLine[]; + totalValue: number; +}> +``` + +**Throws:** Error if no order exists or order doesn't support upsells + +### canAddUpsells + +Checks if upsells can be added to the current order. + +```javascript +if (next.canAddUpsells()) { + console.log('Order supports upsells'); +} +``` + +**Returns:** `boolean` + +### getCompletedUpsells + +Returns array of package IDs that have been added as upsells. + +```javascript +const completedUpsells = next.getCompletedUpsells(); +console.log('Upsells added:', completedUpsells); // ['123', '456'] +``` + +**Returns:** `string[]` + +### isUpsellAlreadyAdded + +Checks if a specific package has already been added as an upsell. + +```javascript +if (next.isUpsellAlreadyAdded(123)) { + console.log('This upsell was already added'); +} +``` + +**Parameters:** +- `packageId` (number): Package ID to check + +**Returns:** `boolean` + +## Event Methods + +### on + +Subscribe to internal SDK events. + +```javascript +// Listen for cart updates +next.on('cart:updated', (cartState) => { + console.log('Cart updated:', cartState.items.length, 'items'); +}); + +// Listen for item additions +next.on('cart:item-added', (data) => { + console.log('Item added:', data.packageId); +}); +``` + +**Parameters:** +- `event` (string): Event name from EventMap +- `handler` (function): Event handler function + +**Returns:** `void` + +**Example:** +```javascript +// Subscribe to multiple events +next.on('cart:item-added', (data) => { + showNotification(`${data.packageName} added to cart`); +}); + +next.on('order:completed', (order) => { + trackConversion(order); +}); +``` + +### off + +Unsubscribe from internal SDK events. + +```javascript +const handler = (data) => console.log(data); + +// Add listener +next.on('cart:updated', handler); + +// Later, remove listener +next.off('cart:updated', handler); +``` + +**Parameters:** +- `event` (string): Event name +- `handler` (function): Handler function to remove (must be the same function reference) + +**Returns:** `void` + +**Example:** +```javascript +// Store handler reference for cleanup +const cartHandler = (data) => { + updateCartUI(data); +}; + +// Subscribe +next.on('cart:updated', cartHandler); + +// Later, when component unmounts +next.off('cart:updated', cartHandler); +``` + +For a complete list of all 34 available events, event data structures, initialization callbacks, and best practices, see the [Events Reference](/docs/campaign-cart/javascript-api/events/). + +## Utility Methods + +### formatPrice + +Formats a price value according to campaign currency. + +```javascript +const formatted = next.formatPrice(19.99); // "$19.99" +const euros = next.formatPrice(19.99, 'EUR'); // "€19.99" +``` + +**Parameters:** +- `amount` (number): Price amount to format +- `currency` (string, optional): Currency code (uses campaign currency if not provided) + +**Returns:** `string` + +### validateCheckout + +Validates if the cart is ready for checkout. + +```javascript +const validation = next.validateCheckout(); +if (!validation.valid) { + console.error('Cannot checkout:', validation.errors); +} +``` + +**Returns:** +```typescript +{ + valid: boolean; + errors?: string[]; +} +``` + +## Debug API + +The debug API provides powerful utilities for development and troubleshooting. Available only when debug mode is enabled with `?debugger=true`. + +### Accessing Debug Mode + +```javascript +// Check if debug mode is available +if (window.nextDebug) { + console.log('Debug mode available'); +} +``` + +### Store Access + +Direct access to all internal stores: + +```javascript +// Cart store +const cartState = nextDebug.stores.cart.getState(); +console.log('Cart items:', cartState.items); + +// Campaign store +const campaignState = nextDebug.stores.campaign.getState(); +console.log('Campaign data:', campaignState.data); + +// Config store +const configState = nextDebug.stores.config.getState(); +console.log('API key:', configState.apiKey); + +// Checkout store +const checkoutState = nextDebug.stores.checkout.getState(); + +// Order store +const orderState = nextDebug.stores.order.getState(); + +// Attribution store +const attributionState = nextDebug.stores.attribution.getState(); +``` + +### Cart Debug Methods + +```javascript +// Quick cart operations +nextDebug.addToCart(123); // Add single item +nextDebug.addToCart(123, 3); // Add with quantity +nextDebug.removeFromCart(123); // Remove item +nextDebug.updateQuantity(123, 5); // Update quantity + +// Add test items (packages 2, 7, 9) +nextDebug.addTestItems(); +``` + +### Campaign Debug Methods + +```javascript +// Reload campaign data +await nextDebug.loadCampaign(); + +// Clear campaign cache +nextDebug.clearCampaignCache(); + +// Get cache information +const cacheInfo = nextDebug.getCacheInfo(); + +// Inspect specific package +nextDebug.inspectPackage(123); + +// Test shipping methods +await nextDebug.testShippingMethod(1); +``` + +### Analytics Debug Methods + +```javascript +// Get analytics status +const status = await nextDebug.analytics.getStatus(); + +// Get loaded providers +const providers = await nextDebug.analytics.getProviders(); + +// Track test event +await nextDebug.analytics.track('test_event', { test: true }); + +// Enable analytics debug mode +await nextDebug.analytics.setDebugMode(true); + +// Invalidate analytics context +await nextDebug.analytics.invalidateContext(); +``` + +### Attribution Debug Methods + +```javascript +// Debug attribution data +nextDebug.attribution.debug(); + +// Get attribution for API +const attribution = nextDebug.attribution.get(); + +// Set funnel name +nextDebug.attribution.setFunnel('debug-funnel'); + +// Set Everflow click ID +nextDebug.attribution.setEvclid('test-evclid-123'); + +// Get current funnel +const funnel = nextDebug.attribution.getFunnel(); + +// Clear persisted funnel +nextDebug.attribution.clearFunnel(); +``` + +### System Debug Methods + +```javascript +// Direct SDK access +nextDebug.sdk.addItem({ packageId: 123 }); + +// Get initialization stats +const stats = nextDebug.getStats(); + +// Reinitialize SDK +await nextDebug.reinitialize(); + +// Test mode manager +nextDebug.testMode.enable(); +nextDebug.testMode.disable(); +nextDebug.testMode.getConfig(); +``` + +### Debug Overlay Control + +```javascript +// Show/hide debug overlay +nextDebug.overlay.show(); +nextDebug.overlay.hide(); +nextDebug.overlay.toggle(); + +// Check visibility +const isVisible = nextDebug.overlay.isVisible(); +``` + +## Related Documentation + +- **[Utilities](/docs/campaign-cart/utilities/)** - FOMO, exit intent, and debugging tools +- **[Events](/docs/campaign-cart/javascript-api/events)** - Complete event system documentation +- **[Profiles API](/docs/campaign-cart/javascript-api/profiles)** - Profile-based package mapping +- **[Attribution API](/docs/campaign-cart/javascript-api/attribution)** - Attribution tracking +- **[URL Parameters API](/docs/campaign-cart/javascript-api/url-parameters)** - URL parameter management +- **[Data Attributes](/docs/campaign-cart/data-attributes/)** - HTML attribute reference + diff --git a/docs/campaign-cart/javascript-api/profiles.md b/docs/campaign-cart/javascript-api/profiles.md new file mode 100644 index 00000000..c151c93c --- /dev/null +++ b/docs/campaign-cart/javascript-api/profiles.md @@ -0,0 +1,359 @@ +--- +title: Profiles API +sidebar_position: 5 +--- + +# Profile API Reference + +The Profile System provides methods for managing dynamic package mappings through the `window.next` API. + +## Methods + +### setProfile(profileId, options?) + +Applies a profile to the current session, swapping all package IDs according to the profile's mappings. + +**Parameters:** +- `profileId` (string, required): The ID of the profile to apply +- `options` (object, optional): + - `clearCart` (boolean): Clear cart before applying profile (default: false) + - `preserveQuantities` (boolean): Maintain item quantities (default: true) + +**Returns:** Promise<void> + +**Example:** +```javascript +// Basic usage +await window.next.setProfile('black_friday'); + +// With options +await window.next.setProfile('vip', { + clearCart: true, + preserveQuantities: false +}); +``` + +### revertProfile() + +Reverts to the original cart state before any profile was applied. + +**Returns:** Promise<void> + +**Example:** +```javascript +await window.next.revertProfile(); +``` + +### getActiveProfile() + +Returns the ID of the currently active profile. + +**Returns:** string | null + +**Example:** +```javascript +const currentProfile = window.next.getActiveProfile(); +console.log(currentProfile); // "black_friday" or null +``` + +### getProfileInfo(profileId?) + +Gets detailed information about a profile. + +**Parameters:** +- `profileId` (string, optional): Profile ID to query. If omitted, returns active profile info. + +**Returns:** Profile object or null + +**Example:** +```javascript +const profile = window.next.getProfileInfo('black_friday'); +console.log(profile); +// { +// id: "black_friday", +// name: "Black Friday Sale", +// packageMappings: { 1: 101, 2: 102, ... } +// } +``` + +### getMappedPackageId(originalId) + +Gets the mapped package ID for the active profile. + +**Parameters:** +- `originalId` (number): The original package ID + +**Returns:** number (mapped ID or original if no mapping) + +**Example:** +```javascript +const mappedId = window.next.getMappedPackageId(1); +console.log(mappedId); // 101 (if black_friday profile is active) +``` + +### getOriginalPackageId(mappedId) + +Gets the original package ID from a mapped ID. + +**Parameters:** +- `mappedId` (number): The mapped package ID + +**Returns:** number | null + +**Example:** +```javascript +const originalId = window.next.getOriginalPackageId(101); +console.log(originalId); // 1 +``` + +### listProfiles() + +Returns an array of all configured profile IDs. + +**Returns:** string[] + +**Example:** +```javascript +const profiles = window.next.listProfiles(); +console.log(profiles); // ["regular", "black_friday", "vip"] +``` + +### hasProfile(profileId) + +Checks if a profile exists in the configuration. + +**Parameters:** +- `profileId` (string): Profile ID to check + +**Returns:** boolean + +**Example:** +```javascript +if (window.next.hasProfile('black_friday')) { + // Profile exists +} +``` + +### registerProfile(profile) + +Registers a new profile dynamically at runtime. + +**Parameters:** +- `profile` (object): + - `id` (string): Unique profile identifier + - `name` (string): Display name + - `description` (string, optional): Profile description + - `packageMappings` (object): Package ID mappings + +**Returns:** void + +**Example:** +```javascript +window.next.registerProfile({ + id: 'flash_sale', + name: 'Flash Sale', + description: '2-hour flash sale', + packageMappings: { + 1: 401, + 2: 402, + 3: 403 + } +}); +``` + +## Events + +### `profile:applied` + +Fired when a profile is successfully applied. + +**Event Data:** +- `profileId` (string): Applied profile ID +- `previousProfileId` (string | null): Previous profile ID +- `itemsSwapped` (number): Number of cart items affected +- `originalItems` (number): Original cart item count +- `cleared` (boolean): Whether cart was cleared +- `profile` (object): Full profile object + +**Example:** +```javascript +window.next.on('profile:applied', (data) => { + console.log(`Profile ${data.profileId} applied`); + console.log(`Swapped ${data.itemsSwapped} items`); +}); +``` + +### `profile:reverted` + +Fired when a profile is reverted. + +**Event Data:** +- `previousProfileId` (string | null): The profile that was active +- `itemsRestored` (number): Number of items restored + +**Example:** +```javascript +window.next.on('profile:reverted', (data) => { + console.log(`Restored ${data.itemsRestored} original items`); +}); +``` + +### `profile:switched` + +Fired when switching between profiles. + +**Event Data:** +- `fromProfileId` (string | null): Previous profile +- `toProfileId` (string): New profile +- `itemsAffected` (number): Number of items affected + +### `profile:registered` + +Fired when a new profile is registered. + +**Event Data:** +- `profileId` (string): Registered profile ID +- `mappingsCount` (number): Number of package mappings + +## Data Attributes + +### data-next-profile + +Activates a profile when clicked. + +**Attributes:** +- `data-next-profile` (required): Profile ID to activate +- `data-next-clear-cart` (optional): Clear cart before applying ("true"/"false") +- `data-next-preserve-quantities` (optional): Preserve item quantities ("true"/"false") +- `data-next-active-text` (optional): Text to show when profile is active +- `data-next-inactive-text` (optional): Text to show when profile is inactive + +**Example:** +```html + +``` + +### data-next-profile-selector + +Creates a dropdown selector for profiles. + +**Attributes:** +- `data-next-profile-selector` (required): Marks element as profile selector +- `data-next-auto-populate` (optional): Auto-populate with configured profiles ("true") +- `data-next-clear-cart` (optional): Clear cart on profile change ("true"/"false") +- `data-next-preserve-quantities` (optional): Preserve quantities ("true"/"false") + +**Example:** +```html + +``` + +### data-next-show-if-profile + +Shows element only when specific profile is active. + +**Attributes:** +- `data-next-show-if-profile` (required): Profile ID to check + +**Example:** +```html +
+ Black Friday prices are active! +
+``` + +## URL Parameters + +### ?profile= + +Applies a profile on page load (preserves existing cart). + +**Example:** +``` +https://example.com/checkout?profile=black_friday +``` + +### ?forceProfile= + +Applies a profile on page load (clears cart first). + +**Example:** +``` +https://example.com/checkout?forceProfile=vip +``` + +### ?packageProfile= + +Alternative parameter name for profile activation. + +**Example:** +``` +https://example.com/checkout?packageProfile=sale_20 +``` + +## Configuration + +Profiles are configured in `window.nextConfig`: + +```javascript +window.nextConfig = { + profiles: { + "profile_id": { + name: "Profile Name", + description: "Optional description", + packageMappings: { + 1: 101, // original_id: mapped_id + 2: 102, + // ... more mappings + } + } + }, + defaultProfile: "profile_id" // Optional default +}; +``` + +## CSS Classes + +The following CSS classes are automatically applied: + +- `.next-profile-switcher` - Applied to profile switcher elements +- `.next-profile-active` - Applied when profile is active +- `.next-profile-selector` - Applied to profile selector dropdowns +- `.next-loading` - Applied during profile switching + +## TypeScript Types + +```typescript +interface Profile { + id: string; + name: string; + description?: string; + packageMappings: Record; + reverseMapping?: Record; + isActive?: boolean; + priority?: number; +} + +interface ProfileState { + profiles: Map; + activeProfileId: string | null; + previousProfileId: string | null; + mappingHistory: MappingEvent[]; + originalCartSnapshot?: CartItem[]; +} + +interface MappingEvent { + timestamp: number; + profileId: string; + action: 'applied' | 'reverted' | 'switched'; + itemsAffected: number; + previousProfileId?: string; +} +``` \ No newline at end of file diff --git a/docs/campaign-cart/javascript-api/url-parameters.md b/docs/campaign-cart/javascript-api/url-parameters.md new file mode 100644 index 00000000..b8163ed2 --- /dev/null +++ b/docs/campaign-cart/javascript-api/url-parameters.md @@ -0,0 +1,347 @@ +--- +title: URL Parameters API +sidebar_position: 6 +--- + +# URL Parameters API + +The SDK provides a comprehensive API for managing URL parameters programmatically. These methods allow you to control parameter-based visibility and behavior without relying solely on URL query strings. + +## Overview + +URL parameters are automatically captured from the page URL when the SDK initializes and stored in sessionStorage for the entire session. You can also programmatically set, get, and clear parameters using the JavaScript API. + +## Methods + +All parameter methods are available on the global `next` object after the SDK initializes. + +### setParam(key, value) + +Sets a single URL parameter. + +**Parameters:** +- `key` (string) - The parameter name +- `value` (string) - The parameter value + +**Example:** +```javascript +// Hide banner elements +next.setParam('banner', 'n'); + +// Enable debug mode +next.setParam('debug', 'true'); +``` + +### setParams(params) + +Sets multiple parameters at once, replacing all existing parameters. + +**Parameters:** +- `params` (object) - Key-value pairs of parameters + +**Example:** +```javascript +next.setParams({ + timer: 'n', + reviews: 'n', + banner: 'n', + exit: 'n' +}); +``` + +### mergeParams(params) + +Merges new parameters with existing ones, preserving parameters not included in the merge. + +**Parameters:** +- `params` (object) - Key-value pairs to merge + +**Example:** +```javascript +// Existing: { timer: 'n', banner: 'y' } +next.mergeParams({ + campaign: 'summer2024', + banner: 'n' +}); +// Result: { timer: 'n', banner: 'n', campaign: 'summer2024' } +``` + +### getParam(key) + +Gets the value of a specific parameter. + +**Parameters:** +- `key` (string) - The parameter name + +**Returns:** +- `string | null` - The parameter value or null if not set + +**Example:** +```javascript +const timerValue = next.getParam('timer'); +if (timerValue === 'n') { + console.log('Timer is disabled'); +} +``` + +### getAllParams() + +Gets all stored parameters as an object. + +**Returns:** +- `object` - All parameters as key-value pairs + +**Example:** +```javascript +const allParams = next.getAllParams(); +console.log('Current parameters:', allParams); +// Output: { timer: 'n', banner: 'n', debug: 'true' } +``` + +### hasParam(key) + +Checks if a parameter exists (regardless of its value). + +**Parameters:** +- `key` (string) - The parameter name + +**Returns:** +- `boolean` - True if the parameter exists + +**Example:** +```javascript +if (next.hasParam('debug')) { + console.log('Debug mode is active'); +} +``` + +### clearParam(key) + +Removes a specific parameter. + +**Parameters:** +- `key` (string) - The parameter name to remove + +**Example:** +```javascript +// Remove debug mode +next.clearParam('debug'); +``` + +### clearAllParams() + +Removes all stored parameters. + +**Example:** +```javascript +// Clear all parameters +next.clearAllParams(); +``` + +## Usage Examples + +### Feature Toggles + +Create a simple feature toggle system: + +```javascript +class FeatureToggle { + static enable(feature) { + next.setParam(`feature_${feature}`, 'enabled'); + } + + static disable(feature) { + next.setParam(`feature_${feature}`, 'disabled'); + } + + static isEnabled(feature) { + return next.getParam(`feature_${feature}`) === 'enabled'; + } + + static toggle(feature) { + if (this.isEnabled(feature)) { + this.disable(feature); + } else { + this.enable(feature); + } + } +} + +// Usage +FeatureToggle.enable('new_checkout'); +if (FeatureToggle.isEnabled('new_checkout')) { + // Show new checkout flow +} +``` + +### A/B Testing + +Implement A/B test variants: + +```javascript +function setABTestVariant(testName, variant) { + next.setParam(`ab_${testName}`, variant); +} + +function getABTestVariant(testName) { + return next.getParam(`ab_${testName}`) || 'control'; +} + +// Set variant +setABTestVariant('checkout_flow', 'variant_b'); + +// Use variant +const variant = getABTestVariant('checkout_flow'); +switch(variant) { + case 'variant_a': + // Show variant A + break; + case 'variant_b': + // Show variant B + break; + default: + // Show control +} +``` + +### User Preferences + +Store user preferences without cookies: + +```javascript +const UserPreferences = { + hideAnnoyances() { + next.setParams({ + timer: 'n', + exit: 'n', + loading: 'n', + banner: 'n', + reviews: 'n' + }); + }, + + showAll() { + next.clearAllParams(); + }, + + setTheme(theme) { + next.setParam('theme', theme); + }, + + getTheme() { + return next.getParam('theme') || 'light'; + } +}; + +// Usage +UserPreferences.hideAnnoyances(); +UserPreferences.setTheme('dark'); +``` + +### Debug Mode + +Toggle debug mode programmatically: + +```javascript +function toggleDebug() { + if (next.hasParam('debug')) { + next.clearParam('debug'); + console.log('Debug mode disabled'); + } else { + next.setParam('debug', 'true'); + console.log('Debug mode enabled'); + // Reload to apply debug mode + window.location.reload(); + } +} + +// Add keyboard shortcut (Ctrl+Shift+D) +document.addEventListener('keydown', (e) => { + if (e.ctrlKey && e.shiftKey && e.key === 'D') { + toggleDebug(); + } +}); +``` + +### Campaign Tracking + +Track marketing campaigns: + +```javascript +function trackCampaign(source, medium, campaign) { + next.mergeParams({ + utm_source: source, + utm_medium: medium, + utm_campaign: campaign, + campaign_timestamp: Date.now().toString() + }); +} + +function getCampaignInfo() { + return { + source: next.getParam('utm_source'), + medium: next.getParam('utm_medium'), + campaign: next.getParam('utm_campaign'), + timestamp: next.getParam('campaign_timestamp') + }; +} + +// Track a campaign +trackCampaign('email', 'newsletter', 'summer_sale_2024'); + +// Get campaign info +const campaign = getCampaignInfo(); +``` + +## Integration with Conditional Display + +Parameters set via the JavaScript API work seamlessly with the conditional display attributes: + +```html + +
+ Promotional banner +
+ + +
+ Debug information +
+``` + +## Storage and Persistence + +- Parameters are stored in sessionStorage with the key `next-url-params` +- They persist across page navigation within the same session +- New URL parameters override stored values with the same key +- Parameters are cleared when the browser session ends + +## Best Practices + +1. **Use consistent naming conventions**: Prefix related parameters (e.g., `feature_*`, `ab_*`, `utm_*`) + +2. **Document your parameters**: Keep a list of all parameters your application uses + +3. **Handle missing parameters gracefully**: Always provide defaults + ```javascript + const theme = next.getParam('theme') || 'light'; + ``` + +4. **Clean up unused parameters**: Remove parameters that are no longer needed + ```javascript + next.clearParam('temporary_flag'); + ``` + +5. **Use type-safe wrappers**: Create utility functions for commonly used parameters + ```javascript + const ParamUtils = { + isFeatureEnabled: (feature) => next.getParam(feature) === 'true', + setFeature: (feature, enabled) => next.setParam(feature, enabled ? 'true' : 'false') + }; + ``` + +## See Also + +- [URL Parameters (Attributes)](/docs/campaign-cart/data-attributes/url-parameters/) - Using parameters with HTML attributes +- [Conditionals](/docs/campaign-cart/data-attributes/state/) - Show/hide elements based on conditions +- [Events](/docs/campaign-cart/javascript-api/events/) - Parameter-related events \ No newline at end of file diff --git a/docs/campaign-cart/upsells/index.md b/docs/campaign-cart/upsells/index.md new file mode 100644 index 00000000..b3c3ad96 --- /dev/null +++ b/docs/campaign-cart/upsells/index.md @@ -0,0 +1,484 @@ +--- +sidebar_label: Upsells +sidebar_position: 2 +--- + +# Upsells + +Maximize order value with post-purchase upsell flows using flexible patterns for presenting additional offers after checkout. + +## Upsell Configuration + +Configure upsell flows and behavior with meta tags and attributes. + +### Basic Configuration + +Set URLs for upsell flow navigation: + +```html + + + + + +``` + +### Page Configuration + +Mark pages as upsell pages: + +```html + + + + + +``` + +### Order Context + +Upsells need order context (from URL parameter): + +```html + + +``` + +### Multiple Upsell Pages + +Chain multiple upsell pages: + +```html + + + + + + + +``` + +### Upsell Analytics + +Track upsell performance: + +```javascript +// Listen to upsell events +next.on('upsell:shown', (data) => { + // Track impression +}); + +next.on('upsell:accepted', (data) => { + // Track conversion +}); + +next.on('upsell:declined', (data) => { + // Track decline +}); +``` + +### Best Practices + +1. **Limit Steps**: Keep upsell flow to 2-3 steps max +2. **Fast Loading**: Optimize page load for upsells +3. **Mobile Friendly**: Ensure upsells work on mobile +4. **Clear Progress**: Show where user is in flow +5. **Easy Exit**: Always provide clear decline option + +## Direct Upsells + +Simple yes/no decision for a single item. Package ID is on the container. + +### Basic Pattern + +```html + +
+

Add Extra Battery?

+

Extend your flight time with an extra battery pack

+ + +
+``` + +### With Product Display + +Show product details using display attributes: + +```html +
+ Extra Battery +

Extra Battery Pack

+

Price: $29.99

+

+ Save 50% +

+ + + +
+``` + +### Styling Examples + +**Card Style:** + +```html +
+
+

Special Offer!

+
+
+

Add protection for your purchase

+
+ Only $14.99 +
+
+
+ + +
+
+``` + +**Minimal Style:** + +```html +
+

Add expedited shipping for just $9.99?

+ + +
+``` + +### Action Attributes + +| Attribute | Description | +|-----------|-------------| +| `data-next-upsell-action="add"` | Accept upsell and add to order | +| `data-next-upsell-action="skip"` | Decline upsell and continue | + +### Container Attributes + +| Attribute | Description | +|-----------|-------------| +| `data-next-upsell="offer"` | Marks container as upsell offer | +| `data-next-package-id` | Package to add if accepted | + +### Best Practices + +1. **Clear Value Proposition**: Explain why they need the item +2. **Show Savings**: Highlight discounts or special pricing +3. **Easy to Decline**: Make "No" option clear and accessible +4. **Single Focus**: One product per direct upsell +5. **Urgency**: Mention if offer is time-limited + +## Selection Upsells + +Multiple options to choose from. Uses selector ID instead of package ID. + +### Card Selection Pattern + +Let customers choose from multiple options: + +```html + +
+

Choose Your Protection Plan

+ +
+

Basic Protection

+

1 Year Coverage

+ $14.99 +
+ +
+

Premium Protection

+

2 Year Coverage + Accidental Damage

+ $24.99 +
+ +
+

Ultimate Protection

+

3 Year Coverage + Everything

+ $39.99 +
+ + + +
+``` + +### Dropdown Selection Pattern + +Compact selection using native select element: + +```html + +
+

Add Training Course?

+ + + + + +
+``` + +### Display Selection Info + +Show details about the selected option: + +```html +
+ + +
+

Selected: None

+

Price: $0

+

+ You save: $0 +

+
+ + +
+``` + +### Grid Layout Example + +```html +
+

Popular Accessories

+ +
+
+ Carrying Case +

Carrying Case

+ $19.99 +
+ +
+ Filter Set +

Filter Set

+ $39.99 +
+ +
+ Extra Props +

Extra Props

+ $24.99 +
+
+ + + +
+``` + +### Key Attributes + +**Container:** +- `data-next-upsell-selector` - Marks as selection upsell +- `data-next-selector-id` - Unique ID for this selector + +**Options:** +- `data-next-upsell-option` - Individual option card +- `data-next-package-id` - Package for this option +- `data-next-selected="true"` - Default selection + +**Dropdown:** +- `data-next-upsell-select` - Native select element +- Option `value` - Package ID for that option + +### CSS Classes + +Options automatically get classes: +- `.next-selected` - Currently selected option +- `.next-upsell-option` - All option cards + +### Best Practices + +1. **Highlight Best Value**: Mark recommended option +2. **Show Comparisons**: Display savings or features +3. **Default Selection**: Pre-select most popular option +4. **Clear Pricing**: Show price for each option +5. **Visual Hierarchy**: Make selection clear + +## Quantity Upsells + +Let customers choose quantity before accepting the upsell. + +### Quantity Controls Pattern + +Simple quantity selector for bulk purchases: + +```html + +
+

Add Extra Batteries?

+

Never run out of power during your flights

+ +
+ + 1 + +
+ +

Price: $29.99 each

+ + + +
+``` + +### Quantity Toggle Cards Pattern + +Click to select quantity - perfect for bundles: + +```html + +
+

Stock Up & Save!

+

Choose your battery bundle:

+ +
+
+

1 Pack

+

$29.99

+
+ +
+

2 Pack

+

$49.99

+ Save $10 +
+ +
+

4 Pack

+

$89.99

+ Best Value! +
+
+ + + +
+``` + +### Advanced Quantity Display + +Show dynamic pricing based on quantity: + +```html +
+

Replacement Filters

+ +
+ + + +
+ +
+

Unit Price: $9.99

+

Total: $1 × + 9.99 = + $9.99 +

+
+ + +
+``` + +### Quantity Attributes + +**Controls:** +- `data-next-upsell-quantity="increase"` - Increment button +- `data-next-upsell-quantity="decrease"` - Decrement button +- `data-next-upsell-quantity="display"` - Shows current quantity + +**Toggle Cards:** +- `data-next-upsell-quantity-toggle` - Set specific quantity on click +- Value is the quantity to set (e.g., "1", "2", "4") + +### Styling Quantity Cards + +```css +/* Style selected quantity card */ +.quantity-card[data-selected="true"] { + border: 2px solid #007bff; + background: #f0f8ff; +} + +/* Disable decrease at minimum */ +button[data-next-upsell-quantity="decrease"]:disabled { + opacity: 0.5; + cursor: not-allowed; +} +``` + +### Common Patterns + +**Tiered Pricing:** + +```html +
+ 1 for $10 +
+
+ 3 for $25 (Save $5) +
+
+ 5 for $40 (Save $10) +
+``` + +**Subscription Quantities:** + +```html +
+ Monthly Supply - $29/mo +
+
+ 3 Month Supply - $79 (Save $8) +
+``` + +### Best Practices + +1. **Show Savings**: Highlight bulk discounts +2. **Display Total**: Show total price for quantity +3. **Preset Options**: Offer common quantities +4. **Visual Feedback**: Mark selected quantity +5. **Limits**: Set min/max quantity if needed + +## Related Documentation + +- [Cart System](/docs/campaign-cart/cart-system/) - Cart management basics diff --git a/docs/campaign-cart/utilities/debugger.md b/docs/campaign-cart/utilities/debugger.md new file mode 100644 index 00000000..49822997 --- /dev/null +++ b/docs/campaign-cart/utilities/debugger.md @@ -0,0 +1,376 @@ +# Debug Mode + +The Next Commerce JS SDK includes a comprehensive debug overlay system that helps developers inspect state, monitor events, and troubleshoot issues during development. + +## Enabling Debug Mode + +Add the `?debugger=true` parameter to any page URL: + +``` +https://yoursite.com/checkout?debugger=true +``` + +This will automatically: +1. Load and display the debug overlay at the bottom of the page +2. Create the global `nextDebug` object for console access +3. Set the logger to DEBUG level for verbose logging + +**Note:** The `nextDebug` object is only available when debug mode is enabled via the URL parameter. If you try to access it without the parameter, you'll get a "nextDebug is not defined" error. + +## Debug Overlay Features + +The debug overlay provides multiple panels for inspecting different aspects of the SDK: + +### 1. Cart Panel +- View current cart state and items +- Inspect cart totals, discounts, and calculations +- Monitor cart updates in real-time +- Test cart operations directly + +### 2. Config Panel +- View current SDK configuration +- Inspect API keys and endpoints +- Check feature flags and settings +- Monitor configuration changes + +### 3. Campaign Panel +- View campaign data and settings +- Inspect product/package information +- Monitor campaign-specific state +- Test campaign operations + +### 4. Checkout Panel +- Monitor checkout form state +- Inspect validation errors +- View payment method selections +- Debug checkout flow issues + +### 5. Event Timeline Panel +- Real-time event monitoring +- Visual timeline of SDK events +- Event details and payloads +- Performance timing information + +### 6. Storage Panel +- Inspect localStorage data +- View sessionStorage contents +- Monitor storage changes +- Clear storage for testing + +## Using Debug Mode + +### Global Access + +When debug mode is active (via `?debugger=true`), a global `nextDebug` object is available in the console: + +```javascript +// Access debug overlay controls +nextDebug.overlay.show() +nextDebug.overlay.hide() +nextDebug.overlay.toggle() +nextDebug.overlay.isVisible() + +// Debug mode management +nextDebug.enableDebug() // Enables debug mode programmatically +nextDebug.disableDebug() // Disables debug mode and removes URL param +nextDebug.toggleDebug() // Toggles debug mode on/off +nextDebug.isDebugMode() // Returns true/false + +// Access to SDK stores +nextDebug.stores.cart // Cart store +nextDebug.stores.campaign // Campaign store +nextDebug.stores.config // Config store +nextDebug.stores.checkout // Checkout store +nextDebug.stores.order // Order store +nextDebug.stores.attribution // Attribution store + +// SDK instance +nextDebug.sdk // NextCommerce instance + +// Utility methods +nextDebug.reinitialize() // Reinitialize the SDK +nextDebug.getStats() // Get initialization statistics +``` + +### Keyboard Shortcuts + +When the debug overlay is visible: +- `ESC` - Toggle overlay visibility +- `Tab` - Switch between panels +- `↑/↓` - Navigate within panels + +### Panel-Specific Features + +#### Cart Panel Actions +- Add/remove test items +- Apply test discounts +- Clear cart +- Simulate cart errors + +#### Event Timeline +- Filter events by type +- Export event log +- Clear timeline +- Pause/resume monitoring + +#### Storage Panel +- Search storage keys +- Edit values directly +- Export/import storage +- Clear specific keys + +## Visual Debugging + +### X-Ray Mode +The debugger includes an X-Ray mode that highlights SDK-enhanced elements: +- Blue outline: Active SDK attributes +- Green outline: Conditional visibility elements +- Red outline: Error states +- Yellow outline: Loading states + +Toggle X-Ray mode from any panel's toolbar. + +### Performance Monitoring +- Track render times +- Monitor API call durations +- Identify performance bottlenecks +- View memory usage + +## Advanced Features + +### Cart Operations + +```javascript +// Add items to cart +nextDebug.addToCart(packageId, quantity) + +// Remove items from cart +nextDebug.removeFromCart(packageId) + +// Update quantity +nextDebug.updateQuantity(packageId, quantity) + +// Add test items (packages 2, 7, 9) +nextDebug.addTestItems() +``` + +### Campaign Inspection + +```javascript +// Reload campaign data +nextDebug.loadCampaign() + +// Clear campaign cache +nextDebug.clearCampaignCache() + +// Get cache information +nextDebug.getCacheInfo() + +// Inspect specific package details +nextDebug.inspectPackage(packageId) + +// Test shipping methods +nextDebug.testShippingMethod(methodId) +``` + +### Analytics Debugging + +```javascript +// Get analytics status +await nextDebug.analytics.getStatus() + +// View active providers +await nextDebug.analytics.getProviders() + +// Track custom event +await nextDebug.analytics.track('custom_event', { data: 'value' }) + +// Enable analytics debug mode +await nextDebug.analytics.setDebugMode(true) + +// Invalidate analytics context +await nextDebug.analytics.invalidateContext() +``` + +### Attribution Debugging + +```javascript +// Debug attribution data +nextDebug.attribution.debug() + +// Get attribution for API +nextDebug.attribution.get() + +// Set funnel name +nextDebug.attribution.setFunnel('FUNNEL_NAME') + +// Set Everflow click ID +nextDebug.attribution.setEvclid('click_id') + +// Get current funnel +nextDebug.attribution.getFunnel() + +// Clear persisted funnel +nextDebug.attribution.clearFunnel() +``` + +### Order Management + +```javascript +// View upsell journey +nextDebug.order.getJourney() + +// Check if order is expired +nextDebug.order.isExpired() + +// Clear order cache +nextDebug.order.clearCache() + +// Get order statistics +nextDebug.order.getStats() +``` + +### Accordion Controls + +```javascript +// Open accordion +nextDebug.accordion.open('accordion-id') + +// Close accordion +nextDebug.accordion.close('accordion-id') + +// Toggle accordion +nextDebug.accordion.toggle('accordion-id') +``` + +### Custom Event Logging + +```javascript +// Log custom events to the timeline +window.dispatchEvent(new CustomEvent('debug:log', { + detail: { + type: 'custom', + message: 'My debug message', + data: { /* any data */ } + } +})); + +// Update debug content +window.dispatchEvent(new CustomEvent('debug:update-content')); +``` + +### State Snapshots +- Save current state for later comparison +- Export state as JSON +- Import saved states for testing +- Compare state differences + +### Network Monitoring +- View API requests and responses +- Inspect request headers +- Monitor request timing +- Simulate network errors + +## Best Practices + +1. **Development Only** - Never enable debug mode in production +2. **Performance** - Debug mode adds overhead; disable when testing performance +3. **Privacy** - Debug overlay may display sensitive data +4. **Storage** - Clear debug storage regularly to avoid conflicts + +## Troubleshooting + +### Debug Overlay Not Appearing +1. Ensure `?debugger=true` is in the URL +2. Check browser console for errors +3. Verify SDK is properly loaded +4. Try hard refresh (Ctrl+F5) + +### Performance Issues +1. Disable event timeline if too many events +2. Clear storage panel data +3. Reduce update frequency in settings +4. Use panel-specific views instead of "All" + +### Console Errors + +#### nextDebug is undefined +This happens when debug mode is not enabled. Solutions: + +1. **Add the URL parameter:** + ``` + https://yoursite.com?debugger=true + ``` + +2. **Enable programmatically (if SDK is loaded):** + ```javascript + // First, check if the SDK is loaded + if (window.next) { + // Add debugger=true to URL and reload + const url = new URL(window.location.href); + url.searchParams.set('debugger', 'true'); + window.location.href = url.toString(); + } + ``` + +3. **For local development, use the debug config:** + ```javascript + // In your config.js + window.nextConfig = { + debug: true, + // other config... + }; + ``` + +#### Panel-specific errors +```javascript +// Refresh specific panel +nextDebug.overlay.updateContent() + +// Reinitialize debug overlay +nextDebug.reinitialize() +``` + +## Security Considerations + +- Debug mode exposes internal state and sensitive data +- Never use in production environments +- Be cautious when sharing debug screenshots +- Clear sensitive data from storage panel +- The debug overlay displays: + - API keys and configuration + - Cart and order details + - Customer information + - Analytics data + - Internal SDK state + +## Related URL Parameters + +For debugging purposes, you may also use: +- `?debugger=true` - Enable debug mode and overlay + +For functional URL parameters like `forcePackageId` and `ref_id`, see [URL Parameters](/docs/campaign-cart/javascript-api/url-parameters/). + +## Meta Tag Configuration + +You can also enable debug mode via meta tag: + +```html + +``` + +Or via JavaScript configuration: + +```javascript +window.nextConfig = { + debug: true +}; +``` + +## Integration with Dev Tools + +The debug overlay complements browser dev tools: +- Console logs include debug context +- Network tab shows SDK requests +- Elements panel highlights SDK attributes +- Performance profiler includes SDK marks \ No newline at end of file diff --git a/docs/campaign-cart/utilities/exit-intent.md b/docs/campaign-cart/utilities/exit-intent.md new file mode 100644 index 00000000..0237a587 --- /dev/null +++ b/docs/campaign-cart/utilities/exit-intent.md @@ -0,0 +1,473 @@ +# Exit Intent Popup + +Show popups when users try to leave the page. Supports both simple image popups and custom HTML templates. + +## Basic Usage (Image Popup) + +```javascript +// Wait for SDK to be fully initialized +window.addEventListener('next:initialized', function() { + console.log('SDK initialized, setting up exit intent...'); + + // Simple image popup setup + next.exitIntent({ + image: 'https://cdn.prod.website-files.com/68106277c04984fe676e423a/6823ba8d65474fce67152554_exit-popup1.webp', + action: async () => { + const result = await next.applyCoupon('SAVE10'); + if (result.success) { + alert('Coupon applied successfully: ' + result.message); + } else { + alert('Coupon failed: ' + result.message); + } + } + }); + + // Optional: Listen to events for analytics + next.on('exit-intent:shown', (data) => { + console.log('Exit intent popup shown:', data); + }); + + next.on('exit-intent:clicked', (data) => { + console.log('Exit intent popup clicked:', data); + }); + + next.on('exit-intent:dismissed', (data) => { + console.log('Exit intent popup dismissed:', data); + }); +}); +``` + +## Template-Based Usage (Custom HTML) + +```html + + + + +``` + +## Simple Examples + +### Just Show a Popup (No Action) + +```javascript +function justShowPopup() { + next.exitIntent({ + image: 'https://example.com/just-popup.webp' + }); +} +``` + +### Redirect Instead of Coupon + +```javascript +function redirectExample() { + next.exitIntent({ + image: 'https://example.com/special-offer.webp', + action: () => { + window.location.href = '/special-offer'; + } + }); +} +``` + +### Conditional Popup Based on Cart + +```javascript +function conditionalExample() { + const cartCount = next.getCartCount(); + + if (cartCount === 0) { + next.exitIntent({ + image: 'https://example.com/empty-cart.webp', + action: () => window.location.href = '/products' + }); + } else { + next.exitIntent({ + image: 'https://example.com/discount.webp', + action: () => next.applyCoupon('SAVE10') + }); + } +} +``` + +### Template with Multiple Offers + +```html + +``` + +## Configuration Options + +| Parameter | Description | Required | Default | +|-----------|-------------|----------|---------| +| `image` | URL of popup image | Yes (if no template) | - | +| `template` | Name of template (matches `data-template` attribute) | Yes (if no image) | - | +| `action` | Function to execute on click/custom action | No | - | +| `showCloseButton` | Show X button on modal | No | false | +| `overlayClosable` | Allow clicking overlay to close | No | true | +| `maxTriggers` | Maximum times to show per session | No | 1 | +| `disableOnMobile` | Disable on mobile devices | No | true | +| `mobileScrollTrigger` | Enable scroll trigger on mobile | No | false | +| `useSessionStorage` | Remember dismissal in session | No | true | +| `sessionStorageKey` | Custom storage key | No | 'exit-intent-dismissed' | + +## Events + +### exit-intent:shown +Fired when popup is displayed: +```javascript +next.on('exit-intent:shown', (data) => { + // data.imageUrl - The image URL shown (if using image) + // data.template - The template name (if using template) +}); +``` + +### exit-intent:clicked +Fired when popup is clicked (image mode only): +```javascript +next.on('exit-intent:clicked', (data) => { + // data.imageUrl - The image URL clicked +}); +``` + +### exit-intent:dismissed +Fired when popup is closed without action: +```javascript +next.on('exit-intent:dismissed', (data) => { + // data.imageUrl - The image URL (if using image) + // data.template - The template name (if using template) +}); +``` + +### exit-intent:closed +Fired when close button is clicked: +```javascript +next.on('exit-intent:closed', (data) => { + // data.template - The template name +}); +``` + +### exit-intent:action +Fired when template action buttons are clicked: +```javascript +next.on('exit-intent:action', (data) => { + // data.action - The action type ('close', 'apply-coupon', 'custom') + // data.couponCode - The coupon code (if action is 'apply-coupon') +}); +``` + +## Advanced Examples + +### Multiple Exit Intent Strategies + +```javascript +// Different popups for different pages +window.addEventListener('next:initialized', function() { + const pathname = window.location.pathname; + + if (pathname.includes('/product')) { + // Product page - offer discount + next.exitIntent({ + image: '/images/10-percent-off.jpg', + action: () => next.applyCoupon('SAVE10') + }); + } else if (pathname.includes('/cart')) { + // Cart page - free shipping offer with template + next.exitIntent({ + template: 'exit-intent-shipping', + showCloseButton: true + }); + } else { + // Other pages - newsletter signup + next.exitIntent({ + template: 'exit-intent-newsletter', + overlayClosable: true + }); + } +}); +``` + +### Cart Value Based Offers + +```javascript +function setupDynamicExitIntent() { + const cartData = next.getCartData(); + const cartTotal = cartData?.totals?.total?.value || 0; + + if (cartTotal === 0) { + // Empty cart - show bestsellers + next.exitIntent({ + image: '/images/bestsellers.jpg', + action: () => window.location.href = '/bestsellers' + }); + } else if (cartTotal < 50) { + // Small cart - offer percentage discount + next.exitIntent({ + template: 'exit-intent-discount-15' + }); + } else if (cartTotal < 100) { + // Medium cart - offer free shipping + next.exitIntent({ + template: 'exit-intent-free-shipping' + }); + } else { + // Large cart - offer free gift + next.exitIntent({ + image: '/images/free-gift-100.jpg', + action: () => { + next.addToCart({ packageId: 99, quantity: 1 }); // Free gift item + } + }); + } +} +``` + +### Time-Based Exit Intent + +```javascript +// Show exit intent only after user has been on page for 30 seconds +let exitIntentTimer; + +window.addEventListener('next:initialized', function() { + exitIntentTimer = setTimeout(() => { + next.exitIntent({ + image: '/images/dont-leave-yet.jpg', + action: () => next.applyCoupon('COMEBACK') + }); + }, 30000); // 30 seconds +}); + +// Clear timer if user completes action +function onUserAction() { + clearTimeout(exitIntentTimer); +} +``` + +## Template Action Attributes + +When using templates, you can add special attributes to buttons and links to trigger actions: + +### Available Actions + +| Attribute | Additional Attributes | Description | +|-----------|----------------------|-------------| +| `data-exit-intent-action="close"` | - | Closes the modal | +| `data-exit-intent-action="apply-coupon"` | `data-coupon-code="CODE"` | Applies coupon and closes modal | +| `data-exit-intent-action="custom"` | - | Triggers the custom action function from options | + +### ⚠️ Important: How Actions Work + +**For Templates:** +- The `action` function is **ONLY** triggered by elements with `data-exit-intent-action="custom"` +- Clicking regular content (divs, text, etc.) does NOT trigger any action +- You MUST add the attribute to buttons/links that should trigger the action + +**For Images:** +- Clicking anywhere on the image triggers the action function + +### Working Example: + +```html + + + +``` + +## Styling + +The exit intent popup can be styled with CSS: + +```css +/* Exit intent overlay */ +[data-exit-intent="overlay"] { + /* Overlay is styled inline but you can override */ + background: rgba(0, 0, 0, 0.8) !important; +} + +/* Exit intent popup container */ +[data-exit-intent="popup"] { + /* Popup container styles */ +} + +/* Template popup specific */ +.exit-intent-template-popup { + background: white; + border-radius: 10px; + padding: 20px; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2); +} + +/* Close button */ +[data-exit-intent="close"] { + /* Close button styles */ +} + +/* Your custom template content */ +.exit-modal-content { + padding: 40px; + text-align: center; +} + +.exit-modal-content h2 { + margin-bottom: 20px; + font-size: 28px; +} + +.exit-modal-content button { + margin: 10px; + padding: 12px 30px; + font-size: 16px; + border-radius: 5px; +} +``` + +## Best Practices + +1. **Choose the Right Method**: + - Use **image popups** for simple, visual offers + - Use **templates** for interactive, complex modals + +2. **Mobile Optimization**: + - Default `disableOnMobile: true` for desktop-only + - Enable `mobileScrollTrigger` for mobile support + - Test template layouts on small screens + +3. **Session Management**: + - Use `maxTriggers: 1` to avoid annoying users + - Leverage `sessionStorage` to remember dismissals + - Reset with `next.exitIntent.reset()` if needed + +4. **Template Best Practices**: + - Keep templates hidden with `