From 2a2303d1dccc120dd7aa5f6b6ade0f89e0064850 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Tue, 25 Nov 2025 07:42:23 +0000
Subject: [PATCH] =Feed block mostly good! Referrals look good to go. Ready for Madi and Heidi to approve

---
 inc/managers/ReferralManager.php | 1468 +++++++++++++++++++++++++++++++++++++++++++++++++++-------
 1 files changed, 1,289 insertions(+), 179 deletions(-)

diff --git a/inc/managers/ReferralManager.php b/inc/managers/ReferralManager.php
index 429ea99..3f000e4 100644
--- a/inc/managers/ReferralManager.php
+++ b/inc/managers/ReferralManager.php
@@ -3,6 +3,7 @@
 
 use JVBase\managers\MagicLinkManager;
 use JVBase\integrations\Cloudflare;
+use JVBase\meta\MetaForm;
 use JVBase\utility\Features;
 use WP_User;
 use WP_Error;
@@ -23,6 +24,7 @@
 	protected MagicLinkManager $magic_link;
 	protected CacheManager $cache;
 	protected string $referrals_table;
+	protected ?int $referralPage = null;
 	protected string $rewards_table;
 
 	// Default reward settings
@@ -35,17 +37,24 @@
 		'referee_reward_applies_to' => 'first_order'  // 'first_order' or 'all_orders'
 	];
 
+	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->cache = CacheManager::for('referrals', WEEK_IN_SECONDS);
+		$this->referrals_table = $wpdb->prefix . BASE . 'referrals';
+		$this->rewards_table = $wpdb->prefix . BASE . 'referral_rewards';
 		$this->magic_link = new MagicLinkManager();
 
-		// Hook into user registration to track referrals
-		add_action('user_register', [$this, 'processReferral'], 10, 1);
+		$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('jvbUserRegistered', [$this, 'processReferral'], 10, 1);
 
 		// Add meta boxes for admin to manage referrals
 		add_action('show_user_profile', [$this, 'displayUserReferralInfo']);
@@ -55,14 +64,59 @@
 		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_action('jvbAdditionalActions', [$this, 'outputShareWidget']);
+		add_filter('jvbAdditionalActions', [$this, 'outputShareWidget']);
 
 		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);
+	}
+
+	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
@@ -113,7 +167,7 @@
 	 */
 	public function getUserReferralCode(int $user_id, ?string $custom_code = null)
 	{
-		$user = get_user_by('ID', $user_id);
+		$user = get_userdata($user_id);
 		if (!$user) {
 			return new WP_Error('invalid_user', 'User not found');
 		}
@@ -189,17 +243,47 @@
 		return !empty($users);
 	}
 
+	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
 	 */
-	public function processReferral(int $user_id): void
+	public function processReferral(int $user_id, string $email, array $data): void
 	{
 		// Check if user was created via referral magic link
-		$referral_code = get_user_meta($user_id, BASE . 'pending_referral_code', true);
+		// Try to get code from multiple sources
+    	$referral_code = $data['referral_code'] ??
+		get_user_meta($user_id, BASE . 'pending_referral_code', true);
 
-		if (!$referral_code) {
+		// Check session/cookie if not in data
+		if (empty($referral_code)) {
+			if (session_status() === PHP_SESSION_NONE) {
+				session_start();
+			}
+			$referral_code = $_SESSION[BASE . 'referral_code'] ?? $_COOKIE[BASE . 'referral_code'] ?? '';
+		}
+
+		if (empty($referral_code)) {
 			return;
 		}
 
@@ -238,7 +322,7 @@
 	 * @param string $code
 	 * @return int|false
 	 */
-	protected function createReferral(int $referrer_id, int $referee_id, string $code)
+	public function createReferral(int $referrer_id, int $referee_id, string $code)
 	{
 		$user = get_user_by('ID', $referee_id);
 
@@ -266,13 +350,17 @@
 	 */
 	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;
+			}
+		);
 	}
 
 	/**
@@ -337,8 +425,6 @@
 			return;
 		}
 
-		$settings = $this->getRewardSettings();
-
 		// Create referrer reward
 		$this->wpdb->insert(
 			$this->rewards_table,
@@ -346,7 +432,7 @@
 				'referral_id' => $referral_id,
 				'user_id' => $referral->referrer_id,
 				'reward_type' => 'referrer',
-				'amount' => $settings['referrer_reward_amount'],
+				'amount' => $this->settings['referrer_reward_amount'],
 				'status' => 'available',
 				'created_at' => current_time('mysql')
 			],
@@ -354,9 +440,9 @@
 		);
 
 		// 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
+		$referee_amount = $this->settings['referee_reward_type'] === 'percentage'
+			? $this->settings['referee_reward_amount']  // Store as percentage
+			: $this->settings['referee_reward_amount'];  // Store as fixed amount
 
 		$this->wpdb->insert(
 			$this->rewards_table,
@@ -365,7 +451,7 @@
 				'user_id' => $referral->referee_id,
 				'reward_type' => 'referee',
 				'amount' => $referee_amount,
-				'reward_calculation' => $settings['referee_reward_type'],
+				'reward_calculation' => $this->settings['referee_reward_type'],
 				'status' => 'available',
 				'created_at' => current_time('mysql')
 			],
@@ -877,17 +963,16 @@
 	{
 
 		$user_id = get_current_user_id();
-		jvbDump($user_id);
 		$content = '<aside class="jvb-referral right">';
 		if (!$user_id) {
 			$content .= $this->getUnloggedInReferral();
 		} else {
 			$content .= $this->getLoggedInReferral($user_id);
 		}
-		$content .= '<button type="button" class="close">'.jvbIcon('close').'</button></aside>';
+		$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,101 +981,116 @@
 		return $actions;
 	}
 
+	/**
+	 * Display referral sidebar for non-logged-in users
+	 */
 	function getUnloggedInReferral(): string
 	{
 		ob_start();
 		JVB()->connect('cloudflare')->renderTurnstile();
 		$turnstile = ob_get_clean();
-		$codeForm = '<form id="referral-code-form">
-					<div class="status" hidden>
-						<div class="spinner"></div>
-						<p class="message"></p>
-					</div>
-					<div class="field text">
-						<label for="referral-name">Your Name</label>
-						<input type="text"
-							   id="referral-name"
-							   name="name"
-							   placeholder="Mister Meeseeks"
-							   autocomplete="name"
-							   required>
-					</div>
 
-					<div class="field email">
-						<label for="referral-email">Your Email</label>
-						<input type="email"
-							   id="referral-email"
-							   name="email"
-							   placeholder="look@me.com"
-							   autocomplete="email"
-							   required>
-					</div>
+		$meta = new MetaForm();
+		$reward_text = $this->getRewardText(true);
 
-					<div class="field text">
-						<label for="referral-code-input">Referral Code</label>
-						<input type="text"
-							   id="referral-code-input"
-							   name="referral_code"
-							   placeholder="e.g., THISISFAKE1234"
-							   required
-							   pattern="[A-Za-z0-9]+"
-							   maxlength="20"
-							   autocomplete="off"
-							   style="text-transform: uppercase;">
-					</div>
+		// Pre-fill code if from referral link
+		$prefill_code = $_GET['ref'] ?? '';
+		$referrer_name = '';
+		if ($prefill_code) {
+			$referrer = $this->getUserByReferralCode($prefill_code);
+			$referrer_name = $referrer ? strtok($referrer->display_name, ' ') : '';
+		}
 
-					<button type="submit">
-						Get Started
-					</button>
+		$codeForm = '<div class="referral-reward-banner">
+		'.jvbIcon('confetti').'
+		<h4>Get ' . esc_html($reward_text) . '!</h4>
+		' . ($referrer_name ? '<p>' . esc_html($referrer_name) . ' invited you to join us</p>' : '') . '
+	</div>
+	<form id="referral-code-form">
+				'.jvbFormStatus().$meta->return('referral_name', null, [
+				'required'	=> true,
+				'type'		=> 'text',
+				'label'		=> 'Your Name',
+				'placeholder'=> 'Mister Meeseeks',
+				'autocomplete'=>'name'
+			]).
+			$meta->return('referral_email', null, [
+				'required'	=> true,
+				'type'		=> 'email',
+				'label'		=> 'Your Email',
+				'placeholder'=> 'look@me.com',
+				'autocomplete'=> 'email'
+			]).
+			$meta->return('referral_code', $prefill_code, [
+				'required'	=> true,
+				'type'		=> 'text',
+				'label'		=> 'Referral Code',
+				'pattern'	=> '[A-Za-z0-9]+',
+				'maxLength'	=> 20,
+				'autocomplete'=>'off',
+				'data-referrer' => $referrer_name
+			]).'
+				<button type="button" class="button-secondary check-code-btn">
+					'.jvbIcon('check-circle', ['size' => 16]).' Verify Code
+				</button>
+				<div class="code-status" hidden></div>
+				<button type="submit">
+					Get Started
+				</button>
 
-					<p class="helper-text">
-						We\'ll send you a link to complete your registration.
-					</p>
-					'.$turnstile.'
-				</form><div class="success-content" hidden>
-					<h3>Check Your Email!</h3>
-					<p>We\'ve sent you a magic link to complete your registration. Click the link to activate your account and claim your reward!</p>
-					<p class="hint">Can\'t find it? Check your spam folder.</p>
-				</div>';
+				<p class="helper-text">
+					We\'ll send you a link to complete your registration.
+				</p>
+				'.$turnstile.'
+			</form>
+			<div class="success-content" hidden>
+				<h3>Check Your Email!</h3>
+				<p>We\'ve sent you a magic link to complete your registration. Click the link to activate your account and claim your reward!</p>
+				<p class="hint">Can\'t find it? Check your spam folder.</p>
+			</div>';
 
-		$loginForm = '<form id ="login-form">
-		<div class="status" hidden>
-			<div class="spinner"></div>
-			<p class="message"></p>
-		</div>
-		<div class="field email">
-			<label for="login-email">Your Email</label>
-			<input id="login-email" name="login-email" type="email" autocomplete="email">
-		</div>
-		'.$turnstile.'
-		<button type="submit">Login With Magic Link</button>
+		$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>';
+<div class="success-content" hidden>
+	<h3>Check Your Email!</h3>
+	<p>We\'ve sent you a magic link to log in - no password required! Click the link in your email to log in.</p>
+	<p class="hint">Can\'t find it? Check your spam folder.</p>
+</div>';
 
+		$footer = '<div class="referral-footer">
+		<a href="' . wp_login_url() . '" class="text-link">Prefer to use a password?</a>
+	</div>';
 		$tabs = [
 			'enterCode' => [
 				'title'	=> 'Have a Code?',
 				'description'	=> [
-					'Enter the code given to you to get 20% off your first treatment!'
+					'Enter your referral code to get started'
 				],
 				'content'	=> $codeForm
 			],
 			'login'	=> [
 				'title'		=> 'Login',
 				'description'	=> [
-					'Login to see your rewards'
+					'Already have an account? Log in to see your rewards'
 				],
-				'content'	=> $loginForm
+				'content'	=> $loginForm.$footer
 			]
 		];
 
+
+
 		return jvbRenderTabs($tabs, true);
 	}
 
+
 	protected function getReferralSuccessMessage(string $code): string
 	{
 		$referrer = $this->getUserByReferralCode($code);
@@ -999,9 +1099,8 @@
 			return '';
 		}
 
-		$settings = $this->getRewardSettings();
-		$reward_amount = $settings['referee_reward_amount'] ?? 20;
-		$reward_type = $settings['referee_reward_type'] ?? 'percentage';
+		$reward_amount = $this->settings['referee_reward_amount'] ?? 20;
+		$reward_type = $this->settings['referee_reward_type'] ?? 'percentage';
 
 		$reward_text = $reward_type === 'percentage'
 			? $reward_amount . '% off'
@@ -1037,86 +1136,73 @@
 		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 (is_wp_error($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>
+		<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); ?>
 
+		<div class="copy-section">
 			<h4>Your Referral Link</h4>
-			<div class="row btw">
-				<code id="your-referral-link"><?= esc_url($share_url)?></code>
-				<button type="button" class="copy" data-target="your-referral-link">
-					Copy Link
+			<div class="copy-group">
+				<code id="referral-link" class="copy-target"><?= esc_url($share_url) ?></code>
+				<button type="button" class="copy-btn" data-target="referral-link" aria-label="Copy referral link">
+					<?php echo jvbIcon('copy', ['size' => 16]); ?>
 				</button>
 			</div>
 
-
 			<h4>Your Code</h4>
-			<div class="row btw">
-				<code id="your-referral-code"><?=esc_html($referral_code)?></code>
-				<button type="button" class="copy" data-target="your-referral-code">
-					Copy Code
+			<div class="copy-group">
+				<code id="referral-code" class="copy-target"><?= esc_html($referral_code) ?></code>
+				<button type="button" class="copy-btn" data-target="referral-code" aria-label="Copy referral code">
+					<?php echo jvbIcon('copy', ['size' => 16]); ?>
 				</button>
 			</div>
+		</div>
 
-			<div class="row btw referral-stats">
-				<div class="stat-item">
-					<span class="stat-value" data-stat="total">-</span>
-					<span class="stat-label">Total Referrals</span>
-				</div>
-				<div class="stat-item">
-					<span class="stat-value" data-stat="treated">-</span>
-					<span class="stat-label">Successful</span>
-				</div>
-				<div class="stat-item">
-					<span class="stat-value" data-stat="pending">-</span>
-					<span class="stat-label">Pending</span>
-				</div>
-				<div class="stat-item">
-					<span class="stat-value" data-stat="rewards">$0.00</span>
-					<span class="stat-label">Available Rewards</span>
-				</div>
+		<div class="recent-referrals-section">
+			<h4>Recent Referrals</h4>
+			<div class="recent-referrals-list" data-user-id="<?= $user_id ?>">
+				<div class="loading">Loading...</div>
 			</div>
+		</div>
+
+		<div class="stats-summary">
+			<div class="stat-row">
+				<span class="stat-label">Total Referrals</span>
+				<span class="stat-value" data-stat="total">-</span>
+			</div>
+			<div class="stat-row">
+				<span class="stat-label">Successful</span>
+				<span class="stat-value" data-stat="treated">-</span>
+			</div>
+			<div class="stat-row">
+				<span class="stat-label">Pending</span>
+				<span class="stat-value" data-stat="pending">-</span>
+			</div>
+			<div class="stat-row highlight">
+				<span class="stat-label">Available Rewards</span>
+				<span class="stat-value" data-stat="rewards">$0.00</span>
+			</div>
+		</div>
+
+		<a href="<?= get_home_url(null, '/dash/referrals')?>" class="view-dashboard-btn">
+			Dashboard <?= jvbIcon('arrow-right', ['size' => 16]); ?>
+		</a>
 
 		<?php
 		return ob_get_clean();
@@ -1130,27 +1216,52 @@
 			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['referee_reward_amount'] ?? 20;
+		$reward_type = $this->settings['referee_reward_type'] ?? 'percentage';
 
 		$reward_text = $reward_type === 'percentage'
 			? $reward_amount . '% off'
 			: '$' . number_format($reward_amount, 2) . ' off';
 
-		$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 (!is_wp_error($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>',
+				jvbEmailLink($code),
+				jvbMailButton($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,
+				'action'	=> 'register'
+			],
+			wp_login_url()
+		);
 	}
 
 	/**
@@ -1198,10 +1309,9 @@
 		}
 
 		// 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!";
+		$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)
 		$this->recordInvitationAttempt($user_id, $invitee_email, $invitee_name);
@@ -1463,5 +1573,1005 @@
 
 		return $csv_content;
 	}
+
+	/**
+	 * 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 [
+			'referrer_reward_applies_to' => in_array($settings['referrer_reward_applies_to'] ?? '', ['per_user', 'flat_total'])
+				? $settings['referrer_reward_applies_to']
+				: 'per_user',
+			'referrer_reward_amount' => floatval($settings['referrer_reward_amount'] ?? 25.00),
+			'referrer_reward_type' => in_array($settings['referrer_reward_type'] ?? '', ['fixed', 'percentage'])
+				? $settings['referrer_reward_type']
+				: 'fixed',
+			'referee_reward_type' => in_array($settings['referee_reward_type'] ?? '', ['percentage', 'fixed'])
+				? $settings['referee_reward_type']
+				: 'percentage',
+			'referee_reward_amount' => floatval($settings['referee_reward_amount'] ?? 20),
+			'referee_reward_applies_to' => in_array($settings['referee_reward_applies_to'] ?? '', ['first_order', 'all_orders'])
+				? $settings['referee_reward_applies_to']
+				: '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>
+
+		<style>
+			.jvb-upload-box {
+				padding: 20px;
+				background: #f9f9f9;
+				border: 1px solid #ddd;
+				border-radius: 4px;
+			}
+			.jvb-upload-box h3 {
+				margin-top: 0;
+			}
+			.referrals-table {
+				width: 100%;
+				border-collapse: collapse;
+			}
+			.referrals-table th,
+			.referrals-table td {
+				padding: 12px;
+				text-align: left;
+				border-bottom: 1px solid #ddd;
+			}
+			.referrals-table th {
+				background: #f5f5f5;
+				font-weight: 600;
+			}
+			.referrals-table tr:hover {
+				background: #f9f9f9;
+			}
+			.referral-status {
+				padding: 4px 8px;
+				border-radius: 3px;
+				font-size: 12px;
+				font-weight: 500;
+			}
+			.referral-status.pending {
+				background: #fff3cd;
+				color: #856404;
+			}
+			.referral-status.consulted {
+				background: #d1ecf1;
+				color: #0c5460;
+			}
+			.referral-status.treated {
+				background: #d4edda;
+				color: #155724;
+			}
+			.referral-actions {
+				display: flex;
+				gap: 5px;
+			}
+			.notice.notice-success,
+			.notice.notice-error {
+				margin: 10px 0;
+			}
+		</style>
+
+		<script>
+			jQuery(document).ready(function($) {
+				// Client upload
+				// Client upload
+				$('#client-upload-form').on('submit', function(e) {
+					e.preventDefault();
+					const formData = new FormData(this);
+					formData.append('file', $('#client_file')[0].files[0]);
+
+					$('#client-upload-status').html('<span class="spinner is-active"></span> Uploading...');
+
+					$.ajax({
+						url: '<?= rest_url('jvb/v1/referrals/upload-clients') ?>',
+						method: 'POST',
+						data: formData,
+						processData: false,
+						contentType: false,
+						beforeSend: function(xhr) {
+							xhr.setRequestHeader('X-WP-Nonce', '<?= wp_create_nonce('wp_rest') ?>');
+						},
+						success: function(response) {
+							if (response.success) {
+								let message = '<div class="notice notice-success"><p>' + response.message;
+								message += '<br>Created: ' + (response.stats.created || 0);
+								message += ', Updated: ' + (response.stats.updated || 0);
+								message += ', Skipped: ' + (response.stats.skipped || 0) + '</p>';
+
+								// Show skipped details if any
+								if (response.stats.skipped_details && response.stats.skipped_details.length > 0) {
+									message += '<details style="margin-top: 10px;"><summary>View skipped records (' + response.stats.skipped_details.length + ')</summary>';
+									message += '<table class="widefat" style="margin-top: 10px;"><thead><tr>';
+									message += '<th>Line</th><th>Name</th><th>Email</th><th>GUID</th><th>Reason</th>';
+									message += '</tr></thead><tbody>';
+									response.stats.skipped_details.forEach(function(item) {
+										message += '<tr>';
+										message += '<td>' + (item.line || '-') + '</td>';
+										message += '<td>' + (item.name || '-') + '</td>';
+										message += '<td>' + (item.email || '-') + '</td>';
+										message += '<td>' + (item.guid || '-') + '</td>';
+										message += '<td>' + item.reason + '</td>';
+										message += '</tr>';
+									});
+									message += '</tbody></table></details>';
+								}
+
+								message += '</div>';
+
+								$('#client-upload-status').html(message);
+								$('#client-upload-form')[0].reset();
+								loadReferralsTable();
+							} else {
+								$('#client-upload-status').html(
+									'<div class="notice notice-error"><p>' + response.message + '</p></div>'
+								);
+							}
+						},
+						error: function(xhr) {
+							$('#client-upload-status').html(
+								'<div class="notice notice-error"><p>Upload failed</p></div>'
+							);
+						}
+					});
+				});
+
+				// Sales upload
+				$('#sales-upload-form').on('submit', function(e) {
+					e.preventDefault();
+					const formData = new FormData(this);
+					formData.append('file', $('#sales_file')[0].files[0]);
+
+					$('#sales-upload-status').html('<span class="spinner is-active"></span> Uploading...');
+
+					$.ajax({
+						url: '<?= rest_url('jvb/v1/referrals/upload-sales') ?>',
+						method: 'POST',
+						data: formData,
+						processData: false,
+						contentType: false,
+						beforeSend: function(xhr) {
+							xhr.setRequestHeader('X-WP-Nonce', '<?= wp_create_nonce('wp_rest') ?>');
+						},
+						success: function(response) {
+							if (response.success) {
+								$('#sales-upload-status').html(
+									'<div class="notice notice-success"><p>' + response.message +
+									'<br>Consultations: ' + response.stats.consultations +
+									', Treatments: ' + response.stats.treatments +
+									', Skipped: ' + response.stats.skipped + '</p></div>'
+								);
+								$('#sales-upload-form')[0].reset();
+								loadReferralsTable();
+							} else {
+								$('#sales-upload-status').html(
+									'<div class="notice notice-error"><p>' + response.message + '</p></div>'
+								);
+							}
+						},
+						error: function(xhr) {
+							$('#sales-upload-status').html(
+								'<div class="notice notice-error"><p>Upload failed</p></div>'
+							);
+						}
+					});
+				});
+
+				// Load referrals table
+				function loadReferralsTable(page = 1) {
+					const status = $('#referral-status-filter').val();
+					const search = $('#referral-search').val();
+
+					$.ajax({
+						url: '<?= rest_url('jvb/v1/referrals/list') ?>',
+						method: 'GET',
+						data: {
+							page: page,
+							per_page: 20,
+							status: status,
+							search: search
+						},
+						beforeSend: function(xhr) {
+							xhr.setRequestHeader('X-WP-Nonce', '<?= wp_create_nonce('wp_rest') ?>');
+							$('#referrals-table-container').html('<div class="jvb-loading">Loading...</div>');
+						},
+						success: function(response) {
+							if (response.success) {
+								renderReferralsTable(response);
+							}
+						}
+					});
+				}
+
+				// Render table
+				function renderReferralsTable(data) {
+					let html = '<table class="referrals-table widefat">';
+					html += '<thead><tr>';
+					html += '<th>Referrer</th>';
+					html += '<th>Referee</th>';
+					html += '<th>Email</th>';
+					html += '<th>Status</th>';
+					html += '<th>Referred Date</th>';
+					html += '<th>Total Referrals</th>';
+					html += '<th>Actions</th>';
+					html += '</tr></thead><tbody>';
+
+					if (data.referrals.length === 0) {
+						html += '<tr><td colspan="7" style="text-align: center;">No referrals found</td></tr>';
+					} else {
+						data.referrals.forEach(function(ref) {
+							html += '<tr>';
+							html += '<td>' + (ref.referrer_name || 'Unknown') + '</td>';
+							html += '<td>' + (ref.referee_display_name || ref.referee_name) + '</td>';
+							html += '<td>' + (ref.referee_display_email || ref.referee_email) + '</td>';
+							html += '<td><span class="referral-status ' + ref.status + '">' + ref.status + '</span></td>';
+							html += '<td>' + new Date(ref.referred_at).toLocaleDateString() + '</td>';
+							html += '<td>' + (ref.referrer_total_referrals || 0) + '</td>';
+							html += '<td class="referral-actions">';
+
+							if (ref.status === 'pending') {
+								html += '<button class="button button-small mark-consulted" data-id="' + ref.id + '">Mark Consulted</button>';
+							}
+							if (ref.status !== 'treated') {
+								html += '<button class="button button-small mark-treated" data-id="' + ref.id + '">Mark Treated</button>';
+							}
+
+							html += '</td>';
+							html += '</tr>';
+						});
+					}
+
+					html += '</tbody></table>';
+
+					// Add pagination
+					if (data.total_pages > 1) {
+						html += '<div class="tablenav"><div class="tablenav-pages">';
+						for (let i = 1; i <= data.total_pages; i++) {
+							const active = i === data.page ? 'button-primary' : 'button';
+							html += '<button class="button ' + active + ' page-link" data-page="' + i + '">' + i + '</button> ';
+						}
+						html += '</div></div>';
+					}
+
+					$('#referrals-table-container').html(html);
+				}
+
+				// Event handlers for actions
+				$(document).on('click', '.mark-consulted', function() {
+					const id = $(this).data('id');
+					if (!confirm('Mark this referral as consulted? This will create the consultation reward.')) return;
+
+					$.ajax({
+						url: '<?= rest_url('jvb/v1/referrals/mark-consulted') ?>',
+						method: 'POST',
+						data: JSON.stringify({ referral_id: id }),
+						contentType: 'application/json',
+						beforeSend: function(xhr) {
+							xhr.setRequestHeader('X-WP-Nonce', '<?= wp_create_nonce('wp_rest') ?>');
+						},
+						success: function(response) {
+							if (response.success) {
+								alert(response.message);
+								loadReferralsTable();
+							} else {
+								alert('Error: ' + response.message);
+							}
+						}
+					});
+				});
+
+				$(document).on('click', '.mark-treated', function() {
+					const id = $(this).data('id');
+					if (!confirm('Mark this referral as treated? This will create rewards for both parties.')) return;
+
+					$.ajax({
+						url: '<?= rest_url('jvb/v1/referrals/mark-treated') ?>',
+						method: 'POST',
+						data: JSON.stringify({ referral_id: id }),
+						contentType: 'application/json',
+						beforeSend: function(xhr) {
+							xhr.setRequestHeader('X-WP-Nonce', '<?= wp_create_nonce('wp_rest') ?>');
+						},
+						success: function(response) {
+							if (response.success) {
+								alert(response.message);
+								loadReferralsTable();
+							} else {
+								alert('Error: ' + response.message);
+							}
+						}
+					});
+				});
+
+				$(document).on('click', '.page-link', function() {
+					loadReferralsTable($(this).data('page'));
+				});
+
+				$('#referral-status-filter, #refresh-table').on('change click', function() {
+					loadReferralsTable();
+				});
+
+				// Search with debounce
+				let searchTimeout;
+				$('#referral-search').on('keyup', function() {
+					clearTimeout(searchTimeout);
+					searchTimeout = setTimeout(function() {
+						loadReferralsTable();
+					}, 500);
+				});
+
+				// Initial load
+				loadReferralsTable();
+			});
+		</script>
+		<?php
+	}
+
+	protected function renderAdminHTML():string
+	{
+		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="referrer_reward_type">Reward Type</label>
+							</th>
+							<td>
+								<select name="referrer_reward_type" id="referrer_reward_type">
+									<option value="fixed" <?php selected($this->settings['referrer_reward_type'], 'fixed'); ?>>Fixed Amount</option>
+									<option value="percentage" <?php selected($this->settings['referrer_reward_type'], 'percentage'); ?>>Percentage</option>
+								</select>
+							</td>
+						</tr>
+						<tr>
+							<th scope="row">
+								<label for="referrer_reward_amount">Reward Amount</label>
+							</th>
+							<td>
+								<input type="number"
+									   name="referrer_reward_amount"
+									   id="referrer_reward_amount"
+									   value="<?= esc_attr($this->settings['referrer_reward_amount']) ?>"
+									   step="0.01"
+									   min="0">
+								<p class="description">Amount in dollars or percentage</p>
+							</td>
+						</tr>
+						<tr>
+							<th scope="row">
+								<label for="referrer_reward_applies_to">Applies To</label>
+							</th>
+							<td>
+								<select name="referrer_reward_applies_to" id="referrer_reward_applies_to">
+									<option value="per_user" <?php selected($this->settings['referrer_reward_applies_to'], 'per_user'); ?>>Per User Referred</option>
+									<option value="flat_total" <?php selected($this->settings['referrer_reward_applies_to'], 'flat_total'); ?>>Flat Total</option>
+								</select>
+							</td>
+						</tr>
+
+						<tr>
+							<th colspan="2"><h3>Referee (New User) Rewards</h3></th>
+						</tr>
+						<tr>
+							<th scope="row">
+								<label for="referee_reward_type">Reward Type</label>
+							</th>
+							<td>
+								<select name="referee_reward_type" id="referee_reward_type">
+									<option value="percentage" <?php selected($this->settings['referee_reward_type'], 'percentage'); ?>>Percentage</option>
+									<option value="fixed" <?php selected($this->settings['referee_reward_type'], 'fixed'); ?>>Fixed Amount</option>
+								</select>
+							</td>
+						</tr>
+						<tr>
+							<th scope="row">
+								<label for="referee_reward_amount">Reward Amount</label>
+							</th>
+							<td>
+								<input type="number"
+									   name="referee_reward_amount"
+									   id="referee_reward_amount"
+									   value="<?= esc_attr($this->settings['referee_reward_amount']) ?>"
+									   step="0.01"
+									   min="0">
+								<p class="description">Amount in dollars or percentage</p>
+							</td>
+						</tr>
+						<tr>
+							<th scope="row">
+								<label for="referee_reward_applies_to">Applies To</label>
+							</th>
+							<td>
+								<select name="referee_reward_applies_to" id="referee_reward_applies_to">
+									<option value="first_order" <?php selected($this->settings['referee_reward_applies_to'], 'first_order'); ?>>First Order Only</option>
+									<option value="all_orders" <?php selected($this->settings['referee_reward_applies_to'], 'all_orders'); ?>>All Orders</option>
+								</select>
+							</td>
+						</tr>
+						<tr>
+							<th scope="row">
+								<label for="<?= BASE ?>client_import_role">Client Import Role</label>
+							</th>
+							<td>
+								<?php
+								$selected_role = get_option(BASE . 'client_import_role', '');
+								$roles = wp_roles()->get_names();
+								?>
+								<select name="<?= BASE ?>client_import_role" id="<?= BASE ?>client_import_role">
+									<?php foreach ($roles as $role_value => $role_name): ?>
+										<option value="<?= esc_attr($role_value) ?>" <?php selected($selected_role, $role_value); ?>>
+											<?= esc_html($role_name) ?>
+										</option>
+									<?php endforeach; ?>
+								</select>
+								<p class="description">
+									Role assigned to users imported from Jane App client list.
+								</p>
+							</td>
+						</tr>
+					</table>
+				</div>
+
+				<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 = get_user_meta($user_id, BASE . 'referral_code', true);
+
+		if (!$referral_code) {
+			$referral_code = $this->getUserReferralCode($user_id);
+		}
+
+		$stats = $this->getUserStats($user_id);
+		$referrals = $this->getUserReferrals($user_id, ['limit' => 20]);
+
+		ob_start();
+		?>
+		<div class="referral-dashboard">
+			<div class="referral-header">
+				<h2>Your Referrals</h2>
+				<p>Share your code and earn rewards when your referrals complete their first treatment!</p>
+			</div>
+
+			<?php $this->getShareButtons($user_id); ?>
+
+			<!-- Referral Code Card -->
+			<div class="referral-code-card">
+				<h3>Your Referral Code</h3>
+				<div class="code-display">
+					<span class="code"><?= esc_html($referral_code) ?></span>
+					<button class="button copy-code" data-code="<?= esc_attr($referral_code) ?>">
+						Copy Code
+					</button>
+				</div>
+				<p class="share-link">
+					Share link: <input type="text" readonly value="<?= home_url('/?ref=' . $referral_code) ?>"
+									   onclick="this.select()" style="width: 100%; margin-top: 5px;" />
+				</p>
+			</div>
+			<form class="invite">
+				<?php
+				$meta = new MetaForm();
+				$field = [
+					'type'	=> 'repeater',
+					'label'	=> 'Invite Your Friends',
+					'fields'	=> [
+						'name'	=> [
+							'type'	=> 'text',
+							'label'	=> 'name',
+						],
+						'email'	=> [
+							'type'	=> 'email',
+							'label'	=> 'email',
+						]
+					]
+				];
+				$meta->render('invite', [], $field);
+				?>
+			</form>
+
+			<!-- Stats Grid -->
+			<div class="stats-grid">
+				<div class="stat-card">
+					<h4>Total Referrals</h4>
+					<span class="stat-number"><?= esc_html($stats['total_referrals'] ?? 0) ?></span>
+				</div>
+				<div class="stat-card">
+					<h4>Completed Treatments</h4>
+					<span class="stat-number"><?= esc_html($stats['treated_count'] ?? 0) ?></span>
+				</div>
+				<div class="stat-card">
+					<h4>Pending</h4>
+					<span class="stat-number"><?= esc_html($stats['pending_count'] ?? 0) ?></span>
+				</div>
+				<div class="stat-card highlight">
+					<h4>Available Rewards</h4>
+					<span class="stat-number">$<?= number_format($stats['available_rewards'] ?? 0, 2) ?></span>
+				</div>
+			</div>
+
+			<!-- Referrals List -->
+			<div class="referrals-list-card">
+				<h3>Your Referrals</h3>
+				<?php if (empty($referrals)): ?>
+					<p>You haven't referred anyone yet. Share your code to get started!</p>
+				<?php else: ?>
+					<table class="referrals-table">
+						<thead>
+						<tr>
+							<th>Name</th>
+							<th>Email</th>
+							<th>Status</th>
+							<th>Referred Date</th>
+						</tr>
+						</thead>
+						<tbody>
+						<?php foreach ($referrals as $ref): ?>
+							<tr>
+								<td><?= esc_html($ref->referee_name) ?></td>
+								<td><?= esc_html($ref->referee_email) ?></td>
+								<td><span class="status-badge <?= esc_attr($ref->status) ?>"><?= esc_html(ucfirst($ref->status)) ?></span></td>
+								<td><?= date('M j, Y', strtotime($ref->referred_at)) ?></td>
+							</tr>
+						<?php endforeach; ?>
+						</tbody>
+					</table>
+				<?php endif; ?>
+			</div>
+		</div>
+
+
+		<script>
+			jQuery(document).ready(function($) {
+				$('.copy-code').on('click', function() {
+					const code = $(this).data('code');
+					navigator.clipboard.writeText(code).then(function() {
+						alert('Code copied to clipboard!');
+					});
+				});
+			});
+		</script>
+		<?php
+		return ob_get_clean();
+	}
+
+	/**
+	 * Handle admin page form submission
+	 *
+	 * @param mixed $result Previous result
+	 * @param string $page_slug Current page slug
+	 * @param array $post_data POST data
+	 * @return array|null Result array or null if not our page
+	 */
+	public function handleAdminSubmission($result, string $page_slug, array $post_data): ?array
+	{
+		// Only handle our page
+		if ($page_slug !== BASE . 'referral-admin') {
+			return $result;
+		}
+
+		try {
+			// Save referral page
+			$page_id = isset($post_data[BASE . 'referral_page_id']) ? absint($post_data[BASE . 'referral_page_id']) : 0;
+			update_option(BASE . 'referral_page_id', $page_id);
+
+			// Save client import role
+			$import_role = sanitize_text_field($post_data[BASE . 'client_import_role'] ?? JVB_USER);
+			update_option(BASE . 'client_import_role', $import_role);
+
+			// Save reward settings
+			$settings = [
+				'referrer_reward_type' => sanitize_text_field($post_data['referrer_reward_type'] ?? 'fixed'),
+				'referrer_reward_amount' => floatval($post_data['referrer_reward_amount'] ?? 25.00),
+				'referrer_reward_applies_to' => sanitize_text_field($post_data['referrer_reward_applies_to'] ?? 'per_user'),
+				'referee_reward_type' => sanitize_text_field($post_data['referee_reward_type'] ?? 'percentage'),
+				'referee_reward_amount' => floatval($post_data['referee_reward_amount'] ?? 20),
+				'referee_reward_applies_to' => sanitize_text_field($post_data['referee_reward_applies_to'] ?? 'first_order')
+			];
+
+			update_option(BASE . 'referral_settings', $settings);
+
+			return [
+				'success' => true,
+				'message' => 'Referral settings saved successfully!'
+			];
+
+		} catch (\Exception $e) {
+			return [
+				'success' => false,
+				'message' => 'Failed to save settings: ' . $e->getMessage()
+			];
+		}
+	}
+
+	/**
+	 * Get formatted reward text for referee
+	 *
+	 * @param bool $full Include "off your first treatment" text
+	 * @return string
+	 */
+	public function getRewardText(bool $full = true): string
+	{
+		$reward_amount = $this->settings['referee_reward_amount'] ?? 20;
+		$reward_type = $this->settings['referee_reward_type'] ?? 'percentage';
+
+		$reward_text = $reward_type === 'percentage'
+			? $reward_amount . '% off'
+			: '$' . number_format($reward_amount, 2) . ' off';
+
+		if ($full) {
+			$reward_text .= ' your first treatment';
+		}
+
+		return $reward_text;
+	}
+
+	public function getShareButtons(int $user_id):void
+	{
+		$referral_code = $this->getUserReferralCode($user_id);
+		if (is_wp_error($referral_code)) {
+			return;
+		}
+
+		$share_url = $this->getShareURL($referral_code);
+		$referral_page_id = $this->getReferralPageId();
+
+		// SMS share text
+		$sms_text = urlencode("Check out " . get_bloginfo('name') . "! " . $share_url);
+
+		// Share message
+		$share_message = urlencode("I love " . get_bloginfo('name') . "! Thought you might want to check them out.");
+
+		?>
+		<nav class="share">
+			<h4>Quick Share</h4>
+			<ul class="share-buttons-grid">
+				<a href="mailto:?subject=<?php echo urlencode('Check out ' . get_bloginfo('name')); ?>&body=<?php echo urlencode($share_message . ' ' . $share_url); ?>"
+				   class="button" title="Email">
+					<?php echo jvbIcon('envelope'); ?>
+				</a>
+				<a href="sms:?&body=<?php echo $sms_text; ?>"
+				   class="button" title="Text">
+					<?php echo jvbIcon('chat'); ?>
+				</a>
+				<a href="https://www.facebook.com/sharer/sharer.php?u=<?php echo urlencode($share_url); ?>"
+				   target="_blank"
+				   rel="noopener noreferrer"
+				   class="button" title="Facebook">
+					<?php echo jvbIcon('facebook-logo'); ?>
+				</a>
+				<a href="https://twitter.com/intent/tweet?url=<?php echo urlencode($share_url); ?>&text=<?php echo urlencode($share_message); ?>"
+				   target="_blank"
+				   rel="noopener noreferrer"
+				   class="button" title="Twitter">
+					<?php echo jvbIcon('twitter-logo'); ?>
+				</a>
+				<a href="https://wa.me/?text=<?php echo $sms_text; ?>"
+				   target="_blank"
+				   rel="noopener noreferrer"
+				   class="button" title="WhatsApp">
+					<?php echo jvbIcon('whatsapp-logo'); ?>
+				</a>
+			</ul>
+		</nav>
+	<?php
+	}
 }
 

--
Gitblit v1.10.0