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 |  997 +++++++++++++++++++++++++++++++++++++++++++++++++---------
 1 files changed, 837 insertions(+), 160 deletions(-)

diff --git a/inc/managers/ReferralManager.php b/inc/managers/ReferralManager.php
index 3ac4511..3f000e4 100644
--- a/inc/managers/ReferralManager.php
+++ b/inc/managers/ReferralManager.php
@@ -44,8 +44,8 @@
 		global $wpdb;
 		$this->wpdb = $wpdb;
 		$this->cache = CacheManager::for('referrals', WEEK_IN_SECONDS);
-		$this->referrals_table = BASE . 'referrals';
-		$this->rewards_table = BASE . 'referral_rewards';
+		$this->referrals_table = $wpdb->prefix . BASE . 'referrals';
+		$this->rewards_table = $wpdb->prefix . BASE . 'referral_rewards';
 		$this->magic_link = new MagicLinkManager();
 
 		$this->referralPage = $this->getReferralPageId();
@@ -73,9 +73,6 @@
 		// Schedule cron jobs for reports
 		$this->registerCronJobs();
 
-		// Register admin subpage
-//		add_filter('jvbAdminSubpages', [$this, 'addSubpage'], 10, 1);
-
 		// Add admin bar label for referral page
 		add_action('admin_bar_menu', [$this, 'addReferralPageLabel'], 999);
 
@@ -87,6 +84,8 @@
 
 		// 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
@@ -168,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');
 		}
@@ -982,85 +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();
+
 		$meta = new MetaForm();
-		$codeForm = '<form id="referral-code-form">
-					'.jvbFormStatus().$meta->return('referral_name', null, [
-						'required'	=> true,
-						'type'		=> 'text',
-						'label'		=> 'Your Name',
-						'placeholder'=> 'Mister Meeseeks',
-						'autocomplete'=>'name'
-					]).
-					$meta->return('referral_email', null, [
-						'required'	=> true,
-						'type'		=> 'email',
-						'label'		=> 'Your Email',
-						'placeholder'=> 'look@me.com',
-						'autocomplete'=> 'email'
-					]).
-					$meta->return('referral_code', null, [
-						'required'	=> true,
-						'type'		=> 'text',
-						'label'		=> 'Referral Code',
-						'pattern'	=> '[A-Za-z0-9]+',
-						'maxLength'	=> 20,
-						'autocomplete'=>'off'
-					]).'
-					<button type="submit">
-						Get Started
-					</button>
+		$reward_text = $this->getRewardText(true);
 
-					<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>';
+		// 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, ' ') : '';
+		}
 
-		$loginForm = '<form id ="login-form">
-		'.jvbFormStatus().$meta->return('login_email', null, [
+		$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>';
+
+		$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>
+	'.$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);
@@ -1106,86 +1136,73 @@
 		return ob_get_clean();
 	}
 
-	public function getLoggedInReferral(int $user_id):string
+	/**
+	 * Display referral sidebar for logged-in users
+	 */
+	public function getLoggedInReferral(int $user_id): string
 	{
-		// Logged-in user widget
-		$referral_code = get_user_meta($user_id, BASE . 'referral_code', true);
-
-		// Generate code if user doesn't have one
-		if (empty($referral_code)) {
-			$referral_code = $this->getUserReferralCode($user_id);
-			if (is_wp_error($referral_code)) {
-				return '';
-			}
+		$referral_code = $this->getUserReferralCode($user_id);
+		if (is_wp_error($referral_code)) {
+			return '';
 		}
 
 		$share_url = $this->getShareURL($referral_code);
-
 		ob_start();
 		?>
-			<header>
-				<h3>Share the ♡</h3>
-				<p>Invite your friends.</p>
-				<p>Earn rewards when they book!</p>
-			</header>
+		<header>
+			<h3>Share the ♡</h3>
+			<p>Invite friends. Earn rewards.</p>
+		</header>
 
-		<div class="row even share-buttons">
-			<a href="mailto:?subject=<?php echo urlencode('Check out ' . get_bloginfo('name')); ?>&body=<?php echo urlencode('I thought you might be interested: ' . $share_url); ?>"
-			   class="share-btn email-share">
-				<?php echo jvbIcon('envelope', ['size' => 20]); ?>
-				Email
-			</a>
-			<a href="https://www.facebook.com/sharer/sharer.php?u=<?php echo urlencode($share_url); ?>"
-			   target="_blank"
-			   rel="noopener noreferrer"
-			   class="share-btn facebook-share">
-				<?php echo jvbIcon('facebook-logo', ['size' => 20]); ?>
-				Facebook
-			</a>
-			<a href="https://twitter.com/intent/tweet?url=<?php echo urlencode($share_url); ?>&text=<?php echo urlencode('Check this out!'); ?>"
-			   target="_blank"
-			   rel="noopener noreferrer"
-			   class="share-btn twitter-share">
-				<?php echo jvbIcon('twitter-logo', ['size' => 20]); ?>
-				Twitter
-			</a>
-		</div>
+		<?php $this->getShareButtons($user_id); ?>
 
+		<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();
@@ -1563,18 +1580,26 @@
 	 * @param array $subpages
 	 * @return array
 	 */
-	public function addSubpage(array $subpages): array
+	public static function addSubpage():void
 	{
-		$subpages[] = [
-			'page_title' => 'Referral Settings',
+		$subpage = [
+			'page_title' => 'Referral System',
 			'menu_title' => 'Referrals',
 			'capability' => 'manage_options',
-			'menu_slug'  => 'jvb-referrals',
-			'callback'   => [$this, 'renderAdminPage'],
-			'icon'       => 'users',
+			'menu_slug' => BASE . 'referral-admin',
+			'callback' => [self::class, 'renderAdminPageStatic']
 		];
+		AdminPages::addSubPage(BASE.'referral-admin', $subpage);
+	}
 
-		return $subpages;
+	/**
+	 * Static wrapper for renderAdminPage
+	 * Called by WordPress when admin page is rendered
+	 */
+	public static function renderAdminPageStatic(): void
+	{
+		// Get the properly initialized instance from JVB singleton
+		JVB()->referrals()->renderAdminPage();
 	}
 
 	/**
@@ -1631,25 +1656,389 @@
 	 */
 	public function renderAdminPage(): void
 	{
-		// Handle form submission
-		if (isset($_POST['submit']) && check_admin_referer(BASE . 'referral_settings_nonce')) {
-			update_option(BASE . 'referral_page_id', absint($_POST[BASE . 'referral_page_id'] ?? 0));
+		?>
+		<div class="wrap jvb-admin-wrap">
+			<h1>Referral System Management</h1>
 
-			$reward_settings = [
-				'referrer_reward_applies_to' => sanitize_text_field($_POST['referrer_reward_applies_to'] ?? 'per_user'),
-				'referrer_reward_amount' => floatval($_POST['referrer_reward_amount'] ?? 25.00),
-				'referrer_reward_type' => sanitize_text_field($_POST['referrer_reward_type'] ?? 'fixed'),
-				'referee_reward_type' => sanitize_text_field($_POST['referee_reward_type'] ?? 'percentage'),
-				'referee_reward_amount' => floatval($_POST['referee_reward_amount'] ?? 20),
-				'referee_reward_applies_to' => sanitize_text_field($_POST['referee_reward_applies_to'] ?? 'first_order'),
-			];
+			<!-- CSV Upload Section -->
+			<div class="card">
+				<h2>Import Data from Jane App</h2>
+				<p>Upload your exported CSV files from Jane App to sync client and sales data.</p>
 
-			update_option(BASE . 'referral_reward_settings', $this->sanitizeRewardSettings($reward_settings));
+				<div class="jvb-upload-section" style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 20px;">
+					<!-- Client List Upload -->
+					<div class="jvb-upload-box">
+						<h3>Client List</h3>
+						<form id="client-upload-form" enctype="multipart/form-data">
+							<input type="file"
+								   name="client_file"
+								   id="client_file"
+								   accept=".csv"
+								   required />
+							<button type="submit" class="button button-primary" style="margin-top: 10px;">
+								Upload Clients
+							</button>
+						</form>
+						<div id="client-upload-status" style="margin-top: 10px;"></div>
+					</div>
 
-			echo '<div class="notice notice-success is-dismissible"><p>Settings saved successfully.</p></div>';
-		}
+					<!-- Sales Export Upload -->
+					<div class="jvb-upload-box">
+						<h3>Sales Export</h3>
+						<form id="sales-upload-form" enctype="multipart/form-data">
+							<input type="file"
+								   name="sales_file"
+								   id="sales_file"
+								   accept=".csv"
+								   required />
+							<button type="submit" class="button button-primary" style="margin-top: 10px;">
+								Upload Sales
+							</button>
+						</form>
+						<div id="sales-upload-status" style="margin-top: 10px;"></div>
+					</div>
+				</div>
+			</div>
 
-		echo $this->renderAdminHTML();
+			<!-- 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
@@ -1660,7 +2049,7 @@
 			<h1>Referral Settings</h1>
 
 			<form method="post" action="">
-				<?php wp_nonce_field(BASE . 'referral_settings_nonce'); ?>
+				<?php wp_nonce_field(BASE . 'admin_page_nonce'); ?>
 
 				<div class="card">
 					<h2>Referral Page</h2>
@@ -1673,6 +2062,9 @@
 							</th>
 							<td>
 								<?php
+								if (!$this->referralPage) {
+									$this->referralPage = $this->getReferralPageId();
+								}
 								wp_dropdown_pages([
 									'name' => BASE . 'referral_page_id',
 									'id' => BASE . 'referral_page_id',
@@ -1772,6 +2164,27 @@
 								</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>
 
@@ -1789,38 +2202,62 @@
 	/**
 	 * Render referral statistics
 	 */
-	protected function renderReferralStats(bool $wrapCard = false):string
+	protected function renderReferralStats(bool $wrapCard = false): string
 	{
-		ob_start();
-		global $wpdb;
+		global $wpdb; // Use fresh global instead of stored reference
 
-		$total_referrals = $wpdb->get_var("SELECT COUNT(*) FROM {$this->referrals_table}");
-		$pending_referrals = $wpdb->get_var("SELECT COUNT(*) FROM {$this->referrals_table} WHERE status = 'pending'");
-		$treated_referrals = $wpdb->get_var("SELECT COUNT(*) FROM {$this->referrals_table} WHERE status = 'treated'");
+		ob_start();
+
+		// Get fresh table name references
+		$referrals_table = $wpdb->prefix . BASE . 'referrals';
+
+		// Use proper WordPress prepare even for COUNT
+		$total_referrals = $wpdb->get_var(
+			$wpdb->prepare(
+				"SELECT COUNT(*) FROM `{$referrals_table}` WHERE 1=%d",
+				1
+			)
+		);
+
+		$pending_referrals = $wpdb->get_var(
+			$wpdb->prepare(
+				"SELECT COUNT(*) FROM `{$referrals_table}` WHERE status = %s",
+				'pending'
+			)
+		);
+
+		$treated_referrals = $wpdb->get_var(
+			$wpdb->prepare(
+				"SELECT COUNT(*) FROM `{$referrals_table}` WHERE status = %s",
+				'treated'
+			)
+		);
 
 		?>
 		<table class="widefat">
 			<tr>
 				<th>Total Referrals</th>
-				<td><?= esc_html($total_referrals) ?></td>
+				<td><?= esc_html($total_referrals ?? 0) ?></td>
 			</tr>
 			<tr>
 				<th>Pending</th>
-				<td><?= esc_html($pending_referrals) ?></td>
+				<td><?= esc_html($pending_referrals ?? 0) ?></td>
 			</tr>
 			<tr>
 				<th>Treated</th>
-				<td><?= esc_html($treated_referrals) ?></td>
+				<td><?= esc_html($treated_referrals ?? 0) ?></td>
 			</tr>
 		</table>
 		<?php
 		$table = ob_get_clean();
+
 		if ($wrapCard) {
 			$table = '<div class="card">
-				<h2>Referral Statistics</h2>
-				'.$table.'
-			</div>';
+            <h2>Referral Statistics</h2>
+            ' . $table . '
+        </div>';
 		}
+
 		return $table;
 	}
 
@@ -1836,6 +2273,9 @@
 		}
 
 		if (!$this->referralPage) {
+			$this->referralPage = $this->getReferralPageId();
+		}
+		if (!$this->referralPage) {
 			return;
 		}
 
@@ -1875,7 +2315,9 @@
 		if ('post.php' !== $pagenow || !$post) {
 			return;
 		}
-
+		if (!$this->referralPage) {
+			$this->referralPage = $this->getReferralPageId();
+		}
 		if ($post->ID === $this->referralPage) {
 			echo '<div class="notice notice-info">';
 			echo '<p>' . __('This page is designated as the <strong>Referral Page</strong>.', 'jvbase') . '</p>';
@@ -1883,18 +2325,253 @@
 		}
 	}
 
-	public function renderDashPage(string $content, string $page):string
+	public function renderDashPage(string $content, string $page): string
 	{
-		if ($page !== 'referrals') {
+		if ($page !== 'Referrals') {
 			return $content;
 		}
-		$out = '';
-		if (current_user_can('manage_options')) {
-			$out .= $this->renderAdminHTML();
-		} else {
-			$out .= $this->renderReferralStats(true);
+
+		// Regular users get their referral dashboard
+		$user_id = get_current_user_id();
+		$referral_code = get_user_meta($user_id, BASE . 'referral_code', true);
+
+		if (!$referral_code) {
+			$referral_code = $this->getUserReferralCode($user_id);
 		}
-		return ($out === '') ? $content : '<form id="referrals" class="col" data-save="referrals">'.$out.'</form>';
+
+		$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