Jake Vanderwerf
2025-11-25 2a2303d1dccc120dd7aa5f6b6ade0f89e0064850
inc/managers/ReferralManager.php
@@ -44,8 +44,8 @@
      global $wpdb;
      $this->wpdb = $wpdb;
      $this->cache = CacheManager::for('referrals', WEEK_IN_SECONDS);
      $this->referrals_table = BASE . 'referrals';
      $this->rewards_table = BASE . 'referral_rewards';
      $this->referrals_table = $wpdb->prefix . BASE . 'referrals';
      $this->rewards_table = $wpdb->prefix . BASE . 'referral_rewards';
      $this->magic_link = new MagicLinkManager();
      $this->referralPage = $this->getReferralPageId();
@@ -73,9 +73,6 @@
      // Schedule cron jobs for reports
      $this->registerCronJobs();
      // Register admin subpage
//    add_filter('jvbAdminSubpages', [$this, 'addSubpage'], 10, 1);
      // Add admin bar label for referral page
      add_action('admin_bar_menu', [$this, 'addReferralPageLabel'], 999);
@@ -87,6 +84,8 @@
      // Handle settings save
      add_action('admin_init', [$this, 'registerSettings']);
      // Handle admin page form submission
      add_filter('jvb_admin_page_submission', [$this, 'handleAdminSubmission'], 10, 3);
   }
   public function addLoginInputs(string $action):void
@@ -168,7 +167,7 @@
    */
   public function getUserReferralCode(int $user_id, ?string $custom_code = null)
   {
      $user = get_user_by('ID', $user_id);
      $user = get_userdata($user_id);
      if (!$user) {
         return new WP_Error('invalid_user', 'User not found');
      }
@@ -982,85 +981,116 @@
      return $actions;
   }
   /**
    * Display referral sidebar for non-logged-in users
    */
   function getUnloggedInReferral(): string
   {
      ob_start();
      JVB()->connect('cloudflare')->renderTurnstile();
      $turnstile = ob_get_clean();
      $meta = new MetaForm();
      $codeForm = '<form id="referral-code-form">
               '.jvbFormStatus().$meta->return('referral_name', null, [
                  'required'  => true,
                  'type'      => 'text',
                  'label'     => 'Your Name',
                  'placeholder'=> 'Mister Meeseeks',
                  'autocomplete'=>'name'
               ]).
               $meta->return('referral_email', null, [
                  'required'  => true,
                  'type'      => 'email',
                  'label'     => 'Your Email',
                  'placeholder'=> 'look@me.com',
                  'autocomplete'=> 'email'
               ]).
               $meta->return('referral_code', null, [
                  'required'  => true,
                  'type'      => 'text',
                  'label'     => 'Referral Code',
                  'pattern'   => '[A-Za-z0-9]+',
                  'maxLength' => 20,
                  'autocomplete'=>'off'
               ]).'
               <button type="submit">
                  Get Started
               </button>
      $reward_text = $this->getRewardText(true);
               <p class="helper-text">
                  We\'ll send you a link to complete your registration.
               </p>
               '.$turnstile.'
            </form><div class="success-content" hidden>
               <h3>Check Your Email!</h3>
               <p>We\'ve sent you a magic link to complete your registration. Click the link to activate your account and claim your reward!</p>
               <p class="hint">Can\'t find it? Check your spam folder.</p>
            </div>';
      // Pre-fill code if from referral link
      $prefill_code = $_GET['ref'] ?? '';
      $referrer_name = '';
      if ($prefill_code) {
         $referrer = $this->getUserByReferralCode($prefill_code);
         $referrer_name = $referrer ? strtok($referrer->display_name, ' ') : '';
      }
      $loginForm = '<form id ="login-form">
      '.jvbFormStatus().$meta->return('login_email', null, [
      $codeForm = '<div class="referral-reward-banner">
      '.jvbIcon('confetti').'
      <h4>Get ' . esc_html($reward_text) . '!</h4>
      ' . ($referrer_name ? '<p>' . esc_html($referrer_name) . ' invited you to join us</p>' : '') . '
   </div>
   <form id="referral-code-form">
            '.jvbFormStatus().$meta->return('referral_name', null, [
            'required'  => true,
            'type'      => 'text',
            'label'     => 'Your Name',
            'placeholder'=> 'Mister Meeseeks',
            'autocomplete'=>'name'
         ]).
         $meta->return('referral_email', null, [
            'required'  => true,
            'type'      => 'email',
            'label'     => 'Your Email',
            'placeholder'=> 'look@me.com',
            'autocomplete'=> 'email'
         ]).
         $meta->return('referral_code', $prefill_code, [
            'required'  => true,
            'type'      => 'text',
            'label'     => 'Referral Code',
            'pattern'   => '[A-Za-z0-9]+',
            'maxLength' => 20,
            'autocomplete'=>'off',
            'data-referrer' => $referrer_name
         ]).'
            <button type="button" class="button-secondary check-code-btn">
               '.jvbIcon('check-circle', ['size' => 16]).' Verify Code
            </button>
            <div class="code-status" hidden></div>
            <button type="submit">
               Get Started
            </button>
            <p class="helper-text">
               We\'ll send you a link to complete your registration.
            </p>
            '.$turnstile.'
         </form>
         <div class="success-content" hidden>
            <h3>Check Your Email!</h3>
            <p>We\'ve sent you a magic link to complete your registration. Click the link to activate your account and claim your reward!</p>
            <p class="hint">Can\'t find it? Check your spam folder.</p>
         </div>';
      $loginForm = '<form id="login-form">
   '.jvbFormStatus().$meta->return('login_email', null, [
            'required'  => true,
            'type'      => 'email',
            'label'     => 'Your Email',
            'autocomplete'=>'email'
         ]).'
      '.$turnstile.'
      <button type="submit">Login With Magic Link</button>
   '.$turnstile.'
   <button type="submit">Login With Magic Link</button>
</form>
   <div class="success-content" hidden>
      <h3>Check Your Email!</h3>
      <p>We\'ve sent you a magic link to log in - no password required! Click the link in your email to log in.</p>
      <p class="hint">Can\'t find it? Check your spam folder.</p>
   </div>';
<div class="success-content" hidden>
   <h3>Check Your Email!</h3>
   <p>We\'ve sent you a magic link to log in - no password required! Click the link in your email to log in.</p>
   <p class="hint">Can\'t find it? Check your spam folder.</p>
</div>';
      $footer = '<div class="referral-footer">
      <a href="' . wp_login_url() . '" class="text-link">Prefer to use a password?</a>
   </div>';
      $tabs = [
         'enterCode' => [
            'title'  => 'Have a Code?',
            'description'  => [
               'Enter the code given to you to get 20% off your first treatment!'
               'Enter your referral code to get started'
            ],
            'content'   => $codeForm
         ],
         'login'  => [
            'title'     => 'Login',
            'description'  => [
               'Login to see your rewards'
               'Already have an account? Log in to see your rewards'
            ],
            'content'   => $loginForm
            'content'   => $loginForm.$footer
         ]
      ];
      return jvbRenderTabs($tabs, true);
   }
   protected function getReferralSuccessMessage(string $code): string
   {
      $referrer = $this->getUserByReferralCode($code);
@@ -1106,86 +1136,73 @@
      return ob_get_clean();
   }
   public function getLoggedInReferral(int $user_id):string
   /**
    * Display referral sidebar for logged-in users
    */
   public function getLoggedInReferral(int $user_id): string
   {
      // Logged-in user widget
      $referral_code = get_user_meta($user_id, BASE . 'referral_code', true);
      // Generate code if user doesn't have one
      if (empty($referral_code)) {
         $referral_code = $this->getUserReferralCode($user_id);
         if (is_wp_error($referral_code)) {
            return '';
         }
      $referral_code = $this->getUserReferralCode($user_id);
      if (is_wp_error($referral_code)) {
         return '';
      }
      $share_url = $this->getShareURL($referral_code);
      ob_start();
      ?>
         <header>
            <h3>Share the â™¡</h3>
            <p>Invite your friends.</p>
            <p>Earn rewards when they book!</p>
         </header>
      <header>
         <h3>Share the â™¡</h3>
         <p>Invite friends. Earn rewards.</p>
      </header>
      <div class="row even share-buttons">
         <a href="mailto:?subject=<?php echo urlencode('Check out ' . get_bloginfo('name')); ?>&body=<?php echo urlencode('I thought you might be interested: ' . $share_url); ?>"
            class="share-btn email-share">
            <?php echo jvbIcon('envelope', ['size' => 20]); ?>
            Email
         </a>
         <a href="https://www.facebook.com/sharer/sharer.php?u=<?php echo urlencode($share_url); ?>"
            target="_blank"
            rel="noopener noreferrer"
            class="share-btn facebook-share">
            <?php echo jvbIcon('facebook-logo', ['size' => 20]); ?>
            Facebook
         </a>
         <a href="https://twitter.com/intent/tweet?url=<?php echo urlencode($share_url); ?>&text=<?php echo urlencode('Check this out!'); ?>"
            target="_blank"
            rel="noopener noreferrer"
            class="share-btn twitter-share">
            <?php echo jvbIcon('twitter-logo', ['size' => 20]); ?>
            Twitter
         </a>
      </div>
      <?php $this->getShareButtons($user_id); ?>
      <div class="copy-section">
         <h4>Your Referral Link</h4>
         <div class="row btw">
            <code id="your-referral-link"><?= esc_url($share_url)?></code>
            <button type="button" class="copy" data-target="your-referral-link">
               Copy Link
         <div class="copy-group">
            <code id="referral-link" class="copy-target"><?= esc_url($share_url) ?></code>
            <button type="button" class="copy-btn" data-target="referral-link" aria-label="Copy referral link">
               <?php echo jvbIcon('copy', ['size' => 16]); ?>
            </button>
         </div>
         <h4>Your Code</h4>
         <div class="row btw">
            <code id="your-referral-code"><?=esc_html($referral_code)?></code>
            <button type="button" class="copy" data-target="your-referral-code">
               Copy Code
         <div class="copy-group">
            <code id="referral-code" class="copy-target"><?= esc_html($referral_code) ?></code>
            <button type="button" class="copy-btn" data-target="referral-code" aria-label="Copy referral code">
               <?php echo jvbIcon('copy', ['size' => 16]); ?>
            </button>
         </div>
      </div>
         <div class="row btw referral-stats">
            <div class="stat-item">
               <span class="stat-value" data-stat="total">-</span>
               <span class="stat-label">Total Referrals</span>
            </div>
            <div class="stat-item">
               <span class="stat-value" data-stat="treated">-</span>
               <span class="stat-label">Successful</span>
            </div>
            <div class="stat-item">
               <span class="stat-value" data-stat="pending">-</span>
               <span class="stat-label">Pending</span>
            </div>
            <div class="stat-item">
               <span class="stat-value" data-stat="rewards">$0.00</span>
               <span class="stat-label">Available Rewards</span>
            </div>
      <div class="recent-referrals-section">
         <h4>Recent Referrals</h4>
         <div class="recent-referrals-list" data-user-id="<?= $user_id ?>">
            <div class="loading">Loading...</div>
         </div>
      </div>
      <div class="stats-summary">
         <div class="stat-row">
            <span class="stat-label">Total Referrals</span>
            <span class="stat-value" data-stat="total">-</span>
         </div>
         <div class="stat-row">
            <span class="stat-label">Successful</span>
            <span class="stat-value" data-stat="treated">-</span>
         </div>
         <div class="stat-row">
            <span class="stat-label">Pending</span>
            <span class="stat-value" data-stat="pending">-</span>
         </div>
         <div class="stat-row highlight">
            <span class="stat-label">Available Rewards</span>
            <span class="stat-value" data-stat="rewards">$0.00</span>
         </div>
      </div>
      <a href="<?= get_home_url(null, '/dash/referrals')?>" class="view-dashboard-btn">
         Dashboard <?= jvbIcon('arrow-right', ['size' => 16]); ?>
      </a>
      <?php
      return ob_get_clean();
@@ -1563,18 +1580,26 @@
    * @param array $subpages
    * @return array
    */
   public function addSubpage(array $subpages): array
   public static function addSubpage():void
   {
      $subpages[] = [
         'page_title' => 'Referral Settings',
      $subpage = [
         'page_title' => 'Referral System',
         'menu_title' => 'Referrals',
         'capability' => 'manage_options',
         'menu_slug'  => 'jvb-referrals',
         'callback'   => [$this, 'renderAdminPage'],
         'icon'       => 'users',
         'menu_slug' => BASE . 'referral-admin',
         'callback' => [self::class, 'renderAdminPageStatic']
      ];
      AdminPages::addSubPage(BASE.'referral-admin', $subpage);
   }
      return $subpages;
   /**
    * Static wrapper for renderAdminPage
    * Called by WordPress when admin page is rendered
    */
   public static function renderAdminPageStatic(): void
   {
      // Get the properly initialized instance from JVB singleton
      JVB()->referrals()->renderAdminPage();
   }
   /**
@@ -1631,25 +1656,389 @@
    */
   public function renderAdminPage(): void
   {
      // Handle form submission
      if (isset($_POST['submit']) && check_admin_referer(BASE . 'referral_settings_nonce')) {
         update_option(BASE . 'referral_page_id', absint($_POST[BASE . 'referral_page_id'] ?? 0));
      ?>
      <div class="wrap jvb-admin-wrap">
         <h1>Referral System Management</h1>
         $reward_settings = [
            'referrer_reward_applies_to' => sanitize_text_field($_POST['referrer_reward_applies_to'] ?? 'per_user'),
            'referrer_reward_amount' => floatval($_POST['referrer_reward_amount'] ?? 25.00),
            'referrer_reward_type' => sanitize_text_field($_POST['referrer_reward_type'] ?? 'fixed'),
            'referee_reward_type' => sanitize_text_field($_POST['referee_reward_type'] ?? 'percentage'),
            'referee_reward_amount' => floatval($_POST['referee_reward_amount'] ?? 20),
            'referee_reward_applies_to' => sanitize_text_field($_POST['referee_reward_applies_to'] ?? 'first_order'),
         ];
         <!-- CSV Upload Section -->
         <div class="card">
            <h2>Import Data from Jane App</h2>
            <p>Upload your exported CSV files from Jane App to sync client and sales data.</p>
         update_option(BASE . 'referral_reward_settings', $this->sanitizeRewardSettings($reward_settings));
            <div class="jvb-upload-section" style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 20px;">
               <!-- Client List Upload -->
               <div class="jvb-upload-box">
                  <h3>Client List</h3>
                  <form id="client-upload-form" enctype="multipart/form-data">
                     <input type="file"
                           name="client_file"
                           id="client_file"
                           accept=".csv"
                           required />
                     <button type="submit" class="button button-primary" style="margin-top: 10px;">
                        Upload Clients
                     </button>
                  </form>
                  <div id="client-upload-status" style="margin-top: 10px;"></div>
               </div>
         echo '<div class="notice notice-success is-dismissible"><p>Settings saved successfully.</p></div>';
      }
               <!-- Sales Export Upload -->
               <div class="jvb-upload-box">
                  <h3>Sales Export</h3>
                  <form id="sales-upload-form" enctype="multipart/form-data">
                     <input type="file"
                           name="sales_file"
                           id="sales_file"
                           accept=".csv"
                           required />
                     <button type="submit" class="button button-primary" style="margin-top: 10px;">
                        Upload Sales
                     </button>
                  </form>
                  <div id="sales-upload-status" style="margin-top: 10px;"></div>
               </div>
            </div>
         </div>
      echo $this->renderAdminHTML();
         <!-- Referrals Table -->
         <div class="card" style="margin-top: 20px;">
            <h2>Referrals Management</h2>
            <div class="jvb-table-controls" style="margin-bottom: 15px; display: flex; gap: 10px; align-items: center;">
               <label>
                  Filter by Status:
                  <select id="referral-status-filter">
                     <option value="">All Statuses</option>
                     <option value="pending">Pending</option>
                     <option value="consulted">Consulted</option>
                     <option value="treated">Treated</option>
                     <option value="cancelled">Cancelled</option>
                  </select>
               </label>
               <input type="text"
                     id="referral-search"
                     placeholder="Search by name or email..."
                     style="min-width: 250px;" />
               <button type="button" class="button" id="refresh-table">Refresh</button>
            </div>
            <div id="referrals-table-container">
               <div class="jvb-loading">Loading referrals...</div>
            </div>
         </div>
         <!-- Settings Section -->
         <?= $this->renderAdminHTML() ?>
      </div>
      <style>
         .jvb-upload-box {
            padding: 20px;
            background: #f9f9f9;
            border: 1px solid #ddd;
            border-radius: 4px;
         }
         .jvb-upload-box h3 {
            margin-top: 0;
         }
         .referrals-table {
            width: 100%;
            border-collapse: collapse;
         }
         .referrals-table th,
         .referrals-table td {
            padding: 12px;
            text-align: left;
            border-bottom: 1px solid #ddd;
         }
         .referrals-table th {
            background: #f5f5f5;
            font-weight: 600;
         }
         .referrals-table tr:hover {
            background: #f9f9f9;
         }
         .referral-status {
            padding: 4px 8px;
            border-radius: 3px;
            font-size: 12px;
            font-weight: 500;
         }
         .referral-status.pending {
            background: #fff3cd;
            color: #856404;
         }
         .referral-status.consulted {
            background: #d1ecf1;
            color: #0c5460;
         }
         .referral-status.treated {
            background: #d4edda;
            color: #155724;
         }
         .referral-actions {
            display: flex;
            gap: 5px;
         }
         .notice.notice-success,
         .notice.notice-error {
            margin: 10px 0;
         }
      </style>
      <script>
         jQuery(document).ready(function($) {
            // Client upload
            // Client upload
            $('#client-upload-form').on('submit', function(e) {
               e.preventDefault();
               const formData = new FormData(this);
               formData.append('file', $('#client_file')[0].files[0]);
               $('#client-upload-status').html('<span class="spinner is-active"></span> Uploading...');
               $.ajax({
                  url: '<?= rest_url('jvb/v1/referrals/upload-clients') ?>',
                  method: 'POST',
                  data: formData,
                  processData: false,
                  contentType: false,
                  beforeSend: function(xhr) {
                     xhr.setRequestHeader('X-WP-Nonce', '<?= wp_create_nonce('wp_rest') ?>');
                  },
                  success: function(response) {
                     if (response.success) {
                        let message = '<div class="notice notice-success"><p>' + response.message;
                        message += '<br>Created: ' + (response.stats.created || 0);
                        message += ', Updated: ' + (response.stats.updated || 0);
                        message += ', Skipped: ' + (response.stats.skipped || 0) + '</p>';
                        // Show skipped details if any
                        if (response.stats.skipped_details && response.stats.skipped_details.length > 0) {
                           message += '<details style="margin-top: 10px;"><summary>View skipped records (' + response.stats.skipped_details.length + ')</summary>';
                           message += '<table class="widefat" style="margin-top: 10px;"><thead><tr>';
                           message += '<th>Line</th><th>Name</th><th>Email</th><th>GUID</th><th>Reason</th>';
                           message += '</tr></thead><tbody>';
                           response.stats.skipped_details.forEach(function(item) {
                              message += '<tr>';
                              message += '<td>' + (item.line || '-') + '</td>';
                              message += '<td>' + (item.name || '-') + '</td>';
                              message += '<td>' + (item.email || '-') + '</td>';
                              message += '<td>' + (item.guid || '-') + '</td>';
                              message += '<td>' + item.reason + '</td>';
                              message += '</tr>';
                           });
                           message += '</tbody></table></details>';
                        }
                        message += '</div>';
                        $('#client-upload-status').html(message);
                        $('#client-upload-form')[0].reset();
                        loadReferralsTable();
                     } else {
                        $('#client-upload-status').html(
                           '<div class="notice notice-error"><p>' + response.message + '</p></div>'
                        );
                     }
                  },
                  error: function(xhr) {
                     $('#client-upload-status').html(
                        '<div class="notice notice-error"><p>Upload failed</p></div>'
                     );
                  }
               });
            });
            // Sales upload
            $('#sales-upload-form').on('submit', function(e) {
               e.preventDefault();
               const formData = new FormData(this);
               formData.append('file', $('#sales_file')[0].files[0]);
               $('#sales-upload-status').html('<span class="spinner is-active"></span> Uploading...');
               $.ajax({
                  url: '<?= rest_url('jvb/v1/referrals/upload-sales') ?>',
                  method: 'POST',
                  data: formData,
                  processData: false,
                  contentType: false,
                  beforeSend: function(xhr) {
                     xhr.setRequestHeader('X-WP-Nonce', '<?= wp_create_nonce('wp_rest') ?>');
                  },
                  success: function(response) {
                     if (response.success) {
                        $('#sales-upload-status').html(
                           '<div class="notice notice-success"><p>' + response.message +
                           '<br>Consultations: ' + response.stats.consultations +
                           ', Treatments: ' + response.stats.treatments +
                           ', Skipped: ' + response.stats.skipped + '</p></div>'
                        );
                        $('#sales-upload-form')[0].reset();
                        loadReferralsTable();
                     } else {
                        $('#sales-upload-status').html(
                           '<div class="notice notice-error"><p>' + response.message + '</p></div>'
                        );
                     }
                  },
                  error: function(xhr) {
                     $('#sales-upload-status').html(
                        '<div class="notice notice-error"><p>Upload failed</p></div>'
                     );
                  }
               });
            });
            // Load referrals table
            function loadReferralsTable(page = 1) {
               const status = $('#referral-status-filter').val();
               const search = $('#referral-search').val();
               $.ajax({
                  url: '<?= rest_url('jvb/v1/referrals/list') ?>',
                  method: 'GET',
                  data: {
                     page: page,
                     per_page: 20,
                     status: status,
                     search: search
                  },
                  beforeSend: function(xhr) {
                     xhr.setRequestHeader('X-WP-Nonce', '<?= wp_create_nonce('wp_rest') ?>');
                     $('#referrals-table-container').html('<div class="jvb-loading">Loading...</div>');
                  },
                  success: function(response) {
                     if (response.success) {
                        renderReferralsTable(response);
                     }
                  }
               });
            }
            // Render table
            function renderReferralsTable(data) {
               let html = '<table class="referrals-table widefat">';
               html += '<thead><tr>';
               html += '<th>Referrer</th>';
               html += '<th>Referee</th>';
               html += '<th>Email</th>';
               html += '<th>Status</th>';
               html += '<th>Referred Date</th>';
               html += '<th>Total Referrals</th>';
               html += '<th>Actions</th>';
               html += '</tr></thead><tbody>';
               if (data.referrals.length === 0) {
                  html += '<tr><td colspan="7" style="text-align: center;">No referrals found</td></tr>';
               } else {
                  data.referrals.forEach(function(ref) {
                     html += '<tr>';
                     html += '<td>' + (ref.referrer_name || 'Unknown') + '</td>';
                     html += '<td>' + (ref.referee_display_name || ref.referee_name) + '</td>';
                     html += '<td>' + (ref.referee_display_email || ref.referee_email) + '</td>';
                     html += '<td><span class="referral-status ' + ref.status + '">' + ref.status + '</span></td>';
                     html += '<td>' + new Date(ref.referred_at).toLocaleDateString() + '</td>';
                     html += '<td>' + (ref.referrer_total_referrals || 0) + '</td>';
                     html += '<td class="referral-actions">';
                     if (ref.status === 'pending') {
                        html += '<button class="button button-small mark-consulted" data-id="' + ref.id + '">Mark Consulted</button>';
                     }
                     if (ref.status !== 'treated') {
                        html += '<button class="button button-small mark-treated" data-id="' + ref.id + '">Mark Treated</button>';
                     }
                     html += '</td>';
                     html += '</tr>';
                  });
               }
               html += '</tbody></table>';
               // Add pagination
               if (data.total_pages > 1) {
                  html += '<div class="tablenav"><div class="tablenav-pages">';
                  for (let i = 1; i <= data.total_pages; i++) {
                     const active = i === data.page ? 'button-primary' : 'button';
                     html += '<button class="button ' + active + ' page-link" data-page="' + i + '">' + i + '</button> ';
                  }
                  html += '</div></div>';
               }
               $('#referrals-table-container').html(html);
            }
            // Event handlers for actions
            $(document).on('click', '.mark-consulted', function() {
               const id = $(this).data('id');
               if (!confirm('Mark this referral as consulted? This will create the consultation reward.')) return;
               $.ajax({
                  url: '<?= rest_url('jvb/v1/referrals/mark-consulted') ?>',
                  method: 'POST',
                  data: JSON.stringify({ referral_id: id }),
                  contentType: 'application/json',
                  beforeSend: function(xhr) {
                     xhr.setRequestHeader('X-WP-Nonce', '<?= wp_create_nonce('wp_rest') ?>');
                  },
                  success: function(response) {
                     if (response.success) {
                        alert(response.message);
                        loadReferralsTable();
                     } else {
                        alert('Error: ' + response.message);
                     }
                  }
               });
            });
            $(document).on('click', '.mark-treated', function() {
               const id = $(this).data('id');
               if (!confirm('Mark this referral as treated? This will create rewards for both parties.')) return;
               $.ajax({
                  url: '<?= rest_url('jvb/v1/referrals/mark-treated') ?>',
                  method: 'POST',
                  data: JSON.stringify({ referral_id: id }),
                  contentType: 'application/json',
                  beforeSend: function(xhr) {
                     xhr.setRequestHeader('X-WP-Nonce', '<?= wp_create_nonce('wp_rest') ?>');
                  },
                  success: function(response) {
                     if (response.success) {
                        alert(response.message);
                        loadReferralsTable();
                     } else {
                        alert('Error: ' + response.message);
                     }
                  }
               });
            });
            $(document).on('click', '.page-link', function() {
               loadReferralsTable($(this).data('page'));
            });
            $('#referral-status-filter, #refresh-table').on('change click', function() {
               loadReferralsTable();
            });
            // Search with debounce
            let searchTimeout;
            $('#referral-search').on('keyup', function() {
               clearTimeout(searchTimeout);
               searchTimeout = setTimeout(function() {
                  loadReferralsTable();
               }, 500);
            });
            // Initial load
            loadReferralsTable();
         });
      </script>
      <?php
   }
   protected function renderAdminHTML():string
@@ -1660,7 +2049,7 @@
         <h1>Referral Settings</h1>
         <form method="post" action="">
            <?php wp_nonce_field(BASE . 'referral_settings_nonce'); ?>
            <?php wp_nonce_field(BASE . 'admin_page_nonce'); ?>
            <div class="card">
               <h2>Referral Page</h2>
@@ -1673,6 +2062,9 @@
                     </th>
                     <td>
                        <?php
                        if (!$this->referralPage) {
                           $this->referralPage = $this->getReferralPageId();
                        }
                        wp_dropdown_pages([
                           'name' => BASE . 'referral_page_id',
                           'id' => BASE . 'referral_page_id',
@@ -1772,6 +2164,27 @@
                        </select>
                     </td>
                  </tr>
                  <tr>
                     <th scope="row">
                        <label for="<?= BASE ?>client_import_role">Client Import Role</label>
                     </th>
                     <td>
                        <?php
                        $selected_role = get_option(BASE . 'client_import_role', '');
                        $roles = wp_roles()->get_names();
                        ?>
                        <select name="<?= BASE ?>client_import_role" id="<?= BASE ?>client_import_role">
                           <?php foreach ($roles as $role_value => $role_name): ?>
                              <option value="<?= esc_attr($role_value) ?>" <?php selected($selected_role, $role_value); ?>>
                                 <?= esc_html($role_name) ?>
                              </option>
                           <?php endforeach; ?>
                        </select>
                        <p class="description">
                           Role assigned to users imported from Jane App client list.
                        </p>
                     </td>
                  </tr>
               </table>
            </div>
@@ -1789,38 +2202,62 @@
   /**
    * Render referral statistics
    */
   protected function renderReferralStats(bool $wrapCard = false):string
   protected function renderReferralStats(bool $wrapCard = false): string
   {
      ob_start();
      global $wpdb;
      global $wpdb; // Use fresh global instead of stored reference
      $total_referrals = $wpdb->get_var("SELECT COUNT(*) FROM {$this->referrals_table}");
      $pending_referrals = $wpdb->get_var("SELECT COUNT(*) FROM {$this->referrals_table} WHERE status = 'pending'");
      $treated_referrals = $wpdb->get_var("SELECT COUNT(*) FROM {$this->referrals_table} WHERE status = 'treated'");
      ob_start();
      // Get fresh table name references
      $referrals_table = $wpdb->prefix . BASE . 'referrals';
      // Use proper WordPress prepare even for COUNT
      $total_referrals = $wpdb->get_var(
         $wpdb->prepare(
            "SELECT COUNT(*) FROM `{$referrals_table}` WHERE 1=%d",
            1
         )
      );
      $pending_referrals = $wpdb->get_var(
         $wpdb->prepare(
            "SELECT COUNT(*) FROM `{$referrals_table}` WHERE status = %s",
            'pending'
         )
      );
      $treated_referrals = $wpdb->get_var(
         $wpdb->prepare(
            "SELECT COUNT(*) FROM `{$referrals_table}` WHERE status = %s",
            'treated'
         )
      );
      ?>
      <table class="widefat">
         <tr>
            <th>Total Referrals</th>
            <td><?= esc_html($total_referrals) ?></td>
            <td><?= esc_html($total_referrals ?? 0) ?></td>
         </tr>
         <tr>
            <th>Pending</th>
            <td><?= esc_html($pending_referrals) ?></td>
            <td><?= esc_html($pending_referrals ?? 0) ?></td>
         </tr>
         <tr>
            <th>Treated</th>
            <td><?= esc_html($treated_referrals) ?></td>
            <td><?= esc_html($treated_referrals ?? 0) ?></td>
         </tr>
      </table>
      <?php
      $table = ob_get_clean();
      if ($wrapCard) {
         $table = '<div class="card">
            <h2>Referral Statistics</h2>
            '.$table.'
         </div>';
            <h2>Referral Statistics</h2>
            ' . $table . '
        </div>';
      }
      return $table;
   }
@@ -1836,6 +2273,9 @@
      }
      if (!$this->referralPage) {
         $this->referralPage = $this->getReferralPageId();
      }
      if (!$this->referralPage) {
         return;
      }
@@ -1875,7 +2315,9 @@
      if ('post.php' !== $pagenow || !$post) {
         return;
      }
      if (!$this->referralPage) {
         $this->referralPage = $this->getReferralPageId();
      }
      if ($post->ID === $this->referralPage) {
         echo '<div class="notice notice-info">';
         echo '<p>' . __('This page is designated as the <strong>Referral Page</strong>.', 'jvbase') . '</p>';
@@ -1883,18 +2325,253 @@
      }
   }
   public function renderDashPage(string $content, string $page):string
   public function renderDashPage(string $content, string $page): string
   {
      if ($page !== 'referrals') {
      if ($page !== 'Referrals') {
         return $content;
      }
      $out = '';
      if (current_user_can('manage_options')) {
         $out .= $this->renderAdminHTML();
      } else {
         $out .= $this->renderReferralStats(true);
      // Regular users get their referral dashboard
      $user_id = get_current_user_id();
      $referral_code = get_user_meta($user_id, BASE . 'referral_code', true);
      if (!$referral_code) {
         $referral_code = $this->getUserReferralCode($user_id);
      }
      return ($out === '') ? $content : '<form id="referrals" class="col" data-save="referrals">'.$out.'</form>';
      $stats = $this->getUserStats($user_id);
      $referrals = $this->getUserReferrals($user_id, ['limit' => 20]);
      ob_start();
      ?>
      <div class="referral-dashboard">
         <div class="referral-header">
            <h2>Your Referrals</h2>
            <p>Share your code and earn rewards when your referrals complete their first treatment!</p>
         </div>
         <?php $this->getShareButtons($user_id); ?>
         <!-- Referral Code Card -->
         <div class="referral-code-card">
            <h3>Your Referral Code</h3>
            <div class="code-display">
               <span class="code"><?= esc_html($referral_code) ?></span>
               <button class="button copy-code" data-code="<?= esc_attr($referral_code) ?>">
                  Copy Code
               </button>
            </div>
            <p class="share-link">
               Share link: <input type="text" readonly value="<?= home_url('/?ref=' . $referral_code) ?>"
                              onclick="this.select()" style="width: 100%; margin-top: 5px;" />
            </p>
         </div>
         <form class="invite">
            <?php
            $meta = new MetaForm();
            $field = [
               'type'   => 'repeater',
               'label'  => 'Invite Your Friends',
               'fields' => [
                  'name'   => [
                     'type'   => 'text',
                     'label'  => 'name',
                  ],
                  'email'  => [
                     'type'   => 'email',
                     'label'  => 'email',
                  ]
               ]
            ];
            $meta->render('invite', [], $field);
            ?>
         </form>
         <!-- Stats Grid -->
         <div class="stats-grid">
            <div class="stat-card">
               <h4>Total Referrals</h4>
               <span class="stat-number"><?= esc_html($stats['total_referrals'] ?? 0) ?></span>
            </div>
            <div class="stat-card">
               <h4>Completed Treatments</h4>
               <span class="stat-number"><?= esc_html($stats['treated_count'] ?? 0) ?></span>
            </div>
            <div class="stat-card">
               <h4>Pending</h4>
               <span class="stat-number"><?= esc_html($stats['pending_count'] ?? 0) ?></span>
            </div>
            <div class="stat-card highlight">
               <h4>Available Rewards</h4>
               <span class="stat-number">$<?= number_format($stats['available_rewards'] ?? 0, 2) ?></span>
            </div>
         </div>
         <!-- Referrals List -->
         <div class="referrals-list-card">
            <h3>Your Referrals</h3>
            <?php if (empty($referrals)): ?>
               <p>You haven't referred anyone yet. Share your code to get started!</p>
            <?php else: ?>
               <table class="referrals-table">
                  <thead>
                  <tr>
                     <th>Name</th>
                     <th>Email</th>
                     <th>Status</th>
                     <th>Referred Date</th>
                  </tr>
                  </thead>
                  <tbody>
                  <?php foreach ($referrals as $ref): ?>
                     <tr>
                        <td><?= esc_html($ref->referee_name) ?></td>
                        <td><?= esc_html($ref->referee_email) ?></td>
                        <td><span class="status-badge <?= esc_attr($ref->status) ?>"><?= esc_html(ucfirst($ref->status)) ?></span></td>
                        <td><?= date('M j, Y', strtotime($ref->referred_at)) ?></td>
                     </tr>
                  <?php endforeach; ?>
                  </tbody>
               </table>
            <?php endif; ?>
         </div>
      </div>
      <script>
         jQuery(document).ready(function($) {
            $('.copy-code').on('click', function() {
               const code = $(this).data('code');
               navigator.clipboard.writeText(code).then(function() {
                  alert('Code copied to clipboard!');
               });
            });
         });
      </script>
      <?php
      return ob_get_clean();
   }
   /**
    * Handle admin page form submission
    *
    * @param mixed $result Previous result
    * @param string $page_slug Current page slug
    * @param array $post_data POST data
    * @return array|null Result array or null if not our page
    */
   public function handleAdminSubmission($result, string $page_slug, array $post_data): ?array
   {
      // Only handle our page
      if ($page_slug !== BASE . 'referral-admin') {
         return $result;
      }
      try {
         // Save referral page
         $page_id = isset($post_data[BASE . 'referral_page_id']) ? absint($post_data[BASE . 'referral_page_id']) : 0;
         update_option(BASE . 'referral_page_id', $page_id);
         // Save client import role
         $import_role = sanitize_text_field($post_data[BASE . 'client_import_role'] ?? JVB_USER);
         update_option(BASE . 'client_import_role', $import_role);
         // Save reward settings
         $settings = [
            'referrer_reward_type' => sanitize_text_field($post_data['referrer_reward_type'] ?? 'fixed'),
            'referrer_reward_amount' => floatval($post_data['referrer_reward_amount'] ?? 25.00),
            'referrer_reward_applies_to' => sanitize_text_field($post_data['referrer_reward_applies_to'] ?? 'per_user'),
            'referee_reward_type' => sanitize_text_field($post_data['referee_reward_type'] ?? 'percentage'),
            'referee_reward_amount' => floatval($post_data['referee_reward_amount'] ?? 20),
            'referee_reward_applies_to' => sanitize_text_field($post_data['referee_reward_applies_to'] ?? 'first_order')
         ];
         update_option(BASE . 'referral_settings', $settings);
         return [
            'success' => true,
            'message' => 'Referral settings saved successfully!'
         ];
      } catch (\Exception $e) {
         return [
            'success' => false,
            'message' => 'Failed to save settings: ' . $e->getMessage()
         ];
      }
   }
   /**
    * Get formatted reward text for referee
    *
    * @param bool $full Include "off your first treatment" text
    * @return string
    */
   public function getRewardText(bool $full = true): string
   {
      $reward_amount = $this->settings['referee_reward_amount'] ?? 20;
      $reward_type = $this->settings['referee_reward_type'] ?? 'percentage';
      $reward_text = $reward_type === 'percentage'
         ? $reward_amount . '% off'
         : '$' . number_format($reward_amount, 2) . ' off';
      if ($full) {
         $reward_text .= ' your first treatment';
      }
      return $reward_text;
   }
   public function getShareButtons(int $user_id):void
   {
      $referral_code = $this->getUserReferralCode($user_id);
      if (is_wp_error($referral_code)) {
         return;
      }
      $share_url = $this->getShareURL($referral_code);
      $referral_page_id = $this->getReferralPageId();
      // SMS share text
      $sms_text = urlencode("Check out " . get_bloginfo('name') . "! " . $share_url);
      // Share message
      $share_message = urlencode("I love " . get_bloginfo('name') . "! Thought you might want to check them out.");
      ?>
      <nav class="share">
         <h4>Quick Share</h4>
         <ul class="share-buttons-grid">
            <a href="mailto:?subject=<?php echo urlencode('Check out ' . get_bloginfo('name')); ?>&body=<?php echo urlencode($share_message . ' ' . $share_url); ?>"
               class="button" title="Email">
               <?php echo jvbIcon('envelope'); ?>
            </a>
            <a href="sms:?&body=<?php echo $sms_text; ?>"
               class="button" title="Text">
               <?php echo jvbIcon('chat'); ?>
            </a>
            <a href="https://www.facebook.com/sharer/sharer.php?u=<?php echo urlencode($share_url); ?>"
               target="_blank"
               rel="noopener noreferrer"
               class="button" title="Facebook">
               <?php echo jvbIcon('facebook-logo'); ?>
            </a>
            <a href="https://twitter.com/intent/tweet?url=<?php echo urlencode($share_url); ?>&text=<?php echo urlencode($share_message); ?>"
               target="_blank"
               rel="noopener noreferrer"
               class="button" title="Twitter">
               <?php echo jvbIcon('twitter-logo'); ?>
            </a>
            <a href="https://wa.me/?text=<?php echo $sms_text; ?>"
               target="_blank"
               rel="noopener noreferrer"
               class="button" title="WhatsApp">
               <?php echo jvbIcon('whatsapp-logo'); ?>
            </a>
         </ul>
      </nav>
   <?php
   }
}