Jake Vanderwerf
4 days ago 747d741293e064a979d7bf6c143ef969ea6d7629
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;
@@ -20,32 +20,55 @@
 */
class ReferralManager
{
   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'
      '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'
      '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;
   protected array $settings;
   public function __construct()
   {
      global $wpdb;
      $this->wpdb = $wpdb;
      $this->cache = new CacheManager('referrals');
      $this->referrals_table = BASE . 'referrals';
      $this->rewards_table = BASE . 'referral_rewards';
      $this->magic_link = new MagicLinkManager();
      $this->defineTables();
      $this->role = Site::getDefaultReferralRole();
      $this->default_settings['referral_role'] = $this->role;
      // Hook into user registration to track referrals
      $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->referralPage = $this->getReferralPageId();
      $this->settings = $this->getRewardSettings();
      add_action('jvbUserRegistered', [$this, 'processRegistrationToken'], 10, 3);
      add_action('jvb_add_token_inputs', [$this, 'addLoginInputs'], 10, 1);
      add_action('user_register', [$this, 'processReferral'], 10, 1);
      // Add meta boxes for admin to manage referrals
@@ -56,7 +79,7 @@
      add_action('personal_options_update', [$this, 'saveUserReferralCode']);
      add_action('edit_user_profile_update', [$this, 'saveUserReferralCode']);
      add_filter(BASE.'new_user_email_content', [$this, 'addReferralToWelcomeEmail'], 99, 2);
      add_filter('jvbNewUserEmail', [$this, 'addReferralToWelcomeEmail'], 99, 2);
      add_filter('jvbAdditionalActions', [$this, 'outputShareWidget']);
@@ -64,6 +87,235 @@
      add_action('wp_enqueue_scripts', [$this, 'enqueueScripts']);
      // Schedule cron jobs for reports
      $this->registerCronJobs();
      // Add admin bar label for referral page
      add_action('admin_bar_menu', [$this, 'addReferralPageLabel'], 999);
      // Add admin notice to referral page edit screen
      add_action('admin_notices', [$this, 'showReferralPageNotice']);
      add_filter('jvbDashboardPage', [$this, 'renderDashPage'], 10, 2);
      // 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('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'",
            '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)) {
         echo '<input type="hidden" name="referral_code" value="'.$_GET['ref'].'">';
      }
   }
   public function modifyLoginText(array $defaults):array
   {
      if (!array_key_exists('ref', $_GET)){
         return $defaults;
      }
      $code = $_GET['ref'];
      $user = $this->getUserByReferralCode($code);
      $desc = ($user) ? strtok($user->display_name, ' ') . ' invited you ' : 'You\'ve been invited ';
      $defaults['title'] = 'Register your Account. Get Your Reward.';
      $defaults['description'] = [
         $desc.' to see the difference with us.',
         'Oh, and you\'ll get 20% off your first treatment!',
         'Finish this account creation (you\'ll still need to make an account on Jane App to book), or let us know when you come in for your appointment.'
      ];
      $defaults['extra'] = [
         'Once you\'re in, you\'ll get your own code you can share.',
         'Get a $25 credit for <b>each</b> person you send our way who comes in for their first treatment!'
      ];
      return $defaults;
   }
   public function enqueueScripts():void
@@ -73,10 +325,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',
@@ -105,6 +368,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
    *
@@ -112,32 +395,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;
   }
   /**
@@ -170,93 +456,141 @@
    * 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
   {
      // Check for referral code in data
      $code = $data['referral_code'] ?? '';
      if (empty($code)) {
         return;
      }
      // Store in session/cookie for processReferral to pick up
      if (session_status() === PHP_SESSION_NONE) {
         session_start();
      }
      $_SESSION[BASE . 'referral_code'] = sanitize_text_field($code);
      setcookie(
         BASE . 'referral_code',
         sanitize_text_field($code),
         time() + (86400 * 30),
         '/'
      );
   }
   /**
    * Track a new referral when user registers
    *
    * @param int $user_id
    * @param array $userData
    * @return bool;
    */
   public function processReferral(int $user_id): void
   public function processReferral(int $user_id, array $userData): bool
   {
      // Check if user was created via referral magic link
      $referral_code = get_user_meta($user_id, BASE . 'pending_referral_code', true);
      $referral = $this->referrals->get(['to_user' => $user_id]);
      if (!$referral_code) {
         return;
      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']
            ];
         }
      }
      // Find the referrer
      $referrer = $this->getUserByReferralCode($referral_code);
      if (!$referrer) {
         delete_user_meta($user_id, BASE . 'pending_referral_code');
         return;
      if (empty($referral)) {
         return false; // No referral code - regular registration
      }
      // Check for duplicates
      $existing = $this->getReferralByReferee($user_id);
      if ($existing) {
         delete_user_meta($user_id, BASE . 'pending_referral_code');
         return;
      // 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;
      }
      // Create referral record
      $result = $this->createReferral($referrer->ID, $user_id, $referral_code);
      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_code);
      // 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'    => $from_user,
         'to_email'     => $referral['to_email'],
         'to_name'      => $userData['first_name'],
//       'to_phone'     =>
         'status'    => 'pending'
      ]);
      if (!$record) {
         error_log('[ReferralManager]::processReferral Could not update record for user: '.print_r($referral, true));
         return false;
      }
      // 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, '/');
      }
      // Clear caches
      $this->cache->flush();
      // Fire action for tracking
      do_action('jvb_referral_processed', $user_id, $from_user->ID, $referral['referral_code']);
      // 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
    */
   protected 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']
      );
         ]
      ]);
   }
   /**
@@ -267,27 +601,30 @@
    */
   public function getUserByReferralCode(string $code): ?WP_User
   {
      $users = get_users([
         'meta_key' => BASE . 'referral_code',
         'meta_value' => $code,
         'number' => 1
      ]);
      return $users[0] ?? null;
      return $this->cache->remember(
         $code,
         function () use ($code) {
            $users = get_users([
               'meta_key' => BASE . 'referral_code',
               'meta_value' => $code,
               'number' => 1
            ]);
            return $users[0] ?? null;
         }
      );
   }
   /**
    * 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;
   }
@@ -303,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) {
@@ -323,55 +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;
      }
      $settings = $this->getRewardSettings();
      // 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 referrer reward
      $this->wpdb->insert(
         $this->rewards_table,
         [
            'referral_id' => $referral_id,
            'user_id' => $referral->referrer_id,
            'reward_type' => 'referrer',
            'amount' => $settings['referrer_reward_amount'],
            'status' => 'available',
            'created_at' => current_time('mysql')
         ],
         ['%d', '%d', '%s', '%f', '%s', '%s']
      );
      // Create referee reward
      $referee_amount = $settings['referee_reward_type'] === 'percentage'
         ? $settings['referee_reward_amount']  // Store as percentage
         : $settings['referee_reward_amount'];  // Store as fixed amount
      // 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
      $this->wpdb->insert(
         $this->rewards_table,
         [
            'referral_id' => $referral_id,
            'user_id' => $referral->referee_id,
            'reward_type' => 'referee',
            'amount' => $referee_amount,
            'reward_calculation' => $settings['referee_reward_type'],
            'status' => 'available',
            'created_at' => current_time('mysql')
         ],
         ['%d', '%d', '%s', '%f', '%s', '%s', '%s']
      );
      $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']
      ]);
   }
   /**
@@ -387,24 +709,44 @@
         'status' => 'all',
         'limit' => 100,
         'offset' => 0,
         'orderby' => 'referred_at',
         'orderby' => 'created_at',
         'order' => 'DESC'
      ];
      $args = wp_parse_args($args, $defaults);
      $where = $this->wpdb->prepare("WHERE referrer_id = %d", $user_id);
      if ($args['status'] !== 'all') {
         $where .= $this->wpdb->prepare(" AND status = %s", $args['status']);
      $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);
      $query = "SELECT * FROM {$this->referrals_table}
                  {$where}
                  ORDER BY {$args['orderby']} {$args['order']}
                  LIMIT {$args['limit']} OFFSET {$args['offset']}";
      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);
      return $this->wpdb->get_results($query);
   }
   /**
@@ -415,42 +757,36 @@
    */
   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->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 {table}
                WHERE from_user = %d",
               [$user_id],
               ARRAY_A
            );
            $stats = $stats[0] ?? [];
      if ($cached !== false) {
         return $cached;
      }
            $rewards = $this->rewards->queryVar(
               "SELECT SUM(amount) FROM {table} WHERE user_id = %d AND reward_type = 'from_user'",
               [$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;
         }
      );
   }
   /**
    * 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'
@@ -458,39 +794,41 @@
    */
   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_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_clause}";
            }
      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}";
      }
      $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);
            foreach ($results as &$result) {
               $user = get_user_by('ID', $result->from_user);
               $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;
         }
      );
   }
   /**
@@ -500,83 +838,108 @@
   {
      $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
        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]
      );
      // 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->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->from_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');
   }
   /**
    * 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) {
         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_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>';
         $cardContent = sprintf(
            '<p>%s <strong>%s</strong></p>',
            $rankBadge,
            esc_html($from_user->user_name)
         );
         $stats = [
            JVB()->email()->stat($from_user->referral_count, 'Total Referrals'),
            JVB()->email()->stat($from_user->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');
   }
   /**
@@ -587,79 +950,40 @@
    */
   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->from_name ?? 'Unknown',
                  $referral->to_name,
                  $referral->to_email,
                  $referral->to_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');
   }
   /**
    * 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>',
@@ -667,31 +991,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>';
      $from_users = [];
      $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>';
      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',
               $from_user->referral_count,
               $from_user->treated_count
            )
         ];
      }
      $content .= '</tbody></table>';
      $content .= JVB()->email()->table($from_users, 'Top 10 Referrers This Week');
      return jvbGetEmailTemplate($content, 'Weekly Referral Summary');
      return $content;
   }
   /**
@@ -780,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>
@@ -798,6 +1113,7 @@
      </table>
   <?php endif; ?>
      <?php /**
      <script>
         function markReferralTreated(referralId) {
            if (!confirm('Mark this referral as treated? This will create reward records.')) {
@@ -822,6 +1138,7 @@
         }
      </script>
      <?php
    */
   }
   /**
@@ -878,7 +1195,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 {
@@ -887,7 +1204,7 @@
      $content .= '</aside>';
      $actions[] =[
         'button' => '<button type="button" class="toggle-referral row" title="Your Referrals" data-action="toggle-referral" aria-label="Open Referral Sidebar" aria-controls="referral" aria-expanded="false">
         'button' => '<button type="button" class="attn toggle-referral row" title="Your Referrals" data-action="toggle-referral" aria-label="Open Referral Sidebar" aria-controls="referral" aria-expanded="false">
               '.jvbIcon('hand-heart').'<span class="screen-reader-text"></span>
            </button>',
         'content'   => $content
@@ -896,96 +1213,152 @@
      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'] ?? '';
      $from_user_name = '';
      if ($prefill_code) {
         $from_user = $this->getUserByReferralCode($prefill_code);
         $from_user_name = $from_user ? strtok($from_user->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),
         ($from_user_name ? '<p>' . esc_html($from_user_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-from-user' => $from_user_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);
      $from_user = $this->getUserByReferralCode($code);
      if (!$referrer) {
      if (!$from_user) {
         return '';
      }
      $settings = $this->getRewardSettings();
      $reward_amount = $settings['referee_reward_amount'] ?? 20;
      $reward_type = $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'
@@ -1014,94 +1387,89 @@
         </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
      return ob_get_clean();
   }
   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 = home_url('/?ref=' . $referral_code);
      $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', ['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', ['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 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'); ?>
               <?= 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 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>
         <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 x-btw">
            <span class="stat-label">Total Referrals</span>
            <span class="stat-value" data-stat="total">-</span>
         </div>
         <div class="row x-btw">
            <span class="stat-label">Successful</span>
            <span class="stat-value" data-stat="treated">-</span>
         </div>
         <div class="row x-btw">
            <span class="stat-label">Pending</span>
            <span class="stat-value" data-stat="pending">-</span>
         </div>
         <div class="row x-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();
   }
@@ -1114,27 +1482,51 @@
         return $content;
      }
      $settings = get_option(BASE . 'referral_settings', []);
      $reward_amount = $settings['referee_reward_amount'] ?? 20;
      $reward_type = $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'
         : '$' . number_format($reward_amount, 2) . ' off';
      $bonus_content = '<div style="background: #e7f5ff; padding: 20px; border-radius: 8px; margin: 20px 0;">';
      $bonus_content .= '<h3 style="margin-top: 0; color: #2271b1;">🎉 Welcome Bonus!</h3>';
      $bonus_content .= '<p>Since you were referred by a friend, you\'ve earned <strong>' . $reward_text . '</strong> your first booking!</p>';
      $bonus_content .= '<p>Your reward will be automatically applied when you book.</p>';
      $bonus_content .= '</div>';
      $bonus_content = sprintf(
         '
         <h3>Thanks for the â™¡</h3>
         <p>Since you were referred by a friend, you\'ve earned <strong>%s</strong> your first booking!</p>
         <p>Your reward will be automatically applied when you book.</p>',
         $reward_text,
      );
      // Insert bonus content after the first paragraph
      $parts = explode('</p>', $content, 2);
      if (count($parts) === 2) {
         return $parts[0] . '</p>' . $bonus_content . $parts[1];
      $code = $this->getUserReferralCode($user->ID);
      $yourCode = '';
      if ($code) {
         $share_url = $this->getShareURL($code);
         $yourCode = sprintf(
            '<div class="callout">
         <h3>Share the â™¡ with Friends</h3>
         <p>If you find you love what we can do for you, you can share your own code!</p>
         <p>Your Referral Code: <strong>%s</strong></p>
         <p>Or click the button below:</p>
         %s
         </div>',
            JVB()->email()->link($code),
            JVB()->email()->button($share_url, 'Share Your Code')
         );
      }
      return $content . $bonus_content;
      return $content . $bonus_content . $yourCode;
   }
   public function getShareURL(string $code):string
   {
      return add_query_arg(
         [
            'ref' => $code
         ],
         get_home_url()
      );
   }
   /**
@@ -1143,15 +1535,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) {
@@ -1164,47 +1553,69 @@
         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');
      }
      // 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;
      }
      // Get reward text for email
      $settings = $this->getRewardSettings();
      $reward_text = $settings['referee_reward_type'] === 'percentage'
         ? "Get {$settings['referee_reward_amount']}% off your first treatment!"
         : "Get \${$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['to_user_reward_type'] === 'percentage'
         ? "{$this->settings['to_user_reward_amount']}% off"
         : "\${$this->settings['to_user_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($from_user->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 [
@@ -1222,7 +1633,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' => [],
@@ -1242,7 +1653,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'][] = [
@@ -1263,7 +1674,7 @@
      return [
         'success' => !empty($results['success']),
         'results' => $results,
         'result' => $results,
         'summary' => sprintf(
            'Sent %d invitations, %d failed',
            count($results['success']),
@@ -1308,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);
   }
@@ -1383,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 = [];
@@ -1424,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
         ];
      }
@@ -1447,5 +1854,1300 @@
      return $csv_content;
   }
   /**
    * Add referral settings subpage to admin menu
    * Add referral settings subpage to admin menu
    *
    * @param array $subpages
    * @return array
    */
   public static function addSubpage():void
   {
      $subpage = [
         'page_title' => 'Referral System',
         'menu_title' => 'Referrals',
         'capability' => 'manage_options',
         'menu_slug' => BASE . 'referral-admin',
         'callback' => [self::class, 'renderAdminPageStatic']
      ];
      AdminPages::addSubPage(BASE.'referral-admin', $subpage);
   }
   /**
    * 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();
   }
   /**
    * Register settings
    */
   public function registerSettings(): void
   {
      register_setting(
         BASE . 'referral_settings',
         BASE . 'referral_page_id',
         [
            'type' => 'integer',
            'sanitize_callback' => 'absint',
            'default' => 0
         ]
      );
      register_setting(
         BASE . 'referral_settings',
         BASE . 'referral_reward_settings',
         [
            'type' => 'array',
            'sanitize_callback' => [$this, 'sanitizeRewardSettings'],
            'default' => $this->default_settings
         ]
      );
   }
   /**
    * Sanitize reward settings
    */
   public function sanitizeRewardSettings(array $settings): array
   {
      return [
         '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',
         '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',
         'to_user_reward_type' => in_array($settings['to_user_reward_type'] ?? '', ['percentage', 'fixed'])
            ? $settings['to_user_reward_type']
            : 'percentage',
         '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',
      ];
   }
   /**
    * Render the admin settings page
    */
   public function renderAdminPage(): void
   {
      ?>
      <div class="wrap jvb-admin-wrap">
         <h1>Referral System Management</h1>
         <!-- 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>
            <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>
               <!-- 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.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.from_user_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
      }
   }
   protected function renderAdminHTML():string
   {
      ob_start();
      ?>
      <div class="wrap">
         <h1>Referral Settings</h1>
         <form method="post" action="">
            <?php wp_nonce_field(BASE . 'admin_page_nonce'); ?>
            <div class="card">
               <h2>Referral Page</h2>
               <p>Select the page where users can access their referral dashboard.</p>
               <table class="form-table">
                  <tr>
                     <th scope="row">
                        <label for="<?= BASE ?>referral_page_id">Referral Page</label>
                     </th>
                     <td>
                        <?php
                        if (!$this->referralPage) {
                           $this->referralPage = $this->getReferralPageId();
                        }
                        wp_dropdown_pages([
                           'name' => BASE . 'referral_page_id',
                           'id' => BASE . 'referral_page_id',
                           'selected' => $this->referralPage,
                           'show_option_none' => __('— Select â€”', 'jvbase'),
                           'option_none_value' => '0'
                        ]);
                        ?>
                        <p class="description">
                           This page will show "Referral Page" in the admin bar when editing.
                        </p>
                     </td>
                  </tr>
               </table>
            </div>
            <div class="card">
               <h2>Reward Settings</h2>
               <table class="form-table">
                  <tr>
                     <th colspan="2"><h3>Referrer Rewards</h3></th>
                  </tr>
                  <tr>
                     <th scope="row">
                        <label for="from_user_reward_type">Reward Type</label>
                     </th>
                     <td>
                        <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="from_user_reward_amount">Reward Amount</label>
                     </th>
                     <td>
                        <input type="number"
                              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>
                     </td>
                  </tr>
                  <tr>
                     <th scope="row">
                        <label for="from_user_reward_applies_to">Applies To</label>
                     </th>
                     <td>
                        <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>
                  <tr>
                     <th colspan="2"><h3>Referee (New User) Rewards</h3></th>
                  </tr>
                  <tr>
                     <th scope="row">
                        <label for="to_user_reward_type">Reward Type</label>
                     </th>
                     <td>
                        <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="to_user_reward_amount">Reward Amount</label>
                     </th>
                     <td>
                        <input type="number"
                              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>
                     </td>
                  </tr>
                  <tr>
                     <th scope="row">
                        <label for="to_user_reward_applies_to">Applies To</label>
                     </th>
                     <td>
                        <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>
                  <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>
            <p class="submit">
               <button type="submit" name="submit" class="button button-primary">Save Settings</button>
            </p>
         </form>
         <?= $this->renderReferralStats(true) ?>
      </div>
      <?php
      return ob_get_clean();
   }
   /**
    * Render referral statistics
    */
   protected function renderReferralStats(bool $wrapCard = false): string
   {
      global $wpdb; // Use fresh global instead of stored reference
      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 ?? 0) ?></td>
         </tr>
         <tr>
            <th>Pending</th>
            <td><?= esc_html($pending_referrals ?? 0) ?></td>
         </tr>
         <tr>
            <th>Treated</th>
            <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>';
      }
      return $table;
   }
   /**
    * Add "Referral Page" label to admin bar
    *
    * @param WP_Admin_Bar $wp_admin_bar
    */
   public function addReferralPageLabel($wp_admin_bar): void
   {
      if (!is_admin()) {
         return;
      }
      if (!$this->referralPage) {
         $this->referralPage = $this->getReferralPageId();
      }
      if (!$this->referralPage) {
         return;
      }
      global $pagenow, $post;
      // Check if we're editing the referral page
      if ('post.php' === $pagenow && $post && $post->ID === $this->referralPage) {
         $wp_admin_bar->add_node([
            'id' => 'referral-page',
            'parent' => 'top-secondary',
            'title' => __('Referral Page', 'jvbase'),
            'meta' => [
               'class' => 'referral-page-notice'
            ]
         ]);
      }
   }
   /**
    * Get the referral page ID
    *
    * @return int|null
    */
   public function getReferralPageId(): ?int
   {
      $page_id = get_option(BASE . 'referral_page_id');
      return $page_id ? (int) $page_id : null;
   }
   /**
    * Show admin notice on referral page edit screen
    */
   public function showReferralPageNotice(): void
   {
      global $pagenow, $post;
      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>';
         echo '</div>';
      }
   }
   public function renderDashPage(string $content, string $page): string
   {
      if ($page !== 'Referrals') {
         return $content;
      }
      // 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);
      }
      $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 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'); ?>
               <?= jvbIcon('check-circle'); ?>
            </button>
         </div>
         <h3>Share Code</h3>
         <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>
      </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([
            'to_name' => [
               'label' => 'Name',
               'type' => 'text',
            ],
            'to_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 = [
            '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);
         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 to_user
    *
    * @param bool $full Include "off your first treatment" text
    * @return string
    */
   public function getRewardText(bool $full = true): string
   {
      $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'
         : '$' . 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 from_user when someone registers
    *
    * @param int $from_user_id
    * @param string $to_name
    */
   protected function sendReferrerNotification(int $from_user_id, string $to_name): void
   {
      $from_user = get_userdata($from_user_id);
      if (!$from_user) {
         return;
      }
      $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",
         $to_name,
         home_url('/dash/referrals')
      );
      JVB()->email()->sendEmail(
         $from_user->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 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"
      $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($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>
                  <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();
   }
   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
      );
   }
}