From 46d681c6b825d21b3f698d793c4e630c687d90ad Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Thu, 21 May 2026 21:41:53 +0000
Subject: [PATCH] =Major CustomBlocks.php overhaul, expanding block support and customization from the editor. theme.json should now be updated on new themes to set brand colours, etc. Also note: major change to .col vs .row alignment: simplifying it to .top .bottom vs the confusion of the differences for .col/.row .start and .a-start

---
 inc/managers/ReferralManager.php | 1411 +++++++++++++++++++++++++++++++++++++++-------------------
 1 files changed, 951 insertions(+), 460 deletions(-)

diff --git a/inc/managers/ReferralManager.php b/inc/managers/ReferralManager.php
index 3f000e4..c521c15 100644
--- a/inc/managers/ReferralManager.php
+++ b/inc/managers/ReferralManager.php
@@ -1,10 +1,10 @@
 <?php
 namespace JVBase\managers;
 
-use JVBase\managers\MagicLinkManager;
-use JVBase\integrations\Cloudflare;
-use JVBase\meta\MetaForm;
-use JVBase\utility\Features;
+use JVBase\meta\Form;
+use JVBase\ui\CRUDSkeleton;
+use JVBase\ui\Tabs;
+use JVBase\base\Site;
 use WP_User;
 use WP_Error;
 
@@ -22,11 +22,19 @@
 {
 	protected $wpdb;
 	protected MagicLinkManager $magic_link;
-	protected CacheManager $cache;
+	protected Cache $cache;
+	protected Cache $requestCache;
+	protected Cache $statsCache;
 	protected string $referrals_table;
 	protected ?int $referralPage = null;
 	protected string $rewards_table;
 
+	protected CustomTable $referrals;
+	protected CustomTable $codes;
+	protected CustomTable $janeClients;
+	protected CustomTable $rewards;
+	protected CustomTable $treatments;
+
 	// Default reward settings
 	protected array $default_settings = [
 		'referrer_reward_applies_to' => 'per_user',  // 'per_user' or 'flat_total'
@@ -34,19 +42,31 @@
 		'referrer_reward_type'	=> 'fixed',
 		'referee_reward_type' => 'percentage',  // 'percentage' or 'fixed'
 		'referee_reward_amount' => 20,  // 20% or $20
-		'referee_reward_applies_to' => 'first_order'  // 'first_order' or 'all_orders'
+		'referee_reward_applies_to' => 'first_order',  // 'first_order' or 'all_orders'
 	];
 
+	protected string $role;
+
 	protected array $settings;
 
 	public function __construct()
 	{
+		$this->defineTables();
+		$this->role = Site::getDefaultReferralRole();
+		$this->default_settings['referral_role'] = $this->role;
 		global $wpdb;
 		$this->wpdb = $wpdb;
-		$this->cache = CacheManager::for('referrals', WEEK_IN_SECONDS);
+		$this->cache = Cache::for('referrals', WEEK_IN_SECONDS);
+		$this->requestCache = Cache::for('referral_requests', WEEK_IN_SECONDS)->connect('referrals', true);
+		$this->statsCache = Cache::for('referral_stats', WEEK_IN_SECONDS)->connect('referrals', true);
+		if (JVB_TESTING) {
+			$this->cache->flush();
+			$this->requestCache->flush();
+			$this->statsCache->flush();
+		}
+
 		$this->referrals_table = $wpdb->prefix . BASE . 'referrals';
 		$this->rewards_table = $wpdb->prefix . BASE . 'referral_rewards';
-		$this->magic_link = new MagicLinkManager();
 
 		$this->referralPage = $this->getReferralPageId();
 		$this->settings = $this->getRewardSettings();
@@ -54,7 +74,7 @@
 
 		add_action('jvbUserRegistered', [$this, 'processRegistrationToken'], 10, 3);
 		add_action('jvb_add_token_inputs', [$this, 'addLoginInputs'], 10, 1);
-		add_action('jvbUserRegistered', [$this, 'processReferral'], 10, 1);
+		add_action('user_register', [$this, 'processReferral'], 10, 1);
 
 		// Add meta boxes for admin to manage referrals
 		add_action('show_user_profile', [$this, 'displayUserReferralInfo']);
@@ -88,6 +108,190 @@
 		add_filter('jvb_admin_page_submission', [$this, 'handleAdminSubmission'], 10, 3);
 	}
 
+	protected function defineTables():void
+	{
+		$this->defineReferralsTable();
+		$this->defineCodeTable();
+		$this->defineJaneClientsTable();
+		$this->defineRewardsTable();
+		$this->defineTreatmentsTable();
+	}
+		protected function defineReferralsTable():void
+		{
+			$table = CustomTable::for('referrals');
+
+			$table->setColumns([
+				'id'		=> 'bigint(20) unsigned NOT NULL AUTO_INCREMENT',
+				'from_user'	=> "{$table->getUserIDType()} NOT NULL",
+				'to_user'	=> "{$table->getUserIDType()} NOT NULL",
+				'to_name'	=> 'varchar(255) NOT NULL',
+				'to_email'	=> 'varchar(255) NOT NULL',
+				'to_phone'	=> 'varchar(50) NOT NULL',
+				'referral_code'=> 'varchar(50) NOT NULL',
+				'status'	=> "ENUM('pending', 'consulted', 'treated', 'cancelled') DEFAULT 'pending'",
+				'created_at'=> 'datetime DEFAULT CURRENT_TIMESTAMP',
+				'consulted_at'=> 'datetime DEFAULT NULL',
+				'treated_at'=> 'datetime DEFAULT NULL',
+				'treatment_count' => 'int DEFAULT 0',
+				'notes'		=> 'text DEFAULT NULL',
+			]);
+
+			$table->setKeys([
+				['key' => 'PRIMARY', 'value' => 'id'],
+				['key' => 'UNIQUE', 'value' => '(`to_user`)'],
+				'from_user (`from_user`)',
+				'status (`status`)',
+				'code (`referral_code`)',
+				'date (`created_at`)',
+				'consult (`consulted_at`)'
+			]);
+
+			$base = BASE;
+			$table->setConstraints([
+				"CONSTRAINT `{$base}referral_from_user_fk` FOREIGN KEY (`from_user`)
+				REFERENCES `{$table->getUserTable()}` (`ID`) ON DELETE CASCADE",
+				"CONSTRAINT `{$base}referral_to_user_fk` FOREIGN KEY (`to_user`)
+				REFERENCES `{$table->getUserTable()}` (`ID`) ON DELETE CASCADE"
+			]);
+			$table->defineTable();
+			$this->referrals = $table;
+		}
+
+		protected function defineCodeTable():void
+		{
+			$table = CustomTable::for('referrals_codes');
+
+			$table->setColumns([
+				'id'		=> 'bigint(20) unsigned NOT NULL AUTO_INCREMENT',
+				'user_id'	=> "{$table->getUserIDType()} NOT NULL",
+				'code'		=> 'varchar(50) NOT NULL',
+				'created_at'=> 'datetime DEFAULT CURRENT_TIMESTAMP',
+			]);
+
+			$table->setKeys([
+				['key' => 'PRIMARY', 'value' => '(`id`)'],
+				['key' => 'UNIQUE', 'value' => '(`code`)'],
+				'user (`user_id`)',
+			]);
+
+			$base = BASE;
+			$table->setConstraints([
+				"CONSTRAINT `{$base}referral_code_user_fk` FOREIGN KEY (`user_id`)
+					REFERENCES `{$table->getUserTable()}` (`ID`) ON DELETE CASCADE",
+			]);
+			$table->defineTable();
+			$this->codes = $table;
+		}
+		protected function defineJaneClientsTable():void
+		{
+			$table = CustomTable::for('referrals_jane_clients');
+
+			$table->setColumns([
+				'id'			=> 'bigint(20) unsigned NOT NULL AUTO_INCREMENT',
+				'patient_guid'	=> 'varchar(50) NOT NULL',
+				'user_id'		=> "{$table->getUserIDType()} NOT NULL",
+				'first_name'	=> 'varchar(100) NOT NULL',
+				'last_name'		=> 'varchar(100) NOT NULL',
+				'email'			=> 'varchar(255) NOT NULL',
+				'imported_at'	=> 'datetime DEFAULT CURRENT_TIMESTAMP',
+				'updated_at'	=> 'datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',
+			]);
+
+			$table->setKeys([
+				['key' => 'PRIMARY', 'value' => '(`id`)'],
+				['key' => 'UNIQUE', 'value' => '(`patient_guid`)'],
+				'user (`user_id`)',
+				'email (`email`)',
+			]);
+
+			$base = BASE;
+			$table->setConstraints([
+				"CONSTRAINT `{$base}jane_clients_user` FOREIGN KEY (`user_id`)
+				REFERENCES `{$table->getUserTable()}` (`ID`) ON DELETE CASCADE",
+			]);
+			$table->defineTable();
+			$this->janeClients = $table;
+		}
+		protected function defineRewardsTable():void
+		{
+			$table = CustomTable::for('referrals_rewards');
+
+			$table->setColumns([
+				'id'				=> 'bigint(20) unsigned NOT NULL AUTO_INCREMENT',
+				'referral_id'		=> 'bigint(20) unsigned NOT NULL',
+				'user_id'			=> "{$table->getUserIDType()} NOT NULL",
+				'reward_type'		=> "ENUM('referrer', 'referee') NOT NULL",
+				'amount'			=> 'decimal(10,2) NOT NULL',
+				'reward_calculation'=> "ENUM('percentage', 'fixed')",
+				'status'			=> "ENUM('available', 'redeemed', 'expired', 'cancelled') DEFAULT 'available'",
+				'created_at'		=> 'datetime NOT NULL DEFAULT CURRENT_TIMESTAMP',
+				'updated_at'		=> 'datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',
+				'redeemed_at'		=> 'datetime DEFAULT NULL',
+				'expires_at'		=> 'datetime DEFAULT NULL',
+				'notes'				=> 'text DEFAULT NULL',
+			]);
+
+			$table->setKeys([
+				['key' => 'PRIMARY', 'value' => '(`id`)'],
+				'referral (`referral_id`)',
+				'user (`user_id`)',
+				'status (`status`)',
+				'type (`reward_type`)'
+			]);
+
+			$base = BASE;
+			$table->setConstraints([
+				"CONSTRAINT `{$base}reward_referral` FOREIGN KEY (`referral_id`)
+				REFERENCES `{$this->referrals->getFullTableName()}` (`id`) ON DELETE CASCADE",
+				"CONSTRAINT `{$base}reward_user` FOREIGN KEY (`user_id`)
+				REFERENCES `{$table->getUserTable()}` (`ID`) ON DELETE CASCADE"
+			]);
+			$table->defineTable();
+			$this->rewards = $table;
+		}
+		protected function defineTreatmentsTable():void
+		{
+			$table = CustomTable::for('referral_treatments');
+
+			$table->setColumns([
+				'id'			=> 'bigint(20) unsigned NOT NULL AUTO_INCREMENT',
+				'referral_id'	=> 'bigint(20) unsigned NOT NULL',
+				'user_id'		=> "{$table->getUserIDType()} NOT NULL",
+				'treatment_type'=> 'varchar(100) NOT NULL',		//Tier 1-6, Brows, etc
+				'treatment_date'=> 'datetime NOT NULL',
+				'invoice_number'=> 'varchar(50) DEFAULT NULL',
+				'amount'		=> 'decimal(10,2) DEFAULT NULL',
+				'status'		=> "ENUM('completed', 'no_show', 'cancelled') DEFAULT 'completed'",
+				'imported_at'	=> 'datetime DEFAULT CURRENT_TIMESTAMP',
+			]);
+
+			$table->setKeys([
+				['key' => 'PRIMARY', 'value' => '(`id`)'],
+				'referral (`referral_id`)',
+				'user (`user_id`)',
+				'date (`treatment_date`)',
+				'type (`treatment_type`)',
+			]);
+
+			$base = BASE;
+			$table->setConstraints([
+				"CONSTRAINT `{$base}treatment_referral` FOREIGN KEY (`referral_id`)
+				REFERENCES `{$this->referrals->getFullTableName()}` (`id`) ON DELETE CASCADE",
+				"CONSTRAINT `{$base}treatment_user` FOREIGN KEY (`user_id`)
+				REFERENCES `{$table->getUserTable()}` (`ID`) ON DELETE CASCADE"
+			]);
+			$table->defineTable();
+			$this->treatments = $table;
+		}
+
+	public function getSettings():array
+	{
+		return $this->settings;
+	}
+	public function getRole():string
+	{
+		return $this->role;
+	}
 	public function addLoginInputs(string $action):void
 	{
 		if (array_key_exists('ref', $_GET)) {
@@ -126,10 +330,21 @@
 			'jvb-a11y',
 			'jvb-popup',
 			'jvb-tabs',
+			'jvb-data-store',
 		];
 
-		if (Features::hasIntegration('cloudflare') && JVB()->connect('cloudflare')->isSetUp()) {
-			$requirements[] = 'cloudflare-turnstile';
+		if (Site::hasIntegration('cloudflare') && JVB()->connect('cloudflare')->isSetUp()) {
+			JVB()->connect('cloudflare')->enqueueTurnstileScripts();
+		}
+		if (is_singular(BASE.'dash')) {
+			$requirements[] = 'jvb-form';
+			$requirements[] = 'jvb-view';
+
+			wp_enqueue_script('jvb-referral-admin',
+			JVB_URL.'assets/js/min/referralAdmin.min.js',
+			['jvb-referral'],
+			'1.0.0',
+			true);
 		}
 		wp_enqueue_script(
 			'jvb-referral',
@@ -158,6 +373,26 @@
 		}
 	}
 
+	public function createCode(int $user_id, string $code):string|false
+	{
+		$code = sanitize_title($code);
+		$existing = $this->codes->get(['code' => $code]);
+		if ($existing) {
+			if ($existing['user_id'] !== $user_id) {
+				return false;
+			}
+			return $code;
+		}
+		$success = $this->codes->insert([
+			'user_id' 	=> $user_id,
+			'code'		=> $code
+		]);
+		if ($success) {
+			return $code;
+		}
+		return false;
+	}
+
 	/**
 	 * Generate or get existing referral code for a user
 	 *
@@ -165,32 +400,35 @@
 	 * @param string|null $custom_code Optional custom code
 	 * @return string|WP_Error
 	 */
-	public function getUserReferralCode(int $user_id, ?string $custom_code = null)
+	public function getUserReferralCode(int $user_id, ?string $custom_code = null):string|false
 	{
 		$user = get_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;
 	}
 
 	/**
@@ -223,24 +461,11 @@
 	 * Check if a referral code is already taken
 	 *
 	 * @param string $code
-	 * @param int|null $exclude_user_id
 	 * @return bool
 	 */
-	protected function isCodeTaken(string $code, ?int $exclude_user_id = null): bool
+	protected function isCodeTaken(string $code): bool
 	{
-		$args = [
-			'meta_key' => BASE . 'referral_code',
-			'meta_value' => $code,
-			'fields' => 'ID',
-			'number' => 1
-		];
-
-		if ($exclude_user_id) {
-			$args['exclude'] = [$exclude_user_id];
-		}
-
-		$users = get_users($args);
-		return !empty($users);
+		return (bool) $this->codes->get(['code' => $code]);
 	}
 
 	public function processRegistrationToken(int $user_id, string $email, array $data): void
@@ -267,51 +492,77 @@
 	 * Track a new referral when user registers
 	 *
 	 * @param int $user_id
+	 * @param array $userData
+	 * @return bool;
 	 */
-	public function processReferral(int $user_id, string $email, array $data): void
+	public function processReferral(int $user_id, array $userData): bool
 	{
-		// Check if user was created via referral magic link
-		// Try to get code from multiple sources
-    	$referral_code = $data['referral_code'] ??
-		get_user_meta($user_id, BASE . 'pending_referral_code', true);
+		$referral = $this->referrals->get(['to_user' => $user_id]);
 
-		// Check session/cookie if not in data
-		if (empty($referral_code)) {
+		if (empty($referral)) {
+			$referral = $this->referrals->get(['to_email' => $userData['email']]);
+		}
+		if (empty($referral)) {
+			// Check session/cookie if not in meta
 			if (session_status() === PHP_SESSION_NONE) {
 				session_start();
 			}
 			$referral_code = $_SESSION[BASE . 'referral_code'] ?? $_COOKIE[BASE . 'referral_code'] ?? '';
+			if (!empty ($referral_code)) {
+				$referral = [
+					'to_user'	=> $user_id,
+					'referral_code'	=> $referral_code,
+					'to_email'		=> $userData['user_email']
+				];
+			}
 		}
 
-		if (empty($referral_code)) {
-			return;
+		if (empty($referral)) {
+			return false; // No referral code - regular registration
 		}
 
 		// Find the referrer
-		$referrer = $this->getUserByReferralCode($referral_code);
+		$referrer = $this->codes->pluck('user_id', ['code' => $referral['referral_code']]);
+		if (empty($referrer)) {
+			//This should not happen, but whatever
+			return false;
+		}
+		$referrer = $referrer[0];
+		$record = $this->referrals->findOrCreate([
+			'to_user'	=> $user_id,
+			'referral_code'	=> $referral['referral_code'],
+		], [
+			'from_user'		=> $referrer,
+			'to_email'		=> $referral['to_email'],
+			'to_name'		=> $userData['first_name'],
+//			'to_phone'		=>
+			'status'		=> 'pending'
+		]);
 
-		if (!$referrer) {
-			delete_user_meta($user_id, BASE . 'pending_referral_code');
-			return;
+		if (!$record) {
+			error_log('[ReferralManager]::processReferral Could not update record for user: '.print_r($referral, true));
+			return false;
 		}
 
-		// Check for duplicates
-		$existing = $this->getReferralByReferee($user_id);
-		if ($existing) {
-			delete_user_meta($user_id, BASE . 'pending_referral_code');
-			return;
+
+		// Clean up temp data
+		delete_user_meta($user_id, BASE . 'pending_referral_code');
+		if (isset($_SESSION[BASE . 'referral_code'])) {
+			unset($_SESSION[BASE . 'referral_code']);
+		}
+		if (isset($_COOKIE[BASE . 'referral_code'])) {
+			setcookie(BASE . 'referral_code', '', time() - 3600, '/');
 		}
 
-		// Create referral record
-		$result = $this->createReferral($referrer->ID, $user_id, $referral_code);
+		// Clear caches
+		$this->cache->flush();
 
-		if ($result) {
-			// Clean up temp meta
-			delete_user_meta($user_id, BASE . 'pending_referral_code');
+		// Fire action for tracking
+		do_action('jvb_referral_processed', $user_id, $referrer->ID, $referral['referral_code']);
 
-			// Fire action for tracking
-			do_action('jvb_referral_processed', $user_id, $referrer->ID, $referral_code);
-		}
+		// Send notification to referrer
+		$this->sendReferrerNotification($referrer->ID, $userData['display_name']);
+		return true;
 	}
 
 	/**
@@ -478,18 +729,42 @@
 
 		$args = wp_parse_args($args, $defaults);
 
-		$where = $this->wpdb->prepare("WHERE referrer_id = %d", $user_id);
+		return $this->requestCache->remember(
+			$this->requestCache->generateKey(array_merge(['user'=>$user_id], $args)),
+			function() use ($user_id, $args) {
+				$where = $this->wpdb->prepare("WHERE referrer_id = %d", $user_id);
 
-		if ($args['status'] !== 'all') {
-			$where .= $this->wpdb->prepare(" AND status = %s", $args['status']);
-		}
+				if ($args['status'] !== 'all') {
+					$where .= $this->wpdb->prepare(" AND status = %s", $args['status']);
+				}
 
-		$query = "SELECT * FROM {$this->referrals_table}
+				$query = "SELECT * FROM {$this->referrals_table}
                   {$where}
                   ORDER BY {$args['orderby']} {$args['order']}
                   LIMIT {$args['limit']} OFFSET {$args['offset']}";
 
-		return $this->wpdb->get_results($query);
+				$results =  $this->wpdb->get_results($query);
+
+				return array_map(function($referral) {
+					$last_invite = get_transient('referral_last_invite_' . md5($referral->referee_email));
+					$can_resend = !$last_invite || (time() - $last_invite) > WEEK_IN_SECONDS;
+					$status = match($referral->status) {
+						'consulted' => 'Awaiting Treatment',
+						'treated'	=> 'Rewarded!',
+						default => 'Pending',
+					};
+					return [
+						'id'			=> $referral->id,
+						'referee_name'	=> $referral->referee_name,
+						'referee_email'	=> $referral->referee_email,
+						'referred_at'	=> JVB()->routes('referral')->formatTimestamp($referral->referred_at),
+						'referral_status'=> $status,
+						'can_resend'	=> $can_resend
+					];
+				}, $results);
+			}
+		);
+
 	}
 
 	/**
@@ -500,38 +775,33 @@
 	 */
 	public function getUserStats(int $user_id): array
 	{
-		$cache_key = 'stats_' . $user_id;
-		$cached = $this->cache->get($cache_key);
+		return $this->statsCache->remember(
+			$user_id,
+			function() use ($user_id) {
+				$stats = $this->wpdb->get_row($this->wpdb->prepare(
+					"SELECT
+			COUNT(*) as code_used,
+			SUM(CASE WHEN status IN ('consulted', 'treated') THEN 1 ELSE 0 END) as consultations,
+			SUM(CASE WHEN status = 'treated' THEN 1 ELSE 0 END) as treatments,
+			SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending
+		FROM {$this->referrals_table}
+		WHERE referrer_id = %d",
+					$user_id
+				), ARRAY_A);
 
-		if ($cached !== false) {
-			return $cached;
-		}
+				// Get total rewards earned (available + redeemed)
+				$rewards = $this->wpdb->get_var($this->wpdb->prepare(
+					"SELECT SUM(amount)
+		FROM {$this->rewards_table}
+		WHERE user_id = %d AND reward_type = 'referrer'",
+					$user_id
+				));
 
-		$stats = $this->wpdb->get_row($this->wpdb->prepare(
-			"SELECT
-                COUNT(*) as total_referrals,
-                SUM(CASE WHEN status = 'treated' THEN 1 ELSE 0 END) as treated_count,
-                SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending_count
-            FROM {$this->referrals_table}
-            WHERE referrer_id = %d",
-			$user_id
-		), ARRAY_A);
-
-		// Get total rewards
-		$rewards = $this->wpdb->get_row($this->wpdb->prepare(
-			"SELECT
-                SUM(CASE WHEN status = 'available' THEN amount ELSE 0 END) as available_rewards,
-                SUM(CASE WHEN status = 'redeemed' THEN amount ELSE 0 END) as redeemed_rewards
-            FROM {$this->rewards_table}
-            WHERE user_id = %d AND reward_type = 'referrer'",
-			$user_id
-		), ARRAY_A);
-
-		$stats = array_merge($stats, $rewards);
-
-		$this->cache->set($cache_key, $stats, HOUR_IN_SECONDS);
-
-		return $stats;
+				$stats['total_rewards'] = floatval($rewards ?? 0);
+				$stats['user_id'] = $user_id;
+				return $stats;
+			}
+		);
 	}
 
 	/**
@@ -543,20 +813,23 @@
 	 */
 	public function getTopReferrers(int $limit = 10, string $period = 'all'): array
 	{
-		$where = '';
+		return $this->statsCache->remember(
+			$this->statsCache->generateKey(['limit'=>$limit, 'period' => $period]),
+			function() use ($limit, $period) {
+				$where = '';
 
-		if ($period !== 'all') {
-			$date_where = match($period) {
-				'day' => "referred_at >= DATE_SUB(NOW(), INTERVAL 1 DAY)",
-				'week' => "referred_at >= DATE_SUB(NOW(), INTERVAL 1 WEEK)",
-				'month' => "referred_at >= DATE_SUB(NOW(), INTERVAL 1 MONTH)",
-				default => "1=1"
-			};
+				if ($period !== 'all') {
+					$date_where = match($period) {
+						'day' => "referred_at >= DATE_SUB(NOW(), INTERVAL 1 DAY)",
+						'week' => "referred_at >= DATE_SUB(NOW(), INTERVAL 1 WEEK)",
+						'month' => "referred_at >= DATE_SUB(NOW(), INTERVAL 1 MONTH)",
+						default => "1=1"
+					};
 
-			$where = "WHERE {$date_where}";
-		}
+					$where = "WHERE {$date_where}";
+				}
 
-		$query = "SELECT
+				$query = "SELECT
                     referrer_id,
                     COUNT(*) as referral_count,
                     SUM(CASE WHEN status = 'treated' THEN 1 ELSE 0 END) as treated_count
@@ -566,16 +839,19 @@
                   ORDER BY referral_count DESC
                   LIMIT {$limit}";
 
-		$results = $this->wpdb->get_results($query);
+				$results = $this->wpdb->get_results($query);
 
-		// Enrich with user data
-		foreach ($results as &$result) {
-			$user = get_user_by('ID', $result->referrer_id);
-			$result->user_name = $user ? $user->display_name : 'Unknown';
-			$result->user_email = $user ? $user->user_email : '';
-		}
+				// Enrich with user data
+				foreach ($results as &$result) {
+					$user = get_user_by('ID', $result->referrer_id);
+					$result->user_name = $user ? $user->display_name : 'Unknown';
+					$result->user_email = $user ? $user->user_email : '';
+				}
 
-		return $results;
+				return $results;
+			}
+		);
+
 	}
 
 	/**
@@ -585,11 +861,8 @@
 	{
 		$yesterday = date('Y-m-d', strtotime('-1 day'));
 
-		// Get new referrals from yesterday
 		$new_referrals = $this->wpdb->get_results($this->wpdb->prepare(
-			"SELECT
-            r.*,
-            u.display_name as referrer_name
+			"SELECT r.*, u.display_name as referrer_name
         FROM {$this->referrals_table} r
         JOIN {$this->wpdb->users} u ON r.referrer_id = u.ID
         WHERE DATE(r.referred_at) = %s
@@ -597,48 +870,46 @@
 			$yesterday
 		));
 
-		// Only send if there's at least 1 new referral
 		if (empty($new_referrals)) {
 			return;
 		}
 
-		// Build email content
-		$content = '<h2>Daily Referral Report</h2>';
-		$content .= '<p><strong>' . count($new_referrals) . '</strong> new referral' .
-			(count($new_referrals) !== 1 ? 's' : '') . ' yesterday (' . $yesterday . ')</p>';
+		$content = JVB()->email()->h1('Daily Referral Report');
+		$content .= JVB()->email()->stat(
+			count($new_referrals),
+			count($new_referrals) === 1 ? 'New Referral' : 'New Referrals',
+			'From ' . $yesterday
+		);
 
-		$content .= '<table style="width:100%; border-collapse: collapse; margin: 20px 0;">';
-		$content .= '<thead><tr style="background: #f5f5f5;">';
-		$content .= '<th style="padding: 10px; text-align: left; border: 1px solid #ddd;">Referee</th>';
-		$content .= '<th style="padding: 10px; text-align: left; border: 1px solid #ddd;">Email</th>';
-		$content .= '<th style="padding: 10px; text-align: left; border: 1px solid #ddd;">Referrer</th>';
-		$content .= '<th style="padding: 10px; text-align: left; border: 1px solid #ddd;">Code</th>';
-		$content .= '</tr></thead><tbody>';
+		$content .= JVB()->email()->spacer(20);
+		$content .= JVB()->email()->h2('New Referrals');
 
+		// Build list of referrals
 		foreach ($new_referrals as $ref) {
-			$content .= '<tr>';
-			$content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%s</td>',
-				esc_html($ref->referee_name));
-			$content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%s</td>',
-				esc_html($ref->referee_email));
-			$content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%s</td>',
-				esc_html($ref->referrer_name));
-			$content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%s</td>',
-				esc_html($ref->referral_code));
-			$content .= '</tr>';
+			$cardContent = sprintf(
+				'<p><strong>%s</strong> (%s)</p>',
+				esc_html($ref->referee_name),
+				esc_html($ref->referee_email)
+			);
+			$cardContent .= sprintf(
+				'<p style="font-size:13px;color:%s;">Referred by: %s | Code: %s</p>',
+				JVB()->email()->colours['dark-200'],
+				esc_html($ref->referrer_name),
+				JVB()->email()->badge($ref->referral_code, 'info')
+			);
+
+			$content .= JVB()->email()->card($cardContent);
 		}
 
-		$content .= '</tbody></table>';
-
-		// Get admin email
 		$to = get_option('admin_email');
-		$subject = sprintf('[%s] %d New Referral%s',
+		$subject = sprintf(
+			'[%s] %d New Referral%s',
 			get_bloginfo('name'),
 			count($new_referrals),
-			count($new_referrals) !== 1 ? 's' : '');
+			count($new_referrals) !== 1 ? 's' : ''
+		);
 
-
-		jvbMail($to, $subject, $content);
+		JVB()->email()->sendEmail($to, $subject, $content, 'DAILY REPORT');
 	}
 
 	/**
@@ -649,19 +920,50 @@
 		$top_referrers = $this->getTopReferrers(10, 'week');
 		$total_referrals = $this->wpdb->get_var(
 			"SELECT COUNT(*) FROM {$this->referrals_table}
-             WHERE referred_at >= DATE_SUB(NOW(), INTERVAL 1 WEEK)"
+         WHERE referred_at >= DATE_SUB(NOW(), INTERVAL 1 WEEK)"
 		);
 
 		if ($total_referrals == 0) {
 			return;
 		}
 
+		$content = JVB()->email()->h1('Weekly Referral Summary');
+		$content .= JVB()->email()->stat(
+			$total_referrals,
+			'Total Referrals',
+			'This week'
+		);
+
+		$content .= JVB()->email()->spacer(30);
+		$content .= JVB()->email()->h2('Top 10 Referrers');
+
+		// Leaderboard style
+		$rank = 1;
+		foreach ($top_referrers as $referrer) {
+			$rankBadge = $rank <= 3
+				? JVB()->email()->badge('#' . $rank, $rank === 1 ? 'success' : 'info')
+				: '<span style="font-weight:600;color:' . JVB()->email()->colours['dark-200'] . ';">#' . $rank . '</span>';
+
+			$cardContent = sprintf(
+				'<p>%s <strong>%s</strong></p>',
+				$rankBadge,
+				esc_html($referrer->user_name)
+			);
+
+			$stats = [
+				JVB()->email()->stat($referrer->referral_count, 'Total Referrals'),
+				JVB()->email()->stat($referrer->treated_count, 'Treated')
+			];
+			$cardContent .= JVB()->email()->grid($stats, 2);
+
+			$content .= JVB()->email()->card($cardContent);
+			$rank++;
+		}
+
 		$to = get_option('admin_email');
 		$subject = '[' . get_bloginfo('name') . '] Weekly Referral Summary - ' . date('F j, Y');
 
-		$message = $this->generateWeeklyReportEmail($top_referrers, $total_referrals);
-
-		wp_mail($to, $subject, $message, ['Content-Type: text/html; charset=UTF-8']);
+		JVB()->email()->sendEmail($to, $subject, $content, 'WEEKLY SUMMARY');
 	}
 
 	/**
@@ -672,69 +974,30 @@
 	 */
 	protected function generateCSV(array $referrals): string
 	{
-		$csv = "Referred By,Referee Name,Referee Email,Referee Phone,Referral Code,Status,Referred At,Treated At\n";
+		$cache = Cache::for('referralCSV', HOUR_IN_SECONDS)->connect('referrals');
+		return $cache->remember(
+			'csv',
+			function () use ($referrals) {
+				$csv = "Referred By,Referee Name,Referee Email,Referee Phone,Referral Code,Status,Referred At,Treated At\n";
 
-		foreach ($referrals as $referral) {
-			$csv .= sprintf(
-				'"%s","%s","%s","%s","%s","%s","%s","%s"' . "\n",
-				$referral->referrer_name ?? 'Unknown',
-				$referral->referee_name,
-				$referral->referee_email,
-				$referral->referee_phone,
-				$referral->referral_code,
-				$referral->status,
-				$referral->referred_at,
-				$referral->treated_at ?? 'Not yet'
-			);
-		}
+				foreach ($referrals as $referral) {
+					$csv .= sprintf(
+						'"%s","%s","%s","%s","%s","%s","%s","%s"' . "\n",
+						$referral->referrer_name ?? 'Unknown',
+						$referral->referee_name,
+						$referral->referee_email,
+						$referral->referee_phone,
+						$referral->referral_code,
+						$referral->status,
+						$referral->referred_at,
+						$referral->treated_at ?? 'Not yet'
+					);
+				}
 
-		return $csv;
-	}
-
-	/**
-	 * Generate HTML email for daily report
-	 *
-	 * @param array $referrals
-	 * @param string $period
-	 * @return string
-	 */
-	protected function generateReportEmail(array $referrals, string $period): string
-	{
-		$count = count($referrals);
-
-		$content = sprintf('<p>You have <strong>%d new referral%s</strong> today.</p>',
-			$count,
-			$count !== 1 ? 's' : ''
+				return $csv;
+			}
 		);
 
-		$content .= '<table style="width:100%; border-collapse: collapse; margin: 20px 0;">';
-		$content .= '<thead><tr style="background: #f5f5f5; text-align: left;">';
-		$content .= '<th style="padding: 10px; border: 1px solid #ddd;">Referred By</th>';
-		$content .= '<th style="padding: 10px; border: 1px solid #ddd;">New User</th>';
-		$content .= '<th style="padding: 10px; border: 1px solid #ddd;">Email</th>';
-		$content .= '<th style="padding: 10px; border: 1px solid #ddd;">Status</th>';
-		$content .= '<th style="padding: 10px; border: 1px solid #ddd;">Time</th>';
-		$content .= '</tr></thead><tbody>';
-
-		foreach ($referrals as $referral) {
-			$content .= '<tr>';
-			$content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%s</td>',
-				esc_html($referral->referrer_name ?? 'Unknown'));
-			$content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%s</td>',
-				esc_html($referral->referee_name));
-			$content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%s</td>',
-				esc_html($referral->referee_email));
-			$content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%s</td>',
-				esc_html(ucfirst($referral->status)));
-			$content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%s</td>',
-				esc_html(date('g:i A', strtotime($referral->referred_at))));
-			$content .= '</tr>';
-		}
-
-		$content .= '</tbody></table>';
-		$content .= '<p><small>See attached CSV for full details.</small></p>';
-
-		return jvbGetEmailTemplate($content, 'Daily Referral Report');
 	}
 
 	/**
@@ -752,31 +1015,22 @@
 			$total_referrals !== 1 ? 's' : ''
 		);
 
-		$content .= '<h3>Top 10 Referrers This Week</h3>';
-		$content .= '<table style="width:100%; border-collapse: collapse; margin: 20px 0;">';
-		$content .= '<thead><tr style="background: #f5f5f5; text-align: left;">';
-		$content .= '<th style="padding: 10px; border: 1px solid #ddd;">Rank</th>';
-		$content .= '<th style="padding: 10px; border: 1px solid #ddd;">User</th>';
-		$content .= '<th style="padding: 10px; border: 1px solid #ddd;">Total Referrals</th>';
-		$content .= '<th style="padding: 10px; border: 1px solid #ddd;">Treated</th>';
-		$content .= '</tr></thead><tbody>';
-
+		$referrers = [];
 		$rank = 1;
 		foreach ($top_referrers as $referrer) {
-			$content .= '<tr>';
-			$content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%d</td>', $rank++);
-			$content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%s</td>',
-				esc_html($referrer->user_name));
-			$content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%d</td>',
-				$referrer->referral_count);
-			$content .= sprintf('<td style="padding: 10px; border: 1px solid #ddd;">%d</td>',
-				$referrer->treated_count);
-			$content .= '</tr>';
+			$referrers[] = [
+				'label' => '#' . $rank++ . ' - ' . esc_html($referrer->user_name),
+				'value' => sprintf(
+					'<strong>Total Referrals:</strong> %d | <strong>Treated:</strong> %d',
+					$referrer->referral_count,
+					$referrer->treated_count
+				)
+			];
 		}
 
-		$content .= '</tbody></table>';
+		$content .= JVB()->email()->table($referrers, 'Top 10 Referrers This Week');
 
-		return jvbGetEmailTemplate($content, 'Weekly Referral Summary');
+		return $content;
 	}
 
 	/**
@@ -883,6 +1137,7 @@
 		</table>
 	<?php endif; ?>
 
+		<?php /**
 		<script>
 			function markReferralTreated(referralId) {
 				if (!confirm('Mark this referral as treated? This will create reward records.')) {
@@ -907,6 +1162,7 @@
 			}
 		</script>
 		<?php
+	 */
 	}
 
 	/**
@@ -963,7 +1219,7 @@
 	{
 
 		$user_id = get_current_user_id();
-		$content = '<aside class="jvb-referral right">';
+		$content = '<aside class="main referral right">';
 		if (!$user_id) {
 			$content .= $this->getUnloggedInReferral();
 		} else {
@@ -990,7 +1246,6 @@
 		JVB()->connect('cloudflare')->renderTurnstile();
 		$turnstile = ob_get_clean();
 
-		$meta = new MetaForm();
 		$reward_text = $this->getRewardText(true);
 
 		// Pre-fill code if from referral link
@@ -1001,27 +1256,55 @@
 			$referrer_name = $referrer ? strtok($referrer->display_name, ' ') : '';
 		}
 
-		$codeForm = '<div class="referral-reward-banner">
-		'.jvbIcon('confetti').'
-		<h4>Get ' . esc_html($reward_text) . '!</h4>
-		' . ($referrer_name ? '<p>' . esc_html($referrer_name) . ' invited you to join us</p>' : '') . '
-	</div>
-	<form id="referral-code-form">
-				'.jvbFormStatus().$meta->return('referral_name', null, [
+		$header = sprintf(
+			'<header><h2>%sGet %s.</h2></header><h3>Have a code?</h3>%s<p>Enter your referral code to get started!</p>',
+			jvbIcon('confetti'),
+			esc_html($reward_text),
+			($referrer_name ? '<p>' . esc_html($referrer_name) . ' invited you to join us</p>' : '')
+		);
+
+		$codeForm = sprintf(
+			'<form id="referral-code-form">
+				%s
+   	 			<input type="hidden" name="user_select" value="%s">
+   	 			%s%s%s%s
+   	 			<div class="row">
+					<button type="button" class="button-secondary check-code-btn">
+						%s Verify Code
+					</button>
+					<button type="submit">
+						Get Started
+					</button>
+				</div>
+
+				<div class="code-status" hidden></div>
+
+				<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'
-			]).
-			$meta->return('referral_email', null, [
+			]),
+			Form::render('referral_email', '', [
 				'required'	=> true,
 				'type'		=> 'email',
 				'label'		=> 'Your Email',
 				'placeholder'=> 'look@me.com',
 				'autocomplete'=> 'email'
-			]).
-			$meta->return('referral_code', $prefill_code, [
+			]),
+			Form::render('referral_code', $prefill_code, [
 				'required'	=> true,
 				'type'		=> 'text',
 				'label'		=> 'Referral Code',
@@ -1029,54 +1312,53 @@
 				'maxLength'	=> 20,
 				'autocomplete'=>'off',
 				'data-referrer' => $referrer_name
-			]).'
-				<button type="button" class="button-secondary check-code-btn">
-					'.jvbIcon('check-circle', ['size' => 16]).' Verify Code
-				</button>
-				<div class="code-status" hidden></div>
-				<button type="submit">
-					Get Started
-				</button>
+			]),
+			$turnstile,
+			jvbIcon('check-circle')
+		);
 
-				<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>';
+		$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')
+		);
 
-		$loginForm = '<form id="login-form">
-	'.jvbFormStatus().$meta->return('login_email', null, [
-				'required'	=> true,
-				'type'		=> 'email',
-				'label'		=> 'Your Email',
-				'autocomplete'=>'email'
-			]).'
-	'.$turnstile.'
-	<button type="submit">Login With Magic Link</button>
-</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>';
 
-		$footer = '<div class="referral-footer">
-		<a href="' . wp_login_url() . '" class="text-link">Prefer to use a password?</a>
-	</div>';
+		$footer = sprintf(
+			'<p class="hint">
+			<a href="%s" class="text-link">Prefer to use a password?</a>
+	</p>',
+			wp_login_url()
+		);
 		$tabs = [
 			'enterCode' => [
 				'title'	=> 'Have a Code?',
 				'description'	=> [
 					'Enter your referral code to get started'
 				],
+				'header' => $header,
 				'content'	=> $codeForm
 			],
 			'login'	=> [
+				'header'	=> $loginHeader,
 				'title'		=> 'Login',
 				'description'	=> [
 					'Already have an account? Log in to see your rewards'
@@ -1142,13 +1424,14 @@
 	public function getLoggedInReferral(int $user_id): string
 	{
 		$referral_code = $this->getUserReferralCode($user_id);
-		if (is_wp_error($referral_code)) {
+		if (!$referral_code) {
 			return '';
 		}
 
 		$share_url = $this->getShareURL($referral_code);
 		ob_start();
 		?>
+		<div class="wrap">
 		<header>
 			<h3>Share the ♡</h3>
 			<p>Invite friends. Earn rewards.</p>
@@ -1156,54 +1439,61 @@
 
 		<?php $this->getShareButtons($user_id); ?>
 
-		<div class="copy-section">
+		<section class="copy">
 			<h4>Your Referral Link</h4>
-			<div class="copy-group">
+			<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" aria-label="Copy referral link">
-					<?php echo jvbIcon('copy', ['size' => 16]); ?>
+				<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="copy-group">
+			<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" aria-label="Copy referral code">
-					<?php echo jvbIcon('copy', ['size' => 16]); ?>
+				<button type="button" class="copy-btn" data-target="referral-code" title="Copy referral code">
+					<?= jvbIcon('copy'); ?>
+					<?= jvbIcon('check-circle'); ?>
 				</button>
 			</div>
-		</div>
+			<p class="hint">Manually copy and paste the code</p>
 
-		<div class="recent-referrals-section">
+		</section>
+
+		<section class="recent-referrals">
 			<h4>Recent Referrals</h4>
 			<div class="recent-referrals-list" data-user-id="<?= $user_id ?>">
 				<div class="loading">Loading...</div>
 			</div>
-		</div>
+		</section>
 
-		<div class="stats-summary">
-			<div class="stat-row">
+		<section class="stats-summary">
+			<div class="row x-btw">
 				<span class="stat-label">Total Referrals</span>
 				<span class="stat-value" data-stat="total">-</span>
 			</div>
-			<div class="stat-row">
+			<div class="row x-btw">
 				<span class="stat-label">Successful</span>
 				<span class="stat-value" data-stat="treated">-</span>
 			</div>
-			<div class="stat-row">
+			<div class="row x-btw">
 				<span class="stat-label">Pending</span>
 				<span class="stat-value" data-stat="pending">-</span>
 			</div>
-			<div class="stat-row highlight">
+			<div class="row x-btw highlight">
 				<span class="stat-label">Available Rewards</span>
 				<span class="stat-value" data-stat="rewards">$0.00</span>
 			</div>
-		</div>
+		</section>
 
 		<a href="<?= get_home_url(null, '/dash/referrals')?>" class="view-dashboard-btn">
 			Dashboard <?= jvbIcon('arrow-right', ['size' => 16]); ?>
 		</a>
-
+		<p class="hint">Bulk-invite your friends via email - the link will pre-fill their name, email, and code!</p>
+		</div>
 		<?php
 		return ob_get_clean();
 	}
@@ -1234,7 +1524,7 @@
 
 		$code = $this->getUserReferralCode($user->ID);
 		$yourCode = '';
-		if (!is_wp_error($code)) {
+		if ($code) {
 			$share_url = $this->getShareURL($code);
 			$yourCode = sprintf(
 				'<div class="callout">
@@ -1244,8 +1534,8 @@
 			<p>Or click the button below:</p>
 			%s
 			</div>',
-				jvbEmailLink($code),
-				jvbMailButton($share_url, 'Share Your Code')
+				JVB()->email()->link($code),
+				JVB()->email()->button($share_url, 'Share Your Code')
 			);
 		}
 
@@ -1257,10 +1547,9 @@
 	{
 		return add_query_arg(
 			[
-				'ref' => $code,
-				'action'	=> 'register'
+				'ref' => $code
 			],
-			wp_login_url()
+			get_home_url()
 		);
 	}
 
@@ -1270,15 +1559,12 @@
 	 * @param int $user_id Referrer's user ID
 	 * @param string $invitee_email Email of person to invite
 	 * @param string $invitee_name Name of person to invite
+	 * @param string $subject
+	 * @param string $message
 	 * @return array|WP_Error Result with success/error
 	 */
-	public function sendReferralInvitation(int $user_id, string $invitee_email, string $invitee_name):array|WP_Error
+	public function sendReferralInvitation(int $user_id, string $invitee_email, string $invitee_name, string $subject, string $message):array|WP_Error
 	{
-		// Verify user exists
-		if (!$this->checkUser($user_id)) {
-			return new WP_Error('invalid_user', 'Invalid user ID');
-		}
-
 		// Check email rate limit (15/hour)
 		$rate_check = $this->checkEmailRateLimit($user_id);
 		if ($rate_check !== true) {
@@ -1291,11 +1577,7 @@
 			return new WP_Error('invalid_email', 'Invalid email address');
 		}
 
-		// Check if this email has already been invited or registered
-		if ($this->isEmailInvited($invitee_email)) {
-			return new WP_Error('already_invited', 'This person has already been invited');
-		}
-
+		// Check if already registered
 		if (email_exists($invitee_email)) {
 			return new WP_Error('user_exists', 'This person already has an account');
 		}
@@ -1304,33 +1586,60 @@
 		$referrer = get_user_by('ID', $user_id);
 		$referral_code = $this->getUserReferralCode($user_id);
 
-		if (is_wp_error($referral_code)) {
+		if ($referral_code) {
 			return $referral_code;
 		}
 
-		// Get reward text for email
-		$reward_text = $this->settings['referee_reward_type'] === 'percentage'
-			? "Get {$this->settings['referee_reward_amount']}% off your first treatment!"
-			: "Get \${$this->settings['referee_reward_amount']} off your first treatment!";
-
-		// Record the invitation attempt (for tracking)
+		// Record the invitation attempt (for rate limiting only)
 		$this->recordInvitationAttempt($user_id, $invitee_email, $invitee_name);
 
-		// Send magic link via MagicLinkManager
-		$result = $this->magic_link->sendMagicLink(
+		// Create registration URL with token (opens sidebar with prefilled form)
+		$token_data = [
+			'name' => sanitize_text_field($invitee_name),
+			'email' => $invitee_email,
+			'expires' => time() + (30 * DAY_IN_SECONDS)
+		];
+
+		// Encode the token
+		$token = base64_encode(json_encode($token_data));
+		$registration_url = add_query_arg([
+			'ref' => $referral_code,
+			'rname' => sanitize_text_field($invitee_name),
+			'remail'=> rawurlencode($invitee_email),
+		], home_url('/'));
+
+		// Get reward text for email
+		$reward_text = $this->settings['referee_reward_type'] === 'percentage'
+			? "{$this->settings['referee_reward_amount']}% off"
+			: "\${$this->settings['referee_reward_amount']} off";
+
+		// Build email content
+		$email_content =
+			sprintf(
+				'<h2>%s invited you to %s!</h2>
+			<p>%s</p>
+			<div class="callout">
+				<h3>Get %s your first treatment!</h3>
+			</div>
+			<p>Click the button below to register and claim your reward:</p>
+			%s
+			<p><small>This invitation expires in 30 days.</small></p>',
+				esc_html($referrer->display_name),
+				esc_html(get_bloginfo('name')),
+				nl2br(esc_html($message)),
+				esc_html($reward_text),
+				JVB()->email()->button($registration_url, 'Register & Get Your Reward')
+			);
+
+		// Send email
+		$sent = JVB()->email()->sendEmail(
 			$invitee_email,
-			MagicLinkManager::TYPE_REFERRAL,
-			[
-				'name' => sanitize_text_field($invitee_name),
-				'referral_code' => $referral_code,
-				'referrer_id' => $user_id,
-				'referrer_name' => $referrer->display_name,
-				'reward_text' => $reward_text
-			]
+			$subject,
+			$email_content
 		);
 
-		if (is_wp_error($result)) {
-			return $result;
+		if (!$sent) {
+			return new WP_Error('email_failed', 'Failed to send invitation email');
 		}
 
 		return [
@@ -1348,7 +1657,7 @@
 	 * @param array $invitations Array of ['email' => '', 'name' => '']
 	 * @return array Results with success/failed arrays
 	 */
-	public function sendBatchReferralInvitations(int $user_id, array $invitations): array
+	public function sendBatchReferralInvitations(int $user_id, array $invitations, string $subject, string $message): array
 	{
 		$results = [
 			'success' => [],
@@ -1368,7 +1677,7 @@
 				continue;
 			}
 
-			$result = $this->sendReferralInvitation($user_id, $email, $name);
+			$result = $this->sendReferralInvitation($user_id, $email, $name, $subject, $message);
 
 			if (is_wp_error($result)) {
 				$results['failed'][] = [
@@ -1389,7 +1698,7 @@
 
 		return [
 			'success' => !empty($results['success']),
-			'results' => $results,
+			'result' => $results,
 			'summary' => sprintf(
 				'Sent %d invitations, %d failed',
 				count($results['success']),
@@ -1576,6 +1885,7 @@
 
 	/**
 	 * Add referral settings subpage to admin menu
+	 * Add referral settings subpage to admin menu
 	 *
 	 * @param array $subpages
 	 * @return array
@@ -1730,7 +2040,7 @@
 			<!-- Settings Section -->
 			<?= $this->renderAdminHTML() ?>
 		</div>
-
+<?php /**
 		<style>
 			.jvb-upload-box {
 				padding: 20px;
@@ -1785,11 +2095,12 @@
 				margin: 10px 0;
 			}
 		</style>
-
+*/
+		if (is_admin()) {
+?>
 		<script>
 			jQuery(document).ready(function($) {
 				// Client upload
-				// Client upload
 				$('#client-upload-form').on('submit', function(e) {
 					e.preventDefault();
 					const formData = new FormData(this);
@@ -1897,12 +2208,12 @@
 					const search = $('#referral-search').val();
 
 					$.ajax({
-						url: '<?= rest_url('jvb/v1/referrals/list') ?>',
+						url: '<?= rest_url('jvb/v1/referrals') ?>',
 						method: 'GET',
 						data: {
-							page: page,
-							per_page: 20,
-							status: status,
+							offset: page -1,
+							limit: 20,
+							status: status === '' ? 'all' : status,
 							search: search
 						},
 						beforeSend: function(xhr) {
@@ -1930,10 +2241,10 @@
 					html += '<th>Actions</th>';
 					html += '</tr></thead><tbody>';
 
-					if (data.referrals.length === 0) {
+					if (data.items.length === 0) {
 						html += '<tr><td colspan="7" style="text-align: center;">No referrals found</td></tr>';
 					} else {
-						data.referrals.forEach(function(ref) {
+						data.items.forEach(function(ref) {
 							html += '<tr>';
 							html += '<td>' + (ref.referrer_name || 'Unknown') + '</td>';
 							html += '<td>' + (ref.referee_display_name || ref.referee_name) + '</td>';
@@ -1976,9 +2287,12 @@
 					if (!confirm('Mark this referral as consulted? This will create the consultation reward.')) return;
 
 					$.ajax({
-						url: '<?= rest_url('jvb/v1/referrals/mark-consulted') ?>',
+						url: '<?= rest_url('jvb/v1/referrals') ?>', // Changed from /mark-consulted
 						method: 'POST',
-						data: JSON.stringify({ referral_id: id }),
+						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') ?>');
@@ -1999,9 +2313,12 @@
 					if (!confirm('Mark this referral as treated? This will create rewards for both parties.')) return;
 
 					$.ajax({
-						url: '<?= rest_url('jvb/v1/referrals/mark-treated') ?>',
+						url: '<?= rest_url('jvb/v1/referrals') ?>', // Changed from /mark-treated
 						method: 'POST',
-						data: JSON.stringify({ referral_id: id }),
+						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') ?>');
@@ -2039,6 +2356,7 @@
 			});
 		</script>
 		<?php
+		}
 	}
 
 	protected function renderAdminHTML():string
@@ -2166,14 +2484,14 @@
 						</tr>
 						<tr>
 							<th scope="row">
-								<label for="<?= BASE ?>client_import_role">Client Import Role</label>
+								<label for="<?= BASE ?>referral_role">Client Import Role</label>
 							</th>
 							<td>
 								<?php
-								$selected_role = get_option(BASE . 'client_import_role', '');
+								$selected_role = get_option(BASE . 'referral_role', '');
 								$roles = wp_roles()->get_names();
 								?>
-								<select name="<?= BASE ?>client_import_role" id="<?= BASE ?>client_import_role">
+								<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) ?>
@@ -2333,122 +2651,202 @@
 
 		// Regular users get their referral dashboard
 		$user_id = get_current_user_id();
-		$referral_code = get_user_meta($user_id, BASE . 'referral_code', true);
 
+		$referral_code = $this->getUserReferralCode($user_id);
 		if (!$referral_code) {
-			$referral_code = $this->getUserReferralCode($user_id);
+			$user = get_userdata($user_id);
+			$referral_code = $this->generateReferralCode($user);
 		}
 
-		$stats = $this->getUserStats($user_id);
 		$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">
-			<div class="referral-header">
-				<h2>Your Referrals</h2>
-				<p>Share your code and earn rewards when your referrals complete their first treatment!</p>
-			</div>
+			<?= $tabs->render(true);?>
+		</div>
 
-			<?php $this->getShareButtons($user_id); ?>
+		<?php
+		return ob_get_clean();
+	}
 
-			<!-- Referral Code Card -->
-			<div class="referral-code-card">
-				<h3>Your Referral Code</h3>
-				<div class="code-display">
-					<span class="code"><?= esc_html($referral_code) ?></span>
-					<button class="button copy-code" data-code="<?= esc_attr($referral_code) ?>">
-						Copy Code
-					</button>
-				</div>
-				<p class="share-link">
-					Share link: <input type="text" readonly value="<?= home_url('/?ref=' . $referral_code) ?>"
-									   onclick="this.select()" style="width: 100%; margin-top: 5px;" />
-				</p>
+	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>
-			<form class="invite">
-				<?php
-				$meta = new MetaForm();
-				$field = [
-					'type'	=> 'repeater',
-					'label'	=> 'Invite Your Friends',
-					'fields'	=> [
-						'name'	=> [
-							'type'	=> 'text',
-							'label'	=> 'name',
-						],
-						'email'	=> [
-							'type'	=> 'email',
-							'label'	=> 'email',
-						]
+			<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
 					]
-				];
-				$meta->render('invite', [], $field);
+				]
+			];
+			$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);
+				}
 				?>
-			</form>
+			</details>
 
-			<!-- Stats Grid -->
-			<div class="stats-grid">
-				<div class="stat-card">
-					<h4>Total Referrals</h4>
-					<span class="stat-number"><?= esc_html($stats['total_referrals'] ?? 0) ?></span>
-				</div>
-				<div class="stat-card">
-					<h4>Completed Treatments</h4>
-					<span class="stat-number"><?= esc_html($stats['treated_count'] ?? 0) ?></span>
-				</div>
-				<div class="stat-card">
-					<h4>Pending</h4>
-					<span class="stat-number"><?= esc_html($stats['pending_count'] ?? 0) ?></span>
-				</div>
-				<div class="stat-card highlight">
-					<h4>Available Rewards</h4>
-					<span class="stat-number">$<?= number_format($stats['available_rewards'] ?? 0, 2) ?></span>
-				</div>
+			<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>
-
-			<!-- Referrals List -->
-			<div class="referrals-list-card">
-				<h3>Your Referrals</h3>
-				<?php if (empty($referrals)): ?>
-					<p>You haven't referred anyone yet. Share your code to get started!</p>
-				<?php else: ?>
-					<table class="referrals-table">
-						<thead>
-						<tr>
-							<th>Name</th>
-							<th>Email</th>
-							<th>Status</th>
-							<th>Referred Date</th>
-						</tr>
-						</thead>
-						<tbody>
-						<?php foreach ($referrals as $ref): ?>
-							<tr>
-								<td><?= esc_html($ref->referee_name) ?></td>
-								<td><?= esc_html($ref->referee_email) ?></td>
-								<td><span class="status-badge <?= esc_attr($ref->status) ?>"><?= esc_html(ucfirst($ref->status)) ?></span></td>
-								<td><?= date('M j, Y', strtotime($ref->referred_at)) ?></td>
-							</tr>
-						<?php endforeach; ?>
-						</tbody>
-					</table>
-				<?php endif; ?>
+			<div 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>
 
-
-		<script>
-			jQuery(document).ready(function($) {
-				$('.copy-code').on('click', function() {
-					const code = $(this).data('code');
-					navigator.clipboard.writeText(code).then(function() {
-						alert('Code copied to clipboard!');
-					});
-				});
-			});
-		</script>
 		<?php
+		// Configure CRUDSkeleton for referrals
+		$crud = new CRUDSkeleton();
+		$crud->title('Your Referrals', 'Track friends you\'ve invited and rewards earned')
+			->content('referral', 'Referral', 'Referrals')
+//			->initMeta('custom', 'referral')
+			->setFields([
+				'referee_name' => [
+					'label' => 'Name',
+					'type' => 'text',
+				],
+				'referee_email' => [
+					'label' => 'Email',
+					'type' => 'text',
+				],
+				'referred_at' => [
+					'label' => 'Code Used',
+					'type' => 'date',
+				],
+				'referral_status' => [
+					'label' => 'Status',
+					'type' => 'text',
+				]
+			])
+			->setStatuses(['all', 'unused', 'registered', 'consulted', 'completed'])
+			->addViews(['table', 'list'])
+			->defaultView('table')
+			->addCapabilities(['view'])
+			->addDateFilter('referred_at')
+			->showBulkControls(false)
+			->showFilters(false)
+			->useCRUDjs(false); // We'll use our custom Referral.js with DataStore
+
+		// Add custom template for actions column
+		$crud->addItemActions(['resend', 'trash']);
+		$crud->defineItemAction('resend', [
+			'title'	=> 'Resend Invitation',
+			'icon'	=> 'paper-plane-tilt'
+		]);
+		$crud->defineItemAction('trash', [
+			'title'	=> 'Remove from List'
+		]);
+
+		// Custom empty state
+		$crud->addTemplate('empty', '
+		<template class="emptyState">
+			<div class="empty-state">
+				<h3>' . jvbDashIcon('hand-heart') . 'Nothing Yet' . jvbDashIcon('hand-heart') . '</h3>
+				<p>Start sharing your referral code to earn rewards!</p>
+				<p><small><i>Share your code using the "Share" tab below.</i></small></p>
+			</div>
+		</template>
+	');
+
+		$crud->render();
+
 		return ob_get_clean();
 	}
 
@@ -2473,8 +2871,8 @@
 			update_option(BASE . 'referral_page_id', $page_id);
 
 			// Save client import role
-			$import_role = sanitize_text_field($post_data[BASE . 'client_import_role'] ?? JVB_USER);
-			update_option(BASE . 'client_import_role', $import_role);
+			$import_role = sanitize_text_field($post_data[BASE . 'referral_role'] ?? Site::getDefaultReferralRole());
+			update_option(BASE . 'referral_role', $import_role);
 
 			// Save reward settings
 			$settings = [
@@ -2526,7 +2924,7 @@
 	public function getShareButtons(int $user_id):void
 	{
 		$referral_code = $this->getUserReferralCode($user_id);
-		if (is_wp_error($referral_code)) {
+		if (!$referral_code) {
 			return;
 		}
 
@@ -2542,7 +2940,7 @@
 		?>
 		<nav class="share">
 			<h4>Quick Share</h4>
-			<ul class="share-buttons-grid">
+			<ul>
 				<a href="mailto:?subject=<?php echo urlencode('Check out ' . get_bloginfo('name')); ?>&body=<?php echo urlencode($share_message . ' ' . $share_url); ?>"
 				   class="button" title="Email">
 					<?php echo jvbIcon('envelope'); ?>
@@ -2573,5 +2971,98 @@
 		</nav>
 	<?php
 	}
+
+	/**
+	 * Send notification to referrer when someone registers
+	 *
+	 * @param int $referrer_id
+	 * @param string $referee_name
+	 */
+	protected function sendReferrerNotification(int $referrer_id, string $referee_name): void
+	{
+		$referrer = get_userdata($referrer_id);
+		if (!$referrer) {
+			return;
+		}
+
+		$subject = sprintf('%s signed up with your referral code!', $referee_name);
+		$message = sprintf(
+			"Great news! %s just signed up using your referral code.\n\n" .
+			"View your referrals: %s",
+			$referee_name,
+			home_url('/dash/referrals')
+		);
+
+		JVB()->email()->sendEmail(
+			$referrer->user_email,
+			$subject,
+			$message
+		);
+	}
+
+	/**
+	 * Get welcome message for newly referred user
+	 *
+	 * @param int $user_id
+	 * @return string HTML content for welcome message
+	 */
+	public function getReferralWelcomeMessage(int $user_id): string
+	{
+		// Check if user was referred
+		$referral = $this->getReferralByReferee($user_id);
+
+		if (!$referral || $referral->status !== 'pending') {
+			return '';
+		}
+
+		// Only show for recent registrations (within 7 days)
+		$registered_time = strtotime($referral->referred_at);
+		if ((time() - $registered_time) > (7 * DAY_IN_SECONDS)) {
+			return '';
+		}
+
+		// Get referrer name
+		$referrer = get_userdata($referral->referrer_id);
+		$referrer_first_name = $referrer ? strtok($referrer->display_name, ' ') : 'Your friend';
+
+		// Get reward text
+		$reward_text = $this->getRewardText(); // Just "20% off" or "$25 off"
+
+		$booking_url = apply_filters('jvb_referral_booking_url', home_url('/contact'));
+		$estimate_url = apply_filters('jvb_referral_estimate_url', home_url('/estimate'));
+
+		ob_start();
+		?>
+		<div class="welcome-banner referral-welcome">
+			<div class="banner-content">
+				<h3><?= jvbIcon('confetti') ?>Welcome! <small><b><?= esc_html($referrer_first_name) ?></b> invited you to save <b><?= esc_html($reward_text) ?></b>!</small></h3>
+				<p>But we're not done yet! Here's what happens next:</p>
+				<div class="callout">
+					<ol>
+						<li>Book your <b>free consultation</b></li>
+						<li>Come in and we'll assess your tattoo</li>
+						<li>Get <?= esc_html($reward_text) ?> your first treatment!</li>
+					</ol>
+				</div>
+				<p class="hint">
+					<strong>Important:</strong> If you book with a different email than
+					<strong><?= esc_html(wp_get_current_user()->user_email) ?></strong>,
+					please let us know so we can apply your reward!
+				</p>
+				<ul class="buttons">
+					<li><a href="<?= esc_url($estimate_url) ?>" class="button-secondary">
+						<?= jvbIcon('calculator') ?> Get an Estimate First
+					</a></li>
+					<li><a href="<?= esc_url($booking_url) ?>" class="button-primary">
+						<?= jvbIcon('calendar') ?> Book Free Consult
+					</a></li>
+				</ul>
+
+
+			</div>
+		</div>
+		<?php
+		return ob_get_clean();
+	}
 }
 

--
Gitblit v1.10.0