Jake Vanderwerf
2026-05-11 ac444cba221832c012c0435fdc8339fe9f37febb
inc/managers/ReferralManager.php
@@ -1,10 +1,10 @@
<?php
namespace JVBase\managers;
use JVBase\managers\MagicLinkManager;
use JVBase\integrations\Cloudflare;
use JVBase\meta\MetaForm;
use JVBase\utility\Features;
use JVBase\meta\Form;
use JVBase\ui\CRUDSkeleton;
use JVBase\ui\Tabs;
use JVBase\base\Site;
use WP_User;
use WP_Error;
@@ -22,11 +22,19 @@
{
   protected $wpdb;
   protected MagicLinkManager $magic_link;
   protected CacheManager $cache;
   protected Cache $cache;
   protected Cache $requestCache;
   protected Cache $statsCache;
   protected string $referrals_table;
   protected ?int $referralPage = null;
   protected string $rewards_table;
   protected CustomTable $referrals;
   protected CustomTable $codes;
   protected CustomTable $janeClients;
   protected CustomTable $rewards;
   protected CustomTable $treatments;
   // Default reward settings
   protected array $default_settings = [
      'referrer_reward_applies_to' => 'per_user',  // 'per_user' or 'flat_total'
@@ -34,19 +42,31 @@
      'referrer_reward_type'  => 'fixed',
      'referee_reward_type' => 'percentage',  // 'percentage' or 'fixed'
      'referee_reward_amount' => 20,  // 20% or $20
      'referee_reward_applies_to' => 'first_order'  // 'first_order' or 'all_orders'
      'referee_reward_applies_to' => 'first_order',  // 'first_order' or 'all_orders'
   ];
   protected string $role;
   protected array $settings;
   public function __construct()
   {
      $this->defineTables();
      $this->role = Site::getDefaultReferralRole();
      $this->default_settings['referral_role'] = $this->role;
      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->magic_link = new MagicLinkManager();
      $this->cache = Cache::for('referrals', WEEK_IN_SECONDS);
      $this->requestCache = Cache::for('referral_requests', WEEK_IN_SECONDS)->connect('referrals', true);
      $this->statsCache = Cache::for('referral_stats', WEEK_IN_SECONDS)->connect('referrals', true);
      if (JVB_TESTING) {
         $this->cache->flush();
         $this->requestCache->flush();
         $this->statsCache->flush();
      }
      $this->referrals_table = $wpdb->prefix . BASE . 'referrals';
      $this->rewards_table = $wpdb->prefix . BASE . 'referral_rewards';
      $this->referralPage = $this->getReferralPageId();
      $this->settings = $this->getRewardSettings();
@@ -54,7 +74,7 @@
      add_action('jvbUserRegistered', [$this, 'processRegistrationToken'], 10, 3);
      add_action('jvb_add_token_inputs', [$this, 'addLoginInputs'], 10, 1);
      add_action('jvbUserRegistered', [$this, 'processReferral'], 10, 1);
      add_action('user_register', [$this, 'processReferral'], 10, 1);
      // Add meta boxes for admin to manage referrals
      add_action('show_user_profile', [$this, 'displayUserReferralInfo']);
@@ -73,9 +93,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,8 +104,194 @@
      // Handle settings save
      add_action('admin_init', [$this, 'registerSettings']);
      // Handle admin page form submission
      add_filter('jvb_admin_page_submission', [$this, 'handleAdminSubmission'], 10, 3);
   }
   protected function defineTables():void
   {
      $this->defineReferralsTable();
      $this->defineCodeTable();
      $this->defineJaneClientsTable();
      $this->defineRewardsTable();
      $this->defineTreatmentsTable();
   }
      protected function defineReferralsTable():void
      {
         $table = CustomTable::for('referrals');
         $table->setColumns([
            'id'     => 'bigint(20) unsigned NOT NULL AUTO_INCREMENT',
            'from_user' => "{$table->getUserIDType()} NOT NULL",
            'to_user'   => "{$table->getUserIDType()} NOT NULL",
            'to_name'   => 'varchar(255) NOT NULL',
            'to_email'  => 'varchar(255) NOT NULL',
            'to_phone'  => 'varchar(50) NOT NULL',
            'referral_code'=> 'varchar(50) NOT NULL',
            'status' => "ENUM('pending', 'consulted', 'treated', 'cancelled') DEFAULT 'pending'",
            'created_at'=> 'datetime DEFAULT CURRENT_TIMESTAMP',
            'consulted_at'=> 'datetime DEFAULT NULL',
            'treated_at'=> 'datetime DEFAULT NULL',
            'treatment_count' => 'int DEFAULT 0',
            'notes'     => 'text DEFAULT NULL',
         ]);
         $table->setKeys([
            ['key' => 'PRIMARY', 'value' => 'id'],
            ['key' => 'UNIQUE', 'value' => '(`to_user`)'],
            'from_user (`from_user`)',
            'status (`status`)',
            'code (`referral_code`)',
            'date (`created_at`)',
            'consult (`consulted_at`)'
         ]);
         $base = BASE;
         $table->setConstraints([
            "CONSTRAINT `{$base}referral_from_user_fk` FOREIGN KEY (`from_user`)
            REFERENCES `{$table->getUserTable()}` (`ID`) ON DELETE CASCADE",
            "CONSTRAINT `{$base}referral_to_user_fk` FOREIGN KEY (`to_user`)
            REFERENCES `{$table->getUserTable()}` (`ID`) ON DELETE CASCADE"
         ]);
         $table->defineTable();
         $this->referrals = $table;
      }
      protected function defineCodeTable():void
      {
         $table = CustomTable::for('referrals_codes');
         $table->setColumns([
            'id'     => 'bigint(20) unsigned NOT NULL AUTO_INCREMENT',
            'user_id'   => "{$table->getUserIDType()} NOT NULL",
            'code'      => 'varchar(50) NOT NULL',
            'created_at'=> 'datetime DEFAULT CURRENT_TIMESTAMP',
         ]);
         $table->setKeys([
            ['key' => 'PRIMARY', 'value' => '(`id`)'],
            ['key' => 'UNIQUE', 'value' => '(`code`)'],
            'user (`user_id`)',
         ]);
         $base = BASE;
         $table->setConstraints([
            "CONSTRAINT `{$base}referral_code_user_fk` FOREIGN KEY (`user_id`)
               REFERENCES `{$table->getUserTable()}` (`ID`) ON DELETE CASCADE",
         ]);
         $table->defineTable();
         $this->codes = $table;
      }
      protected function defineJaneClientsTable():void
      {
         $table = CustomTable::for('referrals_jane_clients');
         $table->setColumns([
            'id'        => 'bigint(20) unsigned NOT NULL AUTO_INCREMENT',
            'patient_guid' => 'varchar(50) NOT NULL',
            'user_id'      => "{$table->getUserIDType()} NOT NULL",
            'first_name'   => 'varchar(100) NOT NULL',
            'last_name'    => 'varchar(100) NOT NULL',
            'email'        => 'varchar(255) NOT NULL',
            'imported_at'  => 'datetime DEFAULT CURRENT_TIMESTAMP',
            'updated_at'   => 'datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',
         ]);
         $table->setKeys([
            ['key' => 'PRIMARY', 'value' => '(`id`)'],
            ['key' => 'UNIQUE', 'value' => '(`patient_guid`)'],
            'user (`user_id`)',
            'email (`email`)',
         ]);
         $base = BASE;
         $table->setConstraints([
            "CONSTRAINT `{$base}jane_clients_user` FOREIGN KEY (`user_id`)
            REFERENCES `{$table->getUserTable()}` (`ID`) ON DELETE CASCADE",
         ]);
         $table->defineTable();
         $this->janeClients = $table;
      }
      protected function defineRewardsTable():void
      {
         $table = CustomTable::for('referrals_rewards');
         $table->setColumns([
            'id'           => 'bigint(20) unsigned NOT NULL AUTO_INCREMENT',
            'referral_id'     => 'bigint(20) unsigned NOT NULL',
            'user_id'         => "{$table->getUserIDType()} NOT NULL",
            'reward_type'     => "ENUM('referrer', 'referee') NOT NULL",
            'amount'       => 'decimal(10,2) NOT NULL',
            'reward_calculation'=> "ENUM('percentage', 'fixed')",
            'status'       => "ENUM('available', 'redeemed', 'expired', 'cancelled') DEFAULT 'available'",
            'created_at'      => 'datetime NOT NULL DEFAULT CURRENT_TIMESTAMP',
            'updated_at'      => 'datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',
            'redeemed_at'     => 'datetime DEFAULT NULL',
            'expires_at'      => 'datetime DEFAULT NULL',
            'notes'           => 'text DEFAULT NULL',
         ]);
         $table->setKeys([
            ['key' => 'PRIMARY', 'value' => '(`id`)'],
            'referral (`referral_id`)',
            'user (`user_id`)',
            'status (`status`)',
            'type (`reward_type`)'
         ]);
         $base = BASE;
         $table->setConstraints([
            "CONSTRAINT `{$base}reward_referral` FOREIGN KEY (`referral_id`)
            REFERENCES `{$this->referrals->getFullTableName()}` (`id`) ON DELETE CASCADE",
            "CONSTRAINT `{$base}reward_user` FOREIGN KEY (`user_id`)
            REFERENCES `{$table->getUserTable()}` (`ID`) ON DELETE CASCADE"
         ]);
         $table->defineTable();
         $this->rewards = $table;
      }
      protected function defineTreatmentsTable():void
      {
         $table = CustomTable::for('referral_treatments');
         $table->setColumns([
            'id'        => 'bigint(20) unsigned NOT NULL AUTO_INCREMENT',
            'referral_id'  => 'bigint(20) unsigned NOT NULL',
            'user_id'      => "{$table->getUserIDType()} NOT NULL",
            'treatment_type'=> 'varchar(100) NOT NULL',     //Tier 1-6, Brows, etc
            'treatment_date'=> 'datetime NOT NULL',
            'invoice_number'=> 'varchar(50) DEFAULT NULL',
            'amount'    => 'decimal(10,2) DEFAULT NULL',
            'status'    => "ENUM('completed', 'no_show', 'cancelled') DEFAULT 'completed'",
            'imported_at'  => 'datetime DEFAULT CURRENT_TIMESTAMP',
         ]);
         $table->setKeys([
            ['key' => 'PRIMARY', 'value' => '(`id`)'],
            'referral (`referral_id`)',
            'user (`user_id`)',
            'date (`treatment_date`)',
            'type (`treatment_type`)',
         ]);
         $base = BASE;
         $table->setConstraints([
            "CONSTRAINT `{$base}treatment_referral` FOREIGN KEY (`referral_id`)
            REFERENCES `{$this->referrals->getFullTableName()}` (`id`) ON DELETE CASCADE",
            "CONSTRAINT `{$base}treatment_user` FOREIGN KEY (`user_id`)
            REFERENCES `{$table->getUserTable()}` (`ID`) ON DELETE CASCADE"
         ]);
         $table->defineTable();
         $this->treatments = $table;
      }
   public function getSettings():array
   {
      return $this->settings;
   }
   public function getRole():string
   {
      return $this->role;
   }
   public function addLoginInputs(string $action):void
   {
      if (array_key_exists('ref', $_GET)) {
@@ -127,10 +330,21 @@
         'jvb-a11y',
         'jvb-popup',
         'jvb-tabs',
         'jvb-data-store',
      ];
      if (Features::hasIntegration('cloudflare') && JVB()->connect('cloudflare')->isSetUp()) {
         $requirements[] = 'cloudflare-turnstile';
      if (Site::hasIntegration('cloudflare') && JVB()->connect('cloudflare')->isSetUp()) {
         JVB()->connect('cloudflare')->enqueueTurnstileScripts();
      }
      if (is_singular(BASE.'dash')) {
         $requirements[] = 'jvb-form';
         $requirements[] = 'jvb-view';
         wp_enqueue_script('jvb-referral-admin',
         JVB_URL.'assets/js/min/referralAdmin.min.js',
         ['jvb-referral'],
         '1.0.0',
         true);
      }
      wp_enqueue_script(
         'jvb-referral',
@@ -159,6 +373,26 @@
      }
   }
   public function createCode(int $user_id, string $code):string|false
   {
      $code = sanitize_title($code);
      $existing = $this->codes->get(['code' => $code]);
      if ($existing) {
         if ($existing['user_id'] !== $user_id) {
            return false;
         }
         return $code;
      }
      $success = $this->codes->insert([
         'user_id'   => $user_id,
         'code'      => $code
      ]);
      if ($success) {
         return $code;
      }
      return false;
   }
   /**
    * Generate or get existing referral code for a user
    *
@@ -166,32 +400,35 @@
    * @param string|null $custom_code Optional custom code
    * @return string|WP_Error
    */
   public function getUserReferralCode(int $user_id, ?string $custom_code = null)
   public function getUserReferralCode(int $user_id, ?string $custom_code = null):string|false
   {
      $user = get_user_by('ID', $user_id);
      $user = get_userdata($user_id);
      if (!$user) {
         return new WP_Error('invalid_user', 'User not found');
         return false;
      }
      // Check if user already has a code
      $existing_code = get_user_meta($user_id, BASE . 'referral_code', true);
      if ($existing_code && !$custom_code) {
         return $existing_code;
      $existing = $this->codes->pluck('code', ['user_id' => $user_id],'created_at', 'DESC');
      if (!empty($existing) && !$custom_code) {
         return $existing[0];
      }
      if ($custom_code && !empty($existing) && !in_array($custom_code, $existing)) {
         $test = $this->createCode($user_id, $custom_code);
         if ($test) {
            return $this->codes->pluck('code', ['user_id' => $user_id], 'created_at', 'DESC')[0];
         } else {
            return $existing[0];
         }
      }
      // Generate new code if custom provided or none exists
      $code = $custom_code ?: $this->generateReferralCode($user);
      $code = $this->generateReferralCode($user);
      // Validate uniqueness
      if ($this->isCodeTaken($code, $user_id)) {
         return new WP_Error('code_taken', 'This referral code is already in use');
      $success = $this->createCode($user_id, $code);
      if ($success) {
         return $this->codes->pluck('code', ['user_id' => $user_id], 'created_at', 'DESC')[0];
      }
      // Save the code
      update_user_meta($user_id, BASE . 'referral_code', $code);
      return $code;
      return false;
   }
   /**
@@ -224,24 +461,11 @@
    * Check if a referral code is already taken
    *
    * @param string $code
    * @param int|null $exclude_user_id
    * @return bool
    */
   protected function isCodeTaken(string $code, ?int $exclude_user_id = null): bool
   protected function isCodeTaken(string $code): bool
   {
      $args = [
         'meta_key' => BASE . 'referral_code',
         'meta_value' => $code,
         'fields' => 'ID',
         'number' => 1
      ];
      if ($exclude_user_id) {
         $args['exclude'] = [$exclude_user_id];
      }
      $users = get_users($args);
      return !empty($users);
      return (bool) $this->codes->get(['code' => $code]);
   }
   public function processRegistrationToken(int $user_id, string $email, array $data): void
@@ -268,51 +492,77 @@
    * Track a new referral when user registers
    *
    * @param int $user_id
    * @param array $userData
    * @return bool;
    */
   public function processReferral(int $user_id, string $email, array $data): void
   public function processReferral(int $user_id, array $userData): bool
   {
      // Check if user was created via referral magic link
      // Try to get code from multiple sources
      $referral_code = $data['referral_code'] ??
      get_user_meta($user_id, BASE . 'pending_referral_code', true);
      $referral = $this->referrals->get(['to_user' => $user_id]);
      // Check session/cookie if not in data
      if (empty($referral_code)) {
      if (empty($referral)) {
         $referral = $this->referrals->get(['to_email' => $userData['email']]);
      }
      if (empty($referral)) {
         // Check session/cookie if not in meta
         if (session_status() === PHP_SESSION_NONE) {
            session_start();
         }
         $referral_code = $_SESSION[BASE . 'referral_code'] ?? $_COOKIE[BASE . 'referral_code'] ?? '';
         if (!empty ($referral_code)) {
            $referral = [
               'to_user'   => $user_id,
               'referral_code'   => $referral_code,
               'to_email'     => $userData['user_email']
            ];
         }
      }
      if (empty($referral_code)) {
         return;
      if (empty($referral)) {
         return false; // No referral code - regular registration
      }
      // Find the referrer
      $referrer = $this->getUserByReferralCode($referral_code);
      $referrer = $this->codes->pluck('user_id', ['code' => $referral['referral_code']]);
      if (empty($referrer)) {
         //This should not happen, but whatever
         return false;
      }
      $referrer = $referrer[0];
      $record = $this->referrals->findOrCreate([
         'to_user'   => $user_id,
         'referral_code'   => $referral['referral_code'],
      ], [
         'from_user'    => $referrer,
         'to_email'     => $referral['to_email'],
         'to_name'      => $userData['first_name'],
//       'to_phone'     =>
         'status'    => 'pending'
      ]);
      if (!$referrer) {
         delete_user_meta($user_id, BASE . 'pending_referral_code');
         return;
      if (!$record) {
         error_log('[ReferralManager]::processReferral Could not update record for user: '.print_r($referral, true));
         return false;
      }
      // Check for duplicates
      $existing = $this->getReferralByReferee($user_id);
      if ($existing) {
         delete_user_meta($user_id, BASE . 'pending_referral_code');
         return;
      // Clean up temp data
      delete_user_meta($user_id, BASE . 'pending_referral_code');
      if (isset($_SESSION[BASE . 'referral_code'])) {
         unset($_SESSION[BASE . 'referral_code']);
      }
      if (isset($_COOKIE[BASE . 'referral_code'])) {
         setcookie(BASE . 'referral_code', '', time() - 3600, '/');
      }
      // Create referral record
      $result = $this->createReferral($referrer->ID, $user_id, $referral_code);
      // Clear caches
      $this->cache->flush();
      if ($result) {
         // Clean up temp meta
         delete_user_meta($user_id, BASE . 'pending_referral_code');
      // Fire action for tracking
      do_action('jvb_referral_processed', $user_id, $referrer->ID, $referral['referral_code']);
         // Fire action for tracking
         do_action('jvb_referral_processed', $user_id, $referrer->ID, $referral_code);
      }
      // Send notification to referrer
      $this->sendReferrerNotification($referrer->ID, $userData['display_name']);
      return true;
   }
   /**
@@ -479,18 +729,42 @@
      $args = wp_parse_args($args, $defaults);
      $where = $this->wpdb->prepare("WHERE referrer_id = %d", $user_id);
      return $this->requestCache->remember(
         $this->requestCache->generateKey(array_merge(['user'=>$user_id], $args)),
         function() use ($user_id, $args) {
            $where = $this->wpdb->prepare("WHERE referrer_id = %d", $user_id);
      if ($args['status'] !== 'all') {
         $where .= $this->wpdb->prepare(" AND status = %s", $args['status']);
      }
            if ($args['status'] !== 'all') {
               $where .= $this->wpdb->prepare(" AND status = %s", $args['status']);
            }
      $query = "SELECT * FROM {$this->referrals_table}
            $query = "SELECT * FROM {$this->referrals_table}
                  {$where}
                  ORDER BY {$args['orderby']} {$args['order']}
                  LIMIT {$args['limit']} OFFSET {$args['offset']}";
      return $this->wpdb->get_results($query);
            $results =  $this->wpdb->get_results($query);
            return array_map(function($referral) {
               $last_invite = get_transient('referral_last_invite_' . md5($referral->referee_email));
               $can_resend = !$last_invite || (time() - $last_invite) > WEEK_IN_SECONDS;
               $status = match($referral->status) {
                  'consulted' => 'Awaiting Treatment',
                  'treated'   => 'Rewarded!',
                  default => 'Pending',
               };
               return [
                  'id'        => $referral->id,
                  'referee_name' => $referral->referee_name,
                  'referee_email'   => $referral->referee_email,
                  'referred_at'  => JVB()->routes('referral')->formatTimestamp($referral->referred_at),
                  'referral_status'=> $status,
                  'can_resend'   => $can_resend
               ];
            }, $results);
         }
      );
   }
   /**
@@ -501,38 +775,33 @@
    */
   public function getUserStats(int $user_id): array
   {
      $cache_key = 'stats_' . $user_id;
      $cached = $this->cache->get($cache_key);
      return $this->statsCache->remember(
         $user_id,
         function() use ($user_id) {
            $stats = $this->wpdb->get_row($this->wpdb->prepare(
               "SELECT
         COUNT(*) as code_used,
         SUM(CASE WHEN status IN ('consulted', 'treated') THEN 1 ELSE 0 END) as consultations,
         SUM(CASE WHEN status = 'treated' THEN 1 ELSE 0 END) as treatments,
         SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending
      FROM {$this->referrals_table}
      WHERE referrer_id = %d",
               $user_id
            ), ARRAY_A);
      if ($cached !== false) {
         return $cached;
      }
            // Get total rewards earned (available + redeemed)
            $rewards = $this->wpdb->get_var($this->wpdb->prepare(
               "SELECT SUM(amount)
      FROM {$this->rewards_table}
      WHERE user_id = %d AND reward_type = 'referrer'",
               $user_id
            ));
      $stats = $this->wpdb->get_row($this->wpdb->prepare(
         "SELECT
                COUNT(*) as total_referrals,
                SUM(CASE WHEN status = 'treated' THEN 1 ELSE 0 END) as treated_count,
                SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending_count
            FROM {$this->referrals_table}
            WHERE referrer_id = %d",
         $user_id
      ), ARRAY_A);
      // Get total rewards
      $rewards = $this->wpdb->get_row($this->wpdb->prepare(
         "SELECT
                SUM(CASE WHEN status = 'available' THEN amount ELSE 0 END) as available_rewards,
                SUM(CASE WHEN status = 'redeemed' THEN amount ELSE 0 END) as redeemed_rewards
            FROM {$this->rewards_table}
            WHERE user_id = %d AND reward_type = 'referrer'",
         $user_id
      ), ARRAY_A);
      $stats = array_merge($stats, $rewards);
      $this->cache->set($cache_key, $stats, HOUR_IN_SECONDS);
      return $stats;
            $stats['total_rewards'] = floatval($rewards ?? 0);
            $stats['user_id'] = $user_id;
            return $stats;
         }
      );
   }
   /**
@@ -544,20 +813,23 @@
    */
   public function getTopReferrers(int $limit = 10, string $period = 'all'): array
   {
      $where = '';
      return $this->statsCache->remember(
         $this->statsCache->generateKey(['limit'=>$limit, 'period' => $period]),
         function() use ($limit, $period) {
            $where = '';
      if ($period !== 'all') {
         $date_where = match($period) {
            'day' => "referred_at >= DATE_SUB(NOW(), INTERVAL 1 DAY)",
            'week' => "referred_at >= DATE_SUB(NOW(), INTERVAL 1 WEEK)",
            'month' => "referred_at >= DATE_SUB(NOW(), INTERVAL 1 MONTH)",
            default => "1=1"
         };
            if ($period !== 'all') {
               $date_where = match($period) {
                  'day' => "referred_at >= DATE_SUB(NOW(), INTERVAL 1 DAY)",
                  'week' => "referred_at >= DATE_SUB(NOW(), INTERVAL 1 WEEK)",
                  'month' => "referred_at >= DATE_SUB(NOW(), INTERVAL 1 MONTH)",
                  default => "1=1"
               };
         $where = "WHERE {$date_where}";
      }
               $where = "WHERE {$date_where}";
            }
      $query = "SELECT
            $query = "SELECT
                    referrer_id,
                    COUNT(*) as referral_count,
                    SUM(CASE WHEN status = 'treated' THEN 1 ELSE 0 END) as treated_count
@@ -567,16 +839,19 @@
                  ORDER BY referral_count DESC
                  LIMIT {$limit}";
      $results = $this->wpdb->get_results($query);
            $results = $this->wpdb->get_results($query);
      // Enrich with user data
      foreach ($results as &$result) {
         $user = get_user_by('ID', $result->referrer_id);
         $result->user_name = $user ? $user->display_name : 'Unknown';
         $result->user_email = $user ? $user->user_email : '';
      }
            // Enrich with user data
            foreach ($results as &$result) {
               $user = get_user_by('ID', $result->referrer_id);
               $result->user_name = $user ? $user->display_name : 'Unknown';
               $result->user_email = $user ? $user->user_email : '';
            }
      return $results;
            return $results;
         }
      );
   }
   /**
@@ -586,11 +861,8 @@
   {
      $yesterday = date('Y-m-d', strtotime('-1 day'));
      // Get new referrals from yesterday
      $new_referrals = $this->wpdb->get_results($this->wpdb->prepare(
         "SELECT
            r.*,
            u.display_name as referrer_name
         "SELECT r.*, u.display_name as referrer_name
        FROM {$this->referrals_table} r
        JOIN {$this->wpdb->users} u ON r.referrer_id = u.ID
        WHERE DATE(r.referred_at) = %s
@@ -598,48 +870,46 @@
         $yesterday
      ));
      // Only send if there's at least 1 new referral
      if (empty($new_referrals)) {
         return;
      }
      // Build email content
      $content = '<h2>Daily Referral Report</h2>';
      $content .= '<p><strong>' . count($new_referrals) . '</strong> new referral' .
         (count($new_referrals) !== 1 ? 's' : '') . ' yesterday (' . $yesterday . ')</p>';
      $content = JVB()->email()->h1('Daily Referral Report');
      $content .= JVB()->email()->stat(
         count($new_referrals),
         count($new_referrals) === 1 ? 'New Referral' : 'New Referrals',
         'From ' . $yesterday
      );
      $content .= '<table style="width:100%; border-collapse: collapse; margin: 20px 0;">';
      $content .= '<thead><tr style="background: #f5f5f5;">';
      $content .= '<th style="padding: 10px; text-align: left; border: 1px solid #ddd;">Referee</th>';
      $content .= '<th style="padding: 10px; text-align: left; border: 1px solid #ddd;">Email</th>';
      $content .= '<th style="padding: 10px; text-align: left; border: 1px solid #ddd;">Referrer</th>';
      $content .= '<th style="padding: 10px; text-align: left; border: 1px solid #ddd;">Code</th>';
      $content .= '</tr></thead><tbody>';
      $content .= JVB()->email()->spacer(20);
      $content .= JVB()->email()->h2('New Referrals');
      // Build list of referrals
      foreach ($new_referrals as $ref) {
         $content .= '<tr>';
         $content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%s</td>',
            esc_html($ref->referee_name));
         $content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%s</td>',
            esc_html($ref->referee_email));
         $content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%s</td>',
            esc_html($ref->referrer_name));
         $content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%s</td>',
            esc_html($ref->referral_code));
         $content .= '</tr>';
         $cardContent = sprintf(
            '<p><strong>%s</strong> (%s)</p>',
            esc_html($ref->referee_name),
            esc_html($ref->referee_email)
         );
         $cardContent .= sprintf(
            '<p style="font-size:13px;color:%s;">Referred by: %s | Code: %s</p>',
            JVB()->email()->colours['dark-200'],
            esc_html($ref->referrer_name),
            JVB()->email()->badge($ref->referral_code, 'info')
         );
         $content .= JVB()->email()->card($cardContent);
      }
      $content .= '</tbody></table>';
      // Get admin email
      $to = get_option('admin_email');
      $subject = sprintf('[%s] %d New Referral%s',
      $subject = sprintf(
         '[%s] %d New Referral%s',
         get_bloginfo('name'),
         count($new_referrals),
         count($new_referrals) !== 1 ? 's' : '');
         count($new_referrals) !== 1 ? 's' : ''
      );
      jvbMail($to, $subject, $content);
      JVB()->email()->sendEmail($to, $subject, $content, 'DAILY REPORT');
   }
   /**
@@ -650,19 +920,50 @@
      $top_referrers = $this->getTopReferrers(10, 'week');
      $total_referrals = $this->wpdb->get_var(
         "SELECT COUNT(*) FROM {$this->referrals_table}
             WHERE referred_at >= DATE_SUB(NOW(), INTERVAL 1 WEEK)"
         WHERE referred_at >= DATE_SUB(NOW(), INTERVAL 1 WEEK)"
      );
      if ($total_referrals == 0) {
         return;
      }
      $content = JVB()->email()->h1('Weekly Referral Summary');
      $content .= JVB()->email()->stat(
         $total_referrals,
         'Total Referrals',
         'This week'
      );
      $content .= JVB()->email()->spacer(30);
      $content .= JVB()->email()->h2('Top 10 Referrers');
      // Leaderboard style
      $rank = 1;
      foreach ($top_referrers as $referrer) {
         $rankBadge = $rank <= 3
            ? JVB()->email()->badge('#' . $rank, $rank === 1 ? 'success' : 'info')
            : '<span style="font-weight:600;color:' . JVB()->email()->colours['dark-200'] . ';">#' . $rank . '</span>';
         $cardContent = sprintf(
            '<p>%s <strong>%s</strong></p>',
            $rankBadge,
            esc_html($referrer->user_name)
         );
         $stats = [
            JVB()->email()->stat($referrer->referral_count, 'Total Referrals'),
            JVB()->email()->stat($referrer->treated_count, 'Treated')
         ];
         $cardContent .= JVB()->email()->grid($stats, 2);
         $content .= JVB()->email()->card($cardContent);
         $rank++;
      }
      $to = get_option('admin_email');
      $subject = '[' . get_bloginfo('name') . '] Weekly Referral Summary - ' . date('F j, Y');
      $message = $this->generateWeeklyReportEmail($top_referrers, $total_referrals);
      wp_mail($to, $subject, $message, ['Content-Type: text/html; charset=UTF-8']);
      JVB()->email()->sendEmail($to, $subject, $content, 'WEEKLY SUMMARY');
   }
   /**
@@ -673,69 +974,30 @@
    */
   protected function generateCSV(array $referrals): string
   {
      $csv = "Referred By,Referee Name,Referee Email,Referee Phone,Referral Code,Status,Referred At,Treated At\n";
      $cache = Cache::for('referralCSV', HOUR_IN_SECONDS)->connect('referrals');
      return $cache->remember(
         'csv',
         function () use ($referrals) {
            $csv = "Referred By,Referee Name,Referee Email,Referee Phone,Referral Code,Status,Referred At,Treated At\n";
      foreach ($referrals as $referral) {
         $csv .= sprintf(
            '"%s","%s","%s","%s","%s","%s","%s","%s"' . "\n",
            $referral->referrer_name ?? 'Unknown',
            $referral->referee_name,
            $referral->referee_email,
            $referral->referee_phone,
            $referral->referral_code,
            $referral->status,
            $referral->referred_at,
            $referral->treated_at ?? 'Not yet'
         );
      }
            foreach ($referrals as $referral) {
               $csv .= sprintf(
                  '"%s","%s","%s","%s","%s","%s","%s","%s"' . "\n",
                  $referral->referrer_name ?? 'Unknown',
                  $referral->referee_name,
                  $referral->referee_email,
                  $referral->referee_phone,
                  $referral->referral_code,
                  $referral->status,
                  $referral->referred_at,
                  $referral->treated_at ?? 'Not yet'
               );
            }
      return $csv;
   }
   /**
    * Generate HTML email for daily report
    *
    * @param array $referrals
    * @param string $period
    * @return string
    */
   protected function generateReportEmail(array $referrals, string $period): string
   {
      $count = count($referrals);
      $content = sprintf('<p>You have <strong>%d new referral%s</strong> today.</p>',
         $count,
         $count !== 1 ? 's' : ''
            return $csv;
         }
      );
      $content .= '<table style="width:100%; border-collapse: collapse; margin: 20px 0;">';
      $content .= '<thead><tr style="background: #f5f5f5; text-align: left;">';
      $content .= '<th style="padding: 10px; border: 1px solid #ddd;">Referred By</th>';
      $content .= '<th style="padding: 10px; border: 1px solid #ddd;">New User</th>';
      $content .= '<th style="padding: 10px; border: 1px solid #ddd;">Email</th>';
      $content .= '<th style="padding: 10px; border: 1px solid #ddd;">Status</th>';
      $content .= '<th style="padding: 10px; border: 1px solid #ddd;">Time</th>';
      $content .= '</tr></thead><tbody>';
      foreach ($referrals as $referral) {
         $content .= '<tr>';
         $content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%s</td>',
            esc_html($referral->referrer_name ?? 'Unknown'));
         $content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%s</td>',
            esc_html($referral->referee_name));
         $content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%s</td>',
            esc_html($referral->referee_email));
         $content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%s</td>',
            esc_html(ucfirst($referral->status)));
         $content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%s</td>',
            esc_html(date('g:i A', strtotime($referral->referred_at))));
         $content .= '</tr>';
      }
      $content .= '</tbody></table>';
      $content .= '<p><small>See attached CSV for full details.</small></p>';
      return jvbGetEmailTemplate($content, 'Daily Referral Report');
   }
   /**
@@ -753,31 +1015,22 @@
         $total_referrals !== 1 ? 's' : ''
      );
      $content .= '<h3>Top 10 Referrers This Week</h3>';
      $content .= '<table style="width:100%; border-collapse: collapse; margin: 20px 0;">';
      $content .= '<thead><tr style="background: #f5f5f5; text-align: left;">';
      $content .= '<th style="padding: 10px; border: 1px solid #ddd;">Rank</th>';
      $content .= '<th style="padding: 10px; border: 1px solid #ddd;">User</th>';
      $content .= '<th style="padding: 10px; border: 1px solid #ddd;">Total Referrals</th>';
      $content .= '<th style="padding: 10px; border: 1px solid #ddd;">Treated</th>';
      $content .= '</tr></thead><tbody>';
      $referrers = [];
      $rank = 1;
      foreach ($top_referrers as $referrer) {
         $content .= '<tr>';
         $content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%d</td>', $rank++);
         $content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%s</td>',
            esc_html($referrer->user_name));
         $content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%d</td>',
            $referrer->referral_count);
         $content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%d</td>',
            $referrer->treated_count);
         $content .= '</tr>';
         $referrers[] = [
            'label' => '#' . $rank++ . ' - ' . esc_html($referrer->user_name),
            'value' => sprintf(
               '<strong>Total Referrals:</strong> %d | <strong>Treated:</strong> %d',
               $referrer->referral_count,
               $referrer->treated_count
            )
         ];
      }
      $content .= '</tbody></table>';
      $content .= JVB()->email()->table($referrers, 'Top 10 Referrers This Week');
      return jvbGetEmailTemplate($content, 'Weekly Referral Summary');
      return $content;
   }
   /**
@@ -884,6 +1137,7 @@
      </table>
   <?php endif; ?>
      <?php /**
      <script>
         function markReferralTreated(referralId) {
            if (!confirm('Mark this referral as treated? This will create reward records.')) {
@@ -908,6 +1162,7 @@
         }
      </script>
      <?php
    */
   }
   /**
@@ -964,7 +1219,7 @@
   {
      $user_id = get_current_user_id();
      $content = '<aside class="jvb-referral right">';
      $content = '<aside class="main referral right">';
      if (!$user_id) {
         $content .= $this->getUnloggedInReferral();
      } else {
@@ -982,85 +1237,142 @@
      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'
               ]).'
      $reward_text = $this->getRewardText(true);
      // 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, ' ') : '';
      }
      $header = sprintf(
         '<header><h2>%sGet %s.</h2></header><h3>Have a code?</h3>%s<p>Enter your referral code to get started!</p>',
         jvbIcon('confetti'),
         esc_html($reward_text),
         ($referrer_name ? '<p>' . esc_html($referrer_name) . ' invited you to join us</p>' : '')
      );
      $codeForm = sprintf(
         '<form id="referral-code-form">
            %s
               <input type="hidden" name="user_select" value="%s">
               %s%s%s%s
               <div class="row">
               <button type="button" class="button-secondary check-code-btn">
                  %s Verify Code
               </button>
               <button type="submit">
                  Get Started
               </button>
            </div>
               <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>';
            <div class="code-status" hidden></div>
      $loginForm = '<form id ="login-form">
      '.jvbFormStatus().$meta->return('login_email', null, [
            <p class="hint">
               We\'ll send you a link to complete your registration.
            </p>
         </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>',
         jvbFormStatus(),
         esc_attr(get_option(BASE.'referral_role','client')),
         Form::render('referral_name', '', [
            'required'  => true,
            'type'      => 'text',
            'label'     => 'Your Name',
            'placeholder'=> 'Mister Meeseeks',
            'autocomplete'=>'name'
         ]),
         Form::render('referral_email', '', [
            'required'  => true,
            'type'      => 'email',
            'label'     => 'Your Email',
            'autocomplete'=>'email'
         ]).'
      '.$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>';
            'placeholder'=> 'look@me.com',
            'autocomplete'=> 'email'
         ]),
         Form::render('referral_code', $prefill_code, [
            'required'  => true,
            'type'      => 'text',
            'label'     => 'Referral Code',
            'pattern'   => '[A-Za-z0-9]+',
            'maxLength' => 20,
            'autocomplete'=>'off',
            'data-referrer' => $referrer_name
         ]),
         $turnstile,
         jvbIcon('check-circle')
      );
      $loginHeader = sprintf(
         '<header><h2>%sLogin</h2></header><p>Already have an account?<br>Log in to see your rewards!</p>',
         jvbIcon('sign-in')
      );
      $loginForm = sprintf(
         '<form id="login-form">%s%s%s
         <button type="submit">%sLogin 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>',
         jvbFormStatus(),
      Form::render('login_email', null, [
         'required'  => true,
         'type'      => 'email',
         'label'     => 'Your Email',
         'autocomplete'=>'email'
      ]),
      $turnstile,
         jvbIcon('magic-wand')
      );
      $footer = sprintf(
         '<p class="hint">
         <a href="%s" class="text-link">Prefer to use a password?</a>
   </p>',
         wp_login_url()
      );
      $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'
            ],
            'header' => $header,
            'content'   => $codeForm
         ],
         'login'  => [
            'header' => $loginHeader,
            '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,87 +1418,82 @@
      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 (!$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>
      <div class="wrap">
      <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); ?>
      <section class="copy">
         <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 row btw nowrap">
            <code id="referral-link" class="copy-target"><?= esc_url($share_url) ?></code>
            <button type="button" class="copy-btn" data-target="referral-link" title="Copy referral link">
               <?= jvbIcon('copy'); ?>
               <?= jvbIcon('check-circle'); ?>
            </button>
         </div>
         <p class="hint">Quickest and easiest: autofills your code.</p>
         <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 row btw nowrap">
            <code id="referral-code" class="copy-target"><?= esc_html($referral_code) ?></code>
            <button type="button" class="copy-btn" data-target="referral-code" title="Copy referral code">
               <?= jvbIcon('copy'); ?>
               <?= jvbIcon('check-circle'); ?>
            </button>
         </div>
         <p class="hint">Manually copy and paste the code</p>
         <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>
      </section>
      <section class="recent-referrals">
         <h4>Recent Referrals</h4>
         <div class="recent-referrals-list" data-user-id="<?= $user_id ?>">
            <div class="loading">Loading...</div>
         </div>
      </section>
      <section class="stats-summary">
         <div class="row btw">
            <span class="stat-label">Total Referrals</span>
            <span class="stat-value" data-stat="total">-</span>
         </div>
         <div class="row btw">
            <span class="stat-label">Successful</span>
            <span class="stat-value" data-stat="treated">-</span>
         </div>
         <div class="row btw">
            <span class="stat-label">Pending</span>
            <span class="stat-value" data-stat="pending">-</span>
         </div>
         <div class="row btw highlight">
            <span class="stat-label">Available Rewards</span>
            <span class="stat-value" data-stat="rewards">$0.00</span>
         </div>
      </section>
      <a href="<?= get_home_url(null, '/dash/referrals')?>" class="view-dashboard-btn">
         Dashboard <?= jvbIcon('arrow-right', ['size' => 16]); ?>
      </a>
      <p class="hint">Bulk-invite your friends via email - the link will pre-fill their name, email, and code!</p>
      </div>
      <?php
      return ob_get_clean();
   }
@@ -1217,7 +1524,7 @@
      $code = $this->getUserReferralCode($user->ID);
      $yourCode = '';
      if (!is_wp_error($code)) {
      if ($code) {
         $share_url = $this->getShareURL($code);
         $yourCode = sprintf(
            '<div class="callout">
@@ -1227,8 +1534,8 @@
         <p>Or click the button below:</p>
         %s
         </div>',
            jvbEmailLink($code),
            jvbMailButton($share_url, 'Share Your Code')
            JVB()->email()->link($code),
            JVB()->email()->button($share_url, 'Share Your Code')
         );
      }
@@ -1240,10 +1547,9 @@
   {
      return add_query_arg(
         [
            'ref' => $code,
            'action' => 'register'
            'ref' => $code
         ],
         wp_login_url()
         get_home_url()
      );
   }
@@ -1253,15 +1559,12 @@
    * @param int $user_id Referrer's user ID
    * @param string $invitee_email Email of person to invite
    * @param string $invitee_name Name of person to invite
    * @param string $subject
    * @param string $message
    * @return array|WP_Error Result with success/error
    */
   public function sendReferralInvitation(int $user_id, string $invitee_email, string $invitee_name):array|WP_Error
   public function sendReferralInvitation(int $user_id, string $invitee_email, string $invitee_name, string $subject, string $message):array|WP_Error
   {
      // Verify user exists
      if (!$this->checkUser($user_id)) {
         return new WP_Error('invalid_user', 'Invalid user ID');
      }
      // Check email rate limit (15/hour)
      $rate_check = $this->checkEmailRateLimit($user_id);
      if ($rate_check !== true) {
@@ -1274,11 +1577,7 @@
         return new WP_Error('invalid_email', 'Invalid email address');
      }
      // Check if this email has already been invited or registered
      if ($this->isEmailInvited($invitee_email)) {
         return new WP_Error('already_invited', 'This person has already been invited');
      }
      // Check if already registered
      if (email_exists($invitee_email)) {
         return new WP_Error('user_exists', 'This person already has an account');
      }
@@ -1287,33 +1586,60 @@
      $referrer = get_user_by('ID', $user_id);
      $referral_code = $this->getUserReferralCode($user_id);
      if (is_wp_error($referral_code)) {
      if ($referral_code) {
         return $referral_code;
      }
      // Get reward text for email
      $reward_text = $this->settings['referee_reward_type'] === 'percentage'
         ? "Get {$this->settings['referee_reward_amount']}% off your first treatment!"
         : "Get \${$this->settings['referee_reward_amount']} off your first treatment!";
      // Record the invitation attempt (for tracking)
      // Record the invitation attempt (for rate limiting only)
      $this->recordInvitationAttempt($user_id, $invitee_email, $invitee_name);
      // Send magic link via MagicLinkManager
      $result = $this->magic_link->sendMagicLink(
      // Create registration URL with token (opens sidebar with prefilled form)
      $token_data = [
         'name' => sanitize_text_field($invitee_name),
         'email' => $invitee_email,
         'expires' => time() + (30 * DAY_IN_SECONDS)
      ];
      // Encode the token
      $token = base64_encode(json_encode($token_data));
      $registration_url = add_query_arg([
         'ref' => $referral_code,
         'rname' => sanitize_text_field($invitee_name),
         'remail'=> rawurlencode($invitee_email),
      ], home_url('/'));
      // Get reward text for email
      $reward_text = $this->settings['referee_reward_type'] === 'percentage'
         ? "{$this->settings['referee_reward_amount']}% off"
         : "\${$this->settings['referee_reward_amount']} off";
      // Build email content
      $email_content =
         sprintf(
            '<h2>%s invited you to %s!</h2>
         <p>%s</p>
         <div class="callout">
            <h3>Get %s your first treatment!</h3>
         </div>
         <p>Click the button below to register and claim your reward:</p>
         %s
         <p><small>This invitation expires in 30 days.</small></p>',
            esc_html($referrer->display_name),
            esc_html(get_bloginfo('name')),
            nl2br(esc_html($message)),
            esc_html($reward_text),
            JVB()->email()->button($registration_url, 'Register & Get Your Reward')
         );
      // Send email
      $sent = JVB()->email()->sendEmail(
         $invitee_email,
         MagicLinkManager::TYPE_REFERRAL,
         [
            'name' => sanitize_text_field($invitee_name),
            'referral_code' => $referral_code,
            'referrer_id' => $user_id,
            'referrer_name' => $referrer->display_name,
            'reward_text' => $reward_text
         ]
         $subject,
         $email_content
      );
      if (is_wp_error($result)) {
         return $result;
      if (!$sent) {
         return new WP_Error('email_failed', 'Failed to send invitation email');
      }
      return [
@@ -1331,7 +1657,7 @@
    * @param array $invitations Array of ['email' => '', 'name' => '']
    * @return array Results with success/failed arrays
    */
   public function sendBatchReferralInvitations(int $user_id, array $invitations): array
   public function sendBatchReferralInvitations(int $user_id, array $invitations, string $subject, string $message): array
   {
      $results = [
         'success' => [],
@@ -1351,7 +1677,7 @@
            continue;
         }
         $result = $this->sendReferralInvitation($user_id, $email, $name);
         $result = $this->sendReferralInvitation($user_id, $email, $name, $subject, $message);
         if (is_wp_error($result)) {
            $results['failed'][] = [
@@ -1372,7 +1698,7 @@
      return [
         'success' => !empty($results['success']),
         'results' => $results,
         'result' => $results,
         'summary' => sprintf(
            'Sent %d invitations, %d failed',
            count($results['success']),
@@ -1559,22 +1885,31 @@
   /**
    * Add referral settings subpage to admin menu
    * Add referral settings subpage to admin menu
    *
    * @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 +1966,397 @@
    */
   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>
         <!-- 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>
<?php /**
      <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>
*/
      if (is_admin()) {
?>
      <script>
         jQuery(document).ready(function($) {
            // 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') ?>',
                  method: 'GET',
                  data: {
                     offset: page -1,
                     limit: 20,
                     status: status === '' ? 'all' : 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.items.length === 0) {
                  html += '<tr><td colspan="7" style="text-align: center;">No referrals found</td></tr>';
               } else {
                  data.items.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') ?>', // Changed from /mark-consulted
                  method: 'POST',
                  data: JSON.stringify({
                     action: 'consulted',  // Added action parameter
                     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') ?>', // Changed from /mark-treated
                  method: 'POST',
                  data: JSON.stringify({
                     action: 'treated',  // Added action parameter
                     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
      }
      echo $this->renderAdminHTML();
   }
   protected function renderAdminHTML():string
@@ -1660,7 +2367,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 +2380,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 +2482,27 @@
                        </select>
                     </td>
                  </tr>
                  <tr>
                     <th scope="row">
                        <label for="<?= BASE ?>referral_role">Client Import Role</label>
                     </th>
                     <td>
                        <?php
                        $selected_role = get_option(BASE . 'referral_role', '');
                        $roles = wp_roles()->get_names();
                        ?>
                        <select name="<?= BASE ?>referral_role" id="<?= BASE ?>referral_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 +2520,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 +2591,9 @@
      }
      if (!$this->referralPage) {
         $this->referralPage = $this->getReferralPageId();
      }
      if (!$this->referralPage) {
         return;
      }
@@ -1875,7 +2633,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 +2643,426 @@
      }
   }
   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 = $this->getUserReferralCode($user_id);
      if (!$referral_code) {
         $user = get_userdata($user_id);
         $referral_code = $this->generateReferralCode($user);
      }
      return ($out === '') ? $content : '<form id="referrals" class="col" data-save="referrals">'.$out.'</form>';
      $referrals = $this->getUserReferrals($user_id, ['limit' => 20]);
      ob_start();
      $tabs = new Tabs();
      $tabs->addTab('share')
         ->title('Share')
         ->icon('share-fat')
         ->description('Share your code and earn rewards when your referrals complete their first treatment!')
         ->content($this->shareDashboard($user_id, $referral_code));
      $tabs->addTab('referrals')
         ->title('Your Referrals')
         ->icon('hand-heart')
         ->content($this->referralCRUD($user_id));
      ?>
      <div class="referral-dashboard">
         <?= $tabs->render(true);?>
      </div>
      <?php
      return ob_get_clean();
   }
   protected function shareDashboard(int $user_id, string $referral_code):string
   {
      ob_start();
      ?>
      <?php $this->getShareButtons($user_id); ?>
      <!-- Referral Code Card -->
      <details open>
         <summary>Your Code</summary>
         <h3>Share Link</h3>
         <div class="row btw nowrap">
            <code id="referral-link" class="copy-target"><?= home_url('/?ref=' . $referral_code) ?></code>
            <button type="button" class="copy-btn" data-target="referral-link" title="Copy referral link">
               <?= jvbIcon('copy'); ?>
               <?= jvbIcon('check-circle'); ?>
            </button>
         </div>
         <h3>Share Code</h3>
         <div class="row btw nowrap">
            <code id="referral-code" class="copy-target"><?= esc_html($referral_code) ?></code>
            <button type="button" class="copy-btn" data-target="referral-code" title="Copy referral code">
               <?= jvbIcon('copy'); ?>
               <?= jvbIcon('check-circle'); ?>
            </button>
         </div>
      </details>
      <form class="invite">
         <h2>Invite your Friends</h2>
         <p>Or, if you prefer, enter your friends name(s) and email(s), and we'll send off some emails.</p>
         <p><small>(No data is stored. Your friends will get an email from our email.)</small></p>
         <?php
         $invite = [
            'type' => 'taglist',
            'label' => 'Invite Your Friends',
            'hint' => 'Add friends to send them a referral link',
            'add_label' => 'Add Invite',
            'tag_format' => '{{name}} ({{email}})', // or 'first_field', 'all_fields', 'email', etc.
            'fields' => [
               'name' => [
                  'type' => 'text',
                  'label' => 'Name',
                  'placeholder' => 'Full Name',
                  'required' => true
               ],
               'email' => [
                  'type' => 'email',
                  'label' => 'Email',
                  'placeholder' => 'email@example.com',
                  'required' => true
               ]
            ]
         ];
         $fields = [
            'subject'   => [
               'type'   => 'text',
               'label'  => 'Email Subject',
               'value'  => 'Try Legacy for Tattoo Removal',
            ],
            'message'   => [
               'type'      => 'textarea',
               'label'     => 'Customize message',
               'value'  => 'I had a great experience at Legacy Tattoo Removal!
If you click the link below, you can get 20% off your first treatment with them.',
               'hint'      => 'We\'ll add your code and a link automatically.'
            ]
         ];
         echo Form::render('invite', '', $invite);
         ?>
         <details>
            <summary class="icon icon-caret-down">Customize Message</summary>
            <?php
            foreach ($fields as $fieldName => $field) {
               $value = (array_key_exists('value', $field)) ? $field['value'] : [];
               echo Form::render($fieldName, $value, $field);
            }
            ?>
         </details>
         <button type="submit"><?=jvbIcon('envelope')?>Send Invites</button>
      </form>
      <?php
      return ob_get_clean();
   }
   protected function referralCRUD(int $user_id):string
   {
      $stats = $this->getUserStats($user_id);
      ob_start();
      ?>
      <!-- Stats Grid with Updated Labels -->
      <div class="item-grid stats">
         <div class="card">
            <h4>Code Used</h4>
            <span class="stat-number" data-stat="code_used"><?= esc_html($stats['code_used'] ?? 0) ?></span>
            <p class="hint">People who used your code</p>
         </div>
         <div class="card">
            <h4>Treatments</h4>
            <span class="stat-number" data-stat="treatments"><?= esc_html($stats['treatments'] ?? 0) ?></span>
            <p class="hint">Completed first treatment</p>
         </div>
         <div class="card highlight">
            <h4>Total Rewards</h4>
            <span class="stat-number" data-stat="total_rewards">$<?= number_format($stats['total_rewards'] ?? 0, 2) ?></span>
            <p class="hint">Earned from referrals</p>
         </div>
      </div>
      <?php
      // Configure CRUDSkeleton for referrals
      $crud = new CRUDSkeleton();
      $crud->title('Your Referrals', 'Track friends you\'ve invited and rewards earned')
         ->content('referral', 'Referral', 'Referrals')
//       ->initMeta('custom', 'referral')
         ->setFields([
            'referee_name' => [
               'label' => 'Name',
               'type' => 'text',
            ],
            'referee_email' => [
               'label' => 'Email',
               'type' => 'text',
            ],
            'referred_at' => [
               'label' => 'Code Used',
               'type' => 'date',
            ],
            'referral_status' => [
               'label' => 'Status',
               'type' => 'text',
            ]
         ])
         ->setStatuses(['all', 'unused', 'registered', 'consulted', 'completed'])
         ->addViews(['table', 'list'])
         ->defaultView('table')
         ->addCapabilities(['view'])
         ->addDateFilter('referred_at')
         ->showBulkControls(false)
         ->showFilters(false)
         ->useCRUDjs(false); // We'll use our custom Referral.js with DataStore
      // Add custom template for actions column
      $crud->addItemActions(['resend', 'trash']);
      $crud->defineItemAction('resend', [
         'title'  => 'Resend Invitation',
         'icon'   => 'paper-plane-tilt'
      ]);
      $crud->defineItemAction('trash', [
         'title'  => 'Remove from List'
      ]);
      // Custom empty state
      $crud->addTemplate('empty', '
      <template class="emptyState">
         <div class="empty-state">
            <h3>' . jvbDashIcon('hand-heart') . 'Nothing Yet' . jvbDashIcon('hand-heart') . '</h3>
            <p>Start sharing your referral code to earn rewards!</p>
            <p><small><i>Share your code using the "Share" tab below.</i></small></p>
         </div>
      </template>
   ');
      $crud->render();
      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 . 'referral_role'] ?? Site::getDefaultReferralRole());
         update_option(BASE . 'referral_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 (!$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>
            <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
   }
   /**
    * Send notification to referrer when someone registers
    *
    * @param int $referrer_id
    * @param string $referee_name
    */
   protected function sendReferrerNotification(int $referrer_id, string $referee_name): void
   {
      $referrer = get_userdata($referrer_id);
      if (!$referrer) {
         return;
      }
      $subject = sprintf('%s signed up with your referral code!', $referee_name);
      $message = sprintf(
         "Great news! %s just signed up using your referral code.\n\n" .
         "View your referrals: %s",
         $referee_name,
         home_url('/dash/referrals')
      );
      JVB()->email()->sendEmail(
         $referrer->user_email,
         $subject,
         $message
      );
   }
   /**
    * Get welcome message for newly referred user
    *
    * @param int $user_id
    * @return string HTML content for welcome message
    */
   public function getReferralWelcomeMessage(int $user_id): string
   {
      // Check if user was referred
      $referral = $this->getReferralByReferee($user_id);
      if (!$referral || $referral->status !== 'pending') {
         return '';
      }
      // Only show for recent registrations (within 7 days)
      $registered_time = strtotime($referral->referred_at);
      if ((time() - $registered_time) > (7 * DAY_IN_SECONDS)) {
         return '';
      }
      // Get referrer name
      $referrer = get_userdata($referral->referrer_id);
      $referrer_first_name = $referrer ? strtok($referrer->display_name, ' ') : 'Your friend';
      // Get reward text
      $reward_text = $this->getRewardText(); // Just "20% off" or "$25 off"
      $booking_url = apply_filters('jvb_referral_booking_url', home_url('/contact'));
      $estimate_url = apply_filters('jvb_referral_estimate_url', home_url('/estimate'));
      ob_start();
      ?>
      <div class="welcome-banner referral-welcome">
         <div class="banner-content">
            <h3><?= jvbIcon('confetti') ?>Welcome! <small><b><?= esc_html($referrer_first_name) ?></b> invited you to save <b><?= esc_html($reward_text) ?></b>!</small></h3>
            <p>But we're not done yet! Here's what happens next:</p>
            <div class="callout">
               <ol>
                  <li>Book your <b>free consultation</b></li>
                  <li>Come in and we'll assess your tattoo</li>
                  <li>Get <?= esc_html($reward_text) ?> your first treatment!</li>
               </ol>
            </div>
            <p class="hint">
               <strong>Important:</strong> If you book with a different email than
               <strong><?= esc_html(wp_get_current_user()->user_email) ?></strong>,
               please let us know so we can apply your reward!
            </p>
            <ul class="buttons">
               <li><a href="<?= esc_url($estimate_url) ?>" class="button-secondary">
                  <?= jvbIcon('calculator') ?> Get an Estimate First
               </a></li>
               <li><a href="<?= esc_url($booking_url) ?>" class="button-primary">
                  <?= jvbIcon('calendar') ?> Book Free Consult
               </a></li>
            </ul>
         </div>
      </div>
      <?php
      return ob_get_clean();
   }
}