Jake Vanderwerf
2026-01-06 2c955cebb5f1e01fbdb866b50d296fe9fbd852b8
assets/js/concise/Referral.js
@@ -1,121 +1,514 @@
/**
 * Referral Widget Manager
 * Handles both logged-in share widget and public code validation widget
 *
 */
class Referral {
   constructor() {
      this.container = document.querySelector('.jvb-referral');
      this.container = document.querySelector('aside.referral');
      if (!this.container) {
         return;
      }
      this.a11y = window.jvbA11y;
      this.toggle = document.querySelector('button[data-action="toggle-referral"]');
      this.hasCopy = navigator.clipboard && navigator.clipboard.writeText;
      this.initElements();
      this.initStore();
      this.initListeners();
      this.checkForReferral();
   }
   initElements()
   {
   initElements() {
      this.selectors = {
         copy: 'button.copy',
         login: '.login',
         copyBtn: '.copy-btn',
         checkCode: '.check-code-btn',
         submit: '[type=submit]',
         recentList: '.recent-referrals-list',
         stats: {
            codeUsed: '[data-stat="code_used"]',
            consultations: '[data-stat="consultations"]',
            treatments: '[data-stat="treatments"]',
            rewards: '[data-stat="total_rewards"]'
         },
      };
      this.forms = this.container.querySelectorAll('form');
      console.log(this.forms);
      this.popup = new window.jvbPopup({
         toggle: this.toggle,
         popup: this.container,
         name: 'Referral Box',
         onOpen: () => {
            this.forms.forEach(form => {
               form.addEventListener('submit', this.submitHandler);
            });
            this.container.addEventListener('click', this.clickHandler);
            this.container.addEventListener('input', this.inputHandler);
            this.bindEventListeners(true);
         },
         onClose: () => {
            this.forms.forEach(form => {
               form.removeEventListener('submit', this.submitHandler);
            });
            this.container.removeEventListener('click', this.clickHandler);
            this.container.removeEventListener('input', this.inputHandler);
            this.bindEventListeners(false);
         }
      });
      this.tabs = null;
      if (this.container.querySelector('nav.tabs')) {
         this.tabs = new window.jvbTabs(this.container, {updateURL: false});
      }
      this.ui = window.uiFromSelectors(this.selectors, this.container);
      this.ui = window.uiFromSelectors(this.selectors);
      if (!this.hasCopy) {
         document.querySelectorAll(this.selectors.copyBtn).forEach(btn => {
            btn.remove();
         });
      }
      this.formController = null;
   }
   initStore() {
      if (!this.isLoggedIn()) return;
      const stores = window.jvbStore.register(
         'referrals',
         [
            // Dashboard stats store
            {
               storeName: 'stats',
               keyPath: 'user_id',
               endpoint: 'referrals/stats',
               TTL: 5 * 60 * 1000,
               showLoading: false,
               delayFetch: false,
               filters: {
                  type: 'dashboard',
                  user: window.auth.getUser()
               }
            },
            // Referrals list store
            {
               storeName: 'list',
               keyPath: 'id',
               endpoint: 'referrals',
               TTL: 10 * 60 * 1000,
               showLoading: false,
               delayFetch: false,
               filters: {
                  user: window.auth.getUser(),
                  status: 'all',
                  limit: 50,
                  offset: 0
               }
            }
         ]
      );
      this.statsStore = stores.stats;
      this.listStore = stores.list;
      // Subscribe to store events
      if (this.statsStore) {
         this.statsStore.subscribe(this.handleStatsEvent.bind(this));
      }
      if (this.listStore) {
         this.listStore.subscribe(this.handleListEvent.bind(this));
      }
   }
   initListeners() {
      this.clickHandler = this.handleClick.bind(this);
      this.inputHandler = this.handleInput.bind(this);
      this.submitHandler = this.handleFormSubmit.bind(this);
      this.changeHandler = this.handleChange.bind(this);
   }
   handleClick(e) {
      if (e.target.classList.contains('.copy')) {
         let target = e.target.dataset.target;
         let value = this.container.querySelector(`#${target}`);
         value = (value) ? value.textContent : false;
         if (value) {
            this.handleCopy(e.target, value);
         }
   bindEventListeners(bind) {
      const method = bind ? 'addEventListener' : 'removeEventListener';
      this.forms.forEach(form => {
         form[method]('submit', this.submitHandler);
      });
      this.container[method]('click', this.clickHandler);
      this.container[method]('input', this.inputHandler);
   }
   isLoggedIn() {
      return Boolean(window.auth.getUser());
   }
   /**
    * Handle DataStore stats events
    */
   handleStatsEvent(event, data) {
      switch(event) {
         case 'data-loaded':
            if (data.items && data.items.length > 0) {
               this.updateStatsDisplay();
            }
            break;
         case 'fetch-error':
            console.error('Error loading stats:', data.error);
            break;
      }
   }
   handleChange(e) {
      if (e.target.id === 'referral-code') {
         window.debouncer.schedule(
            'check-referral',
            ()=> this.makeRequest('referrals/check-code', {code: e.target.value})),
            150
   /**
    * Handle DataStore list events
    */
   handleListEvent(event, data) {
      switch(event) {
         case 'data-loaded':
            // Let ViewController handle main list rendering
            // Only update sidebar preview if it exists
            if (this.ui.recentList) {
               this.renderRecentReferrals();
            }
            break;
         case 'fetch-error':
            console.error('Error loading referrals:', data.error);
            break;
      }
   }
   /**
    * Update stats display
    */
   updateStatsDisplay() {
      if (!this.statsStore.data.size === 0) return;
      let stats = this.statsStore.data.get(parseInt(window.auth.getUser()));
      const updates = {
         total: stats['code_used'] || 0,
         treated: stats.treatments || 0,
         pending: stats.pending || 0,
         rewards: '$' + parseFloat(stats['total_rewards'] || 0).toFixed(2)
      };
      Object.entries(updates).forEach(([key, value]) => {
         const element = this.container.querySelector(`[data-stat="${key}"]`);
         if (element) {
            element.textContent = value;
         }
      });
      // Also update stat cards if on dashboard
      const statCards = this.container.querySelectorAll('.stats .card');
      if (statCards.length >= 4) {
         statCards[0].querySelector('.stat-number').textContent = updates.code_used;
         statCards[1].querySelector('.stat-number').textContent = updates.consultations;
         statCards[2].querySelector('.stat-number').textContent = updates.treatments;
         statCards[3].querySelector('.stat-number').textContent = updates.total_rewards;
      }
   }
   handleClick(e) {
      const target = e.target.closest('.copy-btn, .check-code-btn, .attn');
      if (!target) return;
      if (target.classList.contains('copy-btn')) {
         this.handleCopyClick(target);
      } else if (target.classList.contains('check-code-btn')) {
         this.handleCheckCode(e);
      } else if (target.classList.contains('attn')) {
         target.classList.remove('attn');
      }
   }
   /**
    * Handle copy button click with fallback
    */
   handleCopyClick(button) {
      const targetId = button.dataset.target;
      const codeElement = this.container.querySelector(`#${targetId}`);
      if (!codeElement) return;
      const text = codeElement.textContent.trim();
      // Try clipboard API first
      if (this.hasCopy) {
         navigator.clipboard.writeText(text).then(() => {
            button.classList.toggle('success');
            setTimeout(() => {
               button.classList.remove('success');
            }, 1500);
         });
      }
   }
   /**
    * Handle error response with field-specific feedback
    */
   handleError(form, result) {
      const { message, code, field } = result;
      // If there's a specific field, highlight it
      if (field) {
         this.showFieldError(form, field, message);
      } else {
         // Show general form error using FormController pattern
         this.showFormStatus(form, 'error', message || 'Something went wrong. Please try again.');
      }
      // Handle specific error codes
      switch(code) {
         case 'duplicate_email':
            // Could add additional UI feedback
            break;
         case 'invalid_code':
            // Unlock the referral code field so user can correct it
            const codeInput = form.querySelector('[name="referral_code"]');
            if (codeInput) {
               codeInput.readOnly = false;
               codeInput.focus();
            }
            break;
         case 'turnstile_failed':
            // Refresh Turnstile widget if available
            if (window.turnstile && form.querySelector('.cf-turnstile')) {
               window.turnstile.reset();
            }
            break;
      }
   }
   /**
    * Show error for specific field
    */
   showFieldError(form, fieldName, message) {
      // Find the field wrapper (handles both direct names and referral_ prefixed names)
      let fieldWrapper = form.querySelector(`.field[data-field="${fieldName}"]`);
      if (!fieldWrapper) {
         fieldWrapper = form.querySelector(`.field[data-field="referral_${fieldName}"]`);
      }
      if (!fieldWrapper) {
         // If no field wrapper found, show as general form error
         this.showFormStatus(form, 'error', message);
         return;
      }
      const input = fieldWrapper.querySelector('input, textarea, select');
      const validationMessage = fieldWrapper.querySelector('.validation-message');
      const errorIcon = fieldWrapper.querySelector('.validation-icon.error');
      const successIcon = fieldWrapper.querySelector('.validation-icon.success');
      if (!input) {
         this.showFormStatus(form, 'error', message);
         return;
      }
      // Apply error state (following FormController pattern)
      fieldWrapper.classList.remove('has-success');
      fieldWrapper.classList.add('has-error');
      input.classList.add('error');
      input.setAttribute('aria-invalid', 'true');
      // Show error icon, hide success icon
      if (errorIcon) errorIcon.hidden = false;
      if (successIcon) successIcon.hidden = true;
      // Show error message
      if (validationMessage) {
         validationMessage.textContent = message;
         validationMessage.hidden = false;
      }
      // Focus the problematic field
      input.focus();
      // Announce to screen readers
      this.a11y?.announce(`Error in ${fieldName}: ${message}`);
   }
   showFormStatus(form, status, message = '') {
      const statusWrap = form.querySelector('.fstatus');
      if (!statusWrap) {
         console.warn('No .fstatus element found in form');
         return;
      }
      statusWrap.hidden = false;
      const statusElement = statusWrap.querySelector('.message');
      // Clear previous state
      statusWrap.querySelector('.icon')?.remove();
      statusWrap.querySelector('.actions')?.remove();
      // Status messages
      const messages = {
         'saving': 'Sending...',
         'submitted': 'Sent successfully!',
         'error': 'Something went wrong',
         'checking': 'Checking code...'
      };
      // Status icons (using window.getIcon like FormController)
      const icons = {
         'submitted': 'check-circle',
         'error': 'close-circle',
         'checking': 'loading'
      };
      // Add icon if available
      if (icons[status] && window.getIcon) {
         const icon = window.getIcon(icons[status]);
         if (icon) {
            statusWrap.prepend(icon);
         }
      }
      // Set message
      if (statusElement) {
         statusElement.textContent = message || messages[status] || status;
      }
      // Add loading class for pending states
      statusWrap.classList.toggle('loading', ['saving', 'checking'].includes(status));
      // Auto-hide success messages
      if (status === 'submitted') {
         setTimeout(() => statusWrap.hidden = true, 3000);
      }
      // Announce to screen readers
      if (this.a11y) {
         this.a11y.announce(message || messages[status] || status);
      }
   }
   /**
    * Clear all form errors
    */
   clearFormErrors(form) {
      // Clear field-level errors
      form.querySelectorAll('.field.has-error, .field.has-success').forEach(fieldWrapper => {
         this.clearFieldValidation(fieldWrapper);
      });
      // Hide form status
      const statusWrap = form.querySelector('.fstatus');
      if (statusWrap) {
         statusWrap.hidden = true;
      }
   }
   clearFieldValidation(fieldWrapper) {
      if (!fieldWrapper) return;
      const input = fieldWrapper.querySelector('input, textarea, select');
      const validationMessage = fieldWrapper.querySelector('.validation-message');
      const validationIcons = fieldWrapper.querySelectorAll('.validation-icon');
      // Remove classes
      fieldWrapper.classList.remove('has-error', 'has-success');
      if (input) {
         input.classList.remove('error');
         input.removeAttribute('aria-invalid');
      }
      // Hide icons and messages
      validationIcons.forEach(icon => icon.hidden = true);
      if (validationMessage) {
         validationMessage.hidden = true;
         validationMessage.textContent = '';
      }
   }
   handleInput(e) {
      if (e.target.id === 'referral-code') {
      if (e.target.id === 'referral_code' || e.target.name === 'referral_code') {
         e.target.value = e.target.value.toUpperCase();
      }
      // Clear field error when user types
      const fieldWrapper = e.target.closest('.field');
      if (fieldWrapper && fieldWrapper.classList.contains('has-error')) {
         this.clearFieldValidation(fieldWrapper);
      }
   }
   // ==========================================
   // SHARE WIDGET (Logged-In Users)
   // ==========================================
   /**
    * Handle code verification
    */
   async handleCheckCode(e) {
      e.preventDefault();
   initShareWidget() {
      this.initCopyButton();
      this.loadStats();
      const form = e.target.closest('form');
      const codeInput = form.querySelector('[name="referral_code"]');
      const statusDiv = form.querySelector('.code-status');
      if (!codeInput || !statusDiv) return;
      const code = codeInput.value.trim();
      if (!code) {
         this.showCodeStatus(statusDiv, 'Please enter a code', 'error');
         return;
      }
      // Show loading
      statusDiv.hidden = false;
      statusDiv.className = 'code-status loading';
      statusDiv.innerHTML = '<span class="spinner"></span> Checking...';
      try {
         const result = await this.validateCodeOnly(code);
         if (result.success) {
            this.showCodeStatus(
               statusDiv,
               `✓ Valid! Referred by ${result.referrer_name}`,
               'success'
            );
         } else {
            this.showCodeStatus(statusDiv, result.message || 'Invalid code', 'error');
         }
      } catch (error) {
         console.error('Error checking code:', error);
         this.showCodeStatus(statusDiv, 'Error checking code', 'error');
      }
   }
   /**
    * Show code verification status
    */
   showCodeStatus(statusDiv, message, type) {
      statusDiv.hidden = false;
      statusDiv.className = `code-status ${type}`;
      statusDiv.textContent = message;
      if (type === 'error') {
         setTimeout(() => {
            statusDiv.hidden = true;
         }, 5000);
      }
   }
   /**
    * Check for ?ref parameter in URL and pre-fill code
    */
   async checkForReferral() {
      const isLoggedIn = this.getUrlParameter('seeReferral');
      const refCode = this.getUrlParameter('ref');
      if (!isLoggedIn && !refCode) {
         return;
      }
      if (!refCode) {
         this.popup.openPopup();
      const refName = this.getUrlParameter('rname');
      const refEmail = this.getUrlParameter('remail');
      const seeReferral = this.getUrlParameter('seeReferral');
      if (!refCode && !seeReferral) {
         return;
      }
      const codeInput = this.container.querySelector('#referral-code-input');
      // If logged in user just wants to see referral popup
      if (seeReferral && !refCode) {
         this.popup.openPopup();
         this.removeUrlParameter('seeReferral');
         return;
      }
      const codeInput = this.container.querySelector('[name="referral_code"]');
      if (!codeInput) return;
      // Convert to uppercase
@@ -123,25 +516,44 @@
      // Pre-fill the code input
      codeInput.value = code;
      codeInput.readOnly = true; // Make it read-only since it came from link
      codeInput.readOnly = true;
      this.popup.togglePopup();
      // If we have token data, prefill name and email too
      if (refName || refEmail) {
         const nameInput = this.container.querySelector('[name="referral_name"]');
         if (nameInput) {
            nameInput.value = refName;
         }
      // Validate the code immediately to show referrer info
         const emailInput = this.container.querySelector('[name="referral_email"]');
         if (emailInput) {
            emailInput.value = refEmail;
         }
      }
      // Open the sidebar popup
      this.popup.openPopup();
      // Validate the code immediately
      try {
         const referrer = await this.validateCodeOnly(code);
         if (referrer.success) {
            // Show referrer info banner
            this.showReferrerBanner(referrer.referrer_name, code);
            const statusDiv = codeInput.closest('form').querySelector('.code-status');
            if (statusDiv) {
               this.showCodeStatus(
                  statusDiv,
                  `✓ ${referrer.referrer_name} invited you!`,
                  'success'
               );
            }
            // Focus on name input (first empty field)
            const nameInput = this.container.querySelector('#referral-name');
            if (nameInput) {
            // Focus on name input if not prefilled
            const nameInput = this.container.querySelector('[name="referral_name"]');
            if (nameInput && !nameInput.value) {
               nameInput.focus();
            }
         } else {
            // Invalid code - make input editable and show error
            codeInput.readOnly = false;
            this.showMessage('This referral link is invalid. Please enter a valid code.', 'error');
         }
@@ -150,21 +562,17 @@
         codeInput.readOnly = false;
      }
      // Clean up URL (remove ?ref parameter)
      // Clean up URL
      this.removeUrlParameter('ref');
      this.removeUrlParameter('rname');
      this.removeUrlParameter('remail');
   }
   /**
    * Get URL parameter value
    */
   getUrlParameter(name) {
      const urlParams = new URLSearchParams(window.location.search);
      return urlParams.get(name);
   }
   /**
    * Remove URL parameter (clean URL)
    */
   removeUrlParameter(name) {
      const url = new URL(window.location);
      url.searchParams.delete(name);
@@ -172,13 +580,14 @@
   }
   /**
    * Validate code without registering (just check if valid)
    * Validate code without registering
    */
   async validateCodeOnly(code) {
      const response = await fetch(`${jvbSettings.api}/referrals/check-code`, {
      const response = await fetch(`${jvbSettings.api}referrals/code`, {
         method: 'POST',
         headers: {
            'Content-Type': 'application/json'
            'Content-Type': 'application/json',
            'X-WP-Nonce': window.auth.getNonce()
         },
         body: JSON.stringify({ code: code })
      });
@@ -187,203 +596,142 @@
   }
   /**
    * Show banner with referrer info
    * Render recent referrals list
    */
   showReferrerBanner(referrerName, code) {
      const header = this.container.querySelector('.referral-header');
      if (!header) return;
      // Create banner
      const banner = document.createElement('div');
      banner.className = 'referrer-banner';
      banner.innerHTML = `
            <div class="banner-icon">🎉</div>
            <div class="banner-content">
                <strong>${window.escapeHtml(referrerName)}</strong> referred you!
                <div class="banner-code">Code: <code>${window.escapeHtml(code)}</code></div>
            </div>
        `;
      // Insert after header
      header.parentNode.insertBefore(banner, header.nextSibling);
      // Update header text
      const headerTitle = header.querySelector('h3');
      if (headerTitle) {
         headerTitle.textContent = 'Complete Your Registration';
      }
      const headerDesc = header.querySelector('p');
      if (headerDesc) {
         headerDesc.textContent = 'Enter your details below to claim your welcome reward!';
      }
   }
   /**
    * Copy referral link to clipboard
    */
   handleCopy(button, text = '') {
      if (text === '' || typeof text !== 'string') {
   renderRecentReferrals() {
      let container = this.ui.recentList;
      let referrals = Array.from(this.listStore.data.values());
      if (!referrals || referrals.length === 0) {
         container.innerHTML = '<p class="no-referrals">Share your code to get started!</p>';
         return;
      }
      let originalText = button.textContent;
      if (navigator.clipboard || navigator.clipboard.writeText) {
         navigator.clipboard.writeText(text).then(() => {
            button.textContent = 'Copied!';
            button.style.background = '#00a32a';
            setTimeout(() => {
               button.textContent = originalText;
               button.style.background = '';
            }, 2000);
         })
      }
   }
   async loadStats() {
      const statsContainer = this.container.querySelector('.referral-stats');
      if (!statsContainer) return;
      try {
         const response = await fetch(`${jvbSettings.api}/referrals/stats`, {
            headers: { 'X-WP-Nonce': jvbSettings.nonce }
         });
         const data = await response.json();
         if (data.success && data.stats) {
            this.updateStats(data.stats);
         }
      } catch (error) {
         console.error('Error loading stats:', error);
      }
   }
   /**
    * Update stats display
    */
   updateStats(stats) {
      const elements = {
         total: this.container.querySelector('[data-stat="total"]'),
         treated: this.container.querySelector('[data-stat="treated"]'),
         pending: this.container.querySelector('[data-stat="pending"]'),
         rewards: this.container.querySelector('[data-stat="rewards"]')
      };
      if (elements.total) elements.total.textContent = stats.total_referrals || 0;
      if (elements.treated) elements.treated.textContent = stats.treated_count || 0;
      if (elements.pending) elements.pending.textContent = stats.pending_count || 0;
      if (elements.rewards) {
         elements.rewards.textContent = '$' + parseFloat(stats.available_rewards || 0).toFixed(2);
      }
      container.innerHTML = referrals.map(ref => `
         <div class="referral-item">
            <div class="referral-info">
               <strong>${window.escapeHtml(ref.referee_name)}</strong>
               <span class="status-badge">${ref.referral_status}</span>
            </div>
            <div class="referral-date">${window.formatTimeAgo(ref.referred_at)}</div>
         </div>
      `).join('');
   }
   /**
    * Handle form submission
    */
   async handleFormSubmit(event) {
      console.log('Form Submission!');
      window.debouncer.cancel('check-referral');
      event.preventDefault();
      console.log('Still working?');
      const form = event.target;
      // Get form data
      const formData = new FormData(form);
      let data = {};
      // Disable form
      // Clear any existing errors
      this.clearFormErrors(form);
      this.setFormLoading(true, form);
      try {
         let result = { success: false, message: '' };
         let result = {success: false, message: ''};
         console.log(form);
         console.log(form.id);
         if (form.id === 'referral-code-form') {
            if (!formData.get('name')) {
               result.message += 'We need your name to know who you are.';
            }
            if (!formData.get('email')) {
               result.message += 'We need your email to confirm you have access to it.';
            }
            if (!formData.get('referral_code')) {
               result.message += 'We need the referral code to know who sent you.';
            }
            if (formData.get('name') && formData.get('email') && formData.get('referral_code')) {
               data.name = formData.get('name');
               data.email = formData.get('email');
               data.code = formData.get('referral_code');
               result = await this.makeRequest('referrals/register', data);
            }
         } else if (form.id === 'login-form' && formData.get('login-email')) {
            data.type = 'login';
            data.email = formData.get('login-email');
            data.context = {};
            data.context['redirect_to'] = window.location.href+'?seeReferral=1';
            console.log('Making Request with: ', data);
            result = await this.makeRequest('magic', data);
         }
            // Registration with referral code - goes to LoginRoutes
            let data = {
               name: formData.get('referral_name'),
               email: formData.get('referral_email'),
               referral_code: formData.get('referral_code')
            };
            if (formData.get('cf-turnstile-response')) {
               data['cf-turnstile-response'] = formData.get('cf-turnstile-response');
            }
            if (!data.name || !data.email || !data.referral_code) {
               result.message = 'Please fill in all fields';
            } else {
               result = await this.makeRequest('auth/register', data); // UPDATED endpoint
            }
         } else if (form.id === 'login-form') {
            let data = {
               type: 'login',
               email: formData.get('login_email'),
               context: {
                  redirect_to: window.location.href + '?seeReferral=1'
               }
            };
            if (formData.get('cf-turnstile-response')) {
               data['cf-turnstile-response'] = formData.get('cf-turnstile-response');
            }
            if (!data.email) {
               result.message = 'Please fill in your email';
            } else {
               result = await this.makeRequest('magic', data);
            }
         }
         if (result.success) {
            this.handleSuccess(result);
            this.handleSuccess(form, result);
         } else {
            this.showMessage(result.message || 'Something went wrong. Please try again.', 'error');
            this.setFormLoading(false, form);
            this.handleError(form, result);
         }
      } catch (error) {
         console.error('Error registering:', error);
         this.showMessage('Something went wrong. Please try again.', 'error');
         this.setFormLoading(false, form);
         console.error('Error submitting form:', error);
         this.showFormMessage(form, 'Something went wrong. Please try again.', 'error');
      } finally {
         this.setFormLoading(false, form);
      }
   }
   async makeRequest(endpoint, data) {
      if (![
      const validEndpoints = [
         'magic',
         'referrals/register',
         'referrals/check-code'
      ].includes(endpoint)) {
         return {success:false, message: 'Something went wrong (Invalid endpoint).'}
         'auth/register'
      ];
      if (!validEndpoints.includes(endpoint)) {
         return { success: false, message: 'Invalid endpoint' };
      }
      console.log('Endpoint: ', endpoint);
      console.log('Data: ', data);
      const response = await fetch(`${jvbSettings.api}${endpoint}`, {
         method: 'POST',
         headers: {
            'Content-Type': 'application/json',
            'X-WP-Nonce': jvbSettings.nonce,
            'X-WP-Nonce': window.auth.getNonce(),
         },
         body: JSON.stringify(data)
      });
      // Add error handling to see the actual response
      if (!response.ok) {
         const errorText = await response.text();
         console.error('Error response:', response.status, errorText);
         try {
            return JSON.parse(errorText);
         } catch {
            return { success: false, message: 'Server error' };
         }
      }
      return await response.json();
   }
   /**
    * Show success state
    */
   handleSuccess(result) {
      //Hide forms
      this.container.querySelectorAll('form').forEach(form => {
         window.fade(form, false);
      });
      const successState = this.container.querySelector('.success-message');
      if (!successState) return;
   handleSuccess(form, result) {
      // Hide form
      form.style.display = 'none';
      // Show success message
      successState.hidden = false;
      const successDiv = form.nextElementSibling;
      if (successDiv && successDiv.classList.contains('success-content')) {
         successDiv.hidden = false;
      // Scroll to message
      successState.scrollIntoView({
         behavior: 'smooth',
         block: 'center'
      });
         // Scroll to message
         successDiv.scrollIntoView({
            behavior: 'smooth',
            block: 'center'
         });
      }
      // Fire custom event
      this.dispatchEvent('emailSent', {
@@ -392,38 +740,36 @@
   }
   /**
    * Set form loading state
    * Show message in form status area
    */
   setFormLoading(loading, form) {
      const inputs = form.querySelectorAll('input');
   showFormMessage(form, text, type = 'error') {
      const status = form.querySelector('.status');
      if (!status) return;
      inputs.forEach(input => input.disabled = loading);
      let status = form.querySelector('.status');
      let message = status.querySelector('.message');
      status.hidden = loading;
      status.classList.toggle('loading', loading);
      if (loading) {
         message.textContent = 'Checking with server...';
      } else {
         message.textContent = '';
      const message = status.querySelector('.message');
      if (message) {
         message.textContent = text;
      }
      status.hidden = false;
      status.className = `status ${type}`;
      if (type === 'error') {
         setTimeout(() => {
            status.hidden = true;
         }, 5000);
      }
   }
   /**
    * Show message
    * Set form loading state
    */
   showMessage(text, type = 'success') {
      const messageDiv = this.container.querySelector('#referral-message');
      if (!messageDiv) return;
   setFormLoading(loading, form) {
      const inputs = form.querySelectorAll('input, button, textarea, select');
      inputs.forEach(input => input.disabled = loading);
      messageDiv.textContent = text;
      messageDiv.className = 'message ' + type;
      messageDiv.style.display = 'block';
      if (type === 'error') {
         setTimeout(() => {
            messageDiv.style.display = 'none';
         }, 5000);
      if (loading) {
         this.showFormStatus(form, 'saving');
      }
   }
@@ -437,10 +783,14 @@
      });
      this.container.dispatchEvent(event);
   }
}
document.addEventListener('DOMContentLoaded', () => {
   window.jvbReferral = new Referral();
document.addEventListener('DOMContentLoaded', async function () {
   window.auth.subscribe((event) => {
      if (event === 'auth-loaded') {
         window.jvbReferral = new Referral();
      }
   });
});