Jake Vanderwerf
4 days ago 747d741293e064a979d7bf6c143ef969ea6d7629
inc/managers/ReferralManager.php
@@ -1,12 +1,10 @@
<?php
namespace JVBase\managers;
use JVBase\managers\MagicLinkManager;
use JVBase\integrations\Cloudflare;
use JVBase\meta\Form;
use JVBase\ui\CRUDSkeleton;
use JVBase\ui\Tabs;
use JVBase\utility\Features;
use JVBase\base\Site;
use WP_User;
use WP_Error;
@@ -22,7 +20,6 @@
 */
class ReferralManager
{
   protected $wpdb;
   protected MagicLinkManager $magic_link;
   protected Cache $cache;
   protected Cache $requestCache;
@@ -39,30 +36,32 @@
   // Default reward settings
   protected array $default_settings = [
      'referrer_reward_applies_to' => 'per_user',  // 'per_user' or 'flat_total'
      'referrer_reward_amount' => 25.00,
      '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'
      'referral_role'   => BASE.'client'
      'from_user_reward_applies_to' => 'per_user',  // 'per_user' or 'flat_total'
      'from_user_reward_amount' => 25.00,
      'from_user_reward_type' => 'fixed',
      'to_user_reward_type' => 'percentage',  // 'percentage' or 'fixed'
      'to_user_reward_amount' => 20,  // 20% or $20
      'to_user_reward_applies_to' => 'first_order',  // 'first_order' or 'all_orders'
   ];
   protected string $role = BASE.'client';
   protected string $role;
   protected array $settings;
   public function __construct()
   {
      $this->defineTables();
      global $wpdb;
      $this->wpdb = $wpdb;
      $this->role = Site::getDefaultReferralRole();
      $this->default_settings['referral_role'] = $this->role;
      $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);
      $this->referrals_table = $wpdb->prefix . BASE . 'referrals';
      $this->rewards_table = $wpdb->prefix . BASE . 'referral_rewards';
      if (JVB_TESTING) {
         $this->cache->flush();
         $this->requestCache->flush();
         $this->statsCache->flush();
      }
      $this->referralPage = $this->getReferralPageId();
      $this->settings = $this->getRewardSettings();
@@ -134,7 +133,7 @@
         $table->setKeys([
            ['key' => 'PRIMARY', 'value' => 'id'],
            ['key' => 'UNIQUE', 'value' => 'to_user (`to_user`)'],
            ['key' => 'UNIQUE', 'value' => '(`to_user`)'],
            'from_user (`from_user`)',
            'status (`status`)',
            'code (`referral_code`)',
@@ -165,8 +164,8 @@
         ]);
         $table->setKeys([
            ['key' => 'PRIMARY', 'value' => 'id'],
            ['key' => 'UNIQUE', 'value' => 'code (`code`)'],
            ['key' => 'PRIMARY', 'value' => '(`id`)'],
            ['key' => 'UNIQUE', 'value' => '(`code`)'],
            'user (`user_id`)',
         ]);
@@ -195,7 +194,7 @@
         $table->setKeys([
            ['key' => 'PRIMARY', 'value' => '(`id`)'],
            ['key' => 'UNIQUE', 'value' => 'patient_guid (`patient_guid`)'],
            ['key' => 'UNIQUE', 'value' => '(`patient_guid`)'],
            'user (`user_id`)',
            'email (`email`)',
         ]);
@@ -216,7 +215,7 @@
            '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",
            'reward_type'     => "ENUM('from_user', 'to_user') NOT NULL",
            'amount'       => 'decimal(10,2) NOT NULL',
            'reward_calculation'=> "ENUM('percentage', 'fixed')",
            'status'       => "ENUM('available', 'redeemed', 'expired', 'cancelled') DEFAULT 'available'",
@@ -238,9 +237,9 @@
         $base = BASE;
         $table->setConstraints([
            "CONSTRAINT `{$base}reward_referral` FOREIGN KEY (`referral_id`)
            REFERENCES {$this->referrals->getFullTableName()} (`id`) ON DELETE CASCADE",
            REFERENCES `{$this->referrals->getFullTableName()}` (`id`) ON DELETE CASCADE",
            "CONSTRAINT `{$base}reward_user` FOREIGN KEY (`user_id`)
            REFERENCES {$table->getUserTable()}` (`ID`) ON DELETE CASCADE"
            REFERENCES `{$table->getUserTable()}` (`ID`) ON DELETE CASCADE"
         ]);
         $table->defineTable();
         $this->rewards = $table;
@@ -329,7 +328,7 @@
         'jvb-data-store',
      ];
      if (Features::hasIntegration('cloudflare') && JVB()->connect('cloudflare')->isSetUp()) {
      if (Site::hasIntegration('cloudflare') && JVB()->connect('cloudflare')->isSetUp()) {
         JVB()->connect('cloudflare')->enqueueTurnstileScripts();
      }
      if (is_singular(BASE.'dash')) {
@@ -396,24 +395,24 @@
    * @param string|null $custom_code Optional custom code
    * @return string|WP_Error
    */
   public function getUserReferralCode(int $user_id, ?string $custom_code = null):array|wp_error
   public function getUserReferralCode(int $user_id, ?string $custom_code = null):string|false
   {
      $user = get_userdata($user_id);
      if (!$user) {
         return new WP_Error('invalid_user', 'User not found');
         return false;
      }
      $existing = $this->codes->pluck('code', ['user_id' => $user_id],'created_at', 'DESC');
      if ($existing && !$custom_code) {
         return $existing;
      if (!empty($existing) && !$custom_code) {
         return $existing[0];
      }
      if ($custom_code && !in_array($custom_code, $existing)) {
      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');
            return $this->codes->pluck('code', ['user_id' => $user_id], 'created_at', 'DESC')[0];
         } else {
            return $existing[0];
         }
      } else {
         return $existing;
      }
      // Generate new code if custom provided or none exists
@@ -421,10 +420,10 @@
      $success = $this->createCode($user_id, $code);
      if ($success) {
         return $this->codes->pluck('code', ['user_id' => $user_id], 'created_at', 'DESC');
         return $this->codes->pluck('code', ['user_id' => $user_id], 'created_at', 'DESC')[0];
      }
      return $code;
      return false;
   }
   /**
@@ -517,18 +516,24 @@
         return false; // No referral code - regular registration
      }
      // Find the referrer
      $referrer = $this->codes->pluck('user_id', ['code' => $referral['referral_code']]);
      if (empty($referrer)) {
      // Find the from_user
      $from_user = $this->codes->pluck('user_id', ['code' => $referral['referral_code']]);
      if (empty($from_user)) {
         //This should not happen, but whatever
         return false;
      }
      $referrer = $referrer[0];
      // Check if this email already has a referral record
      if ($this->isEmailInvited($referral['to_email'])) {
         return false;
      }
      $from_user = $from_user[0];
      $record = $this->referrals->findOrCreate([
         'to_user'   => $user_id,
         'referral_code'   => $referral['referral_code'],
      ], [
         'from_user'    => $referrer,
         'from_user'    => $from_user,
         'to_email'     => $referral['to_email'],
         'to_name'      => $userData['first_name'],
//       'to_phone'     =>
@@ -554,39 +559,38 @@
      $this->cache->flush();
      // Fire action for tracking
      do_action('jvb_referral_processed', $user_id, $referrer->ID, $referral['referral_code']);
      do_action('jvb_referral_processed', $user_id, $from_user->ID, $referral['referral_code']);
      // Send notification to referrer
      $this->sendReferrerNotification($referrer->ID, $userData['display_name']);
      // Send notification to from_user
      $this->sendReferrerNotification($from_user->ID, $userData['display_name']);
      return true;
   }
   /**
    * Create a referral record in the database
    *
    * @param int $referrer_id
    * @param int $referee_id
    * @param int $from_user_id
    * @param int $to_user_id
    * @param string $code
    * @return int|false
    */
   public function createReferral(int $referrer_id, int $referee_id, string $code)
   public function createReferral(int $from_user_id, int $to_user_id, string $code)
   {
      $user = get_user_by('ID', $referee_id);
      $user = get_user_by('ID', $to_user_id);
      return $this->wpdb->insert(
         $this->referrals_table,
      return $this->referrals->findOrCreate([
         [
            'referrer_id' => $referrer_id,
            'referee_id' => $referee_id,
            'referee_name' => $user->display_name,
            'referee_email' => $user->user_email,
            'referee_phone' => get_user_meta($referee_id, BASE . 'phone', true) ?: '',
            'to_user' => $to_user_id,
         ],
         [
            'from_user' => $from_user_id,
            'to_name' => $user->display_name,
            'to_email' => $user->user_email,
            'to_phone' => get_user_meta($to_user_id, BASE . 'phone', true) ?: '',
            'referral_code' => $code,
            'status' => 'pending',
            'referred_at' => current_time('mysql')
         ],
         ['%d', '%d', '%s', '%s', '%s', '%s', '%s', '%s']
      );
         ]
      ]);
   }
   /**
@@ -611,17 +615,16 @@
   }
   /**
    * Get referral record by referee ID
    * Get referral record by to_user ID
    *
    * @param int $referee_id
    * @param int $to_user_id
    * @return object|null
    */
   public function getReferralByReferee(int $referee_id): ?object
   public function getReferralByReferee(int $to_user_id): ?object
   {
      $result = $this->wpdb->get_row($this->wpdb->prepare(
         "SELECT * FROM {$this->referrals_table} WHERE referee_id = %d",
         $referee_id
      ));
      $result = $this->referrals->get([
         'to_user'   => $to_user_id
      ]);
      return $result ?: null;
   }
@@ -637,15 +640,14 @@
   {
      $status = $treated ? 'treated' : 'pending';
      $result = $this->wpdb->update(
         $this->referrals_table,
      $result = $this->referrals->update(
         [
            'status' => $status,
            'status' => $status,
            'treated_at' => $treated ? current_time('mysql') : null
         ],
         ['id' => $referral_id],
         ['%s', '%s'],
         ['%d']
         [
            'id'  => $referral_id
         ]
      );
      if ($result && $treated) {
@@ -657,53 +659,41 @@
   }
   /**
    * Create reward records for both referrer and referee
    * Create reward records for both from_user and to_user
    *
    * @param int $referral_id
    */
   protected function createRewardRecords(int $referral_id): void
   {
      $referral = $this->wpdb->get_row($this->wpdb->prepare(
         "SELECT * FROM {$this->referrals_table} WHERE id = %d",
         $referral_id
      ));
      $referral = $this->referrals->get(['id' => $referral_id]);
      if (!$referral) {
         return;
      }
      // Create referrer reward
      $this->wpdb->insert(
         $this->rewards_table,
         [
            'referral_id' => $referral_id,
            'user_id' => $referral->referrer_id,
            'reward_type' => 'referrer',
            'amount' => $this->settings['referrer_reward_amount'],
            'status' => 'available',
            'created_at' => current_time('mysql')
         ],
         ['%d', '%d', '%s', '%f', '%s', '%s']
      );
      // Create from_user reward
      $fromUserReward = $this->rewards->insert([
         'referral_id'  => $referral_id,
         'user_id'      => $referral->from_user,
         'reward_type'  => 'from_user',
         'amount'    => $this->settings['from_user_reward_amount'],
         'reward_calculation' => $this->settings['from_user_reward_type']
      ]);
      // Create referee reward
      $referee_amount = $this->settings['referee_reward_type'] === 'percentage'
         ? $this->settings['referee_reward_amount']  // Store as percentage
         : $this->settings['referee_reward_amount'];  // Store as fixed amount
      $this->wpdb->insert(
         $this->rewards_table,
         [
            'referral_id' => $referral_id,
            'user_id' => $referral->referee_id,
            'reward_type' => 'referee',
            'amount' => $referee_amount,
            'reward_calculation' => $this->settings['referee_reward_type'],
            'status' => 'available',
            'created_at' => current_time('mysql')
         ],
         ['%d', '%d', '%s', '%f', '%s', '%s', '%s']
      );
      // Create to_user reward
      $to_user_amount = $this->settings['to_user_reward_type'] === 'percentage'
         ? $this->settings['to_user_reward_amount']  // Store as percentage
         : $this->settings['to_user_reward_amount'];  // Store as fixed amount
      $toUserReward = $this->rewards->insert([
         'referral_id'  => $referral_id,
         'user_id'      => $referral->to_user,
         'reward_type'  => 'to_user',
         'amount'    => $to_user_amount,
         'reward_calculation' => $this->settings['to_user_reward_type']
      ]);
   }
   /**
@@ -719,47 +709,43 @@
         'status' => 'all',
         'limit' => 100,
         'offset' => 0,
         'orderby' => 'referred_at',
         'orderby' => 'created_at',
         'order' => 'DESC'
      ];
      $args = wp_parse_args($args, $defaults);
      $args['status'] = strtolower($args['status']);
      $tableArgs = [
         'where' => [
            'from_user' => $user_id,
         ],
         'limit'     => $args['limit'],
         'offset' => $args['offset'],
         'orderby'   => $args['orderby'],
         'order'     => $args['order']
      ];
      if (in_array($args['status'], ['pending', 'consulted', 'treated', 'cancelled'])){
         $tableArgs['status'] = $args['status'];
      }
      $referrals = $this->referrals->getMany($tableArgs);
      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']);
            }
            $query = "SELECT * FROM {$this->referrals_table}
                  {$where}
                  ORDER BY {$args['orderby']} {$args['order']}
                  LIMIT {$args['limit']} OFFSET {$args['offset']}";
            $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);
         }
      );
      return array_map(function($referral) {
         $last_invite = get_transient('referral_last_invite_' . md5($referral->to_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,
            'to_name'   => $referral->to_name,
            'to_email'  => $referral->to_email,
            'referred_at'  => JVB()->routes('referral')->formatTimestamp($referral->referred_at),
            'referral_status'=> $status,
            'can_resend'   => $can_resend
         ];
      }, $referrals);
   }
@@ -774,24 +760,23 @@
      return $this->statsCache->remember(
         $user_id,
         function() use ($user_id) {
            $stats = $this->wpdb->get_row($this->wpdb->prepare(
            $stats = $this->referrals->queryResults(
               "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);
                    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 {table}
                WHERE from_user = %d",
               [$user_id],
               ARRAY_A
            );
            $stats = $stats[0] ?? [];
            // 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
            ));
            $rewards = $this->rewards->queryVar(
               "SELECT SUM(amount) FROM {table} WHERE user_id = %d AND reward_type = 'from_user'",
               [$user_id]
            );
            $stats['total_rewards'] = floatval($rewards ?? 0);
            $stats['user_id'] = $user_id;
@@ -801,7 +786,7 @@
   }
   /**
    * Get top referrers for a time period
    * Get top from_users for a time period
    *
    * @param int $limit
    * @param string $period 'day'|'week'|'month'|'all'
@@ -810,44 +795,40 @@
   public function getTopReferrers(int $limit = 10, string $period = 'all'): array
   {
      return $this->statsCache->remember(
         $this->statsCache->generateKey(['limit'=>$limit, 'period' => $period]),
         $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)",
               $date_clause = match($period) {
                  'day'   => "created_at >= DATE_SUB(NOW(), INTERVAL 1 DAY)",
                  'week'  => "created_at >= DATE_SUB(NOW(), INTERVAL 1 WEEK)",
                  'month' => "created_at >= DATE_SUB(NOW(), INTERVAL 1 MONTH)",
                  default => "1=1"
               };
               $where = "WHERE {$date_where}";
               $where = "WHERE {$date_clause}";
            }
            $query = "SELECT
                    referrer_id,
            $results = $this->referrals->queryResults(
               "SELECT
                    from_user,
                    COUNT(*) as referral_count,
                    SUM(CASE WHEN status = 'treated' THEN 1 ELSE 0 END) as treated_count
                  FROM {$this->referrals_table}
                  {$where}
                  GROUP BY referrer_id
                  ORDER BY referral_count DESC
                  LIMIT {$limit}";
                FROM {table}
                {$where}
                GROUP BY from_user
                ORDER BY referral_count DESC
                LIMIT {$limit}"
            );
            $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 : '';
               $user = get_user_by('ID', $result->from_user);
               $result->user_name  = $user ? $user->display_name : 'Unknown';
               $result->user_email = $user ? $user->user_email   : '';
            }
            return $results;
         }
      );
   }
   /**
@@ -857,14 +838,14 @@
   {
      $yesterday = date('Y-m-d', strtotime('-1 day'));
      $new_referrals = $this->wpdb->get_results($this->wpdb->prepare(
         "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
        ORDER BY r.referred_at DESC",
         $yesterday
      ));
      $new_referrals = $this->referrals->queryResults(
         "SELECT {table}.*, u.display_name as from_user_name
        FROM {table}
        JOIN {$this->referrals->getUserTable()} u ON {table}.from_user = u.ID
        WHERE DATE({table}.created_at) = %s
        ORDER BY {table}.created_at DESC",
         [$yesterday]
      );
      if (empty($new_referrals)) {
         return;
@@ -884,13 +865,13 @@
      foreach ($new_referrals as $ref) {
         $cardContent = sprintf(
            '<p><strong>%s</strong> (%s)</p>',
            esc_html($ref->referee_name),
            esc_html($ref->referee_email)
            esc_html($ref->to_name),
            esc_html($ref->to_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),
            esc_html($ref->from_name),
            JVB()->email()->badge($ref->referral_code, 'info')
         );
@@ -909,14 +890,13 @@
   }
   /**
    * Send weekly report with top referrers
    * Send weekly report with top from_users
    */
   public function sendWeeklyReport(): void
   {
      $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)"
      $top_from_users = $this->getTopReferrers(10, 'week');
      $total_referrals = $this->referrals->queryVar(
         "SELECT COUNT(*) FROM {table} WHERE created_at >= DATE_SUB(NOW(), INTERVAL 1 WEEK)"
      );
      if ($total_referrals == 0) {
@@ -935,7 +915,7 @@
      // Leaderboard style
      $rank = 1;
      foreach ($top_referrers as $referrer) {
      foreach ($top_from_users as $from_user) {
         $rankBadge = $rank <= 3
            ? JVB()->email()->badge('#' . $rank, $rank === 1 ? 'success' : 'info')
            : '<span style="font-weight:600;color:' . JVB()->email()->colours['dark-200'] . ';">#' . $rank . '</span>';
@@ -943,12 +923,12 @@
         $cardContent = sprintf(
            '<p>%s <strong>%s</strong></p>',
            $rankBadge,
            esc_html($referrer->user_name)
            esc_html($from_user->user_name)
         );
         $stats = [
            JVB()->email()->stat($referrer->referral_count, 'Total Referrals'),
            JVB()->email()->stat($referrer->treated_count, 'Treated')
            JVB()->email()->stat($from_user->referral_count, 'Total Referrals'),
            JVB()->email()->stat($from_user->treated_count, 'Treated')
         ];
         $cardContent .= JVB()->email()->grid($stats, 2);
@@ -979,10 +959,10 @@
            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->from_name ?? 'Unknown',
                  $referral->to_name,
                  $referral->to_email,
                  $referral->to_phone,
                  $referral->referral_code,
                  $referral->status,
                  $referral->referred_at,
@@ -999,11 +979,11 @@
   /**
    * Generate HTML email for weekly report
    *
    * @param array $top_referrers
    * @param array $top_from_users
    * @param int $total_referrals
    * @return string
    */
   protected function generateWeeklyReportEmail(array $top_referrers, int $total_referrals): string
   protected function generateWeeklyReportEmail(array $top_from_users, int $total_referrals): string
   {
      $content = sprintf(
         '<p>This week you had <strong>%d total referral%s</strong>.</p>',
@@ -1011,20 +991,20 @@
         $total_referrals !== 1 ? 's' : ''
      );
      $referrers = [];
      $from_users = [];
      $rank = 1;
      foreach ($top_referrers as $referrer) {
         $referrers[] = [
            'label' => '#' . $rank++ . ' - ' . esc_html($referrer->user_name),
      foreach ($top_from_users as $from_user) {
         $from_users[] = [
            'label' => '#' . $rank++ . ' - ' . esc_html($from_user->user_name),
            'value' => sprintf(
               '<strong>Total Referrals:</strong> %d | <strong>Treated:</strong> %d',
               $referrer->referral_count,
               $referrer->treated_count
               $from_user->referral_count,
               $from_user->treated_count
            )
         ];
      }
      $content .= JVB()->email()->table($referrers, 'Top 10 Referrers This Week');
      $content .= JVB()->email()->table($from_users, 'Top 10 Referrers This Week');
      return $content;
   }
@@ -1115,8 +1095,8 @@
         <tbody>
         <?php foreach ($referrals as $referral): ?>
            <tr>
               <td><?php echo esc_html($referral->referee_name); ?></td>
               <td><?php echo esc_html($referral->referee_email); ?></td>
               <td><?php echo esc_html($referral->to_name); ?></td>
               <td><?php echo esc_html($referral->to_email); ?></td>
               <td><?php echo esc_html(ucfirst($referral->status)); ?></td>
               <td><?php echo esc_html($referral->referred_at); ?></td>
               <td>
@@ -1246,53 +1226,36 @@
      // Pre-fill code if from referral link
      $prefill_code = $_GET['ref'] ?? '';
      $referrer_name = '';
      $from_user_name = '';
      if ($prefill_code) {
         $referrer = $this->getUserByReferralCode($prefill_code);
         $referrer_name = $referrer ? strtok($referrer->display_name, ' ') : '';
         $from_user = $this->getUserByReferralCode($prefill_code);
         $from_user_name = $from_user ? strtok($from_user->display_name, ' ') : '';
      }
      $codeForm = '<div class="referral-reward-banner">
      '.jvbIcon('confetti').'
      <h4>Get ' . esc_html($reward_text) . '!</h4>
      ' . ($referrer_name ? '<p>' . esc_html($referrer_name) . ' invited you to join us</p>' : '') . '
   </div>
   <form id="referral-code-form">
            '.jvbFormStatus(). '
    <input type="hidden" name="user_select" value="' . 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',
            '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.'
            <button type="button" class="button-secondary check-code-btn">
               '.jvbIcon('check-circle', ['size' => 16]).' Verify Code
            </button>
            <div class="code-status" hidden></div>
            <button type="submit">
               Get Started
            </button>
      $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),
         ($from_user_name ? '<p>' . esc_html($from_user_name) . ' invited you to join us</p>' : '')
      );
            <p class="helper-text">
      $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>
            <div class="code-status" hidden></div>
            <p class="hint">
               We\'ll send you a link to complete your registration.
            </p>
         </form>
@@ -1300,36 +1263,78 @@
            <h3>Check Your Email!</h3>
            <p>We\'ve sent you a magic link to complete your registration. Click the link to activate your account and claim your reward!</p>
            <p class="hint">Can\'t find it? Check your spam folder.</p>
         </div>';
      $loginForm = '<form id="login-form">
   '.jvbFormStatus().Form::render('login_email', null, [
         </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-from-user' => $from_user_name
         ]),
         $turnstile,
         jvbIcon('check-circle')
      );
      $footer = '<div class="referral-footer">
      <a href="' . wp_login_url() . '" class="text-link">Prefer to use a password?</a>
   </div>';
      $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 your referral code to get started'
            ],
            'header' => $header,
            'content'   => $codeForm
         ],
         'login'  => [
            'header' => $loginHeader,
            'title'     => 'Login',
            'description'  => [
               'Already have an account? Log in to see your rewards'
@@ -1346,14 +1351,14 @@
   protected function getReferralSuccessMessage(string $code): string
   {
      $referrer = $this->getUserByReferralCode($code);
      $from_user = $this->getUserByReferralCode($code);
      if (!$referrer) {
      if (!$from_user) {
         return '';
      }
      $reward_amount = $this->settings['referee_reward_amount'] ?? 20;
      $reward_type = $this->settings['referee_reward_type'] ?? 'percentage';
      $reward_amount = $this->settings['to_user_reward_amount'] ?? 20;
      $reward_type = $this->settings['to_user_reward_type'] ?? 'percentage';
      $reward_text = $reward_type === 'percentage'
         ? $reward_amount . '% off'
@@ -1382,7 +1387,7 @@
         </a>
         <div class="referred-by">
            Referred by <strong><?php echo esc_html($referrer->display_name); ?></strong>
            Referred by <strong><?php echo esc_html($from_user->display_name); ?></strong>
         </div>
      </div>
      <?php
@@ -1395,13 +1400,14 @@
   public function getLoggedInReferral(int $user_id): string
   {
      $referral_code = $this->getUserReferralCode($user_id);
      if (is_wp_error($referral_code)) {
      if (!$referral_code) {
         return '';
      }
      $share_url = $this->getShareURL($referral_code);
      ob_start();
      ?>
      <div class="wrap">
      <header>
         <h3>Share the â™¡</h3>
         <p>Invite friends. Earn rewards.</p>
@@ -1409,9 +1415,9 @@
      <?php $this->getShareButtons($user_id); ?>
      <div class="copy-section">
      <section class="copy">
         <h4>Your Referral Link</h4>
         <div class="copy-group row btw nowrap">
         <div class="copy-group row x-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'); ?>
@@ -1422,7 +1428,7 @@
         <h4>Your Code</h4>
         <div class="copy-group row btw nowrap">
         <div class="copy-group row x-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'); ?>
@@ -1431,39 +1437,39 @@
         </div>
         <p class="hint">Manually copy and paste the code</p>
      </div>
      </section>
      <div class="recent-referrals-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>
      </div>
      </section>
      <div class="stats-summary">
         <div class="stat-row">
      <section class="stats-summary">
         <div class="row x-btw">
            <span class="stat-label">Total Referrals</span>
            <span class="stat-value" data-stat="total">-</span>
         </div>
         <div class="stat-row">
         <div class="row x-btw">
            <span class="stat-label">Successful</span>
            <span class="stat-value" data-stat="treated">-</span>
         </div>
         <div class="stat-row">
         <div class="row x-btw">
            <span class="stat-label">Pending</span>
            <span class="stat-value" data-stat="pending">-</span>
         </div>
         <div class="stat-row highlight">
         <div class="row x-btw highlight">
            <span class="stat-label">Available Rewards</span>
            <span class="stat-value" data-stat="rewards">$0.00</span>
         </div>
      </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();
   }
@@ -1476,8 +1482,8 @@
         return $content;
      }
      $reward_amount = $this->settings['referee_reward_amount'] ?? 20;
      $reward_type = $this->settings['referee_reward_type'] ?? 'percentage';
      $reward_amount = $this->settings['to_user_reward_amount'] ?? 20;
      $reward_type = $this->settings['to_user_reward_type'] ?? 'percentage';
      $reward_text = $reward_type === 'percentage'
         ? $reward_amount . '% off'
@@ -1494,7 +1500,7 @@
      $code = $this->getUserReferralCode($user->ID);
      $yourCode = '';
      if (!is_wp_error($code)) {
      if ($code) {
         $share_url = $this->getShareURL($code);
         $yourCode = sprintf(
            '<div class="callout">
@@ -1552,11 +1558,11 @@
         return new WP_Error('user_exists', 'This person already has an account');
      }
      // Get referrer info
      $referrer = get_user_by('ID', $user_id);
      // Get from_user info
      $from_user = get_user_by('ID', $user_id);
      $referral_code = $this->getUserReferralCode($user_id);
      if (is_wp_error($referral_code)) {
      if ($referral_code) {
         return $referral_code;
      }
@@ -1579,9 +1585,9 @@
      ], 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";
      $reward_text = $this->settings['to_user_reward_type'] === 'percentage'
         ? "{$this->settings['to_user_reward_amount']}% off"
         : "\${$this->settings['to_user_reward_amount']} off";
      // Build email content
      $email_content =
@@ -1594,7 +1600,7 @@
         <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($from_user->display_name),
            esc_html(get_bloginfo('name')),
            nl2br(esc_html($message)),
            esc_html($reward_text),
@@ -1713,10 +1719,7 @@
      }
      // Check if there's a pending referral for this email
      $existing = $this->wpdb->get_var($this->wpdb->prepare(
         "SELECT id FROM {$this->referrals_table} WHERE referee_email = %s",
         $email
      ));
      $existing = $this->referrals->pluck('id', ['to_email' => $email]);
      return !empty($existing);
   }
@@ -1788,25 +1791,24 @@
    */
   public function exportReferrals(string $start_date, string $end_date): string
   {
      $referrals = $this->wpdb->get_results($this->wpdb->prepare(
      $referrals = $this->referrals->queryResults(
         "SELECT
            r.id,
            r.referee_name,
            r.referee_email,
            r.referee_phone,
            r.referral_code,
            r.referred_at,
            r.status,
            r.treated_at,
            u.display_name as referrer_name,
            u.user_email as referrer_email
        FROM {$this->referrals_table} r
        JOIN {$this->wpdb->users} u ON r.referrer_id = u.ID
        WHERE DATE(r.referred_at) BETWEEN %s AND %s
        ORDER BY r.referred_at DESC",
         $start_date,
         $end_date
      ));
            {table}.id,
            {table}.to_name,
            {table}.to_email,
            {table}.to_phone,
            {table}.referral_code,
            {table}.created_at,
            {table}.status,
            {table}.treated_at,
            u.display_name as from_name,
            u.user_email as from_email
        FROM {table}
        JOIN {$this->referrals->getUserTable()} u ON {table}.from_user = u.ID
        WHERE DATE({table}.created_at) BETWEEN %s AND %s
        ORDER BY {table}.created_at DESC",
         [$start_date, $end_date]
      );
      // Build CSV
      $csv_lines = [];
@@ -1829,15 +1831,15 @@
      foreach ($referrals as $ref) {
         $csv_lines[] = [
            $ref->id,
            $ref->referee_name,
            $ref->referee_email,
            $ref->referee_phone ?: 'N/A',
            $ref->to_name,
            $ref->to_email,
            $ref->to_phone ?: 'N/A',
            $ref->referral_code,
            $ref->referred_at,
            ucfirst($ref->status),
            $ref->treated_at ?: 'N/A',
            $ref->referrer_name,
            $ref->referrer_email
            $ref->from_name,
            $ref->from_email
         ];
      }
@@ -1914,19 +1916,19 @@
   public function sanitizeRewardSettings(array $settings): array
   {
      return [
         'referrer_reward_applies_to' => in_array($settings['referrer_reward_applies_to'] ?? '', ['per_user', 'flat_total'])
            ? $settings['referrer_reward_applies_to']
         'from_user_reward_applies_to' => in_array($settings['from_user_reward_applies_to'] ?? '', ['per_user', 'flat_total'])
            ? $settings['from_user_reward_applies_to']
            : 'per_user',
         'referrer_reward_amount' => floatval($settings['referrer_reward_amount'] ?? 25.00),
         'referrer_reward_type' => in_array($settings['referrer_reward_type'] ?? '', ['fixed', 'percentage'])
            ? $settings['referrer_reward_type']
         'from_user_reward_amount' => floatval($settings['from_user_reward_amount'] ?? 25.00),
         'from_user_reward_type' => in_array($settings['from_user_reward_type'] ?? '', ['fixed', 'percentage'])
            ? $settings['from_user_reward_type']
            : 'fixed',
         'referee_reward_type' => in_array($settings['referee_reward_type'] ?? '', ['percentage', 'fixed'])
            ? $settings['referee_reward_type']
         'to_user_reward_type' => in_array($settings['to_user_reward_type'] ?? '', ['percentage', 'fixed'])
            ? $settings['to_user_reward_type']
            : 'percentage',
         'referee_reward_amount' => floatval($settings['referee_reward_amount'] ?? 20),
         'referee_reward_applies_to' => in_array($settings['referee_reward_applies_to'] ?? '', ['first_order', 'all_orders'])
            ? $settings['referee_reward_applies_to']
         'to_user_reward_amount' => floatval($settings['to_user_reward_amount'] ?? 20),
         'to_user_reward_applies_to' => in_array($settings['to_user_reward_applies_to'] ?? '', ['first_order', 'all_orders'])
            ? $settings['to_user_reward_applies_to']
            : 'first_order',
      ];
   }
@@ -2216,12 +2218,12 @@
               } 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>' + (ref.from_name || 'Unknown') + '</td>';
                     html += '<td>' + (ref.to_user_display_name || ref.to_name) + '</td>';
                     html += '<td>' + (ref.to_user_display_email || ref.to_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>' + (ref.from_user_total_referrals || 0) + '</td>';
                     html += '<td class="referral-actions">';
                     if (ref.status === 'pending') {
@@ -2378,24 +2380,24 @@
                  </tr>
                  <tr>
                     <th scope="row">
                        <label for="referrer_reward_type">Reward Type</label>
                        <label for="from_user_reward_type">Reward Type</label>
                     </th>
                     <td>
                        <select name="referrer_reward_type" id="referrer_reward_type">
                           <option value="fixed" <?php selected($this->settings['referrer_reward_type'], 'fixed'); ?>>Fixed Amount</option>
                           <option value="percentage" <?php selected($this->settings['referrer_reward_type'], 'percentage'); ?>>Percentage</option>
                        <select name="from_user_reward_type" id="from_user_reward_type">
                           <option value="fixed" <?php selected($this->settings['from_user_reward_type'], 'fixed'); ?>>Fixed Amount</option>
                           <option value="percentage" <?php selected($this->settings['from_user_reward_type'], 'percentage'); ?>>Percentage</option>
                        </select>
                     </td>
                  </tr>
                  <tr>
                     <th scope="row">
                        <label for="referrer_reward_amount">Reward Amount</label>
                        <label for="from_user_reward_amount">Reward Amount</label>
                     </th>
                     <td>
                        <input type="number"
                              name="referrer_reward_amount"
                              id="referrer_reward_amount"
                              value="<?= esc_attr($this->settings['referrer_reward_amount']) ?>"
                              name="from_user_reward_amount"
                              id="from_user_reward_amount"
                              value="<?= esc_attr($this->settings['from_user_reward_amount']) ?>"
                              step="0.01"
                              min="0">
                        <p class="description">Amount in dollars or percentage</p>
@@ -2403,12 +2405,12 @@
                  </tr>
                  <tr>
                     <th scope="row">
                        <label for="referrer_reward_applies_to">Applies To</label>
                        <label for="from_user_reward_applies_to">Applies To</label>
                     </th>
                     <td>
                        <select name="referrer_reward_applies_to" id="referrer_reward_applies_to">
                           <option value="per_user" <?php selected($this->settings['referrer_reward_applies_to'], 'per_user'); ?>>Per User Referred</option>
                           <option value="flat_total" <?php selected($this->settings['referrer_reward_applies_to'], 'flat_total'); ?>>Flat Total</option>
                        <select name="from_user_reward_applies_to" id="from_user_reward_applies_to">
                           <option value="per_user" <?php selected($this->settings['from_user_reward_applies_to'], 'per_user'); ?>>Per User Referred</option>
                           <option value="flat_total" <?php selected($this->settings['from_user_reward_applies_to'], 'flat_total'); ?>>Flat Total</option>
                        </select>
                     </td>
                  </tr>
@@ -2418,24 +2420,24 @@
                  </tr>
                  <tr>
                     <th scope="row">
                        <label for="referee_reward_type">Reward Type</label>
                        <label for="to_user_reward_type">Reward Type</label>
                     </th>
                     <td>
                        <select name="referee_reward_type" id="referee_reward_type">
                           <option value="percentage" <?php selected($this->settings['referee_reward_type'], 'percentage'); ?>>Percentage</option>
                           <option value="fixed" <?php selected($this->settings['referee_reward_type'], 'fixed'); ?>>Fixed Amount</option>
                        <select name="to_user_reward_type" id="to_user_reward_type">
                           <option value="percentage" <?php selected($this->settings['to_user_reward_type'], 'percentage'); ?>>Percentage</option>
                           <option value="fixed" <?php selected($this->settings['to_user_reward_type'], 'fixed'); ?>>Fixed Amount</option>
                        </select>
                     </td>
                  </tr>
                  <tr>
                     <th scope="row">
                        <label for="referee_reward_amount">Reward Amount</label>
                        <label for="to_user_reward_amount">Reward Amount</label>
                     </th>
                     <td>
                        <input type="number"
                              name="referee_reward_amount"
                              id="referee_reward_amount"
                              value="<?= esc_attr($this->settings['referee_reward_amount']) ?>"
                              name="to_user_reward_amount"
                              id="to_user_reward_amount"
                              value="<?= esc_attr($this->settings['to_user_reward_amount']) ?>"
                              step="0.01"
                              min="0">
                        <p class="description">Amount in dollars or percentage</p>
@@ -2443,12 +2445,12 @@
                  </tr>
                  <tr>
                     <th scope="row">
                        <label for="referee_reward_applies_to">Applies To</label>
                        <label for="to_user_reward_applies_to">Applies To</label>
                     </th>
                     <td>
                        <select name="referee_reward_applies_to" id="referee_reward_applies_to">
                           <option value="first_order" <?php selected($this->settings['referee_reward_applies_to'], 'first_order'); ?>>First Order Only</option>
                           <option value="all_orders" <?php selected($this->settings['referee_reward_applies_to'], 'all_orders'); ?>>All Orders</option>
                        <select name="to_user_reward_applies_to" id="to_user_reward_applies_to">
                           <option value="first_order" <?php selected($this->settings['to_user_reward_applies_to'], 'first_order'); ?>>First Order Only</option>
                           <option value="all_orders" <?php selected($this->settings['to_user_reward_applies_to'], 'all_orders'); ?>>All Orders</option>
                        </select>
                     </td>
                  </tr>
@@ -2621,10 +2623,11 @@
      // Regular users get their referral dashboard
      $user_id = get_current_user_id();
      $referral_code = get_user_meta($user_id, BASE . 'referral_code', true);
      $referral_code = $this->getUserReferralCode($user_id);
      if (!$referral_code) {
         $referral_code = $this->getUserReferralCode($user_id);
         $user = get_userdata($user_id);
         $referral_code = $this->generateReferralCode($user);
      }
      $referrals = $this->getUserReferrals($user_id, ['limit' => 20]);
@@ -2658,9 +2661,10 @@
      <?php $this->getShareButtons($user_id); ?>
      <!-- Referral Code Card -->
      <div class="card">
      <details open>
         <summary>Your Code</summary>
         <h3>Share Link</h3>
         <div class="row btw nowrap">
         <div class="row x-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'); ?>
@@ -2668,14 +2672,14 @@
            </button>
         </div>
         <h3>Share Code</h3>
         <div class="row btw nowrap">
         <div class="row x-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>
      </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>
@@ -2766,11 +2770,11 @@
         ->content('referral', 'Referral', 'Referrals')
//       ->initMeta('custom', 'referral')
         ->setFields([
            'referee_name' => [
            'to_name' => [
               'label' => 'Name',
               'type' => 'text',
            ],
            'referee_email' => [
            'to_email' => [
               'label' => 'Email',
               'type' => 'text',
            ],
@@ -2839,17 +2843,17 @@
         update_option(BASE . 'referral_page_id', $page_id);
         // Save client import role
         $import_role = sanitize_text_field($post_data[BASE . 'referral_role'] ?? JVB_USER);
         $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')
            'from_user_reward_type' => sanitize_text_field($post_data['from_user_reward_type'] ?? 'fixed'),
            'from_user_reward_amount' => floatval($post_data['from_user_reward_amount'] ?? 25.00),
            'from_user_reward_applies_to' => sanitize_text_field($post_data['from_user_reward_applies_to'] ?? 'per_user'),
            'to_user_reward_type' => sanitize_text_field($post_data['to_user_reward_type'] ?? 'percentage'),
            'to_user_reward_amount' => floatval($post_data['to_user_reward_amount'] ?? 20),
            'to_user_reward_applies_to' => sanitize_text_field($post_data['to_user_reward_applies_to'] ?? 'first_order')
         ];
         update_option(BASE . 'referral_settings', $settings);
@@ -2868,15 +2872,15 @@
   }
   /**
    * Get formatted reward text for referee
    * Get formatted reward text for to_user
    *
    * @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_amount = $this->settings['to_user_reward_amount'] ?? 20;
      $reward_type = $this->settings['to_user_reward_type'] ?? 'percentage';
      $reward_text = $reward_type === 'percentage'
         ? $reward_amount . '% off'
@@ -2892,7 +2896,7 @@
   public function getShareButtons(int $user_id):void
   {
      $referral_code = $this->getUserReferralCode($user_id);
      if (is_wp_error($referral_code)) {
      if (!$referral_code) {
         return;
      }
@@ -2908,7 +2912,7 @@
      ?>
      <nav class="share">
         <h4>Quick Share</h4>
         <ul class="share-buttons-grid">
         <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'); ?>
@@ -2941,28 +2945,28 @@
   }
   /**
    * Send notification to referrer when someone registers
    * Send notification to from_user when someone registers
    *
    * @param int $referrer_id
    * @param string $referee_name
    * @param int $from_user_id
    * @param string $to_name
    */
   protected function sendReferrerNotification(int $referrer_id, string $referee_name): void
   protected function sendReferrerNotification(int $from_user_id, string $to_name): void
   {
      $referrer = get_userdata($referrer_id);
      if (!$referrer) {
      $from_user = get_userdata($from_user_id);
      if (!$from_user) {
         return;
      }
      $subject = sprintf('%s signed up with your referral code!', $referee_name);
      $subject = sprintf('%s signed up with your referral code!', $to_name);
      $message = sprintf(
         "Great news! %s just signed up using your referral code.\n\n" .
         "View your referrals: %s",
         $referee_name,
         $to_name,
         home_url('/dash/referrals')
      );
      JVB()->email()->sendEmail(
         $referrer->user_email,
         $from_user->user_email,
         $subject,
         $message
      );
@@ -2989,9 +2993,9 @@
         return '';
      }
      // Get referrer name
      $referrer = get_userdata($referral->referrer_id);
      $referrer_first_name = $referrer ? strtok($referrer->display_name, ' ') : 'Your friend';
      // Get from_user name
      $from_user = get_userdata($referral->from_user);
      $from_user_first_name = $from_user ? strtok($from_user->display_name, ' ') : 'Your friend';
      // Get reward text
      $reward_text = $this->getRewardText(); // Just "20% off" or "$25 off"
@@ -3003,7 +3007,7 @@
      ?>
      <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>
            <h3><?= jvbIcon('confetti') ?>Welcome! <small><b><?= esc_html($from_user_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>
@@ -3032,5 +3036,118 @@
      <?php
      return ob_get_clean();
   }
   public function updateStatus(int $referral_id, string $status): bool|WP_Error
   {
      $referral = $this->referrals->get(['id' => $referral_id]);
      if (!$referral) {
         return new WP_Error('not_found', 'Referral not found');
      }
      $data = ['status' => $status, "{$status}_at" => current_time('mysql')];
      if ($status === 'treated') {
         $data['treatment_count'] = ($referral->treatment_count ?? 0) + 1;
      }
      $result = $this->referrals->update($data, ['id' => $referral_id]);
      if ($result === false) {
         return new WP_Error('update_failed', 'Failed to update referral status');
      }
      if ($status === 'treated') {
         $this->createRewardRecords($referral_id);
      }
      $this->cache->flush();
      return true;
   }
   public function removeReferral(int $referral_id, int $user_id): bool|WP_Error
   {
      $referral = $this->referrals->get(['id' => $referral_id]);
      if (!$referral) {
         return new WP_Error('not_found', 'Referral not found');
      }
      if ($referral->from_user != $user_id && !current_user_can('manage_options')) {
         return new WP_Error('unauthorized', 'Unauthorized');
      }
      if ($referral->status !== 'pending') {
         return new WP_Error('invalid_status', 'Can only remove pending referrals');
      }
      $this->referrals->delete(['id' => $referral_id]);
      $this->cache->flush();
      return true;
   }
   public function resendInvitation(int $referral_id, int $user_id): bool|WP_Error
   {
      $referral = $this->referrals->where(['id' => $referral_id, 'from_user' => $user_id])->first();
      if (!$referral) {
         return new WP_Error('not_found', 'Referral not found');
      }
      $transient_key = 'referral_last_invite_' . md5($referral->to_email);
      if (get_transient($transient_key)) {
         return new WP_Error('rate_limit', 'Can only resend once per week');
      }
      $result = $this->sendReferralInvitation(
         $user_id,
         $referral->to_email,
         $referral->to_name,
         sprintf('Reminder: Join %s', get_bloginfo('name')),
         'Just a friendly reminder about my invitation!'
      );
      if (is_wp_error($result)) {
         return $result;
      }
      set_transient($transient_key, time(), WEEK_IN_SECONDS);
      return true;
   }
   public function getAllReferrals(array $args = []): array
   {
      $conditions = ['1=1'];
      $values = [];
      if (!empty($args['status']) && $args['status'] !== 'all') {
         $conditions[] = '{table}.status = %s';
         $values[] = $args['status'];
      }
      if (!empty($args['date_start'])) {
         $conditions[] = 'DATE({table}.created_at) >= %s';
         $values[] = $args['date_start'];
      }
      if (!empty($args['date_end'])) {
         $conditions[] = 'DATE({table}.created_at) <= %s';
         $values[] = $args['date_end'];
      }
      if (!empty($args['search'])) {
         global $wpdb;
         $like = '%' . $wpdb->esc_like($args['search']) . '%';
         $conditions[] = '({table}.to_name LIKE %s OR {table}.to_email LIKE %s OR {table}.referral_code LIKE %s OR u.display_name LIKE %s)';
         array_push($values, $like, $like, $like, $like);
      }
      array_push($values, absint($args['limit'] ?? 50), absint($args['offset'] ?? 0));
      return $this->referrals->queryResults(
         "SELECT {table}.*, u.display_name as from_name
        FROM {table}
        LEFT JOIN {$this->referrals->getUserTable()} u ON {table}.from_user = u.ID
        WHERE " . implode(' AND ', $conditions) . "
        ORDER BY {table}.created_at DESC
        LIMIT %d OFFSET %d",
         $values
      );
   }
}