// static/shared/js/upgrade-prompts.js /** * Upgrade Prompts System * * Provides contextual upgrade prompts based on: * - Usage limits approaching/reached * - Locked features * * Usage: * * 1. Initialize the store (auto-loads usage on init): *
* * 2. Show limit warning banner: * * * 3. Check before action: * * * 4. Show upgrade CTA on dashboard: * */ (function () { 'use strict'; const log = window.LogConfig?.log || console; /** * Upgrade Prompts Store */ const upgradeStore = { // State usage: null, loading: false, loaded: false, error: null, // Computed-like getters get hasLimitsApproaching() { return this.usage?.has_limits_approaching || false; }, get hasLimitsReached() { return this.usage?.has_limits_reached || false; }, get hasUpgradeRecommendation() { return this.usage?.upgrade_available && (this.hasLimitsApproaching || this.hasLimitsReached); }, get upgradeReasons() { return this.usage?.upgrade_reasons || []; }, get currentTier() { return this.usage?.tier || null; }, get nextTier() { return this.usage?.upgrade_tier || null; }, /** * Load usage data from API */ async loadUsage() { if (this.loaded || this.loading) return; try { this.loading = true; this.error = null; const response = await apiClient.get('/vendor/usage'); this.usage = response; this.loaded = true; log.debug('[UpgradePrompts] Loaded usage data', this.usage); } catch (error) { log.error('[UpgradePrompts] Failed to load usage:', error); this.error = error.message; } finally { this.loading = false; } }, /** * Get usage metric by name */ getMetric(name) { if (!this.usage?.usage) return null; return this.usage.usage.find(m => m.name === name); }, /** * Check if should show limit warning for a metric */ shouldShowLimitWarning(metricName) { const metric = this.getMetric(metricName); return metric && (metric.is_approaching_limit || metric.is_at_limit); }, /** * Check if at limit for a metric */ isAtLimit(metricName) { const metric = this.getMetric(metricName); return metric?.is_at_limit || false; }, /** * Get percentage used for a metric */ getPercentage(metricName) { const metric = this.getMetric(metricName); return metric?.percentage || 0; }, /** * Get formatted usage string (e.g., "85/100") */ getUsageString(metricName) { const metric = this.getMetric(metricName); if (!metric) return ''; if (metric.is_unlimited) return `${metric.current} (unlimited)`; return `${metric.current}/${metric.limit}`; }, /** * Get vendor code from URL */ getVendorCode() { const path = window.location.pathname; const segments = path.split('/').filter(Boolean); if (segments[0] === 'vendor' && segments[1]) { return segments[1]; } return null; }, /** * Get billing URL */ getBillingUrl() { const vendorCode = this.getVendorCode(); return vendorCode ? `/vendor/${vendorCode}/billing` : '#'; }, /** * Check limit before action, show modal if at limit */ async checkLimitAndProceed(limitType, onSuccess) { try { const response = await apiClient.get(`/vendor/usage/check/${limitType}`); if (response.can_proceed) { if (typeof onSuccess === 'function') { onSuccess(); } return true; } else { // Show upgrade modal this.showLimitReachedModal(limitType, response); return false; } } catch (error) { log.error('[UpgradePrompts] Failed to check limit:', error); // Proceed anyway on error (fail open) if (typeof onSuccess === 'function') { onSuccess(); } return true; } }, /** * Show limit reached modal */ showLimitReachedModal(limitType, response) { const limitNames = { 'orders': 'monthly orders', 'products': 'products', 'team_members': 'team members' }; const limitName = limitNames[limitType] || limitType; const message = response.message || `You've reached your ${limitName} limit.`; // Use browser confirm for simplicity - could be replaced with custom modal const shouldUpgrade = confirm( `${message}\n\n` + `Current: ${response.current}/${response.limit}\n\n` + (response.upgrade_tier_name ? `Upgrade to ${response.upgrade_tier_name} to get more ${limitName}.\n\nGo to billing page?` : 'Contact support for more capacity.') ); if (shouldUpgrade && response.upgrade_tier_code) { window.location.href = this.getBillingUrl(); } }, /** * Get limit warning banner HTML */ getLimitWarningHTML(metricName) { const metric = this.getMetric(metricName); if (!metric) return ''; const names = { 'orders': 'monthly orders', 'products': 'products', 'team_members': 'team members' }; const name = names[metricName] || metricName; if (metric.is_at_limit) { return `

You've reached your ${name} limit (${metric.current}/${metric.limit})

Upgrade
`; } else if (metric.is_approaching_limit) { return `

You're approaching your ${name} limit (${metric.current}/${metric.limit} - ${Math.round(metric.percentage)}%)

Upgrade
`; } return ''; }, /** * Get upgrade card HTML for dashboard */ getUpgradeCardHTML() { if (!this.usage?.upgrade_tier) return ''; const tier = this.usage.upgrade_tier; const reasons = this.usage.upgrade_reasons || []; return `

Upgrade to ${tier.name}

${reasons.length > 0 ? `
    ${reasons.map(r => `
  • • ${r}
  • `).join('')}
` : ''} ${tier.benefits.length > 0 ? `

Get access to:

    ${tier.benefits.slice(0, 4).map(b => `
  • ${b}
  • `).join('')}
` : ''}

€${(tier.price_monthly_cents / 100).toFixed(0)}

/month

Upgrade Now
`; }, /** * Get compact usage bar HTML */ getUsageBarHTML(metricName) { const metric = this.getMetric(metricName); if (!metric || metric.is_unlimited) return ''; const percentage = Math.min(metric.percentage, 100); const colorClass = metric.is_at_limit ? 'bg-red-500' : metric.is_approaching_limit ? 'bg-yellow-500' : 'bg-green-500'; return `
${metric.current} / ${metric.limit} ${Math.round(percentage)}%
`; }, /** * Reload usage data */ async reload() { try { this.loaded = false; await this.loadUsage(); } catch (error) { log.error('[UpgradePrompts] Failed to reload:', error); } } }; // Register Alpine store when Alpine is available document.addEventListener('alpine:init', () => { Alpine.store('upgrade', upgradeStore); log.debug('[UpgradePrompts] Registered as Alpine store'); }); // Also expose globally window.UpgradePrompts = upgradeStore; })();