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