From 3baf3d2545ba6ece6b74a64c0def59bd0774cf54 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Wed, 10 Jun 2026 16:34:12 +0000
Subject: [PATCH] =Laid the groundwork for an improved DashboardManager.php setup. Have to put it aside so I can get the dang Northeh done though.

---
 assets/js/concise/Referral.js |  472 +++++++++++++++++++++++++---------------------------------
 1 files changed, 206 insertions(+), 266 deletions(-)

diff --git a/assets/js/concise/Referral.js b/assets/js/concise/Referral.js
index d3d1a4b..7764657 100644
--- a/assets/js/concise/Referral.js
+++ b/assets/js/concise/Referral.js
@@ -15,7 +15,6 @@
 
 		this.hasCopy = navigator.clipboard && navigator.clipboard.writeText;
 		this.initElements();
-		this.storesInited = false;
 		this.initStore();
 		this.initListeners();
 		this.checkForReferral();
@@ -27,20 +26,16 @@
 			checkCode: '.check-code-btn',
 			submit: '[type=submit]',
 			recentList: '.recent-referrals-list',
-			invite: 'form.invite',
-			adminList: '.items-list.referral',
-			dash: '.replace .referral-dashboard',
 			stats: {
 				codeUsed: '[data-stat="code_used"]',
 				consultations: '[data-stat="consultations"]',
 				treatments: '[data-stat="treatments"]',
 				rewards: '[data-stat="total_rewards"]'
 			},
-			list: '.referrals-list'
 		};
 
 		this.forms = this.container.querySelectorAll('form');
-		this.popup = new window.jvbPopup({
+		this.popup = window.jvbPopup.registerPopup({
 			toggle: this.toggle,
 			popup: this.container,
 			name: 'Referral Box',
@@ -55,49 +50,19 @@
 		this.tabs = null;
 
 		if (this.container.querySelector('nav.tabs')) {
-			this.tabs = new window.jvbTabs(this.container, {updateURL: false});
+			this.tabs = window.jvbTabs.registerTab(this.container, {updateURL: false});
 		}
 
 
 		this.ui = window.uiFromSelectors(this.selectors);
 
-		this.dashTabs = null;
-		if (this.ui.dash) {
-			this.dashTabs = new window.jvbTabs(this.ui.dash);
-		}
+
 
 		if (!this.hasCopy) {
 			document.querySelectorAll(this.selectors.copyBtn).forEach(btn => {
 				btn.remove();
 			});
 		}
-		this.formController = null;
-
-		if (this.ui.invite) {
-			this.formController = new window.jvbForm();
-			this.formController.registerForm(
-				this.ui.invite,
-				{
-					autosave: true,
-					endpoint: 'referrals',
-					formStatus: false,
-				}
-			);
-
-			this.formController.subscribe((event, data) => {
-				if (event === 'form-submit') {
-					data = data.fullData;
-					data.action = 'invite';
-					window.jvbQueue.addToQueue(
-						{
-							endpoint: 'referrals',
-							data: data,
-							title: 'Submitting invitations',
-						}
-					);
-				}
-			});
-		}
 	}
 
 	initStore() {
@@ -113,7 +78,7 @@
 					endpoint: 'referrals/stats',
 					TTL: 5 * 60 * 1000,
 					showLoading: false,
-					delayFetch: false,
+					delayFetch: true,
 					filters: {
 						type: 'dashboard',
 						user: window.auth.getUser()
@@ -126,7 +91,7 @@
 					endpoint: 'referrals',
 					TTL: 10 * 60 * 1000,
 					showLoading: false,
-					delayFetch: false,
+					delayFetch: true,
 					filters: {
 						user: window.auth.getUser(),
 						status: 'all',
@@ -147,27 +112,9 @@
 		if (this.listStore) {
 			this.listStore.subscribe(this.handleListEvent.bind(this));
 		}
-
-		if (this.ui.dash) {
-			this.initViewController();
-		}
 	}
 
-	initViewController() {
-		if (!this.listStore || !this.ui.adminList) return;
 
-		this.view = new window.jvbViews(this.ui.adminList, this.listStore);
-		this.view.subscribe((event, data) => {
-			switch(event) {
-				case 'item-action':
-					this.handleItemAction(data);
-					break;
-				case 'bulk-action':
-					this.handleBulkAction(data);
-					break;
-			}
-		});
-	}
 
 	initListeners() {
 		this.clickHandler = this.handleClick.bind(this);
@@ -254,82 +201,7 @@
 		}
 	}
 
-	/**
-	 * Handle item actions (remove, resend)
-	 */
-	handleItemAction(data) {
-		const { action, itemId } = data;
 
-		switch(action) {
-			case 'remove':
-				this.removeReferral(itemId);
-				break;
-			case 'resend':
-				this.resendInvite(itemId);
-				break;
-		}
-	}
-
-	/**
-	 * Remove referral from list
-	 */
-	async removeReferral(id) {
-		if (!confirm('Remove this referral from your list?')) return;
-
-		try {
-			const response = await fetch(`${jvbSettings.api}referrals`, {
-				method: 'POST',
-				headers: {
-					'Content-Type': 'application/json',
-					'X-WP-Nonce': window.auth.getNonce()
-				},
-				body: JSON.stringify({
-					action: 'remove',
-					referral_id: id
-				})
-			});
-
-			const result = await response.json();
-
-			if (result.success) {
-				// Refresh DataStore
-				if (this.listStore) this.listStore.fetch();
-				if (this.statsStore) this.statsStore.fetch();
-				this.a11y?.announce('Referral removed');
-			}
-		} catch (error) {
-			console.error('Error removing referral:', error);
-		}
-	}
-
-	/**
-	 * Resend invite email
-	 */
-	async resendInvite(id) {
-		try {
-			const response = await fetch(`${jvbSettings.api}referrals`, {
-				method: 'POST',
-				headers: {
-					'Content-Type': 'application/json',
-					'X-WP-Nonce': window.auth.getNonce()
-				},
-				body: JSON.stringify({
-					action: 'resend',
-					referral_id: id
-				})
-			});
-
-			const result = await response.json();
-
-			if (result.success) {
-				this.a11y?.announce('Invitation resent');
-			} else {
-				alert(result.message || 'Cannot resend yet. Wait 7 days between invites.');
-			}
-		} catch (error) {
-			console.error('Error resending invite:', error);
-		}
-	}
 
 
 	handleClick(e) {
@@ -359,65 +231,203 @@
 		// Try clipboard API first
 		if (this.hasCopy) {
 			navigator.clipboard.writeText(text).then(() => {
-				this.showCopySuccess(button);
-			}).catch(() => {
-				// Fallback to selection
-				this.selectText(codeElement);
-				this.showCopyFallback(button);
+				button.classList.toggle('success');
+				setTimeout(() => {
+					button.classList.remove('success');
+				}, 1500);
 			});
 		}
 	}
 
+
 	/**
-	 * Select text in element
+	 * Handle error response with field-specific feedback
 	 */
-	selectText(element) {
-		if (window.getSelection && document.createRange) {
-			const selection = window.getSelection();
-			const range = document.createRange();
-			range.selectNodeContents(element);
-			selection.removeAllRanges();
-			selection.addRange(range);
-		} else if (document.body.createTextRange) {
-			// IE fallback
-			const range = document.body.createTextRange();
-			range.moveToElementText(element);
-			range.select();
+	handleError(form, result) {
+		const { message, code, field } = result;
+
+		// If there's a specific field, highlight it
+		if (field) {
+			this.showFieldError(form, field, message);
+		} else {
+			// Show general form error using FormController pattern
+			this.showFormStatus(form, 'error', message || 'Something went wrong. Please try again.');
+		}
+
+		// Handle specific error codes
+		switch(code) {
+			case 'duplicate_email':
+				// Could add additional UI feedback
+				break;
+			case 'invalid_code':
+				// Unlock the referral code field so user can correct it
+				const codeInput = form.querySelector('[name="referral_code"]');
+				if (codeInput) {
+					codeInput.readOnly = false;
+					codeInput.focus();
+				}
+				break;
+			case 'turnstile_failed':
+				// Refresh Turnstile widget if available
+				if (window.turnstile && form.querySelector('.cf-turnstile')) {
+					window.turnstile.reset();
+				}
+				break;
 		}
 	}
 
 	/**
-	 * Show copy success feedback
+	 * Show error for specific field
 	 */
-	showCopySuccess(button) {
-		const originalHTML = button.innerHTML;
-		button.innerHTML = window.jvbIcon('check', {size: 16}) + ' Copied!';
-		button.classList.add('success');
+	showFieldError(form, fieldName, message) {
+		// Find the field wrapper (handles both direct names and referral_ prefixed names)
+		let fieldWrapper = form.querySelector(`.field[data-field="${fieldName}"]`);
+		if (!fieldWrapper) {
+			fieldWrapper = form.querySelector(`.field[data-field="referral_${fieldName}"]`);
+		}
 
-		setTimeout(() => {
-			button.innerHTML = originalHTML;
-			button.classList.remove('success');
-		}, 2000);
+		if (!fieldWrapper) {
+			// If no field wrapper found, show as general form error
+			this.showFormStatus(form, 'error', message);
+			return;
+		}
+
+		const input = fieldWrapper.querySelector('input, textarea, select');
+		const validationMessage = fieldWrapper.querySelector('.validation-message');
+		const errorIcon = fieldWrapper.querySelector('.validation-icon.error');
+		const successIcon = fieldWrapper.querySelector('.validation-icon.success');
+
+		if (!input) {
+			this.showFormStatus(form, 'error', message);
+			return;
+		}
+
+		// Apply error state (following FormController pattern)
+		fieldWrapper.classList.remove('has-success');
+		fieldWrapper.classList.add('has-error');
+		input.classList.add('error');
+		input.setAttribute('aria-invalid', 'true');
+
+		// Show error icon, hide success icon
+		if (errorIcon) errorIcon.hidden = false;
+		if (successIcon) successIcon.hidden = true;
+
+		// Show error message
+		if (validationMessage) {
+			validationMessage.textContent = message;
+			validationMessage.hidden = false;
+		}
+
+		// Focus the problematic field
+		input.focus();
+
+		// Announce to screen readers
+		this.a11y?.announce(`Error in ${fieldName}: ${message}`);
+	}
+
+	showFormStatus(form, status, message = '') {
+		const statusWrap = form.querySelector('.fstatus');
+		if (!statusWrap) {
+			console.warn('No .fstatus element found in form');
+			return;
+		}
+
+		statusWrap.hidden = false;
+		const statusElement = statusWrap.querySelector('.message');
+
+		// Clear previous state
+		statusWrap.querySelector('.icon')?.remove();
+		statusWrap.querySelector('.actions')?.remove();
+
+		// Status messages
+		const messages = {
+			'saving': 'Sending...',
+			'submitted': 'Sent successfully!',
+			'error': 'Something went wrong',
+			'checking': 'Checking code...'
+		};
+
+		// Status icons (using window.getIcon like FormController)
+		const icons = {
+			'submitted': 'check-circle',
+			'error': 'close-circle',
+			'checking': 'loading'
+		};
+
+		// Add icon if available
+		if (icons[status] && window.getIcon) {
+			const icon = window.getIcon(icons[status]);
+			if (icon) {
+				statusWrap.prepend(icon);
+			}
+		}
+
+		// Set message
+		if (statusElement) {
+			statusElement.textContent = message || messages[status] || status;
+		}
+
+		// Add loading class for pending states
+		statusWrap.classList.toggle('loading', ['saving', 'checking'].includes(status));
+
+		// Auto-hide success messages
+		if (status === 'submitted') {
+			setTimeout(() => statusWrap.hidden = true, 3000);
+		}
+
+		// Announce to screen readers
+		if (this.a11y) {
+			this.a11y.announce(message || messages[status] || status);
+		}
 	}
 
 	/**
-	 * Show fallback message
+	 * Clear all form errors
 	 */
-	showCopyFallback(button) {
-		const originalHTML = button.innerHTML;
-		button.innerHTML = '✓ Selected - Press Ctrl+C';
-		button.classList.add('selected');
+	clearFormErrors(form) {
+		// Clear field-level errors
+		form.querySelectorAll('.field.has-error, .field.has-success').forEach(fieldWrapper => {
+			this.clearFieldValidation(fieldWrapper);
+		});
 
-		setTimeout(() => {
-			button.innerHTML = originalHTML;
-			button.classList.remove('selected');
-		}, 3000);
+		// Hide form status
+		const statusWrap = form.querySelector('.fstatus');
+		if (statusWrap) {
+			statusWrap.hidden = true;
+		}
+	}
+
+	clearFieldValidation(fieldWrapper) {
+		if (!fieldWrapper) return;
+
+		const input = fieldWrapper.querySelector('input, textarea, select');
+		const validationMessage = fieldWrapper.querySelector('.validation-message');
+		const validationIcons = fieldWrapper.querySelectorAll('.validation-icon');
+
+		// Remove classes
+		fieldWrapper.classList.remove('has-error', 'has-success');
+		if (input) {
+			input.classList.remove('error');
+			input.removeAttribute('aria-invalid');
+		}
+
+		// Hide icons and messages
+		validationIcons.forEach(icon => icon.hidden = true);
+		if (validationMessage) {
+			validationMessage.hidden = true;
+			validationMessage.textContent = '';
+		}
 	}
 
 	handleInput(e) {
 		if (e.target.id === 'referral_code' || e.target.name === 'referral_code') {
 			e.target.value = e.target.value.toUpperCase();
 		}
+		// Clear field error when user types
+		const fieldWrapper = e.target.closest('.field');
+		if (fieldWrapper && fieldWrapper.classList.contains('has-error')) {
+			this.clearFieldValidation(fieldWrapper);
+		}
 	}
 
 	/**
@@ -585,63 +595,6 @@
 	}
 
 	/**
-	 * Load user stats
-	 */
-	async loadStats() {
-		const statsContainer = this.container.querySelector('.stats-summary');
-		if (!statsContainer) return;
-
-		try {
-			const response = await fetch(`${jvbSettings.api}referrals/my-stats?user=${window.auth.getUser()}`, {
-				headers: { 'X-WP-Nonce': window.auth.getNonce() }
-			});
-
-			const data = await response.json();
-			if (data.success && data.stats) {
-				this.updateStats(data.stats);
-			}
-		} catch (error) {
-			console.error('Error loading stats:', error);
-		}
-	}
-
-	async loadSidebarStats() {
-		try {
-			const response = await fetch(
-				`${jvbSettings.api}referrals/stats?user=${window.auth.getUser()}&type=quick`,
-				{ headers: { 'X-WP-Nonce': window.auth.getNonce() } }
-			);
-
-			const data = await response.json();
-			if (data.success && data.stats) {
-				this.updateSidebarStats(data.stats);
-			}
-		} catch (error) {
-			console.error('Error loading sidebar stats:', error);
-		}
-	}
-
-
-	/**
-	 * Update stats display
-	 */
-	updateStats(stats) {
-		const elements = {
-			total: this.container.querySelector('[data-stat="total"]'),
-			treated: this.container.querySelector('[data-stat="treated"]'),
-			pending: this.container.querySelector('[data-stat="pending"]'),
-			rewards: this.container.querySelector('[data-stat="rewards"]')
-		};
-
-		if (elements.total) elements.total.textContent = stats.total_referrals || 0;
-		if (elements.treated) elements.treated.textContent = stats.treated_count || 0;
-		if (elements.pending) elements.pending.textContent = stats.pending_count || 0;
-		if (elements.rewards) {
-			elements.rewards.textContent = '$' + parseFloat(stats.available_rewards || 0).toFixed(2);
-		}
-	}
-
-	/**
 	 * Render recent referrals list
 	 */
 	renderRecentReferrals() {
@@ -664,22 +617,6 @@
 	}
 
 	/**
-	 * Format date nicely
-	 */
-	formatDate(dateString) {
-		const date = new Date(dateString);
-		const now = new Date();
-		const diffTime = Math.abs(now - date);
-		const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
-
-		if (diffDays === 0) return 'Today';
-		if (diffDays === 1) return 'Yesterday';
-		if (diffDays < 7) return `${diffDays} days ago`;
-
-		return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
-	}
-
-	/**
 	 * Handle form submission
 	 */
 	async handleFormSubmit(event) {
@@ -688,6 +625,8 @@
 		const form = event.target;
 		const formData = new FormData(form);
 
+		// Clear any existing errors
+		this.clearFormErrors(form);
 		this.setFormLoading(true, form);
 
 		try {
@@ -695,32 +634,45 @@
 
 			if (form.id === 'referral-code-form') {
 				// Registration with referral code - goes to LoginRoutes
-				const data = {
+				let data = {
 					name: formData.get('referral_name'),
 					email: formData.get('referral_email'),
 					referral_code: formData.get('referral_code')
 				};
 
+				const turnstileInput = form.querySelector('input[name="cf-turnstile-response"]');
+				if (turnstileInput && turnstileInput.value) {
+					data['cf-turnstile-response'] = turnstileInput.value;
+				}
+
 				if (!data.name || !data.email || !data.referral_code) {
 					result.message = 'Please fill in all fields';
 				} else {
 					result = await this.makeRequest('auth/register', data); // UPDATED endpoint
 				}
 			} else if (form.id === 'login-form') {
-				const data = {
+				let data = {
 					type: 'login',
-					email: formData.get('login_email'),
+					user_email: formData.get('login_email'),
 					context: {
 						redirect_to: window.location.href + '?seeReferral=1'
 					}
 				};
-				result = await this.makeRequest('magic', data);
+				const turnstileInput = form.querySelector('input[name="cf-turnstile-response"]');
+				if (turnstileInput && turnstileInput.value) {
+					data['cf-turnstile-response'] = turnstileInput.value;
+				}
+				if (!data['user_email']) {
+					result.message = 'Please fill in your email';
+				} else {
+					result = await this.makeRequest('auth/magic', data);
+				}
 			}
 
 			if (result.success) {
 				this.handleSuccess(form, result);
 			} else {
-				this.showFormMessage(form, result.message || 'Something went wrong. Please try again.', 'error');
+				this.handleError(form, result);
 			}
 		} catch (error) {
 			console.error('Error submitting form:', error);
@@ -732,7 +684,7 @@
 
 	async makeRequest(endpoint, data) {
 		const validEndpoints = [
-			'magic',
+			'auth/magic',
 			'auth/register'
 		];
 
@@ -740,16 +692,11 @@
 			return { success: false, message: 'Invalid endpoint' };
 		}
 
-		const response = await fetch(`${jvbSettings.api}${endpoint}`, {
+		const response = await window.auth.fetch(`${jvbSettings.api}${endpoint}`, {
 			method: 'POST',
-			headers: {
-				'Content-Type': 'application/json',
-				'X-WP-Nonce': window.auth.getNonce(),
-			},
 			body: JSON.stringify(data)
 		});
 
-		// Add error handling to see the actual response
 		if (!response.ok) {
 			const errorText = await response.text();
 			console.error('Error response:', response.status, errorText);
@@ -814,20 +761,11 @@
 	 * Set form loading state
 	 */
 	setFormLoading(loading, form) {
-		const inputs = form.querySelectorAll('input, button');
+		const inputs = form.querySelectorAll('input, button, textarea, select');
 		inputs.forEach(input => input.disabled = loading);
 
-		const status = form.querySelector('.status');
-		if (status) {
-			status.classList.toggle('loading', loading);
-
-			if (loading) {
-				status.hidden = false;
-				const message = status.querySelector('.message');
-				if (message) {
-					message.textContent = 'Sending...';
-				}
-			}
+		if (loading) {
+			this.showFormStatus(form, 'saving');
 		}
 	}
 
@@ -841,6 +779,8 @@
 		});
 		this.container.dispatchEvent(event);
 	}
+
+
 }
 
 document.addEventListener('DOMContentLoaded', async function () {

--
Gitblit v1.10.0